I had to do some maintenance on a Windows ® service and found that you can use an ATL service as a windows service very well without all the overhead.
There are some samples on the internet, but they stop where I wanted to continue. That is, run UI-less services for instance, a job that could be done through a task scheduled-job but better be done through a service?
b.t.w. for the ones interested in a minimal windows service and who feel a sick inclination J writing the smallest EXE possible, they should use a plain C implementation provided by Microsoft’s ® Platform SDK located at [Platform SDK folder]\Samples\WinBase\Service and they should not use MS VC 8 but 4 or so...
Note that the grey highlighted code, is my code, the normal colored code is left over of what the MS Visual Studio created for you through the wizard. In addition, I’ve snipped all trace & debugging code.
The Visual Studio 2005 wizard creates an EXE that is suitable both as COM service (DCOM) and as windows service. You can guess a lot of overhead and unneeded lines.
This service does not perform anything, keep in mind it is a template for a 'scheduled' task-service, a batch for instance. Your code comes within DoDBMSJob...
// Service_Template.h
void
CALLBACK TimerProc(PVOID pdata);
DWORD WINAPI mainJob(LPVOID lpThreadParameter) ;
// Service_Template.cpp : Implementation of WinMain
#include "stdafx.h"
#include "resource.h"
#include "Service_Template.h" //
#pragma warning(disable: 4482) // nonstandard extension used: 'enum CServiceStatus' used in qualified name
[v1_enum] //32 bit rulez
enum CServiceStatus
{
run = 0,
pauze=1,
stop = 2,
shutmedown=3
};
class CService_Module : public CAtlServiceModuleT< CService_Module, IDS_SERVICENAME >
{
private:
// this flag in milliseconds, tells us how often
// is checked for tasks
DWORD m_iPollTime;
HANDLE m_ThreadHandle; // eventually use CHandle
HANDLE m_hServerStopEvent;
bool connDone;
CServiceStatus m_ServiceStatus ;
bool stopNow;
public :
~CService_Module() throw()
{
if (m_ThreadHandle != NULL)
CloseHandle(m_ThreadHandle);
if (m_hServerStopEvent != NULL)
CloseHandle(m_hServerStopEvent);
}
CService_Module() throw(): m_iPollTime(10), m_ThreadHandle(NULL), m_hServerStopEvent(NULL),
stopNow(false)
{
m_ServiceStatus =CServiceStatus::run;
//m_dwTimeOut = 60000; // one minute
}
// we can’t skip this! Leave as is created by wizard
DECLARE_REGISTRY_APPID_RESOURCEID(IDR_GARBAGE_COLLECT, "{44DE094D-9AE1-5A56-A1A7-A20F9EAE0F21}")
HRESULT InitializeSecurity() throw()
{
// we don’t need this here
return S_OK;
}
void OnPauze() throw()
{
m_ServiceStatus = CServiceStatus::pauze;
__super::OnPause();
if ( m_hServerStopEvent != NULL)
SetEvent(m_hServerStopEvent);
}
void OnStop() throw()
{
__super::OnStop();
m_ServiceStatus =CServiceStatus::stop;
if ( m_hServerStopEvent != NULL)
{
stopNow= true;
// be sure not to kill the thread & process
// before it nicely ends
// otherwise, our pointers will ‘Release()’ while
// the instance has been killed already, a common bug!
SetEvent(m_hServerStopEvent);
DWORD tc = GetTickCount();
while(m_hServerStopEvent != NULL || GetTickCount() - tc > 20000) // max 20 seconds wait
Sleep(10);
}
}
void OnContinue( ) throw( )
{
__super::OnContinue();
m_ServiceStatus =CServiceStatus::run;
if ( m_hServerStopEvent != NULL)
SetEvent(m_hServerStopEvent);
}
void OnShutDown() throw()
{
//__super::OnShutDown();
m_ServiceStatus =CServiceStatus::pauze;
if ( m_hServerStopEvent != NULL)
SetEvent(m_hServerStopEvent);
}
HRESULT RegisterAppId(bool bService = false) throw()
{
HRESULT hr = S_OK;
// we extend our service description!
// on W2k and higher, this is user friendly!
// do not forget to add a string ot the string table
// here ‘IDS_Description’
BOOL res = __super::RegisterAppId(bService);
if (bService)
{
//DebugBreak();
if (IsInstalled())
{
SC_HANDLE hSCM = ::OpenSCManagerW(NULL, NULL, SERVICE_CHANGE_CONFIG);
SC_HANDLE hService = NULL;
if (hSCM == NULL)
hr = AtlHresultFromLastError();
else
{
hService = ::OpenServiceW(hSCM, m_szServiceName, SERVICE_CHANGE_CONFIG);
if (hService != NULL)
{
const int m_szServiceNameLen = 4096;
WCHAR m_szServiceDescription[m_szServiceDescriptionLen]={0};
LoadStringW(_AtlBaseModule.GetModuleInstance(),
IDS_Description, m_szServiceDescription, m_szServiceDescriptionLen);
SERVICE_DESCRIPTION sdBuf = {m_szServiceDescription};
res = ChangeServiceConfig2W(hService, SERVICE_CONFIG_DESCRIPTION, &sdBuf);
::CloseServiceHandle(hService);
}
else
hr = AtlHresultFromLastError();
::CloseServiceHandle(hSCM);
}
}
}
return hr;
}
HRESULT PreMessageLoop(int nShowCmd) throw()
{
//problem how to set a timer, we must provide the proc
// a pointer to this
// we could hack it and use HWND instead?
#ifdef _DEBUG
DebugBreak();
#endif
HRESULT hr = __super::PreMessageLoop(nShowCmd);
// if we don't have any COM classes, RegisterClassObjects
// retunrs S_FALSE
// This Causes the process to terminate
// We don't want this, so we return S_OK in this case
if (hr == S_FALSE) hr = S_OK;
if (m_bService == TRUE && hr == S_OK)
m_ThreadHandle = CreateThread(NULL, 0, mainJob, this, 0, 0);
return hr;
}
void __stdcall set_CollectTime(INT iCollectTime) throw()
{
m_iPollTime = iCollectTime;
}
INT get_CollectTime() throw()
{
return m_iPollTime;
}
void __stdcall SetEventHandleForStop(HANDLE eventHandle) throw()
{
m_hServerStopEvent = eventHandle;
}
CServiceStatus GetServiceStatus() throw()
{
return this->m_ServiceStatus;
}
void CALLBACK TimerProc(PVOID pdata) throw()
{
#ifdef _DEBUG
DebugBreak();
#endif
HRESULT hr;
//your registry and initialization comes here
hr = DoDBMSJob(iKeepConnection, bstrConnBuff, dwVersion);
return;
}
// here could be your task
STDMETHODIMP DoDBMSJob(int iAction, PCWSTR bstrConnBuff, DWORD dwVersion) throw()
{
/* iAction 0 do work and exit
* iAction 1 do work, cache connection and exit
* iAction 2 release cached connection and exit
*/
// Your task and cleanup code comes here...
// cleanup code _COULD_ be done in the main class
// but do not forget, if you cleanup there, you might
// fall into the Thread Apartment trap!
// Some COM objects, are not free-threaded
// so if you clean them up when windows
// tells you to stop execution with the thread windows
// ‘gives’ to you, the instance might fail and crash
return hr;
}
};
CService_Module _AtlModule;
DWORD WINAPI mainJob(LPVOID lpThreadParameter) throw()
{
LARGE_INTEGER liDueTime;
//fetch the pointer to our main class
CService_Module* p = static_cast<CService_Module*>(lpThreadParameter);
DWORD dwError = ERROR_SUCCESS;
//optimize memory usage
// should automatically increase if needed
//::SetProcessWorkingSetSize(GetCurrentProcess(), 1280000, 2560000);
DWORD dwWait =0;
HANDLE hServerStopEvent = CreateEventW(
NULL, // no security attributes
TRUE, // manual reset event
FALSE, // not-signalled
NULL); // no name
p->SetEventHandleForStop(hServerStopEvent);
// array with 2 events we want to monitors
HANDLE hEvents[2] = {hServerStopEvent,
::CreateWaitableTimer(NULL, TRUE, L"ASP_Session_Collect") };
HRESULT hr =::CoInitializeEx(NULL, COINIT_DISABLE_OLE1DDE | COINIT_MULTITHREADED);
for (;;) //endless loop
{
INT iPollTime = p->get_CollectTime();
liDueTime.QuadPart=-iPollTime * 10000;
//this timer is not periodic and is recreated each at loop so a negatieve value means a 'relative time'.
if (::SetWaitableTimer(hEvents[1], &liDueTime,0, NULL, NULL, FALSE) == FALSE)
{
dwError = ::GetLastError();
//MessageBox(NULL, _T("CreateTimerFailed"), _T("Error"), MB_OK | MB_ICONERROR);
goto cleanup;
}
// if your Service is Apartment threaded
// use MsgWaitForMultipleObjectsEx!
dwWait = ::WaitForMultipleObjects( 2, hEvents, FALSE, INFINITE);
if (dwWait == WAIT_OBJECT_0+1)
p->TimerProc(NULL);// no need to suspend timer sinced this is recreated each time
else // it was not the timer but a service event
{
CServiceStatus status = p->GetServiceStatus();
if (status==CServiceStatus::stop || status == CServiceStatus::shutmedown )
{
break;
}
while (status == CServiceStatus::pauze)
{
status = p->GetServiceStatus();
Sleep(1000); //wait 1 second
}
if (status==CServiceStatus::stop || status == CServiceStatus::shutmedown )
{
break;
}
// not timer event - error occurred,
}
}
cleanup:
p->DoDBMSJob(2, NULL, 0);
CoUninitialize();
if (hEvents[1])
::CloseHandle(hEvents[1]);
if (hServerStopEvent != NULL)
{
CloseHandle(hServerStopEvent);
hServerStopEvent=NULL;
p->SetEventHandleForStop(hServerStopEvent);
}
return 0;
}
//
extern "C" int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR lpCmdLine, int nShowCmd) throw()
{
return _AtlModule.WinMain(nShowCmd);
}
// we would not be complete without listing stdafx.h this time, I’ve set compatibility with W2k, that would be sufficient for most services
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently,
// but are changed infrequently
#pragma once
#define STRICT
// Modify the following defines if you have to target a platform prior to the ones specified below.
// Refer to MSDN for the latest info on corresponding values for different platforms.
#define WINVER 0x0500 // Change this to the appropriate value to target other versions of Windows.
#define _ATL_ATTRIBUTES //allow db_command etc...
#define _WIN32_WINNT 0x0500 // Change this to the appropriate value to target other versions of Windows.
#define _WIN32_WINDOWS 0x0500 // Change this to the appropriate value to target Windows Me or later.
#ifndef _WIN32_IE // Allow use of features specific to IE 6.0 or later.
#define _WIN32_IE 0x0550 // Change this to the appropriate value to target other versions of IE.
#endif
//explicit disable since our main service ONLY will serve as a link between windows/registration and our mainJob function
#define _ATL_NO_COM_SUPPORT
// IMPORTANT CHANGE THIS instead of _ATL_APARTMENT_THREADED
#define _ATL_FREE_THREADED
#include "resource.h"
#include <atlbase.h>
#include <atlcom.h>
#include <atldbcli.h>
using namespace ATL;
here comes a great ATL feature, support eventlog writing! Put these lines in the RGS file, that Visual Studio created for you. If you don’t do so, the event log will report this: “The description for Event ID ( 0 ) in Source ( CTemplateService) cannot be found. etc)
Add this text in CTemplateService.rgs
HKLM
{
NoRemove SYSTEM
{
NoRemove CurrentControlSet
{
NoRemove Services
{
NoRemove EventLog
{
NoRemove Application
{
'CTemplate Service' ß this name must match IDS_SERVICENAME!
{
val 'EventMessageFile' = s '%MODULE_RAW%'
val 'TypesSupported' = d 7
}
}
}
}
}
}
}
Step 2
Add one resource file (Visual Studio does not support this!) and give it the extension .mc
For instance message.mc
The contents will be:
LanguageNames=(Neutral=0x409:MSG00409)
MessageId=0x1
SymbolicName=MSG_ERROR
Language=English
Your Company: Template Service, returned the following error: %1
.
ß-- one empty line!
In visual studio, you click on the properties of this file and enter at the command line
mc message.mc
outputs:
Enter: message.h message.rc
In your stdafx.h you need to include “message.h” now.
Now you have included message.rc as a resource. A message file is a superior way to include all languages in one executable or dll while the active thread defines the actual languageid being displayed on UI. This trick existed already on NT 3.x but not many developers / translators use it for internationalization. As you see, NT –requires- you to have a message resource file just to support logging to the Event Log store. FYI! Windows ® (Vista) will soon support w3c-‘like’ logging as well! Soon we can say goodbye to the rather unpleasant and difficult to implement eventlog.
Installation
According to the ATL documentation, you can deploy your compiled service as follows:
Install it with: CTemplateService.exe –service
Deinstall it with: CTemplateService.exe -UnRegServer