无法将接口从主线程编组到工作线程

时间:2018-07-06 08:26:07

标签: c++ multithreading com marshalling

简介

我正在建立由VB6客户端使用的进程内COM服务器。

COM服务器需要使用blocking function

这意味着VB6 GUI将被阻塞,直到函数检索到结果为止,这是不可接受的。 因此,我将在工作线程中使用该函数,并在函数解除阻塞时通知主线程。

由于VB6 GUI在单线程单元中运行,因此我决定COM服务器将使用相同的线程模型。

在Google搜索之后,我发现在STA中,一个线程的接口无法访问另一个线程,反之亦然。

由于我总是只有一个工作线程,因此我决定使用CoMarshalInterThreadInterfaceInStream进行接口编组。

问题:

将接口指针从主线程编组到工作线程中后,事件触发将不起作用。

尝试编译时,我得到以下信息:

  

错误C2039:“ Fire_testEvent”:不是“ ISimpleObject”的成员

相关信息在以下部分中。

相关信息

我在Windows 8.1上使用Visual Studio 2008,COM DLL以Windows XP或更高版本为目标。

使用来自this tutorial的说明,我执行了以下步骤:

  • 使用ATL向导创建了COM DLL(选中“合并代理/存根”复选框),并将其命名为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'

我为解决问题所做的努力

我已经用C ++和C#创建了COM客户端,以便测试事件本身。

事件已成功从主页面触发,并且被两个COM客户端成功捕获。

作为备份计划,我创建了一个新项目,该项目在主线程中使用隐藏的message-only window

工作线程使用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()失败(pISO->Fire_testEvent仍给出相同的错误,因此我将for循环更改为使用{{ 1}},因为它是之前提出的。

C ++客户端是使用向导作为Windows控制台应用程序制作的。 下面是相关代码:

pISO->fire()

是COM的新手(我已经在4天前开始学习),并且在使用Google搜索之后,我怀疑自己在引用计数方面犯了一个错误。

更新#3:

谷歌搜索之后,我意识到STA客户端必须具有消息循环,而我的C ++客户端则没有。

我在COM客户端中添加了典型的消息循环,并且错误消失了。

1 个答案:

答案 0 :(得分:0)

您尝试的备份计划将是最简单的解决方案。

  

作为备份计划,我创建了一个新项目,该项目在主线程中使用隐藏的仅消息窗口。   工作线程使用PostMessage API与该窗口进行通信,从而在需要时通知主线程。   主线程接收到消息后,便会在消息处理程序中成功触发事件。

尽管它是OCX,但仍有大约19年前以这种方式运行的程序。
1.14.001 CCO Source Code and Data Files (ZIP File)