因此,此时我正在做的是将 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/