这两天阿瘦找我给他的一个程序写个界面,听说是要参加啥三创比赛(都大四老狗了,汗),然后问要用什么语言——C/C++,Windows平台的。他之前没怎么接触过C++方面的界面开发,然后我就开始了一波Windows教学,顺便自己也回忆回忆(大一大二玩了一年多,之后几乎就没碰过)。

整体流程

#include <windows.h>// 函数提前声明
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);/* Win32界面应用的入口函数:程序将从此入口开始执行  */
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {WNDCLASSEX wc; /* 用于声明窗口属性的窗口类,WNDCLASSEX是WNDCLASS的拓展 */HWND hwnd;     /* 窗口句柄,指向我们创建的窗口 */MSG msg;       /* 存放消息循环过程中产生的消息 *//* 将结构体置为空,我们只修改结构体中的一部分 */memset(&wc,0,sizeof(wc));/* 结构体的第一个字段,主要用于GetClassInfoEx能直接得到结构体大小 */wc.cbSize       = sizeof(WNDCLASSEX); /* 这个函数指针是核心,指定了消息循环过程中消息最终给哪个回调函数处理 */wc.lpfnWndProc    = WndProc;wc.hInstance     = hInstance; /* 应用实例句柄 */wc.hCursor        = LoadCursor(NULL, IDC_ARROW); /* 窗口使用的鼠标样式 *//* 指定窗口的背景颜色 */wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wc.lpszClassName = "WindowClass";                   /* 窗口类的名字*/wc.hIcon      = LoadIcon(NULL, IDI_APPLICATION); /* 窗口图标 */wc.hIconSm        = LoadIcon(NULL, IDI_APPLICATION); /* 窗口小图标 *//* 注册窗口类:WNDCLASSEX用RegisterClassEx注册,WNDCLASS用RegisterClass注册 */ if(!RegisterClassEx(&wc)) {// 注册失败,退出程序 MessageBox(NULL, "Window Registration Failed!","Error!",MB_ICONEXCLAMATION|MB_OK);return 0;}/* 使用刚注册的窗口类创建窗口 */hwnd = CreateWindowEx(WS_EX_CLIENTEDGE,"WindowClass","Caption",WS_VISIBLE|WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, /* 窗口x坐标 */CW_USEDEFAULT, /* 窗口y坐标 */640, /* 窗口宽度 */480, /* 窗口高度 */NULL,NULL,hInstance,NULL);if(hwnd == NULL) {// 窗口句柄为空说明窗口创建失败,退出程序 MessageBox(NULL, "Window Creation Failed!","Error!",MB_ICONEXCLAMATION|MB_OK);return 0;}/*消息循环机制是整个应用的核心,消息循环过程中产生的所有消息都会发送给WndProc GetMessage方法会发生阻塞直到获取到消息后才会返回, 所以这个循环并不会产生不合理的高CPU占用。 */while(GetMessage(&msg, NULL, 0, 0) > 0) { /* 会一直循环下去,直到接收到WM_QUIT消息 *//* 对消息进行翻译(比如把按键码翻译成对应的字符) *//* 如果程序更复杂一点可能还需要调用TranslateAccelerator翻译菜单快捷键*/TranslateMessage(&msg); /* 把消息分发给窗口类中定义的消息回调函数,也就是WndProc */DispatchMessage(&msg);}// 正常结束,把WM_QUIT的wParam参数中的exitCode返回给操作系统 return msg.wParam;
}/*** 窗口的所有消息都会被分发到这个回调函数中 * hwnd窗口句柄,指向当前窗口* message 窗口消息* wParam,lParam 消息的附加参数,每种消息参数的意义都有所不同* LRESULT返回消息处理的结果 */
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {switch(Message) {/* 销毁窗口消息 */case WM_DESTROY: {// 往消息队列中发一个WM_QUIT消息,退出程序 PostQuitMessage(0);break;}/* 其他所有的消息直接交给Windows系统默认的回调函数处理 */default:return DefWindowProc(hwnd, Message, wParam, lParam);}return 0;
}

看到上面这段代码,倍感亲切啊

Windows程序的入口

我们知道标准C的入口函数是main,对于Windows程序来说程序的入口函数是WinMain

不要被main函数限制了你的想象力,main只是入口函数的标记,程序运行时会根据main所指向的函数地址找到要从哪条指令开始执行。我们编译链接的时候可以自行指定这个标记。

在VC的链接器中有**/SUBSYSTEM:CONSOLE/SUBSYSTEM:WINDOWS**两个选项

应用程序类型 入口函数(入口点) 嵌入执行体的启动函数
处理ANSI字符和字符串的GUI应用 程序 _tWinMain (WinMain) WinMainCRTStartup
处理Unicode字符和字符串的GUI应 用程序 _tWinMain (wWinMain) wWinMainCRTStartup
处理ANSI字符和字符串的CUI应用 程序 _tmain (Main) mainCRTStartup
处理Unicode字符和字符串的CUI应 用程序 _tmain (Wmain) wmainCRTStartup

如果指定了**/SUBSYSTEM:WINDOWS链接器开关,链接器就会寻找WinMainwWinMain函数。如果没有找到这两个函数,链接器将返回一个“unresolved external symbol(未解析的外部符号)”错误;否则,它将根据具体情况分别选择WinMainCRTStartupwWinMainCRTStartup**函数。

这篇文章演示了gcc和vc两种编译器自定义入口函数。

WinMain函数的定义如下:

// 返回值为int类型,如果程序在进入消息循环之前就结束了,应该返回0
// 如果函数进入消息循环后,在收到WM_QUIT消息后结束程序,应该返回消息中的wParam参数
int CALLBACK WinMain(// 当前应用的实例句柄_In_ HINSTANCE hInstance,// 前一个应用的实例句柄,大部分情况下为NULL_In_ HINSTANCE hPrevInstance,// 当使用命令行启动应用时,这个参数可以获取完整的命令_In_ LPSTR     lpCmdLine,// 控制窗口如何显示,这个参数一般会传给ShowWindow_In_ int       nCmdShow
);

消息循环机制

消息循环(message loop)机制是整个Windows应用程序的核心。

Windows界面程序是基于事件驱动的,在启动一个进程后,操作系统会为它维护一个单独的消息队列。操作系统会将窗口上的操作以消息的形式放入消息队列,比如鼠标在窗口上移动,窗口获取焦点后键盘敲击,点击窗口的按钮等。
程序员通过调用GetMessage函数从消息队列中获取消息,如果队列中没有消息,GetMessage函数将会阻塞。然后调用DispatchMessage将消息交给程序员定义的消息回调函数WndProc处理。

注意:整个过程都是在主线程处理的,WndProc也会在主线程中回调,所以WndProc中不要执行一些耗时的操作,比如网络请求、耗时的计算,否则主线程阻塞在这,处理不了其他的消息,将会导致窗口“假死”。

消息循环机制不仅存在Windows程序中,其实安卓、Web这些带UI界面的程序本质上都是基于消息循环机制。

GetMessage函数是阻塞式的,如果消息队列中没有消息这个函数会发生阻塞,这时循环是“静止”的,这往往意味着低的CPU占用,对系统、对其它应用程序都是友好的。

另外还有一个PeekMessage的函数,它可以检测消息队列中是否有消息,如果没有消息它会返回而不是发生阻塞,如果有消息可以通过最后一个参数传递**PM_NOREMOVEPM_REMOVE**决定是否从消息队列中移除消息。

HWND hwnd;
BOOL fDone;
MSG msg; fDone = FALSE;
while (!fDone)
{ fDone = DoAnyting(); // 用户定义处理其他事情// 如果队列中有消息就取出消息并从队列中移除while (PeekMessage(&msg, hwnd,  0, 0, PM_REMOVE)) { switch(msg.message) { case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_KEYDOWN: // // 处理消息break;case WM_QUIT:fDone = TRUE; break;} }
}

所以游戏编程里经常会出现这样的代码(DX红龙书上的代码片段):

MSG msg;
::ZeroMessage(&msg, sizeof(msg));static float lastTime = (float)timeGetTime();
while(msg.message != WM_QUIT)
{// 游戏对帧频要求较高,需要不间断的切换缓冲页面if(::PeekMessage(&msg, 0, 0, 0, PM_REMOVE)){::TranslateMessage(&msg);::DispatchMessage(&msg);}else{float currTime = (float)timeGetTime();float timeDelta = (currTime - lastTime) * 0.001f;display(timeDelta); // 调用显示函数lastTime = currTime;}
}

参考:

  • 消息与消息队列:https://msdn.microsoft.com/EN-US/library/windows/desktop/ms632590.aspx

回调函数-消息处理函数

上面WinMain的代码,90%以上都是“样板代码”:通常创建一个Windows窗口应用就必须按照这个流程,可变化的地方很少。

创建一个窗口要写这么多“样板代码”,很明显上面的代码是需要封装的。MFC,Duilib中都有对这些“样板代码”的封装。

我们实际要干的活都在WndProc这个回调函数中。

在让我们认识一下这个回调函数:

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

窗口回调函数有四个参数,事实上这四个参数都来MSG这个消息结构体,因为窗口回调函数就是用来处理消息的:

typedef struct tagMSG {// 窗口句柄,窗口的处理函数会接受这个消息HWND   hwnd;// 消息IDUINT   message;// wParam和lParam都是消息附加信息,// 具体值取决于具体的消息(不同消息附加内容意义不一样)WPARAM wParam;LPARAM lParam;// 消息发出的时间DWORD  time;// 消息发出时光标的位置POINT  pt;
} MSG, *PMSG, *LPMSG;

message是消息的ID号,UNIT类型的有四个字节,不过能用的只有两个低字节(0x0000~0xFFFF),高字节被系统保留。
消息主要分为两种:系统消息和用户自定义消息

  • 系统消息:0x0000 ~ 0x03FF(WM_USER-1)
  • 用户自定义消息:用户自定义消息也分两种
    • 窗口类私有消息:0x0400(WM_USER) ~ 0x7FFF(WM_APP-1)
    • 应用程序私有消息:0x8000(WM_APP) ~ 0xBFFF

还有一种消息范围在0xC000 ~ 0xFFFF,这个范围内的消息是通过RegisterWindowMessage函数进行注册得到的,这些消息ID能保证在整个操作系统是唯一的。

大多数情况下我们都是对系统消息进行处理,这些系统消息的ID都定义在在WinUser.h头文件中。

/** Window Messages*/#define WM_NULL                         0x0000
#define WM_CREATE                       0x0001
#define WM_DESTROY                      0x0002
#define WM_MOVE                         0x0003
#define WM_SIZE                         0x0005
...

消息很多,每个消息的附加参数意义也不一样,你不可能靠脑子去记的,所以需要经常查文档:https://msdn.microsoft.com/EN-US/library/windows/desktop/ms644927.aspx#system_defined

参考:

  • 消息处理函数:https://msdn.microsoft.com/en-us/library/windows/desktop/ms632593.aspx

句柄与指针

前面频繁出现的一个东西HWND——窗口句柄。当然除了窗口句柄(HWND),还有应用实例句柄(HINSTANCE)、文件句柄(HFILE)等各种句柄。有了这些句柄我们就可以操作对应的Windows对象

前面为了方便理解,我们把它叫做指针。每一个程序员都有一颗好奇的心,你肯定尝试过使用这个“指针”去看看它指向的那段内存到底存的是不是窗口或者应用实例,但现实让你碰了一鼻子灰——句柄和指针还是不同的。

指针指向系统中物理内存的地址,而句柄是windows在内存中维护的一个对象内存物理地址列表的整数索引,句柄是一种指向指针的指针

使用Windows对象的句柄规范对系统资源的访问,这主要有两个原因:

  • 给你一个Windows对象句柄而不是整个对象,是因为微软更新Windows系统时可能会修改Windows对象的内部结构(比如多加两个字段),但是API中全部使用句柄,那么系统维护的时候就可以尽可能少的修改API接口,让Windows开发者也尽可能少的修改代码或者不修改代码
  • 为了系统安全性,Windows内部为每个对象维护了一个访问控制列表(ACL),只有指定进程可以在对象上操作,每次为对象创建句柄的时候,系统都会去检查对象的ACL。

关于Windows ACL的详细内容可以参考:https://msdn.microsoft.com/en-us/library/windows/desktop/aa374860.aspx
对指针和句柄的理解,我个人觉的这篇文章讲的非常好,言简意赅:https://blog.csdn.net/u014041012/article/details/44878375

参考:

  • Windows核心编程

  • 句柄与Windows对象:https://msdn.microsoft.com/en-us/library/windows/desktop/ms724457.aspx

Win32开发小回忆相关推荐

  1. GCC for Win32开发环境介绍

    GCC for Win32开发环境介绍(1) 第一章 在视窗操作系统下的GCC 第一节GCC家族概览 GCC是一个原本用于Unix-like系统下编程的编译器.不过,现在GCC也有了许多Win32下的 ...

  2. 跟我一起玩Win32开发

    跟我一起玩Win32开发(1):关于C++的几个要点 我不知道各位,一提起 C++ ,第一感觉是什么?而据俺的观察,许多人几乎成了 " 谈 C 色变 " .不管是 C 还是 C++ ...

  3. 从零开始搭建一个GIS开发小框架(七)——GMap.Net组件WPF版本加载POI性能测试

    目录 1.概述 2.工作内容和步骤: 3.测试结果 4.视频演示 5.总结 6.结束和致谢 1.概述 GIS项目中除了多边形那一套功能,另一个应用最广泛的场景就是POI数据的分析挖掘.今天就给大家演示 ...

  4. 日常安排php,PHP日常开发小技巧

    PHP日常开发小技巧 导语:PHP语言中,如果你懂得一些开发技巧,那么对你学PHP,会有很大的帮助.下面的是百分网小编为大家整理的PHP日常开发小技巧,希望对你能有所帮助. PHP批量取得checkb ...

  5. 用什么服务器开发小程序,开发小程序用什么服务器系统

    开发小程序用什么服务器系统 内容精选 换一换 按照翻译方式的不同,高级语言通常可以分为两类:一类是编译翻译,一类是解释翻译,分别对应着编译型语言和解释型语言.编译型语言典型的如C.C++语言,都属于编 ...

  6. flutter开发小程序_为什么我认为Flutter是移动应用程序开发的未来

    flutter开发小程序 I dabbled a bit in Android and iOS development quite a few years back using Java and Ob ...

  7. mpvue开发小程序所遇问题及h5转化方案

    项目结构 |---build|---pages.js文件目录|---src|---component子组件|---pages|---业务页面|---store,vuex储存|---utils|---请 ...

  8. Silverlight 游戏开发小技巧:动感小菜单2

    Silverlight 游戏开发小技巧:动感小菜单2 动感小菜单其实是想模仿Apple的菜单按钮设计制作,但是画虎不成反类犬,看起来有点别扭,昨天各位园友提了这方面的建议,感觉太硬如果加入动画可能更好 ...

  9. Silve“.NET研究”rlight 游戏开发小技巧:传说中的透视跑马灯

    昨夜元宵佳节,各种灯会热闹非凡,伴随烟火灿烂好不热闹,可惜一点也没看着T_T,那就写一个跑马灯吧,可是跑马灯并不稀奇,各位高手们已经写过而且都各有特点,所以,写也要写点有特色的才好,游戏中经常能看到一 ...

最新文章

  1. vue中mixins的使用方法和注意地方
  2. 前端学习(3271):js中this的使用
  3. html读取本地txt_手机本地电子书籍阅读器 — 静读天下
  4. iOS内存泄漏的常见情况
  5. css-动画-transition-过渡动画
  6. 中台做不好,就会成为“钟台”!阿里高管离职创业,这次会搞砸吗
  7. Swift Basic 3
  8. delphi 中assert
  9. PDF阅读器使用技巧
  10. matlab delay用法,请教Vensim中DELAY1I函数使用的单位设置
  11. Android 垃圾回收机制★★★
  12. 百度火星坐标转wgs84
  13. 简谈浅层拷贝和深层拷贝
  14. NR的SSB子载波间隔讨论——为何无60kHz
  15. linux内存管理笔记(十一)---CMA
  16. AngularJS学习笔记-2
  17. html手抄报怎么制作软件,怎么制作Word电子小报?
  18. OPN/SPN/PLMN的区别
  19. 分数换算小数补0法_一年级数学0基础的全过来,最全知识点及基本方法,包你数学不补课都90+...
  20. 程序员找工作时的技巧

热门文章

  1. Android的windows虚拟机,不用虚拟机 WindowsAndroid让PC运行安卓
  2. SAP项目采购申请与预留,收货入库,项目领用or项目发货,及报表查看操作
  3. 思科向 IETF 提交 TrustSec 标准草案
  4. html cellpadding css,CSS中cellspacing和cellpadding属性用法
  5. F - 悼念512汶川大地震遇难同胞SDUT
  6. python:实现二进制转十六进制算法(附完整源码)
  7. CSS 自适应导航菜单
  8. html5中插入视频无效原,来自wmv的h264剪辑在iPad上的HTML5视频中无效(黑屏)
  9. 信息系统项目管理师论文-项目整体管理
  10. arguments的使用