1. 创建windows console应用程序。vs自带的windows service模板创建出来的项目看不懂。
2. _tmain函数这样写:
/*
Main routine that starts the service control dispatcher
*/
VOID _tmain (
int
argc, LPTSTR argv[])
{
SERVICE_TABLE_ENTRY DispatchTable[]
=
{
{ ServiceName, ServiceMain },
{ NULL, NULL }
};
StartServiceCtrlDispatcher (DispatchTable);
return
;
}
3. 然后就是ServiceMain函数:
/*
ServiceMain entry point, called when the service is created by
the main program.
*/
void
WINAPI ServiceMain (DWORD argc, LPTSTR argv[])
{
HANDLE hFile
=
NULL;
DWORD Context
=
1
;
size_t bytes_need_write
=
0
;
DWORD bytes_written
=
0
;
TCHAR write_buffer[
255
];
SYSTEMTIME cur_time;
DWORD n;
LARGE_INTEGER file_size;
//
init logger
logger_set_root_directory(TEXT(
"
C:\\
"
));
hServStatus.dwServiceType
=
SERVICE_WIN32_OWN_PROCESS;
hServStatus.dwCurrentState
=
SERVICE_START_PENDING;
hServStatus.dwControlsAccepted
=
SERVICE_ACCEPT_STOP
|
SERVICE_ACCEPT_SHUTDOWN;
hServStatus.dwWin32ExitCode
=
ERROR_SERVICE_SPECIFIC_ERROR;
hServStatus.dwServiceSpecificExitCode
=
0
;
hServStatus.dwCheckPoint
=
0
;
//
in 2000ms, we should increment dwCheckPoint or set new status
//
while current status is a PENDING start/continue/stop state
hServStatus.dwWaitHint
=
5000
;
/*
Warning. Older VC++ version do not have RegisterServiceCtrlHandlerEx
* defined. You can use RegisterServiceCtrlHandler just as well
*/
#ifdef RegisterServiceCtrlHandlerEx
hSStat
=
RegisterServiceCtrlHandlerEx(ServiceName, ServerCtrlHandlerEx,
&
Context);
#else
hSStat
=
RegisterServiceCtrlHandler(ServiceName, ServerCtrlHandler);
#endif
if
(hSStat
==
NULL) {
GET_ERROR_CODE(n);
UTILS_RIF_WITH_LOG(FALSE, LOG_LEVEL_ERROR, __SDFILE__, __LINE__,
TEXT(
"
Register service ctrl handle failed. Reason: %s\n
"
),
utils_format_error_string(n));
}
//
PENDING start
if
(
!
SetServiceStatus (hSStat,
&
hServStatus)) {
GET_ERROR_CODE(n);
UTILS_RIF_WITH_LOG(FALSE, LOG_LEVEL_ERROR, __SDFILE__, __LINE__,
TEXT(
"
Set service status failed. Reason: %s\n
"
),
utils_format_error_string(n));
}
//
Open the file and log current time
hFile
=
CreateFile(TEXT(
"
C:\\testserv.txt
"
), GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if
(hFile
==
INVALID_HANDLE_VALUE) {
GET_ERROR_CODE(n);
UTILS_RIF_WITH_LOG(FALSE, LOG_LEVEL_ERROR, __SDFILE__, __LINE__,
TEXT(
"
Create testserv file failed. Reason: %s\n
"
),
utils_format_error_string(n));
}
//
Seek to the end of file
UTILS_RETURN_IF_FAIL(GetFileSizeEx(hFile,
&
file_size));
UTILS_RETURN_IF_FAIL(SetFilePointerEx(hFile, file_size, NULL, FILE_BEGIN));
GetLocalTime(
&
cur_time);
StringCchPrintf(write_buffer, _countof(write_buffer), TEXT(
"
TestServ started, write time: %d-%d-%d %d:%d:%d\n
"
),
cur_time.wYear, cur_time.wMonth, cur_time.wDay, cur_time.wHour, cur_time.wMinute, cur_time.wSecond);
StringCchLength(write_buffer, _countof(write_buffer),
&
bytes_need_write);
WriteFile(hFile, write_buffer, bytes_need_write
*
sizeof
(TCHAR),
&
bytes_written, NULL);
hServStatus.dwCheckPoint
=
0
;
hServStatus.dwWin32ExitCode
=
NO_ERROR;
hServStatus.dwServiceSpecificExitCode
=
0
;
hServStatus.dwCurrentState
=
SERVICE_RUNNING;
if
(
!
SetServiceStatus(hSStat,
&
hServStatus)) {
GET_ERROR_CODE(n);
UTILS_RIF_WITH_LOG(FALSE, LOG_LEVEL_ERROR, __SDFILE__, __LINE__,
TEXT(
"
Set service status failed. Reason: %s\n
"
),
utils_format_error_string(n));
}
//
block here until service stop
while
(
!
service_terminate) {
Sleep(
1000
);
}
//
stop service
GetLocalTime(
&
cur_time);
StringCchPrintf(write_buffer, _countof(write_buffer), TEXT(
"
TestServ stopped, write time: %d-%d-%d %d:%d:%d\n
"
),
cur_time.wYear, cur_time.wMonth, cur_time.wDay, cur_time.wHour, cur_time.wMinute, cur_time.wSecond);
StringCchLength(write_buffer, _countof(write_buffer),
&
bytes_need_write);
WriteFile(hFile, write_buffer, bytes_need_write
*
sizeof
(TCHAR),
&
bytes_written, NULL);
CloseHandle(hFile);
hServStatus.dwCheckPoint
=
0
;
hServStatus.dwWin32ExitCode
=
NO_ERROR;
hServStatus.dwServiceSpecificExitCode
=
0
;
hServStatus.dwCurrentState
=
SERVICE_STOPPED;
if
(
!
SetServiceStatus(hSStat,
&
hServStatus)) {
GET_ERROR_CODE(n);
UTILS_RIF_WITH_LOG(FALSE, LOG_LEVEL_ERROR, __SDFILE__, __LINE__,
TEXT(
"
Set service status failed. Reason: %s\n
"
),
utils_format_error_string(n));
}
return
;
}
有几个关键点:
A. hServStatus是一个SERVICE_STRUCTURE,这个structure非常重要,定义了很多关键的数据。主要是:
(1) dwControlAccepted,不支持PAUSE和CONTINUE就不要写上。
(2) dwWin32ExitCode这个member容易混淆。如果当前service的状态是PENDING(start pending/pause pending/continue pending/stop pending),那么可以设置为ERROR_SERVICE_SPECIFIC_ERROR, 这表示pending的时候如果出错了,让windows SCM去check dwServiceSpecificExitCode member来得到出错码。但是如果状态已经成功转换到了RUNNING/STOPPED,那么,要设置这个dwWin32ExitCode为NO_ERROR,此时windows SCM认为service状态转换没有错误发生,dwServiceSpecificExitCode的值被ignore。如果dwWin32ExitCode还为ERROR_SERVICE_SPECIFIC_ERROR的话,windows SCM就会认为service状态转换出错,然后把dwServiceSpecificExitCode作为出错码取出来报告。这就是为什么上面代码中,RUNNING和STOPPED状态转换完成的时候,要设置这两个member的原因。
(3) dwCheckPoint,也是用于pending状态的。pending状态的时候,windows SCM要知道service当前是否alive,依靠的就是周期性的check这个dwCheckPoint member。所以,我们的service在pending状态的时候,要经常使用SetServiceStatus来更新这个dwCheckPoint,否则windows SCM会认为service down,从而强制stop service。此外,当service完成pending状态切换到新状态后,记得重置dwCheckPoint为0.
(4) dwWaitHint,这个member定义一个时间(单位毫秒),在service处于pending状态时,如果过了这里定义的时间,windows SCM发现service的dwCheckPoint没有更新,或者service没有切换到新的非pending状态,那么,windows SCM就会强制stop service。
B. RegisterServiceCtrlHandlerEx函数。用来注册SCM动作的响应函数。也就是stop/continue这些命令的响应。例子:
/*
Control Handler Function
*/
#ifdef RegisterServiceCtrlHandlerEx
DWORD WINAPI ServerCtrlHandlerEx( DWORD dwControl, DWORD dwEventType,
LPVOID lpEventData, LPVOID lpContext)
#else
DWORD WINAPI ServerCtrlHandler( DWORD dwControl)
#endif
//
requested control code
{
DWORD n;
switch
(dwControl) {
case
SERVICE_CONTROL_SHUTDOWN:
case
SERVICE_CONTROL_STOP:
service_terminate
=
TRUE;
hServStatus.dwCheckPoint
++
;
hServStatus.dwWin32ExitCode
=
ERROR_SERVICE_SPECIFIC_ERROR;
hServStatus.dwServiceSpecificExitCode
=
0
;
hServStatus.dwCurrentState
=
SERVICE_STOP_PENDING;
if
(
!
SetServiceStatus(hSStat,
&
hServStatus)) {
GET_ERROR_CODE(n);
UTILS_RVIF_WITH_LOG(FALSE, NO_ERROR, LOG_LEVEL_ERROR, __SDFILE__, __LINE__,
TEXT(
"
Set service status failed. Reason: %s\n
"
),
utils_format_error_string(n));
}
return
NO_ERROR;
case
SERVICE_CONTROL_PAUSE:
break
;
case
SERVICE_CONTROL_CONTINUE:
break
;
case
SERVICE_CONTROL_INTERROGATE:
return
NO_ERROR;
default
:
if
(dwControl
>
127
&&
dwControl
<
256
)
/*
User Defined
*/
break
;
}
return
ERROR_CALL_NOT_IMPLEMENTED;
}
4. service本身的关键代码就是这些,下面是使用SCM如何创建一个service:
#define
SVCNAME TEXT("EricTestService")
int
_tmain(
int
argc, PTSTR argv[], PTSTR env[])
{
DWORD errcode
=
0
;
SC_HANDLE hManager
=
NULL;
SC_HANDLE hService
=
NULL;
setlocale(LC_ALL,
"
CHS
"
);
//
Open SCManager
hManager
=
OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if
(NULL
==
hManager) {
errcode
=
GetLastError();
_tprintf(TEXT(
"
Open service manager failed. Reason: %s\n
"
), utils_format_error_string(errcode));
goto
failed;
}
//
create service
hService
=
CreateService(
hManager,
//
SCM database
SVCNAME,
//
name of service
SVCNAME,
//
service name to display
SERVICE_ALL_ACCESS,
//
desired access
SERVICE_WIN32_OWN_PROCESS,
//
service type
SERVICE_AUTO_START,
//
start type
SERVICE_ERROR_NORMAL,
//
error control type
TEXT(
"
C:\\ScheduleDownload\\Practise\\TestServ\\Debug\\TestServ.exe
"
),
//
path to service's binary
NULL,
//
no load ordering group
NULL,
//
no tag identifier
NULL,
//
no dependencies
NULL,
//
LocalSystem account
NULL);
//
no password
if
(NULL
==
hService) {
errcode
=
GetLastError();
_tprintf(TEXT(
"
Create service failed. Reason: %s\n
"
), utils_format_error_string(errcode));
CloseServiceHandle(hManager);
goto
failed;
}
CloseServiceHandle(hService);
CloseServiceHandle(hManager);
return
0
;
failed:
//
wait key
_getch();
return
1
;
}
这就比较简单了,使用OpenSCManager打开SCM,使用CreateService创建一个service,此时在windows的服务中就能看到了。start type中,设定为SERVICE_AUTO_START就会在开机时启动(不需要用户登录就会启动的,已经测试过了)。其他的函数比如StartService/DeleteService/QueryService...看看MSDN就OK了。