Windows编程-进程的控制

时间:2022-09-08 14:42:53

1. 获取系统进程

想要获取当前正在运行的一系列进程,可使用ToolHelp函数。

首先使用CreateToolHelp32Snapshot函数给当前系统内执行的进程拍快照(Snapshot),也就是获得一个进程列表,这个列表中记录着进程的ID、进程对应的可执行文件的名称和创建该进程的进程ID等数据。然后使用Process32First函数和Process32Next函数遍历快照中记录的列表。

下面的例子就是获取系统正在运行的进程,并打印出其可执行文件的名称和进程ID号。

#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h> // 声明快照函数的头文件

int main()
{
PROCESSENTRY32 pe32;

// 在使用这个结构之前,先设置它的大小
pe32.dwSize = sizeof(pe32);

//给系统内所有进程拍一个快照
HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if(hProcessSnap == INVALID_HANDLE_VALUE)
{
printf("CreateToolHelp32SnapShot 函数调用失败! \n");
}

//遍历进程快照,轮流显示每个进程的信息
BOOL bMore = ::Process32First(hProcessSnap, &pe32);

while(bMore)
{
printf("进程名称:%s \n", pe32.szExeFile);
printf("进程ID号:%d \n", pe32.th32ProcessID);
bMore = ::Process32Next(hProcessSnap, &pe32);
}

// 不要忘记清除掉snapshot对象
::CloseHandle(hProcessSnap);

return 0;
}

CreateToolhelp32Snapshot 用于获取系统内指定进程的快照,也可以获取被这些进程使用的堆、模块和线程的快照。函数的具体用法如下:

HANDLE
WINAPI
CreateToolhelp32Snapshot(
DWORD dwFlags,//用来指定“快照”中需要返回的对象,可以是TH32CS_SNAPPROCESS等
DWORD th32ProcessID//一个进程ID号,用来指定要获取哪一个进程的快照,
);//当获取系统进程列表或获取当前进程快照时可以设为0

该函数不仅可以获取进程列表,也可以用来获取线程和模块等对象的列表。dwFlags参数指定了获取列表的类型,其值可以是:

  • TH32CS_SNAPHEAPLIST 枚举th32ProcessID参数指定的进程中的堆
  • TH32CS_SNAPPROCESS 枚举系统范围内的进程,此时th32ProcessID参数被忽略
  • TH32CS_SNAPTHREAD 枚举系统范围内的线程,此时th32ProcessID参数被忽略
  • TH32CS_SNAPMODULE 枚举th32ProcessID参数指定的进程中的模块
  • TH32CS_SNAPALL (TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE)
函数执行成功将返回一个快照句柄,否则返回 INVALID_HANDLE_VALUE (即-1)。

从快照列表中获取进程信息需要使用 Process32First 和 Process32Next 函数,函数的每次调用仅返回一个进程的信息。 Process32First 函数用来进程首次调用,以后的调用有Process32Next 函数循环完成,直到所有的信息都被获取为止。当不再有剩余信息的时候,Process32Next 函数返回FALSE。

BOOL
WINAPI
Process32First(
HANDLE hSnapshot,
LPPROCESSENTRY32 lppe
);

BOOL
WINAPI
Process32Next(
HANDLE hSnapshot,
LPPROCESSENTRY32 lppe
);

Process32First 和Process32Next 的第一个参数是快照句柄,第二个参数是指向 PROCESSENTRY32 结构的指针,进程信息会被返回到这个结构中。

typedef struct tagPROCESSENTRY32
{
DWORD dwSize;// 结构的长度,必须预先设置
DWORD cntUsage;// 进程的引用计数
DWORD th32ProcessID; // 进程ID
DWORD th32DefaultHeapID;// 进程默认堆的ID
DWORD th32ModuleID; // 进程模块的ID
DWORD cntThreads;// 进程创建的线程数
DWORD th32ParentProcessID; // 进程的父线程ID
LONG pcPriClassBase; // 进程创建的线程的基本优先级
DWORD dwFlags;// 内部使用
CHAR szExeFile[MAX_PATH]; // 进程对应的可执行文件名
} PROCESSENTRY32;
使用ToolHelp函数不是获取系统内进程信息的唯一方法,函数EnumProcess 也可以。


2. 终止当前进程

终止进程也就是结束程序的执行,让它从内存中卸载。进程终止的原因可能有4种:

(1) 主线程的入口函数返回。

(2) 进程中一个线程调用了ExitProcess函数。

(3) 进程中所有的线程都结束了。

(4) 其他进程中的一个线程调用了TerminateProcess函数。

要结束当前进程一般让主线程的入口函数(如 main函数)返回。当用户的程序入口函数返回的时候,启动函数会调用C/C++运行期退出函数exit,并将用户的返回值传递给他。exit 函数会销毁所有全局的或静态的C++对象,然后调用系统函数 ExitProcess 促使操作系统终止应用程序。 ExitProcess 是一个API 函数,它会结束当前应用程序的执行,并设置它的退出代码,其用法如下:

void ExitProcess(UINT uExitCode);// 参数 uExitCode为此程序的退出代码

当然也可以在程序的任何地方去调用ExitProcess,强制当前程序的执行立即结束。在C/C++应用程序中应避免直接调用此函数,因为这会使C/C++运行期库得不到通知,而没有机会去调用全局的或静态的C++对象的析构函数。


3. 终止其他程序

ExitProcess 函数只能用来结束当前进程,结束其他进程的执行使用TerminateProcess 函数。

// 根据进程的ID号,终止指定进程
BOOL TerminateProcessFromId(DWORD dwId)
{
BOOL bRet = FALSE;
// 打开目标进程,取得进程句柄
HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwId);
if(hProcess != NULL)
{
// 终止进程
bRet = ::TerminateProcess(hProcess, 0);
}
CloseHandle(hProcess);
return bRet;
}

TerminateProcess()的声明如下:

BOOL
WINAPI
TerminateProcess(
HANDLE hProcess,// 要结束的进程(目标进程)的句柄
UINT uExitCode// 指定目标进程的退出代码,你可以使用GetExitCodeProcess取得一个进程的退出代码
);

在对一个进程进行操作前,必须首先取得该进程的进程句柄。CreateProcess 函数创建进程后会返回一个进程句柄,而对于一个已经存在的进程,只能使用OpenProcess函数来取得这个进程的访问权限,函数声明如下:

HANDLE
WINAPI
OpenProcess(
DWORD dwDesiredAccess,// 想得到的访问权限,可以使PROCESS_ALL_ACCESS等
BOOL bInheritHandle,// 指定返回的句柄是否可以被继承
DWORD dwProcessId// 指定要打开的进程的ID号
);

这个函数打开一个存在的进程并返回其句柄。dwDesiredAccess 参数指定了对该进程访问的权限;bInheritHandle 参数指定此函数返回的句柄是否可以被继承。dwProcessId 参数指定了要打开进程的ID号,可以从任务管理器中找到它们,也可以用ToolHelp函数获取。

进程结束以后,调用GetExitCodeProcess函数可以取得其退出代码,如果在调用这个函数时,目标进程还没有结束,此函数会返回STILL_ACTIVE,表示进程还在运行。这就给我们提供了一种检测进程是否已经终止的方法。

一旦进程终止,就会有下列事件发生:

(1) 所有被这个进程创建或打开的对象句柄就会关闭。

(2) 此进程内的所有县城将终止执行。

(3) 进程内核对象变成受信状态,所有等待在此对象上的线程开始运行,即WatiForSingleObject 函数返回。

(4) 系统将进程对象中退出代码的值由STILL_ACTVE 改为指定的退出码


4. GetLastError

OpenProcess 函数执行失败后将返回NULL,这时可以进一步调用GetLastError 函数取得出错代码。 GetLastError 取得调用线程的最后出错代码。最后出错代码是每个线程都维护的基本数据。 VC++提供了一个Error Lookup小工具来查看特定错误代码对应的详细信息,可以通过菜单命令“Tools/Error Lookup” 启动它。

DWORD WINAPI GetLastError(void);