The Part I and Part II articles gave the samples of using SFL in practice. Now, it’s time to delve a bit into the topic and find out what is under the SFL’s hood.
Two chains of class inheritance are present in SFL. The service class chain is:
CServiceProxyT<> -> CYourService -> CServiceBaseT<> -> CServiceRoot
The application class chain looks sufficiently more modest:
CYourApp -> CServiceAppT<>
See details regarding the mentioned classes below in corresponding sections.
As mentioned in Part I, the SFL expects that the console application type is used for building the service application. This is defined by the SFL_BEGIN_SERVICE_MAP macro that implements the _tmain function. The service application class instance is constructed statically before calling the main function—please remember this, it is implementing a customized application class.
Function _tmain
After _tmain gets called, the service class object is constructed. Any information about it is passed to the application object (to the CServiceAppT<>::Main member function) that finally calls StartServiceCtrlDispatcher internally.
The _tmain() function constructs the service map object (in factm it’s an array of CServiceRoot* pointers) whose entries are initialized by values returned by one of the CServiceProxyT<>::Construct static member function versions.
Each map entry initialization call looks like a single SFL_SERVICE_ENTRY(2) macro within the SFL_BEGIN_SERVICE_MAP macro and SFL_END_SERVICE_MAP macro braces. The latter macro finishes the map with a NULL item.
Note: Developers familiar with Unicode programming already have recognized the well-known _tmain macro that eventually gets resolved to main or wmain, depending on the _UNICODE project definition.
Class CServiceRoot
All supplementary service functions are implemented by the CServiceRoot class. They provide information about the service (GetServiceName, GetCurrentStatus, or GetControlsAccepted) and set its status appropriately (SetServiceStatus, SetServiceStatusSpecific). The CheckPoint function serves to properly report a pending status.
Class CServiceBaseT<>
The CServiceBaseT<> class defines the service part of the framework structure and implements one vital part of service code: the ServiceMain member function.
The only additional task performed by the CServiceBaseT<> class is the registration of the proper _Handler(Ex) function according to a service style. The style of the service is defined by the serviceStyle enumeration declared within the SFL_BEGIN_CONTROL_MAP(_EX) macro. SFL_BEGIN_CONTROL_MAP defines the SFL_SERVICESTYLE_NT4 value and the SFL_BEGIN_CONTROL_MAP_EX macro defines SFL_SERVICESTYLE_NT5 appropriately.
Also, the CServiceBaseT<> class contains two void member functions, InitInstance and GetServiceContext. The first one is called inside ServiceMain, and its return value will be affected if the service instance will report to SCM a status SERVICE_RUNNING (in case of successful initialization) or SERVICE_STOPPED (in case of initialization failure).
The GetServiceContext function is called only in case of SFL_SERVICESTYLE_NT5 style, providing a context to a HandlerEx function. You may consider a context like some structure or interface that supplies all data required for proper service functioning. Although the service class itself may be used as service context structure, GetServiceContext provides an additional level of flexibility available to the developer.
Please, refer to the MSDN HandlerEx article regarding service context additional details.
Note: Both these member functions can be overridden in your service class, if this is required.
Your Service Class
CYourService is your own class that implements the essence of your service—control code handling.
Your service class implements a control map and handler methods for corresponding control codes. In fact, a pair of SFL_BEGIN_CONTROL_MAP(_EX) and SFL_END_CONTROL_MAP(_EX) macros construct a non-static member function, Handler(Ex), where each SFL_HANDLE_CONTROL_XXX entry binds the certain control code to its handler.
Of course, all handlers must be implemented in your class. Depending on the service style, they must conform to the appropriate handler prototype.
// SFL_BEGIN_CONTROL_MAP() typedef DWORD (T::*t_handler)(DWORD&, DWORD&, BOOL&); typedef DWORD (T::*t_handler_range)(DWORD, DWORD&, DWORD&, BOOL&); // SFL_BEGIN_CONTROL_MAP_EX() typedef DWORD (T::*t_handler_ex)(DWORD&, DWORD&, DWORD&, BOOL&, DWORD, LPVOID, LPVOID); typedef DWORD (T::*t_handler_range_ex)(DWORD, DWORD&, DWORD&, DWORD&, BOOL&, DWORD, LPVOID, LPVOID);
Each prototype corresponds to the appropriate SFL_HANDLE_CONTROL_XXX macro:
SFL_HANDLE_CONTROL(code, handler) SFL_HANDLE_CONTROL_RANGE(codeMin, codeMax, handler) SFL_HANDLE_CONTROL_EX(code, handler) SFL_HANDLE_CONTROL_RANGE_EX(codeMin, codeMax, handler)
It’s important to remember that non-extended control handler macros/prototypes are usable within the SFL_BEGIN_CONTROL_MAP_EX control map as well as their extended versions (see the demo project provided in Part II).
As mentioned above, your class may implement the GetServiceContext and InitInstance functions as well. Remember, an InitInstance call must return true to let your service start running.
Note: Please, do not use any initialization and resource allocation inside the class constructor because you will have an opportunity do this inside the InitInstance call (see the next section). In case your application runs in console user mode, your service class even cannot be used.
Note: Some of you might think a complementary ExitInstance may exist in the framework to let you de-initialize/de-allocate your service class data structures. Sorry, guys, you are wrong; any service de-initialization must be done in the SERVICE_CONTROL_STOP handler.