一、 编写服务程序
一个服务程序至少包括一个入口函数、服务主函数和控制处理函数。
1. 入口函数
入口函数是可执行程序执行的起点,服务程序的入口函数与一般的可执行程序的入口函数 没有区别。
2. 服务主函数
一个服务程序必须具有服务主函数,服务主函数 是服务启动时执行的入口,也是服务的主线程执行起点。
① 服务主函数 ServiceMain
服务主函数一般称作ServiceMain函数。其函数名称没有任何要求,只是起参数接口和调用类型必须与要求一致。
ServiceMain函数原型如下:
VOID WINAPI ServiceMain(
DWORD dwArgc,
LPTSTR *argv);
服务主函数的参数与 main 函数的参数使用方法类似,但是服务主函数的参数不是通过在命
令行启动时设定的,而是通过 SCM 的相关 API 进行传递的(StartService 函数)。
② 向 SCM 注册服务的主函数 StartServiceCtrIDispatcher
SCM 要对服务进行管理,就必须知道服务程序的服务主函数。服务程序通过调用 StartServiceCtrlDispatcher API函数设置服务主函数,同时通知SCM。
v StartServiceCtrlDispatcher 函数原型如下:
BOOL WINAPI StartServiceCtrlDispatcher(
_In_ CONST SERVICE_TABLE_ENTRYW *lpServiceStartTable
);
结构 SERVICE_TABLE_ENIRY 的原型如下:
typedef struct _SERVICE_TABLE_ENTRYW {
LPWSTR lpServiceName;
LPSERVICE_MAIN_FUNCTIONW lpServiceProc;
}SERVICE_TABLE_ENTRYW, *LPSERVICE_TABLE_ENTRYW;
其中 lpServiceName 为服务名称,lpServiceProc 为指向 ServiceMain 的函数指针。只 要将函数的指针赋值给 lpServiceProc,再调用 StartServiceCtrlDispatcher,这个函数就 成为了服务主函数。
3. 控制处理函数
① 控制处理函数Handle
控制处理函数用于处理SCM向服务传递的服务控制请求。控制处理函数Handler原型如下:
VOID WINAPI ServiceHandler(DWORD opcode);
② 注册控制管理函数
API 函数 RegisterServiceCtrlHandler 用于向 SCM 设置一个服务的控制处理函数。
WINAPI
RegisterServiceCtrlHandlerW(
_In_ LPCWSTR lpServiceName,
_In_ __callback LPHANDLER_FUNCTION lpHandlerProc
);
其中 lpServiceName 为服务名称,lpHandlerProc 为 Handler 函数指针。
二、 实现对服务的控制和管理
使用服 务管理 API 自行编写一个服务管理程序。服务管理程序为系统新建服务,负责启动停止服务 程序,并负责管理服务程序的属性等。
1. 创建、删除服务
① OpenSCManager
WINAPI OpenSCManagerW(
_In_opt_ LPCWSTR lpMachineName,
_In_opt_ LPCWSTR lpDatabaseName,
_In_ DWORD dwDesiredAccess
);
v 参数
lpMachineName:输入参数,机器名,如果是本机则使用NULL。
lpDatabaseName:输入参数,SCM 数据库名,设置为 NULL 或设置为 SERVICES ACTIVE DATABASE,效果等同。
dwDesiredAccess:输入参数,需要的权限,可设置为 SC MANAGER ALL ACCESS、 SC_MANAGER_CREATE_SERVICE、SC_MANAGERCONNECT 等。
v 返回值
SC_HANDLE 类型,SCM 的句柄。
② CreateService
向系统创建服务,创建时指定服务的属性,函数原型如下:
v 参数
CreateService 函数的参数很多,但是设置方法都很简单,直接从参数名就可大体知道参数的意义。
v 返回值
返回 SC_HANDLE 类型,服务的句柄。
③ OpenService
打开服务,获取服务句柄,函数原型如下:
WINAPI OpenServiceW(
_In_ SC_HANDLE hSCManager,
_In_ LPCWSTR lpServiceName,
_In_ DWORD dwDesiredAccess
);
④ DeleteService
删除服务,以服务句柄为参数,函数原型如下:
WINAPI DeleteService(
_In_ SC_HANDLE hService
);
2. 启动、停止服务、向服务发送控制请求
① StartService
启动服务,并设置服务主函数的参数。
BOOL WINAPI StartService(
_In_ SC_HANDLE hService,
_In_ DWORD dwNumServiceArgs,
_In_opt_ LPCTSTR *lpServiceArgVectors
);
参数hService为服务句柄,后两个参数为服务主函数的参数,可以设置为 NULL,返回值表 示是否成功。
② ControlService
向服务发送控制请求码。并获取服务状态。
BOOL WINAPI ControlService(
_In_ SC_HANDLE hService,
_In_ DWORD dwControl,
_Out_ LPSERVICE_STATUS lpServiceStatus
);
参数 hService 为服务句柄,dwControl 为控制码,lpServiceStatus 为返回的服务状态。 如果需要停止服务,需要使用 ControlService 向服务发送 SERVICE CONTROL_STOP 控制码。
③ QueryServiceStatus
获得当前服务的状态。
BOOL WINAPI QueryServiceStatus(
_In_ SC_HANDLE hService,
_Out_ LPSERVICE_STATUS lpServiceStatus
);
3. SERVICE_STATUS结构各成员解析
在编写Windows服务的时候,需要调用API函数::SetServiceStatus()向服务控制管理器(SCM)提交更新当前服务的状态信息,其第2个参数为指向SERVICE_STATUS结构的指针,SERVICE_STATUS结构中包含了表示当前服务状态的信息,对其各成员一一分析:
typedef struct _SERVICE_STATUS {
DWORD dwServiceType;
DWORD dwCurrentState;
DWORD dwControlsAccepted;
DWORD dwWin32ExitCode;
DWORD dwServiceSpecificExitCode;
DWORD dwCheckPoint;
DWORD dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_STATUS;
dwServiceType:指明服务可执行文件的类型。如果可执行文件中只有一个单独的服务,就把这个成员设置成SERVICE_WIN32_OWN_PROCESS;如果拥有多个服务的话,就设置成SERVICE_WIN32_SHARE_PROCESS。除了这两个标志之外,如果你的服务需要和桌面发生交互(当然不推荐这样做),就要用“|”运算符附加上SERVICE_INTERACTIVE_PROCESS。这个成员的值在服务的生存期内绝对不应该改变。
dwCurrentState:用于通知SCM此服务的现行状态。为了报告服务仍在初始化,应该把这个成员设置成SERVICE_START_PENDING。
dwControlsAccepted:指明服务接受什么样的控制通知。如果允许一个服务控制程序(SCP)去暂停/继续服务,就把它设成SERVICE_ACCEPT_PAUSE_CONTINUE。很多服务不支持暂停或继续,就必须自己决定在服务中它是否可用。如果你允许一个SCP去停止服务,就要设置它为SERVICE_ACCEPT_STOP。如果服务要在操作系统关闭的时候得到通知,设置它为SERVICE_ACCEPT_SHUTDOWN可以收到预期的结果。这些标志可以用“|”运算符组合。
dwWin32ExitCode和dwServiceSpecificExitCode:是允许服务报告错误的关键,如果希望服务去报告一个Win32错误代码(预定义在WinError.h中),它就设置dwWin32ExitCode为需要的代码。一个服务也可以报告它本身特有的、没有映射到一个预定义的Win32错误代码中的错误。为了这一点,要把dwWin32ExitCode设置为ERROR_SERVICE_SPECIFIC_ERROR,然后还要设置成员dwServiceSpecificExitCode为服务特有的错误代码。当服务运行正常,没有错误可以报告的时候,就设置成员dwWin32ExitCode为NO_ERROR。
dwCheckPoint和dwWaitHint:是一个服务用来报告它当前的事件进展情况的。当成员dwCurrentState被设置成SERVICE_START_PENDING的时候,应该把dwCheckPoint设成0,dwWaitHint设成一个经过多次尝试后确定比较合适的超时毫秒数,这样服务才能高效运行。一旦服务被完全初始化,就应该重新初始化SERVICE_STATUS结构的成员,更改dwCurrentState为SERVICE_RUNNING,然后把dwCheckPoint和dwWaitHint都改为0。
三、 践行渐远
实践环境:visual studio 2012 C++
编写一个服务应用.exe文件,可以用命令行建立、启动、停止、卸载服务。
可以在服务管理器上控制简单服务。
需要写的部分
1. 定义全局变量
#define LOGFILE "C:\\Myservice\\myservice.txt"
SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE ServiceStatusHandle;
TCHAR ServiceName[] = L"TestService";
2.声明几类函数
/*
*服务主函数
*服务控制护理函数
*服务初始化函数
*辅助函数
*/
int WriteToLog(char* str);
VOID WINAPI ServiceMain(DWORD argc,LPTSTR *argv);
VOID WINAPI ServiceHandler(DWORD opcode);
VOID ServiceInitialization(DWORD argc ,LPTSTR *argv);
BOOL IsServiceInstalled();
BOOL InstallService();
BOOL UninstallService();
BOOL ServiceCtrlStart();
BOOL ServiceCtrlStop();
3.函数实现
① int WriteToLog(char* str);
int WriteToLog(char* str)
{
SYSTEMTIME m_time;
FILE* log;
printf("%s\n",str);
log = fopen(LOGFILE, "a+");
if (log == NULL)
{
printf("write log failure\n");
return -1;
}
GetLocalTime(&m_time);
fprintf(log, "%d-%d-%d %d:%d:%d ** %s\n", m_time.wYear,m_time.wMonth,m_time.wDay,m_time.wHour,m_time.wMinute,m_time.wSecond,str);
fclose(log);
return 0;
}
这里面主要用的函数是:
fopen-以不同的方式打开文件、GetLocalTime-获得系统当前时间
主要目的:编写日志
② VOID WINAPI ServiceMain(DWORD argc,LPTSTR *argv);
VOID WINAPI ServiceMain(DWORD argc,LPTSTR *argv){
ServiceInitialization( argc ,argv);
//注册控制管理函数
ServiceStatusHandle = RegisterServiceCtrlHandler(ServiceName,ServiceHandler);
if(ServiceStatusHandle == NULL)
{
WriteToLog(" RegisterServiceCtrlHandler failure !");
return ;
}
else
{
WriteToLog(" RegisterServiceCtrlHandler successfully !");
}
//设置运行状态
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
if(!SetServiceStatus(ServiceStatusHandle,&ServiceStatus))//启动运行状态
{
WriteToLog(" SetServiceStatus failure");
}
//---------------------------------------------------------------------------------------
//进行自己想要运行的操作
//-----------------------------------------------------------------------------------
return ;
}
这里面需要掌握的函数:
RegisterServiceCtrlHandler、SetServiceStatus以及SERVICE_STATUS结构的初始化。
③ VOID WINAPI ServiceHandler(DWORD opcode);
VOID WINAPI ServiceHandler(DWORD opcode)
{
switch (opcode)
{
case SERVICE_CONTROL_STOP:
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
if(!SetServiceStatus(ServiceStatusHandle,&ServiceStatus))
{
WriteToLog(" SetServiceStatus failure");
}
WriteToLog("stop success !");
return ;
case SERVICE_CONTROL_SHUTDOWN:
ServiceStatus.dwCurrentState = SERVICE_CONTROL_SHUTDOWN;
if(!SetServiceStatus(ServiceStatusHandle,&ServiceStatus))
{
WriteToLog(" SetServiceStatus failure");
}
WriteToLog("shutdown success !");
return ;
default:
WriteToLog("Bad service request");
}
return ;
}
④ VOID ServiceInitialization(DWORD argc ,LPTSTR *argv);
VOID ServiceInitialization(DWORD argc ,LPTSTR *argv)
{
//各种初始化工作
}
⑤ BOOL IsServiceInstalled();
BOOL IsServiceInstalled()
{
BOOL bResult = FALSE;
SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCM != NULL)
{
SC_HANDLE hService = ::OpenService(hSCM, ServiceName, SERVICE_QUERY_CONFIG);
if (hService != NULL)
{
bResult = TRUE;
::CloseServiceHandle(hService);
}
::CloseServiceHandle(hSCM);
}
if(bResult)
{
printf("have installed service.\n");
}
else
{
printf("no installed service.\n");
}
return bResult;
}
⑥ BOOL InstallService();
BOOL InstallService()
{
if (IsServiceInstalled())
return FALSE;
//open the SCM
SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCM == NULL)
{
return FALSE;
}
// Get the executable file path
TCHAR szFilePath[MAX_PATH];
::GetModuleFileName(NULL, szFilePath, MAX_PATH);
//create a service
SC_HANDLE hService = ::CreateService(
hSCM,
ServiceName,
ServiceName,
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL,
szFilePath,
NULL,
NULL,
NULL,
NULL,
NULL);
if (hService == NULL)
{
::CloseServiceHandle(hSCM);
return FALSE;
}
::CloseServiceHandle(hService);
::CloseServiceHandle(hSCM);
return TRUE;
}
⑦ BOOL UninstallService()
BOOL UninstallService()
{
if (!IsServiceInstalled())
return FALSE;
SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCM == NULL)
{
return FALSE;
}
SC_HANDLE hService = ::OpenService(hSCM, ServiceName, SERVICE_STOP | DELETE);
if (hService == NULL)
{
::CloseServiceHandle(hSCM);
return FALSE;
}
SERVICE_STATUS status;
::ControlService(hService, SERVICE_CONTROL_STOP, &status);
//delete service
BOOL bDelete = ::DeleteService(hService);
::CloseServiceHandle(hService);
::CloseServiceHandle(hSCM);
if (bDelete)
return TRUE;
WriteToLog("Service could not be deleted");
return FALSE;
}
⑧ BOOL ServiceCtrlStart();
BOOL ServiceCtrlStart()
{
BOOL bRet;
SC_HANDLE hSCM;
SC_HANDLE hService;
hSCM=OpenSCManager(NULL,NULL,SC_MANAGER_CONNECT);
if (hSCM!=NULL)
{
hService=OpenService( hSCM, ServiceName, SERVICE_START);
if (hService!=NULL)
{
//开始Service
bRet = StartService(hService,0,NULL);
CloseServiceHandle(hService);
}
else
bRet = FALSE;
CloseServiceHandle(hSCM);
}else
{
bRet = FALSE;
}
return bRet;
}
⑨ BOOL ServiceCtrlStop();
BOOL ServiceCtrlStop()
{
BOOL bRet;
SC_HANDLE hSCM;
SC_HANDLE hService;
SERVICE_STATUS ServiceStatus;
hSCM=OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
if (hSCM!=NULL)
{
hService=OpenService( hSCM, ServiceName, SERVICE_STOP|SERVICE_QUERY_STATUS );
if (hService!=NULL)
{
QueryServiceStatus( hService, &ServiceStatus );
if (ServiceStatus.dwCurrentState == SERVICE_RUNNING)
bRet = ControlService( hService, SERVICE_CONTROL_STOP, &ServiceStatus );
else
bRet = FALSE;
CloseServiceHandle( hService );
}
else
bRet = FALSE;
CloseServiceHandle( hSCM );
}
else
bRet = FALSE;
return bRet;
}
4.入口函数main
int main( int argc, char* argv[])
{
SERVICE_TABLE_ENTRY st[2];
st[0].lpServiceName = ServiceName;
st[0].lpServiceProc = ServiceMain;
st[1].lpServiceName = NULL;
st[1].lpServiceProc = NULL;
LPSTR lpCmdLine = argv[1];
if ( argc == 2 )
{
BOOL bRet;
if(strcmp(lpCmdLine, "install") == 0 )
{
bRet = InstallService();
if ( bRet == TRUE )
wprintf( L"Install service %s success\n", ServiceName );
else
wprintf( L"Install service %s failed\n", ServiceName );
}
else
{
if(strcmp(lpCmdLine, "uninstall") == 0 )
{
bRet = UninstallService();
if ( bRet == TRUE )
wprintf(L"Uninstall service %s success\n", ServiceName );
else
wprintf( L"Uninstall service %s failed\n", ServiceName );
}
else
{
if (strcmp(lpCmdLine, "start") == 0)
{
bRet = ServiceCtrlStart();
if ( bRet == TRUE )
wprintf( L"Start service %s success\n", ServiceName );
else
wprintf( L"Start service %s failed\n", ServiceName );
}
else
{
if (strcmp(lpCmdLine, "stop") == 0)
{
bRet = ServiceCtrlStop();
if ( bRet == TRUE )
wprintf( L"Stop service %s success\n", ServiceName );
else
wprintf( L"Stop service %s failed\n", ServiceName );
}
else if (!::StartServiceCtrlDispatcher(st))
WriteToLog("Register Service Main Function Error!");
}
}
}
}
else
{
if (!::StartServiceCtrlDispatcher(st))
{
WriteToLog("Register Service Main Function Error!");
}
}
return 0;
}
编译连接后生成个.exe文件,用管理员权限在.exe所在目录打开命令提示符
输入以下命令控制服务:
***.exe install Enter
***.exe start Enter
***.exe stop Enter
***.exe uninstall Enter
关于一个简单的服务就到此结束了!
下一次会讲解如何自定义服务控制码。