In many applications you often want to perform a series of operations on data. Normally you would just chain a bunch of method calls that pass along the data, however, as anyone realizes, this approach is far from ideal.
The ACE library contains something called Streams which allows you to utilize the ‘Pipes and Filters’ pattern. You basically create a set of ACE_Tasks that perform isolated operations on the data. Then you bind these ACE_Tasks to corresponding ACE_Modules which in turn get pushed onto an execution stack in the ACE Stream.
Since each task extends an ACE_Task you can easily change the object into an Active Object by simply calling activate in the open method:
1 2 3 | int LogOutputTask::open(void*) { return activate(); } |
From that moment on the task runs in its own thread of control and will respond to messages passed onto its message queue from ACE_Tasks earlier in the chain. This allows for the decoupling of input and processing.
Since the required ACE_Modules are all so very similar, it is quite easy to create a template that allows for creation via a macro:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #ifndef BASEMODULE_H #define BASEMODULE_H #include <ace/Task.h> #include <ace/Module.h> #include <ace/Thread_Manager.h> #include <ace/OS_NS_string.h> template <class TASK> class BaseModule : public ACE_Module<ACE_MT_SYNCH> { public: BaseModule(const ACE_TCHAR *name) { // Open only writer side, ignore reader side task this->open(name, &task_, 0, 0, ACE_Module<ACE_SYNCH>::M_DELETE_READER); } private: TASK task_; }; #define BASEMODULE(NAME) typedef BaseModule<NAME> NAME##Module #endif // BASEMODULE_H |
This module only uses the writer side of an ACE_Module, which means that ACE automatically creates an ACE_Thru_Task on the reader side which just passes on the message without modification, making it a one way street.
The example code which comes with ACE has a good example in the C++NPv2 directory, in the file display_logfile.cpp.