[英]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.