Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Service Browser Application for Windows Mobile

0.00/5 (No votes)
11 Feb 2008 1  
Helper application for managing services running under Services.exe process on Windows Mobile platform.

Contents

Introduction

One of the projects I have been working on recently required a background process running on a Smartphone device. Windows Mobile SDK provides the programming model for writing services that run in Services.exe process. I have decided to use that model for my project. During development/debugging of the service, I needed a way to start/stop my service while I have a Visual Studio attached to the Services.exe process on the target device. This article shows how to create an application that allows managing services running on a Smartphone device.

Background

Service Browser was developed using Visual Studio 2005, Windows Mobile 6 SDK Standard and Windows Template Library (WTL) 8.0. The application was tested on the MOTO Q 9h Smartphone and Visual Studio 2005 emulators.

Using the Code

Download CAB and install it on your device or use the provided solution to build the application. Make sure you have SDK and WTL installed.

Services.exe

Services.exe reference from the SDK documentation explains all the steps that are required to create and register a service. We need to know how a service is registered, loaded and unloaded from Services.exe process.

Registration is done by writing an arbitrary key to the Registry under HKEY_LOCAL_MACHINE\Services. That key must have certain values that are described below:

Value Type Description
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. File name with the extension only. 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 for service functions exported from the service DLL. Must be at least 3 symbols.
Index REG_DWORD Service index
DisplayName REG_SZ Display service name
Description REG_SZ Description of display service
Context REG_DWORD Value passed into the initialization routine

NOTE: There is no information on what values are optional and what values are required. From personal experience Order, Dll, Keep Prefix, Index and Context must be provided everything else is optional. I have also seen that some of the services had other different values in the Registry that are service specific.

In order to load a service to the Services.exe process, the application must call ActivateService function:

HANDLE ActivateService(
  LPCWSTR lpszDevKey,
  DWORD dwClientInfo
);

The first parameter of this function is the key under which the service is registered in the Registry. The second parameter should be set to 0. This function loads the service and returns a handle to the service.

To unload a service application can use the handle that was returned by ActivateService and call DeregisterService function:

BOOL DeregisterService(
  HANDLE hDevice
);

In case when an application doesn't have a valid handle to the service, the handle can be obtained by using GetServiceHandle:

HANDLE GetServiceHandle(
  LPWSTR szPrefix,
  LPWSTR szDllName,
  DWORD pdwDllBuf
);

The first parameter is the most important parameter. It specifies the prefix key for the service in the form XXXN:, where XXX is the Prefix that was specified in the Registry for that service and N is the Index from the Registry as well.

NOTE: I have seen that some of the services use prefixes that are more than 3 characters in length, but only the 3 first characters are used by the system. The documentation states that GetServiceHandle is a depreciated function and CreateFile should be used instead with the exception when handle is obtained for the purpose of deregistering the service.

Another way of obtaining a service handle is to enumerate all loaded services using EnumServices function:

BOOL EnumServices(
  PBYTE pBuffer,
  DWORD * pdwServiceEntries,
  DWORD * pdwBufferLen
);

In order to numerate all running services the EnumServices has to be called twice. First with pBuffer set to NULL and pdwBufferLen set to 0. The function will return the required size for pBuffer and number of running services. Second call to EnumServices with the valid pBufffer of the right size will return information about running services. The returned buffer consists of two parts according to the documentation. We will be using the first part which is a total of pdwServiceEntries ServiceEnumInfo structures:

Typedef struct_ServiceEnumInfo {
  WCHAR szPrefix[6];
  WCHAR szDllName;
  HANDLE hServiceHandle;
  DWORD dwServiceState;
} ServiceEnumInfo;

Now we have enough information to build the application.

Application

Please use WTL Mobile Application Wizard under "Other Languages/Visual C++/Smart Device". In this application, I have used "Dialog base" application type with the "Orientation aware" checked. For a detailed step by step description of using the wizard, please refer to my other article: GPS and Web Service using C++ ATL/WTL (Windows Mobile 6, Standard).

The core of the application is the list of available services which is generated by reading all the keys from the Registry under HKEY_LOCAL_MACHINE\Services. EnumServices is used to mark which services are "running" (loaded). Loading and unloading of the selected service from the list is done by using "Start" and "Stop" menu items.

VOID CServiceBrowserDialog::RefreshServices()
{
    ATLTRACE(_T("CServiceBrowserDialog::RefreshServices\n"));

    //Get services information from the registry
    CRegKey rkServices;
    LONG lRes = rkServices.Open(HKEY_LOCAL_MACHINE, _T("Services"));

    //Registry Key/Value name
    DWORD dwIndex = 0;
    DWORD dwNameLength = 64;
    TCHAR szBuffer[64] = {0};

    //Registry Values we are interested in
    CString strPrefx(_T("Prefix"));
    CString strDll(_T("Dll"));
    CString strDisplayName(_T("DisplayName"));
    CString strDescription(_T("Description"));
    CString strIndex(_T("Index"));
    CString strOrder(_T("Order"));
    CString strKeep(_T("Keep"));
    CString strContext(_T("Context"));

    //Registry Value data and type
    DWORD dwType = 0;
    DWORD dwValueIndex = 0;
    BYTE pData[256] = {0};
    DWORD dwDataLength = 256;

    //Enumerate through services subkeys
    while( rkServices.EnumKey(dwIndex++,szBuffer,&dwNameLength) == ERROR_SUCCESS )
    {
        ATLTRACE(_T("%s\n"),szBuffer);

        //Open each Service key
        CRegKey rkService;
        if( rkService.Open(rkServices,szBuffer) == ERROR_SUCCESS )
        {
            CServiceInfo * pServiceInfo = new CServiceInfo();
            if( !pServiceInfo )
                continue;

            pServiceInfo->m_strName = szBuffer;

            //We will reuse buffer for name
            szBuffer[0] = 0;
            dwNameLength = 64;

            dwType = 0;
            dwValueIndex = 0;
            pData[0] = 0;
            dwDataLength = 256;


            //Get all values for each registered service
            while( RegEnumValue(rkService,dwValueIndex++,szBuffer,
	     &dwNameLength,NULL,&dwType, pData, &dwDataLength) == ERROR_SUCCESS )
            {
                switch( dwType )
                {
                case REG_SZ:
                    ATLTRACE(_T("\t%s - %s\n"),szBuffer, pData);
                    if( strPrefx.CompareNoCase(szBuffer) == 0 )
                        pServiceInfo->m_strPrefix = (TCHAR*)pData;
                    else if( strDll.CompareNoCase(szBuffer) == 0 )
                        pServiceInfo->m_strDll = (TCHAR*)pData;
                    else if( strDescription.CompareNoCase(szBuffer) == 0 )
                        pServiceInfo->m_strDescription = (TCHAR*)pData;
                    else if( strDisplayName.CompareNoCase( szBuffer ) == 0 )
                        pServiceInfo->m_strDisplayName = (TCHAR*)pData;
                    break;
                case REG_DWORD:
                    {
                        DWORD dwValue = *((LPDWORD)pData);
                        ATLTRACE(_T("\t%s - %d\n"),szBuffer, dwValue);
                        if( strIndex.CompareNoCase(szBuffer) == 0 )
                            pServiceInfo->m_dwIndex = dwValue;
                        else if( strKeep.CompareNoCase(szBuffer) == 0 )
                            pServiceInfo->m_dwKeep = dwValue;
                        else if( strOrder.CompareNoCase(szBuffer) == 0 )
                            pServiceInfo->m_dwOrder = dwValue;
                        else if( strContext.CompareNoCase(szBuffer) == 0 )
                            pServiceInfo->m_dwContext = dwValue;
                    }
                    break;
                default:
                    ATLTRACE(_T("\t%s\n"),szBuffer);
                    break;
                }

                //Reset everything for next iteration
                szBuffer[0] = 0;
                dwNameLength = 64;

                pData[0] = 0;
                dwDataLength = 256;
            }

            //If there is no "Prefix" it is not a valid service key
            if( pServiceInfo->m_strPrefix.IsEmpty() )
            {
                delete pServiceInfo;
                pServiceInfo = NULL;
            }else
            {
                pServiceInfo->m_strKey.Format(_T("%s%d:"),pServiceInfo->m_strPrefix, 
                                pServiceInfo->m_dwIndex == -1 ? 0: 
						pServiceInfo->m_dwIndex);
                m_services.insert(  InfoPair(pServiceInfo->m_strKey, pServiceInfo) );
            }
        }

        szBuffer[0] = 0;
        dwNameLength = 64;
    }

    //Let's get information about running services
    DWORD nServices = 0;
    DWORD nBufferSize = 0;
    PBYTE pBuffer = NULL;
    ServiceEnumInfo * pInfo = NULL;

    EnumServices(NULL, &nServices, &nBufferSize);

    if( nBufferSize != NULL && nServices != 0 )
    {
        pBuffer = (PBYTE)malloc(nBufferSize);

        nServices = 0;

        if( EnumServices(pBuffer,&nServices, &nBufferSize) && nServices != 0 )
            pInfo = (ServiceEnumInfo *)pBuffer;    
    }

    if( pInfo )
    {
        InfoMap::iterator serviceIter;

        for( int i = 0; i < nServices; i++ )
        {
            serviceIter = m_services.find( pInfo[i].szPrefix );

            if( serviceIter != m_services.end() )
            {
                //Save service handle
                serviceIter->second->m_hHandle = pInfo[i].hServiceHandle;
            }else
            {
                ATLASSERT( serviceIter != m_services.end() );
                continue;
            }
        }
    }

    if( pBuffer )
        free((void*)pBuffer);
}

VOID CServiceBrowserDialog::CleanUp()
{
    m_list.DeleteAllItems();

    InfoMap::iterator serviceIter = m_services.begin();

    while( serviceIter != m_services.end() )
    {
        CServiceInfo * pServiceInfo = serviceIter->second;
        delete pServiceInfo;

        serviceIter++;
    }
    m_services.clear();
}

LRESULT CServiceBrowserDialog::OnStart(WORD /*wNotifyCode*/, 
	WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
    INT nSelected = m_list.GetSelectedIndex();
    if( nSelected != -1 )
    {
        CServiceInfo * pInfo = (CServiceInfo*)m_list.GetItemData(nSelected);
        HANDLE h = ActivateService(pInfo->m_strName, 0);

        UpdateList();
    }
    return 0;
}

LRESULT CServiceBrowserDialog::OnStop(WORD /*wNotifyCode*/, 
	WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
    INT nSelected = m_list.GetSelectedIndex();
    if( nSelected != -1 )
    {
        CServiceInfo * pInfo = (CServiceInfo*)m_list.GetItemData(nSelected);
        HANDLE h = GetServiceHandle(CT2W(pInfo->m_strKey), NULL,0);

        BOOL bRet = DeregisterService(h);

        UpdateList();
    }
    return 0;
} 

Resources

History

  • February 11, 2008 - Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here