Click here to Skip to main content
15,885,767 members

Can not marshal interface from main thread into worker thread

AlwaysLearningNewStuff asked:

Open original thread
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 appartment, 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 marshaling.

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'

I have added logging everywhere, and it seems that CoGetInterfaceAndReleaseStream fails with error The parameter is incorrect.

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:
C++
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:
C++
private:
    HANDLE thread;
    IStream *pIS;
    static unsigned int __stdcall Thread(void *arg);

Below is the implementation of interface marshaling:
C++
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:
C++
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;
}

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.

Update #1:

Thread proc is declaring its own IStream pointer, pIS, initialized to NULL and never changed thereafter.

I use c->pIS for CoGetInterfaceAndReleaseStream argument.

C# client worked, but C++ client fails 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()).

C++ client is made with a wizard, as a Windows Console application. Below is the relevant code:

C++
#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 #2:

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.

COM server was good, the C++ client was badly coded...

QUESTION

How to fix error C2039: 'Fire_testEvent': is not a member of 'ISimpleObject'?

What I have tried:

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.
Tags: C++, COM, Multi-threading, Marshalling

Plain Text
ASM
ASP
ASP.NET
BASIC
BAT
C#
C++
COBOL
CoffeeScript
CSS
Dart
dbase
F#
FORTRAN
HTML
Java
Javascript
Kotlin
Lua
MIDL
MSIL
ObjectiveC
Pascal
PERL
PHP
PowerShell
Python
Razor
Ruby
Scala
Shell
SLN
SQL
Swift
T4
Terminal
TypeScript
VB
VBScript
XML
YAML

Preview



When answering a question please:
  1. Read the question carefully.
  2. Understand that English isn't everyone's first language so be lenient of bad spelling and grammar.
  3. If a question is poorly phrased then either ask for clarification, ignore it, or edit the question and fix the problem. Insults are not welcome.
  4. Don't tell someone to read the manual. Chances are they have and don't get it. Provide an answer or move on to the next question.
Let's work to help developers, not make them feel stupid.
Please note that all posts will be submitted under the http://www.codeproject.com/info/cpol10.aspx.



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900