所以,我现在正在做的是将一个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事件)。
答案 0 :(得分:1)
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);
}