How to control the lifetime of an object when working with COM-interaction?

I have a managed COM object written in C # and my own COM client and receiver written in C ++ (MFC and ATL). The client creates an object and advises its event interface at startup, and also abandons its event interface and frees the object when it completes. The problem is that the COM object has a link to the receiver, which is not freed up until garbage collection is started, after which the client is already demolished and, as a rule, leads to an access violation. This is probably not that important as the client closes anyway, but I would like to resolve it gracefully, if possible. I need my COM object to create my receiver object more quickly, and I donโ€™t know where to start since my COM object does not work with an explicit object.

My COM object:

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); } } } 

The client is a standard MFC dialog program with additional ATL support. My shell class:

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

I have the following members in my dialog class:

 ITestObjectPtr m_TestObject; CComObject<CTestObjectEventsSink>* m_TestObjectEventsSink; DWORD m_Cookie; 

In 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); } } 

In 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(); } 
+7
source share
1 answer

First, Iโ€™ll just say that I used your sample code to implement a copy of what you described, but I donโ€™t see any access violations when testing Debug or Release debugging.

Thus, it is possible that there is an alternative explanation for what you see (for example, you might need to call Marshal.ReleaseCOMObject if you are holding other interfaces for your own client).

There is a detailed description of when / when you should not call ReleaseCOMObject on MSDN here .

Having said that, you are correct that your C # COM object does not work directly with the client object of the COM client, but it communicates with it through the C # event object. This allows you to implement a custom event object so that you can catch the effect of client calls on AtlAdvise and AtlUnadvise .

For example, you can redefine your event as follows (when adding some debug output):

 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); } } 

To continue debugging output, you can add similar diagnostics to the MFC / ATL application and see when the reference counter on the receiver interface is updated (note that this involves debugging assemblies of both projects). So, for example, I added a Dump method to implement the receiver:

 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); } }; 

Then, by starting the Debug client application through the IDE, you will see what happens. First, when creating a COM object:

 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")); } } 

This gives the following debug output (you can see the C # trace from calling AtlAdvise there)

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)

It looks as expected, we have a reference count of 2 - one from the native AddRef code and the other (presumably) from AtlAdvise .

Now you can check what happens if the InvokeTestEvent() method is InvokeTestEvent() - here I do it twice:

 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")); 

This is the appropriate trace.

 TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 3 (after m_TestObject->InvokeTestEvent() first call) TRACE : CTestObjectEventsSink::Dump() - m_dwRef = 3 (after m_TestObject->InvokeTestEvent() second call) 

You can see that an additional AddRef occurred on the first start of the event. I assume this is a link that is not freed before garbage collection.

Finally, in OnDestroy we will again see that the reference count is omitted again. The code

 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(); } 

and trace output

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)

So, you can see that AtlUnadvise does not affect the number of links ( also noted by other people ), but also note that we got a trace from the remove accessory of the COM object of the COM object, which is a possible place for forcing garbage collection or other dumping tasks.

Summarizing:

  • You reported access violations with the code that you sent, but I could not reproduce this error, so it is possible that the error you see is not related to the problem you described.
  • You asked how you can interact with the receiver of a COM client, and I showed one of the possible ways to use a custom implementation of the event. This is supported by a debug result showing how the two COM components interact.

I really hope this is helpful. There are several alternative tips for handling COM and more explanation in this old but otherwise excellent blog post .

+3
source

All Articles