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] >From this I created an ATL project, and have this class:
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.