AlwaysLearningNewStuff Ответов: 1

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


ВСТУПЛЕНИЕ

Я Новостройка внутрипроцессный com-сервер для использования клиентом в VB6.

COM-сервер необходимо использовать блокирующая функция[^].

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

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

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

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

ПРОБЛЕМА

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

При попытке компиляции я получаю следующее:
error C2039: 'Fire_testEvent' : is not a member of 'ISimpleObject'

Я добавил ведение журнала везде, и кажется, что CoGetInterfaceAndReleaseStream выдает ошибку Этот параметр неверен.

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

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

Я использую 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;
}

Чтобы сделать этот пост как можно короче, я опустил полный исходный код. Если требуется дополнительная информация, пожалуйста, запросите ее, оставив комментарий.

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

Thread proc объявляет свой собственный IStream указатель, pIS, инициализируется в NULL и с тех пор никогда не менялся.

Я использую c->pIS для CoGetInterfaceAndReleaseStream аргумент.

Клиент 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_testEventstill дает ту же ошибку, поэтому я изменил цикл для использования 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 дня назад), и после некоторого гугления я подозреваю, что где-то ошибся в подсчете ссылок.

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

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

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

COM-сервер был хорош, клиент C++ был плохо закодирован...

ВОПРОС

Как это исправить error C2039: 'Fire_testEvent': is not a member of 'ISimpleObject'?

Что я уже пробовал:

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

Событие было успешно запущено с главного протектора и успешно поймано обоими клиентами COM.

В качестве резервного плана я создал новый проект, который использует скрытые окно только для сообщений[^] в основном потоке.

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

Как только основной поток получает сообщение, событие успешно запускается в обработчике сообщений.

Я все еще гуглю/обдумываю решение, я обновлю этот пост, если добьюсь какого-либо прогресса.

Richard MacCutchan

Я не вижу определения этого понятия. Fire_testEvent в любом месте приведенного выше кода.

AlwaysLearningNewStuff

Он автоматически генерируется мастером ATL. Поможет ли вам, если я добавлю его в пост (я постарался сделать его как можно более кратким, вот почему я его опустил) ?

Richard MacCutchan

Проблема проста: вы пытаетесь вызвать метод для объекта, который не содержит этого метода. Я не использовал мастер ATL, но я предполагаю, что вы должны включить полное определение каждого метода, который вы хотите в свой интерфейс. У вас есть определения fire и testEvent, но где же определение понятия Fire_testEvent?

AlwaysLearningNewStuff

Теперь все работает!

Вы были правы, я должен был добавить метод к интерфейсу, который запускает событие.

Я сделал это с самого начала, но поскольку мне не удалось обнаружить опечатку (см. обновление № 1), я подумал, что сделал что-то не так.

Моя паранойя стала еще больше, когда клиент C++ перестал работать должным образом.

Как указано в последнем обновлении, я реализовал его плохо (нет цикла сообщений).

После исправления всех этих "мелких" ошибок все работает нормально и так, как и ожидалось.

Спасибо за предложения и за то, что обратились за помощью.

С наилучшими пожеланиями до следующего раза! ;)

Nelek

Совершенно Не По Теме. Я только что дал вам 5 голосов за то, что у вас самый лучший вопрос, который я когда-либо видел.

AlwaysLearningNewStuff

Спасибо за голосование :)

Richard MacCutchan

Я бы предложил открыть новый вопрос, поскольку уже не ясно, в чем заключается проблема. Если метод Fire_testEvent генерируется автоматически, то вам нужно показать фактический код, который его содержит.

AlwaysLearningNewStuff

Я постараюсь перефразировать название, чтобы ОП и обновления стали связным содержанием.

Это займет некоторое время, но я сделаю это...

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

Сначала я потерпел неудачу с Истримом, теперь я потерпел неудачу в другом месте...

Спасибо за ваши предложения.

1 Ответов

Рейтинг:
8

KarstenK

Вы не только сортируете интерфейс, но и объект, и все связанные с ним obejcts, и их память. Это не сработает!!!

Вам нужно создать экземпляр объекта в рабочем потоке и выполнить эту работу. Лучше всего предоставить некоторую выходную память для результатов, которые должен записать рабочий поток.

Ваше сообщение об ошибке-это ошибка компилятора, которая означает, что ваш ISimpleObject не имеет этой функции. Создайте объект класса, который имеет эту функцию.

Для этого вы должны реализовать его в интерфейсе ISimpleObject и реализовать его правильно. Теперь это только в событийной цепочке idl.


Richard MacCutchan

Я рад, что все еще есть люди, которые действительно понимают COM. Я никогда полностью не справлялся с сортировкой (наверное, слишком ленив).