标准控件 前言Windows标准控件,标准控件总是可用的 具体有’’’Static Group Box Button Check Box Radio Button Edit ComboBox ListBox’…
标准控件
前言
Windows标准控件,标准控件总是可用的
具体有
Static
Group Box
Button
Check Box
Radio Button
Edit
ComboBox
ListBox
通用控件
前言
Windows通用控件,代码包含在Comctrl32.dll
具体有
Animation
ComboBoxEx
Date_and_Time_Picker
Drag_List_Box
Flat_Scroll_Bar
Header
HotKey
ImageList
IPAddress
List_View
Month_Calendar
Pager
Progress_Bar
Property_Sheets
Rebar
Status Bars
SysLink
Tab
Toolbar
ToolTip
Trackbar
TreeView
Up_and_Down
使用
画两个通用控件,进行简单排版
修改ID
IDC_LIST_PROCESS
IDC_LIST_MOUDLE
修改输出为报表形式
代码中,还需要添加代码,进行加载DLL
#include <commctrl.h>
#pragma comment(lib,"comctl32.lib")
通用控件在使用前,需要通过INITCOMMONCONTROLSEX进行初始化
只要在您的程序中的任意地方引用了该函数就、会使得WINDOWS的程序加载器PE Loader加载该库
INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&icex);
我们可以去MSDN看一下INITCOMMONCONTROLSEX
这个函数
typedef struct tagINITCOMMONCONTROLSEX {
DWORD dwSize; //当前结构的大小
DWORD dwICC; //通用控件名
} INITCOMMONCONTROLSEX, *LPINITCOMMONCONTROLSEX;
初始化列名信息
//设置ProcessListView风格
VOID InitProcessListView(HWND hDlg)
{
LV_COLUMN lv;
HWND hListProcess;
//初始化
memset(&lv,0,sizeof(LV_COLUMN));
//获取IDC_LIST_PROCESS句柄
hListProcess = GetDlgItem(hDlg,IDC_LIST_PROCESS);
//设置整行选中
SendMessage(hListProcess,LVM_SETEXTENDEDLISTVIEWSTYLE,LVS_EX_FULLROWSELECT,LVS_EX_FULLROWSELECT);
//第一列
lv.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
lv.pszText = TEXT("进程"); //列标题
lv.cx = 150; //列宽
lv.iSubItem = 0; //表示第1列
//ListView_InsertColumn(hListProcess, 0, &lv);
SendMessage(hListProcess,LVM_INSERTCOLUMN,0,(DWORD)&lv);
//第二列
lv.pszText = TEXT("PID");
lv.cx = 90;
lv.iSubItem = 1; //表示第2列
//ListView_InsertColumn(hListProcess, 1, &lv);
SendMessage(hListProcess,LVM_INSERTCOLUMN,1,(DWORD)&lv);
//第三列
lv.pszText = TEXT("镜像基址");
lv.cx = 90;
lv.iSubItem = 2; //表示第3列
ListView_InsertColumn(hListProcess, 2, &lv);
//第四列
lv.pszText = TEXT("镜像大小");
lv.cx = 90;
lv.iSubItem = 3;
ListView_InsertColumn(hListProcess, 3, &lv);
}
消息:
//主窗口初始化
case WM_INITDIALOG:
{
InitProcessListView(hDlg);
}
向进程窗口中新增数据
//向进程窗口中新增数据函数
VOID EnumProcess(HWND hListProcess)
{
//描述成员和元素
LV_ITEM vitem;
//初始化
memset(&vitem,0,sizeof(LV_ITEM));
vitem.mask = LVIF_TEXT; //存储的是文本
vitem.pszText = "csrss.exe"; //第一个成员
vitem.iItem = 0; //第1行
vitem.iSubItem = 0; //第1列
//ListView_InsertItem(hListProcess, &vitem); ListView_InsertItem是一个宏 == SendMessage
//只有第一列是:LVM_INSERTITEM,后面的都是LVM_SETITEM
SendMessage(hListProcess, LVM_INSERTITEM,0,(DWORD)&vitem);
vitem.pszText = TEXT("448");
vitem.iItem = 0;
vitem.iSubItem = 1;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = TEXT("56590000");
vitem.iItem = 0;
vitem.iSubItem = 2;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = TEXT("000F0000");
vitem.iItem = 0;
vitem.iSubItem = 3;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = TEXT("winlogon.exe");
vitem.iItem = 1;
vitem.iSubItem = 0;
//ListView_InsertItem(hListProcess, &vitem);
SendMessage(hListProcess, LVM_INSERTITEM,0,(DWORD)&vitem);
vitem.pszText = TEXT("456");
vitem.iSubItem = 1;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = TEXT("10000000");
vitem.iSubItem = 2;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = TEXT("000045800");
vitem.iSubItem = 3;
ListView_SetItem(hListProcess, &vitem);
}
//设置ModulesListView风格
VOID InitModulesListView(HWND hwndDlg)
{
LV_COLUMN lv;
HWND hListModules;
//初始化
memset(&lv, 0, sizeof(LV_COLUMN));
//获取IDC_LIST_MODULE句柄
hListModules = GetDlgItem(hwndDlg, IDC_LIST_MODULE);
//设置整行选中
SendMessage(hListModules,LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT,LVS_EX_FULLROWSELECT);
//第一列
lv.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
lv.pszText = TEXT("模块名称"); //列标题
lv.cx = 200;//列宽
lv.iSubItem = 0;
// ListView_InsertColumn(hListModules, 0, &lv);
SendMessage(hListModules,LVM_INSERTCOLUMN,0,(DWORD)&lv);
//第二列
lv.pszText = TEXT("模块位置");
lv.cx = 200;
lv.iSubItem = 1;
// ListView_InsertColumn(hListModules, 1, &lv);
SendMessage(hListModules,LVM_INSERTCOLUMN,1,(DWORD)&lv);
}
消息:
//主窗口初始化
case WM_INITDIALOG:
{
InitProcessListView(hDlg);
InitModulesListView(hDlg);
}
向模块新增数据
//向模块新增数据
VOID EnumModules(HWND hListProcess, WPARAM wParam, LPARAM lParam)
{
DWORD dwRowId;
TCHAR szPid[0x20];
LV_ITEM lv;
//初始化
memset(&lv, 0, sizeof(LV_ITEM));
memset(szPid, 0, 0x20);
//获取选择行
//点第一行:dwRowId == 0
//点第二行:dwRowId == 1
dwRowId = SendMessage(hListProcess, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
if (dwRowId == -1)
{
MessageBox(NULL, TEXT("请选择进程"), TEXT("出错咯"), MB_OK);
return;
}
//想要遍历进程的模块,要拿到进程的PID
//获取PID
lv.iSubItem = 1;//要获取的列
lv.pszText = szPid; //指定存储查询结果的缓冲区
lv.cchTextMax = 0x20; //指定缓冲区大小
SendMessage(hListProcess, LVM_GETITEMTEXT, dwRowId, (DWORD)&lv);
MessageBox(NULL, szPid, TEXT("PID"), MB_OK);
}
WM_NOTIFY
&子控件
前言
该消息类型与WM_COMMAND
类型相似,都是由子窗口向父窗口发送的消息
WM_NOTIFY
可以包含比WM_COMMAND
更丰富的信息
Windows通用组件中有很多消息,都是通过WM_NOTIFY来描述的
参数解析
WM_NOTIFY
idCtrl = (int) wParam;
pnmh = (LPNMHDR) lParam;
参数解析
wParam:控件ID
lParam:指向一个结构
typedef struct tagNMHDR {
HWND hwndFrom; //发送通知消息的控制窗口句柄
UINT idFrom; //发送通知消息的控制ID值
UINT code; //通知码,如LVM_SELCHANGED,左键,右键
} NMHDR;
这个结构体能满足一般的要求,但能描述的信息还是有限的
解决方案:对每种不同用途的通知消息都定义另一种结构来表示
针对lParam指向的结构,还有类似的结构,类似继承的思想
typedef struct tagNMLVCACHEHINT {
NMHDR hdr;
int iFrom;
int iTo;
} NMLVCACHEHINT, *PNMLVCACHEHINT;
typedef struct tagLVDISPINFO {
NMHDR hdr;
LVITEM item;
} NMLVDISPINFO, FAR *LPNMLVDISPINFO;
typedef struct _NMLVFINDITEM {
NMHDR hdr;
int iStart;
LVFINDINFO lvfi;
} NMLVFINDITEM, *PNMLVFINDITEM;
总结
通用控件发送消息,都是使用WM_NOTIFY
消息类型
消息调用
case WM_NOTIFY:
{
NMHDR* pNMHDR = (NMHDR*)lParam;
if (wParam == IDC_LIST_PROCESS && pNMHDR->code == NM_CLICK)
{
EnumModules(GetDlgItem(hDlg, IDC_LIST_PROCESS), wParam, lParam);
}
break;
}
代码示例
// test2.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#include "resource.h"
#include <commctrl.h>
#pragma comment(lib,"comctl32.lib")
//向列表中新增数据函数
VOID EnumProcess(HWND hListProcess)
{
LV_ITEM vitem;
//初始化
memset(&vitem,0,sizeof(LV_ITEM));
vitem.mask = LVIF_TEXT;
vitem.pszText = "csrss.exe";
vitem.iItem = 0;
vitem.iSubItem = 0;
//ListView_InsertItem(hListProcess, &vitem);
SendMessage(hListProcess, LVM_INSERTITEM,0,(DWORD)&vitem);
vitem.pszText = TEXT("448");
vitem.iItem = 0;
vitem.iSubItem = 1;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = TEXT("56590000");
vitem.iItem = 0;
vitem.iSubItem = 2;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = TEXT("000F0000");
vitem.iItem = 0;
vitem.iSubItem = 3;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = TEXT("winlogon.exe");
vitem.iItem = 1;
vitem.iSubItem = 0;
//ListView_InsertItem(hListProcess, &vitem);
SendMessage(hListProcess, LVM_INSERTITEM,0,(DWORD)&vitem);
vitem.pszText = TEXT("456");
vitem.iSubItem = 1;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = TEXT("10000000");
vitem.iSubItem = 2;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = TEXT("000045800");
vitem.iSubItem = 3;
ListView_SetItem(hListProcess, &vitem);
}
//设置ProcessListView风格
VOID InitProcessListView(HWND hDlg)
{
LV_COLUMN lv;
HWND hListProcess;
//初始化
memset(&lv,0,sizeof(LV_COLUMN));
//获取IDC_LIST_PROCESS句柄
hListProcess = GetDlgItem(hDlg,IDC_LIST_PROCESS);
//设置整行选中
SendMessage(hListProcess,LVM_SETEXTENDEDLISTVIEWSTYLE,LVS_EX_FULLROWSELECT,LVS_EX_FULLROWSELECT);
//第一列
lv.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
lv.pszText = TEXT("进程");//列标题
lv.cx = 150;//列宽
lv.iSubItem = 0;
//ListView_InsertColumn(hListProcess, 0, &lv);
SendMessage(hListProcess,LVM_INSERTCOLUMN,0,(DWORD)&lv);
//第二列
lv.pszText = TEXT("PID");
lv.cx = 90;
lv.iSubItem = 1;
//ListView_InsertColumn(hListProcess, 1, &lv);
SendMessage(hListProcess,LVM_INSERTCOLUMN,1,(DWORD)&lv);
//第三列
lv.pszText = TEXT("镜像基址");
lv.cx = 90;
lv.iSubItem = 2;
ListView_InsertColumn(hListProcess, 2, &lv);
//第四列
lv.pszText = TEXT("镜像大小");
lv.cx = 90;
lv.iSubItem = 3;
ListView_InsertColumn(hListProcess, 3, &lv);
EnumProcess(hListProcess);
}
//向模块新增数据
VOID EnumModules(HWND hListProcess, WPARAM wParam, LPARAM lParam)
{
DWORD dwRowId;
TCHAR szPid[0x20];
LV_ITEM lv;
//初始化
memset(&lv, 0, sizeof(LV_ITEM));
memset(szPid, 0, 0x20);
//获取选择行
//点第一行:dwRowId == 0
//点第二行:dwRowId == 1
dwRowId = SendMessage(hListProcess, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
if (dwRowId == -1)
{
MessageBox(NULL, TEXT("请选择进程"), TEXT("出错咯"), MB_OK);
return;
}
//想要遍历进程的模块,要拿到进程的PID
//获取PID
lv.iSubItem = 1;//要获取的列
lv.pszText = szPid; //指定存储查询结果的缓冲区
lv.cchTextMax = 0x20; //指定缓冲区大小
SendMessage(hListProcess, LVM_GETITEMTEXT, dwRowId, (DWORD)&lv);
MessageBox(NULL, szPid, TEXT("PID"), MB_OK);
}
//设置ModulesListView风格
VOID InitModulesListView(HWND hDlg)
{
LV_COLUMN lv;
HWND hListModules;
//初始化
memset(&lv, 0, sizeof(LV_COLUMN));
//获取IDC_LIST_MODULE句柄
//GetDlgItem:父窗口的句柄,子窗口的序号
hListModules = GetDlgItem(hDlg, IDC_LIST_MOUDLE);
//设置整行选中
SendMessage(hListModules,LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT,LVS_EX_FULLROWSELECT);
//第一列
lv.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; //列类型
lv.pszText = TEXT("模块名称"); //列标题
lv.cx = 200; //列宽
lv.iSubItem = 0; //第1列
// ListView_InsertColumn(hListModules, 0, &lv);
SendMessage(hListModules,LVM_INSERTCOLUMN,0,(DWORD)&lv); //新增列
//第二列
lv.pszText = TEXT("模块位置");
lv.cx = 200;
lv.iSubItem = 1; //第2列
// ListView_InsertColumn(hListModules, 1, &lv);
SendMessage(hListModules,LVM_INSERTCOLUMN,1,(DWORD)&lv);
}
//消息处理函数
BOOL CALLBACK DialogProc(
HWND hDlg, // handle to dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
)
{
switch(uMsg)
{
//关闭窗口
case WM_CLOSE:
{
EndDialog(hDlg, 0);
break;
}
//主窗口初始化
case WM_INITDIALOG:
{
//设置ProcessListView风格
InitProcessListView(hDlg);
//设置ModulesListView风格
InitModulesListView(hDlg);
}
//通用控件向父窗口发送消息
case WM_NOTIFY:
{
NMHDR* pNMHDR = (NMHDR*)lParam;
if (wParam == IDC_LIST_PROCESS && pNMHDR->code == NM_CLICK)
{
EnumModules(GetDlgItem(hDlg, IDC_LIST_PROCESS), wParam, lParam);
}
break;
}
case WM_COMMAND :
switch (LOWORD (wParam))
{
case IDC_BUTTON_PE:
return TRUE;
case IDC_BUTTON_ABOUT:
{
return TRUE;
}
case IDC_BUTTON_LOGOUT:
{
EndDialog(hDlg, 0);
return TRUE;
}
}
break ;
}
return FALSE ;
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&icex);
// TODO: Place code here.
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG_MAIN), NULL, DialogProc);
return 0;
}
// test2.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#include "resource.h"
#include <commctrl.h>
#pragma comment(lib,"comctl32.lib")
//设置ProcessListView风格
VOID InitProcessListView(HWND hDlg)
{
LV_COLUMN lv;
HWND hListProcess;
//初始化
memset(&lv,0,sizeof(LV_COLUMN));
//获取IDC_LIST_PROCESS句柄
hListProcess = GetDlgItem(hDlg,IDC_LIST_PROCESS);
//设置整行选中
SendMessage(hListProcess,LVM_SETEXTENDEDLISTVIEWSTYLE,LVS_EX_FULLROWSELECT,LVS_EX_FULLROWSELECT);
//第一列
lv.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
lv.pszText = TEXT("进程");//列标题
lv.cx = 150;//列宽
lv.iSubItem = 0;
//ListView_InsertColumn(hListProcess, 0, &lv);
SendMessage(hListProcess,LVM_INSERTCOLUMN,0,(DWORD)&lv);
//第二列
lv.pszText = TEXT("PID");
lv.cx = 90;
lv.iSubItem = 1;
//ListView_InsertColumn(hListProcess, 1, &lv);
SendMessage(hListProcess,LVM_INSERTCOLUMN,1,(DWORD)&lv);
//第三列
lv.pszText = TEXT("镜像基址");
lv.cx = 90;
lv.iSubItem = 2;
ListView_InsertColumn(hListProcess, 2, &lv);
//第四列
lv.pszText = TEXT("镜像大小");
lv.cx = 90;
lv.iSubItem = 3;
ListView_InsertColumn(hListProcess, 3, &lv);
// 遍历进程列表
InitListContentProcess(hwndList);
}
// 遍历进程列表
void InitListContentProcess(hwndList)
{
// 1. 取得所有进程
// 2. 获取进程信息(进程名称, 主模块ImageBase, 主模块ImageSize, 主模块EOP)
// 3. 填充列表数据
//// 1. 获取所有进程ID
//DWORD processIds[1024] = { 0 };
//DWORD dwNumberOfIds = 0;
//if (GetAllProcessId(processIds, sizeof(processIds), &dwNumberOfIds) == FALSE)
//{
//MessageBox(g_hwndMain, TEXT("获取进程列表!"), TEXT("WARRING"), MB_OK);
//return;
//}
HANDLE lpSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (lpSnapshot == INVALID_HANDLE_VALUE)
{
MessageBox(g_hwndMain, TEXT("创建快照失败"), TEXT("ERROR"), MB_OK);
return;
}
PROCESSENTRY32 p32;
p32.dwSize = sizeof(p32);
BOOL pr = Process32First(lpSnapshot, &p32);
// 遍历所有进程
for (int row = 0; pr; row++)
{
// 进程ID
DWORD dwPid = p32.th32ProcessID;
// 进程名
LPTSTR name = p32.szExeFile;
// 主模块信息
MODULEINFO mi = { 0 };
BOOL miResult = FALSE;
miResult = GetMainModuleInfo(dwPid, &mi);
LV_ITEM vitem = { 0 };
vitem.mask = LVIF_TEXT;
vitem.iItem = row;
// 第一列(进程名)
vitem.pszText = name;
vitem.iSubItem = 0;
ListView_InsertItem(hwndList, &vitem);
// 第二列(PID)
TCHAR buffer[16];
wsprintf(buffer, TEXT("%d"), dwPid);
vitem.pszText = buffer;
vitem.iSubItem = 1;
ListView_SetItem(hwndList, &vitem);
if (miResult)
{
// 第三列(主模块基地址)
wsprintf(buffer, TEXT("%p"), mi.lpBaseOfDll);
vitem.pszText = buffer;
vitem.iSubItem = 2;
ListView_SetItem(hwndList, &vitem);
// 第四列(镜像大小)
wsprintf(buffer, TEXT("%p"), mi.SizeOfImage);
vitem.pszText = buffer;
vitem.iSubItem = 3;
ListView_SetItem(hwndList, &vitem);
}
pr = Process32Next(lpSnapshot, &p32);
}
}
//设置ModulesListView风格
VOID InitModulesListView(HWND hDlg)
{
LV_COLUMN lv;
HWND hListModules;
//初始化
memset(&lv, 0, sizeof(LV_COLUMN));
//获取IDC_LIST_MODULE句柄
//GetDlgItem:父窗口的句柄,子窗口的序号
hListModules = GetDlgItem(hDlg, IDC_LIST_MOUDLE);
//设置整行选中
SendMessage(hListModules,LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT,LVS_EX_FULLROWSELECT);
//第一列
lv.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; //列类型
lv.pszText = TEXT("模块名称"); //列标题
lv.cx = 200; //列宽
lv.iSubItem = 0; //第1列
// ListView_InsertColumn(hListModules, 0, &lv);
SendMessage(hListModules,LVM_INSERTCOLUMN,0,(DWORD)&lv); //新增列
//第二列
lv.pszText = TEXT("模块位置");
lv.cx = 200;
lv.iSubItem = 1; //第2列
// ListView_InsertColumn(hListModules, 1, &lv);
SendMessage(hListModules,LVM_INSERTCOLUMN,1,(DWORD)&lv);
}
//消息处理函数
BOOL CALLBACK DialogProc(
HWND hDlg, // handle to dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
)
{
OPENFILENAME = stOpenFile;
switch(uMsg)
{
//关闭窗口
case WM_CLOSE:
{
EndDialog(hDlg, 0);
break;
}
//主窗口初始化
case WM_INITDIALOG:
{
//设置ProcessListView风格
InitProcessListView(hDlg);
//设置ModulesListView风格
InitModulesListView(hDlg);
}
//通用控件向父窗口发送消息
case WM_NOTIFY:
{
NMHDR* pNMHDR = (NMHDR*)lParam;
if (wParam == IDC_LIST_PROCESS && pNMHDR->code == NM_CLICK)
{
EnumModules(GetDlgItem(hDlg, IDC_LIST_PROCESS), wParam, lParam);
}
break;
}
case WM_COMMAND :
switch (LOWORD (wParam))
{
case IDC_BUTTON_ABOUT:
{
return TRUE;
}
case IDC_BUTTON_LOGOUT:
{
EndDialog(hDlg, 0);
return TRUE;
}
//文件选择消息
case IDC_BUTTON_OPEN:
{
TCHAR strPeFileExt[128] = TEXT("PE File(*.exe,*.dll,*.sys)\0*.exe;*.dll*;.sys\0") \
TEXT("All File(*.*)\0*.*\0\0");
TCHAR strFileName[256];
meset(strFileName, 0, 256);
meset(&stOpenFile, 0, sizeof(OPENFILENAME));
stOpenFile.lStructSize = sizeof(OPENFILENAME); //当前结构体大小
stOpenFile.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
stOpenFile.hwndOwner = hDlg;
stOpenFile.lpstrFilter = strPeFileExt; //过滤器,只有符合过滤名的,才会显示出来
stOpenFile.lpstrFile = strFileName;
stOpenFile.nMaxFile = MAX_PATH;
if (GetOpenFileName(&st) == FALSE)
{
SetStaticMessage(TEXT("未选择文件!"));
return;
}
//PVOID g_pFileBuffer = NULL;
//DWORD g_dwFileSize = 0;
if (ReadPeFile(strFileName, &g_pFileBuffer, &g_dwFileSize) == FALSE)
{
SetStaticMessage(TEXT("加载PE文件失败!"));
return;
}
TCHAR buf[1024];
wsprintf(buf, TEXT("已打开文件 %s"), strFileName);
SetStaticMessage(buf);
DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_DIALOG_PE), g_hwndMain, DialogPeProc);
}
}
}
break ;
}
return FALSE ;
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&icex);
// TODO: Place code here.
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG_MAIN), NULL, DialogProc);
return 0;
}
进程
前言
一个进程中,至少有一个线程
线程
前言
表示当前进程中包含了多少线程
线程句柄与线程ID:
线程是由Windows内核负责创建与管理的,句柄相当于一个令牌,有了这个令牌就可以使用线程对象.
线程ID是身份证,唯一的,系统进行线程调度的时候要使用的.
创建线程
注:等待系统分配CPU才可以跑起来
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全属性 通常为NULL
SIZE_T dwStackSize, // 参数用于设定线程可以将多少地址空间用于它自己的堆栈
// 每个线程拥有它自己的堆栈
LPTHREAD_START_ROUTINE lpStartAddress,// 参数用于指明执行的线程函数的地址
LPVOID lpParameter, // 线程函数的参数
// 在线程启动执行时将该参数传递给线程函数
// 既可以是数字,也可以是指向包含其他信息的一个数据结构的指针
DWORD dwCreationFlags,// 0:创建完毕立即调度 CREATE_SUSPENDED:创建后挂起
LPDWORD lpThreadId// 线程ID(out类型参数)
);// 返回值:线程句柄
//::的意思是定义为全局函数
HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
//关闭句柄:线程还在,只是表示系统分配的编号没了
::CloseHandle(hThread);
代码示例
// test00.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <Windows.h>
//线程函数
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
for (int i = 0;i < 1000; i++)
{
Sleep(1000);
printf("%d*************************\n",i);
}
return 0;
}
//线程函数
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
for (int i = 0;i < 1000; i++)
{
Sleep(1000);
printf("%d*************************\n",i);
}
return 0;
}
void Test1()
{
//::的意思是定义为全局函数
HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
//线程还在,只是表示系统分配的编号没了
::CloseHandle(hThread);
}
void Test2()
{
HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
::CloseHandle(hThread);
}
int main(int argc, char* argv[])
{
Test1();
Test2();
for (int i = 0;i < 1000; i++)
{
Sleep(1000); //休息一秒
printf("%d*************************\n",i);
}
return 0;
}
线程数
以任务管理器为例
线程函数&传递变量
全局变量
代码示例
#include "stdafx.h"
#include <windows.h>
//线程函数,就是线程数据 thread data
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
int* p = (int*)lpParameter;
for (int i = 0;i < 1000; i++)
{
Sleep(1000);
printf("**********************%d***\n", *p);
}
return 0;
}
//全局变量
int x = 6;
void Test1()
{
HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc1, (void* )&x, 0, NULL);
::CloseHandle(hThread);
}
int main(int argc, char* argv[])
{
Test1();
for (int i = 0;i < 1000; i++)
{
Sleep(1000);
printf("*************************\n");
}
return 0;
}
线程参数
代码示例
#include "stdafx.h"
#include <windows.h>
//线程函数,就是线程数据 thread data
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
int p = (int)lpParameter;
for (int i = 0;i < 1000; i++)
{
Sleep(1000);
printf("%d*************************\n",p);
}
return 0;
}
void Test2()
{
//局部变量,做线程参数
int x = 6;
HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc2, (void* )x, 0, NULL);
::CloseHandle(hThread);
}
int main(int argc, char* argv[])
{
Test2();
for (int i = 0;i < 1000; i++)
{
Sleep(1000);
printf("*************************\n",i);
}
return 0;
}
总结
线程在代码写完,编译链接完成,正常执行应用程序,期间正常加载至操作系统,形成进程,不做创建线程的操作
默认就是只有一个主线程在工作
若增加线程,那么就相当于同一个事情在同一时间,多个人在一起做
实操
当我们是单线程程序时,点击开始,占了主线程,界面的消息没有cpu去处理
#include "stdafx.h"
#include "resource.h"
HWND hEdit;
int dwNum = 1000;
//线程函数
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
int* p = (int*)lpParameter;
// 获取文本框内容
TCHAR szBuffer[10];
memset(szBuffer, 0, 10);
GetWindowText(hEdit, szBuffer, 10);
// 转成整数
DWORD dwTimer;
sscanf(szBuffer, "%d", &dwTimer);
while(dwTimer > 0)
{
// 转成字符串
memset(szBuffer, 0, 10);
Sleep(1000);
sprintf(szBuffer, "%d", --dwTimer);
// 写回去
SetWindowText(hEdit, szBuffer);
}
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,"1000");
break;
}
case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDC_BUTTON:
{
//创建线程
//HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc, (void*)&dwNum, 0, NULL);
::CloseHandle(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;
}
线程控制
挂起线程
注:当我们挂起线程时,操作系统是不会给它分CPU时间的
::SuspendThread(hThread);
恢复线程
注:Windows操作系统不是一个实时操作系统,在我们恢复线程的时候,看调度程序什么时候给它分CPU
::ResumeThread(hThread);
终止线程
方式一
它是放到线程函数中的,可以使用一个全局变量去判断
::ExitThread(DWORD dwExitCode);
DWORD x = 0;
if(x == 1)
{
::ExitThread(1);
}
dwExitCode:它是退出码,会在Output进行输出,code 1
正常线程执行完毕,return 0
线程是终止的,::ExitThread(1);
特点:会把当前线程的堆栈处理掉,同步调用
同步调用:在同一个线程中,下面的代码是一定不会去执行,返回即代表线程已经结束了
方式二
线程函数返回
表示线程正常结束
特点:资源都是自己去释放
方式三
它是放到Button中的
这种方式是我们告诉操作系统,我们要结束这个线程
::TerminateThread(hThread,2);
特点:堆栈并不会被清理,异步调用(需要一个函数去判断线程是否被关闭了)
异步调用:在起一个线程,去关闭当前线程,两者在不同线程
它做的只是:告诉操作系统一声,把这个线程关闭,并不会去管操作系统是否已经关闭
所以,引出了,等待操作系统关闭线程的函数WaitForSingleObject
::WaitForSingleObject(hThread,INFINITE);
获取结束码
使用GetExitCodeThread
函数
BOOL GetExitCodeThread(
HANDLE hThread,
LPDWORD lpExitCode
);
参数解析
hThread:要结束的线程句柄
dwExitCode:指定线程的退出代码。可以通过GetExitCodeThread来查看一个线程的退出代码
代码示例
// jiayou.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#include "resource.h"
int dwNum = 1000;
HWND hEdit;
HANDLE hThread;
//线程函数
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
int* p = (int*)lpParameter;
// 获取文本框内容
TCHAR szBuffer[10];
memset(szBuffer, 0, 10);
GetWindowText(hEdit, szBuffer, 10);
// 转成整数
DWORD dwTimer;
sscanf(szBuffer, "%d", &dwTimer);
while(dwTimer > 0)
{
// 转成字符串
memset(szBuffer, 0, 10);
Sleep(1000);
sprintf(szBuffer, "%d", --dwTimer);
// 写回去
SetWindowText(hEdit, szBuffer);
}
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,"1000");
break;
}
case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDC_BUTTON_1:
{
//创建线程
HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
//hThread = ::CreateThread(NULL, 0, ThreadProc, (void*)&dwNum, 0, NULL);
::CloseHandle(hThread);
return TRUE;
}
case IDC_BUTTON_2:
{
//挂起线程
::SuspendThread(hThread);
return TRUE;
}
case IDC_BUTTON_3:
{
//恢复线程
::ResumeThread(hThread);
return TRUE;
}
case IDC_BUTTON_4:
{
//终止线程
::TerminateThread(hThread,2);
::WaitForSingleObject(hThread,INFINITE);
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;
}
线程CONTEXT对象
前言
每个线程在执行的时候,操作系统会给他分一个CPU时间片(20毫秒)
但时间到了,但是它的线程没有跑完,要切到另外一个线程了,那么各种寄存器的值是怎么保存的呢?
切回来之后,从哪个地址开始执行呢?各种寄存器的值又是如何恢复的呢?
解决
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.
//
//Debug(调试)寄存器
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
//
BYTEExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;
获取线程CONTEXT结构
//挂起线程
SuspendThread(线程句柄);
CONTEXT context
//设置要获取的类型
context.ContextFlags = CONTEXT_CONTROL;
//获取
BOOL ok = ::GetThreadContext(hThread,&context);
//设置(自己切换线程)
context.Eip = 0x401000;
//写回去
SetThreadContext(hThread,&context);
//恢复线程
::ResumeThread(hThread);
代码示例
// jiayou.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#include "resource.h"
int dwNum = 1000;
HWND hEdit;
HANDLE hThread;
//线程函数
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
int* p = (int*)lpParameter;
// 获取文本框内容
TCHAR szBuffer[10];
memset(szBuffer, 0, 10);
GetWindowText(hEdit, szBuffer, 10);
// 转成整数
DWORD dwTimer;
sscanf(szBuffer, "%d", &dwTimer);
while(dwTimer > 0)
{
// 转成字符串
memset(szBuffer, 0, 10);
Sleep(1000);
sprintf(szBuffer, "%d", --dwTimer);
// 写回去
SetWindowText(hEdit, szBuffer);
}
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,"1000");
break;
}
case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDC_BUTTON_1:
{
//创建线程
HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
//hThread = ::CreateThread(NULL, 0, ThreadProc, (void*)&dwNum, 0, NULL);
::CloseHandle(hThread);
return TRUE;
}
case IDC_BUTTON_2:
{
//挂起线程
::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_BUTTON_3:
{
//恢复线程
::ResumeThread(hThread);
return TRUE;
}
case IDC_BUTTON_4:
{
//终止线程
::TerminateThread(hThread,2);
::WaitForSingleObject(hThread,INFINITE);
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;
}
多线程&全局变量
前言
两个线程会互相抢占
代码示例
它是有问题的
HWND hEdit ;
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
TCHAR szBuffer[10];
DWORD dwIndex = 0;
DWORD dwCount;
while(dwIndex<10)
{
GetWindowText(hEdit,szBuffer,10);
sscanf( szBuffer, "%d", &dwCount );
dwCount++;
memset(szBuffer,0,10);
sprintf(szBuffer,"%d",dwCount);
SetWindowText(hEdit,szBuffer);
dwIndex++;
}
return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
TCHAR szBuffer[10];
DWORD dwIndex = 0;
DWORD dwCount;
while(dwIndex<10)
{
GetWindowText(hEdit,szBuffer,10);
sscanf( szBuffer, "%d", &dwCount );
dwCount++;
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_EDIT1);
SetWindowText(hEdit,"0");
break;
}
case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDC_BUTTON_T1:
{
HANDLE hThread1 = ::CreateThread(NULL, 0, ThreadProc1,
NULL, 0, NULL);
::CloseHandle(hThread1);
return TRUE;
}
case IDC_BUTTON_T2:
{
HANDLE hThread2 = ::CreateThread(NULL, 0, ThreadProc2,
NULL, 0, NULL);
::CloseHandle(hThread2);
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;
}
总结
进程就是4GB空间,线程就是EIP
线程安全
本质
每个线程都会有自己的堆栈,参数,局部变量都会压到堆栈中
但是,全局变量是在全局区
线程安全的本质是:多个线程操作了同一块"资源"
临界区
前言
进行线程调度
示意图
创建CRITICAL_SECTION:
//创建令牌,一个全局的结构体
CRITICAL_SECTION cs;
typedef struct _RTL_CRITICAL_SECTION {
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread;
HANDLE LockSemaphore;
DWORD SpinCount;
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
参数解析:
LockCount:
它被初始化为数值 -1
此数值等于或大于 0 时,表示此临界区被占用
LockCount = 0 表示有1个线程等待获得临界区
LockCount = 1 表示有2个线程等待获得临界区
LockCount = 2 表示有3个线程等待获得临界区
等待获得临界区的线程数:LockCount - (RecursionCount -1)
RecursionCount:
此字段包含所有者线程已经获得该临界区的次数
OwningThread:
此字段包含当前占用此临界区的线程的线程标识符
此线程 ID 与GetCurrentThreadId 所返回的 ID 相同
初始化
//初始化令牌
InitializeCriticalSection(&cs);
函数中使用
DWORD WINAPI 线程A(PVOID pvParam)
{
//获取令牌
EnterCriticalSection(&cs);
//对全局遍历X的操作
//释放令牌
LeaveCriticalSection(&cs);
return(0);
}
DWORD WINAPI 线程B(PVOID pvParam)
{
EnterCriticalSection(&g_cs);
//对全局遍历X的操作
LeaveCriticalSection(&g_cs);
return(0);
}
删除CRITICAL_SECTION
注:当线程不再试图访问共享资源时
//销毁令牌
VOID DeleteCriticalSection(PCRITICAL_SECTION pcs);
实例一
多线程程序,应该将获取令牌和释放令牌放到循环里,只有涉及到全局变量,才会放到临界区
实例二
多线程程序,没有实现多线程,应该把全局变量放到临界区中
实例三
多线程程序,下面的线程没有拿到令牌,就闯进去了
实例四
分析这个程序:
全局变量X
全局变量Y
全局变量Z
线程1
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_cs);
使用X
使用Y
LeaveCriticalSection(&g_cs);
return(0);
}
线程2
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_cs);
使用X
使用Z
LeaveCriticalSection(&g_cs);
return(0);
}
线程3
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_cs);
使用Y
使用X
LeaveCriticalSection(&g_cs);
return(0);
}
这个程序的问题在于:浪费时间
解决方案:针对每一个全局变量,我们要单独去把它放到临界区
CRITICAL_SECTION g_csX;
CRITICAL_SECTION g_csY;
CRITICAL_SECTION g_csZ;
线程1
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_csX);
使用X
LeaveCriticalSection(&g_csX);
EnterCriticalSection(&g_csY);
使用Y
LeaveCriticalSection(&g_csY);
return(0);
}
线程2
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_csX);
使用X
LeaveCriticalSection(&g_csX);
EnterCriticalSection(&g_csZ);
使用Z
LeaveCriticalSection(&g_csZ);
return(0);
}
线程3
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_csX);
使用X
LeaveCriticalSection(&g_csX);
return(0);
}
总结
多个线程不访问全局变量,或者对全局变量是只读的操作,是不需要去管的
多个线程,对全局变量修改
的地方,都要单独放到临界区里
死锁
示意图
一个多线程程序,看CPU切换的时机
线程A: 线程B:
拿A的令牌 拿B的令牌
1、CPU时间片到了 2、CPU时间片到了
拿B的令牌拿A的令牌
还B的令牌还A的令牌
还A的令牌 还B的令牌
总结
避免死锁:
1、每个线程函数中,获取令牌的顺序一致
2、尽量不要嵌套,拿一个还一个
WaitForSingleObject
前言
它是一个等待函数,当我们调用它的时候,操作系统处于阻塞状态(一直在循环等着)
结构
DWORD WaitForSingleObject(
HANDLE hHandle,// 内核对象的句柄
DWORD dwMilliseconds
);
参数说明
hHandle:内核对象的句柄,不同的内核对象,处理方式不同
dwMilliseconds:等待时间,单位是毫秒 INFINITE(-1)一直等待
往下执行的两种情况:
1、等待对象变为已通知
2、超时
至于是哪一种情况,可以通过返回值的宏去判断
返回值:
WAIT_OBJECT_0(0)等待对象变为已通知
WAIT_TIMEOUT(0x102) 超时
功能说明
等待函数可使线程自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止
特别说明
1、内核对象中的每种对象都可以说是处于已通知或未通知的状态之中
2、这种状态的切换是由Microsoft为每个对象建立的一套规则来决定的
3、当线程正在运行的时候,线程内核对象处于未通知状态
4、当线程终止运行的时候,它就变为已通知状态
5、在内核中就是个BOOL值,运行时FALSE 结束TRUE
代码示例
#include "stdafx.h"
#include <windows.h>
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
for(int i=0;i<5;i++)
{
printf("+++++++++\n");
Sleep(1000);
}
return 0;
}
int main(int argc, char* argv[])
{
//创建一个新的线程
HANDLE hThread1 = ::CreateThread(NULL, 0, ThreadProc1,
NULL, 0, NULL);
//等待函数
DWORD dwCode = ::WaitForSingleObject(hThread1, INFINITE);
MessageBox(0,0,0,0);
return 0;
}
可以通过看dwCode判断等待函数是如何结束的
可以看到它是正常结束的
超时结束的
CloseHandle
注意
这个函数是用来关闭关闭内核对象
内核对象是操作系统替我们创建的,保存在高2G的内存中
内核对象总结:线程、互斥体
1、当我们程序执行完后,我们没有主动关闭内核对象(CloseHandle),但是操作系统会替我们关闭内核对象
2、当我们程序正在运行时,我们没有主动关闭内核对象(CloseHandle),会有内核对象泄露的风险
WaitForMultipleObjects
前言
它也是一个等待函数,等待多个线程,使用数组保存多个内核对象
结构
DWORD WaitForMultipleObjects(
DWORD nCount,
CONST HANDLE *lpHandles,
BOOL bWaitAll,
DWORD dwMilliseconds
);
参数解析
nCount:要查看内核对象的数量
lpHandles:内核对象数组
bWaitAll:等待类型
TRUE:等待所有线程变为已通知
FALSE:只要有一个线程变为已通知
dwMilliseconds:超时时间(INFINITE一直等待)
返回值:
bWaitAll:TRUE时,返回WAIT_OBJECT_0(0)代码所有内核对象都变成已通知
bWaitAll:FALSE时,返回最先变成已通知的内核对象在数组中的索引
第一个线程变成已通知,返回0
第二个线程变成已通知,返回1
WAIT_TIMEOUT(0x102),超时
功能说明
同时查看若干个内核对象的已通知状态
代码示例
#include "stdafx.h"
#include <windows.h>
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
for(int i=0;i<5;i++)
{
printf("+++++++++\n");
Sleep(1000);
}
return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
for(int i=0;i<3;i++)
{
printf("---------\n");
Sleep(1000);
}
return 0;
}
int main(int argc, char* argv[])
{
HANDLE hArray[2];
//创建第一个线程
hArray[0] = ::CreateThread(NULL, 0, ThreadProc1,
NULL, 0, NULL);
//创建第二个线程
hArray[1] = ::CreateThread(NULL, 0, ThreadProc2,
NULL, 0, NULL);
//等待多个函数
DWORD dwCode = ::WaitForMultipleObjects(2, hArray,FALSE,INFINITE);
MessageBox(0,0,0,0);
return 0;
}
互斥体
创建互斥体
使用CreateMutex
查看MSDN
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // 权限控制,一般传NULL即可
BOOL bInitialOwner, // initial owner
LPCTSTR lpName// 互斥体的名字
);
注:有权限控制的对象即为内核对象
打开互斥体
在B进程中,得到A进程创建的互斥体
使用OpenMutex
函数
HANDLE OpenMutex(
DWORD dwDesiredAccess, // 权限控制
BOOL bInheritHandle,// 是否被继承
LPCTSTR lpName // 互斥体的名字
);
进入&退出互斥体
进入:
WaitForSingleObject(g_hMutex,INFINITE);
离开:
ReleaseMutex(g_hMutex);
特点
互斥体可以,跨进程的,对两个进程的线程,进行互斥控制
互斥体&临界区
1、临界区只能用于单个进程间的线程控制
2、互斥体可以设定等待超时,但临界区不能
3、线程意外终结时,互斥体可以避免无限等待
4、互斥体效率没有临界区高,因为它跨进程了
内核对象
前言
常见的内核对象有:进程、线程、文件、文件映射、事件、互斥体等等
一旦创建,即是在高2G的内存
示意图
内核对象的创建
//事件内核对象
HANDLE g_hEvent = CreateEvent(NULL, TRUE, FALSE, "XYZ");
//互斥体内核对象
HANDLE g_hMutex = CreateMutex(NULL,FALSE, "XYZ");
我们可以这么理解:
g_hMutex
它是一个指针的指针,它这个值很小,是操作系统给的编号
为了安全考虑,避免非法的手段修改内核对象
内核对象的获取
HANDLE OpenEvent(
DWORD dwDesiredAccess, // access
BOOL bInheritHandle,// inheritance option
LPCTSTR lpName // object name
);
HANDLE g_hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, "XYZ");
HANDLE g_hMutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE, "XYZ");
内核对象的销毁
注意:CloseHandle
函数其实它是关闭句柄,内核对象的销毁其实未必
BOOL CloseHandle(HANDLE hobj);
(1)、当没有其他程序引用时,系统会销毁内核对象(使用数量)
(2)、内核对象的生命周期,可能比创建它的对象要长
内核对象的生命周期
内核对象在创建出来之前,在高2G会有一个结构体
结构体中有一个成员,它是计数器,初始是0
HANDLE g_hEvent = CreateEvent(NULL, TRUE, FALSE, "XYZ"); //计数器+1
HANDLE g_hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, "XYZ"); //计数器+1
然后我们调用CloseHandle
函数,关闭互斥体的句柄
计数器(2)-1=1,所以当前的内核对象并不会被销毁
内核对象的生命周期比创建内核的程序,时间要长
事件对象
事件对象的创建
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性 NULL时为系统默认
BOOL bManualReset,
//TRUE:通过调用ResetEvent将事件对象标记为未通知
//FALSE:调用它的时候,自动变成未通知状态
//简单理解:是否自动复位
BOOL bInitialState, // TRUE:已通知状态 FALSE:未通知状态
LPCTSTR lpName // 对象名称,跨进程时使用它的对象,否则写NULL
);
事件对象的控制
BOOL SetEvent(HANDLE hEvent); //将对象设置为已通知
关闭事件对象句柄
CloseHandle(); //关闭句柄
代码示例
// test6.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#include "resource.h"
HANDLE g_hEvent;
HWND hEdit1;
HWND hEdit2;
HWND hEdit3;
HWND hEdit4;
HANDLE hThread1;
HANDLE hThread2;
HANDLE hThread3;
HANDLE hThread4;
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
TCHAR szBuffer[10] = {0};
//当事件变成已通知时
WaitForSingleObject(g_hEvent, INFINITE);
//读取内容
GetWindowText(hEdit1,szBuffer,10);
SetWindowText(hEdit2,szBuffer);
return 0;
}
DWORD WINAPI ThreadProc3(LPVOID lpParameter)
{
TCHAR szBuffer[10] = {0};
//当事件变成已通知时
WaitForSingleObject(g_hEvent, INFINITE);
//读取内容
GetWindowText(hEdit1,szBuffer,10);
SetWindowText(hEdit3,szBuffer);
return 0;
}
DWORD WINAPI ThreadProc4(LPVOID lpParameter)
{
TCHAR szBuffer[10] = {0};
//当事件变成已通知时
WaitForSingleObject(g_hEvent, INFINITE);
//读取内容
GetWindowText(hEdit1,szBuffer,10);
SetWindowText(hEdit4,szBuffer);
return 0;
}
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
//创建事件
//默认安全属性 手动设置未通知状态(TRUE) 初始状态未通知 没有名字
g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
HANDLE hThread[3];
//创建3个线程
hThread[0] = ::CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
hThread[1] = ::CreateThread(NULL, 0, ThreadProc3, NULL, 0, NULL);
hThread[2] = ::CreateThread(NULL, 0, ThreadProc4, NULL, 0, NULL);
//设置文本框的值
SetWindowText(hEdit1,"1000");
//设置事件为已通知
//三个线程全处于可调度状态
SetEvent(g_hEvent);
//等待线程结束 销毁内核对象
WaitForMultipleObjects(3, hThread, TRUE, INFINITE);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
CloseHandle(hThread[2]);
CloseHandle(g_hEvent);
return 0;
}
//消息处理函数
BOOL CALLBACK DialogProc(
HWND hDlg, // handle to dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
)
{
switch(uMsg)
{
case WM_INITDIALOG :
hEdit1 = GetDlgItem(hDlg,IDC_EDIT1);
hEdit2 = GetDlgItem(hDlg,IDC_EDIT2);
hEdit3 = GetDlgItem(hDlg,IDC_EDIT3);
hEdit4 = GetDlgItem(hDlg,IDC_EDIT4);
//设置文本框的值
SetWindowText(hEdit1,"0");
SetWindowText(hEdit2,"0");
SetWindowText(hEdit3,"0");
SetWindowText(hEdit4,"0");
break;
case WM_COMMAND :
switch (LOWORD (wParam))
{
caseIDC_BUTTON_BEGIN:
{
::CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
return TRUE;
}
}
break ;
}
return FALSE ;
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG_MAIN), NULL, DialogProc);
return 0;
}
我们发现三个线程都执行了
原因在于我们创建事件的第二个参数
g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
TRUE:通过调用ResetEvent将事件对象标记为未通知
而当我们SetEvent(g_hEvent);
,三个线程全处于可调度状态
在每个线程执行过程中,我们并没有手动将它改回未通知状态
第二个线程执行,它还是已通知状态
第三个线程执行,它还是已通知状态
所以三个线程都执行了
我们创建事件的第二个参数改为FALSE时
g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
FALSE:调用它的时候,自动变成未通知状态
所以只要有一个线程执行了,它就变成未通知状态了
线程是由操作系统去调度的,所以会执行一个线程,其他两个线程都在阻塞
我们若是想实现一个一个去调度
首先创建事件的第二个参数改成FALSE
然后在每个线程调度完成之后,把它的状态改为已通知即可
SetEvent(g_hEvent);
总结
线程同步:线程的先后顺序(类比生产者线程和消费者线程)
事件对象
线程互斥:多个线程对同一个资源进行访问的时候,我们要保证某一个时刻,只能有一个线程对资源进行操作
临界区(一个进程)、互斥体(内核对象-->性能较差,因为它要先进到r0、可跨进程)、事件对象
线程同步
前言
可以用一个全局变量去控制线程同步,但是效率很低
事件&线程同步
前言
事件可以做一些简单的线程同步
主要是利用创建事件时的,第二个和第三个参数
代码示例
HANDLE g_hSet, g_hClear;
int g_Max = 10;
int g_Number = 0;
//生产者线程函数
DWORD WINAPI ThreadProduct(LPVOID pM)
{
for (int i = 0; i < g_Max; i++)
{
WaitForSingleObject(g_hSet, INFINITE);
g_Number = 1;
DWORD id = GetCurrentThreadId();
printf("生产者%d将数据%d放入缓冲区\n",id, g_Number);
Sleep(1000);
//修改消费者为已通知状态
SetEvent(g_hClear);
}
return 0;
}
//消费者线程函数
DWORD WINAPI ThreadConsumer(LPVOID pM)
{
for (int i = 0; i < g_Max; i++)
{
WaitForSingleObject(g_hClear, INFINITE);
g_Number = 0;
DWORD id = GetCurrentThreadId();
printf("----消费者%d将数据%d放入缓冲区\n",id, g_Number);
Sleep(1000);
//修改生产者为已通知状态
SetEvent(g_hSet);
}
return 0;
}
int main(int argc, char* argv[])
{
HANDLE hThread[2];
//第二个参数(FALSE):调用完它的时候,自动变成未通知状态,不抢占CPU
//第三个参数(FALSE):创建事件即为已通知状态,所以生产者事件肯定先执行
g_hSet = CreateEvent(NULL, FALSE, TRUE, NULL);
//第三个参数:创建事件为未通知状态,所以消费者事件肯定后执行
g_hClear = CreateEvent(NULL, FALSE, FALSE, NULL);
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(g_hSet);
CloseHandle(g_hClear);
return 0;
}
编译时,有一个bug
LIBCD.lib(wincrt0.obj) : error LNK2001: unresolved external symbol _WinMain@16
Debug/memset.exe : fatal error LNK1120: 1 unresolved externals
Error executing link.exe.
Project->Settings->Link->Project Options下
将/subsystem:windows
修改为/subsystem:console
kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /incremental:yes /pdb:"Debug/m.pdb" /debug /machine:I386 /out:"Debug/m.exe" /pdbtype:sept
重新编译即可
特点
当A线程处于未通知状态时,操作系统不会给你分CPU时间,不会浪费资源,效率较高
信号量
前言
它也是内核对象,要去控制想有几个线程,就有几个线程在跑
创建信号量
使用CreateSemaphore
函数
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount,
LONG lMaximumCount,
LPCTSTR lpName
);
参数解析:
第一个参数表示安全控制,一般直接传入NULL
第二个参数表示初始资源数量。0时不发送信号
第三个参数表示最大并发数量。lInitialCount<=lMaximumCount
第四个参数表示信号量的名称,可以跨进程获取,传入NULL表示匿名信号量
打开信号量
在B进程中,得到A进程创建的信号量
使用OpenSemaphore
函数
HANDLE OpenSemaphore(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName
);
参数解析:
第一个参数表示访问权限,对一般传入SEMAPHORE_ALL_ACCESS。详细解释可以查看MSDN文档
第二个参数表示信号量句柄继承性,一般传入FALSE即可
第三个参数表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个信号量
递增信号量&当前资源计数
调用它可以让当前的资源数+1
使用ReleaseSemaphore
函数
BOOL ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount,
LPLONG lpPreviousCount
);
参数解析:
第一个参数是信号量的句柄
第二个参数表示增加个数,必须大于0且不超过最大资源数量
第三个参数返回当前资源数量的原始值,设为NULL表示不需要传出
注:没有一个函数可以用来查询信标的当前资源数量的值
代码示例
#include "stdafx.h"
#include "resource.h"
#include <stdio.h>
HINSTANCE hAppinstance;
HANDLE hSemaphore;
HANDLE hThread[3];
HWND hEditSet;
HWND hEdit1;
HWND hEdit2;
HWND hEdit3;
// 因为有3个线程函数,所以定义一个长度为3的窗口句柄数组
//用数组去封装
HWND hArray[3];
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
TCHAR szBuffer[10];
DWORD dwTimmer=0;
WaitForSingleObject(hSemaphore, INFINITE);
//接收传进来的值,强转一下
DWORD dwIndex = (DWORD)lpParameter;
while(dwTimmer<100)
{
Sleep(100);
memset(szBuffer, 0, 10);
GetWindowText(hArray[dwIndex], szBuffer, 10);
sscanf( szBuffer, "%d", &dwTimmer );
dwTimmer++;
memset(szBuffer,0,10);
sprintf(szBuffer,"%d",dwTimmer);
SetWindowText(hArray[dwIndex],szBuffer);
}
ReleaseSemaphore(hSemaphore, 1, NULL); // 设置当前的信号量
return 0;
}
DWORD WINAPI ThreadBegin(LPVOID lpParameter)
{
TCHAR szBuffer[10];
DWORD dwMoney=0;
//创建信号量
//初始是0,创建之后不发送信号量
//最多可以有3个线程同时跑
//第二个参数<=第三个参数
hSemaphore = CreateSemaphore(NULL, 0, 3, NULL);
//使用一份相同的代码创建3个线程,下面的第四个参数代表传递的是数组的下标
//传参的时候要进行强转
hThread[0] = ::CreateThread(NULL, 0, ThreadProc1, (void*)0, 0, NULL);
hThread[1] = ::CreateThread(NULL, 0, ThreadProc1, (void*)1, 0, NULL);
hThread[2] = ::CreateThread(NULL, 0, ThreadProc1, (void*)2, 0, NULL);
//开始准备红包
while(dwMoney<1000)
{
//Sleep(50);
memset(szBuffer, 0, 10);
GetWindowText(hEditSet, szBuffer, 10);
sscanf( szBuffer, "%d", &dwMoney );
dwMoney++;
memset(szBuffer, 0, 10);
sprintf(szBuffer, "%d", dwMoney);
SetWindowText(hEditSet, szBuffer);
}
//开始发信号量
//3:允许有3个线程同时跑
ReleaseSemaphore(hSemaphore, 3, NULL);
::WaitForMultipleObjects(3, hThread, TRUE, INFINITE);
//关闭三个线程
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
CloseHandle(hThread[2]);
//关闭信号量
::CloseHandle(hSemaphore);
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:
{
hEditSet = GetDlgItem(hDlg, IDC_EDIT1);
hEdit1 = GetDlgItem(hDlg, IDC_EDIT2);
hEdit2 = GetDlgItem(hDlg, IDC_EDIT3);
hEdit3 = GetDlgItem(hDlg, IDC_EDIT4);
SetWindowText(hEditSet, "0");
SetWindowText(hEdit1, "0");
SetWindowText(hEdit2, "0");
SetWindowText(hEdit3, "0");
hArray[0] = hEdit2;
hArray[1] = hEdit3;
hArray[2] = hEdit4;
break;
}
case WM_COMMAND:
{
switch (LOWORD (wParam))
{
case IDC_BUTTON_BEGIN:
{
CreateThread(NULL, 0, ThreadBegin, NULL, 0, NULL);
return TRUE;
}
}
break;
}
break;
}
return bRet;
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
hAppinstance = hInstance;
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG_MAIN), NULL, MainDlgProc);
return 0;
}
总结
线程互斥
前言
当多个线程访问同一个全局变量,或者同一个资源(比如打印机)的时候,需要进行线程间的互斥操作,来保证访问的安全性.
临界区&互斥体
各自特点
临界区:
初始化:Initialize-CriticalSection
进入互斥区域:Enter-CriticalSection
离开互斥区域:Leave-CriticalSection
销毁:DeleteCriticalSection
互斥体:
初始化:CreateMutex
进入互斥区域:WaitForSingleObject
离开互斥区域:ReleaseMutex
销毁:CloseHandle
区别
1、临界区只能用于进程内的线程互斥,性能较好
2、互斥体属于内核对象,可以用于进程间的线程互斥,性能较差.
3、线程意外终究时,互斥体可以正常执行,因为它是内核对象,内核可以检测到当前线程已经结束了
线程同步
前言
当有多个线程同时执行时,可能需要线程按照一定的顺序执行
事件&信号量
各自特点
事件:
创建 使事件进入触发状态 使事件进入未触发状态 销毁
CreateEvent SetEvent ResetEventCloseHandle
信号量:
创建 递减计数递增计数销毁
CreateSemaphore WaitForSingleObject ReleaseSemaphoreCloseHandle
区别
1、都是内核对象,使用完毕后应该关闭句柄
2、信号量可以用于相当复杂的线程同步控制
进程
进程的创建步骤
步骤一:
当系统启动后,创建一个进程,Explorer.exe
-->是桌面进程
步骤二:
当用户双击某一个EXE时,Explorer 进程使用CreateProcess函数创建被双击的EXE进程
简单来说:我们在桌面上双击创建的进程都是Explorer进程的子进程
使用XueTr.exe
我们可以找到
explore.exe 进程ID:1548 父进程ID:1484
注:它的父进程,创建完explore.exe
后就被终结了
父进程挂了,子进程依然可以执行
当我们在桌面启动一个exe
可以看到它的父进程ID就是explore.exe
进程的创建过程
一、创建内核对象
注:句柄表是为了保证内核对象的安全
二、分配4GB的虚拟空间(Win2位)
三、创建进程的主线程
当进程的空间创建完毕,EXE与导入表中的DLL都正确加载完毕后,会创建一个线程
当线程得到CPU的时候,程序就正开始指向了,EIP的初始值设定为:ImageBase+OEP
HANDLE CreateThread(
PSECURITY_ATTRIBUTES psa,
DWORD cbStack,
PTHREAD_START_ROUTINE pfnStartAddr,
PVOID pvParam,
DWORD fdwCreate,
PDWORD pdwThreadID);
当进程创建成功后,会将进程句柄、主线程句柄、进程ID以及主线程ID存储在CreateProcess的最后一个 OUT 参数
typedef struct _PROCESS_INFORMATION
{
HANDLE hProcess; //进程句柄
HANDLE hThread; //主线程句柄
DWORD dwProcessId; //进程ID
DWORD dwThreadId;//线程ID
} PROCESS_INFORMATION;
参数解析
我们要得到的是:创建新进程的句柄、ID,主线程的句柄
BOOL CreateProcess(
PCTSTR pszApplicationName, //应用程序名(常量字符串)
PTSTR pszCommandLine, //控制行参数
PSECURITY_ATTRIBUTES psaProcess,
PSECURITY_ATTRIBUTES psaThread,
BOOL bInheritHandles, //是否允许当前进程继承父进程中,允许被继承的句柄
DWORD fdwCreate,
PVOID pvEnvironment,
PCTSTR pszCurDir,
PSTARTUPINFO psiStartInfo, //out类型参数,应用程序的状态
PPROCESS_INFORMATION ppiProcInfo //out类型参数,这个结构体是用来接收进程句柄、主线程句柄、进程ID、线程ID
);
PSTARTUPINFO psiStartInfo
参数的结构体
我们只需要给第一个参数赋值即可
typedef struct _STARTUPINFO
{
DWORD cb; //当前结构体的大小
PSTR lpReserved;
PSTR lpDesktop;
PSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
PBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;
PPROCESS_INFORMATION ppiProcInfo
参数的结构体
typedef struct _PROCESS_INFORMATION
{
HANDLE hProcess; //进程句柄
HANDLE hThread; //主线程句柄
DWORD dwProcessId; //进程ID
DWORD dwThreadId;//线程ID
} PROCESS_INFORMATION;
可以有三种方式去传递
第一种方式:
使用第一个参数
特点:需要绝对路径
TCHAR szApplicationName[] =TEXT("c://program files//internet explorer//iexplore.exe");
BOOL res = CreateProcess(
szApplicationName,
NULL,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL, &si, &pi);
第二种方式
使用第二个参数
特点:
1、可以直接传入参数,前提是exe可以接收这个参数
2、可以写相对路径iexplore
,但是它是不安全的,它会默认给加上一个.exe
-->iexplore.exe
查找顺序:当前程序的目录-->系统环境变量-->操作系统目录
都没有找见的话,创建失败
TCHAR szCmdline[] =TEXT("c://program files//internet explorer//iexplore.exe http://www.baidu.com");
BOOL res = CreateProcess(
NULL,
szCmdline,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL, &si, &pi);
第三种方式:
组合使用
注意:第二个参数进行传参,记得有一个空格
TCHAR szCmdline[] =TEXT(" http://www.baidu.com");
BOOL res = CreateProcess(
TEXT("c://program files//internet explorer//iexplore.exe"),
szCmdline,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL, &si, &pi);
句柄&ID
1、都是系统分配的一个编号,句柄是客户程序
使用、ID主要是系统调度
时使用
2、调用CloseHandle关闭进程或者线程句柄的时候,只是让内核计数器减少一个,并不是终止进程或者线程
进程或线程将继续运行,直到它自己终止运行
3、进程ID和线程ID并非是永久性控制
进程ID与线程ID是不可能相同。但不要通过进程或者线程的ID来操作进程或者线程,因为,这个编号是会重复使用的,也就是说,当你通过ID=100这个编号去访问一个进程的时候,它已经结束了,而且系统将这个编号赋给了另外一个进程或者线程
程序挂了之后,编号会重复使用
进程的终止
整个过程
1、进程中剩余的所有线程全部终止运行
2、进程指定的所有用户对象均被释放(应用层),所有内核对象均被关闭(内核层)
3、进程内核对象的状态未通知-->已通知的状态
4、进程内核对象的使用计数递减1
三种方式
1、VOID ExitProcess(UINT fuExitCode) //进程自己调用
2、BOOL TerminateProcess(HANDLE hProcess, UINT fuExitCode); //终止其他进程
3、ExitThread//终止进程中的所有线程,进程也会终止
退出码
用来获取进程是如何退出的
BOOL GetExitCodeProcess(HANDLE hProcess,PDWORD pdwExitCode);
句柄的继承
前言
可以实现,不同的进程拥有相同的内核对象
代码示例
进程A的代码
// test1.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
int main(int argc, char* argv[])
{
char szBuffer[256] = {0};
char szHandle[8] = {0};
//若要创建能继承的句柄,父进程必须指定一个SECURITY_ATTRIBUTES(安全属性)结构并对它进行初始化
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa); //结构体大小
sa.lpSecurityDescriptor = NULL; //默认安全属性
sa.bInheritHandle = TRUE; //是否可以继承
//创建一个可以被继承的内核对象
//注意第一个参数是:安全属性
//我们手动创建一个安全属性的结构体
//当我们填NULL:表示系统默认,句柄表中填的就是0,不能继承
HANDLE g_hEvent = CreateEvent(&sa, TRUE, FALSE, NULL);
//转换成字符串,作为命令行参数
sprintf(szHandle,"%x",g_hEvent);
sprintf(szBuffer,"C:/test2.exe %s",szHandle);
//定义创建进程需要用的结构体
STARTUPINFO si = {0};
PROCESS_INFORMATION pi;
si.cb = sizeof(si);
//创建子进程,注意第5个参数:设置子进程可以进程父进程中句柄表为1复制到子进程句柄表
BOOL res = CreateProcess(
NULL,
szBuffer,
NULL,
NULL,
TRUE, //TRUE的时候,说明当前进程可以继承父进程的句柄表,复制父进程中句柄表为1复制到当前进程句柄表
CREATE_NEW_CONSOLE,
NULL,
NULL, &si, &pi);
//设置事件为已通知
SetEvent(g_hEvent);
//关闭句柄,内核对象并不会被销毁
CloseHandle(g_hEvent);
return 0;
}
进程B的代码
注:将进程B的代码编译链接生成的exe文件为test2.exe
将其复制到目录:C:/test2.exe
// test2.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
int main(int argc, char* argv[])
{
char szBuffer[256] = {0};
//argv[0]:当前内存的地址
//argv[1]:传入的命令行参数
memcpy(szBuffer,argv[1],8);
DWORD dwHandle = 0;
//字符串转成DWORD
sscanf(szBuffer,"%x",&dwHandle);
printf("%s\n",argv[0]);
printf("%x\n",dwHandle);
//重新转成句柄
//编号拿过来之后,重新转型就可以用了,因为它已经继承过来了,句柄表中是有的
HANDLE g_hEvent = (HANDLE)dwHandle;
printf("开始等待.....\n");
//当事件变成已通知时
WaitForSingleObject(g_hEvent, INFINITE);
DWORD dwCode = GetLastError();
printf("等到消息.....%x\n",dwCode);
getchar();
return 0;
}
断点调试
现在子进程已经创建了
断到这里了
F10单步将事件设为已通知
可以看到成功继承
挂起&创建进程
前言
空间分了,但是内容还没有给
参数分析
注意第三、第四个参数
BOOL CreateProcess(
LPCTSTR lpApplicationName, //应用程序名(常量字符串)
LPTSTR lpCommandLine, //控制行参数
LPSECURITY_ATTRIBUTES lpProcessAttributes, //当前进程的子进程是否可以继承刚刚创建的这个进程的进程句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, //当前进程的子进程是否可以继承刚刚创建的这个进程的线程的句柄
BOOL bInheritHandles, //是否允许当前进程继承父进程中,允许被继承的句柄
DWORD dwCreationFlags, // creation flags
LPVOID lpEnvironment,
//传入CREATE_NEW_CONSOLE:表示子进程和父进程都有自己独立的控制台
//传入NULL:子进程将自己输出的信息打印到父进程
LPCTSTR lpCurrentDirectory,
//进程的当前目录,当创建子进程时,传入NULL,则子进程获取当前目录将获取父进程的目录
LPSTARTUPINFO lpStartupInfo, //out类型参数,应用程序的状态
LPPROCESS_INFORMATION lpProcessInformation //out类型参数,这个结构体是用来接收进程句柄、主线程句柄、进程ID、线程ID
);
代码示例
单进程代码示例
// test2.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
int main(int argc, char* argv[])
{
STARTUPINFO ie_si = {0};
PROCESS_INFORMATION ie_pi;
ie_si.cb = sizeof(ie_si);
TCHAR szBuffer[256] = "C:\\notepad.exe";
//挂起进程
CreateProcess(
NULL,
szBuffer,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&ie_si,
&ie_pi
);
//恢复执行
ResumeThread(ie_pi.hThread);
return 0;
}
两个进程联动示例
进程A的代码
// test1.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
int main(int argc, char* argv[])
{
char szBuffer[256] = {0};
char szHandle[8] = {0};
SECURITY_ATTRIBUTES ie_sa_p;
ie_sa_p.nLength = sizeof(ie_sa_p);
ie_sa_p.lpSecurityDescriptor = NULL;
ie_sa_p.bInheritHandle = TRUE;
SECURITY_ATTRIBUTES ie_sa_t;
ie_sa_t.nLength = sizeof(ie_sa_t);
ie_sa_t.lpSecurityDescriptor = NULL;
ie_sa_t.bInheritHandle = TRUE;
//创建一个可以被继承的内核对象,此处是个进程
STARTUPINFO ie_si = {0};
PROCESS_INFORMATION ie_pi;
ie_si.cb = sizeof(ie_si);
TCHAR szCmdline[] =TEXT("c://program files//internet explorer//iexplore.exe");
//创建一个进程
//看第三个参数和第四个参数
/*
句柄表中:
X 进程内核对象 1
Y 线程内核对象 1
*/
CreateProcess(
NULL,
szCmdline,
&ie_sa_p, //设置进程可被继承的安全属性 ie_sa_p.bInheritHandle = TRUE;
&ie_sa_t, //设置线程可被继承的安全属性 ie_sa_t.bInheritHandle = TRUE;
TRUE,
CREATE_NEW_CONSOLE,
NULL,
NULL, &ie_si, &ie_pi);
//进程句柄、线程句柄,命令行参数传进来
sprintf(szHandle,"%x %x",ie_pi.hProcess,ie_pi.hThread);
sprintf(szBuffer,"C:/z2.exe %s",szHandle);
//定义创建进程需要用的结构体
STARTUPINFO si = {0};
PROCESS_INFORMATION pi;
si.cb = sizeof(si);
//创建子进程
//注意第5个参数:说明当前进程可以继承父进程的句柄表,复制父进程中句柄表为1复制到当前进程句柄表
BOOL res = CreateProcess(
NULL,
szBuffer,
NULL,
NULL,
TRUE,
CREATE_NEW_CONSOLE,
NULL,
NULL, &si, &pi);
return 0;
}
进程B的代码
// test2.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
int main(int argc, char* argv[])
{
//进程句柄
DWORD dwProcessHandle = -1;
//线程句柄
DWORD dwThreadHandle = -1;
char szBuffer[256] = {0};
//argv[0]:当前内存的地址
//argv[1]:传入的命令行参数
//继承过来的进程ID给dwProcessHandle
memcpy(szBuffer,argv[1],8);
sscanf(szBuffer,"%x",&dwProcessHandle);
//继承过来的线程ID给dwThreadHandle
memset(szBuffer,0,256);
memcpy(szBuffer,argv[2],8);
sscanf(szBuffer,"%x",&dwThreadHandle);
printf("获取IE进程、主线程句柄\n");
Sleep(10000);
//挂起主线程
printf("挂起主线程\n");
::SuspendThread((HANDLE)dwThreadHandle);
Sleep(10000);
//恢复主线程
::ResumeThread((HANDLE)dwThreadHandle);
printf("恢复主线程\n");
Sleep(10000);
//关闭浏览器ID进程
::TerminateProcess((HANDLE)dwProcessHandle,1);
::WaitForSingleObject((HANDLE)dwProcessHandle, INFINITE);
printf("ID进程已经关闭.....\n");
return 0;
}
总结
进行应用:
1、挂起的方式创建进程,获取进程的ImageBase和AddressOfEntryPoint
// test2.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
int main(int argc, char* argv[])
{
STARTUPINFO ie_si = {0};
PROCESS_INFORMATION ie_pi;
ie_si.cb = sizeof(ie_si);
//以挂起的方式创建进程
TCHAR szBuffer[256] = "C:\\notepad.exe";
CreateProcess(
NULL,// name of executable module
szBuffer,// command line string
NULL,// SD
NULL,// SD
FALSE, // handle inheritance option
CREATE_SUSPENDED,// creation flags
NULL,// new environment block
NULL,// current directory name
&ie_si, // startup information
&ie_pi // process information
);
CONTEXT contx;
//获取其他所有的寄存器
contx.ContextFlags = CONTEXT_FULL;
//获取主线程的上下文对象,首先要知道主线程的句柄
GetThreadContext(ie_pi.hThread, &contx);
//获取入口点
DWORD dwEntryPoint = contx.Eax;
//获取ImageBase
//contx.Ebx+8,是一个地址,它里面存储的值才是ImageBase
//注:这个地址它是notepad.exe的地址,不是我们程序本身的地址
char* baseAddress = (CHAR *) contx.Ebx+8;
memset(szBuffer,0,256);
//读其他进程的ImageBase
ReadProcessMemory(ie_pi.hProcess,baseAddress,szBuffer,4,NULL);
ResumeThread(ie_pi.hThread);
return 0;
}
2、修改外壳程序的内容,注意要修改程序的ImageBase和入口点
3、将恶意程序拉伸,替换外壳程序
4、恢复执行
- 本文作者: 略略略
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/1402
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!