Не удается маршалировать интерфейс из основного потока в рабочий поток
ВСТУПЛЕНИЕ
Я Новостройка внутрипроцессный com-сервер для использования клиентом в VB6.
COM-сервер необходимо использовать блокирующая функция[^].
Это означает, что графический интерфейс VB6 будет заблокирован до тех пор, пока функция не получит результат, что недопустимо. Поэтому я буду использовать эту функцию в рабочем потоке и уведомлять основной поток, когда функция разблокируется.
Поскольку графический интерфейс VB6 работает в однопоточной квартире, я решил, что COM-сервер будет использовать ту же модель потоковой передачи.
Погуглив, я выяснил, что в STA интерфейсы из одного потока недоступны в другом, и наоборот.
Поскольку у меня всегда будет только один рабочий поток, я решил использовать CoMarshalInterThreadInterfaceInStream [^] для маршалинга интерфейсов.
ПРОБЛЕМА
После маршалинга указателя интерфейса из основного потока в рабочий, запуск события не работает.
При попытке компиляции я получаю следующее:
error C2039: 'Fire_testEvent' : is not a member of 'ISimpleObject'
Я добавил ведение журнала везде, и кажется, что
CoGetInterfaceAndReleaseStream
выдает ошибку Этот параметр неверен.Соответствующая информация приводится в следующем разделе.
СООТВЕТСТВУЮЩАЯ ИНФОРМАЦИЯ
Я использую 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; }
Чтобы сделать этот пост как можно короче, я опустил полный исходный код. Если требуется дополнительная информация, пожалуйста, запросите ее, оставив комментарий.
Обновление №1:
Thread proc объявляет свой собственный
IStream
указатель, pIS
, инициализируется в NULL
и с тех пор никогда не менялся. Я использую
c->pIS
для CoGetInterfaceAndReleaseStream
аргумент.Клиент C# работал, но клиент C++ терпит неудачу с
First-chance exception at 0x77095ef8 in SO_Demo_client.exe: 0x80010108: The object invoked has disconnected from its clients
когда пытаешься это сделать pISO->fire()
событие из функции потока ( pISO->Fire_testEventstill
дает ту же ошибку, поэтому я изменил цикл для использования 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 дня назад), и после некоторого гугления я подозреваю, что где-то ошибся в подсчете ссылок.
Обновление №2:
Погуглив вокруг, я понял, что STA клиенты должен есть цикл сообщений, которого не было у моего клиента C++.
Я добавил типичный цикл сообщений в COM-клиенте, и ошибки исчезли.
COM-сервер был хорош, клиент C++ был плохо закодирован...
ВОПРОС
Как это исправить
error C2039: 'Fire_testEvent': is not a member of 'ISimpleObject'
?Что я уже пробовал:
Я создал COM-клиент на C++ и C#, чтобы протестировать само событие.
Событие было успешно запущено с главного протектора и успешно поймано обоими клиентами COM.
В качестве резервного плана я создал новый проект, который использует скрытые окно только для сообщений[^] в основном потоке.
Использование рабочего потока
PostMessage
API для связи с этим окном, таким образом уведомляя основной поток при необходимости.Как только основной поток получает сообщение, событие успешно запускается в обработчике сообщений.
Я все еще гуглю/обдумываю решение, я обновлю этот пост, если добьюсь какого-либо прогресса.
Richard MacCutchan
Я не вижу определения этого понятия. Fire_testEvent
в любом месте приведенного выше кода.
AlwaysLearningNewStuff
Он автоматически генерируется мастером ATL. Поможет ли вам, если я добавлю его в пост (я постарался сделать его как можно более кратким, вот почему я его опустил) ?
Richard MacCutchan
Проблема проста: вы пытаетесь вызвать метод для объекта, который не содержит этого метода. Я не использовал мастер ATL, но я предполагаю, что вы должны включить полное определение каждого метода, который вы хотите в свой интерфейс. У вас есть определения fire
и testEvent
, но где же определение понятия Fire_testEvent
?
AlwaysLearningNewStuff
Теперь все работает!
Вы были правы, я должен был добавить метод к интерфейсу, который запускает событие.
Я сделал это с самого начала, но поскольку мне не удалось обнаружить опечатку (см. обновление № 1), я подумал, что сделал что-то не так.
Моя паранойя стала еще больше, когда клиент C++ перестал работать должным образом.
Как указано в последнем обновлении, я реализовал его плохо (нет цикла сообщений).
После исправления всех этих "мелких" ошибок все работает нормально и так, как и ожидалось.
Спасибо за предложения и за то, что обратились за помощью.
С наилучшими пожеланиями до следующего раза! ;)
Nelek
Совершенно Не По Теме. Я только что дал вам 5 голосов за то, что у вас самый лучший вопрос, который я когда-либо видел.
AlwaysLearningNewStuff
Спасибо за голосование :)
Richard MacCutchan
Я бы предложил открыть новый вопрос, поскольку уже не ясно, в чем заключается проблема. Если метод Fire_testEvent генерируется автоматически, то вам нужно показать фактический код, который его содержит.
AlwaysLearningNewStuff
Я постараюсь перефразировать название, чтобы ОП и обновления стали связным содержанием.
Это займет некоторое время, но я сделаю это...
Дело в том, что я пытаюсь маршалировать интерфейс из одной квартиры в другую и терплю неудачу.
Сначала я потерпел неудачу с Истримом, теперь я потерпел неудачу в другом месте...
Спасибо за ваши предложения.