SDK 窗口程序创建

目录

Windows 窗口

窗口的基本概念

创建一个窗口的流程

句柄

创建一个窗口的基本流程

WinMain 应用程序入口点

WNDCLASS 窗口类

FormatMessage()函数  显示错误信息

CreateWindow  创建窗口

显示,更新窗口

创建消息循环

Windows消息循环机制

与窗口相关消息

建立消息循环

窗口过程函数

默认消息处理

switch 处理消息

SDK空程序模板

如何终止程序


Windows 窗口

Winodws这个操作系统就是由窗口命名的,可见窗口的重要性

SDK的学习本质就是学习各种API,需要学会独立阅读官方文档

窗口的基本概念

通过这篇微软官方文档可以了解到窗口的基本概念:什么是窗口 - Win32 apps | Microsoft Learn

接下来了解一些更深层次的东西

Windows的窗口是由窗口类定义的,窗口类定义了窗口的外观和行为,并将其注册到系统中。每个窗口都是窗口类的一个实例,它们具有相同的属性,但可以拥有不同的内容和状态。

Windows 窗口通常包括以下几个部分:

  1. 标题栏(Title Bar):位于窗口的顶部,用于显示窗口的标题和一些控制按钮(例如最小化、最大化、关闭等)。
  2. 菜单栏(Menu Bar):可选的,位于窗口的顶部或程序的主窗口下方,用于显示应用程序的菜单和子菜单。
  3. 工具栏(Tool Bar):可选的,通常位于窗口的顶部或菜单栏下方,用于提供快捷方式和工具按钮。
  4. 客户区(Client Area):窗口的主要部分,用于显示应用程序的内容、控件和用户界面。
  5. 状态栏(Status Bar):位于窗口的底部,用于显示应用程序的状态信息、提示信息等。

除此之外,窗口还可以包含各种控件,例如按钮、文本框、列表框、复选框等,以及自定义的绘图和布局逻辑。

创建一个窗口的流程

  1. 要有一个窗口类,并且得到一个实例
  2. 调用系统API注册窗口类,需要指定回调函数
  3. 再调用系统API创建窗口,会返回一个窗口的句柄

句柄

程序员是不能直接操作窗口的,需要调用系统的API;

当应用程序请求创建一个新的句柄时,操作系统内核会为该句柄分配一段内存空间来存储句柄所引用的对象或资源的相关信息,并返回该句柄给应用程序。对于不同类型的句柄,内核会使用不同的数据结构来管理它们。

句柄(Handle)是在计算机编程中用于表示对象或资源的标识符。在操作系统中,句柄可以被认为是对对象或资源的引用,程序可以通过句柄来访问和操作这些对象或资源。句柄是操作系统提供的一种标识和访问对象或资源的机制,它由操作系统内核来管理

想要访问这些系统资源,需要API+句柄来实现

下面是一些常见的句柄类型及其含义:

  1. 窗口句柄(HWND):窗口句柄是对窗口对象的引用,可以用于标识和操作特定的窗口。通过窗口句柄,程序可以改变窗口的属性、位置、大小,发送消息到窗口,以及获取窗口的属性和状态等。
  2. 文件句柄(File Handle):文件句柄是对文件对象的引用,用于表示打开的文件。通过文件句柄,程序可以读取、写入、关闭文件,以及获取文件的属性和状态等。
  3. 设备句柄(Device Handle):设备句柄是对设备对象的引用,用于表示打开的设备。通过设备句柄,程序可以与设备进行通信,执行设备操作,例如读写设备数据、控制设备行为等。
  4. 内存句柄(Memory Handle):内存句柄是对内存块的引用,用于表示分配的内存。通过内存句柄,程序可以读写分配的内存区域,释放内存,以及获取内存的相关信息。

在学习Win32桌面编程之前需要阅读以下文档:

数据类型说明:Windows 编码约定 - Win32 apps | Microsoft Learn

字符串说明:操作字符串 - Win32 apps | Microsoft Learn

窗口说民:什么是窗口 - Win32 apps | Microsoft Learn

创建一个窗口的基本流程

  • 设计注册窗口类
  • 创建窗口实例
  • 显示窗口
  • 更新窗口
  • 消息循环
  • 实现窗口过程函数(窗口回调函数)

接下来开始学习写一个Win32的模板程序

WinMain 应用程序入口点

参考文档:WinMain 应用程序入口点 - Win32 apps | Microsoft Learn

WinMain是A版本的,wWinMain是W版本的,一般普遍使用后者

函数原型如下

int WINAPI wWinMain(
	_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPWSTR lpCmdLine,
	_In_ int nShowCmd)

函数调用约定是stdcall,这里是一个宏,VS2019可在如下位置设置调用约定

SDK 窗口程序创建_第1张图片

三种常见调用约定参考文章:带你玩转Visual Studio——调用约定__cdecl、__stdcall和__fastcall-CSDN博客

微软官方给出的参考:自变量传递和命名约定 | Microsoft Learn

参数说明:

  • hInstance 是应用程序的当前实例的句柄。 当可执行文件加载到内存中时,操作系统使用此值来标识可执行文件或 EXE。 某些 Windows 函数需要实例句柄,例如加载图标或位图。
  • hPrevInstance 没有任何意义。 它在 16 位 Windows 中使用,但现在始终为零。
  • pCmdLine 以 Unicode 字符串的形式包含命令行参数。
  • nCmdShow 是一个标志,指示主应用程序窗口是最小化、最大化还是正常显示。

返回值:函数返回一个 int 值。 操作系统不使用返回值,但你可以使用 该值将状态代码传递给另一个程序。

WNDCLASS 窗口类

微软官方给出的参考文档中给出的窗口类有两种:

WNDCLASS:WNDCLASSA (winuser.h) - Win32 apps | Microsoft Learn

WNDCLASSEX:WNDCLASSEXA (winuser.h) - Win32 apps | Microsoft Learn

两者区别不大,有两个区别。 WNDCLASSEX 包括 cbSize 成员(指定结构的大小)和 hIconSm 成员,该成员包含与窗口类关联的小图标的句柄。

同样每个窗口类都有两个版本,多字节版本和Unicode版本

每个窗口都必须与一个窗口类相关联。 窗口类不是 C++ 意义上的类。 相反,它是操作系统内部使用的数据结构。 窗口类在运行时向系统注册。 创建一个窗口需要实例化一个窗口类,并向操作系统注册才能创建窗口。

WNDCLASSW的原型

typedef struct tagWNDCLASSW {
  UINT      style;
  WNDPROC   lpfnWndProc;
  int       cbClsExtra;
  int       cbWndExtra;
  HINSTANCE hInstance;
  HICON     hIcon;
  HCURSOR   hCursor;
  HBRUSH    hbrBackground;
  LPCWSTR   lpszMenuName;
  LPCWSTR   lpszClassName;
} WNDCLASSW, *PWNDCLASSW, *NPWNDCLASSW, *LPWNDCLASSW;

参数说明如下,另附上官方文档:WNDCLASSW (winuser.h) - Win32 apps | Microsoft Learn

WNDPROC的声明,定义了一个函数指针类型 WNDPROC,该类型指向一个具有特定参数和返回值的函数。该函数的返回值是 LRESULT 类型。LRESULT这个类型定义通常用于在 Windows 应用程序中表示指针或句柄值。

typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);

参数1:类的样式,常用:CS_HREDRAW | CS_VREDRAW  表示当窗口的纵横坐标发生变化时要重画整个窗口。无论你怎样拉动窗口的大小,那行字都会停留在窗口的正中部,而假如把这个参数设为0的话,当改动窗口的大小时,那行字则不一定处于中部了。这种方式是或运算,表示两种眼样式都具备,Win32编程中,这种方式很常见。这些参数是宏,本质是16进制数。

参数2:指向应用程序定义的函数(称为 窗口过程 或 窗口过程)的指针。 将接收Windows发送给窗口的消息,并执行相应的任务。并且必须在模快定义中回调它。WndProc是一个回调函数(详见消息循环)

参数3:要根据窗口类结构分配的额外字节数。 系统将字节初始化为零。当有两个以上的窗口属于同一窗口类时,如果想将不同的数据和每个窗口分别相对应。则使用该域很有用。这般来讲,你只要把它们设为0就行了,不必过多考虑。

参数4:在窗口实例之后分配的额外字节数。

参数5:实例的句柄,该实例包含类的窗口过程。一成员可使Windows连接到正确的程序(自己的程序)

参数6:类图标的句柄。 此成员必须是图标资源的句柄。 如果此成员为 NULL,则系统会提供默认图标。被设置成应用程序所使用图标的句柄,图标是将应用程序最小化时出现在任务栏里的的图标,用以表示程序仍驻留在内存中。Windows提供了一些默认图标,我们也可定义自己的图标,VC里面专有一个制作图标的工具。

参数7:类游标的句柄。 此成员必须是游标资源的句柄。 如果此成员为 NULL,则每当鼠标移动到应用程序的窗口中时,应用程序都必须显式设置光标形状。定义该窗口产生的光标形状。LoadCursor可返回固有光标句柄或者应用程序定义的光标句柄。IDC_ARROW表示箭头光标.

参数8:类背景画笔的句柄。 决定Windows用于着色窗口背景的刷子颜色,函数GetStockObject返回窗口的颜色,本程序中返回的是白色

参数9:类菜单的资源名称,该名称显示在资源文件中。用来指定菜单名,本程序中没有定义菜单,所以为NULL。

参数10:指定窗口类名。类名是操作系统识别类的唯一ID

三个重要参数(必填写):

  • lpfnWndProc :指向应用程序定义的函数(称为 窗口过程 或 窗口过程)的指针。 窗口过程定义窗口的大部分行为。
  • hInstance 是应用程序实例的句柄。 从 的 hInstance 参数 wWinMain获取此值。
  • lpszClassName 是标识窗口类的字符串。类名是当前进程的本地名称,因此该名称只需在进程中是唯一的。

代码实例:

	//设计注册窗口类
	TCHAR szWndClassName[] = { _T("Martlet14fly") };

	WNDCLASSEX wc = { 0 };					//创建一个类名为 wc 的窗口类
	wc.cbSize = sizeof(WNDCLASSEX);         //报错参数不对,官方文档要求填写此参数
	wc.style = CS_VREDRAW | CS_HREDRAW; 	//默认填CS_VREDRAM | CS_HREDRAM 的组合,如果调整了窗口的长度和宽度,就重绘窗口
	wc.lpfnWndProc = MyWindowProc;		    //窗口过程函数(窗口回调函数)
	wc.cbClsExtra = 0;						//默认填0
	wc.cbWndExtra = 0;						//默认填0
	wc.hInstance = hInstance;				//标注这个窗口类属于哪个进程,便于从消息队列获取该进程的消息
	wc.hIcon = LoadIcon(NULL, IDI_HAND);	//图标
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);	//光标
	wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 255)); //用户去背景 白色
	wc.lpszMenuName = NULL;					//菜单
	wc.lpszClassName = szWndClassName;		//窗口的类名

LoadIcon(NULL, IDI_HAND);从与应用程序实例关联的可执行 (.exe) 文件中加载指定的图标资源。

参数1:DLL 或可执行文件 (.exe 模块的句柄,) 包含要加载的图标的文件。

参数2:如果 hInstance 为非 NULL, 则 lpIconName 按名称或序号指定图标资源;如果 hInstance 为 NULL, 则 lpIconName 将指定标识符 (从要加载的预定义系统图标的 IDI_前缀) 开始 。

其他的几个基本类似

注册窗口类

RegisterClass(&wc);
	// 注册窗口
	if (RegisterClassEx(&wc) == 0) {
		ShowErrorMsg();
		return 0;
	}

FormatMessage()函数  显示错误信息

函数模板:

void ShowErrorMsg() {
	// 声明一个void 类型的指针,表示任意类型的指针,这是一个缓冲区
	LPVOID lpMsgBuf;

	// 调用函数把错误信息写入缓冲区
	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_SYSTEM |
		FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR)&lpMsgBuf,
		0,
		NULL
	);
	MessageBox(NULL, (LPCTSTR)lpMsgBuf, _T("ERROR"), MB_OK | MB_ICONINFORMATION);
	// 释放由 FormatMessage 函数分配的缓冲区,以避免内存泄漏。
	LocalFree(lpMsgBuf);
}

LPVOID:它是一个指向 void 类型的指针。LPVOID 的全称是 "Long Pointer to VOID",它主要用于在 Win32 API 中表示一个指向未知类型的指针。void 类型是一种特殊的类型,它表示没有值的类型。在 C/C++ 中,void* 类型可以用来表示任意类型的指针,而 LPVOID 则是 void* 在 Windows 平台上的别名。这样可以实现对不同类型的数据进行通用的处理

FormatMessage()参考文档:FormatMessage 函数 (winbase.h) - Win32 apps | Microsoft Learn

调用 FormatMessage 函数来获取最近一次系统错误代码对应的错误信息。FormatMessage 函数会根据错误代码,在系统消息资源表中查找对应的错误信息,并返回格式化后的错误信息字符串。

函数返回值:

如果函数成功,则返回值是存储在输出缓冲区中的 TCHAR 数,不包括终止 null 字符。

如果函数失败,则返回值为零。 要获得更多的错误信息,请调用 GetLastError。

这个函数只能显示最近一次系统错误代码对应的错误信息。如果在调用 GetLastError 函数之后又发生了其他的系统错误,那么这个函数就无法显示新的错误信息。

CreateWindow  创建窗口

创建重叠窗口、弹出窗口或子窗口。 它指定窗口类、窗口标题、窗口样式和 ((可选)) 窗口的初始位置和大小。 函数还指定窗口的父级或所有者(如果有)以及窗口的菜单。

参考文档:CreateWindowA 宏 (winuser.h) - Win32 apps | Microsoft Learn

函数原型

HWND CreateWindow(
  LPCTSTR lpClassName,  // 登记的窗口类名
  LPCTSTR lpWindowName, // 用来表明窗口的标题
  DWORD dwStyle,        // 用来表明窗口的风格,如有无最大化,最小化按纽啊什么的
  int x,                // 用来表明程序运行后窗口在屏幕中的坐标值。
  int y,                // 用来表明程序运行后窗口在屏幕中的坐标值。
  int nWidth,           // 用来表明窗口初始化时(即程序初运行时)窗口的大小,即长度与宽度。
  int nHeight,          // 用来表明窗口初始化时(即程序初运行时)窗口的大小,即长度与宽度。
  HWND hWndParent,      // 在创建窗口时可以指定其父窗口,这里没有父窗口则参数值为0。
  HMENU hMenu,          // 用以指明窗口的菜单,菜单以后会讲,这里暂时为0。
  HANDLE hInstance,     // 应用程序实例句柄,与程序绑定
  LPVOID lpParam        // 最后一个参数是附加数据,一般都是0。
);

参数说明:

  • 参数1:注册类的名字,对应窗口类名
  • 参数2:窗口名,左上角的标题
  • 参数3:窗口风格
  • 参数4-7:窗口大小和位置
  • 参数8:父窗口句柄,无父窗口填NULL即可
  • 参数9:菜单句柄
  • 参数10:该程序实例的句柄

返回值:类型:HWND

如果函数成功,则返回值是新窗口的句柄。

如果函数失败,则返回值为 NULL。

窗口风格参考文档:窗口样式 (Winuser.h) - Win32 apps | Microsoft Learn

代码实例

	// 创建窗口
	TCHAR szWndName[] = { _T("51asm") };
	HWND hWnd = CreateWindowEx(
		0,
		szWndClassName,           // 窗口类名
		szWndName,                // 窗口名
		WS_OVERLAPPEDWINDOW,      // 组合属性,窗口的最大化,最小化,调整大小边框,标题栏
		CW_USEDEFAULT,            // 系统默认值
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		NULL,                     // 父窗口句柄
		NULL,                     // 菜单句柄
		hInstance,                // 应用程序实例句柄
		NULL);

显示,更新窗口

//如果窗口可见,显示非0,否则显示0
ShowWindow(hWnd, SW_SHOWNORMAL);	//hwnd是窗口实例的句柄,ID
// SW_SHOWNORMAL:激活并显示窗口。 如果窗口最小化、最大化或排列,系统会将其还原到其原始大小和位置。 应用程序应在首次显示窗口时指定此标志。

UpdateWindow(hwnd);

函数官方文档:

ShowWindow 函数 (winuser.h) - Win32 apps | Microsoft Learn

updateWindow 函数 (winuser.h) - Win32 apps | Microsoft Learn

参数1:窗口的句柄

参数2:WinMain函数的最有一个参数

返回值:类型: BOOL

如果窗口以前可见,则返回值为非零值。

如果以前隐藏窗口,则返回值为零。

调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即使窗口得到更新.且不通过消息循环。

创建消息循环

Windows消息循环机制

Windows为每个正在运行的应用程序都保持一个消息队列。当你按下鼠标或者键盘时,Windows并不是把这个输入事件直接送给应用程序,而是将输入的事件先翻译成一个消息,然后把这个消息放入到这个应用程序的消息队列中去。

消息结构体:MSG定义

typedef struct tagMSG {     // msg  
    HWND   hwnd;      //要发送的窗口句柄。如果是在一个有多个窗口的应用程序中,用这个参数就可决定让哪个窗口接收消息。
    UINT   message;	  //消息编号
    WPARAM wParam;	  //两个参数,不同的消息,一个32位的消息参数,这个值的确切意义取决于消息本身
    LPARAM lParam;	  //两个参数,不同的消息,一个32位的消息参数,这个值的确切意义取决于消息本身
    DWORD  time;	  //消息放入消息队列中的时间(消息发生时间),在这个域中写入的并不是日期,而是从Windows启动后所测量的时间值。Windows用这个域来使用消息保持正确的顺序。
    POINT  pt;		  //消息放入消息队列时的鼠标坐标.
} MSG;

操作系统将消息封装成MSG结构体投递到消息队列。

用程序的WinMain函数通过执行一段代码从她的队列中来检索Windows送往她的消息。然后WinMain就把这些消息分配给相应的窗口函数以便处理它们,这段代码是一段循环代码,故称为”消息循环”。

SDK 窗口程序创建_第2张图片

拿到消息以后,在通过获取的消息对应处理调用窗口回调函数

SDK 窗口程序创建_第3张图片

与窗口相关消息

官方文档:窗口通知 - Win32 apps | Microsoft Learn

常见的消息如下:

WM_CREATE

当应用程序请求通过调用 CreateWindow 函数创建窗口时发送。 (消息在函数返回之前发送。) 新窗口的窗口过程在窗口创建后、窗口变为可见之前接收此消息。窗口通过其 WindowProc 函数接收此消息。

WM_CLOSE

作为窗口或应用程序应终止的信号发送。窗口通过其 WindowProc 函数接收此消息。

应用程序可以在销毁窗口之前提示用户进行确认,方法是处理 WM_CLOSE 消息,并仅在用户确认选择时调用 DestroyWindow 函数。

默认情况下, DefWindowProc 函数调用 DestroyWindow 函数来销毁窗口。

WM_DESTORY

在窗口被销毁时发送。 从屏幕中删除窗口后,它会发送到正在销毁的窗口的窗口过程。

此消息首先发送到要销毁的窗口,然后发送到子窗口, (是否有任何) 被销毁。 在处理消息期间,可以假定所有子窗口仍然存在。

窗口通过其 WindowProc 函数接收此消息。

WM_QUIT

指示终止应用程序的请求,并在应用程序调用 PostQuitMessage 函数时生成。 此消息导致 GetMessage 函数返回零。此消息没有返回值,因为它会导致消息循环在消息发送到应用程序的窗口过程之前终止。

建立消息循环

	// 消息循环
	BOOL bRet;
	MSG msg;
	while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
	{
		if (bRet == -1) {
			break;
		}
		else {
			TranslateMessage(&msg);// 转换键盘消息
			DispatchMessage(&msg); // 派发消息
		}
	}

GetMessage():getMessage 函数 (winuser.h) - Win32 apps | Microsoft Learn

函数原型

BOOL GetMessage( 
    LPMSG lpMsg, 		//接收消息的MSG结构的地址
    HWND hWnd, 			//窗口句柄,NULL则表示要获取该应用程序创建的所有窗口的消息;
    UINT wMsgFilterMin, //指定消息范围。后面三个参数被设置为默认值
    UINT wMsgFilterMax  //指定消息范围。后面三个参数被设置为默认值
); 

后面三个参数被设置为默认值这就是说你打算接收发送到属于这个应用程序的任何一个窗口的所有消息。

返回值:

  • 在接收到除WM_QUIT之外的任何一个消息后,GetMessage()都返回TRUE。
  • 如果GetMessage收到一个WM_QUIT消息,则返回FALSE
  • 如收到其他消息,则返回TRUE。
  • 如果出现错误,则返回值为 -1。 例如,如果 hWnd 是无效的窗口句柄或 lpMsg 是无效的指针,则该函数将失败。

因此,在接收到WM_QUIT之前,带有GetMessage()的消息循环可以一直循环下去。只有当收到的消息是WM_QUIT时,GetMessage才返回FALSE,结束消息循环,从而终止应用程序。

TranslateMessage():消息用GetMessage读入后(注意这个消息可不是WM_QUIT消息),它首先要经过函数TranslateMessage()进行翻译,这个函数会转换成一些键盘消息,它检索匹配的WM_KEYDOWN和WM_KEYUP消息,并为窗口产生相应的ASCII字符消息(WM_CHAR),它包含指定键的ANSI字符

DispatchMessage():要求Windows将消息传送给在MSG结构中为窗口所指定的窗口过程。Windows会调用函数WindowsProc()来处理这个消息。

在WindowProc()处理完消息后,代码又循环到开始去接收另一个消息,这样就完成了一个消息循环。

窗口过程函数

窗口过程函数是由Windows操作系统内部的消息循环机制调用的。当窗口收到消息时,操作系统会将消息传递给窗口过程函数进行处理。应用程序需要通过消息循环机制来处理这些消息,并尽量减少窗口过程函数的执行时间,避免阻塞主线程。

窗口过程函数是由Windows操作系统内部的消息循环机制调用的。窗口过程具有以下签名。

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

有四个参数:

  • hwnd 是窗口的句柄。
  • uMsg 是消息代码;例如, WM_SIZE 消息指示窗口已调整大小。
  • wParam 和 lParam 包含与消息相关的其他数据。 确切的含义取决于消息代码。

在 Windows 操作系统中,窗口过程函数的返回值类型为 LRESULT,通常是一个整数类型。窗口过程函数可以通过返回不同的值来表示不同的处理结果或状态。

具体来说,窗口过程函数的返回值可以有以下几种情况:

  1. 如果窗口过程函数成功处理了消息并且不需要进一步的默认处理,可以返回一个非零值,表示消息已经被处理了。

  2. 如果窗口过程函数需要继续将消息传递给默认的窗口过程函数进行处理,可以调用 DefWindowProc 函数,并将其返回值作为自己的返回值。

  3. 如果窗口过程函数无法处理某个特定的消息,可以返回 0 或调用 DefWindowProc 函数进行默认处理。

需要注意的是,窗口过程函数是在应用程序的主线程中运行的。因此,如果窗口过程函数执行时间过长,可能会导致应用程序无法响应其他操作,甚至出现假死现象。因此,在编写窗口过程函数时,需要尽量减少函数的执行时间,避免阻塞主线程。

默认消息处理

如果不在窗口过程中处理特定消息,请将消息参数直接传递给 DefWindowProc 函数。 此函数对消息执行默认操作,该操作因消息类型而异。

return DefWindowProc(hwnd, uMsg, wParam, lParam);

switch 处理消息

消息的其他数据包含在 lParam 和 wParam 参数中。 这两个参数都是指针宽度的整数值, (32 位或 64 位) 。 每个代码的含义取决于 (uMsg) 的消息代码。 对于每个消息,需要查找 MSDN 上的消息代码,并将参数强制转换为正确的数据类型。 通常,数据是数值或指向结构的指针。 某些消息没有任何数据。

例如, WM_SIZE 消息的文档指出:

  • wParam 是一个标志,指示窗口是最小化、最大化还是调整大小。
  • lParam 包含窗口的新宽度和高度,因为 16 位值打包成一个 32 位或 64 位数字。 需要执行一些位移来获取这些值。 幸运的是,头文件 WinDef.h 包含执行此操作的帮助程序宏。

典型的窗口过程处理数十条消息,因此它可以长得相当长。 使代码更加模块化的一种方法是将用于处理每个消息的逻辑放在单独的函数中。 在窗口过程中,将 wParam 和 lParam 参数强制转换为正确的数据类型,并将这些值传递给函数。 例如,若要处理 WM_SIZE 消息,窗口过程如下所示:

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_SIZE:
        {
            int width = LOWORD(lParam);  // Macro to get the low-order word.
            int height = HIWORD(lParam); // Macro to get the high-order word.

            // Respond to the message:
            OnSize(hwnd, (UINT)wParam, width, height);
        }
        break;
    }
}

void OnSize(HWND hwnd, UINT flag, int width, int height)
{
    // Handle resizing
}

LOWORD 和 HIWORD 宏从 lParam 获取 16 位宽度和高度值。 (可以在 MSDN 文档中查找每个消息代码的此类详细信息。) 窗口过程提取宽度和高度,然后将这些值传递给 OnSize 函数。

SDK空程序模板

#include 
#include 


void ShowErrorMsg() {
	// 声明一个void 类型的指针,表示任意类型的指针,这是一个缓冲区
	LPVOID lpMsgBuf;

	// 调用函数把错误信息写入缓冲区
	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_SYSTEM |
		FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR)&lpMsgBuf,
		0,
		NULL
	);
	MessageBox(NULL, (LPCTSTR)lpMsgBuf, _T("ERROR"), MB_OK | MB_ICONINFORMATION);
	// 释放由 FormatMessage 函数分配的缓冲区,以避免内存泄漏。
	LocalFree(lpMsgBuf);
}



LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}




int WINAPI wWinMain(
	_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPWSTR lpCmdLine,
	_In_ int nShowCmd)
{
	//设计注册窗口类
	TCHAR szWndClassName[] = { _T("Martlet14fly") };

	WNDCLASSEX wc = { 0 };					//创建一个类名为 wc 的窗口类
	wc.cbSize = sizeof(WNDCLASSEX);         //报错参数不对,官方文档要求填写此参数
	wc.style = CS_VREDRAW | CS_HREDRAW; 	//默认填CS_VREDRAM | CS_HREDRAM 的组合,如果调整了窗口的长度和宽度,就重绘窗口
	wc.lpfnWndProc = MyWindowProc;		    //窗口过程函数(窗口回调函数)
	wc.cbClsExtra = 0;						//默认填0
	wc.cbWndExtra = 0;						//默认填0
	wc.hInstance = hInstance;				//标注这个窗口类属于哪个进程,便于从消息队列获取该进程的消息
	wc.hIcon = LoadIcon(NULL, IDI_HAND);	//图标
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);	//光标
	wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 255)); //用户去背景 白色
	wc.lpszMenuName = NULL;					//菜单
	wc.lpszClassName = szWndClassName;		//窗口的类名

	// 注册窗口类
	if (RegisterClassEx(&wc) == 0) {
		ShowErrorMsg();
		return 0;
	}


	// 创建窗口
	TCHAR szWndName[] = { _T("Martlet") };
	HWND hWnd = CreateWindowEx(
		0,
		szWndClassName,           // 窗口类名
		szWndName,                // 窗口名
		WS_OVERLAPPEDWINDOW,      // 组合属性,窗口的最大化,最小化,调整大小边框,标题栏
		CW_USEDEFAULT,            // 系统默认值
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		NULL,                     // 父窗口句柄
		NULL,                     // 菜单句柄
		hInstance,                // 应用程序实例句柄
		NULL);


	if (hWnd == NULL) 
	{
		ShowErrorMsg();
		return 0;
	}


	// 显示更新窗口
	ShowWindow(hWnd, SW_SHOWNORMAL);
	UpdateWindow(hWnd);

	// 消息循环
	BOOL bRet;
	MSG msg;
	while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
	{
		if (bRet == -1) {
			break;
		}
		else {
			TranslateMessage(&msg);// 转换键盘消息
			DispatchMessage(&msg); // 派发消息
		}
	}

	return 0;
}

如何终止程序

Windows是一种非剥夺式多任务操作系统。只有的应用程序交出CPU控制权后,Windows才能把控制权交给其他应用程序。当GetMessage函数找不到等待应用程序处理的消息时,自动交出控制权,Windows把CPU的控制权交给其他等待控制权的应用程序。由于每个应用程序都有一个消息循环,这种隐式交出控制权的方式保证合并各个应用程序共享控制权。一旦发往该应用程序的消息到达应用程序队列,即开始执行GetMessage语句的下一条语句。

当WinMain函数把控制返回到Windows时,应用程序就终止了。应用程序的启动消息循环前要检查引导出消息循环的每一步,以确保每个窗口已注册,每个窗口都已创建。如存在一个错误,应用程序应返回控制权,并显示一条消息。

但是,一旦WinMain函数进入消息循环,终止应用程序的唯一办法就是使用PostQuitMessage把消息WM_QUIT发送到应用程序队列。当GetMessage函数检索到WM_QUIT消息,它就返回NULL,并退出消息外循环。通常,当主窗口正在删除时(即窗口已接收到一条WM_DESTROY消息),应用程序主窗口的窗口函数就发送一条WM_QUIT消息。

虽然WinMain指定了返回值的数据类型,但Windows并不使用返回值。不过,在调试一应用程序时,返回值地有用的。通常,可使用与标准C程序相同的返回值约定:0表示成功,非0表示出错。PostQuitMessage函数允许窗口函数指定返回值,这个值复制到WM_QUIT消息的wParam参数中。为了的结束消息循环之后返回这个值,我们使用了以下语句:

return msg.wParam ; //表示从PostQuitMessage返回的值

例如:当Windows自身终止时,它会撤消每个窗口,但不把控制返回给应用程序的消息循环,这意味着消息循环将永远不会检索到WM_QUIT消息,并且的循环之后的语句也不能再执行。Windows的终止前的确发送一消息给每个应用程序,因而标准C程序通常会的结束前清理现场并释放资源,但Windows应用程序必须随每个窗口的撤消而被清除,否则会丢失一些数据。

你可能感兴趣的:(SDK,SDK,C++,Windows,1024程序员节)