1. 程式人生 > >WIN32程序快照以及程序ID和控制代碼查詢

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。