Link to home
Start Free TrialLog in
Avatar of LuRen
LuRen

asked on

[COM &multithread] why is there a exception happened?

I have created a multithread COM with ATL (VC++ 6.0/Sp5).  I use the ATL wizard. Here is some detail of the COM:
 
    ================================
 
   "New ATL Object..." -> "Objects" -> "Simple Object"  -> after input the name of the COM, i set the attributes of the com below:
           Threading Model : Apartment  (Default)
           Interface       : Dual (Default)
           Aggregation     : Yes (Default)

     Select "Support ISupportErrorInfo"
     Select "Support Connection Points"
     don't select "Free Threaded Marshaler"

   ===========================================

   OK, here is a simple COM created. Now i added methods and event of the COM. just for testing, I added two method:    

StartUp(), Shutdown(), and added three event: OnFirstEvt() , OnSecondEvt(), OnThirdEvt().

   I defined four message : MSG_FIRST, MSG_SECOND, MSG_THIRD, MSG_QUIT

   In StartUp(), I created two thread : DaemonThread, TestThread.

   DaemonThread had a message queue to receive the four message (MSG_FIRST, MSG_SECOND, MSG_THIRD, MSG_QUIT). If it

receive MSG_FIRST, it call fire_OnFirstEvt() and the client (maybe UI) can receive the event.  MSG_SECOND and MSG_THIRD are in the same way. When it receive MSG_QUIT, the thread is finished. here is some codes of it :

     ==============================================

                ...

            while (1) {
                  WaitMessage();
                  while( GetMessage( &msg, NULL, MSG_FIRST, MSG_QUIT ) ) {
                        switch(msg.message) {
                        case MSG_FIRST:
                              lpCOM->Fire_OnFirstEvt();
                              break;

                        case MSG_SECOND:
                              lpCOM->Fire_OnSecondEvt();
                              break;

                        case MSG_THIRD:
                              lpCOM->Fire_OnThirdEvt();
                              break;

                        case MSG_QUIT:
                              return 0;

                        default:
                              break;
                        }
                  }

                ...

     ==============================================

   TestThread is very simple. It send message to DaemonThread. here is some codes of it :
 

     ==============================================

                ...

            status = PostThreadMessage( lpDaemonThreadID, MSG_FIRST, (WPARAM)NULL, (LPARAM)NULL );

            if (status == FALSE) {
                        // ...
                  return -1;
            } else
                        // ...

            Sleep(3000);

            status = PostThreadMessage( lpDaemonThreadID, MSG_SECOND, (WPARAM)NULL, (LPARAM)NULL );

            if (status == FALSE) {
                        // ...
                  return -1;
            } else
                        // ...

            Sleep(3000);

            status = PostThreadMessage( lpDaemonThreadID, MSG_THIRD, (WPARAM)NULL, (LPARAM)NULL );

            if (status == FALSE) {
                        // ...
                  return -1;
            } else
                        // ...

            Sleep(3000);

                ...

     ==============================================

    It is all right Compiling and linking.   Now i use this COM in VB. When i debug it in VB IDE, it is all right : all threads of COM work well, all events are fired well . Then i made a exe file. but when i ran the exe file, a exception was happened and exe were stopped. i don't know what's the matter?

is there anybody who has similiar experience?

any ideas or suggestions will be very appreciated.
Avatar of DanRollins
DanRollins
Flag of United States of America image

What was the exception code?
Avatar of MootPoint
MootPoint

Did you call CoInitialize() on any threads you start?
Some questions?

1. How did you pass lpDaemonThreadID to TestThread?

2. Why did you use an 'lp' prefix in lpDaemonThreadID?

The first argument of PostThreadMessage() is a DWORD and not a pointer.  

3. What happens to the TestThread after posting the three messages?
    Does it terminate or is the showed sequence part of  a (message) loop?
    If it terminates, when does the crash happen? Between the messages or after?

I had a similar crash recently where a thread terminated and freed resources that have been still in use by the main thread. So, maybe  you should post all the code of TestThread.

4. Do you know the system libraries that are linked to your VB program?

I have no experiences with VB, but with C++ you shouldn't mix single-threaded libraries with multi-threaded libraries, as it could crash if a single-threaded function is called from a multithreaded DLL because of wrong linking.


Regards, Alex

When you debug in the VB IDE, everything is single threaded - the VB code that is (your C++ code can and often does create it's own threads). But once compiled the VB code can become multi-threaded as well.

For a VB GUI multi-threading is disastrous - that's why you cannot add a multi-threaded OCX control to a VB form.

You did mention that your [potentially] raising events back to the UI, and this is via multiple threads. Don't quite like the sound of this, and obviously VB doesn't either. It should, of couse, queue these calls as it's the main apartment of the process, but we are talking VB ...

Or MootPoint may have a point (no pun intended) and you're missing CoInitialize calls on every apartment created thread.
Avatar of LuRen

ASKER

>  What was the exception code?
   when the daemonthread receives the first message, the exception happened. If  testthread do not sleep after post first message, maybe the exception will be happened after daemonthread receives second message. (I have tested.)


> Did you call CoInitialize() on any threads you start?
The two threads are started in COM interface method StartUp(). The method is called by VB UI (Maybe a button). Do you mean that i call CoInitialize() in StartUp() before create the thread? I have used CoInitialize() in VC UI before. would you like to explain more detail?

I have tried call CoInitialize() before CreateThread() in StartUp(), and exception is still happened  like before.

>1. How did you pass lpDaemonThreadID to TestThread?

>2. Why did you use an 'lp' prefix in lpDaemonThreadID?

>The first argument of PostThreadMessage() is a DWORD and not a pointer.  

Yes, you are right. I made a mistake with the prefix. it should be dwDaemonThreadID. i pass the parameter throuth lpParam when creating thread.

>3. What happens to the TestThread after posting the three messages?
>    Does it terminate or is the showed sequence part of  a (message) loop?
>    If it terminates, when does the crash happen? Between the messages or after?

The TestThread is just used to test the mechanism of the COM multithread. after it post the four message (include MSG_QUIT), it terminate.

the crash happens after DaemonThread receives the message MSG_FIRST.

>I had a similar crash recently where a thread terminated and freed resources that have been still >in use by the main thread. So, maybe  you should post all the code of TestThread.

All these two threads are just used to test. there is no resource used in them.


>4. Do you know the system libraries that are linked to your VB program?

i don't know. the mechanism of VB is not similiar with VC. The developer needn't to care the libraries. everything is related with component.


      


>When you debug in the VB IDE, everything is single threaded - the VB code that is (your C++ >code can and often does create it's own threads). But once compiled the VB code can become >multi-threaded as well.

i am sure that the threads are all created right when i debug in VB IDE. And i have used "Process Viewer" , to check wheather there is more thread created  when i debugging.  the result is 2 thread added.

>For a VB GUI multi-threading is disastrous - that's why you cannot add a multi-threaded OCX >control to a VB form.

I think i did not use multi-threaded OCX control.  it is a COM made with ATL. and it has no outline, just a COM Object. After referenced, the app can use it . like this :

      ' the following is some codes of VB:

          private WithEvents objTest as OBJTEST          ' OBJTEST is the COM Object i programmed.

>You did mention that your [potentially] raising events back to the UI, and this is via multiple >threads. Don't wuite like the sound of this, and obviously VB doesn't either. It should, of couse, >queue these calls as it's the main apartment of the process, but we are talking VB ...

I think this the way using COM is very useful. For if the app use some hardware, the hardware works and must tell  GUI what happend, GUI can decide how to do  according to the information. maybe somebody have more good idea, i'd like to know it.


>Or MootPoint may have a point (no pun intended) and you're missing CoInitialize calls on every >apartment created thread.

i am waiting for more idea or suggestions.

> I have tried call CoInitialize() before CreateThread() in StartUp(), and exception is still happened  like before.

Again, not sure that this IS your problem, but calling CoInitialize() prior to CreateThread() does NOT initialize COM for the thread you're starting, it HAS to be called WITHIN the thread itself, if that makes any sense.
Avatar of LuRen

ASKER

>Again, not sure that this IS your problem, but calling CoInitialize() prior to CreateThread() does >NOT initialize COM for the thread you're starting, it HAS to be called WITHIN the thread itself, if that >makes any sense.

I have tried calling CoInitialize() in thread (first sentence). the problem is still happened.

if you will, you can make a test like this app. if you need, i can  send all the codes of the test to you.  Thx.

continue to test and waiting for more idea or suggestions.
>> I have tried calling CoInitialize() in thread (first sentence).

MSDN says:

CoInitialize
Initializes the COM library on the current apartment and identifies the concurrency model as single-thread apartment (STA).

You have to use CoInitializeEx instead.

Hope, that helps
Avatar of LuRen

ASKER

>You have to use CoInitializeEx instead.
i have tried CoInitializeEx() and exception is still happened.

BTW, if somebody is interesting in this problem, i'd like send my test projects (VC & VB)  to you. please tell me your mail-address first.  

I think Multithread is often used in COM/COM+. if you're a COM guru, please tell me some suggrestion. TIA.
Just noticed lpCOM is accessed from DaemonThread. How is lpCOM passed into the thread, or is it globally accessible perhaps.

Within COM an object can generally only be accessed from within the apartment it was created in (i.e. the creating thread). This can be further constrained as you chose apartment-threading. Your object is thread bound.

Hence why I ask about lpCOM. If you want to touch it from another thread you gotta work it a little.
>> i have tried CoInitializeEx() and exception is still happened.

You have to use CoInitializeEx() both in TestThread and DaemonThread, say in all threads ...

>> if somebody is interesting in this problem, i'd like send my test projects (VC & VB)  to you

I'm interested, however i'will be not at home til Friday. So, i wouldn't give a quick response. And as i have no VB environment i would need the executable and runtime dlls - if any. If you still want/need help,  send all using a .zip or .rar attach to info@sbsweb.info.

>> if you're a COM guru,

I'm not ... but pretty experienced with C++. i would try to debug the C++ dll and might find out where it crashes...

Regards, Alex
Similarly I've limited email access ATM. However I've provided an email address within my profile, and, owner permitting, I'll access your test project from there.

Unlike itsmeandnobodyelse I do have VB installed (*shudder*).
Avatar of LuRen

ASKER

itsmeandnobodyelse, _ys_
    i have send the codes to your mail-box (a RAR file named test.rar). Please check it.

>Just noticed lpCOM is accessed from DaemonThread. How is lpCOM passed into the thread, or is it >globally accessible perhaps.

>Within COM an object can generally only be accessed from within the apartment it was created in >(i.e. the creating thread). This can be further constrained as you chose apartment-threading. Your >object is thread bound.

>Hence why I ask about lpCOM. If you want to touch it from another thread you gotta work it a little.

i pass the pointer of the COM obj to the thread, is it the reason of the exception?  but i need use COM obj in thread. Please check the codes.  there is not other else pointer passed to the thread.

Avatar of LuRen

ASKER

itsmeandnobodyelse, _ys_

      do you receive the test codes?  
Haven't received anything. Trying a different email address this time.
If you wouldn't mind sending to me again, I'll doctor it.

[email address available within my profile]
Yes, i got it. I'll soon have time to look around.

Regards, Alex
Avatar of LuRen

ASKER

_ys_, I send you the file again.  Please check your mail-box.



Ok, could build eztest.dll - though was already there?

Ok, could install vbtest - whatever it did (don't know much of VB)

However, where is an executable? Double-clicking on any file in vbtest-directory didn't work on my machine.

Made some code analyzing in C++.

>> CoInitializeEx( NULL,COINIT_APARTMENTTHREADED );

MSDN says:
-------------------------------------------------------------------------------------------------------------------------
Apartment-threading, the default model for earlier versions of Windows NT, while allowing for multiple threads of execution, serializes all incoming calls by requiring that calls to methods of objects created by this thread always run on the same thread – the apartment/thread that created them. In addition, calls can arrive only at message-queue boundaries (i.e., only during a PeekMessage, SendMessage, DispatchMessage, etc.). Because of this serialization, it is not typically necessary to write concurrency control into the code for the object, other than to avoid calls to PeekMessage and SendMessage during processing that must not be interrupted by other method invocations or calls to other objects in the same apartment/thread.
-------------------------------------------------------------------------------------------------------------------------

However, in your thread there is a call to PostThreadMessage that is different from PeekMessage, SendMessage, DispatchMessage as required from the threading model. Therefore, you should  do this:

  CoInitializeEx( NULL,COINIT_MULTHITHREADED);

And i recommend this for both threads. if this still doesn't work you should send me an vb executable and a readme how to use it.

Regards, Alex



Avatar of LuRen

ASKER

Thx, itsmeandnobodyelse, I will test as your said and tell you the result.
ASKER CERTIFIED SOLUTION
Avatar of _ys_
_ys_

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of LuRen

ASKER

itsmeandnobodyelse,
    i have a try, and the problem is still exist.

_ys_,
    thx, i think the solution is right. i will test it and tell you the result.
Avatar of LuRen

ASKER

_ys_,
   i have a try and read some reference. there are some questions i am still confused.

   1. how does the CEventProxy.h come from? is it made by the article author?

   2. where does the project( ThreadEvents ) use the CComDynamicUnkArray_GIT(defined in CEventProxy.h)?

   3. I compiled the "ThreadEvents" project and success. But when i use the COM ( EventThreadObj of ThreadEvents.dll) in VB, there is some error happening. do you use it in VB? if you can, plese send me your VB test project. (BTW, do you receive my test codes?)

   I will continue to have a try, and waiting for your response.
I did receive your test project, and that's what I used. I did not modify the VB code at all. As for the C++ code I modifed it as described in the article.

CEventProxy.h was authored by the article author - at least it sounds that way.

The C++ code I modified is provided below.

ATest.cpp
-----------
unsigned long __stdcall DaemonThread(void *lpParameter)
{
    MSG msg;
    CATest* lpObj = reinterpret_cast<CATest*>(lpParameter);

    CoInitialize(NULL);
    CreateMsgQueue();
// ... rest as is
    CoUnitialize();
}

exTestCP.h
-------------
#ifndef _EZTESTCP_H_
#define _EZTESTCP_H_

#include <map>
#include <comdef.h>
#include <atlcom.h>
using namespace std;

class CComDynamicUnkArray_GIT : public CComDynamicUnkArray
{
    private:
        IGlobalInterfaceTable*  GIT;

    public:

        CComDynamicUnkArray_GIT() : CComDynamicUnkArray()
        {
            GIT = NULL;

            CoCreateInstance( CLSID_StdGlobalInterfaceTable,
                              NULL,
                              CLSCTX_INPROC_SERVER,
                              __uuidof(IGlobalInterfaceTable),
                              reinterpret_cast< void** >(&GIT) );
        }

          ~CComDynamicUnkArray_GIT()
          {
            clear();
            if( GIT != NULL )
            {
                GIT->Release();
            }
          }
          DWORD Add(IUnknown* pUnk);
          BOOL Remove(DWORD dwCookie);

        CComPtr<IUnknown> GetAt(int nIndex)
          {
            DWORD dwCookie = (DWORD)CComDynamicUnkArray::GetAt( nIndex );

            if( dwCookie == 0 )
                      return NULL;

            if( CookieMap.find( dwCookie ) == CookieMap.end() )
            {
                    return (IUnknown*)dwCookie;
            }
            if( GIT != NULL )
            {
                CComPtr<IUnknown>   ppv;

                HRESULT hr = GIT->GetInterfaceFromGlobal(
                  CookieMap[dwCookie],              //Cookie identifying the desired global
                                                    //interface and its object
                  __uuidof(IUnknown),               //IID of the registered global interface
                  reinterpret_cast< void** >(&ppv)  //Indirect pointer to the desired interface
                );
                if( hr == S_OK )
                {
                    return ppv;
                }
            }
                return (IUnknown*)dwCookie;
          }

          void clear()
          {
            CComDynamicUnkArray::clear();

            if( GIT != NULL )
            {
                map< DWORD, DWORD >::iterator iter;
                for( iter = CookieMap.begin(); iter != CookieMap.end(); ++iter )
                {
                    GIT->RevokeInterfaceFromGlobal(
                      iter->second              //Cookie that was returned from
                                                //RegisterInterfaceInGlobal
                    );
                }
            }
            CookieMap.clear();
          }
    protected:
        map< DWORD, DWORD > CookieMap;
};

inline DWORD CComDynamicUnkArray_GIT::Add(IUnknown* pUnk)
{
    DWORD Result = CComDynamicUnkArray::Add( pUnk );

    HRESULT hr;
    DWORD   pdwCookie = 0;
    if( GIT != NULL )
    {
        hr = GIT->RegisterInterfaceInGlobal(
          pUnk,                 //Pointer to interface of type riid of object
                                //containing global interface
          __uuidof(IUnknown),   //IID of the interface to be registered
          &pdwCookie            //Supplies a pointer to the cookie that provides
                                //a caller in another apartment access to the
                                //interface pointer
        );
    }
    if( hr == S_OK )
    {
        CookieMap[Result] = pdwCookie;
    }

      return Result;
}

inline BOOL CComDynamicUnkArray_GIT::Remove(DWORD dwCookie)
{
    BOOL Result = CComDynamicUnkArray::Remove( dwCookie );

    if( GIT != NULL )
    {
        if( CookieMap.find( dwCookie ) != CookieMap.end() )
        {
            GIT->RevokeInterfaceFromGlobal(
              CookieMap[dwCookie]   //Cookie that was returned from
                                    //RegisterInterfaceInGlobal
            );
            CookieMap.erase(dwCookie);
        }
    }
    return Result;
}


template <class T>
class CProxy_IATestEvents : public IConnectionPointImpl<T, &DIID__IATestEvents, CComDynamicUnkArray_GIT>
{
      //Warning this class may be recreated by the wizard.
public:
      VOID Fire_FirstEvt()
      {
            T* pT = static_cast<T*>(this);
            int nConnectionIndex;
            int nConnections = m_vec.GetSize();
            
            for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
            {
                  pT->Lock();
                  CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
                  pT->Unlock();
                  CComQIPtr< IDispatch > pDispatch( sp );
                  if (pDispatch != NULL)
                  {
                        DISPPARAMS disp = { NULL, NULL, 0, 0 };
                        pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, NULL, NULL, NULL);
                  }
            }
      
      }
      VOID Fire_SecondEvt()
      {
            T* pT = static_cast<T*>(this);
            int nConnectionIndex;
            int nConnections = m_vec.GetSize();
            
            for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
            {
                  pT->Lock();
                  CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
                  pT->Unlock();
                  CComQIPtr< IDispatch > pDispatch( sp );
                  if (pDispatch != NULL)
                  {
                        DISPPARAMS disp = { NULL, NULL, 0, 0 };
                        pDispatch->Invoke(0x2, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, NULL, NULL, NULL);
                  }
            }
      
      }
      VOID Fire_ThirdEvt()
      {
            T* pT = static_cast<T*>(this);
            int nConnectionIndex;
            int nConnections = m_vec.GetSize();
            
            for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
            {
                  pT->Lock();
                  CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
                  pT->Unlock();
                  CComQIPtr< IDispatch > pDispatch( sp );
                  if (pDispatch != NULL)
                  {
                        DISPPARAMS disp = { NULL, NULL, 0, 0 };
                        pDispatch->Invoke(0x3, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, NULL, NULL, NULL);
                  }
            }
      
      }
};
#endif
Avatar of LuRen

ASKER

_ys_,
        Thx. I modified my test codes and it works well.

        BTW, if someboby needs the  test code, please mail to me( LuRen_12342002@yahoo.com.cn). I'd like to share it.