使用COM互操作时如何管理对象生存期? 我有一个托管的COM对象,用C语言编写,一个本地的COM客户端和接收器用C++编写(MFC和ATL)。客户端在启动时创建对象并向其事件接口发出建议,在关闭时从其事件接口发出建议并释放对象。问题是COM对象有一个对接收器的引用,该引用在垃圾收集运行之前不会释放,此时客户端已经被拆除,因此通常会导致访问冲突。这可能没什么大不了的,因为客户正在关闭,但如果可能的话,我想优雅地解决这个问题。我需要我的COM对象以更及时的方式释放我的接收器对象,而且我真的不知道从哪里开始,因为我的COM对象没有显式地与接收器对象一起工作

使用COM互操作时如何管理对象生存期? 我有一个托管的COM对象,用C语言编写,一个本地的COM客户端和接收器用C++编写(MFC和ATL)。客户端在启动时创建对象并向其事件接口发出建议,在关闭时从其事件接口发出建议并释放对象。问题是COM对象有一个对接收器的引用,该引用在垃圾收集运行之前不会释放,此时客户端已经被拆除,因此通常会导致访问冲突。这可能没什么大不了的,因为客户正在关闭,但如果可能的话,我想优雅地解决这个问题。我需要我的COM对象以更及时的方式释放我的接收器对象,而且我真的不知道从哪里开始,因为我的COM对象没有显式地与接收器对象一起工作,com,com-interop,Com,Com Interop,我的COM对象: public delegate void TestEventDelegate(int i); [ComVisible(true)] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface ITestObject { int TestMethod(); void InvokeTestEvent(); } [ComVisible(true)] [InterfaceType(Co

我的COM对象:

public delegate void TestEventDelegate(int i);

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITestObject
{
    int TestMethod();
    void InvokeTestEvent();
}

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITestObjectEvents
{
    void TestEvent(int i);
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(ITestObjectEvents))]
public class TestObject : ITestObject
{
    public event TestEventDelegate TestEvent;
    public TestObject() { }
    public int TestMethod()
    {
        return 42;
    }
    public void InvokeTestEvent()
    {
        if (TestEvent != null)
        {
            TestEvent(42);
        }
    }
}
该客户端是一个基于标准MFC对话框的程序,并添加了对ATL的支持。我的水槽班:

class CTestObjectEventsSink : public CComObjectRootEx<CComSingleThreadModel>, public ITestObjectEvents
{
public:
    BEGIN_COM_MAP(CTestObjectEventsSink)
        COM_INTERFACE_ENTRY_IID(__uuidof(ITestObjectEvents), ITestObjectEvents)
    END_COM_MAP()
    HRESULT __stdcall raw_TestEvent(long i)
    {
        return S_OK;
    }
};

首先,我要说的是,我已经使用您的示例代码实现了您所描述内容的副本,但是在测试调试或发布版本时,我没有看到任何访问冲突

因此,有可能对您看到的内容有其他解释(例如,如果您持有本机客户端的其他接口,则可能需要调用
封送.ReleaseCOMObject

对何时/何时不调用
ReleaseCOMObject
on有一个全面的描述

话虽如此,您的C#COM对象并不直接与COM客户端的接收器对象一起工作,但它确实通过C#event对象与之通信,这是对的。这允许您实现自定义事件对象,以便捕获客户端调用
AtlAdvise
AtlUnadvise
的效果

例如,您可以按如下方式重新实现事件(添加一些调试输出):

private event TestEventDelegate\u TestEvent;
公共事件测试事件删除测试事件
{
添加
{
WriteLine(“TRACE:TestObject.TestEventDelegate.add()调用”);
_TestEvent+=值;
}
去除
{
WriteLine(“TRACE:TestObject.TestEventDelegate.remove()调用”);
_TestEvent-=值;
}
}
public void invoketesvent()
{
如果(_TestEvent!=null)
{
_受试者(42);
}
}
要继续调试输出,您可以将类似的诊断添加到MFC/ATL应用程序,并准确查看接收器接口上的引用计数何时更新(请注意,这假定两个项目的调试版本)。例如,我在sink实现中添加了一个
Dump
方法:

class-ctestObjectEventsLink:公共CComObjectRootEx,公共ITestObjectEvents
{
公众:
开始COM映射(CTestObjectEventsSink)
COM_接口_条目_IID(uuuIdof(ITestObjectEvents),ITestObjectEvents)
END_COM_MAP()
HRESULT\uuu stdcall原始测试事件(长i)
{
返回S_OK;
}
无效转储(LPCTSTR szMsg)
{
跟踪(“跟踪:CTestObjectEventsSink::Dump()-m_dwRef=%u(%S)\n”,m_dwRef,szMsg);
}
};
然后,通过IDE运行调试客户机应用程序,您可以看到发生了什么。首先,在创建COM对象期间:

public delegate void TestEventDelegate(int i);

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITestObject
{
    int TestMethod();
    void InvokeTestEvent();
}

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITestObjectEvents
{
    void TestEvent(int i);
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(ITestObjectEvents))]
public class TestObject : ITestObject
{
    public event TestEventDelegate TestEvent;
    public TestObject() { }
    public int TestMethod()
    {
        return 42;
    }
    public void InvokeTestEvent()
    {
        if (TestEvent != null)
        {
            TestEvent(42);
        }
    }
}
HRESULT hr=m_TestObject.CreateInstance(__uuidof(TestObject));
if(m_TestObject)
{
hr=ccombject::CreateInstance(&m_TestObjectEventsSink);
如果(成功(hr))
{
m_TestObjectEventsSink->Dump(_T(“在CreateInstance之后”);
m_TestObjectEventsSink->AddRef();//ccombject::createInstance()提供了一个ref计数为0的对象
m_TestObjectEventsSink->Dump(_T(“在AddRef之后”);
hr=AtlAdvise(m_TestObject,m_TestObjectEventsLink,m_uuidof(ITestObjectEvents),m_Cookie);
m_TestObjectEventsSink->Dump(_T(“在AtlAdvise之后”);
}
}
这将提供以下调试输出(您可以从那里的
AtlAdvise
调用中看到C#跟踪)

TRACE:CTestObjectEventsSink::Dump()-m_dwRef=0(在CreateInstance之后)

TRACE:CTestObjectEventsSink::Dump()-m_dwRef=1(在AddRef之后)

TRACE:TestObject.TestEventDelegate.add()调用
TRACE:CTestObjectEventsSink::Dump()-m_dwRef=2(在AtlAdvise之后)

这看起来和预期的一样,我们有一个2的引用计数-一个来自本机代码
AddRef
,另一个(大概)来自
AtlAdvise

现在,您可以检查如果调用了
invoketestvent()
方法会发生什么情况-这里我做了两次:

m_TestObject->invoketestvent();
m_TestObjectEventsSink->Dump(_T(“在m_TestObject->invoketestvent()第一次调用之后”);
m_TestObject->invoketestvent();
m_TestObjectEventsSink->Dump(_T(“在m_TestObject->invoketestvent()第二次调用之后”);
这是对应的跟踪

TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 3 (after m_TestObject->InvokeTestEvent() first call)   
TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 3 (after m_TestObject->InvokeTestEvent() second call) 
您可以看到,在第一次触发事件时,发生了另一个
AddRef
。我猜这是在垃圾收集之前不会被释放的引用

最后,在OnDestroy中,我们可以看到引用计数再次下降。代码是

if(m_TestObject)
{
m_TestObjectEventsSink->Dump(_T(“在AtlUnadvise之前”);
HRESULT hr=AtlUnadvise(m_TestObject,u uuidof(ITestObjectEvents),m_Cookie);
m_TestObjectEventsSink->Dump(_T(“在AtlUnadvise之后”);
m_=0;
m_TestObjectEventsSink->Release();
m_TestObjectEventsSink->Dump(_T(“发布后”);
m_TestObjectEventsSink=NULL;
m_TestObject.Release();
}
跟踪输出是

TRACE:CTestObjectEventsSink::Dump()-m_dwRef=3(在AtlUnadvise之前)

TRACE:TestObject.TestEventDelegate.remove()调用
TRACE:CTestObjectEventsSink::Dump()-m_dwRef=3(在AtlUnadvise之后)

TRACE:CTestObjectEventsSink::Dump()-m_dwRef=2(发布后)
if(m_TestObject) { HRESULT hr = AtlUnadvise(m_TestObject, __uuidof(ITestObjectEvents), m_Cookie); m_Cookie = 0; m_TestObjectEventsSink->Release(); m_TestObjectEventsSink = NULL; m_TestObject.Release(); }
TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 3 (after m_TestObject->InvokeTestEvent() first call)   
TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 3 (after m_TestObject->InvokeTestEvent() second call)