簡體   English   中英

無法將接口從主線程編組到工作線程

[英]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或更高版本為目標。

使用本教程中的說明 ,我執行了以下步驟:

  • 使用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客戶端成功捕獲。

作為備份計划,我創建了一個新項目,該項目在主線程中使用隱藏的僅消息窗口

工作線程使用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 clientspISO->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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM