编写 Windows 服务加载器

selph
selph
发布于 2020-10-22 / 885 阅读
0
0

编写 Windows 服务加载器

前言

上一篇完成了服务程序的编写,但是将服务程序创建成服务,启动,暂停,删除等操作都是通过sc命令来实现了,对于测试服务程序工作情况而言,反复手敲命令创建,启动,暂停,删除是很麻烦的,通过编写一个Windows 服务加载器可以很好的解决这个问题。

服务加载器程序介绍

通过封装一个函数通过给定参数来实现服务的创建,启动,暂停,删除操作:

  1. 打开SCM数据库:OpenSCManager

  2. 通过操作参数来判断进行的操作:

    • 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;
}

只需要设置好服务程序的位置,即可调用完成服务的启动,需要的时候可以一键关闭

效果演示

image-20201021152508476


评论