Win32 API与MFC入门


一、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_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中创建消息映射可以通过类向导来创建。

  1. 右击项目名称,选择类向导。
    打开类向导
  2. 先选择类名,再选择消息,输入要处理的消息,再选择处理程序,最后点击添加处理程序按钮。
    类向导的使用
    消息中有写好的处理函数是因为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项目入了一个门,希望对各位能有所帮助,如有什么不足的地方希望各位大佬指出。

  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值