Pocket PC Services

 

Victor Sharov and Vassili Philippov
Spb Software House

June 2004

Applies to:
   Windows Mobile-based Pocket PCs
   Windows Mobile 2003 Second Edition software for Pocket PCs
   Microsoft® eMbedded Visual C++®

Download Pocket PC Services.msi from the Microsoft Download Center.

Summary: This article describes creating services for Windows Mobile-based Pocket PCs and how to shape existing background applications into services that run in one process as different threads. (16 printed pages)

Contents

Introduction
Creating Service DLLs Using MFC
Creating Service DLL Without MFC
Service Registration in Registry
Using Services on Pocket PC 2000/2002
Installing Service
Debugging Pocket PC Services
Conclusion
Appendix

Introduction

Many Pocket PC applications need a background process. For example:

  • A program that shows a window when SD card is inserted
  • A permanent backup program
  • A program that control GPRS traffic
  • A Web server
  • A program that adds a special icons to the taskbar

Now almost all Pocket PC developers create an executable file and put shortcut to this file to \Windows\StartUp so this program is started just after reset and runs in background. This approach has a serious problem because number of processes in Windows® CE is limited by 32. For example in XDA II Pocket PC devices there are 27 processes running just after soft-reset. If you install 2-3 third-party programs that need background process you will have only 1-2 processes for running programs! Some Pocket PC devices contain a lot of processes running even after hard-reset, as shown in Figure 1.

Figure 1. Processes running on a device after a hard-reset.

Microsoft has introduced a solution for this problem in the Windows Mobile platform. The Windows Mobile platform supports services that are DLLs running in one process as different threads. It solves the problem. This article describes how to create and distribute services for Windows Mobile-based Pocket PCs.

The Pocket PC service interface is similar to Pocket PC driver interface. A Pocket PC service is a DLL that should export a set of functions. The services.exe process loads these DLLs and initializes them by calling one of the functions.

This article is an appeal to Pocket PC developers "Use services for background tasks!"

Creating Service DLLs Using MFC

In order to implement an MFC-based service take these steps:

In Microsoft® eMbedded Visual C++® create new WCE MFC AppWizard (dll) project, which is shown in Figure 2:

Figure 2. Creating new WCE MFC AppWizard (dll) project.

  1. Add exported functions definitions (you should use your own prefix instead of MYS). Services.exe process expects these functions to be exported from each service DLL and calls them to initialize and communicate with service:

    extern "C" DWORD PASCAL EXPORT MYS_Close(DWORD dwData)
    {
       AFX_MANAGE_STATE(AfxGetStaticModuleState());
       return 0;
    }
    
    extern "C" DWORD PASCAL EXPORT MYS_Deinit(DWORD dwData)
    {
       AFX_MANAGE_STATE(AfxGetStaticModuleState());
       return 0;
    }
    
    extern "C" DWORD PASCAL EXPORT MYS_Init(DWORD dwData)
    {
       AFX_MANAGE_STATE(AfxGetStaticModuleState());
       return 1;
    }
    
    extern "C" DWORD PASCAL EXPORT MYS_IOControl(
      DWORD dwData,
      DWORD dwCode,
      PBYTE pBufIn,
      DWORD dwLenIn,
      PBYTE pBufOut,
      DWORD dwLenOut,
      PDWORD pdwActualOut)
    {
       AFX_MANAGE_STATE(AfxGetStaticModuleState());
       return 1;
    }
    
    extern "C" DWORD PASCAL EXPORT MYS_Open(
      DWORD dwData,
      DWORD dwAccess,
      DWORD dwShareMode)
    {
       AFX_MANAGE_STATE(AfxGetStaticModuleState());
       return 0;
    }
    
    extern "C" DWORD PASCAL EXPORT MYS_Read(
      DWORD dwData,
      LPVOID pBuf,
      DWORD dwLen)
    {
       AFX_MANAGE_STATE(AfxGetStaticModuleState());
       return 0;
    }
    
    extern "C" DWORD PASCAL EXPORT MYS_Seek(
      DWORD dwData,
      long pos,
      DWORD type)
    {
       AFX_MANAGE_STATE(AfxGetStaticModuleState());
       return 0;
    }
    
    extern "C" DWORD PASCAL EXPORT MYS_Write(
      DWORD dwData,
      LPCVOID pInBuf,
      DWORD dwInLen)
    {
       AFX_MANAGE_STATE(AfxGetStaticModuleState());
       return 0;
    }
    

    MYS here is a function name prefix that is used by applications to make calls to the service through Services.exe (here MYS stands for MY Service). You should use your own 3-letters prefix for calls to …_Init and other functions.

    Note   As this DLL will be dynamically linked any functions exported from this DLL which call into MFC must have the AFX_MANAGE_STATE macro added at the very beginning of the function. It is very important that this macro appear in each function, prior to any calls into MFC. This means that it must appear as the first statement within the function, even before any object variable declarations as their constructors may generate calls into the MFC DLL. Please see MFC Technical Notes 33 and 58 for additional details.

  2. Add these functions names to your project .def file export table (you should use your own prefix instead of MYS). New Project Wizard creates this file is created automatically:

    EXPORTS
        ; Explicit exports can go here
       MYS_Close
       MYS_Deinit
       MYS_Init
       MYS_IOControl
       MYS_Open
       MYS_Read
       MYS_Seek
       MYS_Write
    
  3. Add some specific initialization code to MYS_Init function. Most likely you need to create a new thread here that will encapsulate all service logic. To do that define thread controlling function. That function is the right place to create additional windows, timers or any other application-specific needs. In this sample we create a timer and run thread message loop.

    UINT MyControllingFunction( LPVOID pParam )
    {
       theApp.m_nTimer = SetTimer(0, 0, 10 * 1000, MyTimerProc); 
    
       MSG msg;
       while (GetMessage(&msg, 0, 0, 0))
       {
          TranslateMessage(&msg);
          DispatchMessage(&msg);
       }
       return 0;
    }
    
    extern "C" DWORD PASCAL EXPORT MYS_Init(DWORD dwData)
    {
       AFX_MANAGE_STATE(AfxGetStaticModuleState());
    
       theApp.m_pThread = AfxBeginThread( MyControllingFunction, 0);
    
       return 1;
    }
    

Note Be sure to return non-zero value from MYS_Init function as zero return value indicates service initialization failure and causes service DLL to be unloaded immediately.

Depending on your application needs you can put some logic to the other MYS_… methods in the same manner.

Creating Service DLL Without MFC

The following steps are to be taken in order to implement an MFC-based service:

In Microsoft eMbedded Visual C++ create new WCE Dynamic-Link Library project, which is shown in Figure 3:

Figure 3. Creating new WCE Dynamic-Link Library project.

  1. Add exported functions definitions (you should use your own prefix instead of MYS). Services.exe process expects these functions to be exported from each service DLL and calls them to initialize and communicate with service:

    DWORD MYS_Close(DWORD dwData)
    {
       return 0;
    }
    
    DWORD MYS_Deinit(DWORD dwData)
    {
       return 0;
    }
    
    DWORD MYS_Init(DWORD dwData)
    {
       return 1;
    }
    
    DWORD MYS_IOControl(
      DWORD dwData,
      DWORD dwCode,
      PBYTE pBufIn,
      DWORD dwLenIn,
      PBYTE pBufOut,
      DWORD dwLenOut,
      PDWORD pdwActualOut)
    {
    
       return 1;
    }
    
    DWORD MYS_Open(
      DWORD dwData,
      DWORD dwAccess,
      DWORD dwShareMode)
    {
       return 0;
    }
    
    DWORD MYS_Read(
      DWORD dwData,
      LPVOID pBuf,
      DWORD dwLen)
    {
    
       return 0;
    }
    
    DWORD MYS_Seek(
      DWORD dwData,
      long pos,
      DWORD type)
    {
    
       return 0;
    }
    
    DWORD MYS_Write(
      DWORD dwData,
      LPCVOID pInBuf,
      DWORD dwInLen)
    {
    
       return 0;
    }
    

    Note MYS here is a function name prefix that is used by applications to make calls to the service through Services.exe (here MYS stands for MY Service). You should use your own 3-letters prefix for calls to …_Init and other functions.

  2. Add these functions names to your project .def file export table (you should use your own prefix instead of MYS). You have to create manually this text file named as you project with .def extension and add it to your project:

    EXPORTS
        ; Explicit exports can go here
       MYS_Close
       MYS_Deinit
       MYS_Init
       MYS_IOControl
       MYS_Open
       MYS_Read
       MYS_Seek
       MYS_Write
    
  3. Add some specific initialization code to MYS_Init function. Most likely you need to create a new thread here that will encapsulate all service logic. To do this define thread controlling function. That function is the right place to create additional windows, timers or any other application-specific needs. In this sample we create a timer and run thread message loop.

    unsigned long __cdecl SSWControllingFunction( LPVOID pParam )
    {
       g_nTimer = SetTimer(0, 0, 10 * 1000, SSWTimerProc); 
    
       MSG msg;
       while (GetMessage(&msg, 0, 0, 0))
       {
          TranslateMessage(&msg);
          DispatchMessage(&msg);
       }
       return 0;
    }
    
    DWORD MYS_Init(DWORD dwData)
    {
    
       HANDLE hThread = CreateThread( 0, 0, SSWControllingFunction, 0, 0, 0);
    
       return 1;
    }
    

Note Be sure to return non-zero value from MYS_Init function as zero return value indicates service initialization failure and causes service DLL to be unloaded immediately.

Depending on your application needs you can put some logic to the other MYS_… methods in the same manner.

Service Registration in Registry

In order to have service automatically started at system startup you should add an uniquely named subkey to the registry HKEY_LOCAL_MACHINE\Services\Service key and specify the following values:

Order : REG_DWORD

  • Order in which Services.exe will load each service. The service with the lowest order is loaded first.

Dll : REG_SZ

  • Dynamic-link library (DLL) file to be loaded. Only file name without path. This DLL should be located in \Windows folder.

Keep : REG_DWORD

  • Must be 1 for services that should run in background, if Keep = 0, the DLL will be unloaded immediately after initialization.

Prefix : REG_SZ

  • Prefix of your functions exported from the service DLL (instead of xxx in xxx_Init, etc). Must be 3 symbols.

Index : REG_SZ

  • Service index (set to 0).

DisplayName : REG_SZ

  • Display service name.

Description : REG_SZ

  • Description of display service.

Context : REG_DWORD

  • Initial value passed into the initialization routine (must by 0).

For example, for the Sample MFC-based service to start as a service at boot time, the following registry key should be used. Figure 4 shows other service settings in the registry.

[HKEY_LOCAL_MACHINE\Services\MyServ]
    "Dll"="MYS.dll"
    "Order"=dword:8
    "Keep"=dword:1
    "Prefix"="MYS"
    "Index"=dword:0
    "Context"=dword:0
    "DisplayName"="Sample MFC Service"
    "Description"="Sample Service demonstratig MFC usage"

Figure 4. Service settings in registry.

Most probably you will write these values to registry in your installer using .inf file. Here is code for .inf file:

HKLM,Services\MyServ,Dll, 0x00000000,MYS.dll
HKLM,Services\MyServ,Prefix, 0x00000000,MYS
HKLM,Services\MyServ,Order, 0x00010001, 8
HKLM,Services\MyServ,Keep, 0x00010001, 1
HKLM,Services\MyServ,Index, 0x00010001, 0
HKLM,Services\MyServ,Context, 0x00010001, 0
HKLM,Services\MyServ,DisplayName, 0x00000000,Sample MFC Service
HKLM,Services\MyServ,Description, 0x00000000,Sample Service demonstratig MFC usage

Please refer to MSDN® Windows CE .NET 4.2 for details.

Using Services on Pocket PC 2000/2002

Microsoft services.exe is not available on Pocket PC 2000 and Pocket PC 2002 platforms. You can use the third-party solution described in the Pocket PC services article at Pocket PC Developer Network to run your Pocket PC service on these platforms.

Installing Service

To install a service you should copy your service DLL into the \Windows folder and start this service from Install_Exit function in Setup.dll. Do not forget to stop the service before uninstalling your program (in Uninstall_Init function of your Setup.dll).

Step by step:

  1. Include your service DLL into your .inf file and install it to \Windows folder

  2. Add lines into your .inf file to register your service in the registry: (for more information, see Service Registration in Registry)

    [RegSettings.All]
    HKLM,Services\MyServ,Dll, 0x00000000,MYS.dll
    HKLM,Services\MyServ,Prefix, 0x00000000,MYS
    HKLM,Services\MyServ,Order, 0x00010001, 8
    HKLM,Services\MyServ,Keep, 0x00010001, 1
    HKLM,Services\MyServ,Index, 0x00010001, 0
    HKLM,Services\MyServ,Context, 0x00010001, 0
    HKLM,Services\MyServ,DisplayName, 0x00000000,Sample MFC Service
    HKLM,Services\MyServ,Description, 0x00000000,Sample Service demonstratig MFC usage
    
  3. Start your service after installation your program (please refer to Using Installation Functions in Setup.dll for details on creating custom installations:

    typedef HANDLE Proc_ActivateService(LPCWSTR lpszDevKey, DWORD dwClientInfo);
    …codeINSTALL_EXIT
    Install_Exit(
        HWND    hwndParent,
        LPCTSTR pszInstallDir,
        WORD    cFailedDirs,
        WORD    cFailedFiles,
        WORD    cFailedRegKeys,
        WORD    cFailedRegVals,
        WORD    cFailedShortcuts
    )
    {
       …   HANDLE hService;
    
       HINSTANCE hLibrary = ::LoadLibrary(_T("coredll.dll"));
    
       if (NULL == hLibrary)
       {
          return FALSE;
       }
    
       FARPROC pProc = ::GetProcAddress(hLibrary, _T("ActivateService"));
    
       if (NULL == pProc)
       {
          ::FreeLibrary(hLibrary);
          return FALSE;
       }
    
       Proc_ActivateService *pProcActivateService = (Proc_ActivateService*)pProc;
    
       hService = pProcActivateService(lpName, 0);
       …}
    
    Stop your service before uninstalling your program: 
    
    typedef BOOL Proc_DeregisterService(HANDLE hService);
    typedef HANDLE Proc_GetServiceHandle(LPWSTR szPrefix, LPWSTR szDllName, DWORD pdwDllBuf);
    …codeUNINSTALL_INIT
    Uninstall_Init(
        HWND        hwndParent,
        LPCTSTR     pszInstallDir
    )
    {
       …   HINSTANCE hLibrary = LoadLibrary(_T("coredll.dll"));
    
       if (NULL == hLibrary)
       {
          return FALSE;
       }
    
       FARPROC pProc = GetProcAddress(hLibrary, _T("DeregisterService"));
    
       if (NULL == pProc)
       {
          ::FreeLibrary(hLibrary);
          return FALSE;
       }
    
       Proc_DeregisterService *pProcDeregisterService = (Proc_DeregisterService*)pProc;
    
       pProc = GetProcAddress(hLibrary, _T("GetServiceHandle"));
    
       if (NULL == pProc)
       {
          ::FreeLibrary(hLibrary);
          return FALSE;
       }
    
       Proc_GetServiceHandle *pProcGetServiceHandle = (Proc_GetServiceHandle*)pProc;
    
       // Add "0:" to specify instance desired
       TCHAR lpFullName[7];
       _tcscpy(lpFullName, lpName);
       _tcscat(lpFullName, _T("0:"));
       DWORD dwDllName = 0;
       HANDLE hService = pProcGetServiceHandle(lpFullName, NULL, dwDllName);
    
       bRes = pProcDeregisterService(hService);
       …}
    
  4. You should also stop the service before installing your program to support "upgrade" scenario

Debugging Pocket PC Services

You cannot attach your debugger to services.exe because you cannot copy this file from ROM to get local copy (but if you have local copy of Windows Mobile services.exe you can attach to this process). Nevertheless you can use log files (for example STLogFile Library) to trace your program.

Conclusion

We strongly recommend using services for background tasks because of the 32 processes limit imposed by Windows CE. Pocket PC 2000 and Pocket PC 2002 do not implement services.exe process but there is a way to use your services on those devices as well. Please refer to the Pocket PC services article on the Pocket PC Developer Network Web site for details.

Appendix

Sample MyService Project

This eMbedded Visual C++ project can be used as a template for writing a service. The service itself does not do much, it just starts a beeper that beeps about every 10 seconds. Please note doing regular actions on a timer on a mobile device is generally a bad idea because it keeps the processor active and hence heavily reduces battery life.

In order to run this service you need to make sure you have the following keys set. You must reset your device once you have made these entries.

    [HKEY_LOCAL_MACHINE\Services\MyService]
    "Dll"="MyService.dll"
    "Order"=dword:8
    "Keep"=dword:1
    "Prefix"="SRV"
    "Index"=dword:0
    "Context"=dword:0
    "DisplayName"="Sample MFC Service"
    "Description"="This sample service makes your device beep about every 10 seconds."