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系统是微软旗下的产品,同样最强IDE
:VS
,也是微软旗下的产品,所以我们用VS开发windows
程序是非常方便的,两者天然适配。
3.从零写一个windows窗口
windows程序最大的特色自然就是窗口了,前面我们用EGE
库写的推箱子小游戏,其实也是封装的win API函数,所以只能在windows
平台使用。
这次我们就不用EGE库,而是用最底层的win API来开发一个窗口,来看一看我们当初用EGE几个函数就能完成的任务,实际是怎么实现的!
3.1 建立窗口的过程
想要在windows系统上创建一个窗口,可以简单划分为三个步骤:
- 向系统注册一个窗体类
- 根据窗体类创建窗口
- 进入消息循环
虽然看上去很简单,但由于其中涉及到了大量Win API函数,所以新手一般刚开始学习的时候都会感到异常的痛苦。
接下来就开始详细介绍具体各个步骤。
3.2 建立项目
还是老规矩,在解决方案中新建项目day9-windows
,不过不是控制台应用了,而是窗口应用,我们需要选择windows桌面向导来帮我们创建一个window程序:
这里选择桌面应用程序,以及空项目:
设为启动项,添加源文件main.cpp
:
前面一直没说过,请注意,在命名这些文件与项目时,尽量采用英文字母,拼音也行,一定要避免使用中文。
因为存在编码的问题,即使现在你运行没有问题,说不定未来的某一天就出问题了,而且还很难找到原因,用英文的适配性永远是最高的。
至此一个空的带窗口程序空项目就建立完成了,但现在才刚刚开始!
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
函数举例,进入官方文档,就可以看到可填参数有很多:
后面两个函数还多一个程序实例的参数,我都填的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即可