[英]Can not marshal interface from main thread into worker thread
我正在建立要由VB6
客戶端使用的VB6
COM服務器。
COM服務器需要使用阻止功能 。
這意味着VB6
GUI將被阻止,直到函數檢索到結果為止,這是不可接受的。 因此,我將在工作線程中使用該函數,並在函數解除阻塞時通知主線程。
由於VB6
GUI在單線程單元中運行,因此我決定COM服務器將使用相同的線程模型。
谷歌搜索之后,我發現在STA中,一個線程的接口在另一個線程中不可訪問,反之亦然。
由於我將始終只有一個工作線程,因此我決定使用CoMarshalInterThreadInterfaceInStream進行接口編組。
將接口指針從主線程編組到工作線程中后,事件觸發將不起作用。
嘗試編譯時,我得到以下信息:
錯誤C2039:“ Fire_testEvent”:不是“ ISimpleObject”的成員
相關信息在以下部分中。
我在Windows 8.1上使用Visual Studio 2008,COM DLL以Windows XP或更高版本為目標。
使用本教程中的說明 ,我執行了以下步驟:
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'
?
為了測試事件本身,我已經用C ++和C#創建了COM客戶端。
事件已從主頁面成功觸發,並且被兩個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
嘗試從Thread函數First-chance exception at 0x77095ef8 in SO_Demo_client.exe: 0x80010108: The object invoked has disconnected from its clients
pISO->fire()
事件時, First-chance exception at 0x77095ef8 in SO_Demo_client.exe: 0x80010108: The object invoked has disconnected from its clients
( pISO->Fire_testEvent
仍然給出相同的錯誤,自從之前建議以來,我已經將for
循環更改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:
谷歌搜索之后,我意識到STA客戶端必須具有消息循環,而我的C ++客戶端則沒有。
我在COM客戶端中添加了典型的消息循環,錯誤消失了。
您嘗試的備份計划將是最簡單的解決方案。
作為備份計划,我創建了一個新項目,該項目在主線程中使用隱藏的僅消息窗口。 工作線程使用PostMessage API與該窗口進行通信,從而在需要時通知主線程。 主線程接收到消息后,將在消息處理程序中成功觸發事件。
盡管它是OCX,但是某些程序以這種方式運行大約19年前。
1.14.001 CCO源代碼和數據文件(ZIP文件)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.