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函数,我们只需要在函数前面加上::全局域作用符。