all groups > dotnet interop > october 2005 >
You're in the

dotnet interop

group:

Access violation calling C# interface from C++


Access violation calling C# interface from C++ Tony Towers
10/31/2005 10:45:10 AM
dotnet interop:
My company has been given a set of COM interfaces ( as .idl and .tlb
files) by a customer, which represents the interfaces to a system
they are in the process of developing. One of the interfaces
included is outgoing (i.e., implemented by us), and will be used to
notify our code of changes. The interface is immutable.

The customer code will not be available for some time, so I am
implementing a test harness to enable us to test the interface from
our side. The test harness is written in C++, as is the customer's
system. Our own code is written in C#.

There are three COM interfaces involved: ITLS, which is used to
create instances of other COM objects; IEntity, from which
notifications are sent; and INotification, which receives them.

INotification exposes a method EntityChanged, which takes two [in]
parameters, an IEntity* and a BSTR containing the changes to the
entity.

ITLS exposes a method "CreateEntity", which takes an optional
INotification* parameter, and returns an instance of IEntity. It
also calls EntityChanged on the INotification*.

I am having great trouble in getting the outgoing interface to work
correctly. I have got it working in the CreateEntity method, but not
otherwise. In order to simulate the customer system, I want to use
timeSetEvent to periodically notify our code. Unfortunately when the
outgoing interface's notify method is called, I get the error
"Unhandled exception at 0x001401e5 in TestHarness.exe: 0xC0000005:
Access violation writing location 0x002d5178".

I am not overly attached to using timeSetEvent - any other method of
generating periodic notifications will do. The only things that
can't change are the COM interfaces and the languages used.


Does anybody have any ideas?

Apologies for the length of this article.


The idl file we have been given is (in part):

[
uuid(...),
oleautomation,
helpstring("ITLS Interface"),
pointer_default(unique)
]
interface ITLS : IUnknown
{
[helpstring("method CreateEntity")] HRESULT
CreateEntity([in]INotification* pNotification, [out, retval]IEntity**
ppEntity);
};

[
uuid(...),
oleautomation,
helpstring("ITLS.IEntity Interface"),
pointer_default(unique)
]
interface IEntity : IUnknown
{
[helpstring("method Retrieve")] HRESULT Retrieve([out,retval]BSTR*
pXMLStream);
}

[
uuid(...),
oleautomation,
helpstring("ITLS.INotification Interface")
]
interface INotification : IUnknown
{
[helpstring("method EntityChanged")] HRESULT
EntityChanged([in]IEntity* entity, [in]BSTR changes);
};


[quoted text, click to view]

class ATL_NO_VTABLE CHarnessServer :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CHarnessServer, &CLSID_HarnessServer>,
public IDispatchImpl<IHarnessServer, &IID_IHarnessServer,
&LIBID_COInS, /*wMajor =*/ 1, /*wMinor =*/ 0>,
public IDispatchImpl<ITLS, &__uuidof(ITLS), &LIBID_TLS, /* wMajor = */
1, /* wMinor = */ 0>,
public IDispatchImpl<IEntity, &__uuidof(IEntity), &LIBID_TLS, /*
wMajor = */ 1, /* wMinor = */ 0>,
{
private:
static ResultGenerator::IResultGenerator* iug;

void DomNotify();

public:
static void CALLBACK NotifyDomain(UINT, UINT, DWORD_PTR, DWORD_PTR,
DWORD_PTR);
MMRESULT timerID;

CHarnessServer() {
mNotification = NULL;
timerID = -1;
}

~CHarnessServer() {
if (timerID >= 0) {
timeKillEvent(timerID);
}
}

DECLARE_REGISTRY_RESOURCEID(IDR_HARNESSSERVER)


BEGIN_COM_MAP(CHarnessServer)
COM_INTERFACE_ENTRY(IHarnessServer)
COM_INTERFACE_ENTRY(ITLS)
COM_INTERFACE_ENTRY(IEntity)
END_COM_MAP()


DECLARE_PROTECT_FINAL_CONSTRUCT()

HRESULT FinalConstruct()
{
return S_OK;
}

void FinalRelease()
{
}

private:

//IEntity variables
INotification* mNotification;


public:

// ITLS Methods
STDMETHOD(CreateEntity)(INotification* pNotification, IEntity**
ppEntity);

// IEntity Methods
STDMETHOD(Retrieve)(BSTR* pXMLStream);
};

OBJECT_ENTRY_AUTO(__uuidof(HarnessServer), CHarnessServer)

typedef CComObject<CHarnessServer> CComHarnessServer;


The C++ code to create an entity is:

HRESULT STDMETHODCALLTYPE CHarnessServer::CreateEntity(INotification*
pNotification, IEntity** ppEntity)
{
if (ppEntity == NULL)
{
return E_INVALIDARG;
}

CComHarnessServer* entity;
CComHarnessServer::CreateInstance(&entity);
entity->mNotification = pNotification;


if (pNotification != NULL) {
if (iug == NULL) {
HRESULT hr = CoCreateInstance(ResultGenerator::CLSID_Generator,
NULL, CLSCTX_INPROC_SERVER,
ResultGenerator::IID_IResultGenerator, (void**) &this->iug);
}
entity->EntNotify();
timeSetEvent(5000, 500, CHarnessServer::NotifyEntity, (DWORD_PTR)
entity, TIME_PERIODIC);
}

*ppEntity = static_cast<IEntity *>(entity);
return S_OK;
}


The timeSetEvent callback is:

void CALLBACK CHarnessServer::NotifyEntity(UINT uTimerID, UINT uMsg,
DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2) {
CHarnessServer* entity = (CHarnessServer*) dwUser;
entity->EntNotify();
}

void CHarnessServer::EntNotify() {
if (mNotification != NULL) {
BSTR res = this->iug->Generate();
mNotification->EntityChanged(this, res);
}
}


The C# class that implements INotification is:

public class EntityNotifier : INotification {
public EntityNotifier() {
Console.WriteLine("Constructed EntityNotifier\n");
}

~EntityNotifier() {
Console.WriteLine("Destroying EntityNotifier\n");
}

public void EntityChanged(IEntity entity, string changes) {
Console.WriteLine(changes);
}
}


And, finally, the C# code used to instantiate the entity is:

Type t = Type.GetTypeFromProgID("ITLTestHarness.HarnessServer.1",
itlsServer, true);
ITLS tls = (ITLS) Activator.CreateInstance(t);

// Instantiate the TLS Entity Notifier.
EntityNotifier entityNotifier = new EntityNotifier();

// Create an Entity Object instance.
IEntity tlsEntity = tls.CreateDomain(entityNotifier);



Thanks in advance,
Tony Towers.
Re: Access violation calling C# interface from C++ Igor Tandetnik
10/31/2005 2:06:42 PM
[quoted text, click to view]

I would recommend changing that to

CComPtr<INotification> mNotification;

You completely disregard reference counting in your code. This would
help get at least some of it right.

[quoted text, click to view]

Note that CComObject::CreateInstance creates an object with reference
count of zero. It is advisable to AddRef it immediately after creation,
otherwise it is in a somewhat fragile state where a stray AddRef/Release
pair leads to a premature destruction.

[quoted text, click to view]

Case in point: when keeping an [in] interface pointer beyond the
duration of the call, you must AddRef it. You didn't. By making
mNotification a smart pointer, it would happen automatically.

[quoted text, click to view]

This strikes me as a bad idea. How do you know, when the timer callback
arrives, that the pointer is still valid? It can be destroyed at any
moment. You need to keep it AddRef'ed while the timer is still alive,
and remember it somewhere so you can Release it after you stop the
timer.

[quoted text, click to view]

You must AddRef an [out] interface pointer before returning.
--
With best wishes,
Igor Tandetnik

With sufficient thrust, pigs fly just fine. However, this is not
necessarily a good idea. It is hard to be sure where they are going to
land, and it could be dangerous sitting under them as they fly
overhead. -- RFC 1925

Re: Access violation calling C# interface from C++ sat320 NO[at]SPAM yahoo.co.uk
11/1/2005 2:01:30 AM

[quoted text, click to view]

This is my first attempt at COM programming; thanks for the advice.
I'll have to read up on reference counting.

With the changes you suggested the test harness now works - thank-you
very much. I still need to clean up the code (and make sure all those
references get released at the proper time), but I'm getting there.

[quoted text, click to view]

OK, in order to keep track of allocated objects I'm thinking of
implementing a linked list. I can store the object pointer in a
list element, and when it gets Released I can clear the pointer
in the list element and unlink it. By passing the list element
as the user data I can check whether the object pointer is still
valid before using it when the timer fires.

[quoted text, click to view]

(*ppEntity)->AddRef()?

Is that in addition to the AddRef when "entity" was allocated?

Thanks again for your help,
Kind Regards,
Tony Towers.
Re: Access violation calling C# interface from C++ Tony Towers
11/1/2005 3:35:44 AM
[quoted text, click to view]

Answering myself from the documentation: "if you are passing a copy of
a pointer back from a function, you must call IUnknown::AddRef on that
pointer."

Tony Towers.
Re: Access violation calling C# interface from C++ Igor Tandetnik
11/1/2005 8:03:48 AM
[quoted text, click to view]

If you AddRef there, you don't need to AddRef again here, as long as you
don't cache your own reference to the object.
--
With best wishes,
Igor Tandetnik

With sufficient thrust, pigs fly just fine. However, this is not
necessarily a good idea. It is hard to be sure where they are going to
land, and it could be dangerous sitting under them as they fly
overhead. -- RFC 1925

Re: Access violation calling C# interface from C++ Tony Towers
11/1/2005 8:37:20 AM

[quoted text, click to view]

Great, thanks very much for your help. Everything is now working well.

Kind regards,
Tony Towers.
AddThis Social Bookmark Button