问题描述:在windows窗体中加入ActiveXControl控件例如axCanlendar显式释放Control控件资源清除对它引用后该Control控件仍然无法被GC所回收
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
问题重现:在Form中加入个axCanlendarControl控件及个Button按钮单击代码如下:
private void button1_Click(object sender, EventArgs e)
{
this.Controls.Remove(this.axCalendar1);
WeakReference wr = WeakReference(this.axCalendar1, false);
axCalendar1.Dispose;
//Marshal.ReleaseComObject(axCalendar1.GetOcx);
axCalendar1 = null;
i = 0;
while (wr.IsAlive)
{
i;
GC.Collect;
MessageBox.Show(i.);
}
}
--------------------------------------------------------------------------------------------------------------------------
问题分析:.netGC是通过引用来判断某资源是否可以回收对于非托管资源我们般需要显式其Dispose或close等来释放非托管资源但是当我们显式axCanlendarDispose释放资源并且将其设为null后GC仍然无法将其回收介绍说明它依然被某变量引用着那么到底是什么引用着它呢我们利用windbg可以轻松地查看它根
0:009> !dumpheap -type AxMSACAL.AxCalendar
Loading the heap objects o our cache.
Address MT Size
012c94f8 00978424 336 2 AxMSACAL.AxCalendar
0:009> !gcroot 012c94f8
ESP:12e5fc:Root: 012c8bd8(WindowsApplication4.Form1)->
012cbc78(.Windows.Forms.LayoutEventArgs)->
012c94f8(AxMSACAL.AxCalendar)
可以看到axCalendar被LayoutEventArgs引用着LayoutEventArgs是什么类型而这个引用又是什么被添加呢?带着这样疑问我们打开reflector找到LayoutEventArgs类型进行分析后发现.Windows.Forms.Control.PerformLayout会使用LayoutEventArgs类型PerfomLayout共有 3种形式:PerformLayout, PerformLayout(control, ), PerformLayout(LayoutEventArgs)
[EditorBrowsable(EditorBrowsableState.Advanced)]
public void PerformLayout
{
(this.cachedLayoutEventArgs != null)
{
this.PerformLayout(this.cachedLayoutEventArgs);
this.cachedLayoutEventArgs = null;
this.SetState2(0x40, false);
}
{
this.PerformLayout(null, null);
}
}
同时PerformLayout会被ResumeLayout可见我们axCalendar被FormcachedLayoutEventArgs引用到了分析这 3个PerformLayout(control, )通过control和构造个LayoutEventArgs后PerformLayout(LayoutEventArgs)而PerformLayout(LayoutEventArgs)中有如下代码段:
(this.layoutSuspendCount > 0)
{
this.SetState(0x200, true);
((this.cachedLayoutEventArgs null) || (this.GetState2(0x40) && (args != null)))
{
this.cachedLayoutEventArgs = args;
(this.GetState2(0x40))
{
this.SetState2(0x40, false);
}
}
}
所以我们可以利用以下 3句代码来将cachedLayoutEventArgs设为null
this.SuspendLayout;
this.PerformLayout(null, null);
this.ResumeLayout(false);
重新运行可是axCanlendar仍然没有被释放于是利用windbg再次查看axCanlendar根
Root: 012c36ac(.Windows.Forms.Application+ThreadContext)->
012d0990(WindowsApplication4.Form1)->
012d0ae0(.Windows.Forms.PropertyStore)->
012f885c(.Windows.Forms.PropertyStore+ObjectEntry)->
012f76f0(.Windows.Forms.AxHost+AxContainer)->
012f781c(.Collections.Hashtable)->
012f7854(.Collections.Hashtable+bucket)->
012d2500(AxMSACAL.AxCalendar)
现在axCanlendar又被PropertyStore给引用到了PropertyStore类中有个类型静态变量currentKey通过CreateKey来增加key值Contorl类中有个PropertyStrore类型变量化时通过CreateKey来标记各种Form中各种对象如ControlsCollections这样可以通过ProertyStoreGetObject( key)来获得各种对象相应ProertyStore中SetObject( key, object value)用来设置各种对象所以只要我们取得AxHost(由gcroot可以看出)Key便可以通过SetObject(key, null)来消除PropertyStore对axCalendar引用
但是PropertyStore是ernal 所以需要通过反射它经我测试在windows xp下key值为74而在windows2003下key值为72代码如下:
private void MyFunc
{
Type tControl = typeof(Control);
BindingFlags bf = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static
| BindingFlags.NonPublic | BindingFlags.Instance;
object objProperty = tControl.InvokeMember("Properties", bf | BindingFlags.GetProperty, null, (Control)this, null);
Type tProperty = objProperty.GetType;
object args = object { 74, this.Controls };
tProperty.InvokeMember("SetObject", bf | BindingFlags.InvokeMethod, null, objProperty, args);
}
通过的前 3句代码加上这个axCanlendar被GC成功回收
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
问题整理总结:通过这个问题发现Form会对添加到Form中ActiveXControl控件添加引用即使显式释放Control控件资源并且将其设为null该Control控件仍然无法被GC所回收可见添加到Form中ActiveXControl控件生命周期和Form联系在起通过windbgreflector等工具我们查到该问题根本原因并且利用反射等手段解决了问题
最新评论