Не удается маршалировать интерфейс из основного потока в рабочий поток - PullRequest
0 голосов
/ 06 июля 2018

Введение

Я собираю встроенный COM-сервер для использования клиентом VB6.

COM-сервер должен использовать функцию блокировки .

Это означает, что графический интерфейс VB6 будет заблокирован до тех пор, пока функция не получит результат, что недопустимо. Поэтому я буду использовать функцию в рабочем потоке и сообщать основному потоку, когда функция разблокируется.

Поскольку VB6 GUI работает в однопоточной квартире, я решил, что COM-сервер будет использовать ту же модель потоков.

После поиска в Google я обнаружил, что в STA интерфейсы из одного потока недоступны в другом, и наоборот.

Поскольку у меня всегда будет только один рабочий поток, я решил использовать CoMarshalInterThreadInterfaceInStream для маршалинга интерфейса.

ПРОБЛЕМА:

После маршалинга указателя интерфейса из основного потока в рабочий поток срабатывание события не работает.

При попытке компиляции получаю следующее:

ошибка C2039: «Fire_testEvent»: не является членом «ISimpleObject»

Соответствующая информация приведена в следующем разделе.

СООТВЕТСТВУЮЩАЯ ИНФОРМАЦИЯ

Я использую Visual Studio 2008 в Windows 8.1, COM DLL предназначена для Windows XP или выше.

Используя инструкции из этого урока , я выполнил следующие шаги:

  • создал COM DLL с помощью мастера ATL (поставлен флажок «Merge Proxy / Stub»), назвал его 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'?

МОИ УСИЛИЯ ДЛЯ РЕШЕНИЯ ПРОБЛЕМЫ

Я создал COM-клиент в C ++ и C # для проверки самого события.

Событие было успешно запущено из основного шага и успешно перехвачено обоими 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 при попытке 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 дня назад), и после некоторого поиска в Google, я подозреваю, что где-то допустил ошибку при подсчете ссылок.

Обновление № 3:

После поиска в Google я понял, что у клиентов STA должен быть цикл обработки сообщений, которого не было у моего клиента C ++.

Я добавил типичный цикл сообщений в COM-клиент, и ошибки исчезли.

1 Ответ

0 голосов
/ 06 июля 2018

План резервного копирования, который вы пробовали, был бы самым простым решением.

В качестве резервного плана я создал новый проект, который использует скрытое окно только для сообщений в главном потоке. Рабочий поток использует PostMessage API для связи с этим окном, таким образом уведомляя основной поток при необходимости. Как только основной поток получает сообщение, событие успешно запускается в обработчике сообщений.

Хотя это OCX, есть программы, которые работают примерно 19 лет назад.
1.14.001 Исходный код CCO и файлы данных (ZIP-файл)

...