INTRODUCTION
I am buidling in-proc COM server to be consumed by VB6 client.
COM server needs to use
blocking function[
^].
This means that the VB6 GUI would be blocked until function retrieves the result, which is unacceptable. Therefore I will use the function in a worker thread, and notify the main thread when function unblocks.
Since VB6 GUI runs in single-threaded appartment, I have decided that COM server will use the same threading model.
After Googling, I have found out that in STA, interfaces from one thread are inaccessible in the other, and vice versa.
Since I will always have only one worker thread, I have decided to use
CoMarshalInterThreadInterfaceInStream [
^] for interface marshaling.
PROBLEM
After marshaling interface pointer from main thread into the worker one, event firing does not work.
When trying to compile, I get the following:
error C2039: 'Fire_testEvent' : is not a member of 'ISimpleObject'
I have added logging everywhere, and it seems that
CoGetInterfaceAndReleaseStream
fails with error
The parameter is incorrect.
Relevant information follows in the below section.
RELEVANT INFORMATION
I am using Visual Studio 2008 on Windows 8.1, COM DLL targets Windows XP or higher.
Using instructions from
this tutorial[
^], I have performed the following steps:
- created COM DLL with ATL Wizard (ticked "Merge Proxy/Stub" checkbox), named it SO_ATL_Demo
- added ATL Simple Object (ticked "ISupportErrorInfo" and "Connection Points" checkboxes) and named it SimpleObject
- added method to the main interface named (it should start thread and marshal interface pointer) as instructed in the tutorial
- added method for the event, as instructed in the tutorial
built the solution - added connection points as instructed in the tutorial
Relevant parts of the IDL:
interface ISimpleObject : IDispatch{
[id(1), helpstring("starts worker thread and marshals interface")] HRESULT test(void);
[id(2), helpstring("used to fire event in main thread")] HRESULT fire(void);
dispinterface _ISimpleObjectEvents
{
properties:
methods:
[id(1), helpstring("simple event")] HRESULT testEvent([in] BSTR b);
};
coclass SimpleObject
{
[default] interface ISimpleObject;
[default, source] dispinterface _ISimpleObjectEvents;
};
I have added the following variables/methods to the CSimpleObject:
private:
HANDLE thread;
IStream *pIS;
static unsigned int __stdcall Thread(void *arg);
Below is the implementation of interface marshaling:
STDMETHODIMP CSimpleObject::test(void)
{
HRESULT hr = S_OK;
IUnknown *pUn(NULL);
hr = QueryInterface(IID_IUnknown, reinterpret_cast<void**>(&pUn));
if(S_OK != hr)
{
::CoUninitialize();
return hr;
}
hr = ::CoMarshalInterThreadInterfaceInStream(IID_ISimpleObject, pUn, &pIS);
pUn->Release();
pUn = NULL;
if(S_OK != hr)
{
::CoUninitialize();
return hr;
}
thread = reinterpret_cast<HANDLE>(::_beginthreadex(NULL, 0, Thread, this, 0, NULL));
if(NULL == thread)
{
pIS->Release();
hr = HRESULT_FROM_WIN32(::GetLastError());
::CoUninitialize();
return hr;
}
return S_OK;
}
Unmarshaling implementation:
unsigned int __stdcall CSimpleObject::Thread(void *arg)
{
HRESULT hr = ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if(S_OK != hr)
return -1;
CSimpleObject *c = static_cast<CSimpleObject *>(arg);
if(NULL == c)
return -1;
IStream *pIS(NULL);
ISimpleObject *pISO(NULL);
hr = CoGetInterfaceAndReleaseStream(pIS, IID_ISimpleObject, reinterpret_cast<void**>(&pISO));
if(S_OK != hr)
return -1;
for(int i = 0; i < 11; ++i)
{
::Sleep(1000);
pISO->Fire_testEvent(L"Test string"); }
pISO->Release();
::CoUninitialize();
return 0;
}
In order to keep this post as short as possible, I have omitted full source code. If further info is required please request for it by leaving a comment.
Update #1:
Thread proc is declaring its own
IStream
pointer,
pIS
, initialized to
NULL
and never changed thereafter.
I use
c->pIS
for
CoGetInterfaceAndReleaseStream
argument.
C# client worked, but C++ client fails with
First-chance exception at 0x77095ef8 in SO_Demo_client.exe: 0x80010108: The object invoked has disconnected from its clients
when trying to
pISO->fire()
event from Thread function (
pISO->Fire_testEventstill
gives the same error, so I have changed for loop to use
pISO->fire()
).
C++ client is made with a wizard, as a Windows Console application. Below is the relevant code:
#include "stdafx.h"
#include <iostream>
#import "SomePath\\SO_ATL_Demo.dll"
static _ATL_FUNC_INFO StringEventInfo = { CC_STDCALL, VT_EMPTY, 1, { VT_BSTR } };
class CMyEvents :
public IDispEventSimpleImpl<1, CMyEvents, &__uuidof(SO_ATL_DemoLib::_ISimpleObjectEvents)>
{
public:
BEGIN_SINK_MAP(CMyEvents)
SINK_ENTRY_INFO(1, __uuidof(SO_ATL_DemoLib::_ISimpleObjectEvents), 1, onStringEvent, &StringEventInfo)
END_SINK_MAP()
HRESULT __stdcall onStringEvent(BSTR bstrParam)
{
std::wcout << "In event! " << bstrParam << std::endl;
return S_OK;
}
};
struct ComInit_SimpleRAII
{
HRESULT m_hr;
ComInit_SimpleRAII()
{
m_hr = ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
}
~ComInit_SimpleRAII()
{
::CoUninitialize();
}
};
int _tmain(int argc, _TCHAR* argv[])
{
ComInit_SimpleRAII ci;
if(S_OK != ci.m_hr)
{
_com_error e(ci.m_hr);
::OutputDebugStr(L"CoInitializeEx failed\n");
::OutputDebugStr(e.ErrorMessage());
::OutputDebugStr(e.Description());
return -1;
}
SO_ATL_DemoLib::ISimpleObjectPtr pISO;
HRESULT hr = pISO.CreateInstance(__uuidof(SO_ATL_DemoLib::SimpleObject));
if(S_OK != hr)
{
_com_error e(hr);
::OutputDebugStr(L"CreateInstance\n");
::OutputDebugStr(e.ErrorMessage());
::OutputDebugStr(e.Description());
return -1;
}
CMyEvents c;
hr = c.DispEventAdvise(pISO);
if(S_OK != hr)
{
_com_error e(hr);
::OutputDebugStr(L"DispEventAdvise\n");
::OutputDebugStr(e.ErrorMessage());
::OutputDebugStr(e.Description());
pISO->Release();
return -1;
}
::OutputDebugStr(L"testing fire()\n");
hr = pISO->fire();
if(S_OK != hr)
{
_com_error e(hr);
::OutputDebugStr(L"pISO->fire() failed\n");
::OutputDebugStr(e.ErrorMessage());
::OutputDebugStr(e.Description());
pISO->Release();
return -1;
}
::OutputDebugStr(L"testing test()");
hr = pISO->test();
if(S_OK != hr)
{
pISO->Release();
_com_error e(hr);
::OutputDebugStr(L"pISO->test()!\n");
::OutputDebugStr(e.ErrorMessage());
::OutputDebugStr(e.Description());
hr = c.DispEventUnadvise(pISO);
return -1;
}
std::cin.get();
hr = c.DispEventUnadvise(pISO);
if(S_OK != hr)
{
}
return 0;
}
Being new to COM (I have started learning 4 days ago), and after some Googling, I suspect that I made a mistake somewhere in reference counting.
Update #2:
After Googling around, I realized that STA clients
must have message loop, which my C++ client did not have.
I have added typical message loop in the COM client, and errors disappeared.
COM server was good, the C++ client was badly coded...
QUESTION
How to fix
error C2039: 'Fire_testEvent': is not a member of 'ISimpleObject'
?
What I have tried:
I have created COM client in C++ and C# in order to test the event itself.
Event was fired successfully from the main tread, and was caught successfully by the both COM clients.
As a back-up plan, I have created new project that uses hidden
message-only window[
^] in the main thread.
Worker thread uses
PostMessage
API to communicate with this window, thus notifying the main thread when needed.
Once main thread receives the message, event is fired successfully in message handler.
I am still Googling/pondering for a solution, I will update this post if I make any progress.