c++ - 无法将接口(interface)从主线程编码到工作线程

标签 c++ multithreading com marshalling

介绍

我正在构建进程内 COM 服务器以供 VB6 使用客户。

COM服务器需要使用blocking function .

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

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

谷歌搜索后,我发现在 STA 中,一个线程的接口(interface)在另一个线程中是不可访问的,反之亦然。

因为我总是只有一个工作线程,所以我决定使用 CoMarshalInterThreadInterfaceInStream用于接口(interface)编码。

问题:

将主线程的接口(interface)指针编码到工作线程后,事件触发不起作用。

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

error C2039: 'Fire_testEvent' : is not a member of 'ISimpleObject'



相关信息见下节。

相关信息

我在 Windows 8.1 上使用 Visual Studio 2008,COM DLL 面向 Windows XP 或更高版本。

使用来自 this tutorial 的说明,我执行了以下步骤:
  • 使用 ATL 向导创建 COM DLL(勾选“合并代理/ stub ”复选框),将其命名为 SO_ATL_Demo
  • 添加 ATL 简单对象(勾选“ISupportErrorInfo”和“连接点”复选框)并将其命名为 SimpleObject
  • 按照教程
  • 中的说明,将方法添加到名为(它应该启动线程和编码接口(interface)指针)的主接口(interface)中
  • 根据教程
  • 中的说明为事件添加了方法
  • 构建解决方案
  • 按照教程
  • 中的说明添加连接点

    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);
    

    下面是接口(interface)编码的实现:
    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() 时来自 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 天前开始学习),经过一些谷歌搜索,我怀疑我在引用计数的某个地方犯了错误。

    更新#3:

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

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

    最佳答案

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

    As a back-up plan, I have created new project that uses hidden message-only window in the main thread. Worker thread uses PostMessage API to communicate with this window, thus notifying the main thread when needed. Once main thread receives the message, event is fired successfully in message handler.



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

    关于c++ - 无法将接口(interface)从主线程编码到工作线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51206042/

    相关文章:

    c++ - 使用宏计算源文件行数?

    c++ - 从集合列表中删除重复项

    c++ - ThreadSanitizer 在使用嵌入式引用计数器时报告 "data race on operator delete(void*)"

    C# #import 从缺失的类型库中引用了一个类型; '__missing_type__' 用作占位符

    python - 如何从 Python 3.x 调用 CAPL 通用函数?

    c++ - 在 C++ 中调用 COM 时如何包装错误?

    c++ - 多线程boost asio中的随机EOF

    c++ - 如何链接到静态库?

    c++ - 是否可以获取 atomic_int 的底层存储地址?

    java - Thread.sleep()的CPU消耗