Click here to Skip to main content
15,887,746 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more: , +
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.
Posted
Updated 9-Jul-18 23:12pm
v6
Comments
Richard MacCutchan 6-Jul-18 5:42am    
I do not see a definition of Fire_testEvent anywhere in the above code.
AlwaysLearningNewStuff 6-Jul-18 6:00am    
It is auto-generated by the ATL wizard. Will it help you if I add it in the post (I tried to keep it as brief as possible, that's why I omitted it) ?
Richard MacCutchan 6-Jul-18 6:17am    
The issue is simple; you are trying to call a method on an object which does not contain that method. I have not used the ATL wizard, but I assume you have to include a full definition of each method that you want in your interface. You have definitions of fire and testEvent, but where is the definition of Fire_testEvent?
AlwaysLearningNewStuff 10-Jul-18 5:16am    
Everything works now!

You were right, I had to add method to the interface that fires the event.

I did so from the start but because I failed to spot a typo (see Update #1) I thought that I did something wrong.

My paranoia became even bigger when C++ client failed to work properly.

As stated in the latest update, I have implemented it badly (no message loop).

After fixing all these "small" mistakes, everything works fine and as expected.

Thanks for the suggestions and for reaching out to help.

Best regards until next time! ;)
Nelek 6-Jul-18 6:25am    
Fully Off-Topic. I just gave you a 5 vote for having the best question I have seen in a time.

1 solution

You are NOT only marshalling the interface, but the object and all related obejcts and their memory. That wont work!!!

You need to create the instance of the object in the worker thread and do the work. Best is to provide some output memory for the results which the worker thread must write.

Your error message is a compiler error which means that your ISimpleObject hasnt the function. Create a object of the class which has this function.

To do this you must implement it in the interface of ISimpleObject and implement it correct. Now it is only in the event chain of the idl.
 
Share this answer
 
Comments
Richard MacCutchan 6-Jul-18 7:00am    
I'm glad there are still people around who really understand COM. I never quite got to grips with marshalling (too lazy I guess).

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



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