如何允许事件处理程序连接到COM对象的对象上的垃圾回收

时间:2014-05-29 20:48:50

标签: .net com event-handling garbage-collection

我有一个为COM对象提供适配器的类。这是一个非常简化的版本:

public class DocumentWrapper
{
    private COMDocument doc;
    public DocumentWrapper(COMDocument doc)
    {
        this.doc = doc;
        this.doc.OnClose += OnClose;
    }
    private void OnClose()
    {
        this.doc.OnClose -= OnClose;
        this.doc = null;
    }
    public bool IsAlive { get { return this.doc != null; } }
}

问题是OnClose事件处理程序使两个对象都保持活动状态。两个物体应该具有相同的寿命,所以当一个物体消失时,另一个物体也应该存在,即没有人保持一个存活并且期望另一个物体消失。

我尝试过弱引用:

COMDocument com = CreateComDocument();
var doc = new DocWrapper(com);
WeakReference weak1 = new WeakReference(com);
WeakReference weak2 = new WeakReference(doc);
GC.Collect(2, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
GC.Collect(2, GCCollectionMode.Forced);
if (weak1.Target != null)
    Console.WriteLine("COM was not collected");
if (weak2.Target != null)
    Console.WriteLine("Wrapper was not collected");

如果未注册OnClose,则会收集这两个对象。如果注册了OnClose,则不会收集任何对象。如何在不丢失事件的情况下确保这对对象具有收藏价值?

1 个答案:

答案 0 :(得分:1)

实际的解决方案要复杂得多,因为我不得不担心多个线程(终结器在后台线程上运行,并且取消注册COM事件必须调用COM线程,如果我不小心可能会触发死锁) 。但这里是如何构建事物的基本思想,以确保它可以收集垃圾:

public class DocumentWrapper
{
    private COMDocument doc;
    private WeakHandler closeHandler;
    public DocumentWrapper(COMDocument doc)
    {
        this.doc = doc;
        this.closeHandler = new WeakHandler(this);
    }
    ~DocumentWrapper()
    {
        if (closeHandler != null)
            closeHandler.Unregister();
    }
    public bool IsAlive { get { return this.doc != null; } }

    private class WeakHandler : IDisposable
    {
        private WeakReference owner;
        public WeakHander(DocumentWrapper owner)
        {
            this.owner = new WeakReference(owner);
            owner.doc.OnClose += Unregister();
        }
        private void Unregister()
        {
            if (owner == null)
                return;
            var target = owner.Target as DocumentWrapper;
            if (target != null && target.doc != null)
            {
                target.doc.OnClose -= OnClose;
                target.closeHandler = null;
                GC.SupressFinalize(target);
            }
            this.owner = null;
        }
    }
}