文章目录
一、Win32 API以及WinMain
Win32 API是一套专门给C语言程序员编写Windows桌面应用程序的API(Application Programming Interface,应用程序开发接口,可以视为一个中间件,它允许开发者访问和使用某些功能或数据,而无需了解背后的详细实现),想要使用这套API需要包含windows.h
头文件。编写Win32程序时类似于C语言的控制台程序,它有自己的入口函数WinMain
(unicode字符集下为wWinMain
其他无差别),函数原型如下:
int APIENTRY WinMain(//APIENTRY为调用约定(规定入栈方式等),类似于c语言的__stdcall
HINSTANCE hInstance, //当前应用程序实例句柄
HINSTANCE hPrevInst, //当前应用程序的前一个应用程序 实例句柄
LPTSTR lpCmdLine, //命令行
int cmdCode) //显示方式
跟踪LPTSTR
这个类型,发现其是wchar_t*
的别名,wchar_t
是宽字符集的数据类型,相当于char
。至于第一个参数和第二参数中所说的句柄类型,关于句柄,我找到的资料中以《windows内核编程》(谭文,陈铭霖著)里面介绍的较为清晰。它是这样说的:
“每个进程都有一个表,这个表中的每一项保存着需要访问的内核对象信息,系统为用户态应用程序提供一个“句柄“值,这个句柄值实际上是这个表的某种索引,通过这个值,可以在表中定位到具体需要访问的内核对象信息。用户态程序通过AP创建或打开一个内核对象时,这个表中的信息会增加一项,用来描述这个内核对象的信息,并产生一个相应的句柄值,用户态程序把这个句柄传递到相应API,API进入内核后,通过这个句柄值定位到需要操作的内核对象,对内核对象进行相应的操作。句柄就好比是内核对象的凭证,通过这个凭证,用户态程序可以间接操作内核对象。”
但总的来说,句柄这个概念对本篇内容影响不大,当做是进程的id即可。这里提一句大多数情况下如何区分函数适用于哪个字符集:
- 函数名后面有Ex 说明是升级版本。
- A unicode字符集兼容多字节字符集。
- W unicode字符集。
二、窗口注册六部曲
1.注册窗口类
示例代码如下:
WNDCLASSEX wc = { 0 };
//初始化类的成员变量,变量代表的含义参考下一个示例代码中类的原型。
wc.cbSize = sizeof(WNDCLASSEX);
wc.hInstance = hInstance;
wc.lpszClassName = L"窗口类的名称";
wc.cbClsExtra = NULL;
wc.cbWndExtra = NULL;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);//默认画刷
wc.hCursor = LoadCursor(NULL, IDC_CURSOR1);//默认光标,IDC_CURSOR1为资源id
wc.hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(IDI_PROJECT2));//MAKEINTRESOURCE将资源id转换成资源
wc.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SMALL));
wc.lpfnWndProc = wndProc;//消息处理函数,在最后一步中有讲
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
wc.style = CS_HREDRAW | CS_VREDRAW;
//注册函数
RegisterClassEx(&wc);
关于WNDCLASSEX
类的原型如下:
typedef struct tagWNDCLASSEXW {
UINT cbSize; //结构体大小 必须赋值 非Ex版本没这个玩意
/* Win 3.x */
UINT style; //类型 宏
WNDPROC lpfnWndProc; //窗口的消息处理函数
int cbClsExtra; //类的附加信息
int cbWndExtra; //窗口附加信息
HINSTANCE hInstance; //应用程序实例句柄
HICON hIcon; //图标
HCURSOR hCursor; //光标
HBRUSH hbrBackground; //背景画刷
LPCWSTR lpszMenuName; //菜单
LPCWSTR lpszClassName; //类名
/* Win 4.0 */
HICON hIconSm; //小图标
} WNDCLASSEXW, *PWNDCLASSEXW, NEAR *NPWNDCLASSEXW, FAR *LPWNDCLASSEXW;
#ifdef UNICODE
typedef WNDCLASSEXW WNDCLASSEX;
2.创建窗口
使用CreateWindowExW
函数即可,该函数原型如下(_In_等宏主要用于说明参数的用途,可以不用理会):
HWND CreateWindowExW(
_In_ DWORD dwExStyle,//正在创建的窗口的扩展窗口样式
_In_opt_ LPCWSTR lpClassName,//窗口类名
_In_opt_ LPCWSTR lpWindowName,//窗口名称
_In_ DWORD dwStyle,//正在创建的窗口的样式
_In_ int X,//窗口的初始水平位置
_In_ int Y,//窗口的初始垂直位置
_In_ int nWidth,//窗口的宽度
_In_ int nHeight,//窗口的高度
_In_opt_ HWND hWndParent,//正在创建的窗口的父窗口或所有者窗口的句柄
_In_opt_ HMENU hMenu,//菜单句柄,或指定子窗口标识符,具体取决于窗口样式
_In_opt_ HINSTANCE hInstance,//要与窗口关联的模块实例的句柄
_In_opt_ LPVOID lpParam);//指向要通过 CREATESTRUCT 结构传递到窗口的值的指针
示例代码如下:
//2. 创建窗口
HWND hWnd = CreateWindowEx(wc.style,//返回窗口句柄
wc.lpszClassName,
L"第一个windows窗口",
WS_OVERLAPPEDWINDOW,
100,100,
500,500,
NULL,NULL,hInstance,NULL);
if (NULL == hWnd) {
MessageBox(NULL, L"创建窗口失败", L"tips", NULL);
return -1;
}
3.显示
ShowWindow(hWnd, SW_SHOW);//hWnd为上文创建的窗口的句柄
4.刷新
UpdateWindow(hWnd);
5.消息循环
MSG msg;
while (1) {
//5.1 获取消息
GetMessage(&msg,NULL,0,0);
//5.2 翻译消息
TranslateMessage(&msg);
//5.3 派发消息
DispatchMessage(&msg);
}
6.消息处理函数
这个需要自己定义声明,但是得依照规定的函数指针的方式,如下:
LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
//hwnd是要处理窗口的句柄;message是消息ID,代表了不同的消息类型;
//wParam和lParam代表了消息的附加信息。
//使用该函数时最后最好使用默认的返回函数。
//return DefWindowProc(hWnd, msg, wParam, lParam);//默认的消息处理函数
使用示例如下:
LRESULT CALLBACK wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_TIMER:
switch(wParam){
case 1://计时器标识符
//.....
}
break;
case WM_SYSKEYDOWN:
//....
}
return DefWindowProc(hWnd, msg, wParam, lParam);//默认的消息处理函数
}
三、关于vs项目保留预编译头的项目结构
- framework.h 是工程自带的,包含了一些默认头文件。
- targetver.h 为了支持winNT版本的头文件。
- resource.h 资源头文件。
- pch.h(或者stdafx.h)预处理头文件,头文件包含都放到pch.h中,程序中每个.c .cpp都要包含这个pch.h。
- 工程名.h
- pch.cpp 和pch.h对应,不用理会
- 工程名.cpp。
四、消息机制
简要介绍
Windows是通过消息机制驱动的,用户任何的行为(动作)操作系统会产生对应的消息(结构体),操作系统把消息发送给窗口应用程序。窗口应用程序中有个循环用于接收消息(从消息队列中获取消息,消息队列就是存放消息的数据结构,分为普通消息队列以及系统消息队列),翻译消息(如对按键消息就需要翻译),派发消息(调用消息处理函数,消息处理完后继续循环)。消息大致可以分为队列消息(会进入消息队列)和非队列消息(不会进入消息队列)。
消息结构体的原型如下:
typedef struct tagMSG {
HWND hwnd; //窗口句柄
UINT message; //消息
WPARAM wParam; //消息的附加信息1
LPARAM lParam; //消息的附加信息2
DWORD time; //时间
POINT pt; //焦点
#ifdef _MAC
DWORD lPrivate;
#endif
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
常用消息
- WM_CREATE
创建窗口后自动发送此消息,显示窗口前会处理WM_CREATE
消息。 - WM_DESTROY
窗口摧毁消息 一般在收到这个消息后调用PostQuitMessage
函数,然后GetMessage
函数就会返回false。 - WM_CLOSE
窗口关闭消息,实际上是隐藏窗口,等同于ShowWindow(hWnd,SW_HIDE);
。点关闭按钮时先发送WM_CLOSE
消息再发送WM_DESTROY
消息。 - 鼠标消息
- WM_MOUSEMOVE
鼠标移动消息,lParam的低16位是窗口客户区的 x坐标,lParam的高16位是窗口客户区的 y坐标。 - WM_LBUTTONDBLCLK
双击消息要在注册窗口类的时候使用CS_DBLCLKS风格。 - WM_LBUTTONDOWN
鼠标左击消息。 - WM_MOUSEWHEEL
鼠标滚轮消息。滚轮消息的坐标是屏幕坐标,往上翻滚显示120,往下是65416(计算机存储的-120)。
- WM_MOUSEMOVE
- 键盘消息
- 普通按键
WM_KEYDOWN
以及WM_KEYUP
对应键盘按下以及弹起。非系统键是在未按下 Alt 键时按下的键。消息处理函数中的附加消息wParam
判断是否为非系统密钥的虚拟密钥代码(VK_DOWN
等)。lParam
用于记录重复计数、扫描代码、扩展键标志、上下文代码、以前的键状态标志和转换状态标志。第24位用于判断指示键是否为扩展键。使用WM_KEYDOWN
发现不会响应可能是因为被截获了,这时需要需要重写 一个虚函数PreTranslateMessage
在翻译消息的时候去处理 。如下代码所示:
- 普通按键
BOOL PreTranslateMessage(MSG* pMsg)
{
// TODO: 在此添加专用代码和/或调用基类
CString str;
switch (pMsg->message) {
case WM_KEYDOWN:
if (VK_SPACE == pMsg->wParam) {
//...
}
else if (VK_RETURN == pMsg->wParam) {
//...
}
break;
case WM_KEYUP:
break;
}
return CDialogEx::PreTranslateMessage(pMsg);
}
- 系统按键
WM_SYSKEYDOWN
以及WM_SYSKEYUP
,当用户按 F10 键(激活菜单栏)或按住 Alt 键,然后按另一个键时,发布到具有键盘焦点的窗口触发。
- WM_TIMER
定时器消息。使用SetTimer
设置计时器。SetTimer
原型如下:
WINUSERAPI
UINT_PTR
WINAPI
SetTimer(
_In_opt_ HWND hWnd,//与计时器关联的窗口的句柄
_In_ UINT_PTR nIDEvent,//非零计时器标识符,可以理解为计时器的id
_In_ UINT uElapse,//设置的时间
_In_opt_ TIMERPROC lpTimerFunc);//超过设置时间后要通知的函数指针
设置完计时器后如上述介绍消息处理函数的示例代码一样使用即可。
- WM_DEVICECHANGE
设备改变消息。如插拔U盘等。 - WM_COMMAND
菜单,按钮,控件(编辑框这些用户可与之交互以便输入或操作数据的对象)等的响应。 - WM_PAINT
绘图消息。 - WM_SIZE
窗口改变大小的消息,要添加WS_THICKFRAME
风格才能拖动方式改变对话框大小。
自定义消息
首先用数值定义一个消息,最好在WM_USER
(一般为1024)上加上一个数,避免与系统的消息重复。示例代码如下:
#define UM_XIXI (WM_USER + 1)
使用SendMessage
发送消息,原型如下:
WINUSERAPI
LRESULT
WINAPI SendMessageW(
_In_ HWND hWnd,//窗口的句柄,其窗口过程将接收消息
_In_ UINT Msg,//要发送的消息
_Pre_maybenull_ _Post_valid_ WPARAM wParam,//附加信息
_Pre_maybenull_ _Post_valid_ LPARAM lParam);//附加信息
最后在消息处理函数中像其他消息一样(switch中)接收消息即可。
五、对话框
什么是对话框以及分类
对话框是特殊的窗口,其包含一些控件,与窗口不同的是对话框的控件都是用来与用户交互的,并且有一些约定的使用规则:
- 按下Tab键或上、下、左、右方向键,各个控件依次获得输入焦点。
- 如果一个按钮获得输入焦点,这时按下空格键或者回车键,相当于鼠标左键点击了该按钮。
对话框的默认窗口过程在处理有关的按键消息时,会自动解析处理上述使用规则。而一般窗口的窗口过程,没有这些缺省的案件消息处理。
对话框主要分为两大类: - 模态对话框:阻塞的,对话框结束才可以继续操作,对话框没有结束不能操作其他窗口的。
- 非模态对话框:非阻塞的,对话框 还没有结束就可以操作其他窗口。
添加对话框资源
1.右击项目解决方案管理器的资源文件添加资源。
选择Dialog,即对话框,然后点击新建。
然后可以在属性面板中修改属性(标题,id等),还可以在工具箱面板中拖拽控件编辑。
模态对话框
使用函数DialogBox
创建,DialogBox
函数是DialogBoxParamW
函数的宏替换,其原型如下:
INT_PTR
WINAPID ialogBoxParamW(
_In_opt_ HINSTANCE hInstance,//包含对话框模板的模块的句柄
_In_ LPCWSTR lpTemplateName,//对话框模板
_In_opt_ HWND hWndParent,//拥有对话框的窗口的句柄。
_In_opt_ DLGPROC lpDialogFunc,//对话框消息处理函数
_In_ LPARAM dwInitParam);//要传递到WM_INITDIALOG消息的 lParam 参数中的对话框的值。
使用示例如下:
DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAIN_DLG), NULL,
mainDlgProc);
另外:对话框消息处理函数返回false说明对话框没有处理的消息由操作系统来管理;返回true说明所有消息都由当前对话框来处理 就算没有处理的操作系统也不去处理。
非模态对话框
使用CreateDialog
函数(该函数为CreateDialogParamW
函数的宏替换)创建并获取窗口句柄,该函数原型如下:
//如果函数成功,则返回值是对话框的窗口句柄。如果函数失败,则返回值为 NULL。
//参数与DialogBox函数相同
HWND
WINAPI
CreateDialogParamW(
_In_opt_ HINSTANCE hInstance,
_In_ LPCWSTR lpTemplateName,
_In_opt_ HWND hWndParent,
_In_opt_ DLGPROC lpDialogFunc,
_In_ LPARAM dwInitParam);
然后再使用ShowWindow
以及UpdateWindow
函数,这时窗口才算完全创建。
对话框的消息处理
用WM_COMMAND
消息处理子控件,LOWORD(wParam)
(wParam的低两字节)来区分对话框的子控件 (按钮id)。示例如下:
switch (msg) {
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDC_BUTTON2://控件id
ShowWindow(g_chatDlg, SW_SHOW);//重新显示
break;
case ID_CHAT:
另外:DestroyWindow(hWnd);
是真正删掉对话框,而EndDialog(hWnd, IDCANCEL);
是隐藏并非真正关掉和ShowWindow(hWnd,SW_HIDE);
等同。
六、MFC入门
MFC基本介绍
MFC(Microsoft Fundation Classes 微软基础类库)是微软推出的 windows操作系统上的面向对象的 C++ 的类库,它封装了windows api。能实现windows操作系统能实现的绝大部分功能。
MFC的入口函数
MFC的模板主要有两个类:继承自CWinApp
类的CMyWinApp
类(我自己继承的类,创建项目时该类名称通常是项目名,以下同理)以及主窗口类CMyDialog
类(继承自CDialog
类,主要是MFC的基于对话框的程序)。MFC程序首先会创建CMyWinApp
的全局对象(全局对象初始化在入口函数WinMain
前),然后进入appmodul.cpp文件中的_tWinMain
函数(WinMain
的宏替换),里面调用了AfxWinMain
函数。在该函数中进行初始化线程,调用InitInstance
初始化窗口(该虚函数需要自己重写,通常在这里创建CMyDialog
类的对象),并调用Run
函数进入消息循环。所以我们如果想要在空项目中创建MFC程序,只需要包含afxwin.h文件,然后创建继承自CWinApp
的类并重写它的虚函数InitInstance
,再创建一个继承自CDialog
类的窗口类,并在InitInstance
函数中创建该对象并将他显示出来。
消息映射
声明消息映射
在CMyDialog
类的.h文件中的DECLARE_MESSAGE_MAP()
下声明消息映射,如下代码所示:
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnSize(UINT nType, int cx, int cy);
};
转到DECLARE_MESSAGE_MAP()
的定义可以发现它是以下代码的宏替换。
protected:
static const AFX_MSGMAP* PASCAL GetThisMessageMap();
const AFX_MSGMAP* GetMessageMap() const override;
PASCAL
是__stdcall
的宏替换,再查看AFX_MSGMAP
发现它是一个结构体,定义如下:
struct AFX_MSGMAP
{
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
const AFX_MSGMAP_ENTRY* lpEntries;
};
里面有一个函数指针返回AFX_MSGMAP
的指针对象,还有一个AFX_MSGMAP_ENTRY
类型,查看它的定义:
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of control id's
UINT_PTR nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
文件中用英文标注了各个成员的含义。
定义消息映射
在CMyDialog类中
的.cpp文件中定义消息映射。格式如下:
BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
//中间写对应的消息处理函数
END_MESSAGE_MAP()
将宏全部展开
PTM_WARNING_DISABLE
const AFX_MSGMAP* CMyDialog::GetMessageMap() const
{ return GetThisMessageMap(); }
const AFX_MSGMAP* PASCAL CMyDialog::GetThisMessageMap()
{
typedef CMyDialog ThisClass;
typedef CDialog TheBaseClass;
__pragma(warning(push))
__pragma(warning(disable: 4640)) /* message maps can only be called by single threaded message pump */ \
static const AFX_MSGMAP_ENTRY _messageEntries[] =
{
{ WM_CREATE, 0, 0, 0, AfxSig_is,
(AFX_PMSG)(AFX_PMSGW)
(static_cast<int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT)> (&ThisClass::OnCreate)) },
{ WM_CLOSE, 0, 0, 0, AfxSig_vv,
(AFX_PMSG)(AFX_PMSGW)
(static_cast<void (AFX_MSG_CALL CWnd::*)(void)> (&ThisClass::OnClose)) },
//将消息处理函数转化成CWnd类的消息处理函数指针类型
{ WM_DESTROY, 0, 0, 0, AfxSig_vv,
(AFX_PMSG)(AFX_PMSGW)
(static_cast<void (AFX_MSG_CALL CWnd::*)(void)> (&ThisClass::OnDestroy)) },
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
};
__pragma(warning(pop))
static const AFX_MSGMAP messageMap =
{ &TheBaseClass::GetThisMessageMap, & _messageEntries[0] };
return &messageMap;
}
PTM_WARNING_RESTORE
可以看出,MFC的消息机制定义是通过定义GetThisMessageMap
将消息与消息处理函数绑定在一起。
在vs中创建消息映射可以通过类向导来创建。
- 右击项目名称,选择类向导。
- 先选择类名,再选择消息,输入要处理的消息,再选择处理程序,最后点击添加处理程序按钮。
消息中有写好的处理函数是因为CDialog
类继承自CWnd
类,比如CWnd
类中有虚函数OnCreate
就是WM_CREATE
消息的处理函数。
自定义消息
与Win32类似,先用宏定义替换一个数值,再如常规消息一样声明消息处理函数,最后只需用ON_MESSAGE(自定义消息,自定义消息处理函数)
定义即可。定义使用如下:
BEGIN_MESSAGE_MAP()
ON_MESSAGE(自定义消息,自定义消息处理函数)
END_MESSAGE_MAP()
另外:在CMyDialog
中获取theApp
对象使用函数AfxGetApp()
,对话框对象获取使用this
;在CMyWinApp类中对话框对象的获取使用AfxGetMainWnd()
,theApp对象的获取使用this
。
总结
本篇文章简单地介绍了Win32 API的一些概念以及MFC项目入了一个门,希望对各位能有所帮助,如有什么不足的地方希望各位大佬指出。