Saturday, April 22, 2006

How ATL supports windows service.

If you want to create a service based on the ATL library, you normally will inherit the service from CAtlServiceModuleT which is included in atlbase.h

template
class ATL_NO_VTABLE CAtlServiceModuleT : public CAtlExeModuleT
{
......
int WinMain(int nShowCmd) throw()
}

When the the service control manager (SCM) is asked to start a service, through the StartService function, it starts the process using the CreateProcess function, it will go into the int WinMain(int nShowCmd) throw() inside the class.

int WinMain(int nShowCmd) throw()
{
if (CAtlBaseModule::m_bInitFailed)
{
ATLASSERT(0);
return -1;
}

T* pT = static_cast(this);
HRESULT hr = S_OK;

LPTSTR lpCmdLine = GetCommandLine();
if (pT->ParseCommandLine(lpCmdLine, &hr) == true)
hr = pT->Start(nShowCmd);

#ifdef _DEBUG
// Prevent false memory leak reporting. ~CAtlWinModule may be too late.
_AtlWinModule.Term();
#endif // _DEBUG
return hr;
}


This function is called by the main thread in the process, no additional thread is created yet. This function will in turn call the Start() function.

Inside the start function, it starts to hook up the real windows service stuff here, by checking the registery,

TCHAR szValue[MAX_PATH];
DWORD dwLen = MAX_PATH;
lRes = key.QueryStringValue(_T("LocalService"), szValue, &dwLen);

It will decide whether this is a service. [ In debug build, this won't be compiled and registered as a service to make life easier to do the debug.] If this is registered as a service, then a service table is created and StartServiceCtrlDispatcher is called to connect this service to the SCM.

SERVICE_TABLE_ENTRY st[] =
{
{ m_szServiceName, _ServiceMain },
{ NULL, NULL }
};
if (::StartServiceCtrlDispatcher(st) == 0)
m_status.dwWin32ExitCode = GetLastError();


StartServiceCtrlDispatcher establishes a connection that the SCM can use to send control commands to the service. StartServiceCtrlDispatcher will not return until the service has indicated that it has stopped. Once the connection to the SCM is established, StartServiceCtrlDispatcher creates a secondary thread that is the real starting point for the service. The second thread in this case is a static function called _ServiceMain, which in turn forwards the call to real ServiceMain function.

[MSDN:

When the service control manager starts a service process, it waits for the process to call the StartServiceCtrlDispatcher function. The main thread of a service process should make this call as soon as possible after it starts up. If StartServiceCtrlDispatcher succeeds, it connects the calling thread to the service control manager and does not return until all running services in the process have terminated. The service control manager uses this connection to send control and service start requests to the main thread of the service process. The main thread acts as a dispatcher by invoking the appropriate HandlerEx function to handle control requests, or by creating a new thread to execute the appropriate ServiceMain function when a new service is started.

]

The thread ServiceMain run is NOT the main thread referred here, the main thread is the controlling thread.

static void WINAPI _ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv) throw()
{
((T*)_pAtlModule)->ServiceMain(dwArgc, lpszArgv);
}

The real ServiceMain is here:

void ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv) throw()
{
lpszArgv;
dwArgc;
// Register the control request handler
m_status.dwCurrentState = SERVICE_START_PENDING;
m_hServiceStatus = RegisterServiceCtrlHandler(m_szServiceName, _Handler);
if (m_hServiceStatus == NULL)
{
LogEvent(_T("Handler not installed"));
return;
}
SetServiceStatus(SERVICE_START_PENDING);

m_status.dwWin32ExitCode = S_OK;
m_status.dwCheckPoint = 0;
m_status.dwWaitHint = 0;

T* pT = static_cast(this);
#ifndef _ATL_NO_COM_SUPPORT

HRESULT hr = E_FAIL;
hr = T::InitializeCom();
if (FAILED(hr))
{
// Ignore RPC_E_CHANGED_MODE if CLR is loaded. Error is due to CLR initializing
// COM and InitializeCOM trying to initialize COM with different flags.
if (hr != RPC_E_CHANGED_MODE || GetModuleHandle(_T("Mscoree.dll")) == NULL)
{
return;
}
}
else
{
m_bComInitialized = true;
}

m_bDelayShutdown = false;
#endif //_ATL_NO_COM_SUPPORT
// When the Run function returns, the service has stopped.
m_status.dwWin32ExitCode = pT->Run(SW_HIDE);

#ifndef _ATL_NO_COM_SUPPORT
if (m_bService && m_bComInitialized)
T::UninitializeCom();
#endif

SetServiceStatus(SERVICE_STOPPED);
LogEvent(_T("Service stopped"));
}

The first thing in service is to call RegisterServiceCtrlHandler to register a callback function that the control dispatcher, inside StartServiceCtrlDispatcher, can use to pass control requests to the service. RegisterServiceCtrlHandler also returns a handle that is used in calls to the SetServiceStatus function to update the SCM’s status information about the service.
GISResearch

No comments: