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"));
CRegKey rkServices;
LONG lRes = rkServices.Open(HKEY_LOCAL_MACHINE, _T("Services"));
DWORD dwIndex = 0;
DWORD dwNameLength = 64;
TCHAR szBuffer[64] = {0};
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"));
DWORD dwType = 0;
DWORD dwValueIndex = 0;
BYTE pData[256] = {0};
DWORD dwDataLength = 256;
while( rkServices.EnumKey(dwIndex++,szBuffer,&dwNameLength) == ERROR_SUCCESS )
{
ATLTRACE(_T("%s\n"),szBuffer);
CRegKey rkService;
if( rkService.Open(rkServices,szBuffer) == ERROR_SUCCESS )
{
CServiceInfo * pServiceInfo = new CServiceInfo();
if( !pServiceInfo )
continue;
pServiceInfo->m_strName = szBuffer;
szBuffer[0] = 0;
dwNameLength = 64;
dwType = 0;
dwValueIndex = 0;
pData[0] = 0;
dwDataLength = 256;
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;
}
szBuffer[0] = 0;
dwNameLength = 64;
pData[0] = 0;
dwDataLength = 256;
}
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;
}
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() )
{
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 ,
WORD , HWND , BOOL& )
{
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 ,
WORD , HWND , BOOL& )
{
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