Ubuntu安装Gitlab

1.安装和配置必要的依赖项

sudo apt-get update
sudo apt-get install -y curl openssh-server ca-certificates tzdata perl
sudo apt-get install -y postfix

2.添加GitLab包仓库并安装包

curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash
sudo apt-get install gitlab-ce

3.设置浏览器要访问的IP地址及端口号

编辑配置文件

vim /etc/gitlab/gitlab.rb

4.重新载入配置及启动服务

gitlab-ctl reconfigure

gitlab-ctl start

5.查找Gitlab默认root密码

vim /etc/gitlab/initial_root_password

注:该文件为临时文件,将于reconfigure后的24小时后自动删除,注意尽早保存。

静态链接DLL与动态调用DLL

静态链接DLL:

需要设置“附加包含目录”,“附加库目录”以及“附加依赖项”。

然后通过外部引用函数定义即可。

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

extern int add(int a, int b);

int main(int argc, char* argv[])
{
	MessageBoxW(NULL, L"Test", L"Test", MB_OK);

	printf("5 + 3 = %d", add(5, 3));

	return 0;
}

动态调用DLL:

即通过函数调用的方式获得所需函数的地址,然后通过函数指针的方式调用函数。

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

int main(int argc, char* argv[])
{
	HMODULE load_lib = NULL;
	FARPROC get_proc_add = 0;

	MessageBoxW(NULL, L"Test", L"Test", MB_OK);

	do
	{
		load_lib = LoadLibraryW(L"C:\\Users\\Raiseki\\source\\repos\\dll_learn\\x64\\Debug\\dll_learn.dll");
		if (load_lib == NULL)
		{
			break;
		}
		get_proc_add = GetProcAddress(load_lib, "add");
		if (get_proc_add == NULL)
		{
			break;
		}
		printf("5 + 3 = %lld", get_proc_add(5, 3));
	} while (0);

	return 0;
}

注意:

当找不到DLL文件时,采用静态链接方式的程序系统会报错且无法运行。而采用动态调用方式的程序虽然无法调用到所需函数,但系统不会报错。

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

获取进程pid,ppid以及进程名

获取pid:

pid = GetCurrentProcessId();

获取ppid和进程名:

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

该函数的第二个或第三个参数如不需要可以填零。

创建事件CreateEventW

函数原型:

CreateEventW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
    _In_ BOOL bManualReset,
    _In_ BOOL bInitialState,
    _In_opt_ LPCWSTR lpName
    );

参数1:安全描述符

参数2:手动复位
TRUE为手动,FALSE为自动。影响等待线程释放后,线程是手动还是自动的回到复位状态(即无信号状态)。

参数3:初始状态
TRUE为置位(有信号),FALSE为复位(无信号)。

参数4:事件对象名称
可设置一个最长为MAX_PATH的时间对象名称。
可以选择本地或全局(Local或Global),
如:CreateEvent( NULL, FALSE, FALSE, “Global\\CSAPP” );
利用全局内核对象命名空间可以做到进程间的同步。
也可以活用此功能避免运行重复的进程。

返回值:
如果函数成功,则返回值是事件对象的句柄。如果命名事件对象在函数调用之前存在,则该函数返回现有对象的句柄,而 GetLastError返回 ERROR_ALREADY_EXISTS。
如果函数失败,则返回值为NULL。要获取扩展的错误信息,请调用 GetLastError。

事件(Event)

多用于线程同步。

可能用到的函数:

1.CreateEventW

HANDLE CreateEventW(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL                  bManualReset,
  BOOL                  bInitialState,
  LPCWSTR               lpName
);

https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createeventw

2.WaitForSingleObject

DWORD WaitForSingleObject(
  HANDLE hHandle,
  DWORD  dwMilliseconds
);

https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject

3.SetEvent

BOOL SetEvent(
  HANDLE hEvent
);

https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-setevent

4.ResetEvent

BOOL ResetEvent(
  HANDLE hEvent
);

https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-resetevent

5.SetEventWhenCallbackReturns

void SetEventWhenCallbackReturns(
  PTP_CALLBACK_INSTANCE pci,
  HANDLE                evt
);

https://docs.microsoft.com/en-us/windows/win32/api/threadpoolapiset/nf-threadpoolapiset-seteventwhencallbackreturns

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

服务的概念:
Microsoft Windows 服务(过去称为 NT 服务)允许用户创建可在其自身的 Windows 会话中长时间运行的可执行应用程序。 这些服务可在计算机启动时自动启动,可以暂停和重启,并且不显示任何用户界面。
注:所以一个正常运行的服务程序是不应该执行完就退出的。应该在所有进程中的所有服务均终止时才返回退出。

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

组成部分:
使用C语言通过WindowsAPI编写一个可用的服务至少要包括以下几部分内容:
1. 服务程序的入口点
2. 服务程序的主体ServiceMain函数
3. 服务程序的控制处理功能函数
以上三者缺一不可,否则将无法正常启动。

具体步骤:

1.创建服务
该部分可选,如果忽略创建服务部分可使用手动方式添加服务。
2.编写入口点
第一个重要组成部分,必不可少。

		SERVICE_TABLE_ENTRY	service_table[2];				//为可以在调用过程中运行的服务指定ServiceMain函数
		service_table[0].lpServiceName = L"TestService";			//此服务在进程中运行的名称
		service_table[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;	//指向ServiceMain函数的指针
		service_table[1].lpServiceName = NULL;					//表中最后一个条目的成员必须具有NULL值才能指定表的末尾
		service_table[1].lpServiceProc = NULL;

		result = StartServiceCtrlDispatcherW(service_table);	//将服务进程的主线程连接到服务控制管理器,该服务控制管理器使该线程成为调用过程的服务控制调度程序线程
		if (result == 0)
		{
			printf("StartServiceCtrlDispatcherW error: %d\n", GetLastError());

			break;
		}

3.编写ServiceMain函数
第二个重要组成部分,必不可少。

VOID WINAPI ServiceMain(DWORD argc, LPWSTR* argv)
{
	service_status_handle = RegisterServiceCtrlHandlerW(L"TestService", (LPHANDLER_FUNCTION)ServiceControlHandler);  //参数2为函数指针,该指针指向的函数定义了如何处理服务控制管理器发来的控制请求
        //填充SERVICE_STATUS结构体,该结构体为全局变量
	service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
	service_status.dwCurrentState = SERVICE_RUNNING;            //如果有需要初始化的内容,此处可以先填SERVICE_START_PENDING,待初始化完成后将值改为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);   //该函数用以更新服务控制管理器的的状态信息

	return;

4.编写控制处理程序功能(即前面代码参数2对应的函数)
第三个重要组成部分,必不可少。该函数用于接收服务控制管理器(SCM)发来的指令,然后根据指令让服务做出相应的状态更新。如下面代码,若不增加对停止服务指令的状态更新,则服务无法正常停止。

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

包含以上内容就完成了作为一个最基本的Windows服务。

以下为该示例完整代码:

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

SERVICE_STATUS           service_status;
SERVICE_STATUS_HANDLE    service_status_handle;

BOOL InstallService()
{
	BOOL		result = FALSE;
	SC_HANDLE	SCM_handle = NULL;
	SC_HANDLE	service_handle = NULL;
	DWORD		return_value = 0;
	TCHAR		Path[MAX_PATH];
	HANDLE		file_handle = NULL;
	char		error[64];
	DWORD		bytes_written = 0;
	DWORD		last_error = 0;

	SecureZeroMemory(error, sizeof(error));

	do
	{
		return_value = GetModuleFileNameW(NULL, Path, MAX_PATH);
		if (return_value == 0)
		{
			printf("GetModuleFileName error: %d\n", GetLastError());

			break;
		}

		SCM_handle = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);
		if (SCM_handle == NULL)
		{
			printf("OpenSCManagerW error: %d\n", GetLastError());

			break;
		}

		service_handle = OpenServiceW(SCM_handle, L"TestService", SC_MANAGER_ALL_ACCESS);
		if (service_handle != NULL)
		{
			printf("Test Service Created\n");
		}
		else
		{
			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)
			{
				printf("CreateServiceW error: %d\n", GetLastError());

				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);

	return;
}

int main(int argc, char* argv[])
{
	BOOL		result = 0;
	HANDLE		file_handle = NULL;
	DWORD		last_error = 0;

	do
	{
		result = InstallService();
		if (result == 0)
		{
			printf("InstallService faild.\n");
			break;
		}

		SERVICE_TABLE_ENTRY	service_table[2];
		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)
		{
			printf("StartServiceCtrlDispatcherW error: %d\n", GetLastError());

			break;
		}
	} while (0);

	return 0;
}

ANSI与Unicode

  1. C语言中用char数据类型来表示一个8位ANSI字符。
  2. MicroSoft的C/C++编译器定义了一个内建的数据类型wchar_t,它表示一个16位的Unicode(UTF-16)字符。
  3. 自Windows NT起,Windows的所有版本都完全用Unicode来构建。也就是说,所有核心函数(创建窗口、显示文本、进行字符串处理等等)都需要Unicode字符串。
  4. 调用Windows函数时,如果向它传入一个ANSI字符串,那么函数首先会把字符串转换为Unicode,再把结果传给操作系统。这会让系统产生时间和内存上的开销。
  5. Windows API中的一些函数(如WinExee和OpenFile)存在的唯一目的就是为了兼容16位Windows程序,因为其只支持ANSI字符串。所以在开发新程序是应避免使用这些函数。在使用WinExee和OpenFile调用的地方,应该用CreateProcess和CreateFile函数调用代替。
  6. 在C运行库中,strlen就是一个能返回ANSI字符串长度的函数。与之对应的是wcslen,这个C运行库函数能返回Unicode字符串的长度。

枚举服务状态EnumServicesStatusExW

在指定的服务控制管理器数据库中枚举服务。

函数原型:
BOOL
WINAPI
EnumServicesStatusExW(
_In_ SC_HANDLE hSCManager,
参数1:服务控制管理器数据库的句柄。该句柄由OpenSCManager函数返回 ,并且必须具有SC_MANAGER_ENUMERATE_SERVICE访问权限。

_In_ SC_ENUM_TYPE InfoLevel,
参数2:要返回的服务属性。使用SC_ENUM_PROCESS_INFO检索数据库中每个服务的名称和服务状态信息。所述lpServices参数是一个指向接收数组的缓冲器 ENUM_SERVICE_STATUS_PROCESS结构。缓冲区必须足够大以容纳结构及其成员指向的字符串。

_In_ DWORD dwServiceType,
参数3:
要枚举的服务类型。此参数可以是以下值中的一个或多个。

表1
价值 意义
SERVICE_DRIVER
0x0000000B
类型为SERVICE_KERNEL_DRIVERSERVICE_FILE_SYSTEM_DRIVER的服务
SERVICE_FILE_SYSTEM_DRIVER
0x00000002
文件系统驱动程序服务。
SERVICE_KERNEL_DRIVER
0x00000001
驱动服务。
SERVICE_WIN32
0x00000030
类型为SERVICE_WIN32_OWN_PROCESSSERVICE_WIN32_SHARE_PROCESS的服务
SERVICE_WIN32_OWN_PROCESS
0x00000010
在自己的进程中运行的服务。
SERVICE_WIN32_SHARE_PROCESS
0x00000020
与一个或多个其他服务共享一个流程的服务。有关更多信息,请参见服务程序

_In_ DWORD dwServiceState,
参数4:
要枚举的服务状态。此参数可以是以下值之一。

表2
价值 意义
SERVICE_ACTIVE
0x00000001
枚举处于以下状态的服务:SERVICE_START_PENDINGSERVICE_STOP_PENDINGSERVICE_RUNNINGSERVICE_CONTINUE_PENDINGSERVICE_PAUSE_PENDINGSERVICE_PAUSED
SERVICE_INACTIVE
0x00000002
枚举处于SERVICE_STOPPED状态的服务。
SERVICE_STATE_ALL
0x00000003
合并SERVICE_ACTIVESERVICE_INACTIVE状态。

_Out_writes_bytes_opt_(cbBufSize) LPBYTE lpServices,
参数5:指向接收状态信息的缓冲区的指针。该数据的格式取决于InfoLevel参数的值。该数组的最大大小为256K字节。若要确定所需的大小,请为此参数指定NULL,为cbBufSize参数指定0 。该函数将失败,并且GetLastError将返回ERROR_MORE_DATA。该pcbBytesNeeded参数将接收到所需的大小。

_In_ DWORD cbBufSize,
参数6:lpServices参数指向的缓冲区大小,以字节为单位。

_Out_ LPDWORD pcbBytesNeeded,
参数7:如果缓冲区太小,则指向变量的指针,该变量接收返回剩余服务条目所需的字节数。

_Out_ LPDWORD lpServicesReturned,
参数8:指向一个变量的指针,该变量接收返回的服务条目数。

_Inout_opt_ LPDWORD lpResumeHandle,
参数9:指向变量的指针,该变量在输入时指定枚举的起点。您必须在第一次调用EnumServicesStatusEx函数时将此值设置为零 。在输出中,如果函数成功,则该值为零。但是,如果函数返回零,并且 GetLastError函数返回ERROR_MORE_DATA,则此值指示调用EnumServicesStatusEx函数以检索其他数据时要读取的下一个服务条目 。

_In_opt_ LPCWSTR pszGroupName
参数10:加载顺序组名称。如果此参数是字符串,则枚举的唯一服务是属于具有由字符串指定的名称的组的服务。如果此参数为空字符串,则仅枚举不属于任何组的服务。如果此参数为NULL,则将忽略组成员身份,并枚举所有服务。
);

示例:

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

int main(int argc, char* argv[])
{
    HANDLE							handle_service_manager = 0;
    int								return_value = 0;
    unsigned char*					service_status_process = NULL;
    DWORD							buffer_size = 0;
    DWORD							bytes_needed = 0;
    DWORD							services_returned = 0;
    ENUM_SERVICE_STATUS_PROCESS*	enmu_service_status_process;

    setlocale(LC_ALL, "chs");

    do
    {
        handle_service_manager = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);
        if (handle_service_manager == NULL)
        {
            printf("OpenSCManager Error:%d\n", GetLastError());

            break;
        }

        return_value = EnumServicesStatusExW(
            handle_service_manager,		//服务控制管理器数据库的句柄。
            SC_ENUM_PROCESS_INFO,		//要返回的服务属性。
            SERVICE_WIN32,				//要枚举的服务类型。
            SERVICE_STATE_ALL,			//要枚举的服务状态。
            NULL,						//指向接收状态信息的缓冲区的指针。该数据的格式取决于InfoLevel参数的值。
            buffer_size,				//lpServices参数指向的缓冲区大小,以字节为单位。
            &bytes_needed,				//如果缓冲区太小,则指向变量的指针,该变量接收返回剩余服务条目所需的字节数。
            &services_returned,			//指向一个变量的指针,该变量接收返回的服务条目数。
            0,							//指向变量的指针,该变量在输入时指定枚举的起点。您必须在第一次调用EnumServicesStatusEx函数时将此值设置为零。
            NULL						//加载顺序组名称。
        );
        if (return_value == 0 && GetLastError() != ERROR_MORE_DATA)
        {
            printf("get bytes needed faild\n");

            break;
        }
        printf("bytes_needed:%d\n", bytes_needed);
        buffer_size = bytes_needed;
        service_status_process = LocalAlloc(LPTR, buffer_size);
        if (service_status_process == NULL)
        {
            break;
        }
        SecureZeroMemory(service_status_process, buffer_size);

        return_value = EnumServicesStatusExW(
            handle_service_manager,		//服务控制管理器数据库的句柄。
            SC_ENUM_PROCESS_INFO,		//要返回的服务属性。
            SERVICE_WIN32,				//要枚举的服务类型。
            SERVICE_STATE_ALL,			//要枚举的服务状态。
            service_status_process,		//指向接收状态信息的缓冲区的指针。该数据的格式取决于InfoLevel参数的值。
            buffer_size,				//lpServices参数指向的缓冲区大小,以字节为单位。
            &bytes_needed,				//如果缓冲区太小,则指向变量的指针,该变量接收返回剩余服务条目所需的字节数。
            &services_returned,			//指向一个变量的指针,该变量接收返回的服务条目数。
            0,							//指向变量的指针,该变量在输入时指定枚举的起点。您必须在第一次调用EnumServicesStatusEx函数时将此值设置为零。
            NULL						//加载顺序组名称。
        );
        if (return_value == 0)
        {
            printf("EnumServicesStatusExW2 faild:%d\n", GetLastError());

            break;
        }

        printf("services returned:%d\n\n", services_returned);

        enmu_service_status_process = (ENUM_SERVICE_STATUS_PROCESS*)service_status_process;
        for (size_t i = 0; i < services_returned; i++)
        {
            printf("ServiceName:%ws\n", enmu_service_status_process[i].lpServiceName);
            printf("DisplayName:%ws\n", enmu_service_status_process[i].lpDisplayName);
            printf("ProcessId:%d\n", enmu_service_status_process[i].ServiceStatusProcess.dwProcessId);
            printf("CurrentState:%d\n\n", enmu_service_status_process[i].ServiceStatusProcess.dwCurrentState);
        }

    } while (0);

    return 0;
}

注:遍历多个相同的结构体时可以把结构体看做数组,以数组下标的方式表示。

https://docs.microsoft.com/zh-cn/windows/win32/api/winsvc/nf-winsvc-enumservicesstatusexw