简体   繁体   English

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

[英]Can not marshal interface from main thread into worker thread

INTRODUCTION 介绍

I am buidling in-proc COM server to be consumed by VB6 client. 我正在建立要由VB6客户端使用的VB6 COM服务器。

COM server needs to use blocking function . COM服务器需要使用阻止功能

This means that the VB6 GUI would be blocked until function retrieves the result, which is unacceptable. 这意味着VB6 GUI将被阻止,直到函数检索到结果为止,这是不可接受的。 Therefore I will use the function in a worker thread, and notify the main thread when function unblocks. 因此,我将在工作线程中使用该函数,并在函数解除阻塞时通知主线程。

Since VB6 GUI runs in single-threaded apartment, I have decided that COM server will use the same threading model. 由于VB6 GUI在单线程单元中运行,因此我决定COM服务器将使用相同的线程模型。

After Googling, I have found out that in STA, interfaces from one thread are inaccessible in the other, and vice versa. 谷歌搜索之后,我发现在STA中,一个线程的接口在另一个线程中不可访问,反之亦然。

Since I will always have only one worker thread, I have decided to use CoMarshalInterThreadInterfaceInStream for interface marshalling. 由于我将始终只有一个工作线程,因此我决定使用CoMarshalInterThreadInterfaceInStream进行接口编组。

PROBLEM: 问题:

After marshaling interface pointer from main thread into the worker one, event firing does not work. 将接口指针从主线程编组到工作线程中后,事件触发将不起作用。

When trying to compile, I get the following: 尝试编译时,我得到以下信息:

error C2039: 'Fire_testEvent' : is not a member of 'ISimpleObject' 错误C2039:“ Fire_testEvent”:不是“ ISimpleObject”的成员

Relevant information follows in the below section. 相关信息在以下部分中。

RELEVANT INFORMATION 相关信息

I am using Visual Studio 2008 on Windows 8.1, COM DLL targets Windows XP or higher. 我在Windows 8.1上使用Visual Studio 2008,COM DLL以Windows XP或更高版本为目标。

Using instructions from this tutorial , I have performed the following steps: 使用本教程中的说明 ,我执行了以下步骤:

  • created COM DLL with ATL Wizard (ticked "Merge Proxy/Stub" checkbox), named it SO_ATL_Demo 使用ATL向导创建的COM DLL(选中“合并代理/存根”复选框),将其命名为SO_ATL_Demo
  • added ATL Simple Object (ticked "ISupportErrorInfo" and "Connection Points" checkboxes) and named it SimpleObject 添加了ATL简单对象(选中“ ISupportErrorInfo”和“连接点”复选框)并将其命名为SimpleObject
  • added method to the main interface named (it should start thread and marshal interface pointer) as instructed in the tutorial 按照本教程中的说明,将方法添加到命名的主接口(它应该启动线程和元数据接口指针)中
  • added method for the event, as instructed in the tutorial 按照教程中的指示为事件添加了方法
  • built the solution 建立解决方案
  • added connection points as instructed in the tutorial 按照教程中的说明添加了连接点

Relevant parts of the IDL: 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;
    };

I have added the following variables/methods to the CSimpleObject : 我已经将以下变量/方法添加到CSimpleObject

private:
    HANDLE thread;
    IStream *pIS;
    static unsigned int __stdcall Thread(void *arg);

Below is the implementation of interface marshaling: 下面是接口封送处理的实现:

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

Unmarshaling implementation: 解组实现:

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

As requested, here is the fire method implementation: 根据要求,这是fire方法的实现:

STDMETHODIMP CSimpleObject::fire(void)
{
    return Fire_testEvent(L"Test string");
}

In order to keep this post as short as possible, I have omitted full source code. 为了使这篇文章尽可能短,我省略了完整的源代码。 If further info is required please request for it by leaving a comment. 如果需要更多信息,请在评论中提出要求。

QUESTION

How to fix error C2039: 'Fire_testEvent' : is not a member of 'ISimpleObject' ? 如何解决error C2039: 'Fire_testEvent' : is not a member of 'ISimpleObject'

MY EFFORTS TO SOLVE THE PROBLEM 我为解决问题所做的努力

I have created COM client in C++ and C# in order to test the event itself. 为了测试事件本身,我已经用C ++和C#创建了COM客户端。

Event was fired successfully from the main tread, and was caught successfully by the both COM clients. 事件已从主页面成功触发,并且被两个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. 工作线程使用PostMessage API与该窗口进行通信,从而在需要时通知主线程。

Once main thread receives the message, event is fired successfully in message handler. 主线程接收到消息后,将在消息处理程序中成功触发事件。

I am still Googling/pondering for a solution, I will update this post if I make any progress. 我仍在谷歌搜索/寻找解决方案,如果取得任何进展,我将更新此帖子。

update #1: I have added logging everywhere, and got the info that CoGetInterfaceAndReleaseStream fails with error The parameter is incorrect. 更新#1:我到处都添加了日志记录,并得到了信息CoGetInterfaceAndReleaseStream失败,并显示错误参数错误。

update #2: 更新#2:

I have changed the code as suggested in the comment (from hr = CoGetInterfaceAndReleaseStream(pIS,..) to hr = CoGetInterfaceAndReleaseStream(c->pIS,... ), and the C# client worked. 我已经按照注释中的建议更改了代码(从hr = CoGetInterfaceAndReleaseStream(pIS,..)更改为hr = CoGetInterfaceAndReleaseStream(c->pIS,... ),并且C#客户端正常工作。

C++ client failed with First-chance exception at 0x77095ef8 in SO_Demo_client.exe: 0x80010108: The object invoked has disconnected from its clients when trying to pISO->fire() event from Thread function ( pISO->Fire_testEvent still gives the same error, so I have changed for loop to use pISO->fire() since it was suggested earlier). 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 clientspISO->Fire_testEvent仍然给出相同的错误,自从之前建议以来,我已经将for循环更改for使用pISO->fire() )。

C++ client is made with a wizard, as a Windows Console application. C ++客户端是使用向导作为Windows控制台应用程序制作的。 Below is the relevant code: 下面是相关代码:

#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;
}

Being new to COM (I have started learning 4 days ago), and after some Googling, I suspect that I made a mistake somewhere in reference counting. 对COM还是陌生的(我已经在4天前开始学习),并且在使用Google谷歌搜索之后,我怀疑我在引用计数方面犯了一个错误。

Update #3: 更新#3:

After Googling around, I realized that STA clients must have message loop, which my C++ client did not have. 谷歌搜索之后,我意识到STA客户端必须具有消息循环,而我的C ++客户端则没有。

I have added typical message loop in the COM client, and errors disappeared. 我在COM客户端中添加了典型的消息循环,错误消失了。

The backup plan that you tried would be the simplest solution. 您尝试的备份计划将是最简单的解决方案。

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. 工作线程使用PostMessage API与该窗口进行通信,从而在需要时通知主线程。 Once main thread receives the message, event is fired successfully in message handler. 主线程接收到消息后,将在消息处理程序中成功触发事件。

Although it is OCX, there are programs that are running about 19 years ago in that way. 尽管它是OCX,但是某些程序以这种方式运行大约19年前。
1.14.001 CCO Source Code and Data Files (ZIP File) 1.14.001 CCO源代码和数据文件(ZIP文件)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM