在C++中使用COM/ATL接口时,C#对象未被销毁



最近我做了一个小项目,它涉及一个DLL模块(用C#创建),我需要在我的应用程序中使用它(用非托管C++编写)。为了实现这一点,我使用ATL/COM。

我注意到,尽管我在C++应用程序中使用_com_ptr_t来处理我的核心com接口,但只有在应用程序关闭时才会调用C#对象的析构函数。

让我给你一些来源,让事情变得更清楚:

我的一些C#代码:

[ComVisible(true)]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]
public interface ITestCOM
{
[DispId(1)]
void Connect([In, MarshalAs(UnmanagedType.U2)] ushort value);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(ITestCOMEvents))]
public partial class TestCOM : ITestCOM
{
...
~TestCOM()
{
MessageBox.Show("DESTRUCTOR");
}
...
public void Connect(ushort value)
{
...
}
}

我正在使用以下内容创建.tlb文件:">RegAsm.exe TestCOM.dll/tlb:Test.tlb/codebase">

在我的C++标题中,我有:

#import "C:...mscorlib.tlb"
#import "......TestCOM.tlb" named_guids exclude("ISupportErrorInfo")
#include <afxdisp.h>
#include <atlcom.h>
class Unit : public ::IDispEventSimpleImpl<0, Unit, &__uuidof(TestCOM::ITestCOMEvents)>
{
public:
BEGIN_SINK_MAP(Unit)
SINK_ENTRY_INFO(0, __uuidof(TestCOM::ITestCOMEvents), 0x1, OnEventCallback, &OnEventCallbackDef)
END_SINK_MAP()
...
private
TestCOM::ITestCOMPtr mTestCOM;
// NOTE: This would be the same as "_com_ptr_t<_com_IIID<TestCOM::ITestCOM,  &__uuidof(TestCOM::ITestCOM)> > mTestCOM;"
}

我的C++源文件创建了我的"mTestCOM",如下所示:

mTestCOM.CreateInstance(TestCOM::CLSID_TestCOM)

基本上就是这样。我可以使用我的C#"TestCOM"对象的任何函数,如下所示:

mTestCOM->Connect(7);

问题是:为什么我的C#测试COM对象的析构函数只在我的应用程序关闭时调用,而不是在我的C++"单元"对象被破坏时调用?

虽然我不熟悉C#和COM集成,但我知道C#中的析构函数与C++中的析构函数的工作方式非常不同。C#对象是内存管理的,并且是垃圾收集的。这意味着,在对象停止被它所属的应用程序引用并变得"不可访问"后的某个时刻,垃圾收集器将销毁它。

因此,第一件重要的事情是,从一个对象被放弃到垃圾收集器销毁它之间的延迟是不确定的。。。它将发生在"未来的某个时刻",也就是应用程序终止的时刻。

其次,垃圾收集器并不总是在运行。有一个"内存压力"的概念,当你的应用程序正在分配大块内存,而可用的可用内存正在耗尽。。。这时,垃圾收集器将启动以清除旧的、无法访问的对象。如果您的应用程序没有分配大量内存,它就不会受到任何内存压力,垃圾收集器也不需要运行。

如果您想决定性地清理托管对象的一些资源,则需要使用类似IDisposable接口的东西,并显式调用Dispose方法。

这是完全正常的,也是垃圾收集器的副作用。它只收集垃圾,并在托管应用程序分配足够的内存来填充生成并触发收集时运行终结器。如果你没有分配足够的资源,那么在应用程序终止之前,这种情况不会发生。

这应该不是问题,请确保除了释放未释放的非托管资源之外,您没有使用终结器执行任何其他操作。在99.9%的情况下,编写终结器是错误的,终结器是.NET框架类的实现细节。就像SafeHandle类一样。否则,就无法让客户端应用程序中的确定性销毁在托管代码中产生确定性处置。如果确实需要处置成员,则需要公开客户端代码可以调用的方法。

最新更新