[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

microsoft.public.dotnet.framework.interop

Is this a CCW interop bug: C++ event sink for C# delegate

jstevenco

7/7/2007 12:08:00 AM

Hi all,

In the process of investigating interop between .NET and COM, I have
discovered a problem that I am as of yet unable to solve. Basically,
I am trying to use a .NET delegate as an outbound interface. I am
able to get the functionality I would expect, but I am also observing
an interface leak. My .NET COM object is constructed thus:

using System;
using System.Runtime.InteropServices;

namespace CCWTestObject
{
[ComVisible(false)]
public delegate void TestEventDelegate(string strMsg);

[Guid("8138740E-7A5E-4cfe-BE77-328B8EAFEE77")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IComInterfaceEvents
{
[DispId(1)]
void TestEvent(string strMsg);
}

[Guid("3967980E-C065-42ca-92C1-46EAE21795C8")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface _ICOMInterface
{
[DispId(1)]
void FireTestEvent();
}

[Guid("6269664E-A0AA-4f1c-936B-6E362679136C")]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IComInterfaceEvents))]
[ProgId("CCWTestObject.ICOMInterface")]
public class ICOMInterface : _ICOMInterface
{
public event TestEventDelegate TestEvent;

public ICOMInterface() {}

public void FireTestEvent()
{
TestEvent("Event fired.");
}
};
}

I import the typelib produced from this with #import
"CCWTestObject.tlb" named_guids.

My client is a simple C++ console app. I'm using ATL to reduce the
complexity. For the sink event object, I have:

#define TESTEVENT_ID 1

#define DISPID_TESTEVENT 0x00000001

static _ATL_FUNC_INFO OnTestEventInfo = {CC_STDCALL,VT_EMPTY,1,
{VT_BSTR} };

class CTestDotNetEventSink :
IDispEventImpl<TESTEVENT_ID,CTestDotNetEventSink,
&CCWTestObject::DIID_IComInterfaceEvents,
&CCWTestObject::LIBID_CCWTestObject,
1,0>
{
public:
BEGIN_SINK_MAP(CTestDotNetEventSink)
SINK_ENTRY_INFO(TESTEVENT_ID,
CCWTestObject::DIID_IComInterfaceEvents,
DISPID_TESTEVENT, OnTestEvent, &OnTestEventInfo)
END_SINK_MAP()

CTestDotNetEventSink(CCWTestObject::_ICOMInterface* spDotNetObject)
{
m_spDotNetObject = spDotNetObject;
m_spDotNetObject->AddRef();

HRESULT hr = DispEventAdvise((IUnknown*)m_spDotNetObject);
if (FAILED(hr))
{
ATLTRACE("Error in Advise.\n");
}
}

~CTestDotNetEventSink(void)
{
m_spDotNetObject->Release();
HRESULT hr = DispEventUnadvise((IUnknown*)m_spDotNetObject);

if (FAILED(hr))
{
ATLTRACE("Error in Unadvise.\n");
}
}

void __stdcall OnTestEvent(BSTR strMsg)
{
ATLTRACE(strMsg);
ATLTRACE("\n");
SysFreeString(strMsg);
}

private:
CCWTestObject::_ICOMInterface* m_spDotNetObject;
};

my main looks like:

int _tmain(int argc, _TCHAR* argv[])
{
CoInitialize(NULL);

CCWTestObject::_ICOMInterfacePtr
spDotNetObject(__uuidof(CCWTestObject::ICOMInterface));

if (spDotNetObject)
{
CTestDotNetEventSink* pTestEvent =
new
CTestDotNetEventSink((CCWTestObject::_ICOMInterface*)spDotNetObject);

//spDotNetObject->FireTestEvent();

delete pTestEvent;
}

spDotNetObject = NULL;

CoUninitialize();

return 0;
}

Note that I have the call to the FireTestEvent method commented out.
Running this in debug mode with _ATL_DEBUG_INTERFACES defined and
stepping over the line

HRESULT hr = DispEventAdvise((IUnknown*)m_spDotNetObject);

in the CTestDotNetEventSink constructor, I see the following:

QIThunk - 1 AddRef : Object = 0x009d3be0 Refcount = 1
IDispEventImpl - IComInterfaceEvents
QIThunk - 2 AddRef : Object = 0x009d3be0 Refcount = 1
IDispEventImpl - IUnknown
QIThunk - 2 AddRef : Object = 0x009d3be0 Refcount = 2
IDispEventImpl - IUnknown
QIThunk - 2 AddRef : Object = 0x009d3be0 Refcount = 3
IDispEventImpl - IUnknown
QIThunk - 2 Release : Object = 0x009d3be0 Refcount = 2
IDispEventImpl - IUnknown
QIThunk - 2 Release : Object = 0x009d3be0 Refcount = 1
IDispEventImpl - IUnknown
QIThunk - 1 Release : Object = 0x009d3be0 Refcount = 0
IDispEventImpl - IComInterfaceEvents

When the event sink is destroyed, invoking the DispEventUnadvise,
nothing happens. When the program exits, I see the following:

ATL: QIThunk - 2 LEAK : Object = 0x009c3be0 Refcount = 1
MaxRefCount = 3 IDispEventImpl - IUnknown
First-chance exception at 0x79e8db38 in CCWTestClient.exe: 0xC0000005:
Access violation reading location 0xfeeefef6.

with the FireThestEvent method uncommented I see:

QIThunk - 1 AddRef : Object = 0x009c3be0 Refcount = 1
IDispEventImpl - IComInterfaceEvents
QIThunk - 2 AddRef : Object = 0x009c3be0 Refcount = 1
IDispEventImpl - IUnknown
QIThunk - 2 AddRef : Object = 0x009c3be0 Refcount = 2
IDispEventImpl - IUnknown
QIThunk - 2 AddRef : Object = 0x009c3be0 Refcount = 3
IDispEventImpl - IUnknown
QIThunk - 2 Release : Object = 0x009c3be0 Refcount = 2
IDispEventImpl - IUnknown
QIThunk - 2 Release : Object = 0x009c3be0 Refcount = 1
IDispEventImpl - IUnknown
QIThunk - 1 Release : Object = 0x009c3be0 Refcount = 0
IDispEventImpl - IComInterfaceEvents
QIThunk - 2 AddRef : Object = 0x009c3be0 Refcount = 2
IDispEventImpl - IUnknown
QIThunk - 3 AddRef : Object = 0x009c3be0 Refcount = 1
IDispEventImpl - IComInterfaceEvents
QIThunk - 2 Release : Object = 0x009c3be0 Refcount = 1
IDispEventImpl - IUnknown
QIThunk - 3 Release : Object = 0x009c3be0 Refcount = 0
IDispEventImpl - IComInterfaceEvents
QIThunk - 2 AddRef : Object = 0x009c3be0 Refcount = 2
IDispEventImpl - IUnknown
QIThunk - 4 AddRef : Object = 0x009c3be0 Refcount = 1
IDispEventImpl - IComInterfaceEvents
QIThunk - 2 Release : Object = 0x009c3be0 Refcount = 1
IDispEventImpl - IUnknown
QIThunk - 4 AddRef : Object = 0x009c3be0 Refcount = 2
IDispEventImpl - IComInterfaceEvents
Event fired.
QIThunk - 4 Release : Object = 0x009c3be0 Refcount = 1
IDispEventImpl - IComInterfaceEvents
ATL: QIThunk - 2 LEAK : Object = 0x009c3be0 Refcount = 1
MaxRefCount = 3 IDispEventImpl - IUnknown
ATL: QIThunk - 4 LEAK : Object = 0x009c3be0 Refcount = 1
MaxRefCount = 2 IDispEventImpl - IComInterfaceEvents
First-chance exception at 0x79e8db38 in CCWTestClient.exe: 0xC0000005:
Access violation reading location 0xfeeefef6.
First-chance exception at 0x79e8db38 in CCWTestClient.exe: 0xC0000005:
Access violation reading location 0xfeeefef6.

Using a standard ATL-based COM server I see (with FireTestEvent method
enabled):

QIThunk - 1 AddRef : Object = 0x009c3be0 Refcount = 1
IDispEventImpl - _ICOMInterfaceEvents
QIThunk - 1 AddRef : Object = 0x009c3be0 Refcount = 2
IDispEventImpl - _ICOMInterfaceEvents
Event fired.
QIThunk - 1 Release : Object = 0x009c3be0 Refcount = 1
IDispEventImpl - _ICOMInterfaceEvents
QIThunk - 1 Release : Object = 0x009c3be0 Refcount = 0
IDispEventImpl - _ICOMInterfaceEvents

I have downloaded a couple of other code examples from the web and
analyzed them as well; they too exhibit this behavior. So the
question is: could there be something hinky with my system, or is
this a CCW interop bug? Or am I missing something here like a
difference in threading models etc.

Other data points of possible interest:

(1) My machine is a pretty "well-used" dev machine -- vs6, 2003, and
2005 installed.
(2) In addition, v1, v1.1, v2, AND v3 of .NET are installed!!
(3) I have run this code on a "v1.1 only" virtual machine with the
same result.

If the code looks okay, I'd appreciate some feedback and/or
independent verification. Or, obviously, clue me in to my
ignorance! ;-)

Thanks in advance,

Steve