Win32(1)

目录

1、字符

(1)字符编码

(2)C语言的宽字符

(3)API中的宽字符

2、进程

(1)进程的创建过程

(2)创建进程

(3)句柄表

(4)进程相关API

3、线程

(1)创建线程

(2)线程控制

(3)临界区的构建

(4)互斥体

(5)事件


1、字符

(1)字符编码

1、原始的ASCII表: 0—7F(0—127)

2、扩展的ASCII表(ANSI编码)

0—FF(0—255)
    00-7F:原始的ASCII表
    80-FF:一些不常用的符号

    GB2312或GB2312-80
    利用80-FF这个范围,每2个字节编码一个汉字

    中国    D6 D0 B9 FA
    1. 这些使用多个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码
    2. 在简体中文Windows操作系统中,ANSI 编码代表 GB2312编码
    3. 在繁体中文Windows操作系统中,ANSI编码代表Big5
    4. 在日文Windows操作系统中,ANSI 编码代表 JIS 编码

3、Unicode编码

编码范围:0—0x10FFFF

存在问题:只规定了符号的二进制代码,没有规定代码如何存储(有的字符占2个字节,有的占3个字节)

4、如何存储Unicode

UTF-16 / UTF-8 是Unicode的实现方式(存储方式)

UTF-16

以16位无符号整数为单位 (以2个字节对齐)
注意:
    1. 是16位为一个单位,不是一个字符就只有16位
    2. 要看字符的Unicode编码处于什么范围,字符可能占2字节,也可能占4字节
    3. Unicode 编码一般指的就是 UTF-16
缺点:
    1. 有些字符一个字节就能表示,比如:字母,使用 UTF-16却要使用2个字节
    2. 影响网络传输效率

UTF-8:  变长编码,以1/2/3/4字节为单位

                UTF-8 的 xxxx 部分对应的是 UTF-16 的编码

Unicode编码 / UTF-16(16进制)UTF-8字节流(二进制)
000000-00007F0xxxxxxx
000080-0007FF110xxxxx  0xxxxxxx
000800-00FFFF1110xxxx  10xxxxxx  10xxxxxx
010000-10FFFF11110xxx  10xxxxxx  10xxxxxx  10xxxxxx

BOM(Byte Order Mark):表明文件的存储格式,位于文件开头

UTF-8                     

EF BB BF

UTF-16LE(小端)

FF FE

UTF-16BE(大端)FE FF
字符:A中
// 用 UE打开  --> 使用16进制编辑
UTF-16: 41 00 2D 4E (小端存储)
        01000001 00000000 00101101 01001110
        'A'  的编码范围: 00-7F
        '中' 的编码范围: 0800-FFFF
        转换成 UTF-8编码
UTF-8:  41 E4 B8 AD
        01000001 1110(0100) 10(111000) 10(101101)

        (0100) (111000) (101101) = 01001110 00101101 = 4E 2D

(2)C语言的宽字符

1、字符串在内存中的值

char a[] = "中国";
D6 D0 B9 FA 00                 // 拓展的 ASCII,占 5字节(包含 '/0')

wchar_t b[] = L"中国";         // 以 UTF-16存储,不写 L采用项目默认编码
2D 4E FD 56 00 00              // Unicode编码,占 6字节(包含 '/0')

2、在控制台打印宽字符

#include<locale.h>

setlocale(LC_ALL,"");        // 使用控制台默认的编码
wchar_t a[] = L"中国";
wprintf(L"%s\n",a);

3、字符串长度

char a[] = "abcde";        // 占6字节内存
wchar_t b[] = L"abcde";    // 占12字节

strlen(a);           // 获取字符长度,不包含00,返回5
wcslen(b);           // 获取字符长度,不包含00 00,返回5
// 分别用于 ASCII字符串和 Unicode的字符串,不同的字符串要使用对应的函数
// 如果强制转型,使用错误的函数成功编译,不能得到正确的结果

4、常用函数

char       wchar_t            // 多字节字符类型    宽字符类型
printf     wprintf            // 打印到控制台函数
strlen     wcslen             // 获取长度
strcpy     wcscpy             // 字符串复制
strcat     wcscat             // 字符串拼接
strcmp     wcscmp             // 字符串比较
strstr     wcsstr             // 字符串查找

(3)API中的宽字符

1、Win32概念

主要存放在 C:\Windows\system32 ( 存放64位的 dll ) 和 C:\Windows\SysWow64 (32位)下面的所有的 dll

Kernel32.dll最核心的功能模块,如:内存管理、进程/线程相关的函数
User32.dllWindows用户界面相关应用程序接口,如:创建窗口、发送消息
GDI32.dll全称Graphical Device Interface(图形设备接口),包含:用于画图和显示文本的函数

2、Win32的数据类型

int MessageBox(
    HWND hWnd,             // handle to owner window
    LPCTSTR IpText,        // text in message box
    LPCTSTR IpCaption,     // message box title
    UINT tType             // message box style
);
// LPCTSTR --> LPCSTR --> CONST CHAR * --> const char *
// UINT --> unsigned int

常用数据类型
汇编:
    byte 		BYTE		PBYTE
    word		WORD		PWORD
    dword		DWORD		PDWORD
C语言:
    char                  CHAR          PCHAR
    unsigned char         UCHAR         PUCHAR
    short                 SHORT         PSHORT
    unsigned short        USHORT        PUSHORT
    int                   INT           PINT
    unsigned int          UINT          PUINT
C++语言:
    bool                  BOOL

3、在Win32中使用字符串

字符类型:
    CHAR szStr[] = "中国";            // ASCII
    WCHAR swzStr[] = L"中国";         // Unicode
    TCHAR stzStr[] = TEXT("中国");    // TCHAR是一个宏,根据项目的字符编码 自动转换字符的编码

字符串指针:
    PSTR pszStr = "中国";           // char*
    PWSTR pwszStr = L"中国";        // wchat_t*
    PTSTR ptszStr = TEXT("中国");   // PTSTR 是一个宏,有利于跨平台
    // 建议使用 TCHAR和 PTSTR

4、第一个Win32 API的使用

int main(){
    CHAR szTitle[] = "标题";
    CHAR szContent[] = "欢迎大家来的Win32 API世界";
    MessageBoxA(0, szContent, szTitle, MB_OK);      // ASCII 版本

    WCHAR swzTitle[] = L"标题";
    WCHAR swzContent[] = L"欢迎大家来的Win32 API世界";
    MessageBoxW(0, swzContent, swzTitle, MB_OK);    // Unicode 版本

    TCHAR stzTitle[] = TEXT("标题");
    TCHAR stzContent[] = TEXT("欢迎大家来的Win32 API世界");
    MessageBox(0, stzContent, stzTitle, MB_YESNO);  // 自动识别项目编码,但是 windows内核是 Unicode编码
    // MessageBox 也是一个宏,能做到自动识别
    return 0;
}

 

2、进程

(1)进程的创建过程

1、概念:进程为程序提供所需的资源(代码 / 数据),线程使用资源

2、进程内存空间的地址划分

  • 每个字节的内存空间,都进行了编址
  • 进程可以理解为:内存上的空间,线程负责使用空间内的资源
  • 所有进程共用高2GB 内核的内存空间
分区x86 32位Windows
空指针赋值区(64KB)0x00000000 - 0x0000FFFF(从未使用)
用户模式区0x00010000 - 0x7FFEFFFF
64KB禁入区0x7FFF0000 - 0x7FFFFFFF
内核0x80000000 - 0xFFFFFFFF(所有进程共用内核高2GB的内存空间)

  • 进程空间内有许多模块文件(dll),每个模块都是一个可执行文件,遵守相同的格式,即 PE结构

 

3、进程的创建过程任何进程都是由别的进程创建的,进程创建时总会创建一个线程,CreateProcess( )

1. 映射EXE文件         // 将 A.exe载入进程空间
2. 创建进程内核对象EPROCESS,每个进程都有自己的 EPROCESS对象
3. 映射系统 DLL(ntdll.dll)
4. 创建线程内核对象 ETHREAD
5. 系统启动线程:
          // 将A进程要执行的 DLL换成B进程的,最终执行的是B
          映射DLL(ntdll.LdrlnitializeThunk)    // 将所有需要使用的 dll,载入进程用户空间
          线程开始执行

// 创建进程,会创建一个 EPROCESS 结构体
// 创建线程,会创建一个 ETHREAD  结构体

 

(2)创建进程

BOOL CreateProcess (
    LPCTSTR lpApplicationName,                   // 对象名称,完整的路径名
    LPTSTR lpCommandLine,                        // 命令行参数,可以不使用为NULL
    LPSECURITY_ATTRIBUTES lpProcessAttributes,   // 创建的进程是否允许被继承
    LPSECURITY_ATTRIBUTES lpThreadAttributes,    // 同时创建的线程是否允许被继承
    BOOL bInheritHandles,                        // 是否要继承父进程的句柄表
    DWORD dwCreationFlags,                       // CREATE_SUSPENDED
    LPVOID lpEnvironment,                        // 环境块
    LPCTSTR lpCurrentDirectory,                  // 当前进程的工作目录
    LPSTARTUPINFO lpStartupInfo,                 // 新窗口如何显示,STARTUPINFO结构存储启动状态,IN参数
    LPPROCESS_INFORMATION lpProcessInformation   // PROCESS_INFORMATION结构,OUT参数
);

1、命令行

  • www.4399.com 就是命令行的参数
使用cd命令打开浏览器
使用cd命令打开浏览器

2、父进程为子进程填充STARTUPINFO结构(存储启动状态信息)

  • 如果进程的 STARTUPINFO结构信息出现异常,说明进程处于被调试状态(由调试器启动),不是从桌面双击启动(反调试)

 3、代码

#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
using namespace std;

/*
typedef struct _PROCESS_INFORMATION {
    HANDLE hProcess;	    // 进程句柄
    HANDLE hThread;         // 线程句柄 (创建进程一定会同时创建一个线程,所有该结构体包含线程的信息)
    DWORD dwProcessId;	    // 进程id
    DWORD dwThreadsId;	    // 线程id
} PROCESS_INFORMATION, *PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
*/

/*
typedef struct _STARTUPINFOA {
    DWORD cb;                    // 指定该结构的大小
    LPSTR IpReserved;            // 保留,曾为 NULL
    LPSTR IpDesktop;             // 指定一个字符串,包括该进程的桌面名或窗口位置名
    LPSTR IpTitle;               // 指定控制台进程创建的新控制台窗口标题
    DWORD dwX;                   // 指定新窗口左上角 x和 y偏移量(以像素为单位)
    DWORD dwY;		
    DWORD dwXSize;               // 指定新窗口的宽度和高度
    DWORD dwYSize;
    DWORD dwXCountChars;         // 指定新窗口的屏幕缓冲区的宽度和高度
    DWORD dwYCountChars;
    DWORD dwFillAttribute;       // 指定新窗口的初始文字和背景颜色
    DWORD dwFlags;               // 创建窗口标志
    WORD wShowWindow;            // 新窗口的显示状态
    WORD cbReserved2;            // 保留,必须置为0
    LPBYTE IpReserved2;          // 保留,必须置为 NULL
    HANDLE hStdInput;            // 指定一个句柄,该句柄用作进程的标准输入句柄
    HANDLE hStdOutput;           // 指定一个句柄,该句柄用作进程的标准输出句柄
    HANDLE hStdError;            // 指定一个句柄,该句柄用作进程的标准错误句柄
} STARTUPINFOA, *LPSTARTUPINFOA;
*/

// 输出 STARTUPINFO结构体信息
void AntiDebug() {
    STARTUPINFO si;
    GetStartupInfo(&si);	// 将父进程给子进程填写的启动信息存入结构体 si中(子进程就是要创建的进程)
// 打印获取到的信息
    printf("%x %x %x %x %x %x %x %x\n", si.dwX, si.dwY,
        si.dwXCountChars, si.dwYCountChars, si.dwFillAttribute,
        si.dwXSize, si.dwYSize, si.dwFlags);
}

// 创建子进程
BOOL CreatChildProcess(PTCHAR szChildProcessName, PTCHAR szComandLine) {
    STARTUPINFO si;                     // 在堆栈中创建这两个结构体
    PROCESS_INFORMATION pi;

    ZeroMemory(&pi, sizeof(pi));        // 结构体内存置 0
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);                 // cb 成员,记录结构体大小
// 创建子进程,返回成功或失败
    if (!CreateProcess(
        szChildProcessName,             // 对象名称,要创建的进程的名字
        szComandLine,                   // 命令行
        NULL,            // 不继承进程句柄
        NULL,            // 不继承线程句柄 
        FALSE,           // 不继承句柄,父进程句柄表与子进程无关
        0,               // 没有创建标志
        NULL,            // 使用父进程环境变量
        NULL,            // 使用父进程目录作为当前目录,可以自己设置目录
        &si,             // STARTUPINFOA结构体详细信息,IN参数,需要传入的参数
        &pi              // Process_INFORMATION结构体进程信息,OUT参数,传出函数执行结果
    )) {
        printf("错误代码:%d \n", GetLastError());
        return FALSE;
    }
// 释放句柄 (无效关闭,无法关闭创建的进程)
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return true;
}

int main() {
    TCHAR szApplicationName[] = TEXT("C:\\Edge\\Application\\msedge.exe");
    TCHAR szCmdLine[] = TEXT("\"C:\\Edge\\Application\\msedge.exe\" \"https://www.huya.com/11342396\"");
    CreatChildProcess(szApplicationName, NULL);
    // AntiDebug();
    getchar();
    return 0;
}

 

(3)句柄表

1、内核对象

  • 进程、线程、文件、互斥体、事件等在内核有一个对应的结构体,这些结构体由内核负责管理,称为内核对象;
  • CloseHandle 函数可以关闭的对象,全是内核对象
  • 如:Access token、Communications device、Console input、Console screen buffer、Event、File、File mapping
  • Job、Mailslot、Mutex、Named pipe、Process、Semaphore、Socket、Thread

2、管理内核对象

  • 创建内核对象,会在内核层生成对应的结构体,同时会返回结构体在内核层的地址( 返回给句柄表,不会返回给用户层 )
  • 访问内核时,如果访问了错误的内存地址,不会返回内存不可访问错误,而是导致蓝屏
  • 为了避免访问错误的内存地址,应用层无法直接看到内核结构体的地址,也不能直接访问内核结构体
  • 只能通过句柄表访问

3、每个进程有一个句柄表

  • 不是所有内核对象都拥有句柄表,只有进程的内核对象才拥有
  • 句柄表存放了,内核结构体的内存地址
  • 句柄表隔离了用户层和内核层,让用户无法直接操作内核

4、多进程共享一个内核对象(方式一)

  • 所有进程共用内核层,所有内核对象可以跨进程共享
  • 句柄表是每个进程私有的一张表,句柄值在本进程内才有意义
  • 内核对象A使用计数为2,CloseHandle 只是将内核对象的使用计数减1,并不是关闭;当使用计数为0,内核对象会被关闭

  • 结束一个线程句柄不会结束相关联的线程,要关闭线程内核对象,既要结束线程,也要让使用计数为0
  • 只要线程不结束,进程就不会结束;

 

5、句柄是否可以被继承(共享内核方式二)

  • 创建的内核对象中一定包含安全描述符(LPSECURITY_ATTRIBUTES 结构体)
  • 如:CreatEvent 函数的第一个参数就是安全描述符,所以 Event 是一个内核对象
  • 句柄表表项(3列):当前内核对象继承标志(是否可被继承)、索引、内核结构体地址
  • 下图,父进程中2、4项可被子进程继承,子进程获取父进程句柄表,其中子进程可直接使用父进程的句柄值;
// 函数原型
HANDLE CreateEvent(
    LPSECURITY_ATTRIBUTES lpEventAttributes,    // 安全描述符
    BOOL bManualReset,
    BOOL bInitialState,
    LPCWSTR lpName
);
// 安全描述符
typedef struct _SECURITY_ATTRIBUTES {
    DWORD nLength;                    // 结构体大小
    LPVOID lpSecurityDescriptor;      // 指针
    BOOL bInheritHandle;              // 当前内核对象,是否允许继承
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;

int main(){
    SECURITY_ATTRIBUTES sa;
    ZeroMemory(&sa,sizeof(SECURITY_ATTRIBUTES));
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.bInheritHandle = TRUE;

    CreateEvent(NULL, FALSE, FALSE);    // 该内核对象不能被继承,句柄表的标志为 0
    CreateEvent(sa, FALSE, FALSE);      // 允许被继承,句柄表的标志位 1
}

6、句柄是否允许被继承

  • CreateProcess 函数的第 5个参数,为 Flase 时,子进程不继承父进程的句柄表;
  • 为 True 时,子进程将继承父进程句柄表中能被继承的表项
BOOL CreateProcess (
    LPCTSTR lpApplicationName,                   // 对象名称,完整的路径名
    LPTSTR lpCommandLine,                        // 命令行参数,可以不使用为NULL
// 安全描述符
    LPSECURITY_ATTRIBUTES lpProcessAttributes,   // 创建的进程是否允许被继承
    LPSECURITY_ATTRIBUTES lpThreadAttributes,    // 同时创建的线程是否允许被继承

    BOOL bInheritHandles,                        // 是否要继承父进程的句柄表
    DWORD dwCreationFlags,                       // CREATE_SUSPENDED
    LPVOID lpEnvironment,                        // 环境块
    LPCTSTR lpCurrentDirectory,                  // 当前进程的工作目录
    LPSTARTUPINFO lpStartupInfo,                 // 新窗口如何显示,STARTUPINFO结构存储启动状态,IN参数
    LPPROCESS_INFORMATION lpProcessInformation   // PROCESS_INFORMATION结构,OUT参数
);

 

(4)进程相关API

1、ID与句柄

  • 句柄:当前进程句柄表的索引,称为句柄
  • 全局句柄表 (操作系统所有):包含所有正在运行中的进程和线程;
  • 进程ID / 线程ID:全局句柄表的一个索引;

BOOL TerminateProcess(
    HANDLE hProcess,        // 进程句柄
    UINT uExitCode          // 进程结束的原因
);
// 打开一个已经存在的进程 (相当于共享了 EPROCESS内核结构)
HANDLE OpenProcess(
    DWORD dwDesiredAccess,    // 被打开的进程,拥有的访问权限
    BOOL bInheritHandle,      // 是否能被继承
    DWORD dwProcessId         // 进程的 PID
);    

// 使用句柄关闭进程,失败
int main() {
    HANDLE hProcess;
    hProcess = (HANDLE)0xf8;
    if (!TerminateProcess(hProcess, 1)) {
        printf("终止进程失败:%d\n", GetLastError());        // 失败,错误代码 6(句柄无效)
    }
    // 进程句柄是私有的,不能用 A进程的句柄,来结束 A进程
    // 因为在 main进程中, A进程的句柄值是无效的
    return 0;
}
// 使用 PID关闭进程,成功
int main() {
    HANDLE hProcess;
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 0x14d8);    // 使用 PID
    // 使用 OpenProcess函数后,main进程和 A进程共享了内核对象
    // main 进程能通过 hProcess关闭 A进程
    if (!TerminateProcess(hProcess, 1)) {
        printf("终止进程失败:%d\n", GetLastError()); 
    }
    return 0;
}

2、以挂起的形式创建进程

// CreateProcess 函数的 dwCreationFlags参数
    值:CREATE_NEW_CONSOLE
    含义:新的进程将使用一个新的控制台,而不是继承父进程的控制台。这个标志不能与DETACHED_PROCESS标志一起使用

    值:CREATE_SUSPENDED
    含义:新进程的主线程会以暂停的状态被创建,直到调用 ResumeThread函数被调用时才运行

// 进程创建过程发生改变
    1.映射EXE文件 (A.exe)
    2.创建内核对象EPROCESS,每个进程都有自己的EPROCESS对象
    3.映射系统DLL(ntdll.dll)
    4.创建系统内核对象ETHREAD
    5.进程以挂起的方式创建:
          。。。进程进入等待状态      //可在此注入自己的代码
    6.恢复以后再继续执行:
              映射DLL(ntdll.LdrlnitializeThunk)
              线程开始执行

BOOL CreatChildProcess(PTCHAR szChildProcessName, PTCHAR szComandLine) {
        STARTUPINFO si;					
        PROCESS_INFORMATION pi;
        ZeroMemory(&pi, sizeof(pi));        // 结构体置 0
        ZeroMemory(&si, sizeof(si));
        si.cb = sizeof(si);                 // cb 成员,记录结构体大小

	// 创建子进程,返回成功或失败
	if (!CreateProcess(
		szChildProcessName,  // 对象名称,要创建的进程的名字
		szComandLine,        // 命令行
		NULL,                // 不继承进程句柄
		NULL,                // 不继承线程句柄 
		FALSE,               // 不继承句柄
		CREATE_SUSPENDED,    // 挂起方式创建
		NULL,        // 使用父进程环境变量
		NULL,        // 使用父进程目录作为当前目录,可以自己设置目录
		&si,         // STARTUPINFOA结构体详细信息,IN参数,需要传入的参数
		&pi          // Process_INFORMATION结构体进程信息,OUT参数,传出函数执行结果
	)) {
		printf("错误代码:%d \n", GetLastError());
		return FALSE;
	}
	// 获取进程信息
	printf("%x  %x", pi.dwProcessId, pi.hProcess);
        Sleep(10000);
        // 线程执行前,插入我们想执行的代码

        ResumeThread(pi.hThread);    
	// 释放句柄
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);
	return true;
}

int main() {
	TCHAR szApplicationName[] = TEXT("C:\\b.exe");
	CreatChildProcess(szApplicationName, NULL);
	return 0;
}

3、模块目录、工作目录

int main() {
        TCHAR strModule[256];
        GetModuleFileName(NULL, strModule, 256);    // 获取当前模块(当前运行的exe),所在的路径

        TCHAR strWork[256];                         // 获取工作路径(工作路径由父进程设置,默认是父进程的工作路径)
        GetCurrentDirectory(256, strWork);

        printf("模块路径:%s\n", strModule);
        printf("工作路径:%s\n", strWork);
        return 0;
}

4、其他进程相关API

GetCurrentProcessId            获取进程 PID
GetCurrentProcess              获取进程句柄
GetCommandLine                 获取命令行
GetStartupInfo                 获取启动信息
EnumProcess                    遍历进程 PID(系统中所有进程)
CreateToolhelp32Snapshot       // 进程快照:获取系统当前全部进程,进程拥有的模块

#include <stdio.h>
#include <Windows.h>
#include <tlhelp32.h>

/*
typedef struct tagMODULEENTRY32W{
	DWORD   dwSize;
	DWORD   th32ModuleID;       // This module
	DWORD   th32ProcessID;      // owning process
	DWORD   GlblcntUsage;       // Global usage count on the module
	DWORD   ProccntUsage;       // Module usage count in th32ProcessID's context
	BYTE  * modBaseAddr;        // Base address of module in th32ProcessID's context
	DWORD   modBaseSize;        // Size in bytes of module starting at modBaseAddr
	HMODULE hModule;            // The hModule of this module in th32ProcessID's context
	WCHAR   szModule[MAX_MODULE_NAME32 + 1];
	WCHAR   szExePath[MAX_PATH];
} MODULEENTRY32W;
*/

// 其他进程相关 API 作业
int main() {
	MODULEENTRY32 ModulInfo;
	ZeroMemory(&ModulInfo, sizeof(MODULEENTRY32));
	ModulInfo.dwSize = sizeof(MODULEENTRY32);

	HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
	if (hSnapshot == NULL){
		printf("Error:%d\n", GetLastError());
		return FALSE;
	}
	if (!Module32First(hSnapshot, &ModulInfo)){
		printf("Error:%d\n", GetLastError());
		CloseHandle(hSnapshot);
		return FALSE;
	}

// 打印模块信息
	do
	{
		wprintf(L"---------------------------------------------------\n");
		wprintf(L"ModulName:%s\n", ModulInfo.szModule);
		wprintf(L"ModulPath:%s\n", ModulInfo.szExePath);
		wprintf(L"ModulBaseAddr:%p\n", ModulInfo.modBaseAddr);
		wprintf(L"ModulHandle:%d\n", ModulInfo.hModule);
		wprintf(L"ModulSize:%d\n", ModulInfo.dwSize);
		wprintf(L"ProcessID:%d\n", ModulInfo.th32ProcessID);
		wprintf(L"---------------------------------------------------\n");
	} while (Module32Next(hSnapshot, &ModulInfo));
	CloseHandle(hSnapshot);

// 打印进程信息
	PROCESSENTRY32 pe32;
	ZeroMemory(&pe32, sizeof(PROCESSENTRY32));
	pe32.dwSize = sizeof(PROCESSENTRY32);
	hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (hSnapshot == INVALID_HANDLE_VALUE){
		printf("Error:%d\n", GetLastError());
		return FALSE;
	}
	if (!Process32First(hSnapshot, &pe32)){
		printf("Error:%d\n", GetLastError());
		CloseHandle(hSnapshot);
		return FALSE;
	}
	do
	{
		wprintf(L"---------------------------------------------------\n");
		wprintf(L"Proc ID:%d\n", pe32.th32ProcessID);
		wprintf(L"Proc Name:%s\n", pe32.szExeFile);
		wprintf(L"Proc Parent ID:%d\n", pe32.th32ParentProcessID);
		wprintf(L"Threads count:%d\n", pe32.cntThreads);
		wprintf(L"---------------------------------------------------\n");
	} while (Process32Next(hSnapshot, &pe32));
	return 0;
}

 

3、线程

(1)创建线程

1、线程

  • 线程是附属在进程上的执行实体是代码的执行流程
  • 一个进程可以包含多个线程,但一个进程至少要包含一个线程
  • main 函数就是一个线程

2、创建线程

HANDLE CreateThread(
    LPSECURITY_ATTRIBUTES lpThreadAttributes, 	    // SD,安全描述符
    SIZE_T dwStackSize,                             // initial stack size,线程需要的初始堆栈大小
    LPTHREAD_START_ROUTINE lpStartAddress,    	    // thread function,线程要执行的代码(有指定的格式)
    LPVOID lpParameter,                             // thread argument,参数
    DWORD dwCreationFlags,                          // creation option,创建标志
    // 0:创建后立即运行; CREATE_SUSPENDED:挂起的方式创建
    LPDWORD lpThreadId                              // thread identifier,返回线程 id
);

DWORD WINAPI ThreadProc(
    LPVOID lpParameter   			// thread data
);

3、向线程函数传递变量

// 标准格式,参数可以使用或不使用
// 不使用参数 CreateThread(NULL,0, ThreadProc,NULL,0,NULL);
DWORD WINAPI ThreadProc(LPVOID lpThreadParameter) {
    int* p = (int*)lpThreadParameter;
    for (int i = 0; i < *p; i++) {
        Sleep(500);
        printf("Thread --------------%d\n", i);
    }
    return 0;
}

// 需要强制转型
void WINAPI TestProc() {
    for (int i = 0; i < 20; i++) {
        Sleep(500);
        printf("Thread --------------%d\n", i);
    }
}

int main() {
    int n = 20;	
    // 局部变量 n在 main函数的堆栈中定义 (建议使用全局变量)
    // 向线程函数传递参数时,要保证参数的生命周期比线程的生命更长
    // 如果 main函数执行完毕后,线程还没执行完毕,main函数会清栈,参数 n会丢失
    HANDLE hThread = CreateThread(NULL,0, ThreadProc,(LPVOID)&n,0,NULL);
    //HANDLE hThread = CreateThread(NULL,0, (LPTHREAD_START_ROUTINE)TestProc,NULL,0,NULL);

    CloseHandle(hThread);
    // 线程被清理的2个条件:直接 CloseHandle无法清理线程
    // 线程内核对象的使用计数器清0,线程执行的代码已经执行完毕,线程对应的内核对象才会被清理
    for (int i = 0; i < 20; i++) {
        Sleep(500);
        printf("Main --------------%d\n", i);
    }
    return 0;
}

 

(2)线程控制

1、让线程停下来

Sleep()                // 让自己停下来
SuspendThread()        // 让别人停下来
ResumeThread()         // 线程恢复

DWORD WINAPI ThreadProc(LPVOID lpThreadParameter) {
    for (int i = 0; i < 20; i++) {
        Sleep(50);
        printf("Thread --------------%d\n", i);
    }
    return 0;
}

int main() {
    HANDLE hThread = CreateThread(NULL,0, ThreadProc,NULL,0,NULL);

    Sleep(5000);                    // 让 main线程,休息5秒
    SuspendThread(hThread);         // 使线程处于阻塞状态
    // SuspendThread(hThread);      // 挂起同一线程 2次,也必须恢复2次
    Sleep(5000);
    ResumeThread(hThread);

    CloseHandle(hThread);
    return 0;
}

2、等待线程结束

  • WaitForSingleObject 函数、WaitForMultipleObjects 函数
DWORD WaitForSingleObject(
    HANDLE hHandle,            // 进程或线程句柄
    DWORD dwMilliseconds       // 等待时间
);

int main() {
    HANDLE hThread = CreateThread(NULL,0, ThreadProc,NULL,0,NULL);
    
    WaitForSingleObject(hThread,INFINITE);  // 无限等待,直到目标对象状态发生改变,main函数才会继续执行
    printf("线程执行完毕\n");                // 线程执行完毕,打印这句话
    CloseHandle(hThread);
    return 0;
}


DWORD WaitForMultipleObjects(
    DWORD nCount,                // 等待内核对象的个数
    CONST HANDLE* lpHandles,     // 内核对象的句柄数组
    BOOL bWaitAll,               // 等待方式 True:所有对象状态改变,才继续执行
    DWORD dwMilliseconds         // 等待时间
);

int main() {
    HANDLE hThread[2];		// 创建 2个线程
    hThread[0] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
    hThread[1] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);

    WaitForMultipleObjects(2,hThread,TRUE,INFINITE);	// 直到 2个目标对象状态发生改变,main函数才会继续执行
    printf("线程执行完毕\n");			
    CloseHandle(hThread[0]);
    CloseHandle(hThread[1]);
    return 0;
}
  • GetExitCodeThread 函数
BOOL GetExitCodeThread(
    _In_ HANDLE hThread,            // 线程句柄
    _Out_ LPDWORD lpExitCode        // 返回状态
);


DWORD WINAPI ThreadProc(LPVOID lpThreadParameter) {
    // ... 线程函数执行完毕,返回 0
    return 0;
}

DWORD WINAPI ThreadProc1(LPVOID lpThreadParameter) {
    // ... 线程函数执行完毕,返回 1
    return 1;
}

int main() {
    HANDLE hThread[2];		
    hThread[0] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
    hThread[1] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
    DWORD dwResult1, dwResult2;                       // 2个线程的返回结果

    WaitForMultipleObjects(2,hThread,TRUE,INFINITE);
    GetExitCodeThread(hThread[0], &dwResult1);        // 获取线程执行结果,存入 dwResult1
    GetExitCodeThread(hThread[1], &dwResult2);
    printf("线程执行完毕\n");

    CloseHandle(hThread[0]);
    CloseHandle(hThread[1]);
    return 0;
}

3、设置、获取线程上下文

  • 单核处理器只能同时运行一个线程,在进行线程切换时,线程会把当前 CPU寄存器的内容保存在 结构体 context 
typedef struct DECLSPEC_NOINITALL _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;
  • 获取、设置寄存器的值
// 函数原型
BOOL GetThreadContext(
    HANDLE hThread,            // handle to thread with context
    LPCONTEXT lpContext        // context structure
);

BOOL SetThreadContext(
    HANDLE hThread,            // handle to thread
    CONST CONTEXT *lpContext   // context structure
);


int main() {
    HANDLE hThread[2];		
    hThread[0] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
    hThread[1] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);

    SuspendThread(hThread[0]);              // 线程挂起后,才能获取上下文
    CONTEXT ct1;
    ct1.ContextFlags = CONTEXT_INTEGER;     // 需要获取哪写寄存器的值,就给 ContextFlags赋对应的值
    GetThreadContext(hThread[0], &ct1);
    printf("%x %x %x %x\n", ct1.Eax, ct1.Ebx, ct1.Ecx, ct1.Edx);
    // 设置寄存器的值,用 SetThreadContext 函数传入
    ResumeThread(hThread[0]);

    CloseHandle(hThread[0]);
    CloseHandle(hThread[1]);
    return 0;
}

 

(3)临界区的构建

1、线程安全问题

  • 每个线程都有自己的栈,而局部变量是存储在栈中的,这就意味着每个线程都有一份自己的 "局部变量"
  • 如果线程仅仅使用 "局部变量那么就不存在线程安全问题
  • 如果多个线程共用一个全局变量,且对全局变量进行写操作

2、解决方法

  • 令牌:就是一个全局变量(是应用层的令牌)

 

3临界区实现之线程锁

1. 创建全局变量
        CRITICAL_SECTION cs;    // 结构体

2. 初始化全局变量
        InitializeCriticalSection(&cs);

3. 实现临界区 (线程锁)
        EnterCriticalSection(&cs);
            // 使用临界资源的代码(判断和读写全局变量的代码,放在临界区内部)
        LeaveCriticalSection(&cs);

int tickets = 10;
CRITICAL_SECTION cs;	// 临界区结构体

DWORD WINAPI Thread1(LPVOID lpThreadParameter) {
        EnterCriticalSection(&cs);
        while (tickets > 0) {            // 判断全局变量的代码,不能放在临界区之外
                                         // 会导致卖出后,还剩 -1张票
            printf("还有 %d张,", tickets);
            tickets--;
            printf("卖出一张,还剩 %d张\n", tickets);
        }
        LeaveCriticalSection(&cs);
        return 0;
}

int main() {
	InitializeCriticalSection(&cs);		// 初始化
	DWORD dwResult1, dwResult2;
	HANDLE hThread[2];
	// 用 2个窗口来卖票
	hThread[0] = CreateThread(NULL, 0, Thread1, NULL, 0, NULL);
	hThread[1] = CreateThread(NULL, 0, Thread1, NULL, 0, NULL);

	WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
	GetExitCodeThread(hThread[0], &dwResult1);
	GetExitCodeThread(hThread[1], &dwResult2);
	printf("%d  %d\n", dwResult1, dwResult2);
	CloseHandle(hThread[0]);
	CloseHandle(hThread[1]);
	return 0;
}

 

(4)互斥体

1、内核级临界资源

  • 线程锁:只能控制同一个进程中的两个或多个线程,共享一个临界资源,只有一个进程能访问令牌
  • 互斥体:内核级别的令牌,多个进程能同时访问互斥体

2、互斥体的使用

HANDLE CreateMutexW(
    LPSECURITY_ATTRIBUTES lpMutexAttributes,
    BOOL bInitialOwner,            // False:创建后,能直接使用(有信号)
                                   // True: 创建后,不能直接使用(没信号),表示当前互斥体属于当前的进程     
    LPCWSTR lpName                 // 给内核互斥体命名
);

获取互斥体令牌的方式:
    1. 有信号
    2. 线程的拥有者 (即使参数为 True,A进程仍能获取互斥体,但 B进程无法获取)


// 互斥体.cpp (A进程)
int main() {
    // 创建一个互斥体
    HANDLE hMutex = CreateMutex(NULL, FALSE, L"XYZ");
    // 获取互斥体
    WaitForSingleObject(hMutex, INFINITE);

    for (int i = 0; i < 10; i++) {
        Sleep(1000);
        printf("A进程的X线程 %d\n", i);
    }
    // 释放互斥体
    ReleaseMutex(hMutex);
    return 0;
}

// Test.cpp (B进程)
int main() {
    HANDLE hMutex = CreateMutex(NULL, FALSE, L"XYZ");        // 这个互斥体创建失败,因为 A进程已经创建了一个同名互斥体
                                                             // 但仍会返回同名互斥体的句柄
    WaitForSingleObject(hMutex, INFINITE);

    for (int i = 0; i < 10; i++) {
        Sleep(1000);
        printf("B进程的Y线程 %d\n", i);
    }
    ReleaseMutex(hMutex);
    return 0;
}

3、互斥体与线程锁的区别

  1. 线程锁只能用于单个进程间的线程控制
  2. 互斥体可以设定等待超时,但线程锁不能
  3. 进程A的线程意外终结时(会自动释放令牌),Mutex可以避免进程B无限等待
  4. Mutex效率没有线程锁高
int main() {
	HANDLE hMutex = CreateMutex(NULL, TRUE, L"XYZ");
	WaitForSingleObject(hMutex, INFINITE);

	for (int i = 0; i < 10; i++) {
		Sleep(1000);
		if (i == 5)  ExitProcess(0);        // A进程意外终结,会自动释放互斥体
		printf("A进程的X线程 %d\n", i);
	}
	ReleaseMutex(hMutex);
	return 0;
}

4、互斥体应用

  • 防止游戏(程序)多开,保证一个进程只能运行一份
int main() {
    // 创建一个互斥体
    HANDLE hMutex = CreateMutex(NULL, FALSE, L"防止多开");
    DWORD dwRet = GetLastError();
/*
先判断句柄是否有值,
有值:
    1. 第一次创建,返回句柄值,GetLastError函数返回 0
    2. 再次创建,返回句柄值,GetLastError函数返回 ERROR_ALREADY_EXISTS
无值:
    程序创建失败
*/
    if(hMutex){
        if(ERROR_ALREADY_EXISTS == dwRet){
            CloseHandle(hMutex);
            return 0;
        }
    }
    else{
        printf("创建失败,程序退出\n");
        CloseHandle(hMutex);
        return 0;
    }

    while(1){
        printf("执行程序\n");
    }
    return 0;
}

 

(5)事件

1、事件可作为通知类型来使用

  • 同时通知(唤醒)许多其他线程
HANDLE CreateEventW(
    LPSECURITY_ATTRIBUTES lpEventAttributes,
    BOOL bManualReset,            // True:当前 Event是通知类型  False:Event是互斥体
    BOOL bInitialState,           // False:创建后,无信号       True:创建后,有信号
    // 无信号,使用 WaitForSingleObject无法获取目标对象
    LPCWSTR lpName                // 给 Event命名,不命名只能在当前进程使用该 Event,其他进程无法使用
);


HANDLE hEvent;
DWORD WINAPI Thread1(LPVOID lpThreadParameter) {
	TCHAR szBuffer[10] = { 0 };
	// 当事件变成通知时
	WaitForSingleObject(hEvent, INFINITE);
	// 线程执行
	printf("Thread1执行了\n");
	getchar();
	return 0;
}

DWORD WINAPI Thread2(LPVOID lpThreadParameter) {
	TCHAR szBuffer[10] = { 0 };
	// 当事件变成通知时
	WaitForSingleObject(hEvent, INFINITE);
	printf("Thread2执行了\n");
	getchar();
	return 0;
}

int main() {
	// 创建事件
	// 默认安全属性,True通知/False互斥,初始没信号、没名字
	hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
	HANDLE hThread[2];
	hThread[0] = CreateThread(NULL, 0, Thread1, NULL, 0, NULL);
	hThread[1] = CreateThread(NULL, 0, Thread2, NULL, 0, NULL);

	// 设置事件为已通知(设置 Event为有信号)
	SetEvent(hEvent);

	// 等待事件结束,销毁内核对象
	WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
	CloseHandle(hThread[0]);
	CloseHandle(hThread[1]);
	CloseHandle(hEvent);
	return 0;
}

2、线程同步

  • 线程互斥:
  1. 线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。
  2. 当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用
  3. 其它要使用该资源的线程必须等待,直到占用资源者释放该资源。

 

  • 线程同步:
  1. 线程同步是指线程之间所具有的一种制约关系
  2. 一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。

3、同步的前提是互斥

  • 同步 = 互斥 + 有序
  • 使用 互斥体实现线程同步,缺点:浪费 cpu时间片
HANDLE hMutex;       // 互斥体
int max = 10;        // 生产 10个产品
int number = 0;      // 产品容器(缓冲区)
// 生产者线程
DWORD WINAPI ThreadProduct(LPVOID lpThreadParameter) {
    for (int i = 0; i < max; i++) {
    // 互斥的访问缓冲区
        WaitForSingleObject(hMutex, INFINITE);
        if (number == 0){
            number = 1;
            DWORD id = GetCurrentThreadId();
            printf("生产者%d将数据%d放入缓冲区\n", id, number);
        }else {
            i--;    // 浪费了 cpu时间片
        }
        ReleaseMutex(hMutex);
    }
    return 0;
}

// 消费者线程
DWORD WINAPI ThreadConsumer(LPVOID lpThreadParameter) {
    for (int i = 0; i < max; i++) {
    // 互斥的访问缓冲区
        WaitForSingleObject(hMutex, INFINITE);
        if (number != 0) {
            number = 0;
            DWORD id = GetCurrentThreadId();
            printf("消费者%d将数据%d放入缓冲区\n", id, number);
        }else {
            i--;	// 白白消耗 cpu时间
        }
        ReleaseMutex(hMutex);
    }
    return 0;
}

int main() {
    hMutex = CreateMutex(NULL, FALSE, NULL);
    HANDLE hThread[2];
    hThread[0] = CreateThread(NULL, 0, ThreadProduct, NULL, 0, NULL);
    hThread[1] = CreateThread(NULL, 0, ThreadConsumer, NULL, 0, NULL);

    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    CloseHandle(hThread[0]);
    CloseHandle(hThread[1]);

    CloseHandle(hMutex);
    return 0;
}
  • 使用事件实现线程同步,解决了 cpu 时间片浪费的问题
HANDLE hSet,hClear;     // 生产者事件、消费者事件
int max = 10;           // 生产 10个产品
int number = 0;         // 产品容器(缓冲区)
// 生产者线程
DWORD WINAPI ThreadProduct(LPVOID lpThreadParameter) {
    for (int i = 0; i < max; i++) {
    // 互斥的访问缓冲区
        WaitForSingleObject(hSet, INFINITE);
        number = 1;
        DWORD id = GetCurrentThreadId();
        printf("生产者%d将数据%d放入缓冲区\n", id, number);
        SetEvent(hClear);
    }
    return 0;
}

// 消费者线程
DWORD WINAPI ThreadConsumer(LPVOID lpThreadParameter) {
    for (int i = 0; i < max; i++) {
    // 互斥的访问缓冲区
        WaitForSingleObject(hClear, INFINITE);	// 获取对象后,把 hClear设为无信号
        number = 0;
        DWORD id = GetCurrentThreadId();
        printf("消费者%d将数据%d放入缓冲区\n", id, number);
        SetEvent(hSet);
        // 将 hSet设为有信号(唤醒生产者线程),并将消费者线程挂起(即使还有多余的cpu时间片,仍会挂起)
    }
    return 0;
}

int main() {
    // 只在 main进程使用,第4个参数为 NULL,不命名
    hSet = CreateEvent(NULL, FALSE, TRUE, NULL);		// 初始有信号,让生产者先运行
    hClear = CreateEvent(NULL, FALSE, FALSE, NULL);		// 初始无信号

    HANDLE hThread[2];
    hThread[0] = CreateThread(NULL, 0, ThreadProduct, NULL, 0, NULL);
    hThread[1] = CreateThread(NULL, 0, ThreadConsumer, NULL, 0, NULL);

    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    CloseHandle(hThread[0]);
    CloseHandle(hThread[1]);

    CloseHandle(hSet);
    CloseHandle(hClear);
    return 0;
}

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值