处理用C#编写的COM +组件处理

时间:2017-05-10 12:02:11

标签: c# com vb6 dispose finalizer

所以,我现在正在做的是将一个vb6类重写为C#。最终结果是它将用作COM +组件。

假设我们有一个设置为COM +组件的日志类。从vb6开始,你可以这样使用它:

Set logger = CreateObject("LoggingComponent")

我在C#中重写了它,将C#类安装为COM +组件,我可以在vb6中使用它。在这一点上,一切都很好。接下来是我的问题。

要在文件中写入日志,必须调用执行实际写入的Flush()方法。到目前为止,消息都在内存中的队列中。我的问题是当我忘记调用Flush方法时会发生什么。在vb6中,它们会变得红润。在C#中,它们不会被刷新。现在出现了差异:

在原始的vb6代码中,有一个方法

Private Sub Class_Terminate()
    Flush()
End Sub

我认为这个确保即使我们不调用flush,也会写入日志。

在C#中,我实现了IDisposable&一个析构函数,但是当vb6应用程序完成并处理COM + logger实例时,它们不会被调用(请忽略丢失的{}和其他无用的细节,我删除它们只是为了使代码更容易阅读https://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.100).aspx) :

public void Dispose()
     Flush(); // does not get called


~Logger()
     Flush(); // does not get called

所以...有谁知道我错过了什么?为什么在这种情况下不会调用~Logger?是否有任何可以处理的COM +事件(如Application.Current.Exit事件)。

1 个答案:

答案 0 :(得分:1)

OP的原始问题是GC没有运行。一种选择是构建定期调用GC.Collect的计时器。这可能是最安全的解决方案。

我可以提供替代解决方案。这个解决方案涉及主要的hackery,因此开发人员应该注意这种方法。我没有彻底审查过这个。使用COM进程外服务器(此处不使用COM +)进行简单测试证明这种方法基本可行,但我没有花时间研究和测试它完全有效。随意进一步探索这个想法,但没有额外的研究和测试,我不建议你做这个

我们的想法是使用我们自己创建的方法替换底层Release对象的vtable中的IUnknown方法,以便我们可以观察引用计数何时达到0。

// !!! THIS CODE INVOLVES A SERIOUS HACK !!!
// !!! USE AT YOUR OWN RISK              !!!

[ComVisible(true)]
[Guid(...)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IMyInterface))]
public class MyObject : IMyInterface, IDisposable
{
    // constructor
    public MyObject()
    {
        // get and store this object's IUnknown* (this adds a reference)
        _pUnknown = Marshal.GetIUnknownForObject(this);

        // get a pointer to the vtable of the IUnknown
        _pVTable = Marshal.ReadIntPtr(_pUnknown);

        // get a pointer to the Release method from the vtable
        var pRelease = Marshal.ReadIntPtr(_pVTable, 2 * IntPtr.Size);

        // get and store a delegate to the original Release method
        _originalRelease = (ReleaseDelegate) Marshal.GetDelegateForFunctionPointer(pRelease, typeof(ReleaseDelegate));

        // set the entry for the Release method in the vtable to a pointer for the ReleaseOverride method
        var pReleaseOverride = Marshal.GetFunctionPointerForDelegate(OverriddenRelease);
        Marshal.WriteIntPtr(_pVTable, 2 * IntPtr.Size, pReleaseOverride);
    }

    // this method will be called when a COM client releases
    private static int ReleaseOverride(IntPtr pUnknown)
    {
        // get the object being released
        var o = (MyObject) Marshal.GetObjectForIUnknown(pUnknown);

        // call the original release method
        var refCount = o._originalRelease(pUnknown);

        // if the remaining reference count is 1, the only
        // outstanding reference is the reference acquired through
        // the Marshal.GetIUnknownForObject call in the constructor
        if (refCount == 1)
        {
            // call Dispose
            o.Dispose();

            // restore the original Release method
            var pRelease = Marshal.GetFunctionPointerForDelegate(o._originalRelease);
            Marshal.WriteIntPtr(o._pVTable, 2 * IntPtr.Size, pRelease);

            // release the reference we acquired in the constructor
            refCount = Marshal.Release(o._pUnknown);
        }

        // return the ref count
        return refCount;
    }

    // this method will now be called when all COM clients release
    public void Dispose()
    {
    }

    // the IUnknown pointer for this object
    private readonly IntPtr _pUnknown;

    // a pointer to the v-table of the IUnknown
    private readonly IntPtr _pVTable;

    // a delegate to the original Release method
    private readonly ReleaseDelegate _originalRelease;

    // a delegate to the ReleaseOverride method
    private static readonly ReleaseDelegate OverriddenRelease = ReleaseOverride;

    // the Release delegate type
    private delegate int ReleaseDelegate(IntPtr pUnknown);
}