前言
上一篇完成了服务程序的编写,但是将服务程序创建成服务,启动,暂停,删除等操作都是通过sc命令来实现了,对于测试服务程序工作情况而言,反复手敲命令创建,启动,暂停,删除是很麻烦的,通过编写一个Windows 服务加载器可以很好的解决这个问题。
服务加载器程序介绍
通过封装一个函数通过给定参数来实现服务的创建,启动,暂停,删除操作:
-
打开SCM数据库:OpenSCManager
-
通过操作参数来判断进行的操作:
-
0:创建服务:CreateService 或者打开服务:OpenService
-
1:启动服务:StartService
-
2:停止服务:ControlService
-
3:删除服务:DeleteService
-
主程序只需要根据需求去依次调用这些功能即可
编写服务加载器
调试报错函数
void ShowError(char *lpszText)
{
char szErr[MAX_PATH] = { 0 };
::wsprintf(szErr, "%s Error!\nError Code Is:%d\n", lpszText, ::GetLastError());
#ifdef _DEBUG
::MessageBox(NULL, szErr, "ERROR", MB_OK | MB_ICONERROR);
#endif
}
这个#ifdef的工作就是如果程序处于调试状态,就把错误信息弹出,否则就不弹出提示
服务操作函数
BOOL SystemServiceOperate(char *lpszExePath, int iOperateType)
服务操作函数接收2个参数:服务程序路径和操作码
BOOL bRet = TRUE;
char szName[MAX_PATH] = { 0 };
lstrcpyA(szName, lpszDriverPath);
PathStripPathA(szName);
SC_HANDLE shOSCM = NULL, shCS = NULL;//前者用来接收打开SCM的句柄,后者接收打开服务的句柄
SERVICE_STATUS ServiceStatus;
DWORD dwEooroCode = 0;
BOOL bSuccess = FALSE;
//打开SCM数据库
shOSCM = OpenSCManagerA(NULL,NULL,SC_MANAGER_ALL_ACCESS);
if (!shOSCM) {
ShowError("OpenSCManager");
return FALSE;
}
这里是函数内部的局部变量定义和打开SCM数据库部分,分成三个部分来看:
最上面一部分,将参数中的程序路径接收到buffer里,通过PathStripPathA API过滤文件路径得到文件名
中间则是变量的定义,下面是打开SCM数据库的操作,API定义如下:
SC_HANDLE OpenSCManagerA(
LPCSTR lpMachineName, //目标计算机名称,为NULL或为空字符串,则连接本地计算机上的SCM
LPCSTR lpDatabaseName,//SCM数据库的名称,为NULL默认打开SERVICES_ACTIVE_DATABASE数据库
DWORD dwDesiredAccess//对SCM访问权限,SC_MANAGER_ALL_ACCESS可获得全部权限
);
函数执行成功返回SCM句柄,失败返回NULL
https://docs.microsoft.com/zh-cn/windows/win32/api/winsvc/nf-winsvc-openscmanagera
接着往下看:
if (0 != iOperateType)
{
// 打开一个已经存在的服务
shCS = OpenService(shOSCM, szName, SERVICE_ALL_ACCESS);
if (!shCS)
{
ShowError("OpenService");
::CloseServiceHandle(shOSCM);
shOSCM = NULL;
return FALSE;
}
}
这里紧接着是一个判断,判断操作码是否为0,因为操作码0的作用是创建服务,如果已经创建服务了,就不会再调用操作码0了,所以操作码不为0的时候,服务是创建好的状态,只需要打开即可,相关API定义如下:
SC_HANDLE OpenServiceA(
SC_HANDLE hSCManager, //SCM句柄
LPCSTR lpServiceName, //要打开服务的名称(创建服务时由CreateService函数的lpServiceName参数指定的名称)
DWORD dwDesiredAccess //访问权限,SERVICE_ALL_ACCESS为所有权限
);
成功返回服务句柄,失败返回NULL
https://docs.microsoft.com/zh-cn/windows/win32/api/winsvc/nf-winsvc-openservicea
到这里,该判断的也都判断了,该进行不同操作码的不同操作的编写了
switch (iOperateType)
{
case 0:
{
// 创建服务
// SERVICE_AUTO_START 随系统自动启动
// SERVICE_DEMAND_START 手动启动
shCS = ::CreateService(shOSCM, szName, szName,
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL,
lpszExePath, NULL, NULL, NULL, NULL, NULL);
if (!shCS)
{
ShowError("CreateService");
bRet = FALSE;
}
break;
}
操作码0:创建服务,CreateServiceA:
SC_HANDLE CreateServiceA(
SC_HANDLE hSCManager, //SCM 句柄
LPCSTR lpServiceName, //服务名称
LPCSTR lpDisplayName, //用户界面程序标识程序的显示名称
DWORD dwDesiredAccess,//访问权限
DWORD dwServiceType, //服务类型
DWORD dwStartType, //服务启动类型
DWORD dwErrorControl, //服务无法启动时,采取的措施
LPCSTR lpBinaryPathName, //服务二进制文件的路径,如果程序路径有空格,则必须包含引号
LPCSTR lpLoadOrderGroup, //所属负载顺序组名,无则NULL
LPDWORD lpdwTagId, //NULL
LPCSTR lpDependencies, //NULL
LPCSTR lpServiceStartName, //运行的账户名称,如果为NULL,则使用本地系统账户
LPCSTR lpPassword //指定账户的密码,本地系统账户的话,填NULL
);
参数很多,可选择的项也很多,具体见文档:
https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-createservicea
接着往下看:
case 1:
{
// 启动服务
if (!::StartService(shCS, 0, NULL))
{
ShowError("StartService");
bRet = FALSE;
}
break;
}
操作码1:启动服务,StartServiceA API
BOOL StartServiceA(
SC_HANDLE hService, //服务句柄 来自OpenService或CreateService
DWORD dwNumServiceArgs, //服务参数个数 0
LPCSTR *lpServiceArgVectors//服务参数 NULL
);
https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-startservicea
接着往下看:
case 2:
{
// 停止服务
if (!::ControlService(shCS, SERVICE_CONTROL_STOP, &ss))
{
ShowError("ControlService");
bRet = FALSE;
}
break;
}
操作码2:停止服务,ControlServiceExA API
BOOL ControlService(
SC_HANDLE hService, //服务句柄
DWORD dwControl, //控制代码,SERVICE_CONTROL_STOP:停止,
PVOID pControlParams//指向服务控制参数的指针,(服务状态结构体)
);
控制代码很多种,暂停,恢复,停止等,具体查文档吧
https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-controlserviceexa
继续往下看:
case 3:
{
// 删除服务
if (!::DeleteService(shCS))
{
ShowError("DeleteService");
bRet = FALSE;
}
break;
}
default:
break;
}
操作码3:删除服务,DeleteService
BOOL DeleteService(
SC_HANDLE hService //服务句柄
);
到函数的最后,操作结束了之后,要记得关闭服务句柄:
// 关闭句柄
if (shCS)
{
::CloseServiceHandle(shCS);
shCS = NULL;
}
if (shOSCM)
{
::CloseServiceHandle(shOSCM);
shOSCM = NULL;
}
return bRet;
到此,这个服务操作函数就完成了
主函数
int main() {
BOOL bRet = FALSE;
char szExePath[] = " D:\\MyFirstWindowsService.exe";
//加载服务
bRet = SystemServiceOperate(szExePath, 0);
if (bRet){
printf("INSTALL OK.\n");
}
else{
printf("INSTALL ERROR.\n");
}
// 启动服务
bRet = SystemServiceOperate(szExePath, 1);
if (bRet){
printf("START OK.\n");
}
else{
printf("START ERROR.\n");
}
system("pause");
// 停止服务
bRet = SystemServiceOperate(szExePath, 2);
if (bRet){
printf("STOP OK.\n");
}
else{
printf("STOP ERROR.\n");
}
// 卸载服务
bRet = SystemServiceOperate(szExePath, 3);
if (bRet){
printf("UNINSTALL OK.\n");
}
else{
printf("UNINSTALL ERROR.\n");
}
return 0;
}
只需要设置好服务程序的位置,即可调用完成服务的启动,需要的时候可以一键关闭