【技術分享】】深入理解win32(十一)
前言
在上一節里面我們實現了進程的遍歷以及初步了解了線程,在這一節里面我們繼續來對線程控制來進行探究。
線程控制
我們首先來看兩個api
SuspendThread
用來掛起線程,如果函數成功, 傳回線程目前的掛起次數。如果失敗, 則傳回0xFFFFFFFF
case IDC_BUTTON2: { ::SuspendThread(hThread);
return TRUE; }
ResumeThread
用來恢復線程,如果函數成功, 則傳回線程的前一個掛起次數。如果失敗, 則傳回0xFFFFFFFF。這個函數允許調用端指定一個線程睡眠(掛起)。直到又有人調用了ResumeThread(), 線程才會醒來。因此,睡眠中的線程不可能喚醒自己。
case IDC_BUTTON3: { ::ResumeThread(hThread);
return TRUE; }
這里我們編寫一個win32程序并設置兩個按鈕,一個按鈕為掛起線程,另一個按鈕為恢復線程進行測試,實現代碼如下
// thread CONTEXT.cpp : Defines the entry point for the application.//
#include "stdafx.h"
HWND hEdit;HANDLE hThread;
DWORD WINAPI ThreadProc1(LPVOID lpParameter) { TCHAR szBuffer[10]; DWORD dwIndex = 0; DWORD dwCount;
while(dwIndex<1000) { GetWindowText(hEdit,szBuffer,10); sscanf( szBuffer, "%d", &dwCount ); dwCount++;
Sleep(200);
memset(szBuffer,0,10); sprintf(szBuffer,"%d",dwCount); SetWindowText(hEdit,szBuffer); dwIndex++; }
return 0; }
BOOL CALLBACK MainDlgProc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam) { BOOL bRet = FALSE;
switch(uMsg) { case WM_CLOSE: { EndDialog(hDlg,0); break; } case WM_INITDIALOG: { hEdit = GetDlgItem(hDlg,IDC_EDIT); SetWindowText(hEdit,"0");
break; } case WM_COMMAND:
switch (LOWORD (wParam)) { case IDC_BUTTON1: { //創建線程 hThread = ::CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
return TRUE; } case IDC_BUTTON2: { //掛起線程 ::SuspendThread(hThread);
return TRUE; }case IDC_BUTTON3: { //恢復線程 ::ResumeThread(hThread);
return TRUE; } } break ; }
return bRet; }
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow){// TODO: Place code here. DialogBox(hInstance,MAKEINTRESOURCE(IDD_DIALOG_MAIN),NULL,MainDlgProc);
return 0;}
演示效果如下

終止線程
我們知道在線程結束的時候是有一個返回值的,在正常結束線程的情況下,返回值為0

那在這里我們加一段代碼,這里為終止線程的第一種方法,使用到ExitThread這個api,這種方法終止線程的同時會清理堆棧
ExitThread
void ExitThread( [in] DWORD dwExitCode); ::ExitThread(DWORD dwExitCode);
這里相當于如果手動中止線程的話就會返回8


同步調用&異步調用
同步調用的通俗理解就是比如有三個程序要執行,必須第一個程序被觸發,執行結束了之后,才輪到其他程序執行

異步調用則是所有程序的執行不需要同步,可以多個觸發,互相獨立的執行相應的指令
在同步調用中關閉線程之后會得到操作系統的返回值,在往下執行代碼
這里終止線程的第二種方法就是使用TerninateThread這個api,結構如下
TerninateThread
BOOL TerminateThread( [in, out] HANDLE hThread, [in] DWORD dwExitCode);
在異步調用中如果光使用如下代碼,在得到關閉線程指令后不會等待線程關閉的消息返回,而是直接往下執行
::TerminateThread(hThread,2);
所以這里就需要再加上一行等待的代碼
::WaitForSingleObject(hThread,INFINITE);
這里使用的異步調用是不清理堆棧的

CONTEXT結構
每個線程在執行的時候,都會獨自占用一個CPU,當系統中的線程數量 > CPU的數量時,就會存在多個線程共用一個CPU的情況。但CPU每次只能運行一個線程,Windows每隔20毫秒會進行線程的切換,那比如線程A執行到地址:0x2345678 eax:1 ecx:2 edx:3 ebx:4…還有eflag標志寄存器中的值等等
此時,線程執行時間到了,被切換到了線程B。當線程B的時間片也到了,再切換會線程A時,系統是如何知道該從哪個地址開始執行呢?被切換前用到的各種寄存器的值該如何恢復呢?
這里在進行線程的切換的時候要對原線程中的寄存器的值進行保存,這時候就會用到CONTEXT這個結構體

CONTEXT結構如下
CONTEXT:
該結構包含了特定處理器的寄存器數據。
typedef struct _CONTEXT {
// // The flags values within this flag control the contents of // a CONTEXT record. // // If the context record is used as an input parameter, then // for each portion of the context record controlled by a flag // whose value is set, it is assumed that that portion of the // context record contains valid context. If the context record // is being used to modify a threads context, then only that // portion of the threads context will be modified. // // If the context record is used as an IN OUT parameter to capture // the context of a thread, then only those portions of the thread's // context corresponding to set flags will be returned. // // The context record is never used as an OUT only parameter. //
DWORD ContextFlags;
// // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is // set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT // included in CONTEXT_FULL. //
DWORD Dr0; DWORD Dr1; DWORD Dr2; DWORD Dr3; DWORD Dr6; DWORD Dr7;
// // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_FLOATING_POINT. //
FLOATING_SAVE_AREA FloatSave;
// // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_SEGMENTS. //
DWORD SegGs; DWORD SegFs; DWORD SegEs; DWORD SegDs;
// // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_INTEGER. //
DWORD Edi; DWORD Esi; DWORD Ebx; DWORD Edx; DWORD Ecx; DWORD Eax;
// // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_CONTROL. //
DWORD Ebp; DWORD Eip; DWORD SegCs; // MUST BE SANITIZED DWORD EFlags; // MUST BE SANITIZED DWORD Esp; DWORD SegSs;
// // This section is specified/returned if the ContextFlags word // contains the flag CONTEXT_EXTENDED_REGISTERS. // The format and contexts are processor specific //
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;
那么這里測試一下,代碼如下
case IDC_BUTTON2: { ::SuspendThread(hThread);
CONTEXT context;
//設置要獲取的類型
context.ContextFlags = CONTEXT_CONTROL;
//獲取
BOOL ok = ::GetThreadContext(hThread,&context);
//設置
context.Eip = 0x401000;
SetThreadContext(hThread,&context);
::ResumeThread(hThread);
return TRUE; }
演示效果如下,這里因為eip的值是隨便設置的所以掛掉了



GetExitCodeThread
用來判斷判斷線程是否結束,此函數調用成功返回TRUE,失敗返回FALSE,只表示這個函數是否調用成功而己。不能根據返回值來判斷一個線程是否結束,而要根據 lpExitCode的值來確定,lpExitCode為STILL_ACTIVE時表示線程正在運行。若線程己經結束,則lpExitCode中存儲指定線程的返回值,結構如下
BOOL GetExitCodeThread( HANDLE hThread, LPDWORD lpExitCode ); hThread[in] Handle to the thread. Windows NT/2000/XP: The handle must have THREAD_QUERY_INFORMATION access. For more information, see Thread Security and Access Rights. lpExitCode[out] Pointer to a variable to receive the thread termination status.
其中返回值為一個指針,實現代碼如下
case IDC_BUTTON5: { DWORD dwExitCode = 0; ::GetExitCodeThread(hThread, &dwExitCode);
OutputDebugStringF("The process ID is:%d",dwExitCode);
return TRUE; }
實現效果如下,這里的259就是16進制103,STILL_ACTIVE,證明線程還存在沒有終止
首先點擊Start,然后GetID為259


點擊Hang即掛起,GetID還是259證明還是線程還是處于STILL_ACTIVE狀態


點擊Stop即終止線程,GetID為2證明線程已經終止


這里再測試一下最終效果,代碼如下
// thread CONTEXT.cpp : Defines the entry point for the application.//
#include "stdafx.h"
HWND hEdit;HANDLE hThread;
DWORD WINAPI ThreadProc1(LPVOID lpParameter) { TCHAR szBuffer[10]; DWORD dwIndex = 0; DWORD dwCount;
while(dwIndex<1000) { GetWindowText(hEdit,szBuffer,10); sscanf( szBuffer, "%d", &dwCount ); dwCount++;
Sleep(200);
memset(szBuffer,0,10); sprintf(szBuffer,"%d",dwCount); SetWindowText(hEdit,szBuffer); dwIndex++; }
return 0; }
BOOL CALLBACK MainDlgProc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam) { BOOL bRet = FALSE;
switch(uMsg) { case WM_CLOSE: { EndDialog(hDlg,0); break; } case WM_INITDIALOG: { hEdit = GetDlgItem(hDlg,IDC_EDIT); SetWindowText(hEdit,"0");
break; } case WM_COMMAND:
switch (LOWORD (wParam)) { case IDC_BUTTON1: { //創建線程 hThread = ::CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
return TRUE; } case IDC_BUTTON2: { //掛起線程 ::SuspendThread(hThread);
/*CONTEXT context;
//設置要獲取的類型
context.ContextFlags = CONTEXT_CONTROL;
//獲取
BOOL ok = ::GetThreadContext(hThread,&context);
//設置
context.Eip = 0x401000;
SetThreadContext(hThread,&context);
::ResumeThread(hThread);*/
return TRUE; }case IDC_BUTTON3: { //恢復線程 ::ResumeThread(hThread);
return TRUE; }case IDC_BUTTON4: { ::TerminateThread(hThread,2); ::WaitForSingleObject(hThread,INFINITE);
return TRUE; }case IDC_BUTTON5: { DWORD dwExitCode = 0; ::GetExitCodeThread(hThread, &dwExitCode);
OutputDebugStringF("The process ID is:%d",dwExitCode);
return TRUE; } } break ; }
return bRet; }
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow){// TODO: Place code here. DialogBox(hInstance,MAKEINTRESOURCE(IDD_DIALOG_MAIN),NULL,MainDlgProc);
return 0;}
我們知道程序窗口是主線程,我們又自己創建了一個線程,觀察線程返回ID和線程數的變化
首先我們啟動程序,因為主程序啟動了所以只有1個線程

點擊start之后,因為使用CreateThread又創建了一個線程,所以為兩個線程

然后點擊Hang即掛起,使用GetID查看為259,線程仍然存在,處于STILL_ACTIVE狀態,任務管理器里面也可以看到線程仍然為2

點擊Stop,使用GetID查看為2,證明線程已經終止,任務管理器里面的線程也變為了1
