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