本文主要是介绍Windows编程_Lesson004_Windows消息机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
WinMain函数
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPWSTR lpCmdLine,_In_ int nCmdShow);
Windows的调用约定:
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
这些调用约定即使不写的话,也可以通过编译。这些调用约定会决定函数的传参、平栈的方式变得不同。这是因为:
函数是以栈的方式进行参数传递,参数传递完成后,栈帧进行平栈。
__stdcall表示(参考百度百科)
1.参数从右向左压入堆栈
2.函数被调用者修改堆栈
3.函数名(在编译器这个层次)自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸
在win32应用程序里,宏APIENTRY,WINAPI,都表示_stdcall,非常常见。
__cdecl __fastcall与__stdcall,三者都是调用约定(Calling convention),它决定以下内容:1)函数参数的压栈顺序,2)由调用者还是被调用者把参数弹出栈,3)以及产生函数修饰名的方法。
1、__stdcall调用约定:函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈。
2、__cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。注意:对于可变参数的成员函数,始终使用__cdecl的转换方式。
3、__fastcall调用约定:它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈)。
4、thiscall仅仅应用于”C++”成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。
5、nakedcall采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用。
In HINSTANCE hInstance
本身的进程实例句柄。
hInstance是一个传入的参数,那么为什么我们自己的进程还需要从外部传入呢?
实际上我们的wWinmain函数是一个被操作系统所调用的函数,如下图所示:
从图上也可以看出,我们进程本身是不能创建自己的,只能通过操作系统来对这个进程进行创建,创建完成之后,我自己也不知道自己的进程编号,所以只能够由外部来传入。
那么疑问就来了,我们以前写的控制台程序中的main函数里面也可以没有参数呀!更别说要传递进程编号了,那么是不是这两个主函数就是两个不同的函数呢?其实并不是这样的,main函数是c语言中的,因为c语言出现的时候,还没有操作系统这个概念,所以一个main函数就是一个进程,并不需要外部的参数传入。但是我们如果使用的是c语言的main函数,在Windows下,还是照样能够通过系统的API来获取自己的实例句柄的。
In_opt HINSTANCE hPrevInstance
父进程的实例句柄,启动本进程的那个进程,可以做一些简单的检测。
In LPWSTR lpCmdLine
命令行参数
可以使外部和进程进行通信。我们可以在exe程序的快捷方式里面加上一些参数,来进行命令行的传递。例如:
In int nCmdShow
显示方式,Windows所特有的。
和外部进行通信。
窗口创建
// 初始化全局字符串LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);LoadStringW(hInstance, IDC_WIN32WINDOWSDEMO, szWindowClass, MAX_LOADSTRING);
IDS_APP_TITLE和IDC_WIN32WINDOWSDEMO代表资源表中的信息。
MyRegisterClass(hInstance);
这个函数的主要作用是将WNDCLASSEXW结构体用RegisterClassExW(&wcex)进行注册,从而得到自己想要的风格的窗口。
需要注意的是,只要遇到cbSize这个参数,就一定要正确的填充,否则会出现错误或者异常。
WNDCLASSEXW wcex;wcex.cbSize = sizeof(WNDCLASSEX);wcex.style = CS_HREDRAW | CS_VREDRAW;wcex.lpfnWndProc = WndProc;wcex.cbClsExtra = 0;wcex.cbWndExtra = 0;wcex.hInstance = hInstance;wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32WINDOWSDEMO));wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WIN32WINDOWSDEMO);wcex.lpszClassName = szWindowClass;wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
通过调用注册窗口类来生成一个窗口模板。
保存实例句柄并创建主窗口
// 执行应用程序初始化: if (!InitInstance (hInstance, nCmdShow)){return FALSE;}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{hInst = hInstance; // 将实例句柄存储在全局变量中HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);if (!hWnd){return FALSE;}ShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);return TRUE;
}
窗口过程函数
在看窗口过程函数之前,我们先再来看一下NT结构。
细心的你会发现从用户态到核心态再到硬件层,没有一个箭头是向上的,都是从上而下的,也就是说数据的传递都是从上而下的,而没有从下到上的数据传递。并且核心态又不能直接操作用户态,那么问题就来了,当硬件发生改变时,我们用户态的软件又是如何得知这一改变呢?请往下看:
当硬件发生改变时,内核会将这个改变写到一块固定的区域里面,这块区域会不停地被Win32子系统去扫描,当扫描到硬件发生改变时,Win32子系统就会将这个改变翻译成一个消息(为什么不在内核态来做呢?原因很简单,是遵循内核精简原则才这么设计的)
主消息循环
每个应用程序都有一个消息循环,也就是一个消息队列,
// 主消息循环: while (GetMessage(&msg, nullptr, 0, 0)){if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){TranslateMessage(&msg);DispatchMessage(&msg);}}
当消息队列里面接收到消息时,就会调用下面的窗口过程函数,进行消息处理。
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目的: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch (message){case WM_COMMAND:{int wmId = LOWORD(wParam);// 分析菜单选择: switch (wmId){case IDM_ABOUT:DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);break;case IDM_EXIT:DestroyWindow(hWnd);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}}break;case WM_PAINT:{PAINTSTRUCT ps;HDC hdc = BeginPaint(hWnd, &ps);// TODO: 在此处添加使用 hdc 的任何绘图代码...EndPaint(hWnd, &ps);}break;case WM_DESTROY:PostQuitMessage(0);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0;
}
浅析MFC
在MFC中,我们调用的所有函数都是MFC中的函数,我们调用MessageBoxW(L”“)函数,它的函数原型如下所示,最多需要传递三个参数。
int _AFX_FUNCNAME(MessageBox)(LPCTSTR lpszText, LPCTSTR lpszCaption = NULL, UINT nType = MB_OK);
而Win32中调用MessageBoxW()函数,它的函数原型如下所示:
MessageBoxW(_In_opt_ HWND hWnd,_In_opt_ LPCWSTR lpText,_In_opt_ LPCWSTR lpCaption,_In_ UINT uType);
如果我们想要在MFC中调用全局的Win32函数,我们只需要在函数前面加上::全局域作用符。
这篇关于Windows编程_Lesson004_Windows消息机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!