Creating and Registering COM Components with LocalService

 

Donis Marshall
Gearhead Press

December 1999

Summary: This article documents the steps for creating and registering a COM component within a service and is specific to Microsoft Windows 2000 and Microsoft Windows NT. You can craft a service based on the LocalService registry key, which is a hybrid of a service and a local server. In general, the rules of COM and services both apply to the entity. (10 printed pages)

Contents

Introduction
Registering with LocalService
Coding the Service
The COM Component
Clients
Conclusion

Introduction

LocalService is a registry key that merges two fundamental constructs of the Microsoft Win32® environment. It binds a Component Object Model (COM) component to a service and, in doing so, combines the benefits of both technologies. The resulting object is available to COM clients and service controllers. This article documents the steps for creating and registering a COM component within a service and is specific to Microsoft Windows® 2000 and Microsoft Windows NT®.

This article assumes a modicum of COM and Services experience. Its emphasis is the convergence of these two technologies, not the specifics of their individual implementations.

COM components are binary entities exported via interface pointers from COM servers. Clients connect to a server in a language- and vendor-independent manner. COM is a set of guidelines promoting component reuse, universal interprocess communication, data transfer, and location transparency.

Services are background applications that execute across logon sessions. They are managed by the Service Control Manager (SCM), which is a system component of the operating system. Most services do not interact with the interactive user or default desktop. For this reason, special programs called Service Control Programs (SCPs) exist to communicate with services.

The benefits of using the LocalService key are:

  • Enable remote management of a COM component
  • Provide two methodologies for connecting to a COM component
  • Apply component theory to services
  • Add language independence, location transparency, and other COM tenets into a service
  • Provide easier access to a service in a heterogeneous environment
  • Allow a COM component to run with special security privileges

Services are not supported in Windows 95 or Windows 98. Therefore, LocalService should only be used with Windows NT and Windows 2000.

For this article, I created a service named ServiceA (granted, it's not a very original name). It exports a single operation, TaskA. To manipulate the COM component of ServiceA, the article provides sample code for a COM client and a service client.

Registering with LocalService

A service exporting a COM component must be recognized by the operating system as a COM server and as a running service. Therefore, it must be registered directly in the registry as a COM server and in the services database as a service.

You must make several entries in the registry in support of LocalService. First, add the clsid subkey of the component to \HKEY_CLASSES_ROOT\CLSID. Attach an AppID named value to the clsid subkey. The text value should be the clsid of the component. Also, insert a clsid subkey at \HKEY_CLASSES_ROOT\AppID. Add the LocalService named value to the subkey. The text content should be the short name of the service. This name is identical to the name of the service in the services database. A second named value, ServiceParameters, is optional. Its data is passed to the entry point of the service (similar to command-line data).

The registry entries for ServiceA are illustrated in Figures 1 and 2.

ms809975.localserv_1(en-us,MSDN.10).gif

Figure 1. \HKEY_CLASSES_ROOT\AppID\(clsid)

ms809975.localserv_2(en-us,MSDN.10).gif

Figure 2. \HKEY_CLASSES_ROOT\CLSID\(clsid)

More generic COM registration may be done as well. For example, the service may publish a program identifier (ProgId). This requires making extra entries in the registry. Another possibility for COM registration is registering a local server. If LocalService and LocalServer32 are available, LocalService is preferred, and LocalServer32 is ignored. A COM component executing in this context supports most COM registry entries.

The service application cannot be registered as an in-process server. Therefore, the process framework cannot be a dynamic link library (DLL) or DLL surrogate.

You can enter this data in the registry by means of several techniques. You can use a registry batch file or registry script file. Additionally, you can add the data programmatically. This article provides example code for registering ServiceA.

To complete the process, a service must be registered properly as a service. To do so, write an installation procedure or use a service utility. The code to register a service with a COM component is identical to the code to register a generic service. The following code example registers a simple service.

// Handle to service control manager and service
SC_HANDLE hSCM, hService;

// Buffer for drive letters
TCHAR szDrives[]="abcdefghijklmnopqrstuvwxyz";

// Get current drive number and build path to service.
int nDrive=_getdrive();
TCHAR lpszBinaryPathName[100];
sprintf(lpszBinaryPathName, "%c:%s", szDrives[nDrive-1],
   TEXT("\\program files\\servicea\\servicea.exe")); 
   
// Obtain handle to SCM
hSCM=OpenSCManager( NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS );

// Install service - ServiceA
hService = CreateService( hSCM, TEXT("ServiceA"), "ServiceA",
   SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, lpszBinaryPathName, NULL, NULL, NULL,NULL, NULL); 

// Close handles
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);

Coding the Service

As with most COM applications, to export a service component, you should begin by defining a language-independent interface. Use the interface definition language (IDL) to define the interface. You should use the output files that result from compiling the IDL files to create, link, and register a proxy stub DLL. You create a proxy stub DLL to marshal any custom interfaces.

Coding a COM component in a service is much like coding a traditional service. In the primary thread, start the service dispatcher thread. Next, expose a COM component through the service and not through the primary thread of the service application. Place the COM-specific code in the service. Finally, create proxies to expose the component's interfaces to any clients.

When a COM client starts a service, "-service" is passed as the command-line argument of the application. After building a service entry table, invoke StartServiceCtrlDispatcher in the primary thread to link to the service control dispatcher thread.

When the service starts, to provide COM components to a client it should:

  • Load the COM libraries
  • Register the class object of the component
  • Revoke the class object of the component
  • Unload the COM libraries
  • Provide service proxies for non-COM clients

As a standard service, the service should register a handler with RegisterServiceCtrlHandlerEx and regularly provide service status updates.

In our example, ServiceA resides in a single-threaded apartment (STA). As such, it contains a message loop for processing COM requests. In our example, the message loop is also used for interthreaded communication between the service handler and a related service. The following code demonstrates this.

Note   The numbers following comments that precede bolded code annotate specific code and are not part of the actual program.

void WINAPI ServiceMain(DWORD argc, LPTSTR *arg)
{

   // Message buffer and register cookie
   MSG msg;
   DWORD dwRegister;

   // Register service handler
   hServiceStatus=RegisterServiceCtrlHandlerEx( szService, ServiceHandler, NULL);

   // Set and report pending start state.
   servicestatus.dwCheckPoint=500;
   servicestatus.dwWaitHint=1;
   servicestatus.dwCurrentState=SERVICE_START_PENDING;
   SetServiceStatus( hServiceStatus, &servicestatus);

   // Get current thread id.
   dwThreadId=GetCurrentThreadId();

   // Load COM libraries. (1)
   CoInitialize(NULL);

   // Initialize interface and classfactory.
   IServiceInterface *pInt=NULL;
   XBuildLSO* pClf=new XBuildLSO;

   // Register class factory. (2)
   CoRegisterClassObject(CLSID_Service,(IUnknown*) pClf, CLSCTX_SERVER,
      REGCLS_MULTIPLEUSE, &dwRegister);
   
   // Report service as running.
   servicestatus.dwCheckPoint=0;
   servicestatus.dwWaitHint=0;
   servicestatus.dwCurrentState=SERVICE_RUNNING;
   SetServiceStatus( hServiceStatus, &servicestatus);

   msg.message=0;

   // Message loop of servie
   while(msg.message != STOP)
   {
      // Get and dispatch next message. (3)
      GetMessage(&msg, NULL, 0, 0);
      DispatchMessage(&msg);
      
      // If proprietary message....(4)
      if(msg.message == 130)
      {
         // If component not created, create it and initialize interface
         if(pComponent == NULL )
            pClf->CreateInstance(NULL, IID_IServiceA, (void**) &pInt);
         else
            // Initialize interface
            pComponent->QueryInterface(IID_IServiceA, (void**) &pInt);

         // Invoke task (5)
         pInt->TaskA();
         pInt->Release();
      }
      
   }

   // Revoke class factory before exiting service. (6)
   CoRevokeClassObject(dwRegister);

   // Unload COM libraries, and set service status to stop.
   CoUninitialize();
   servicestatus.dwCheckPoint=0;
   …

The important elements of the program are:

  1. Load the COM libraries with CoInitialize. If desired, substitute CoInitializeEx to change the apartment model.
  2. Register the class object with CoRegisterClassObject. The context of the class object is CLSCTX_ALL, CLSCTX_SERVER, or CLSCTX_LOCAL_SERVER. The class object is registered with the REGCLS_MULTIUSE flag. Do not use REGCLS_SINGLEUSE. Registration fails if you use this flag. The operating system allows a single instance of a service. For this reason, the COM component must be available to multiple clients simultaneously.
  3. Get and dispatch a message from the thread queue.
  4. In this example, clients are expected to submit commands via custom service control messages (between 128 and 255). When ServiceA receives such a request, it validates the component and interface. If a component instance does not exist, CreateInstance is invoked to create the component and set the interface pointer. Otherwise, QueryInterface is called to initialize only the interface pointer.
  5. The service invokes the appropriate task on behalf (as a proxy) of the service client using the interface pointer.
  6. Because the COM component is linked to the service, the program revokes the class object and unloads the COM libraries before the service exits.

The following code example reflects one proposed algorithm. The nature of the service application, service thread, or COM component could dictate a different approach. The service handler for ServiceA provided here is not exceptional. It processes service control code requests by posting them to the service thread.

DWORD WINAPI ServiceHandler( DWORD fdwControl, DWORD dwEventType,
   LPVOID lpEventData, LPVOID lpContext )
{
   // Switch on service control message.
   switch(fdwControl)
   {

      // Interrogate: report status of service.
      case SERVICE_CONTROL_INTERROGATE:
         SetServiceStatus( hServiceStatus, &servicestatus);
         break;

      // Post stop flag to service thread.
      case SERVICE_CONTROL_STOP:
         PostThreadMessage(dwThreadId, STOP, 0, 0);
         break;

      // Post Transaction code to service thread.
      case TASKA:
         PostThreadMessage(dwThreadId,(UINT) TASKA, 0, 0);
         break;

      default:
         return ERROR_CALL_NOT_IMPLEMENTED;
   }

   return NO_ERROR;
}

Within a service, you can assign a COM component a user or system security context. If you don't assign a user context, the security context defaults to LocalSystem. LocalSystem provides services with expanded privileges, but it limits access to shared resources. In addition, LocalSystem restricts access to the interactive user and desktop.

Services registered with LocalService are not automatically stopped between logon sessions. Like other services, LocalService continues to execute when a user session concludes. The service provides the appropriate behavior for stop and shutdown control messages.

The COM Component

The coding of the COM component and its affiliated class factory is completely conventional. Inspect component.cpp, classfactory.cpp, component.h, and classfactory.h to view the code for ServiceA.

Clients

As indicated earlier, a service with a COM component supports two types of client connections. COM clients can attach to the COM component through an exposed class object with CoGetClassObject or CoCreateInstance. Curiously, there is not a specific server context for LocalService. Use one of the following flags: CLSCTX_ALL, CLSCTX_SERVER, or CLSCTX_LOCAL_SERVER. The following example is typical of a COM client that creates and manipulates a COM server.

// Load COM libraries and initialize class factory pointer.
CoInitialize(NULL);
CoGetClassObject( CLSID_Service, CLSCTX_SERVER, NULL,
   IID_IClassFactory, (void**) &pClf);

// Connect to component instance and acquire interface pointer.
pClf->CreateInstance(NULL, IID_IServiceA, (void**) &pInt);

// Perform a task.
pInt->TaskA();

// Release interface pointers and COM libraries.
…

With the assistance of the SCM, the service client acquires a service handle to connect to the service and send instructions to the COM component. The ControlService function is used to send commands, such as start or stop, to the service. This code follows:

hSCM=OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS );

hService=OpenService(hSCM, "ServiceA", SERVICE_ALL_ACCESS);
…


void CTransactionDlg::OnTran1() 
{
   // TODO: Add your control notification handler code here.

   // Send command to service component.
   SERVICE_STATUS service_status;
   ControlService(   hService, 130, &service_status);
}

Conclusion

This article helps you craft a service based on the LocalService registry key. It is a hybrid of a service and a local server. Remember, in general, the rules of COM and services both apply to the entity. You need a fundamental understanding of local servers and services to successfully code this type of service.