?
Solved

Using COM Objects with Multithreading and ATL7

Posted on 2003-02-24
8
Medium Priority
?
1,490 Views
Last Modified: 2013-12-14
I have a COM server and a COM client, both written with ATL7.

In the server, I need to keep a list of all the objects created and under particular situations fire events to these objects.

At the moment, in the FinalConstruct of the object, I keep the 'this' pointer in an array.

Then in another part of the code, when a receive a message from another application, I iterate throught the array and call the OnEvent method create by ATL to fire the event.

This work fine if the FinalConstruct and the location where I fire the event are in the same thread but as it happends, my application is multithreaded and the thread firing the event is not the same thread as the thread that created the object, so I get a 0x8001010E error code.

Given that I have the pointer to the object, what can I do to fire my event in the second thread?
0
Comment
Question by:mridey
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
8 Comments
 

Author Comment

by:mridey
ID: 8014110
Here's a bit of code:

// INovadMsg
[
     object,
     uuid("AF4D93C9-54DE-4FBA-9183-F24CAF4DC2E1"),
     dual,     helpstring("INovadMsg Interface"),
     pointer_default(unique)
]
__interface INovadMsg : IDispatch
{
};


// _INovadMsgEvents
[
     dispinterface,
     uuid("5EB4E8DF-13DA-4E2D-876A-CD19D022A4D0"),
     helpstring("_INovadMsgEvents Interface")
]
__interface _INovadMsgEvents
{
     [id(1), helpstring("method OnMsg")] HRESULT OnMsg([in] BSTR xmlMsg);
};


// CNovadMsg

[
     coclass,
     threading("both"),
     event_source("com"),
     vi_progid("NovadEvents.NovadMsg"),
     progid("NovadEvents.NovadMsg.1"),
     version(1.0),
     uuid("84087C9F-DF1A-4FE5-B67C-42483AEE8F5B"),
     helpstring("NovadMsg Class")
]
class ATL_NO_VTABLE CNovadMsg :
     public INovadMsg
{
public:
     CNovadMsg()
     {
     }

     __event __interface _INovadMsgEvents;

     DECLARE_PROTECT_FINAL_CONSTRUCT()

     HRESULT FinalConstruct();
     void FinalRelease();

};

and

#include <list>
std::list<CNovadMsg*> g_Controls;

// CNovadMsg

HRESULT CNovadMsg::FinalConstruct()
{
     // Add object to the list of connected controls
     g_Controls.push_front(this);
     return S_OK;
}
     
void CNovadMsg::FinalRelease()
{
     // Remove control from the list of connected controls
     g_Controls.remove(this);
}

and generated by ATL inside CNovadMsg header file

#injected_line
    HRESULT OnMsg(::BSTR i1)
    {
        HRESULT hr = S_OK;
        IConnectionPointImpl<CNovadMsg, &__uuidof(_INovadMsgEvents), CComDynamicUnkArray>* p = this;
        VARIANT rgvars[1];
        Lock();
        IUnknown** pp = p->m_vec.begin();
        Unlock();
        while (pp < p->m_vec.end()) {
            if (*pp != NULL) {
                IDispatch* pDispatch = (IDispatch*) *pp;
                ::VariantInit(&rgvars[0]);
                rgvars[0].vt = VT_BSTR;
                V_BSTR(&rgvars[0])= (BSTR) i1;
                DISPPARAMS disp = { rgvars, NULL, 1, 0 };
                VARIANT ret_val;
                hr = __ComInvokeEventHandler(pDispatch, 1, 1, &disp, &ret_val);
                if (FAILED(hr)) {
                    break;
                }
            }
            pp++;
        }
        return hr;
    }

//--- End Injected Code For Attribute 'event'

the __ComInvokeEventHandler call returns 0x8001010e
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 8014128
Out of my area of expertise, but maybe you could keep the thread ID as well as the this pointer, then PostThreadMessage to that thread, asking *it* to fire its own event.  If that is stupid, please disregard because I'm actually just
Listening...
0
 

Author Comment

by:mridey
ID: 8014197
Yes, that would work and I'm already looking at this work-around but I like to keep my code clean of work-arounds. I'm trying to learn of the proper way to handle this problem. Thanks anyway.
0
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 

Author Comment

by:mridey
ID: 8014691
I found a series of Dr. GUI articles that partially touch to this problem.
They are:

"Ask Dr. GUI #57" (March/April 2001)
"Dr. GUI and COM Events, Part 1" (August 23, 1999)
"Dr. GUI and COM Events, Part 2" (October 25, 1999)

And the work-around solution for ATL using a hidden window created during the FinalConstruct is:

"PRB: Firing Event in Second Thread Causes IPF or GPF Q 196026"

But i'm still interested in someone with a solution close to this description by Dr GUI:

"" The good doctor mentioned before that if you create a new thread, you cannot fire events from it. Although it's tempting to simply call Fire_Changed from the new thread, this is dead wrong.

The problem is that the thread that stores the event interface pointer (when it calls IConnectionPoint::Advise) isn't the same thread as the thread using the stored pointer (when it calls Fire_Changed). The code in Fire_Changed is using the stored interface pointer directly -- the code is:

CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);

This violates COM's rule that you can't pass an unmarshaled (raw) interface pointer across threads. This is easy to ignore because the interface pointers are pretty well hidden in ATL and the Fire_ method. But you still have to do it right. After all, you have no way of knowing whether your client is capable of handling calls on different threads -- i.e., whether the client is capable of multi-threaded operation. Many aren't.

To fix this, you'll have to marshal the interface pointer in IConnectionPoint::Advise (while you're running on the original thread) and unmarshal it in Fire_Changed (while you're running on the new thread). Don't forget to call CoInitializeEx from the new thread; this is necessary for all threads that use COM. ""

Or maybe a clue as how to override the IConnectionPoint::Advise and Unadvise methods in my COM object ?




0
 
LVL 3

Accepted Solution

by:
elcapitan earned 2000 total points
ID: 8015170
1. Find in your project the class that inherits from IConnectionPointImpl. (should be ProjectNameCP.h).
2. Override the advise and unadvise methods. Something like that:
STDMETHOD(Advise)(IUnknown* pUnkSink, DWORD* pdwCookie)
{
     T* pT = static_cast<T*>(this);
     IUnknown* p;
     HRESULT hRes = S_OK;
     if (pUnkSink == NULL || pdwCookie == NULL)
          return E_POINTER;
     IID iid;
     GetConnectionInterface(&iid);
     hRes = pUnkSink->QueryInterface(iid, (void**)&p);
     if (SUCCEEDED(hRes))
     {
          pT->Lock();

          IStream* pStream = NULL;

          // marshal interface
          hRes = AtlMarshalPtrInProc(p,IID_IUnknown ,&pStream);

          if (SUCCEEDED(hRes))
          {
               p->Release();
               p=NULL;
               *pdwCookie = m_vec.Add(pStream);
               hRes = (*pdwCookie != NULL) ? S_OK : CONNECT_E_ADVISELIMIT;
          }

          pT->Unlock();
          if (hRes != S_OK)
          {
               pStream->Release();
               pStream=NULL;
          }

     }
     else if (hRes == E_NOINTERFACE)
          hRes = CONNECT_E_CANNOTCONNECT;
     if (FAILED(hRes))
          *pdwCookie = 0;
     return hRes;
}
 
STDMETHOD(Unadvise)(DWORD dwCookie)
{
     T* pT = static_cast<T*>(this);
     pT->Lock();
//     IUnknown* p = _CDV::GetUnknown(dwCookie);
     IUnknown* p = NULL;
     IStream *pStream = reinterpret_cast<IStream*>(_CDV::GetUnknown(dwCookie));
     if(pStream)
     {
          AtlUnmarshalPtr(pStream,IID_IUnknown,&p);  
          AtlFreeMarshalStream(pStream);
          pStream=NULL;
     }
     HRESULT hRes = m_vec.Remove(dwCookie) ? S_OK : CONNECT_E_NOCONNECTION;
     pT->Unlock();
     if (hRes == S_OK && p != NULL)
     {
          p->Release();
          p=NULL;
     }
         
     return hRes;
}

3. Don't forget to unmarshal IStream pointer in the Fire_XXX methods. something like that:
instead of:
IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
add:
IDispatch* pDispatch = NULL;
::IStream* pStream = reinterpret_cast<::IStream*>(sp.p);
HRESULT hr = AtlUnmarshalPtr(pStream ,IID_IDispatch,(IUnknown**)&pDispatch);

I did all that long time ago, so I hope I didn't forget anything.

--EC--
0
 
LVL 22

Expert Comment

by:ambience
ID: 8015449
there are so many different ways to fire events from different threads, the easiest being that of using PostMessageXXXX etc.

Besides that you can use CoMarshalInterThreadInterfaceInStream and CoGetInterfaceAndReleaseStream to do your own interface marshalling across threads.

Under the situation that you have described using a global interface table might be the easiest approach.

The following url describes a class that you can use inplace of the regular IConnectionPointImpl to make your component's events accessible across threads. Note that it too uses the global interface table.

http://support.microsoft.com/default.aspx?scid=KB;en-us;q280512
0
 
LVL 22

Expert Comment

by:ambience
ID: 8015513
One more thing if you plan to use the GIT (Global interface Table) be sure to check out the platform you are developing for, i cant seem to remeber exactly but i guess IGlobalInterfaceTable is not avaialble on NT 4.0 (ofcourse then you have to install service packs).

0
 

Author Comment

by:mridey
ID: 8015673
here's the adjusted code, maybe the changes are due to ATL7.

HRESULT CNovadMsg::Advise(IUnknown* pUnkSink, DWORD* pdwCookie)
{
          IConnectionPointImpl<CNovadMsg, &__uuidof(_INovadMsgEvents), CComDynamicUnkArray>* x = this;
//    T* pT = static_cast<T*>(this);
    IUnknown* p;
    HRESULT hRes = S_OK;
    if (pUnkSink == NULL || pdwCookie == NULL)
         return E_POINTER;
    IID iid;
    x->GetConnectionInterface(&iid);
    hRes = pUnkSink->QueryInterface(iid, (void**)&p);
    if (SUCCEEDED(hRes))
    {
         /*pT->*/ Lock();

         IStream* pStream = NULL;

         // marshal interface
         hRes = AtlMarshalPtrInProc(p,IID_IUnknown ,&pStream);

         if (SUCCEEDED(hRes))
         {
              p->Release();
              p=NULL;
              *pdwCookie = x->m_vec.Add(pStream);
              hRes = (*pdwCookie != NULL) ? S_OK : CONNECT_E_ADVISELIMIT;
         }

         /*pT->*/ Unlock();
         if (hRes != S_OK)
         {
              pStream->Release();
              pStream=NULL;
         }

    }
    else if (hRes == E_NOINTERFACE)
         hRes = CONNECT_E_CANNOTCONNECT;
    if (FAILED(hRes))
         *pdwCookie = 0;
    return hRes;
}

HRESULT CNovadMsg::Unadvise(DWORD dwCookie)
{
          IConnectionPointImpl<CNovadMsg, &__uuidof(_INovadMsgEvents), CComDynamicUnkArray>* x = this;
//    T* pT = static_cast<T*>(this);
    /*pT->*/ Lock();
//     IUnknown* p = _CDV::GetUnknown(dwCookie);
    IUnknown* p = NULL;
    IStream *pStream = reinterpret_cast<IStream*>(x->m_vec._CDV::GetUnknown(dwCookie));
    if(pStream)
    {
         AtlUnmarshalPtr(pStream,IID_IUnknown,&p);  
         AtlFreeMarshalStream(pStream);
         pStream=NULL;
    }
    HRESULT hRes = x->m_vec.Remove(dwCookie) ? S_OK : CONNECT_E_NOCONNECTION;
    /*pT->*/ Unlock();
    if (hRes == S_OK && p != NULL)
    {
         p->Release();
         p=NULL;
    }
         
    return hRes;
}

HRESULT CNovadMsg::OnMsgStream(::BSTR i1)
{
    HRESULT hr = S_OK;
    IConnectionPointImpl<CNovadMsg, &__uuidof(_INovadMsgEvents), CComDynamicUnkArray>* p = this;
    VARIANT rgvars[1];
    Lock();
    IUnknown** pp = p->m_vec.begin();
    Unlock();
    while (pp < p->m_vec.end()) {
        if (*pp != NULL) {
//            IDispatch* pDispatch = (IDispatch*) *pp;
                              IDispatch* pDispatch = NULL;
                              ::IStream* pStream = (::IStream*) *pp;
                              HRESULT hr = AtlUnmarshalPtr(pStream ,IID_IDispatch,(IUnknown**)&pDispatch);
            ::VariantInit(&rgvars[0]);
            rgvars[0].vt = VT_BSTR;
            V_BSTR(&rgvars[0])= (BSTR) i1;
            DISPPARAMS disp = { rgvars, NULL, 1, 0 };
            VARIANT ret_val;
            hr = __ComInvokeEventHandler(pDispatch, 1, 1, &disp, &ret_val);
            if (FAILED(hr)) {
                break;
            }
        }
        pp++;
    }
    return hr;
}

Works great.
0

Featured Post

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

IntroductionThis article is the second in a three part article series on the Visual Studio 2008 Debugger.  It provides tips in setting and using breakpoints. If not familiar with this debugger, you can find a basic introduction in the EE article loc…
In this article you will learn how to create a free basic website on Bitbucket, a git service provider. Polymer creates dynamic HTML components, which allow more flexibility than static HTML. This tutorial uses Ubuntu Linux but can also be done on W…
The purpose of this video is to demonstrate how to set up the WordPress backend so that each page automatically generates a Mailchimp signup form in the sidebar. This will be demonstrated using a Windows 8 PC. Tools Used are Photoshop, Awesome…
The viewer will learn how to use and create new code templates in NetBeans IDE 8.0 for Windows.
Suggested Courses

800 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question