Windows程序设计(1)——Win32运行原理(三)

时间:2022-12-24 08:45:26

4 进程控制

4.1 获得系统进程

使用toolhelp模块可以实现获取系统中当前运行当中的进程列表。

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

#include <windows.h>
#include <TlHelp32.h> // for snapshot
#include <stdio.h>

int main(int argc, char* argv[])
{
    PROCESSENTRY32 pe32;
    int iProcessCount;

    // initial its size
    pe32.dwSize = sizeof(pe32);
    iProcessCount = 0;

    // create snapshot for all processes
    HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hProcessSnap == INVALID_HANDLE_VALUE)
    {
        printf("CreateToolhelp32Snapshot failure!\n");
        exit(1);
    }

    // traversal snapshot
    BOOL bMore = ::Process32First(hProcessSnap, &pe32);
    while (bMore) 
    {
        printf("%-20.20s%6u%4u%6u%6u%6u%3ld\n", pe32.szExeFile, 
            pe32.th32ProcessID, pe32.cntUsage, pe32.th32DefaultHeapID, 
            pe32.th32ModuleID, pe32.th32ParentProcessID, pe32.pcPriClassBase);
        bMore = ::Process32Next(hProcessSnap, &pe32);
        iProcessCount ++;
    }

    // clear snapshot
    ::CloseHandle(hProcessSnap);

    printf("Processes count: %d\n\n", iProcessCount);

    getchar();
    return 0;
}

结果很容易理解。

Windows程序设计(1)——Win32运行原理(三)

CreateToolhelp32Snapshot用于获取系统内指定进程的快照,也可以获取这些进程使用的堆、模块、线程的快照。如:

HANDLE WINAPI CreateToolhelp32Snapshot( 
    DWORD dwFlags,      // 指定快照内容
    DWORD th32ProcessID // 指定进程ID
);

dwFlags参数可以是如下值:

  • TH32CS_SNAPHEAPLIST枚举`th32ProcessID参数指定进程中的堆。
  • TH32CS_SNAPMODULE枚举`th32ProcessID参数指定进程中的模块。
  • TH32CS_SNAPPROCESS,此时`th32ProcessID参数被忽略。
  • TH32CS_SNAPTHREAD,此时`th32ProcessID参数被忽略。
  • TH32CS_SNAPALL,相当于以上4个值的并集。

根据获取快照的不同,使用下面几组函数来获取快照信息:

  • Heap32ListFirst Heap32ListNext
  • Module32First Module32Next
  • Process32First Process32Next
  • Thread32First Thread32Next
  • Heap32First Heap32Next

以上函数的第二个参数是指向如下之一结构的指针:

  • HEAPLIST32
  • HEAPENTRY32
  • MODULEENTRY32
  • THREADENTRY32
  • PROCESSENTRY32
typedef struct tagHEAPLIST32 { 
    DWORD dwSize; 
    DWORD th32ProcessID; 
    DWORD th32HeapID; 
    DWORD dwFlags; 
} HEAPLIST32; 

typedef struct tagHEAPENTRY32 
{ 
    DWORD dwSize; 
    HANDLE hHandle; 
    DWORD dwAddress; 
    DWORD dwBlockSize; 
    DWORD dwFlags; 
    DWORD dwLockCount; 
    DWORD dwResvd; 
    DWORD th32ProcessID; 
    DWORD th32HeapID; 
} HEAPENTRY32;

typedef struct tagMODULEENTRY32 { 
    DWORD dwSize; 
    DWORD th32ModuleID; 
    DWORD th32ProcessID; 
    DWORD GlblcntUsage; 
    DWORD ProccntUsage; 
    BYTE *modBaseAddr; 
    DWORD modBaseSize; 
    HMODULE hModule;
    TCHAR szModule[MAX_MODULE_NAME32 + 1]; 
    TCHAR szExePath[MAX_PATH]; 
    DWORD dwFlags
} MODULEENTRY32;

typedef struct tagTHREADENTRY32{ 
    DWORD dwSize; 
    DWORD cntUsage; 
    DWORD th32ThreadID; 
    DWORD th32OwnerProcessID; 
    LONG tpBasePri; 
    LONG tpDeltaPri; 
    DWORD dwFlags;
    DWORD th32AccessKey;
    DWORD th32CurrentProcessID;
} THREADENTRY32; 

typedef struct tagPROCESSENTRY32 { 
    DWORD dwSize; 
    DWORD cntUsage; 
    DWORD th32ProcessID; 
    DWORD th32DefaultHeapID; 
    DWORD th32ModuleID; 
    DWORD cntThreads; 
    DWORD th32ParentProcessID; 
    LONG pcPriClassBase; 
    DWORD dwFlags; 
    TCHAR szExeFile[MAX_PATH]; 
    DWORD th32MemoryBase;
    DWORD th32AccessKey;
} PROCESSENTRY32; 

4.2 终止当前进程

终止一个进程有如下4种方式:

  1. 主线程的入口函数返回。
  2. 进程中一个线程调用了ExitProcess函数。
  3. 此进程中的所有线程都结束了。
  4. 其他进程中的一个线程调用了TerminateProcess函数。

最常用的方式是让主线程的入口函数返回。当入口函数返回时,启动函数会调用C/C++运行期退出函数exit,并将用户的返回值传递给它。exit函数会销毁所有的全局的或者静态的C++对象,然后调用系统函数ExitProcess使操作系统终止应用程序。ExitProcess是一个API函数,它会结束当前应用程序的执行,并设置它的退出代码。

VOID ExitProcess(  
    UINT uExitCode   // 所有线程的退出代码
);

在程序的任何地方都可以调用ExitProcess,强制当前程序的执行立即结束。这对操作系统来说,是正常的。但对C/C++应用程序应该避免直接调用这个函数。因为这会使C/C++运行期库得不到通知,而没有机会去调用全局的或者静态的C++对象的析构函数。

4.3 终止其他进程

ExitProcess函数只能用来结束当前进程,不能用于结束其他进程。如果要终止其他进程,可以使用TerminateProcess函数。

BOOL TerminateProcess(
    HANDLE hProcess, // 目标进程句柄
    UINT uExitCode   // 目标进程的退出代码
);

前面介绍过,使用CreateProcess函数创建新的进程,会得到新进程的句柄。对于不是自己创建的进程,可以使用OpenProcess函数来取得这进程的访问权限,函数如下:

HANDLE OpenProcess(
    DWORD dwDesiredAccess,  // 访问权限
    BOOL bInheritHandle,    // 返回的句柄是否可被继承
    DWORD dwProcessId       // 要打开的进程的ID
);

参数dwDesiredAccess指定的对进程的访问权限,可以是如下值:

说明
PROCESS_ALL_ACCESS 所有可进行的权限
PROCESS_CREATE_PROCESS 创建一个进程
PROCESS_CREATE_THREAD 创建一个线程
PROCESS_DUP_HANDLE 做为DuplicateHandle参数
PROCESS_QUERY_INFORMATION 查看进程信息的权限,如优先级类等
PROCESS_QUERY_LIMITED_INFORMATION 需要返回特定信息
PROCESS_SET_INFORMATION 设置进程的优先级
PROCESS_SET_QUOTA 设置内存限制
PROCESS_SUSPEND_RESUME 睡眠和唤醒
PROCESS_TERMINATE 关闭进程
PROCESS_VM_OPERATION 修改进程的地址空间
PROCESS_VM_READ 读进程内存
PROCESS_VM_WRITE 写进程内存
SYNCHRONIZE 等待进程结束

在进程结束后,调用GetExitCodeProcess函数可以取得其退出代码,如果调用时,目标进程还没有结束,此函数会返回STILL_ACTIVE,表示进程还在运行。通过该函数可以检测进程是否结束了。

BOOL GetExitCodeProcess(
    HANDLE hProcess,     // 目标进程句柄
    LPDWORD lpExitCode   // 目标进程的退出句柄
);

如果进程已经结束,其退出代码是通过下面几种方式设置的:

  1. ExitProcess或者TerminateProcess函数中指定。
  2. main或者WinMain的返回值。
  3. 由于未处理的异常导致的结束,返回相应的异常值。

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

  1. 所有被这个进程创建的或撕开的对象句柄就会关闭。
  2. 此进程内的所有线程将终止执行。
  3. 进程内核对象变成受信状态,所有等待在此对象上的线程开始运行,即WaitForSingleObject函数返回。
  4. 系统将进程对象中退出代码值由STILL_ACTIVE设置为指定的退出代码。