【问题标题】:C# object not destroyed when using COM/ATL interface in C++在 C++ 中使用 COM/ATL 接口时 C# 对象未销毁
【发布时间】:2012-11-03 02:26:54
【问题描述】:

最近我正在做一个涉及 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# TestCOM 对象的析构函数仅在我的应用程序关闭时调用,而不是在我的 C++“单元”对象被销毁时调用?

【问题讨论】:

    标签: c# c++ com atl


    【解决方案1】:

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

    所以第一件重要的事情是,一个对象被放弃和垃圾收集器销毁它之间的延迟是不确定的......它会发生在“未来的某个时候”,很可能是在应用程序终止的时候.

    其次,垃圾收集器并不总是在运行。有“内存压力”的概念,当您的应用程序正在分配大块内存并且可用的内存快用完时......此时,垃圾收集器将启动以摆脱旧的、无法访问的对象。如果您的应用程序没有分配大量内存,它将不会受到任何内存压力,垃圾收集器也不需要运行。

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 感谢 Hans Passant,我接受了 Rook 的回答,但两个回答都一样好。
      猜你喜欢
      • 2016-08-12
      • 2011-09-22
      • 1970-01-01
      • 2011-09-18
      • 2023-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多