0

Introduction or Going from PassiveX to ActiveX

In this article I show how to create a multithreaded ActiveX control using ATL. The reason for doing so is that many industrial solutions have to do some asynchronous reading or writing from an external device. These devices can be discs, barcode readers and other com-port devices, parallel ports, databases, sockets, and so on. As an example I create an ActiveX control that can use the RS-232 Communication port for sending and receiving bytes between machines using a NULL modem. The control can be used from ActiveX control container languages like VB and Delphi to access the com-port. The overall design of the ActiveX control can be seen in the following figure (the circles are threads):

I use the ActiveX control for creating a chat program in VB. I know a chat program isnt the most intelligent usage but it is simple and very illustrative. My interest for multithreading was awoken during University where many of the basic concepts such as deadlocks, monitors, race conditions, and so on, where introduced. I didnt do any multithreaded programming for many years until a very large (in Danish terms) company wanted a multithreaded ActiveX control written using the Control Wizard in MFC using Visual C++ v4.2. This control is now running in a multimedia environment. The problem about this control is that it takes up 1.6 Mbytes in memory (including MFC). One of the benefits about using ATL is the size of the runtime DLL. There are also some drawbacks: When using ATL you are more or less left on your own when concerning multithreading. If you havent tried creating a multithreaded ActiveX control before you will experience that this is real ActiveX and the past only has been PassiveX. I will now describe the ActiveX Communication control.

The Communication Control

Creating the ActiveX control for the chat program can be divided into two parts. The first part is the transmitter and the second part is off course the receiver. There are many solutions to the problem. The solution I will give in this article is a simple one. One could have made two different controls in the same DLL each sharing the com-port or one could have created the controls in two different DLLs. With two different controls a decision should be made about how to share the com-port or if there should be a com-port for each control. But all in all, the solution to have one control is the simplest and it is the most logical.

I will start developing the transmitter and during the development I will tell how to improve it so it will meet normal industrial demands of a multithreaded ActiveX control. I will also introduce the concept of Active Objects, meaning objects running in their own thread. During this introduction I will also give a Design Pattern/strategy for terminating Active Objects. I will then continue with the receiver part, which is a bit more complicated because it has to fire events from a worker thread. Actually there has to be done a lot of work from the programmers side to make it work properly.

Creating the Control

First I create a usual ATL DLL project. I will not go into detail about that because there is already tons of more or less good articles on this subject. At this point it is very important to select the Merge Proxy/Stub code because we later will be using marshal interface pointers. If we dont the proxy/stub will have to be manually created and registered and that can introduce a lot of problems at distribution time.

Next I create a usual full ActiveX control that is invisible at runtime. It will support apartment threading because it must act as a single threaded ActiveX control seen from languages like VB or Delphi. I doesnt need a window because it is invisible at runtime, but it is put on anyway.

The Transmitter Part of the Control

The first task is to make the ActiveX Control able to send the data. I would like the sending of data to be asynchronous so the data transmission dont interfere with any graphical program that would make use of the control. This task could easily be solved using a single threaded ActiveX control and asynchronous IO-writing. But for introducing the concepts about Active Objects, critical sections, and win32 events this practice is pretty good. The ActiveX control has one method called TransmitByte that takes a single character as argument and sends it to the com-port via the worker thread. Having one method that takes only one simple argument is COM Automation in one of its most simple forms. I will come back to TransmitByte later but before I do that I will introduce the threading concept called Active Objects.

Active Objects or Object-Oriented Threading

Threads are normal C-functions started when calling the C-runtime library functions _beginthread or _beginthreadex either explicitly or implicitly (like in MFC) with the thread functions as an argument. The _beginthread also takes a void* as argument. The begin-methods creates a new stack and starts the method by calling it with the void* as argument. As in the nature of void pointers the void* can point to anything. This void* is used for arguments to the thread function. The thread function then have to unpack the data from the void* by typecasting it to something well known. The function is running in its own thread until it returns.


struct rec{
	double d;
	char ch;
}

void f(void * r)
{
	rec *tmp = (rec*) r;
	loop ..
}

rec r;  r.d = 3.1415; r.ch = 2;
_beginthread(f, 0, (void*)&r);
loop too 

This approach isnt very object-oriented. If one wants a more object-oriented approach to threading you can easily adopt to the scheme similar to one described in [1]. The idea in this article is to have class that can have instances running in their own thread. In this article I will call the class CBasicThread. CBasicThread has a virtual method called Command(). When creating a subclass from this class that solves a problem in the user domain you overwrite this method with you own Command() method and do your object oriented computing from there. This is the concept of Active Objects. But how is the object activated. Some way or another the _beginthread function must be called. It is not possible to give an object as argument to _beginthread as it was with MFCs AfxBeginThread. It is only possible to give a function and a void pointer as argument. This extra void pointer will become important in a short while. The start of the active object goes in two steps:

  1. Create the active object like an ordinary C++ object.
  2. Call a member function StartThread on the newly created class. This method will call _beginthread with the static member function RunProc and the self pointer this typecasted into void* and then it will terminate. The method RunProc is now running in its own thread and has as argument a void* to an object. It then typecasts the void* back into a CBasicThread* and calls the true member function CBasicThread::Run(). The Run() method works as a pseudo main() function because it initiates the thread of computing. It loops and for each iteration it calls Command() until some spooky halt condition is meet. The method then terminates and finally the thread stops executing

The definition of the CBasicThread looks like:

class CBasicThread
{
public: 
	static unsigned int __stdcall RunProc( void *pvoid );
	CBasicThread(HWND & hWnd);
	~CBasicThread();
	void StartThread(int nPriority = THREAD_PRIORITY_NORMAL); 
	// starts the thread, call TerminateThread to stop the thread
	// StartThread calls Run() that iterates until TerminateThread 
	// is called
	// Use the virtual method Command to specify what you want to 
	// do during the iterations
	void TerminateThread();
protected:
	int Run();
	virtual bool MayIContinue();
	string m_threadname;
	HANDLE m_workerThread;
private:
	bool m_continue;
	bool GetContinue();
	void SetContinue(bool val);
	_CritSec m_CritSec;
public:
	virtual void PreCommand(){};
	virtual void PostCommand(){};
	virtual void Command() = 0; // the method to further bind. All 
    // parameters should 
					    // be set using a new method or 
};

The StartThread method looks like:

void CBasicThread::StartThread(int nPriority)
{
	unsigned unThreadAddr;
	m_workerThread = reinterpret_cast
		(_beginthreadex(NULL, 0, RunProc, 
                            this, 1, &unThreadAddr));
}

The program will have to be linked with the multithreaded versions of the C-runtime-libraries. This can be set in Project|Settings|C/C++|Code Generation|Use run-time Library. Remember there are different version for debug and release versions. The static RunProc looks like:

unsigned int __stdcall CBasicThread::RunProc( void *pvoid )
{
	CBasicThread *pThread = static_cast(pvoid);
	return pThread->Run();
}

The Run method that is the Active part of the object and looks like:

int CBasicThread::Run()
{
	PreCommand();
	while (MayIContinue())
	{
		Command();
	}
	PostCommand();
	return 0;
}

If you want to terminate the thread from the outside you have to ask it nicely and friendly to do so by itself like you would ask you grandmother to bake your favorite cookies. The alternative would be to kill it from the outside but by doing so you can easily loose resources so it is not a very good idea. So what you do is raising a flag so the method CBasicThread::MayIContinue will return false and the thread will terminate by running out. This is done from the outside (i.e. the main thread in the ActiveX control) by calling the method CBasicThread::TerminateThread:

void CBasicThread::TerminateThread()
{
	SetContinue(false); // Terminates the thread by asking it to do it
// Waits until the thread is terminated
	::WaitForSingleObject( m_workerThread, INFINITE ); 
}

As seen, the calling thread (i.e. the main thread) will wait until the thread terminates. So if the thread doesnt terminate we have an endless loop. We will investigate that later on. Before and after the main loop the virtual methods CBasicThread::PreCommand and CBasicThread::PostCommand are called to do some subclass specific initialization and cleaning up. When using subclasses of CBasicThread you have to be careful about the following: Do not create the object on the stack (in a function) and the let that function run out of scope before the thread is stopped. It will lead to unexpected behavior because the object is destructed when it runs out of scope and the thread is still running.

The Active Transmitter Class (TransmitterThread)

The ActiveX control that we are creating will contain a subclass to the CBasicThread class that is specific for sending data to the com-port. It will specialize the abstract method Command so it will retrieve data from the mainthread and send it to the com-port. The class definition looks like:

class CTransmitThread : public CBasicThread  
{
public:
	CTransmitThread(HWND & hWnd ,ts_queue & rqueue);
	virtual ~CTransmitThread();
protected:
	virtual bool MayIContinue();
	virtual void PreCommand();
	virtual void Command(void);
	ts_queue & m_rqueue;
	ComPort * m_pComPort;
};

Communication between Control and Active Object

The communication between the Active Object (worker thread) and the main thread has to be threadsafe. The communication will be done through a STL queue of bytes. The methods must be thread safe so we dont get any simultaneous reading and writing. This can easily be done by specializing the STL adapter class queue by using a critical section, which is the most simple and fastest synchronization object on Win32 to protect the reading and writing inside the same process:


template 
class ts_queue: public queue
{
	typedef ThreadModel _ThreadModel;
	typedef _ThreadModel::AutoCriticalSection _CritSec;
public:
	ts_queue(){};
	void put(const T in);
	T get();
	bool empty();
protected:
	void Lock() {m_critsec.Lock();}
	void Unlock() {m_critsec.Unlock();}
private:
	_CritSec m_critsec;
};

template 
bool ts_queue::empty()
{
	bool tmp;
	Lock();
	tmp = queue::empty();
	Unlock();
	return tmp;
}


template 
void ts_queue::put(const T in)
{
	Lock();
	push(in);
	Unlock();
}

template 
T ts_queue::get()
{
	T tmp;
	Lock();
	tmp = front();
	pop();
	Unlock();
	return tmp;
}

The ActiveX control creates the thread safe queue and hands a reference to the Active Object through its constructor so they share this instance for communication. The Active Object will implement the Command method like:


void CTransmitThread::Command(void)
{
	if (!m_rqueue.empty())
	{
		unsigned char ch;
		ch = m_rqueue.get();
		m_pComPort->Transmit(ch);
	}
}

The pointer to the comport-object is initialized in the constructor for the thread, but this might as well have been in the PreCommand method. I will not go into detail about the comport object except it opens a com-port for serial communication at 2400 Baud and it implements a method called ComPort::Transmit that will write a byte to the com-port. We will investigate the com-port object later on when talking about the Active Receiver Object.

Adding a Method to the ActiveX Control

The ATL Method Wizard is used for adding a method TransmitByte to the control. In IDL it looks like:


[id(1), helpstring("method TransmitByte")] HRESULT TransmitByte([in] unsigned char ch);

TransmitByte takes a single character as argument and send it to the com-port via the worker thread. Having one method that takes only one simple argument is COM in one of its most simple forms (Automation). The implementation of TransmitByte looks like:


STDMETHODIMP CTransmit::TransmitByte(unsigned char ch)
{
	// TODO: Add your implementation code here
	m_tsqueue.put(ch);
	return S_OK;
}

It sends the byte to the Active Transmitter Object through the thread safe queue. Both the threadsafe queue and the Active Transmitter Object are instances belonging to the ActiveX control. The constructor for the control will initialize the Active Transmitter Object with the threadsafe queue, like:

	CComm():
		m_tt(m_tsqueue)
	{
		m_bWindowOnly = TRUE; 
	}

The control will also implement the two methods FinalConstruct and FinalDestroy that is virtual methods on CComControl (one of the superclasses for our control) that is called respectively right after the total construction and right before total destruction of the ActiveX control. In FinalConstruct the worker thread will be started depending on the state of the container (we will dig more into that later). In the destructor the thread will be stopped. Stopping the thread is not as simple as it may seem and I will discuss it in great detail. FinalConstruct and FinalRelease are used instead of the normal C++ constructor and destructor. This is because both the constructor and the destructor arent good targets for the operations because we cannot be sure of the state of the member variables in this phase of construction/destruction. In this case the finals are responsible for the lifetime of the Active Transmitter Object, like:

HRESULT CComm::FinalConstruct( )
{
	BOOL runmode = FALSE;
	HRESULT hres = GetAmbientUserMode(runmode);
	if (!SUCCEEDED(hres))
		runmode = TRUE
	if (runmode)
	{
		m_tt.StartThread();
	}
	return S_OK;
}

void CComm::FinalRelease( )
{
	BOOL runmode = FALSE;
	HRESULT hres = GetAmbientUserMode(runmode);
	if (!SUCCEEDED(hres))
		runmode = TRUE
	if (runmode)
	{
		m_tt-> TerminateThread(); // this method will wait for the worker thread to terminate
	}
}

The two final-methods use the method GetAmbientUserMode to determine whether or not the ActiveX control is in design mode or run mode. Design mode is when you are designing your GUI using VB. VB (like many other containers) loads and instansiates the control also in design mode. It would be stupid to start the threads in design mode so we try to avoid this, but as many other MS stuff this doesnt work. The moral is: do not rely on GetAmbientUserMode to determine the state of the container. Use some other technique to do that. This will be investigated at the end.

Behavior and Termination

At this point we can create a VB program that uses the control. It should have one Edit box where the property MultiLine is set to TRUE and it should hold one instance of the ActiveX Control (You will have to browse for the DLL).

At design time the form could look like:

The code for the VB program would look like:


Private Sub Text1_KeyPress(KeyAscii As Integer)
   Comm1.TransmitByte (KeyAscii)
End Sub

At runtime the VB program would look like:

As you can see the control is invisible at runtime. This is a registry setting that can be manipulated through the MiscStatus entry in the reg-script. Finding the right bits to set is not a simple task and this is an effort in itself. When changes are made to the MiscStatus you have to recompile your VB program to make the changes shine through. The problem about the current implementation of the ActiveX control is that the Active Transmitter Object waits actively on input from container (VB program), meaning it loops and for each iteration it checks some halt condition. This active waiting will slow down execution of the VB program (and actually the whole desktop). It will feel like rubber bands are attached to all windows. The active waiting happens in the virtual Command method. In the following I will solve the problem in two tempi. First I will do the obvious one using Win32 events on behalf of loosing the ability to control the termination of the Active Object. In the second step I will state the Design Pattern/ strategy for termination of Active Objects by introducing Terminators

Adding Support for Win32 Events

The rubber band problem can easily be solved using theWin32 synchronization mechanism: Events. The reader on the thread safe queue will wait passively for someone to put data in the queue using the Win32 ::WaitForSingleObject. The event that something has arrived will be signaled from the put method on the thread safe queue, like:


template 
class ts_queue: public queue
{
	typedef ThreadModel _ThreadModel;
	typedef _ThreadModel::AutoCriticalSection _CritSec;
public:
	ts_queue():m_event(false, true){}; //manually reset
	void put(const T in);
	T get();
	bool empty();
	bool waitNonEmpty();
	void Lock() {m_critsec.Lock();}
	void Unlock() {m_critsec.Unlock();}
private:
	_CritSec m_critsec;
	CEvent m_event;
};



template 
bool ts_queue::waitNonEmpty()
{
	Lock();
	if (!empty())
	{
		Unlock();
		return true;
	}
	m_event.ResetEvent();
	Unlock();
	::WaitForSingleObject(m_event, INFINITE);
	return true;
}


template 
void ts_queue::put(const T in)
{
	Lock();
	push(in);
	m_event.SetEvent();
	Unlock();
}

The Active Transmitter Object will have to call waitNonEmpty() instead of calling the empty() method actively. This method will execute (passively) until a character is put into the queue. The method Command() now looks like:


void CTransmitThread::Command(void)
{
	if (m_rqueue.waitNonEmpty() && (MayIContinue()))
	{
		m_pComPort->Transmit(m_rqueue.get());
	}
}

The CEvent class used in the threadsafe queue is not a part of ATL but snapped and modified from the MFC library. It simply encapsulates the Win32 event mechanism into a class (see the source).


class CEvent : public CSyncObject
{

// Constructor
public:
	CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE,
		LPCTSTR lpszNAme = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL);

// Operations
public:
	BOOL SetEvent();
	BOOL PulseEvent();
	BOOL ResetEvent();
	BOOL Unlock();

// Implementation
public:
	virtual ~CEvent();
};

Using the CEvent and calling WaitForSingleObject will make termination of the thread awkward. Termination can only happen right after a character has been sent to the Active Transmitter Object. This is not a good strategy for termination. In the following I will introduce a strategy for termination that is widely applicable.

The Terminator

The following has nothing to do with Arnold Schwartzenegger and it is not science fiction. It is based on a true story but there might be a sequal. The idea of the Terminator is that each time an active object has to wait for some kind of Win32 object using a ::WaitForSingleObject it will use the terminator instead. The Active Objects will at startup time sign up as an observer for the termination event. When the ActiveX control is terminating it will ask the terminator to set the terminate event and all Active Objects that is passively waiting for an event to happen will at this time be interrupted and can then terminate in an ordered manner. Instead of calling ::WaitForSingleObject the Active Objects will call Terminator::Wait with the event as argument. The Wait() method will then call ::WaitForMultipleObjects instead of ::WaitForSingleObject with both the event from the Active Object and the terminate event as argument. The Terminator class looks like:


class Terminator
{
private:
	CEvent m_termEvent;
	bool is_terminating;
	typedef CComMultiThreadModel ThreadModel; 
	typedef ThreadModel _ThreadModel;
	typedef _ThreadModel::AutoCriticalSection _CritSec;

	_CritSec m_signUpCS; 
 	vector m_runningThreads; // protected by m_signUpCS
public:
	void TerminateAll();
	bool TerminatingAll();
	bool Wait(CSyncObject * pSyncObj); // True if pSync is now Locked
									   // False Means terminated or timed out
	bool Wait(HANDLE eventHandle); // True if pSync is now Locked
	void SignUp(HANDLE hThread);
public: // singleton
	static Terminator* Instance();
	LONG Release();
	static LONG m_cRef;
protected:// Singleton
	Terminator();
	~Terminator();
private: // Singleton
	static Terminator* m_instance;

};

The implementation of the Terminator::Wait() method looks like:


bool Terminator::Wait(HANDLE eventHandle)
{
	HANDLE h[2];
	h[0] = eventHandle;
	h[1] = m_termEvent.m_hObject;
	DWORD lockVal;
	lockVal = ::WaitForMultipleObjects(2, h, false, INFINITE);
	return lockVal == WAIT_OBJECT_0;
}

And the Terminator::TerminateAll that is used by the control to terminate the control looks like:


void Terminator::TerminateAll()
{
	is_terminating = true;
	m_termEvent.SetEvent();

	vector::iterator i;
	for (i=m_runningThreads.begin(); i

The Active Objects sign up during their initialization using Terminator::SignUp. This concept is fairly simple and applicable for many similar situations. In this case the Terminator is a singleton and can only be reached through the static method Terminator::Instance(). This ensures us that there is only one terminator in the game at any time. Other schemes can off cause be adopted. The thread safe queue will now change its implementation of waitNonEmpty to:


template 
bool ts_queue::waitNonEmpty()
{
	Lock();
	if (!empty())
	{
		Unlock();
		return true;
	}
	m_event.ResetEvent();
	Unlock();
	Terminator * m_pTerm = Terminator::Instance();
	bool tmp = m_pTerm->Wait(&m_event);
	m_pTerm->Release();
	m_event.ResetEvent();
	return tmp;
}

This small modification ensures there will be no rubberbands in the GUI and that the active object is terminated in a descent way. This finishes the transmitter part of the control.

The Receiver Part of the Control

The second task is to make the ActiveX control able to receive data from the com-port. My solution will both use Active Objects (threading) and the Terminator to do asynchronous communication between the VB program and the com-port. When a byte arrives at the com-port it will be sent to the VB program using the COM mechanism: Events. This mechanism is not to be confused with the Win32 synchronization objects: Events. This scheme is not very efficient because an event has to be fired each time a byte arrives. Other more efficient schemes could have been adopted but for the sake of illustration is it sufficient. The Receiver part of the control is more tricky than the transmitter because the events have to been fired from a worker thread inside an Active Receiver Object. I will here describe a mechanism that uses marshalled interface pointer to the control[6] [7].

Firing Events

First the control must be enabled for firing events This is done in three tempi as described in [2]: Defining the event interface, using the ATL proxy generator, and implementing a bit of code. This is not as easy as in MFC and VB but it is well described and very fast to do.

First: Defining IDL

First I define the event that is to be fired in IDL. I call it Received. The definition looks like:


library COMMUNICATIONLib
{
	importlib("stdole32.tlb");
	importlib("stdole2.tlb");

    [
        uuid(974da5c0-b1f8-11d1-aabb-006097636471),
        helpstring("Event interface for ReceiveCtl")
    ]
    dispinterface _CommEvents
    {
        properties:
        methods:
        [id(1)] void Received([in]unsigned char  ch);
    };

	[
		uuid(3BC3F4C1-B80C-11D1-AABD-006097636471),
		helpstring("Comm Class")
	]
	coclass Comm
	{
		[default] interface IComm;
		[default, source] dispinterface _CommEvents;
	};
};

It is important to remember to have the definition of the event disp-interface inside the library definition otherwise the MIDL compiler wont generate the right code. The interface could also have been defined outside and just referenced inside the library. The event interface does not have to be a pure dispatch interface (dispinterface) but can also be a pure custom interface. VB (from 6.0 and forwards) doesnt care as long as it not dual. After running the MIDL compiler the ATL proxy generator is used.

Second: The ATL Proxy Generator

The code for the events can be generated by the Proxy Generator. Using the Proxy generator (as described in the article The Proxy Generator in VC++ 5.0 (VC++ 6.0 looks a bit different):

>

The generated proxy is now in the file CPCommunication.h

Third: Changing the CComm class definition

Add the interface and map changes. Include CPCommunication.h.

// Comm.h : Declaration of the Ccomm
.
#include "CPCommunication.h" // for events

/////////////////////////////////////////////////////////////////////////////
// CComm
class ATL_NO_VTABLE CComm : 
	public CComObjectRootEx,
	public CComCoClass,
	public CComControl,
	public IDispatchImpl,
	public IProvideClassInfo2Impl<&CLSID_Comm, &DIID__CommEvents, &LIBID_COMMUNICATIONLib>, //DIID__CommEvents added for events 
	public IPersistStreamInitImpl,
	public IPersistStorageImpl,
	public IQuickActivateImpl,
	public IOleControlImpl,
	public IOleObjectImpl,
	public IOleInPlaceActiveObjectImpl,
	public IViewObjectExImpl,
	public IOleInPlaceObjectWindowlessImpl,
	public IDataObjectImpl,
	public ISupportErrorInfo,
	public CProxy_CommEvents, // for events
	public IConnectionPointContainerImpl,
	public IPropertyNotifySinkCP
//	public ISpecifyPropertyPagesImpl
{
public:
	CComm():
.
..
BEGIN_COM_MAP(CComm)

	COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer)
END_COM_MAP()

BEGIN_CONNECTION_POINT_MAP(CComm)
    CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink)
    CONNECTION_POINT_ENTRY(DIID__CommEvents)  // comm events
END_CONNECTION_POINT_MAP()

The control can now fire events by calling the method Fire_Received defined on the control (through multiple inheritance). The event have to be fired from the a worker thread so lets create the Active Receiver Object that will act as the worker thread for reading the bytes from the port.

The Active Receiver Object

The active Receiver object is quite similar to the Active Transmitter Object. I also subclasses the CBasicThread class. The Command() method looks like:


void CReceiveThread::Command(void)
{
	unsigned char ch;
	while (m_pComPort->Receive(ch))
	{
m_ts_receive_queue.put(ch);
	}
}

The ComPort::Receive waits passively using the Terminator::Wait mechanism as in CTransmitThread on an incomming byte from the com-port. This ensures good termination and no rubber-band effect in the GUI.

Instead of firing the event directly from the worker thread through a reference to the control another mechanism has to be used. This is because control containers like VB arent reentrant and only one thread can be in the apartment belonging to VB. The trick is to make a synchronized call to VB. The easy path is to let COM do the synchronization for us. The workerthread should be given an interface to the ActiveX control through CoMarshalInterThreadInterfaceInStream and CoGetInterfaceAndReleaseStream and COM will then synchronize any calls through this marshaled interface pointer (hurrah!). If the Receive thread makes the calls through the marshaled interface pointer it will wait until the call returns (and that is pretty bad). This will probably not be a problem in Windows 2000 where it is possible to make asynchronous COM calls. But Windows 2000 is in the future and we are in the present so this asynchronous mechanism can be simulated by introducing an extra thread that is a true COM apartment thread. This thread owns the marshaled interface pointer. Communication between the Receive thread and this Windows 2000 asynchronous simulation thread (lets call it the Event thread) is done through another instance of the threadsafe queue.

There are only three tasks left to finish the control: Creating an extra internal interface that can be marshaled and handled to the workerthread so it can fire events, creating the extra Event thread, and enabling communication between the threads.

Creating the internal synchronization interface

It is very important that the call to the instance of the C++ class representing the ActiveX control is done through a COM interface and not just through a simple C++ pointer to the object. This is because COM can do the synchronization for you (COM is your buddy) but to do that it needs an interface defined in IDL. The MIDL compiler will then generate the necessary code to do the marshaling of the call and all will be fine.


	[
		object,
		uuid(3BC3F4CA-B80C-11D1-AABD-006097636471),
		helpstring("IInternalComm Interface"),
		pointer_default(unique)
	]
	interface IInternalComm : IUnknown
	{
		[helpstring("method TransmitByte")] HRESULT 
incommingByte([in] unsigned char ch);
	};

The implementation of this interface is very straightforward. This method just calls Fire_Received and returns, like:


STDMETHODIMP CComm::incommingByte(unsigned char ch)
{
	// TODO: Add your implementation code here
	Fire_Received(ch);
	return S_OK;
}

Creating the Event Thread

The Event thread is like the Transmit thread except we need to add a PreCommand, a PostCommand and make a small change in the Command.

The PreCommand

The thread must belong to a new apartment for making it possible for COM to do the marshaling. This is done through a call to ::CoInitialize[6][7]. It is now belonging to a new COM apartment. It then has to retrieve the interface for the ActiveX control. This is done through a call to ::CoGetInterfaceAndReleaseStream on a stream that is created by the ActiveX control (later). We now have a valid interface pointer that points to a IInternalComm interface  EASY!


void CEventThread::PreCommand()
{
	Terminator * m_pTerm = Terminator::Instance();
	m_pTerm->SignUp(m_workerThread);
	m_pTerm->Release();
	HRESULT hr = CoInitialize(NULL);
	if (FAILED(hr))
		::MessageBox(NULL,"Bad Carma","EventThread",MB_OK);
	if (FAILED(::CoGetInterfaceAndReleaseStream(m_pStream, 
               IID_IInternalComm, (void**)&m_pIInternalComm)))
	{
 	   ::MessageBox(NULL,"Bad Carma, GetInterfaceAndR..","EventThread",MB_OK);
	}
}

The Command

The command must wait for bytes from the Receive thread and when they come it uses the I IInternalComm interface pointer retrieved in the PreCommand to call the ActiveX control. We use the well known terminator to wait passively for the bytes EASY AGAIN!


void CEventThread::Command(void)
{
	if (m_rqueue.waitNonEmpty() && (MayIContinue()))
	{
		unsigned char ch;
		ch = m_rqueue.get();
		m_pIInternalComm->incommingByte(ch);
	}
}

The PostCommand

Here we release the interface pointer and uninitializes COM for this apartment (VERY EASY AND SIMPLE!):


void CEventThread::PostCommand()
{
	m_pIInternalComm->Release();
	::CoUninitialize();
}

Enabling communication between the ActiveX control and the Event Thread

The ActiveX control hands out two variables to the event thread: the marshaled interface pointer and the queue between the Receiver thread and the Event thread.

The marshaled interface pointer

The ActiveX control gives out a marshaled interface pointer to its own IInternalComm interface. This is done by first calling ::CoMarshalInterThreadInterfaceInStream and putting the result in a variable readable by the Event thread (Ive done some hacking here to make it work in a hurry)


	HRESULT hr =::CoMarshalInterThreadInterfaceInStream(IID_IInternalComm,  
                            static_cast(this), 
                            &m_et.m_pStream);
	if (FAILED(hr))
	{
		::MessageBox(NULL,"Could not CoMarshal", "CCom control", MB_OK);
		return S_FALSE;
	}

The m_et.m_pStream is a public member variable on the Event thread (ugly). I have also neglected a QueryInterface by using a static_cast (ugly too). If you at creation time forgot to set the merging proxy/stub code you will at runtime end up with at CoMarshalInterThreadInterfaceInStream that will fail, because it will search the registry for the proxy/stub DLL. What you can do is to manually create the proxy/stub DLL by calling nmake (nmake -fcommunicationps.mk) and then register it using Regsvr32 (regsvr32 comunnicationps.dll). The MIDL compiler (MIDL is you buddy too) creates the makefile for the project. You can also set it up in Developers Studio under Project | Setting | the Post-Build Step tab.

The Queue between the Event thread and the Receiver Thread.

The queue is belonging to the ActiveX control and is given to both the Receiver Thread and the Event Thread in their constructors. A more beautiful scheme would be to create a C++ singleton and then letting the thread acquire their pointer, but this was faster to make!

The internals of the system now looks like:

Polishing the Control (Making it Professional)

When working with controls from VB it is pretty anoying that the control has to do all its initialization in design mode. It is very important to take care of this fact. In the following the GetAmbientUserMode will be fixed so desing mode can be recognized from the control. The only problem is that it only works after FinalConstruct.

As mentioned before the GetAmbientUserMode could not be trusted. A way to overcome this problem is to use an explicit initializer that "fires up" the worker threads. In that way you will avoid the client (i.e. VB environment to behave in a strange manner when doing something bad in you threads. This technique has been added to the control. Compared to implicit initialization as could have been done in FinalConstruct this method introduces yet another source of errors, so be "careful out there!"

Finishing the VB program

The VB chat program can now be finished. The VB program must set up a handler for the Received event and create a new multiline text control. The new userinterface looks like:

The implementation of the event handler looks like:


Private Sub Comm1_Received(ByVal ch As Byte)
    If ch = 13 Or ch = 10 Then
        Text2.Text = Text2.Text & vbCrLf
    Else
        Text2.Text = Text2.Text & Chr(ch)
    End If
End Sub

The code makes sure to insert a newline when it receives on. This switch should be longer but that will not bring anything new to fact that we are using a multithreaded control. At runtime the control looks like:

Actually there is an extra button on the VB program in the demo. This button has to be pressed before the communication can work. It will write some garbage to the screen (ascii chars from 33 to 200) but that simply indicates that the pipe from the worker thread is open. Pressing more than once will make the program crash, so don't do that :-) Pressing the button will start the explicit initializer as mentioned in Polishing the Control that fires up the worker threads.

Conclusion

In this article I have described how to create a control that can handle the demands for a small efficient industrial strength multithreaded ActiveX control using ATL (3.0). I have shown how Active Objects and Terminators can be used efficiently to handle the lifetime and behavior of objects running in their own thread. I have also shown how to use COM to syncronize between a worker thread and the main thread (the thread in the main apartment). The solution works perfectly on both WinNT 4.0 (sp1) and win95 with at least DCOM95 installed. Good Luck!

In a future article I hope to show how to extend the idea of components so the communication part of the threads can become a polymorphic COM object so it can easily be replaced with a COM object that uses i.e. sockets or DCOM.

Acknowledgement

I would like to thank Thomas Sonne Olesen, DTI, CIT, for introducing me to Terminators and Ove Frost Sxrensen, Kommunedata, for introducing me to multithreaded ActiveX controls using MFC. I would also like to thank my girlfriend Lise for being patient in the process of writing this article. And remember COM IS LOVE!

References

  1. MSJ 1997 Volume 12, More First Aid for the Thread Impaired: Cool Ways to Take Advantage of Multithreading by Russell Weisz
  2. ATL Tutorial: Step 5: Adding an Event to Your Control
  3. MS Knowledge Base (Q157437): FIREEV.EXE Fires Events from Second Thread
  4. MSDN News, Ask Dr Gui #37, Can You Use a Second Thread for Sutures
  5. MSDN News, May 15, 1997, Events: From an ATL Server to a Visual Basic Sink
  6. Microsoft Press, Inside COM, Dale Rogersen
  7. Addison Wesley, Essential COM, Don Box
  8. Addison Wesley, Effective COM, Chris Sells, Tim Ewald, Keith Brown, Don Box

Download demo project - 33 KB

http://www.codeguru.com/cpp/com-tech/atl/atl/article.php/c73/

关闭 返回顶部
联系我们
Copyright © 2011. 聚财吧. All rights reserved.