Can not marshal interface from main thread into worker thread

1

INTRODUCTION

I am buidling in-proc COM server to be consumed by VB6 client.

COM server needs to use blocking function.

This means that the VB6 GUI would be blocked until function retrieves the result, which is unacceptable. 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.

After Googling, I have found out that in STA, interfaces from one thread are inaccessible in the other, and vice versa.

Since I will always have only one worker thread, I have decided to use CoMarshalInterThreadInterfaceInStream for interface marshalling.

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'

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.

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
  • added ATL Simple Object (ticked "ISupportErrorInfo" and "Connection Points" checkboxes) and named it 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:

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:

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:

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' ?

MY EFFORTS TO SOLVE THE PROBLEM

I have created COM client in C++ and C# in order to test the event itself.

Event was fired successfully from the main tread, and was caught successfully by the both COM clients.

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.

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.

update #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.

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_testEventstill gives the same error, so I have changed for loop to use pISO->fire() since it was suggested earlier).

C++ client is made with a wizard, as a Windows Console application. 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.

Update #3:

After Googling around, I realized that STA clients must have message loop, which my C++ client did not have.

I have added typical message loop in the COM client, and errors disappeared.

c++
multithreading
com
marshalling
asked on Stack Overflow Jul 6, 2018 by AlwaysLearningNewStuff • edited Jul 10, 2018 by AlwaysLearningNewStuff

1 Answer

0

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. 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.
1.14.001 CCO Source Code and Data Files (ZIP File)

answered on Stack Overflow Jul 6, 2018 by kunif

User contributions licensed under CC BY-SA 3.0