C语言控制台程序编写Windows服务(二)

C语言控制台程序编写Windows服务(一)的基础上进行了改动。对比上次,本次用到了Event(事件)和父子进程的概念,解决了服务在系统重启后无法随系统自动启动的问题。

目标回顾:编写一个服务,能够正常添加到Windows服务列表并能随系统启动,可正常手动启停。暂时不需要通过服务实现任何实际的功能。

修改过程中明确了以下几点:
1. 明确原理,仔细阅读文档说明。
以此方式写exe服务在exe文件启动时会存在两个进程。一个控制台程序的进程以及一个服务进程。
2. 明确需要做的工作。同时满足以下两件事。
(1)判断服务进程是否已经存在。如果不存在则通过控制台程序进程创建服务并启动,否则退出,不要让程序运行到StartServiceCtrlDispatcherW。
(2)要避免服务进程重复创建进程导致程序出错。这就需要能够判断当前进程是控制台程序进程还是服务进程。服务进程的特点是它们都是services.exe的子进程,所以可以通过判断当前进程的父进程是否为service.exe的方式来对两种进程进行区分。方法如下:
获取进程pid,ppid以及进程名
3.今后编写代码要注意清晰简洁。大量无意义的代码有时会让自己陷入混乱。

以下为修改后的代码:

#include <stdio.h>
#include <Windows.h>
#include <tlhelp32.h>

SERVICE_STATUS			service_status;
SERVICE_STATUS_HANDLE	service_status_handle;

BOOL GetProcessInfoWithPID(DWORD pid, DWORD* ppid, WCHAR* process_name)
{
	BOOL			result = FALSE;
	HANDLE			snapshot_handle = NULL;
	PROCESSENTRY32W	process_entry32;
	BOOL			process_return = FALSE;

	do
	{
		snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
		if (snapshot_handle == INVALID_HANDLE_VALUE)
		{
			break;
		}
		process_entry32.dwSize = sizeof(process_entry32);
		process_return = Process32FirstW(snapshot_handle, &process_entry32);

		while (process_return)
		{
			if (process_entry32.th32ProcessID == pid)
			{
				if (ppid != NULL)
				{
					*ppid = process_entry32.th32ParentProcessID;
				}
				if (process_name != NULL)
				{
					lstrcpyW(process_name, process_entry32.szExeFile);
				}
				result = TRUE;
				break;
			}
			process_entry32.dwSize = sizeof(process_entry32);
			process_return = Process32NextW(snapshot_handle, &process_entry32);
		}

	} while (0);

	if (snapshot_handle)
	{
		CloseHandle(snapshot_handle);
	}
	return result;
}

BOOL ServiceStart(HANDLE handle)
{
	BOOL					result = FALSE;
	SERVICE_STATUS_PROCESS	status;
	DWORD					bytes_need = 0;

	SecureZeroMemory(&status, sizeof(status));
	do
	{
		result = QueryServiceStatusEx(handle, SC_STATUS_PROCESS_INFO, (LPBYTE)&status, sizeof(SERVICE_STATUS_PROCESS), &bytes_need);
		if (result == FALSE)
		{
			break;
		}
		else if ((status.dwCurrentState == SERVICE_RUNNING) || (status.dwCurrentState == SERVICE_START_PENDING))
		{
			service_status.dwCurrentState = SERVICE_RUNNING;
			result = TRUE;
			break;
		}
		result = StartServiceW(handle, 0, NULL);
		if (result == FALSE)
		{
			break;
		}
		printf("please wait...\n");
		do
		{
			result = FALSE;
			Sleep(500);
			result = QueryServiceStatusEx(handle, SC_STATUS_PROCESS_INFO, (LPBYTE)&status, sizeof(SERVICE_STATUS_PROCESS), &bytes_need);
			if (result == FALSE)
			{
				break;
			}
		} while (status.dwCurrentState == SERVICE_START_PENDING);
		result = FALSE;
		if (status.dwCurrentState == SERVICE_RUNNING)
		{
			result = TRUE;
			break;
		}
	} while (0);

	return result;
}

BOOL InstallService()
{
	BOOL		result = FALSE;
	SC_HANDLE	SCM_handle = NULL;
	SC_HANDLE	service_handle = NULL;
	DWORD		return_value = 0;
	TCHAR		Path[MAX_PATH];

	do
	{
		return_value = GetModuleFileNameW(NULL, Path, MAX_PATH);
		if (return_value == 0)
		{
			break;
		}
		SCM_handle = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);
		if (SCM_handle == NULL)
		{
			break;
		}
		service_handle = CreateServiceW(
			SCM_handle,
			L"TestService",
			L"TestService",
			SC_MANAGER_ALL_ACCESS,
			SERVICE_WIN32_OWN_PROCESS,
			SERVICE_AUTO_START,
			SERVICE_ERROR_IGNORE,
			Path,
			NULL,
			NULL,
			NULL,
			NULL,
			NULL
		);
		if (service_handle == NULL)
		{
			break;
		}
		result = ServiceStart(service_handle);
		if (result == FALSE)
		{
			break;
		}
		result = TRUE;
	} while (0);

	if (SCM_handle != NULL)
	{
		CloseServiceHandle(SCM_handle);
	}
	if (service_handle != NULL)
	{
		CloseServiceHandle(service_handle);
	}

	return result;
}

VOID WINAPI ServiceControlHandler(DWORD parameter)
{
	service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
	service_status.dwCurrentState = SERVICE_RUNNING;
	service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
	service_status.dwWin32ExitCode = 0;
	service_status.dwServiceSpecificExitCode = 0;
	service_status.dwCheckPoint = 0;
	service_status.dwWaitHint = 0;

	if (parameter == SERVICE_CONTROL_STOP)
	{
		service_status.dwCurrentState = SERVICE_STOPPED;
	}
	SetServiceStatus(service_status_handle, &service_status);

	return;
}

VOID WINAPI ServiceMain(DWORD argc, LPWSTR* argv)
{
	service_status_handle = RegisterServiceCtrlHandlerW(L"TestService", (LPHANDLER_FUNCTION)ServiceControlHandler);

	service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
	service_status.dwCurrentState = SERVICE_RUNNING;
	service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
	service_status.dwWin32ExitCode = 0;
	service_status.dwServiceSpecificExitCode = 0;
	service_status.dwCheckPoint = 0;
	service_status.dwWaitHint = 0;

	SetServiceStatus(service_status_handle, &service_status);

	while (1)
	{
		Sleep(10000);
		OutputDebugStringA("TestService Running");
	}

	return;
}

int main(int argc, char* argv[])
{
	BOOL					result = 0;
	HANDLE					event_handle = NULL;
	DWORD					pid = 0;
	DWORD					ppid = 0;
	WCHAR					process_name[MAX_PATH];
	int						wcscmp_return;
	SERVICE_TABLE_ENTRY		service_table[2];

	SecureZeroMemory(process_name, sizeof(process_name));

	do
	{
		event_handle = CreateEventW(NULL, FALSE, TRUE, L"Global\\Test");
		if (event_handle == NULL)
		{
			break;
		}
		pid = GetCurrentProcessId();
		result = GetProcessInfoWithPID(pid, &ppid, process_name);
		if (result == FALSE)
		{
			break;
		}
		result = GetProcessInfoWithPID(ppid, 0, process_name);
		if (result == FALSE)
		{
			break;
		}
		wcscmp_return = lstrcmpiW(process_name, L"services.exe");
		if (wcscmp_return != 0)
		{
			if (GetLastError() == ERROR_ALREADY_EXISTS)
			{
				break;
			}
			result = InstallService();
			if (result == 0)
			{
				break;
			}
			break;
		}

		service_table[0].lpServiceName = L"TestService";
		service_table[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
		service_table[1].lpServiceName = NULL;
		service_table[1].lpServiceProc = NULL;

		result = StartServiceCtrlDispatcherW(service_table);
		if (result == 0)
		{
			break;
		}

	} while (0);
	if (event_handle != NULL)
	{
		CloseHandle(event_handle);
	}
	return 0;
}