超实用!WIN32进程快照以及进程ID和句柄查找
扫描二维码
随时随地手机看文章
要对进程进行某种操作,就必须首先知道该进程的进程句柄或者进程ID,否则一切无从谈起,对于程序自己创建的子进程来说,CreateProcess函数返回了进程句柄和进程ID,但如果需要调试系统中已经运行的进程,那就必须首先获取它们的句柄才行。Win32中并没有直接获取其他进程句柄的函数,但如果知道进程ID,可以由此得到进程句柄,所以可以首先通过某种途径获取进程ID。
一、获取进程ID
1. 从窗口句柄获取进程句柄
获取进程ID的方法之一是使用GetWindowThreadProcessId函数,这个函数可以从一个窗口句柄获得创建该窗口的进程的进程ID,而通过FindWindow函数得到窗口句柄是很简单的,所以GetWindowThreadProcessId函数的用途相当广泛。该函数的用法是:
DWORD GetWindowThreadProcessId(
HWND hWnd, // handle to window
LPDWORD lpdwProcessId // process identifier
);
其中hWnd参数指定需要用来获取进程ID的窗口句柄,lpdwProcessId指向一个双字变量,函数在这里返回创建窗口的进程ID,函数的返回值是目标进程中创建该窗口的线程的线程句柄(一个有用的副产品!)。
2. 通过快照来获取进程ID
每一个应用程序实例在运行起来后都会在当前系统下产生一个进程,大多数应用程序均拥有可视界面,用户可以通过标题栏上的关闭按钮关闭程序。但是也有为数不少的在后台运行的程序是没有可视界面的,对于这类应用程序用户只能通过CTRL+ALT+DEL热键呼出"关闭程序"对话框显示出当前系统进程列表,从中可以结束指定的任务。显然,该功能在一些系统监控类软件中还是非常必需的,其处理过程大致可以分为两步:借助系统快照实现对系统当前进程的枚举和根据枚举结果对进程进行管理。本文下面即将对此过程的实现进行介绍。
当前进程的枚举
要对当前系统所有已开启的进程进行枚举,就必须首先获得那些加载到内存的进程当前相关状态信息。在Windows操作系统下,这些进程的当前状态信息不能直接从进程本身获取,系统已为所有保存在系统内存中的进程、线程以及模块等的当前状态的信息制作了一个只读副本--系统快照,用户可以通过对系统快照的访问完成对进程当前状态的检测。在具体实现时,系统快照句柄的获取是通过Win32 API函数CreateToolhelp32Snapshot()来完成的,通过该函数不仅可以获取进程快照,而且对于堆、模块和线程的系统快照同样可以获取。该函数原型声明如下:
HANDLE WINAPI CreateToolhelp32Snapshot(DWORD dwFlags,DWORD th32ProcessID);
其中,参数dwFlags:指定将要创建包含哪一类系统信息的快照句柄,本程序中只需要检索系统进程信息,因此可将其设置为 TH32CS_SNAPPROCESS;函数第二个参数th32ProcessID`则指定了进程的标识号,当设置为0时指定当前进程。如果成功函数将返回一个包含进程信息的系统快照句柄。在得到快照句柄之后只能以只读的方式对其进行访问。至于对系统快照句柄的使用同普通对象句柄的使用并没有什么太大区别,在使用完之后也需要通过CloseHandle()函数将其销毁。
在得到系统的快照句柄后,就可以对当前进程的标识号进行枚举了,通过这些枚举出的进程标识号可以很方便的对进程进行管理。进程标识号通过函数 Process32First() 和 Process32Next()而得到,这两个函数可以枚举出系统当前所有开启的进程,并且可以得到相关的进程信息。 这两个函数原型声明如下:
BOOL WINAPI Process32First(HANDLE hSnapshot, LPPROCESSENTRY32 lppe);
BOOL WINAPI Process32Next(HANDLE hSnapshot,LPPROCESSENTRY32 lppe);
以上两个函数分别用于获得系统快照中第一个和下一个进程的信息,并将获取得到的信息保存在指针lppe所指向的PROCESSENTRY32结构中。函数第一个参数hSnapshot为由CreateToolhelp32Snapshot()函数返回得到的系统快照句柄;第二个参数lppe为指向结构 PROCESSENTRY32的指针,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;
以上三个API函数均在头文件tlhelp32.h中声明,运行时需要有kernel32.lib库的支持。通过这三个函数可以枚举出当前系统已开启的所有进程,并可获取到进程的各相关信息,下面给出一个简单的应用示例。在此示例中将枚举出系统的所有进程,并逐个比较进程名,查找需要的进程信息。如果有,则返回进程ID,否则返回 -1。
pid_t CProbeCMaster::is_process_running(const char* process_name)
{
pid_t process_id = -1;
#ifdef ACE_WIN32
HANDLE Snapshot;
Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
//获得某一时刻系统的进程、堆(heap)、模块(module)或线程的快照信息
PROCESSENTRY32 processListStr;
processListStr.dwSize = sizeof(PROCESSENTRY32);
BOOL return_value;
return_value = Process32First(Snapshot,&processListStr);
//获得系统进程链表中第一个进程的信息
while(return_value)
{
if( ACE_OS::strcmp(process_name, processListStr.szExeFile) == 0 )
{ //比较进程名,如果此进程与传入的进程名相同,那么就找到了需要的进程信息
process_id = processListStr.th32ProcessID;
break;
}
return_value = Process32Next(Snapshot, &processListStr);
//获得系统进程链表中下一个进程的信息
}
CloseHandle( Snapshot );
#endif
return process_id;
}
二、获取进程的句柄
得到了进程ID以后,就可以通过OpenProcess函数来获取该进程的句柄了。
HANDLE OpenProcess(
DWORD dwDesiredAccess, // access flag
BOOL bInheritHandle, // handle inheritance option
DWORD dwProcessId // process identifier
);
函数的参数定义如下:
● dwDesiredAccess——指定需要对该进程进行的操作,要对目标进程进行某种操作,必须指定操作代码,但是在Windows NT操作系统中,对其他进程操作需要有相应的权限,如需要结束目标进程就必须有PROCESS_TERMINATE权限才行,当权限不够的时候,打开进程的操作就会失败。一般来说,除了系统进程以外,可以对其他进程进行任何操作,操作码可以是以下取值的组合:
■ PROCESS_ALL_ACCESS——等于下面全部操作码的组合。
■ PROCESS_CREATE_THREAD——允许创建远程线程。
■ PROCESS_DUP_HANDLE——允许进程句柄被复制。
■ PROCESS_QUERY_INFORMATION——允许使用GetExitCodeProcess函数查询进程的退出码或使用GetPriorityClass函数查询进程的优先级。
■ PROCESS_SET_INFORMATION——允许使用SetPriorityClass函数设置进程的优先级。
■ PROCESS_TERMINATE——允许终止进程。
■ PROCESS_VM_OPERATION—允许使用WriteProcessMemory函数或VirtualProtectEx函数修改进程的地址空间。
■ PROCESS_VM_READ——允许对进程的地址空间进行读操作。
■ PROCESS_VM_WRITE——允许对进程的地址空间进行写操作。
● bInheritHandle——指明返回的进程句柄是否可以被当前进程的子进程继承,如果参数指定为TRUE,则句柄可以被继承。
● dwProcessId——指定目标进程的进程ID。
如果函数执行成功,返回值是被打开的进程句柄。如果函数执行失败则返回NULL。一般打开失败的原因是由权限不够引起的。当完成对目标进程的操作以后,必须使用CloseHandle将获得的句柄关闭。
三、对进程的管理
在得到各枚举进程的标识号后就可以实现对进程的管理了,由于被管理进程在当前进程之外,因此必须首先通过OpenProcess()函数来获取一个已经存在的进程对象的句柄,然后才可以通过该句柄对指定的进程进行管理和控制。在OpenProcess()函数的调用时把进程标识号作为参数传入, OpenProcess()函数的原型声明如下:
HANDLE OpenProcess(DWORD dwDesiredAccess, // 访问标志
BOOL bInheritHandle, // 处理继承的标志
DWORD dwProcessId // 进程标识号);
如果函数执行成功将返回由进程标识号指定的进程对象句柄。下面同样也对其给出一个简单的应用示例,在此示例中根据所获取的进程对象句柄通过TerminateProcess()函数将指定的进程终止:
// 查看"test.ext"进程是否存在,如果存在则返回它的进程ID
pid_t process_id = is_process_running("test.exe");
if ( process_id > 0 )
{
// 利用进程的ID值,打开该进程,获得进程句柄
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE,data);
// 检测句柄的有效性,如有效则终止该进程
if (hProcess)
TerminateProcess(hProcess,0);
}
由于需要在调用TerminateProcess()函数终止进程时确保进程句柄可有效使用,因此在前面调用OpenProcess()时,需要指定其访问标致为PROCESS_TERMINATE。