Введение
Я собираю встроенный COM-сервер для использования клиентом VB6
.
COM-сервер должен использовать функцию блокировки .
Это означает, что графический интерфейс VB6
будет заблокирован до тех пор, пока функция не получит результат, что недопустимо.
Поэтому я буду использовать функцию в рабочем потоке и сообщать основному потоку, когда функция разблокируется.
Поскольку VB6
GUI работает в однопоточной квартире, я решил, что COM-сервер будет использовать ту же модель потоков.
После поиска в Google я обнаружил, что в STA интерфейсы из одного потока недоступны в другом, и наоборот.
Поскольку у меня всегда будет только один рабочий поток, я решил использовать CoMarshalInterThreadInterfaceInStream для маршалинга интерфейса.
ПРОБЛЕМА:
После маршалинга указателя интерфейса из основного потока в рабочий поток срабатывание события не работает.
При попытке компиляции получаю следующее:
ошибка C2039: «Fire_testEvent»: не является членом «ISimpleObject»
Соответствующая информация приведена в следующем разделе.
СООТВЕТСТВУЮЩАЯ ИНФОРМАЦИЯ
Я использую Visual Studio 2008 в Windows 8.1, COM DLL предназначена для Windows XP или выше.
Используя инструкции из этого урока , я выполнил следующие шаги:
- создал COM DLL с помощью мастера ATL (поставлен флажок «Merge Proxy / Stub»), назвал его SO_ATL_Demo
- добавлен простой объект ATL (отмечены флажками «ISupportErrorInfo» и «Точки подключения») и назван SimpleObject
- добавлен метод к основному интерфейсу с именем (он должен запускать поток и указатель на маршальный интерфейс), как указано в руководстве
- добавлен метод для события, как указано в руководстве
- построено решение
- добавлены точки подключения, как указано в руководстве
Соответствующие части 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;
};
Я добавил следующие переменные / методы в CSimpleObject
:
private:
HANDLE thread;
IStream *pIS;
static unsigned int __stdcall Thread(void *arg);
Ниже приведена реализация маршалинга интерфейса:
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;
}
Реализация демаршалинга:
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;
}
По запросу, вот реализация fire
метода:
STDMETHODIMP CSimpleObject::fire(void)
{
return Fire_testEvent(L"Test string");
}
Чтобы этот пост был максимально коротким, я опустил полный исходный код. Если требуется дополнительная информация, пожалуйста, запросите ее, оставив комментарий.
ВОПРОС
Как исправить error C2039: 'Fire_testEvent' : is not a member of 'ISimpleObject'
?
МОИ УСИЛИЯ ДЛЯ РЕШЕНИЯ ПРОБЛЕМЫ
Я создал COM-клиент в C ++ и C # для проверки самого события.
Событие было успешно запущено из основного шага и успешно перехвачено обоими COM-клиентами.
В качестве резервного плана я создал новый проект, который использует скрытое окно только для сообщений в главном потоке.
Рабочий поток использует PostMessage
API для связи с этим окном, таким образом уведомляя основной поток при необходимости.
Как только основной поток получает сообщение, событие успешно запускается в обработчике сообщений.
Я все еще гуглюсь / размышляю над решением, я буду обновлять этот пост, если добьюсь прогресса.
обновление # 1: Я добавил ведение журнала везде и получил информацию о том, что CoGetInterfaceAndReleaseStream
завершается с ошибкой Параметр неверен.
обновление № 2:
Я изменил код в соответствии с предложением в комментарии (с hr = CoGetInterfaceAndReleaseStream(pIS,..)
до hr = CoGetInterfaceAndReleaseStream(c->pIS,...
), и клиент C # работал.
Клиент C ++ потерпел неудачу с First-chance exception at 0x77095ef8 in SO_Demo_client.exe: 0x80010108: The object invoked has disconnected from its clients
при попытке pISO->fire()
события из функции Thread (pISO->Fire_testEvent
по-прежнему выдает ту же ошибку, поэтому я изменил цикл for
для использования pISO->fire()
, как это было предложено ранее).
Клиент C ++ сделан с помощью мастера, как консольное приложение Windows.
Ниже приведен соответствующий код:
#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;
}
Будучи новичком в COM (я начал учиться 4 дня назад), и после некоторого поиска в Google, я подозреваю, что где-то допустил ошибку при подсчете ссылок.
Обновление № 3:
После поиска в Google я понял, что у клиентов STA должен быть цикл обработки сообщений, которого не было у моего клиента C ++.
Я добавил типичный цикл сообщений в COM-клиент, и ошибки исчезли.