[Webinar] Streamline your web hosting managementRegister Today

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1508
  • Last Modified:

Using COM Objects with Multithreading and ATL7

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
mridey
Asked:
mridey
1 Solution
 
mrideyAuthor Commented:
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
 
DanRollinsCommented:
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
 
mrideyAuthor Commented:
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
The new generation of project management tools

With monday.com’s project management tool, you can see what everyone on your team is working in a single glance. Its intuitive dashboards are customizable, so you can create systems that work for you.

 
mrideyAuthor Commented:
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
 
elcapitanCommented:
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
 
ambienceCommented:
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
 
ambienceCommented:
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
 
mrideyAuthor Commented:
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

The new generation of project management tools

With monday.com’s project management tool, you can see what everyone on your team is working in a single glance. Its intuitive dashboards are customizable, so you can create systems that work for you.

Tackle projects and never again get stuck behind a technical roadblock.
Join Now