9. Windows编程入门:C/C++创建窗口程序详解

1.前言

第一章我曾说过,C/C++ 是无所不能的,但若你只学C/C++本身,就又会发现自己什么都干不了。

所以从这章开始,我们就正式开始学习一些特定方向的知识,从最熟悉的开始,比如我们用的最多的windows平台上的软件,都是怎么开发出来的?

这就涉及到了windows平台的软件开发相关知识。

2.windows编程介绍

所谓windows编程,就是指我们写的程序代码最终只能在windows系统上运行,该程序乃至源代码在其它平台无法运行以及编译(比如安卓、linux、mac等等)。

而之所以我们在windows编程中写的程序无法在其它系统中运行,就是因为我们调用了很多win API函数。

比如上一章我们用到的URLDownloadToFileA,这个强大的函数就是windows系统提供给我们用的,而其它平台就没有这个函数。

所以总结一下就是,C/C++语言本身是跨平台、无所不能的,可如果你的代码中包含了某个平台所特有的函数,那么你所写的代码,就只能在该平台编译运行。

所谓windows编程,指的就是使用win API的过程,通过win API进行windows系统上的程序开发。

目前windows系统的总体市场占有率依旧还是很高,并且其生态已经相当完善了,相信即使再过十年二十年,windows都很难被淘汰,所以了解一下windows编程对你来说应该是很有帮助的。

windows系统是微软旗下的产品,同样最强IDEVS,也是微软旗下的产品,所以我们用VS开发windows程序是非常方便的,两者天然适配。

3.从零写一个windows窗口

windows程序最大的特色自然就是窗口了,前面我们用EGE库写的推箱子小游戏,其实也是封装的win API函数,所以只能在windows平台使用。

这次我们就不用EGE库,而是用最底层的win API来开发一个窗口,来看一看我们当初用EGE几个函数就能完成的任务,实际是怎么实现的!

3.1 建立窗口的过程

想要在windows系统上创建一个窗口,可以简单划分为三个步骤:

  1. 向系统注册一个窗体类
  2. 根据窗体类创建窗口
  3. 进入消息循环

虽然看上去很简单,但由于其中涉及到了大量Win API函数,所以新手一般刚开始学习的时候都会感到异常的痛苦。

接下来就开始详细介绍具体各个步骤。

3.2 建立项目

还是老规矩,在解决方案中新建项目day9-windows,不过不是控制台应用了,而是窗口应用,我们需要选择windows桌面向导来帮我们创建一个window程序:

image-20231205161819649

这里选择桌面应用程序,以及空项目:

image-20231205161939042

设为启动项,添加源文件main.cpp

image-20231205162045348

前面一直没说过,请注意,在命名这些文件与项目时,尽量采用英文字母,拼音也行,一定要避免使用中文。

因为存在编码的问题,即使现在你运行没有问题,说不定未来的某一天就出问题了,而且还很难找到原因,用英文的适配性永远是最高的。

至此一个空的带窗口程序空项目就建立完成了,但现在才刚刚开始!

3.3 WinMain函数

学到这里,相信至少对C/C++基础应该是有一定熟悉的了,一个最简单的C/C++程序就必须要写上main函数,否则无法编译成功,因为这是控制台程序的入口函数。

这里也一样,当你想写带界面的窗口程序,你就需要将入口函数改为WinMain,这是窗口程序的入口函数。

函数原型:

int WINAPI WinMain(
  [in] HINSTANCE hInstance, //当前应用程序的实例
  [in] HINSTANCE hPrevInstance, //这个不用管,想了解的可参考官方文档
  [in] LPSTR     lpCmdLine, //传入的命令行参数
  [in] int       nShowCmd //控制当前应用窗口如何显示
);

官方文档点这里

前面的[in]等只是让你知道这个参数是输入还是输出的,其实就是空宏,我前面也提到过,可以直接删除掉,改下格式后代码如下:

在这里插入图片描述

强调一下,不要试图去记忆这些东西,这会加重你的学习负担。

你只需要知道有这么个东西,然后当你需要用到的时候直接复制即可,至于各个参数的含义,会在后面具体的代码中用到,用的多了就自然而然的记住了。

3.4 注册窗口类

写好WinMain入口函数后,就可以开始来创建我们的窗口了,也就是前面提到的三个步骤。

其中第一个步骤就是向系统注册窗口类,主要用到API函数RegisterClass,函数原型为:

ATOM RegisterClassA(
  [in] const WNDCLASSA *lpWndClass //填入要注册的窗口结构
);

它只有一个参数结构WNDCLASSA,含义如下:

typedef struct tagWNDCLASSA {
  UINT      style;  //设置窗口格式,可参考官方文档,一般设置为水平重画与垂直重画:CS_HREDRAW | CS_VREDRAW
  WNDPROC   lpfnWndProc; //窗口的回调函数,也就是窗口接收到消息后,交给哪个函数处理
  int       cbClsExtra; //为类额外分配内存,一般为0
  int       cbWndExtra; //为窗口额外分配内存,一般为0
  HINSTANCE hInstance; //程序实例,这里就用到了WinMain函数的第一个参数hInstance
  HICON     hIcon; //设置程序图标
  HCURSOR   hCursor; //设置程序光标
  HBRUSH    hbrBackground; //设置程序背景色
  LPCSTR    lpszMenuName; //设置菜单名称
  LPCSTR    lpszClassName; //设置类名称
} WNDCLASSA, *PWNDCLASSA, *NPWNDCLASSA, *LPWNDCLASSA;

其使用方式为:

#include<Windows.h>
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd) {
	WNDCLASS wndcls; //创建一个窗体类
	wndcls.cbClsExtra = 0;//类的额外内存,默认为0即可
	wndcls.cbWndExtra = 0;//窗口的额外内存,默认为0即可
	wndcls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//获取画刷句柄(将返回的HGDIOBJ进行强制类型转换)
	wndcls.hCursor = LoadCursor(NULL, IDC_CROSS);//设置光标
	wndcls.hIcon = LoadIcon(NULL, IDI_ERROR);//设置窗体左上角的图标
	wndcls.hInstance = hInstance;//设置窗体所属的应用程序实例
	wndcls.lpfnWndProc = NULL;//设置窗体的回调函数,暂时没写,先设置为NULL,后面补上
	wndcls.lpszClassName = L"test";//设置窗体的类名
	wndcls.lpszMenuName = NULL;//设置窗体的菜单,没有,填NULL
	wndcls.style = CS_HREDRAW | CS_VREDRAW;//设置窗体风格为水平重画和垂直重画
	RegisterClass(&wndcls);//向操作系统注册窗体
}

在上述代码中,还用到了三个额外的API:

  • GetStockObject:获取电脑自带的画刷,也可以获取其它资源。
  • LoadCursor:加载光标,也就是我们电脑屏幕上常见的箭头,十字,转圈等图形
  • LoadIcon:加载图标资源

这三个函数很简单,因为参数很少,不好理解的可能是这些参数应该怎么填?从哪里来?

既然是win API函数,那当然是查看官方文档了,虽然是英文,但现在的翻译也还是比较强大的,应该也难不倒诸位吧。

比如这里用GetStockObject函数举例,进入官方文档,就可以看到可填参数有很多:

image-20231205162504997

后面两个函数还多一个程序实例的参数,我都填的NULL,意思就是从系统默认中加载即可,因为我们也没有自己做的图标。

3.5 创建窗口

这里用到的CreateWindow就可以说是一个超级函数了,参数多达11个,它的作用就是创建一个窗口。

函数原型:

void CreateWindowA(
  [in, optional]  lpClassName,//窗口类名,也就是上面注册窗口类时填的类名
  [in, optional]  lpWindowName, //窗口名,也就是窗口显示时,左上角显示的名称
  [in] dwStyle, //窗口风格,一般填WS_OVERLAPPEDWINDOW即可,这样创建的窗口就有最大化,最小化等等等等属性。。。
  [in] x, //窗口最开始被创建在哪里?这里填X坐标,一般默认CW_USEDEFAULT即可
  [in] y, //窗口最开始被创建在哪里?这里填Y坐标,一般默认CW_USEDEFAULT即可