Last Modification: January 06, 2004

Why does my VB client keep crashing when compiled and not in the IDE when I use an ActiveX Control with a worker thread?

You probably fire events from the worker thread in your control. Since all ActiveX Controls live in single-threaded apartments, the event sink your VB client supplies lives in that STA too. VB operates in apartment model only, hence the pointer for the event sink is in fact a direct pointer to the object in VB. Hence you are able to call through this pointer. Unfortunately, by doing so you violate the COM threading rules - every interface pointer is valid only within the apartment it is obtained in. Since VB is not thread-safe - you experience the crash.

Solutions. There are three possible solutions (described in ATL terms, but they are appropriate for straight C++ COM coding too):

  1. The easiest solution is to create a hidden window upon the object's construction in FinalConstruct (it is not a good idea to put such code in the constructor). Then whenever you need to raise an event, you post a message to that window instead. The message handler then fires the event. The drawback is that you have to package any arguments and unpackage them in the message handler. An additional benefit is that unlike the other approaches, this way the worker thread is immediately ready to continue with the next task - asynchronous notifications. This approach is made possible by the rule that all STA threads must have a message loop (in this case implemented by VB).
  2. Rewrite the implementation of IConnectionPointImpl::Advise and Unadvise and forward the call to the worker thread somehow. The implementation uses CoMarshalInterThreadInterfaceInStream and the worker thread uses CoGetInterfaceAndReleaseStream to marshal the interface pointer to the worker thread. Then the marshaled pointer is stored in the map instead of the direct pointer from the client. The worker thread must enter an apartment (MTA or create new STA). The advantage of this approach is that the code generated by the ATL connection points wizard doesn't change. The drawback is that the events must be fired from the worker thread's apartment only (so if you have multiple worker threads you better enter the MTA in all of them). The worker thread is suspended for the duration of the call.
  3. This one is a generalized version of the second. Instead of explicitly marshaling the interface pointer for a specific apartment up front, the code in the Advise method is modified to use the Global Interface Table to store the interface and a cookie is stored in the map instead. Whenever any thread wants to fire an event, the cookie is used to temporarily obtain an interface pointer for the current apartment then the event is fired and the interface pointer is released. The drawback (with ATL in mind) is that in addition to the IConnectionPointImpl code, you have to modify the code for the proxy generated by the ATL wizard. The advantage is that events can be fired from any apartment. All threads which fire events must enter an apartment. The thread firing the event is blocked for the duration of the call.

References and samples: