Solved

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

Posted on 2004-03-29
24
1,026 Views
Last Modified: 2013-12-14
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.
0
Comment
Question by:LuRen
  • 10
  • 6
  • 5
  • +2
24 Comments
 
LVL 49

Expert Comment

by:DanRollins
ID: 10711723
What was the exception code?
0
 

Expert Comment

by:MootPoint
ID: 10713592
Did you call CoInitialize() on any threads you start?
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 10716137
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

0
 
LVL 9

Expert Comment

by:_ys_
ID: 10716657
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.
0
 
LVL 1

Author Comment

by:LuRen
ID: 10717584
>  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.

0
 

Expert Comment

by:MootPoint
ID: 10721993
> 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.
0
 
LVL 1

Author Comment

by:LuRen
ID: 10722378
>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.
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 10722500
>> 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
0
 
LVL 1

Author Comment

by:LuRen
ID: 10725396
>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.
0
 
LVL 9

Expert Comment

by:_ys_
ID: 10725728
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.
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 10725735
>> 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
0
 
LVL 9

Expert Comment

by:_ys_
ID: 10725948
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*).
0
How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

 
LVL 1

Author Comment

by:LuRen
ID: 10726249
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.

0
 
LVL 1

Author Comment

by:LuRen
ID: 10739757
itsmeandnobodyelse, _ys_

      do you receive the test codes?  
0
 
LVL 9

Expert Comment

by:_ys_
ID: 10739945
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]
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 10739983
Yes, i got it. I'll soon have time to look around.

Regards, Alex
0
 
LVL 1

Author Comment

by:LuRen
ID: 10744744
_ys_, I send you the file again.  Please check your mail-box.



0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 10745055
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



0
 
LVL 1

Author Comment

by:LuRen
ID: 10759865
Thx, itsmeandnobodyelse, I will test as your said and tell you the result.
0
 
LVL 9

Accepted Solution

by:
_ys_ earned 125 total points
ID: 10760090
> i pass the pointer of the COM obj to the thread, is it the reason of the exception?
Yes.

> but i need use COM obj in thread.
Ok. The normal way to pass COM pointers (between threads in the same process) is the function pair CoMarshalInterThreadInterfaceInStream / CoGetInterfaceAndReleaseStream.

It's cumbersome, and in your case (due to ATL) is was frustrating to implement satisfactorily.

Alternatively I found this, and have tested it with complete success:
http://www.codeguru.com/Cpp/COM-Tech/atl/atl/article.php/c75

Be sure to download the header file, and not copy-paste, as the provided code doesn't compile; that provided in the header file does. Also, DaemonThread needs to be COM initialized i.e. CoInitialize (NULL);

FYI, the source code alters the provided local 'proxy event' implementation, storing the local COM instance in the GIT. This is all provided transparently - see the codeguru header file for full details.

Enjoy,
_ys_
0
 
LVL 1

Author Comment

by:LuRen
ID: 10768924
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.
0
 
LVL 1

Author Comment

by:LuRen
ID: 10772762
_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.
0
 
LVL 9

Expert Comment

by:_ys_
ID: 10772927
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
0
 
LVL 1

Author Comment

by:LuRen
ID: 10773128
_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.
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

This article shows you how to optimize memory allocations in C++ using placement new. Applicable especially to usecases dealing with creation of large number of objects. A brief on problem: Lets take example problem for simplicity: - I have a G…
Whether you've completed a degree in computer sciences or you're a self-taught programmer, writing your first lines of code in the real world is always a challenge. Here are some of the most common pitfalls for new programmers.
This tutorial covers a step-by-step guide to install VisualVM launcher in eclipse.
The goal of the video will be to teach the user the difference and consequence of passing data by value vs passing data by reference in C++. An example of passing data by value as well as an example of passing data by reference will be be given. Bot…

758 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

Need Help in Real-Time?

Connect with top rated Experts

17 Experts available now in Live!

Get 1:1 Help Now