gpt4 book ai didi

com - 使用 COM 互操作时如何管理对象生命周期?

转载 作者:行者123 更新时间:2023-12-04 12:41:40 30 4
gpt4 key购买 nike

我有一个用 C# 编写的托管 COM 对象和一个用 C++(MFC 和 ATL)编写的 native COM 客户端和接收器。客户端创建对象并在启动时向其事件接口(interface)提供建议,并在其事件接口(interface)中取消建议并在关闭时释放对象。问题是 COM 对象具有对接收器的引用,该引用在垃圾收集运行之前不会释放,此时客户端已经被拆除,因此通常会导致访问冲突。这可能没什么大不了的,因为客户端无论如何都会关闭,但如果可能的话,我想优雅地解决这个问题。我需要我的 COM 对象以更及时的方式释放我的接收器对象,但我真的不知道从哪里开始,因为我的 COM 对象不能明确地与接收器对象一起使用。

我的 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;
}
};

我的对话类中有以下成员:
ITestObjectPtr m_TestObject;
CComObject<CTestObjectEventsSink>* m_TestObjectEventsSink;
DWORD m_Cookie;

在 OnInitDialog() 中:
HRESULT hr = m_TestObject.CreateInstance(__uuidof(TestObject));
if(m_TestObject)
{
hr = CComObject<CTestObjectEventsSink>::CreateInstance(&m_TestObjectEventsSink);
if(SUCCEEDED(hr))
{
m_TestObjectEventsSink->AddRef(); // CComObject::CreateInstace() gives an object with a ref count of 0
hr = AtlAdvise(m_TestObject, m_TestObjectEventsSink, __uuidof(ITestObjectEvents), &m_Cookie);
}
}

在 OnDestroy() 中:
if(m_TestObject)
{
HRESULT hr = AtlUnadvise(m_TestObject, __uuidof(ITestObjectEvents), m_Cookie);
m_Cookie = 0;
m_TestObjectEventsSink->Release();
m_TestObjectEventsSink = NULL;
m_TestObject.Release();
}

最佳答案

首先,我只想说我已经使用您的示例代码来实现您所描述的内容的副本,但是在测试 Debug 或 Release 构建时我没有看到任何访问冲突。

因此,您所看到的可能有一些替代解释(例如,如果您拥有与 native 客户端的其他接口(interface),您可能需要调用 Marshal.ReleaseCOMObject)。

对何时/何时不调用 ReleaseCOMObject 进行了全面的描述在 MSDN here .

话虽如此,您的 C# COM 对象不能直接与 COM 客户端的接收器对象一起工作,但它确实通过 C# 事件对象与其通信。这允许您实现一个自定义的事件对象,这样您就可以捕获客户端对 AtlAdvise 的调用的影响。和 AtlUnadvise .

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

private event TestEventDelegate _TestEvent;
public event TestEventDelegate TestEvent
{
add
{
Debug.WriteLine("TRACE : TestObject.TestEventDelegate.add() called");
_TestEvent += value;
}
remove
{
Debug.WriteLine("TRACE : TestObject.TestEventDelegate.remove() called");
_TestEvent -= value;
}
}

public void InvokeTestEvent()
{
if (_TestEvent != null)
{
_TestEvent(42);
}
}

要继续调试输出,您可以向 MFC/ATL 应用程序添加类似的诊断,并准确查看接收器接口(interface)上的引用计数何时更新(请注意,这假定两个项目的调试版本)。因此,例如,我添加了 Dump接收器实现的方法:

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;
}
void Dump(LPCTSTR szMsg)
{
TRACE("TRACE : CTestObjectEventsSink::Dump() - m_dwRef = %u (%S)\n", m_dwRef, szMsg);
}
};

然后,通过 IDE 运行调试客户端应用程序,您可以看到发生了什么。首先,在创建 COM 对象期间:

HRESULT hr = m_TestObject.CreateInstance(__uuidof(TestObject));
if(m_TestObject)
{
hr = CComObject<CTestObjectEventsSink>::CreateInstance(&m_TestObjectEventsSink);
if(SUCCEEDED(hr))
{
m_TestObjectEventsSink->Dump(_T("after CreateInstance"));
m_TestObjectEventsSink->AddRef(); // CComObject::CreateInstace() gives an object with a ref count of 0
m_TestObjectEventsSink->Dump(_T("after AddRef"));
hr = AtlAdvise(m_TestObject, m_TestObjectEventsSink, __uuidof(ITestObjectEvents), &m_Cookie);
m_TestObjectEventsSink->Dump(_T("after AtlAdvise"));
}
}

这给出了以下调试输出(您可以从其中的 AtlAdvise 调用中看到 C# 跟踪)
TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 0 (after CreateInstance) TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 1 (after AddRef) TRACE : TestObject.TestEventDelegate.add() called TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 2 (after AtlAdvise)
这看起来和预期的一样,我们有一个引用计数 2 - 一个来自 native 代码 AddRef另一个(大概)来自 AtlAdvise .

现在,您可以检查如果 InvokeTestEvent()方法被调用 - 这里我做了两次:

m_TestObject->InvokeTestEvent();
m_TestObjectEventsSink->Dump(_T("after m_TestObject->InvokeTestEvent() first call"));
m_TestObject->InvokeTestEvent();
m_TestObjectEventsSink->Dump(_T("after m_TestObject->InvokeTestEvent() second call"));

这是对应的踪迹
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("before AtlUnadvise"));
HRESULT hr = AtlUnadvise(m_TestObject, __uuidof(ITestObjectEvents), m_Cookie);
m_TestObjectEventsSink->Dump(_T("after AtlUnadvise"));
m_Cookie = 0;
m_TestObjectEventsSink->Release();
m_TestObjectEventsSink->Dump(_T("after Release"));
m_TestObjectEventsSink = NULL;
m_TestObject.Release();
}

并且跟踪输出是
TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 3 (before AtlUnadvise) TRACE : TestObject.TestEventDelegate.remove() called TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 3 (after AtlUnadvise) TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 2 (after Release)
所以你可以看到 AtlUnadvise不影响引用计数( also noted by other people ),但还要注意我们从 remove 获得了跟踪。 C# COM 对象事件的访问器,它是强制执行某些垃圾收集或其他拆除任务的可能位置。

总结一下:
  • 您报告了您发布的代码的访问冲突,但我无法重现该错误,因此您看到的错误可能与您描述的问题无关。
  • 您询问了如何与 COM 客户端接收器进行交互,我已经展示了一种使用自定义事件实现的潜在方式。显示两个 COM 组件如何交互的调试输出支持这一点。

  • 我真的希望这会有所帮助。 this old but otherwise excellent blog post 中有一些替代的 COM 处理技巧和更多解释。 .

    关于com - 使用 COM 互操作时如何管理对象生命周期?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17243136/

    30 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com