c# - 在用 C# 编写的 COM+ 组件上处理 dispose

标签 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+ 记录器实例时它们不会被调用(请忽略丢失的 {} 和其他无用的细节,我删除它们只是为了代码更易于阅读 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 事件)。

最佳答案

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

我可以提供替代解决方案。此解决方案涉及大量黑客攻击,因此开发人员应提防此方法。我还没有彻底审查这个。使用 COM 进程外服务器(这里不使用 COM+)进行的简单测试证明这种方法基本有效,但我还没有花必要的时间来研究和测试它是否完全有效。随意进一步探讨这个想法,但无需额外的研究和测试,我不建议您这样做

想法是用我们自己创建的方法替换底层IUnknown对象的vtable中的Release方法,这样我们就可以在ref时看到计数达到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);
}

关于c# - 在用 C# 编写的 COM+ 组件上处理 dispose,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43891931/

相关文章:

c# - Newtonsoft.Json 中的 "Self referencing loop detected"

c++ - 直接显示捕获过滤器 "wrapper"

com - .NET/COM 如何处理通过 Regasm 注册的多个版本?

ms-access - 在 Recordset 循环中迭代 ADODB 字段

image - 为 Visual Basic 命令按钮创建图像?

vb6 - 如何在不使用已安装的播放器的情况下播放 VB6 中的 .mp3 文件?

c# - Unity3d,当前选择的脚本后端(.NET)未安装

c# - C# 是否在循环/lambda 语句中自动优化代码?

c# - 在 COM 对象上使用反射调用方法

c# - Foreach 因编码 COM 接口(interface)而失败