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 apartment, 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 marshalling.
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'
Relevant information follows in the below section.
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:
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"); //error C2039: 'Fire_testEvent' : is not a member of 'ISimpleObject'
// I know this was ugly, but this is just a demo, and I am in a time crunch...
}
pISO->Release();
::CoUninitialize();
return 0;
}
As requested, here is the fire
method implementation:
STDMETHODIMP CSimpleObject::fire(void)
{
return Fire_testEvent(L"Test string");
}
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.
How to fix error C2039: 'Fire_testEvent' : is not a member of 'ISimpleObject'
?
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.
update #1: I have added logging everywhere, and got the info that CoGetInterfaceAndReleaseStream
fails with error The parameter is incorrect.
update #2:
I have changed the code as suggested in the comment (from hr = CoGetInterfaceAndReleaseStream(pIS,..)
to hr = CoGetInterfaceAndReleaseStream(c->pIS,...
), and the C# client worked.
C++ client failed 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_testEvent
still gives the same error, so I have changed for
loop to use pISO->fire()
since it was suggested earlier).
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)
{
// skipped for now...
}
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 #3:
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.
The backup plan that you tried would be the simplest solution.
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.
Although it is OCX, there are programs that are running about 19 years ago in that way.
1.14.001 CCO Source Code and Data Files (ZIP File)
User contributions licensed under CC BY-SA 3.0