在本专栏上一篇文章中带领大家学习了对话框的创建,并且在项目中创建出了对话框。在这一篇文章中,我将带领大家学习Win32通用控件,了解_WM_NOTIFY
消息,并且带领大家初步写出课程中加载Windows所有进程的应用程序的雏形,带领大家详解其中的原理。
Windows中有很多控件,有一些控件是我们不需要加载的,使用频率很高我们称为标准控件,有一些使用频率不是很高,我们在使用的时候需要加载,我们称为通用控件。
标准控件:
Static
Group Box
Button
CHeck Box
Radio Box
Edit
ComboBox
ListBox
Windows通用控件,包含在commctrl32.dll
中,使用前需要:
#include
#pragma comment(lib,"comctl32.lib")
通用控件包括:
Animation
ComboBoxEx
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
Trackbar
TreeView
Up_and_Down
特别说明:
通过用控件在使用前,需要通过INITCOMMONCONTROLSEX
进行初始化。 只要在你的程序中任意地方使用了该函数,就会使得Windows的程序加载器PE Loader加载该库。
INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_WIN95_CLASSES;
InitCOmmonControlsEx(&icex);
MSDN官方文档;WM_INITDIALOG消息
#define WM_INITDIALOG 0x0110
发送时间:在对话框显示之前,发送到对话框回调函数。
参数:
· wParem:用于接收默认键盘焦点的控件的句柄。 仅当对话框过程返回 TRUE 时,系统才分配默认键盘焦点。
lParam:其他初始化数据。
该消息类型与WM_COMMAND类型相似,都是由子窗口向父窗口发送的消息。
WM_NOTIFY消息可以包含比WM_COMMAND更为丰富的信息。
Windows中有很多通用控件的消息,都是通过WM_NOTIFY来描述的。
MSDN官方文档:WM_NOTIFY消息
typedef struct tagNMHDR{
HWNDhwndFrom; //发送通知消息的控件窗口句柄
UINT idFrom; //发送通知消息的控件ID
UINT code; //通知代码
};
这一章开始,带领大家开发一个加载Windows进程的EXE,其中包括:加载Windows当前的所有进程,包括进程名,PID,镜像地址,镜像大小,并且再单击进程的时候,加载该进程所有的模块。
本篇文章将会一步一步带领大家写出基本的代码,在最后给出完整的代码。在项目开发完毕后,我会发布完整代码。
#include "framework.h"
#include "PEProcess.h"
#include
#pragma comment(lib,"comctl32.lib") //需要使用通用控件
HINSTANCE hIns = 0;
//入口函数
int APIENTRY WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
) {
hIns = hInstance;
//使用通用控件的向前声明
INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&icex);
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG_MAIN), NULL, (DLGPROC)MainDlgProc); //加载主对话框
return 0;
}
这里我们使用到了INITCOMMONCONTROLSEX
结构体,我们来看看这个结构体:
MSDN官方文档:INITCOMMONTROLSEX结构体
typedef struct tagINITCOMMONCONTROLSEX {
DWORD dwSize;
DWORD dwICC;
} INITCOMMONCONTROLSEX, *LPINITCOMMONCONTROLSEX;
该结构通常用于从动态链接库加载常见控件类信息。
参数说明:
dwSize:结构大小(以字节为单位)
dwICC:指向将从DLL加载哪些常见控件类的位标志集。
InitCommonControlsEx
函数MSDN官方文档:InitCOmmonControlsEx函数
BOOL InitCOmmonControlsEx(
[in] const INITCOMMONCONTROLSEX *pocce
);
函数说明:
函数功能:确保加载公共控件(Comctl32),并从该DLL注册特定的公共控件类
参数说明:
指向INITCOMMONCONTROLSEX结构体,该结构包含指定将注册哪些控件类信息。
返回值:
成功返回TRUE,失败返回FALSE.
在写入了入口函数,并且加载了主对话框之后,我们运行,发现程序页面已经出来了:
我们应该想到:
WM_INITDIALOG
,初始化的工作应该在这个消息内完成OOL CALLBACK MainDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
BOOL nRet = FALSE;
switch (uMsg) {
case WM_CLOSE: {
EndDialog(hDlg, 0);
PostQuitMessage(0);
break;
}
case WM_INITDIALOG: {
InitProcessListView(hDlg); //设置ProcessListView的风格
break;
}
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BUTTON_ABOUT: {
DialogBox(hIns, MAKEINTRESOURCE(IDD_ABOUTBOX), NULL, NULL);
}
case IDC_BUTTON_PE: {
//打开新的对话框,PE查看器
return 0;
}
case IDC_BUTTON_OUT: {
EndDialog(hDlg, 0);
return TRUE;
}
}
}
}
return nRet;
}
VOID InitProcessListView(HWND hDlg) {
//设置窗口风格的话调用了结构体
LV_COLUMN lv;
HWND hListProcess;
//初始化
memset(&lv, 0, sizeof(LV_COLUMN));
//获取IDC_LIST_PROCESS句柄
hListProcess = GetDlgItem(hDlg, IDC_LIST3);
//设置整行选中
SendMessage(hListProcess, LVM_GETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT);
//第一列:
lv.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
lv.pszText = (LPWSTR)TEXT("进程"); //列标题
lv.cx = 200; //行宽
lv.iSubItem = 0;
//ListView_InsertColumn(hListProcess,0,&lv);
SendMessage(hListProcess, LVM_INSERTCOLUMN, 0, (DWORD)&lv);
//EnumProcess(hLostProcess);
//第二列
lv.pszText = (LPWSTR)TEXT("PID");
lv.cx = 75;
lv.iSubItem = 1;
//ListView_InsertColumn(hListProcess, 1, &lv);
SendMessage(hListProcess, LVM_INSERTCOLUMN, 1, (DWORD)&lv);
//第三列
lv.pszText = (LPWSTR)TEXT("镜像基址");
lv.cx = 150;
lv.iSubItem = 2;
//ListView_InsertColumn(hListProcess, 2, &lv);
SendMessage(hListProcess, LVM_INSERTCOLUMN, 2, (DWORD)&lv);
//第三列
lv.pszText = (LPWSTR)TEXT("镜像大小");
lv.cx = 174;
lv.iSubItem = 3;
//ListView_InsertColumn(hListProcess, 3, &lv);
SendMessage(hListProcess, LVM_INSERTCOLUMN, 3, (DWORD)&lv);
}
OID InitMoudleListView(HWND hDlg) {
//设置窗口风格需要调用结构体
LV_COLUMN lv;
HWND hListMoudles;
//初始化
memset(&lv, 0, sizeof(LV_COLUMN));
//获取模块列表句柄
hListMoudles = GetDlgItem(hDlg, IDC_LIST_MOUDLE);
//设置整行选中
SendMessage(hListMoudles, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT);
//第一列:
lv.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
lv.pszText = (LPWSTR)TEXT("模块名称");
lv.cx = 400;
lv.iSubItem = 0;
//ListView_Insertcolumn(hListMoudles,0,&lv);
SendMessage(hListMoudles, LVM_INSERTCOLUMN, 0, (DWORD)&lv);
//第二列:
lv.pszText = (LPWSTR)TEXT("模块位置");
lv.cx = 400;
lv.iSubItem = 1;
SendMessage(hListMoudles, LVM_INSERTCOLUMN, 0, (DWORD)&lv);
}
我们可以发现,在初始化进程窗口和初始化模块窗口函数中,我们先写好了我们想要显示的标题,之后使用发送消息的方式使内容显示。这里我注释掉了一个,实际上这两种方式没有什么区别,都能使内容显示。
我们来看看ListView——Insertcolumn
宏:
ListView——Insertcolumn
宏MSDN官方文档:ListView——Insertcolumn宏
void ListView_InsertColumn(
hwnd,
iCol,
pcol
);
LVCOLUMN 结构
:MSDN官方文档:LVCOLUMN 结构
包含有关报表视图中的列的信息。
这个结构体大家到微软文档中自行查看吧,解释起来真的很费劲。
由于我们还没有学习如何加载操作系统中的所有进程,所以今天带领大家向列表中加载假的进程数据,有关如何加载操作系统真正的进程,后续会为大家讲解。
//加载进程函数
VOID EnumProcess(HWND hListProcess) {
LV_ITEM vitem;
//初始化,第一个进程
memset(&vitem, 0, sizeof(LV_ITEM));
vitem.mask = LVIF_TEXT;
//假数据:
vitem.pszText = (LPWSTR)TEXT("csrss.exe");
vitem.iItem = 0;
vitem.iSubItem = 0;
//ListView_Insertem(hListProcess,*vitem);
SendMessage(hListProcess, LVM_INSERTITEM, 0, (DWORD)&vitem);
vitem.pszText = (LPWSTR)TEXT("448");
vitem.iItem = 0;
vitem.iSubItem = 1;
SendMessage(hListProcess, LVM_SETITEM, 0, (DWORD)&vitem);
//ListView_SetItem(hListProcess, &vitem);
vitem.pszText = (LPWSTR)TEXT("56590000");
vitem.iItem = 0;
vitem.iSubItem = 2;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = (LPWSTR)TEXT("000F0000");
vitem.iItem = 0;
vitem.iSubItem = 3;
ListView_SetItem(hListProcess, &vitem);
//第二个进程假数据:
vitem.pszText = (LPWSTR)TEXT("QQ.exe");
vitem.iItem = 1;
vitem.iSubItem = 0;
SendMessage(hListProcess, LVM_INSERTITEM, 0, (DWORD)&vitem);
vitem.pszText = (LPWSTR)TEXT("153");
vitem.iItem = 1;
vitem.iSubItem = 1;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = (LPWSTR)TEXT("65580000");
vitem.iItem = 1;
vitem.iSubItem = 2;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = (LPWSTR)TEXT("001E0000");
vitem.iItem = 1;
vitem.iSubItem = 3;
ListView_SetItem(hListProcess, &vitem);
//第三个进程假数据:
vitem.pszText = (LPWSTR)TEXT("WeChat.exe");
vitem.iItem = 2;
vitem.iSubItem = 0;
SendMessage(hListProcess, LVM_INSERTITEM, 0, (DWORD)&vitem);
vitem.pszText = (LPWSTR)TEXT("256");
vitem.iItem = 2;
vitem.iSubItem = 1;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = (LPWSTR)TEXT("75960000");
vitem.iItem = 2;
vitem.iSubItem = 2;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = (LPWSTR)TEXT("015B0000");
vitem.iItem = 2;
vitem.iSubItem = 3;
ListView_SetItem(hListProcess, &vitem);
}
这里加载了一些假的数据。
这里有个需要尤其注意的地方: 就是在列表中显示我们加入的数据的时候,只有在第一个,我们使用SendMessage
函数的时候,发送了LVM_INSERTITEM
类型的消息,使用宏的时候使用了ListView_Insertem
宏,在后续显示数据的时候,我们都是用的是LVM_SETITEM
类型的消息,或者是使用了ListView_SetItem
宏,这里要尤其注意,如果使用错误的话,是不能显示的。
我们来看看这两种消息的区别:
LVM_INSERTITEM
消息MSDN官方文档:LVM_INSERTITEM消息
LVM_SETITEM
消息MSDN官方文档:LVSETITEM消息
注意两者区别为在列表视图控件中插入新项和添加子项。
LVITEMA
结构MSDN官方文档:LVITEMA结构
结构用途:用于指定或接收列表视图项的属性。
这个结构由于用法很多,大家自行到MSDN官方文档中查阅。
这个函数与老师上课讲的完全一致,但是并非真的添加了数据:
VOID EnumMoudles(HWND hListProcess, WPARAM wParam, LPARAM lParam) {
DWORD dwRowId;
TCHAR szPid[21];
LV_ITEM lv;
//初始化
memset(&lv, 0, sizeof(LV_ITEM));
//获取选择行
dwRowId = SendMessage(hListProcess, LVM_GETNEXTITEM,-1 , LVNI_SELECTED);
if (dwRowId == -1) {
MessageBox(NULL, TEXT("请选择进程"), TEXT("出错啦"), MB_OK);
return;
}
//获取PID
lv.iSubItem = 1;
lv.pszText = szPid;
lv.cchTextMax = 0x20;
SendMessage(hListProcess, LVM_GETITEMTEXT, dwRowId, (DWORD)&lv);
MessageBox(NULL, szPid, TEXT("PID"), MB_OK);
}
讲述了这些代码,这里给大家给出完整代码,但是由于控件的ID不同,复制的时候还需要自己改动。
// PEProcess.cpp : 定义应用程序的入口点。
//
#include "framework.h"
#include "PEProcess.h"
#include
#pragma comment(lib,"comctl32.lib")
HINSTANCE hIns = 0;
//程序向前声明
BOOL CALLBACK MainDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); //主窗口(对话框)回调函数
VOID InitProcessListView(HWND hDlg); //初始化主窗口进程列表函数
VOID EnumProcess(HWND hListProcess); //设置假的进程数据函数
VOID InitMoudleListView(HWND hDlg); //初始化主窗口模块列表函数
VOID EnumMoudles(HWND hListProcess, WPARAM wParam, LPARAM lParam); //设置假的模块列表函数
int APIENTRY WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
) {
hIns = hInstance;
//使用通用控件的向前声明
INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&icex);
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG_MAIN), NULL, (DLGPROC)MainDlgProc);
return 0;
}
//各类函数实现
BOOL CALLBACK MainDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
BOOL nRet = FALSE;
switch (uMsg) {
case WM_CLOSE: {
EndDialog(hDlg, 0);
PostQuitMessage(0);
break;
}
case WM_INITDIALOG: {
InitProcessListView(hDlg); //设置ProcessListView的风格,初始化进程列表
InitMoudleListView(hDlg); //设置MoudleListView的风格,初始化模块列表
break;
}
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BUTTON_ABOUT: {
DialogBox(hIns, MAKEINTRESOURCE(IDD_ABOUTBOX), NULL, NULL);
}
case IDC_BUTTON_PE: {
//打开新的对话框,PE查看器
return 0;
}
case IDC_BUTTON_OUT: {
EndDialog(hDlg, 0);
PostQuitMessage(0);
return TRUE;
}
}
}
case WM_NOTIFY: {
NMHDR* pNMHDR = (NMHDR*)lParam;
if (wParam == IDC_LIST_PROCESS && pNMHDR->code == NM_CLICK) {
EnumMoudles(GetDlgItem(hDlg, IDC_LIST_PROCESS), wParam, lParam);
}
break;
}
}
return nRet;
}
VOID InitProcessListView(HWND hDlg) {
//设置窗口风格调用结构体
LV_COLUMN lv;
HWND hListProcess;
//初始化
memset(&lv, 0, sizeof(LV_COLUMN));
//获取进程列表句柄
hListProcess = GetDlgItem(hDlg, IDC_LIST_PROCESS);
//设置整行选中
SendMessage(hListProcess, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT);
//出错代码:::::
SendMessage(hListProcess, LVM_GETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT);
//第一列:
lv.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
lv.pszText = (LPWSTR)TEXT("进程"); //列标题
lv.cx = 350; //行宽
lv.iSubItem = 0;
//ListView_InsertColumn(hListProcess,0,&lv);
SendMessage(hListProcess, LVM_INSERTCOLUMN, 0, (DWORD)&lv);
//第二列
lv.pszText = (LPWSTR)TEXT("PID");
lv.cx = 100;
lv.iSubItem = 1;
//ListView_InsertColumn(hListProcess, 1, &lv);
SendMessage(hListProcess, LVM_INSERTCOLUMN, 1, (DWORD)&lv);
//第三列
lv.pszText = (LPWSTR)TEXT("镜像基址");
lv.cx = 150;
lv.iSubItem = 2;
//ListView_InsertColumn(hListProcess, 2, &lv);
SendMessage(hListProcess, LVM_INSERTCOLUMN, 2, (DWORD)&lv);
//第四列
lv.pszText = (LPWSTR)TEXT("镜像大小");
lv.cx = 200;
lv.iSubItem = 3;
//ListView_InsertColumn(hListProcess, 3, &lv);
SendMessage(hListProcess, LVM_INSERTCOLUMN, 3, (DWORD)&lv);
EnumProcess(hListProcess);
}
//加载进程函数
VOID EnumProcess(HWND hListProcess) {
LV_ITEM vitem;
//初始化,第一个进程
memset(&vitem, 0, sizeof(LV_ITEM));
vitem.mask = LVIF_TEXT;
//假数据:
vitem.pszText = (LPWSTR)TEXT("csrss.exe");
vitem.iItem = 0;
vitem.iSubItem = 0;
//ListView_Insertem(hListProcess,*vitem);
SendMessage(hListProcess, LVM_INSERTITEM, 0, (DWORD)&vitem);
vitem.pszText = (LPWSTR)TEXT("448");
vitem.iItem = 0;
vitem.iSubItem = 1;
SendMessage(hListProcess, LVM_SETITEM, 0, (DWORD)&vitem);
//ListView_SetItem(hListProcess, &vitem);
vitem.pszText = (LPWSTR)TEXT("56590000");
vitem.iItem = 0;
vitem.iSubItem = 2;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = (LPWSTR)TEXT("000F0000");
vitem.iItem = 0;
vitem.iSubItem = 3;
ListView_SetItem(hListProcess, &vitem);
//第二个进程假数据:
vitem.pszText = (LPWSTR)TEXT("QQ.exe");
vitem.iItem = 1;
vitem.iSubItem = 0;
SendMessage(hListProcess, LVM_INSERTITEM, 0, (DWORD)&vitem);
vitem.pszText = (LPWSTR)TEXT("153");
vitem.iItem = 1;
vitem.iSubItem = 1;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = (LPWSTR)TEXT("65580000");
vitem.iItem = 1;
vitem.iSubItem = 2;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = (LPWSTR)TEXT("001E0000");
vitem.iItem = 1;
vitem.iSubItem = 3;
ListView_SetItem(hListProcess, &vitem);
//第三个进程假数据:
vitem.pszText = (LPWSTR)TEXT("WeChat.exe");
vitem.iItem = 2;
vitem.iSubItem = 0;
SendMessage(hListProcess, LVM_INSERTITEM, 0, (DWORD)&vitem);
vitem.pszText = (LPWSTR)TEXT("256");
vitem.iItem = 2;
vitem.iSubItem = 1;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = (LPWSTR)TEXT("75960000");
vitem.iItem = 2;
vitem.iSubItem = 2;
ListView_SetItem(hListProcess, &vitem);
vitem.pszText = (LPWSTR)TEXT("015B0000");
vitem.iItem = 2;
vitem.iSubItem = 3;
ListView_SetItem(hListProcess, &vitem);
}
VOID InitMoudleListView(HWND hDlg) {
//设置窗口风格需要调用结构体
LV_COLUMN lv;
HWND hListMoudles;
//初始化
memset(&lv, 0, sizeof(LV_COLUMN));
//获取模块列表句柄
hListMoudles = GetDlgItem(hDlg, IDC_LIST_MOUDLE);
//设置整行选中
SendMessage(hListMoudles, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT);
//第一列:
lv.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
lv.pszText = (LPWSTR)TEXT("模块名称");
lv.cx = 400;
lv.iSubItem = 0;
//ListView_Insertcolumn(hListMoudles,0,&lv);
SendMessage(hListMoudles, LVM_INSERTCOLUMN, 0, (DWORD)&lv);
//第二列:
lv.pszText = (LPWSTR)TEXT("模块位置");
lv.cx = 400;
lv.iSubItem = 1;
SendMessage(hListMoudles, LVM_INSERTCOLUMN, 0, (DWORD)&lv);
}
VOID EnumMoudles(HWND hListProcess, WPARAM wParam, LPARAM lParam) {
DWORD dwRowId;
TCHAR szPid[21];
LV_ITEM lv;
//初始化
memset(&lv, 0, sizeof(LV_ITEM));
//获取选择行
dwRowId = SendMessage(hListProcess, LVM_GETNEXTITEM,-1 , LVNI_SELECTED);
if (dwRowId == -1) {
MessageBox(NULL, TEXT("请选择进程"), TEXT("出错啦"), MB_OK);
return;
}
//获取PID
lv.iSubItem = 1;
lv.pszText = szPid;
lv.cchTextMax = 0x20;
SendMessage(hListProcess, LVM_GETITEMTEXT, dwRowId, (DWORD)&lv);
MessageBox(NULL, szPid, TEXT("PID"), MB_OK);
}
今天的分享就到这里,如果文章中出现错误或者是本人理解不到位的地方,还请大家及时指出来,我会非常虚心地学习。希望大家共同进步!!!