1.句柄
句柄就是个数字,⼀般和当前系统下的整数的位数⼀样,⽐如32bit系统下就是4个字节。
这个数字是⼀个对象的唯⼀标示,和对象⼀⼀对应
这个对象可以是⼀个块内存,⼀个资源,或者⼀个服务的context(如 socket,thread)等等
指针其实也是⼀种”句柄”,只是由于指针同时拥有更特殊的含义——实实在在地对应内存⾥地⼀个地址
在进程的地址空间中设⼀张表,表⾥头专⻔保存⼀些编号和由这个编号对应⼀个地址,⽽由那个地址去引⽤实际的对象,这个编号跟那个地址在数值上没有任何规律性的联系,纯粹是个映射⽽已。
在windows这个编号就是“句柄”
为什么不用首地址呢?
- 暴露了内核对象本身,使得程序(⽽不是操作系统内核)也可以任意地修改对象地内部状态,这显然是操作系统内核所不允许的
- 操作系统有定期整理内存的责任,如果⼀些内存整理过⼀次后,对象被搬⾛了怎么办?
看一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <Windows.h> #include <stdio.h> #include <string.h> #include <iostream> #include<tchar.h> using namespace std; int main(int argc, char* argv[]) {
TCHAR szPath[MAX_PATH] = { 0 }; HWND hw = FindWindow(L"notepad", NULL); printf("%x", hw); RECT rect; GetWindowRect(hw, &rect); int w = rect.right - rect.left, h = rect.bottom - rect.top; cout << "宽:" << w << " " << "高:" << h << endl; GetModuleFileName(NULL, szPath, MAX_PATH); _tprintf(TEXT("%s\n"), szPath); return 0; }
|
所以说,有了某个进程的句柄,就能对之进行操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <Windows.h> #include <stdio.h> #include <string.h> #include <iostream> #include<tchar.h> using namespace std; int main(int argc, char* argv[]) { DWORD dwThreadId; HWND hw = FindWindow(L"notepad",NULL); GetWindowThreadProcessId(hw, &dwThreadId); HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, dwThreadId); printf("%x\n", hProcess); system("pause"); return 0; }
|
句柄表存放在ring0空间,⽽句柄的存在⽬的就是为了避免应⽤层直接修改内核都对象。
2.和线程相关的API函数
创建进程
1 2 3 4 5 6 7 8 9 10 11 12 13
| BOOL CreateProcess( LPCWSTR pszImageName, LPCWSTR pszCmdLine, LPSECURITY_ATTRIBUTES psaProcess, LPSECURITY_ATTRIBUTES psaThread, BOOL fInheritHandles, DWORD fdwCreate, LPVOID pvEnvironment, LPWSTR pszCurDir, LPSTARTUPINFOW psiStartInfo, LPPROCESS_INFORMATION pProcInfo );
|
CreateProcess函数的最后两个参数 结构体指针 LPSTARTUPINFOW 指向的结构体和 LPPROCESS_INFORMATION结构体。LPPROCESS_INFORMATION指向的结构体用来接收新创建的进程的相关信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| typedef struct _STARTUPINFO { DWORD cb; LPTSTR lpReserved; LPTSTR lpDesktop; LPTSTR lpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFlags; WORD wShowWindow; WORD cbReserved2; LPBYTE lpReserved2; HANDLE hStdInput; HANDLE hStdOutput; HANDLE hStdError; } STARTUPINFO, *LPSTARTUPINFO;
|
1 2 3 4 5 6 7
| typedef struct _PROCESS_INFORMATION { HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; } PROCESS_INFORMATION;
|
使⽤API函数GetStartipInfo来获取⽗进程创建⾃⼰时使⽤的STARTUPINFO结构。
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include <iostream> #include <Windows.h> int main() { LPCWSTR pszImageName = L"C:\\Windows\\System32\\notepad.exe"; wchar_t cmdLine[] = L" C:\\Users\ASUS\\Desktop\\123\\ttt.txt"; C:\\Users\\z\\Desktop\\ttt.txt" LPWSTR pszCmdLine = cmdLine; STARTUPINFOW si = { sizeof(si) }; si.wShowWindow = TRUE; PROCESS_INFORMATION pi; BOOL bCr = CreateProcess( pszImageName, pszCmdLine, NULL, NULL, false, CREATE_SUSPENDED, NULL, NULL, &si, &pi); if (bCr) { HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, pi.dwThreadId); ResumeThread(hThread); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); std::cout << "新进程的ID号是" << pi.dwProcessId << std::endl; } }
|
遍历进程
CreateToolhelp32Snapshot:
CreateToolhelp32Snapshot给当前系统内执⾏的进程拍快照,Process32First函数和Process32Next函数遍历快照表中记录的列表。
1 2 3 4 5 6 7 8 9 10 11 12 13
| HANDLE WINAPI CreateToolhelp32Snapshot( DWORD dwFlags, DWORD th32ProcessID ); BOOL WINAPI Process32First( HANDLE hSnapshot, LPPROCESSENTRY32 lppe ); BOOL WINAPI Process32Next( HANDLE hSnapshot, LPPROCESSENTRY32 lppe );
|
需要重点关注的是Process32First和Process32Next的第⼆个参数,⽤来获取相关信息的结构体,PROCESSENTRY32结构体定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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; typedef PROCESSENTRY32* PPROCESSENTRY32; typedef PROCESSENTRY32* LPPROCESSENTRY32;
|
使用该结构体前第一个参数要先初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <iostream> #include <Windows.h> #include <tlhelp32.h> int main() { PROCESSENTRY32 pe32; pe32.dwSize = sizeof(pe32); HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hProcessSnap == INVALID_HANDLE_VALUE) { std::cout << "快照失败!" << std::endl; return -1; } BOOL bMore = ::Process32First(hProcessSnap, &pe32); while (bMore) { std::cout << "进程名称:" << pe32.szExeFile << std::endl; std::cout << "进程ID号:" << pe32.th32ProcessID << std::endl; std::cout << std::endl; bMore = ::Process32Next(hProcessSnap, &pe32); } CloseHandle(hProcessSnap); return 0; }
|
当然快照不仅仅可以用来获取系统进程信息,还可以获取指定进程中的堆,模块等.
详细还得看文档
终止进程
除了进程中所有线程结束或主线程的⼊⼝函数返回之外,我们还可以通过调⽤函数强⾏终⽌进程,ExitProcess****终⽌当前进程,TerminateProcess**可以终⽌其他进程。
1 2
| VOID ExitProcess( UINT uExitCode); BOOL TerminateProcess( HANDLE hProcess, DWORD uExitCode);
|
可以很明显的看到两个函数都有⼀个参数uExitCode,该参数⽤来重置退出代码的值,进程中所有线程在没有结束时Exit Code的值都是STILL_ACTIVE,进程结束,进程中的所有线程的Exit Code的值替换成该参数。该值可以通过GetExitCodeThread获取。
不建议使⽤这两个函数终⽌进程,这两个函数结束进程的时候可能导致内存泄露。
3.线程操作
Win32线程控制只有是围绕线程这⼀内核对象的创建、挂起、恢复、终结以及通信等操作,这些操作都依赖于Win32操作系统提供的⼀组API和具体编译器的C运⾏时库函数。
_beginthread
- _beginthread(函数名,栈大小,参数指针)
- Win32 函数中提供了多线程的函数,包括创建线程,管理线程,终止线程,线程同步等接口
线程函数=》线程开始执行的函数
DWORD WINAPI ThreadFunc (LPVOID lpvThreadParm );【 PVIOD 一个普通指针类型】
线程创建
1 2 3 4 5 6 7
| HANDLE CreateThread ( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );
|
第⼀个参数lpThreadAtt,是⼀个指向SECURITY- ATTRIBUTES结构的指针,该结构制定了线程的安全属性,缺省为 NULL。
第⼆个参数dwStackSize,是栈的⼤⼩,⼀般设置为0。
第三个参数lpFun是新线程开始执⾏时,线程函数的⼊⼝地址。它必须是将要被新线程执行的函数地址,不能为NULL。
第四个参数lpParameter,是线程函数定义的参数。可以通过这个参数传送值,包括指针或者NULL
第五个参数dwCreationFlags,控制线程创建的附加标志,可以设置两种值。
- 0表示线程在被创建后就会⽴即开始执⾏;
- 如果该参数为CREATE_SUSPENDED,则系统产⽣线程后,该线程处于挂起状态,并不⻢上执⾏,直⾄函数ResumeThread被调⽤;
第六个参数lpThreadID,为指向32位变量的指针,该参数接受所创建线程的ID号。如果创建成功则返回线程的ID,否则返回NULL。
CreateThread不会执⾏C运⾏时数据块, 因此在C运⾏时库的应⽤程序中,不能使⽤CreateThread创建线程,微软提供了另外的创建线程的⽅法:创建线程⽤process.h头⽂件中声明的C执⾏时期链接库函数_beginthread。
1 2 3 4 5
| hThread = _beginthread ( void( __cdecl *start_address )( void * ), unsigned stack_size, void *arglist) ; 线程函数的语法: void __cdecl ThreadProc (void * pParam) ;
|
在windows平台下可以通过windows的线程库来实现多线程编程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include "stdio.h" #include <windows.h> #include <process.h> #include <iostream> #include <fstream> using namespace std;
void ThreadFunc1(PVOID param) { while (1) { Sleep(1000); cout << " This is ThreadFunc1 " << endl; } } void ThreadFunc2(PVOID param) { while (1) { Sleep(1000); cout << " This is ThreadFunc2 " << endl; } }
int main() { int i = 0; _beginthread(ThreadFunc1, 0, NULL); _beginthread(ThreadFunc2, 0, NULL); Sleep(5000); cout << "end" << endl; return 0; }
|
这里可以看到我们开了2个线程,两个进程同时在我们程序中运行,所以一般要优化程序运行速度就会开多个线程去跑这个程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
|
HANDLE WINAPI CreateThread( _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ SIZE_T dwStackSize, _In_ LPTHREAD_START_ROUTINE lpStartAddress, _In_opt_ LPVOID lpParameter, _In_ DWORD dwCreationFlags, _Out_opt_ LPDWORD lpThreadId );
DWORD WINAPI SuspendThread( _In_ HANDLE hThread );
DWORD WINAPI ResumeThread( _In_ HANDLE hThread );
:线程超时 DWORD WINAPI WaitForSingleObject( _In_ HANDLE hHandle, _In_ DWORD dwMilliseconds ); DWORD WINAPI WaitForMulipleObjects(_In_ DWORD nCount, _In_ const HANDLE *lpHandles, _In_ BOOL bWaitAll, 变为已通知 _In_ DWORD dwMilliseconds );
DWORD WINAPI ExitThread(DWORD exitCode); DWORD WINAPI TerminateThread(_In_ HANDLE hThread, _In_ DWORD exitCode); BOOL GetExitCodeThread(_In_ HANDLE hThread, _Out_ DWORD exitCode) WaitForSingleObject 使⽤/ DWORD WINAPI SuspendThread(_In_ HANDLE hhandle); CONTEXT context; context.ContextFlage = CONTEXT_CONTROL; BOOL GetThreadContext(_In_ HANDLE hThread, _In_ CONTEXT* context);
CONTEXTSetThreadContext(_In_ HANDLE hThread, _In_ CONTEXT* context);
CRITICAL_SECTION cs;
VOID WINAPI InitializeCriticalSection(&cs);
VOID WINAPI EnterCriticalScetion(&cs);
VOID WINAPI LeaveCriticalSection(&cs);
VOID WINAPI DeleteCriticalSection(cs);
时造成死锁 DWORD a; DWORD b; DWORD c; CRITICAL_SECTION ka; CRITICAL_SECTION kb; CRITICAL_SECTION kc;
HANDLE gMutex = CreateMutex(nullptr, FALSE, "name");
HANDLE gMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "name"); WaitForSingleObject(HANDLE g_Mutex, DWORD timeOut); 只能等待,不能接着往下 ReleaseMutex(HANDLE dMutex);
|
以上是大致的线程操作。
互斥体
互斥体一般用于防止程序双开,我们这边看一个堵塞例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include "stdio.h" #include <windows.h> #include <iostream> using namespace std; int main() { HANDLE g_hMutex = CreateMutex(0, FALSE, TEXT("Z")); 号 FALSE 创建出来有信号 WaitForSingleObject(g_hMutex, INFINITE); for (int i = 0; i < 10; i++) { Sleep(1000); printf("A进程的X线程:%d \n", i); } ReleaseMutex(g_hMutex); return 0; }
|
可以使用 互斥对象 来保护共享资源,防止多个线程或进程同时访问。 每个线程必须等待互斥体的所有权,然后才能执行访问共享资源的代码。 例如,如果多个线程共享对数据库的访问,则线程可以使用互斥对象一次只允许一个线程写入数据库.
这⾥两个例⼦是不可以同时进⾏打印的,因为他们有互斥体,运⾏会发现,如果没有得到令牌,程序是会堵塞住的,直到获取令牌。