Win32 API 中的 CreateProcess 是一个强大的函数,用于创建新的进程。在 Windows 操作系统中,每个程序都运行在自己独立的进程中,CreateProcess 可以让我们通过代码启动新的进程,并在其中运行我们想要的程序。本文将探究 CreateProcess 在 Win32 API 中的使用方法和常见场景,并介绍一些相关的注意事项。
一、CreateProcess 函数的调用方法
CreateProcess 函数的原型如下:
```
BOOL CreateProcess(
LPCWSTR lpApplicationName,
LPWSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCWSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
```
其中,各个参数的含义如下:
1. `lpApplicationName`:要启动的应用程序的名称或路径。
2. `lpCommandLine`:要传递给应用程序的命令行参数。如果要启动的应用程序是控制台应用程序,则命令行参数应该传递到应用程序的 main 函数中。
3. `lpProcessAttributes`:一个指向 SECURITY_ATTRIBUTES 结构体的指针,该结构体描述了新创建的进程的安全属性。如果为 NULL,则新创建的进程将继承当前进程的安全属性。
4. `lpThreadAttributes`:一个指向 SECURITY_ATTRIBUTES 结构体的指针,该结构体描述了新创建的主线程的安全属性。如果为 NULL,则新创建的线程将继承当前线程的安全属性。
5. `bInheritHandles`:一个 BOOL 值,指示新进程是否应该继承当前进程的所有句柄。如果为 TRUE,则新进程将继承当前进程的所有句柄;如果为 FALSE,则新进程将不会继承任何句柄。
6. `dwCreationFlags`:指定进程的创建标志。这些标志可以使用单个标志或多个标志的组合。取值可以参考 https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags。
7. `lpEnvironment`:指向环境变量字符串的指针。如果为 NULL,则新创建的进程将继承当前进程的环境变量。
8. `lpCurrentDirectory`:指定当前工作目录。如果为 NULL,则新创建的进程将继承当前进程的当前目录。
9. `lpStartupInfo`:一个指向 STARTUPINFO 结构的指针,该结构指定新进程的主窗口的外观和默认的输入输出处理方式。此参数是可选的。如果为 NULL,则使用默认的 STARTUPINFO。
10. `lpProcessInformation`:一个指向 PROCESS_INFORMATION 结构的指针,该结构包含新进程的标识符和主线程的标识符。创建进程成功后,可以使用此结构体中的信息与该进程进行交互。这是一个输出参数,必须传递有效的指针。
CreateProcess 函数使用非常灵活,可以通过不同的参数组合来控制新进程的行为,比如:
1. 启动控制台应用程序:
```
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
CHAR cmd[] = "cmd.exe";
if (!CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
{
printf("Error: %d\n", GetLastError());
return 1;
}
```
2.启动 GUI 应用程序:
```
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
WCHAR cmd[] = L"notepad.exe";
if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
{
printf("Error: %d\n", GetLastError());
return 1;
}
```
3. 在新的进程中指定当前目录:
```
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
WCHAR cmd[] = L"path/to/myprog.exe";
WCHAR dir[] = L"path/to/";
if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, dir, &si, &pi))
{
printf("Error: %d\n", GetLastError());
return 1;
}
```
二、CreateProcess 在常见场景下的使用
1. 启动应用程序:
CreateProcess 函数最常见的用途就是启动应用程序。我们可以使用 CreateProcess 函数来启动各种类型的应用程序,包括控制台应用程序、Windows 应用程序、DLL 等。
2. 启动监控子进程:
有时候我们需要在一个进程中监控其它进程的行为。这时候可以使用 CreateProcess 函数来启动子进程,并通过 `lpProcessInformation` 参数来获取子进程的句柄和 ID。然后使用 WaitForSingleObject 函数等待子进程退出,并在退出之后进行处理。
下面是一个示例代码,用于启动一个控制台应用程序,并监视其输出:
```
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
CHAR cmd[] = "cmd.exe";
if (!CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
{
printf("Error: %d\n", GetLastError());
return 1;
}
// 等待子进程退出
if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED)
{
printf("WaitForSingleObject failed: %d\n", GetLastError());
}
else
{
printf("Process exited with code %d.\n", pi.dwExitCode);
}
// 关闭句柄
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
```
3. 启动服务:
在 Windows 操作系统中,许多系统服务都是通过启动子进程来实现的。CreateProcess 函数是启动服务的最佳选择之一。
在创建服务时,可以通过 SERVICE_CONTROL_STOP 消息来关闭进程,也可以使用 SendMessageTimeout 函数向进程发送 WM_CLOSE 消息。
以下是一个示例代码,用于启动一个服务,并在收到 SERVICE_CONTROL_STOP 消息时关闭进程:
```
// 启动服务
SC_HANDLE svc = OpenService(
scm, // SCM 句柄
L"ServiceName", // 要启动的服务名称
SERVICE_QUERY_STATUS | // 需要查询服务状态
SERVICE_START // 需要启动服务
);
if (svc == NULL)
{
// 处理错误
return 1;
}
// 启动服务
if (!StartService(svc, 0, NULL))
{
// 处理错误
return 1;
}
// 等待服务启动完毕
while (TRUE)
{
SERVICE_STATUS status;
if (!QueryServiceStatus(svc, &status))
{
// 处理错误
return 1;
}
// 服务已启动
if (status.dwCurrentState == SERVICE_RUNNING)
{
break;
}
// 等待一段时间后再次查询服务状态
Sleep(status.dwWaitHint);
}
// 等待服务停止
while (TRUE)
{
SERVICE_STATUS status;
if (!QueryServiceStatus(svc, &status))
{
// 处理错误
return 1;
}
// 收到 SERVICE_CONTROL_STOP 消息
if (status.dwCurrentState == SERVICE_STOPPED)
{
break;
}
// 发送 WM_CLOSE 消息
if (status.dwCurrentState == SERVICE_RUNNING)
{
if (!GenerateConsoleCtrlEvent(CTRL_CLOSE_EVENT, 0))
{
// 处理错误
return 1;
}
if (SendMessageTimeout(
HWND_BROADCAST,
WM_CLOSE,
0,
0,
SMTO_ABORTIFHUNG | SMTO_BLOCK,
5000,
NULL
) == 0)
{
// 处理错误
return 1;
}
}
// 等待一段时间后再次查询服务状态
Sleep(status.dwWaitHint);
}
// 关闭服务句柄
CloseServiceHandle(svc);
```
三、注意事项
在使用 CreateProcess 函数时,需要注意以下几点:
1. 路径和命令行参数中需要使用完整路径和引号进行包装,以避免出现无法识别的空格和文件名。
2. 如果要继承当前进程的安全属性和环境变量,请将 lpProcessAttributes 和 lpEnvironment 参数设置为 NULL。
3. 使用 `CREATE_SUSPENDED` 标志启动进程时,新进程将被挂起,直到调用 ResumeThread 函数。
4. 使用 `CREATE_NEW_CONSOLE` 标志启动控制台应用程序时,必须将 `bInheritHandles` 参数设置为 TRUE,否则将无法从子进程读取控制台输出。
5. 使用 `CREATE_NO_WINDOW` 标志启动应用程序时,新进程将在后台运行,并且不会显示任何窗口或图标。
四、总结
CreateProcess 函数是 Win32 API 中最常用的函数之一,它可以用于启动各种类型的应用程序,并且还可以用于创建新的进程,以及监控子进程的行为。通过使用不同的标志和参数组合,我们可以根据自己的需求来控制新进程的行为。在使用 CreateProcess 函数时,需要特别注意路径和命令行参数的格式,以及参数的正确组合。