Windows编程入门

  • HelloVisualStudio这个项目介绍了,Windows程序的“心脏”——WinMain函数,以及MessageBox函数的详细用法。
  • FirstBlood!这个项目介绍了,PlaySound函数,以及如何连接库文件。
  • GameCore这个项目详细介绍如何完整的创建一个窗口,后面两部分都会这个窗口框架。】

HelloVisualStudio

这个项目就是认识一下Win32窗口程序,最终效果如下图,嗯,就是启动了一个MessageBox函数。

关键词:WinMain,MessageBox

完整代码在此(因为代码较短我就直接贴出来了,后面代码较多的时候我就放一些主要的函数就行了)

#include <Windows.h>
int WINAPI WinMain(  HINSTANCE hInstance,  HINSTANCE hPrevInstance,  LPSTR lpCmdLine, int nShowCmd )
{MessageBox(NULL,L"你好,Visual Studio! ", L"消息窗口",0);return 0;
}
  • HelloVisualStudio源代码目录

下面就开始对WinMain函数进行介绍,在MSDN中查到它有如下原型

int WINAPI WinMain(_In_ HINSTACE hInstance, _In_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow);

首先介绍一下WINAPI(其实不用管),这个就是_stdcall,只是为了让我们清楚知道这里的_stdcall表示的是一种调用约定,它让编译器知道了应当以Windows兼容的方式来产生机器指令。

同理,_In_ 表示可以理解是一个宏,表示是一个输入参数,_Out_ 是一个输出参数,_In_opt_ 表示输入、可选的(optional)。

函数的第一个参数,HINSTANCE类型的hInstance,它表示该程序当前运行的实例句柄(其实就是一个ID号)。HINSTANCE 是“句柄型”数据类型。相当于装入到了内存的资源的ID。HINSTANCE对应的资源是instance.句柄实际上是一个 无符号长整数。但它是“句柄型”,所以你不能把它当成真的无符号长整数,拿来派别的用处,例如,不能拿来做四则运算。

第二个参数,HINSTANCE类型的hPrevInstance,他表示当前实例的前一个实例的句柄。MSDN中表示在Win32环境下,这个参数总是取NULL。

第三个参数,LPSTR类型的lpCmdLine,lp表示这个参数是一个指针,cmd表示command。例如,在Windows7操作系统下的E盘有一个交ForTheDream.txt的文件,我们用鼠标双击这个文件时将启动记事本程序(notepad.exe)。此时系统会将E:\ForTheDream.txt作为命令行的参数传递给记事本程序的WinMain函数,记事本程序在得到这个文件的文件路径后,就会在窗口中正确显示这个文件的内容。

第四个参数,int类型的nCmdShow,指定程序窗口应该如何显示,是最大化,最小化,还是隐藏等等。可以参照DOTA2客户端可以选择窗口化还是全屏,这个信息是写在本地的配置的文件,启动后,系统会将配置文件的参数传递给WinMain函数。

下面介绍一下,示例程序中用于显示出“你好,Visual Studio!”消息窗口的那句代码的函数原型,另外提一句,这些函数原型都可以在MSDN的官方文档上查到,所以我们要养成多查文档的好习惯。

int WINAPI MessageBox(_In_opt_ HWND hWnd, _In_opt_ LPCTSTR lpText,_In_opt_ LPCTSTR lpCaption,_In_ UINT uType);
  • 第一个参数,HWND类型的hWnd,h 是类型描述,表示句柄(handle), Wnd 是变量对象描述,表示窗口,所以hWnd 表示窗口句柄,表示我们显示的消息框所属的窗口的句柄。 在Windows应用中,窗口都是通过窗口句柄(HWND)来标识的。我们要对某个窗口进行操作的话,首先就是要得到这个窗口的句柄。HANDLE(句柄)是Windows操作系统中的一个概念。在Windows程序中,有各种各样的资源(窗口、图标、光标等),系统在创建这些资源时会为它们分配内存,并返回标示这些资源的标示号,即句柄。
    句柄指的是一个核心对象在某一个进程中的唯一索引,而不是指针。
    上古时期的程序员, 肯定都知道Handle对象, 一般中文翻译成句柄. 一般的Handle在实现上, 都是一个整数, 而这个整数可以理解为一个指针, 指针指向的地址呢, 又保存了另外一个指针. 之所以这么搞, 是因为这样搞可以让真实的对象可以挪动。
    考虑一个一个对象A, 保存在Handle里面, 由于某种原因, 我需要把这个对象A从原来的位置移走, 那么移走之后的对象叫对象B, 那我只需要修改一下Handle里面的指针, 就可以保证正确性, 然后在用户看来, 还是同一个对象, 因为Handle没有改变。
    正是因为Handle这种特性, 所以可以基于Handle做一个GC系统. miloyip翻译的那本游戏编程的书里面也讲过, 之前在主机上有人用Handle来管理内存。

  • 第二个参数,LPCTSTR类型的lpText,它是一个以NULL结尾的字符串,表示所要显示的消息的内容。
    L"你好,Visual Studio! "其中L表示我们要把字符串"你好,Visual Studio! "转化为宽字符版。因为在Visual Studio中默认使用的是Unicode字符集。否则就会有不能将参数从const char[15]转换为LPCWSTR的错误。

  • 第三个参数,LPCTSTR类型的lpCaption,它也是一个以NULL结尾的字符串,在其中填我们要显示的消息框的标题的内容。

  • 第四个参数,UINT类型的uType,表示我们消息窗口需要什么样的样式。这里我们看到是0,其实它对应

#define MB_OK                       0x00000000L

消息框带有唯一一个按钮:OK。需要注意的是,MB_OK是系统默认的MessageBox样式。

是不是对上面神奇的命名格式表示不解,我在这里就直接贴出一些,常用的命名规范,但注意我们可以遵守,但不要墨守成规。
命名规则(适用于C++与Java):

描述 实例
类名混合使用大小写,首字母大写 ClassName
类型定义,包括枚举和typedef,混合使用大小写,首字母大写 TypeName
枚举类型除了混合使用大小写外,总以复数形式表示 EnumeratedTypes
局部变量除了混合使用大小写外,且首字母小写,其名字应该与底层数据类型无关,而且应该反映该变量所代表的事物 localVariable
子程序参数的格式混合使用大小写,且首字母大写,其名字应该与底层数据类型无关,而且应该反映该变量所代表的事物 RoutineParameter
对类的多个子程序可见(且只对该类可见)的成员变量名用m_前缀 m_ClassVariable
全局变量名用g_前缀 g_GlobalVariable
具名常量全部大写 CONSTANT
宏全部大写,单词间用分割符"_"隔开 SCREEN_WIDTH
枚举类型成员名用能反映其基础类型的。单数形式的前缀——例如,Color_Red,Color_Blue Base_EnumberatedType

匈牙利命名法中常用的小写字母的前缀,如下表:

前缀写法 类型 描述 实例
ch char 8位字符 chGrade
ch TCHAR 如果_UNICODE定义,则为16位字符 chName
b BOOL 布尔值 bEnable
n int 整形(其大小依赖于操作系统) nLength
n UINT 无符号值(其大小依赖于操作系统) nHeight
w WORD 16位无符号值 wPos
l LONG 32位有符号整型 lOffset
dw DWORD 32位无符号整型 dwRange
p * 指针 pDoc
lp FAR* 远指针 lpszName
lpsz LPSTR 32位字符串指针 lpszName
lpsz LPCSTR 32位常量字符串指针 lpszName
lpsz LPCTSTR 如果_UNICODE定义,则为32位常量字符串指针 lpszName
h handle Windows对象句柄 hWnd
lpfn callback 指向CALLBACK函数的远指针 lpfnName

在Windows程序中,有各种各样的资源,比如窗口、图标、光标等。系统创建这些资源时会为它们分配内存,并返回标识这些资源的标识号。这些标识号就是句柄。handle

关键字字符组合表格如下:

描述内容 使用的关键字母组合
最大值 Max
最小值 Min
初始化 Init
临时变量 T(或Temp)
源对象 Src
目标对象 Dest

FirstBlood!

一个播放Dota中拿下第一个人头时“Firstblood!”音效的实例程序。

关键词:PlaySound,链接

#include <Windows.h>
#pragma comment(lib,"winmm.lib")
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{PlaySound(L"FirstBlood.wav",NULL,SND_FILENAME | SND_ASYNC);  //播放音效//显示一个消息框MessageBox(NULL,L"First blood! 你好,游戏开发的世界,我们来征服你了!",L"First blood! 消息窗口",0);return 0;
}
  • FirstBlood!源代码目录

一般高级语言程序编译的过程:预处理、编译、汇编、链接。作者在P51提到的“如果要使用PlaySound函数的话,必须在编译之前链接winmm.lib库文件。这里我觉得措辞应该改为在程序中声明。
可以在MSDN中查到PlaySound函数的定义如下:

BOOL PlaySound(LPCTSTR pszSound, HMODULE hmod, DWORD fdwSound);

具体的参数名,上面的表格都有提到,这里就不做过多的介绍了。

GameCore

窗口代码,后面学习GDI和DirectX游戏编程时所用的基本框架

关键词:

1、Windows窗口程序的书写思路——WinMain函数,窗口创建四部曲、消息循环、窗口类的注销、窗口过程函数

2、窗口创建四部曲:窗口类的设计、窗口类的注册、窗口的正式创建、窗口的显示与更新

【1】窗口创建四部曲之一:开始设计一个完整的窗口类

WNDCLASSEX wndClass = { 0 };    //用WINDCLASSEX定义一个窗口类wndClass.cbSize = sizeof(WNDCLASSEX); //设置结构体的字节数大小wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式wndClass.lpfnWndProc = WndProc;       //设置指向窗口过程函数的指针wndClass.cbClsExtra = 0;        //窗口类的附加内存,取0就可以了wndClass.cbWndExtra = 0;       //窗口的附加内存,依然取0就可以了wndClass.hInstance = hInstance;   //指定包含窗口过程的程序的实例句柄wndClass.hIcon = (HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE);   //本地加载自定义ico图标wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);      //指定窗口类的光标句柄wndClass.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);   //为hbrBackground成员指定一个灰色画刷句柄wndClass.lpszMenuName = NULL;  //用一个以空终止的字符串,指定菜单资源的名字wndClass.lpszClassName = L"ForTheDreamOfGameDevelop";  //用一个以空终止的字符串,指定窗口类的名字。

这里就是创建一个WNDCLASSEX类,然后填好的各项属性。唯一要注意的是:

wndClass.lpfnWndProc = WndProc;     //设置指向窗口过程函数的指针

这里先介绍一下,针对Windows的消息处理机制,窗口过程函数被调用的过程:

  • 第一步:在设计窗口类的时候,将窗口过程函数的地址赋值给lpfnWndProc成员变量。
  • 第二步:调用Regsiter(&Wndclass)注册窗口类,那么系统就有了我们所编写的窗口过程函数的地址。
  • 第三步:当应用程序接收到某一窗口的消息时,调用DispatchMessage(&msg)将消息回传给系统。系统则利用先前注册窗口类时得到的函数指针,调用窗口过程函数对消息进行处理。

【2】窗口创建四部曲之二:注册窗口类

if( !RegisterClassEx(&wndClass))return -1;

【3】窗口创建四部曲之三:正式创建窗口

得到窗口句柄,具体函数定义可以在MSDN查到。

HWND hwnd = CreateWindow(L"ForTheDreamOfGameDevelop",WINDOW_TITLE,    //创建窗口函数CreateWindowWS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,WINDOW_WIDTH,WINDOW_HEIGHT,NULL,NULL,hInstance,NULL);

【4】窗口创建四步曲之四:窗口的移动、显示与更新

 MoveWindow(hwnd,250,80,WINDOW_WIDTH,WINDOW_HEIGHT,true);    ShowWindow(hwnd,nShowCmd);  //调用ShowWindow函数来显示窗口UpdateWindow(hwnd);    //对窗口进行更新,就像我们买了房子要装修一样

UpdateWindow间WM_PAINT消息直接发送给了窗口过程函数进行处理,而没有放到我们前面所说的消息队列里。

【5】消息循环过程

  MSG msg = {0};    //定义并初始化msgwhile(msg.message != WM_QUIT)   //使用while循环,如果消息不是WM_QUIT,就继续循环{if(PeekMessage(&msg,0,0,0,PM_REMOVE)) //查看应用程序消息队列,有消息时将队列中的消息派发出去{TranslateMessage(&msg); //将虚拟键消息转换为字符消息DispatchMessage(&msg);   //分发一个消息给窗口程序}}

无论应用程序消息队列是否有消息,PeekMessage函数都立即返回,程序的得以继续执行后面的语句(无消息则执行其他指令,有消息时一般要将消息派发出去,在执行其他指令)

而GetMessage函数只有在消息队列中有消息时才返回,队列中无消息就会一直等,知道下一个消息出现时才返回。

【6】窗口类的注销

UnregisterClass(L"ForTheDreamOfGameDevelop",wndClass.hInstance);   //程序准备结束,注销窗口类

Windows程序的“中枢神经”——窗口过程函数

描述:窗口过程函数WndProc,对窗口消息进行处理。其中函数名可以随意取,只要后面的参数匹配就OK了。

LRESULT CALLBACK  WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch(message)    //switch语句开始{case WM_PAINT: //若是客户区重绘消息ValidateRect(hwnd,NULL); //更新客户区的显示break;    case WM_KEYDOWN:if(wParam == VK_ESCAPE)   //如果按下的键是ESCDestroyWindow(hwnd);    //销毁窗口,并发送一条WM_DESTROY消息break;case WM_DESTROY:PostQuitMessage(0);    //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息break;     //跳出该switch语句default:   //若上述case条件都不符合return DefWindowProcW(hwnd,message,wParam,lParam);   //调用默认的窗口过程}return 0;
}

下面是整个项目的完整代码:

//------------------------------【程序说明】-----------------------------
// 程序名称:GameCore
// 2018年3月Creat by XY
// 描述:用代码勾勒出游戏开发所需的程序框架
//-----------------------------------------------------------------------//------------------------------【头文件包含部分】-----------------------
//描述:包含程序所依赖的头文件
//-----------------------------------------------------------------------
#include <windows.h>//------------------------------【宏定义部分】---------------------------
//描述:定义一些辅助宏
//-----------------------------------------------------------------------
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
#define WINDOW_TITLE L"【致我们永不熄灭的游戏开发梦想】程序核心框架"    //为窗口标题定义的宏//------------------------------【全局函数声明部分】---------------------
//描述:全局函数声明,防止“未声明的标识”系列错误
//-----------------------------------------------------------------------
LRESULT CALLBACK  WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lparam);   //窗口过程函数//------------------------------【WinMain()函数】-----------------------
//描述:Windows应用程序的入口函数,我们的程序从这里开始
//------------------------------------------------------------------------
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd )
{//【1】窗口创建四部曲之一:开始设计一个完整的窗口类WNDCLASSEX wndClass = { 0 };    //用WINDCLASSEX定义一个窗口类wndClass.cbSize = sizeof(WNDCLASSEX); //设置结构体的字节数大小wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式wndClass.lpfnWndProc = WndProc;       //设置指向窗口过程函数的指针wndClass.cbClsExtra = 0;        //窗口类的附加内存,取0就可以了wndClass.cbWndExtra = 0;       //窗口的附加内存,依然取0就可以了wndClass.hInstance = hInstance;   //指定包含窗口过程的程序的实例句柄wndClass.hIcon = (HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE);   //本地加载自定义ico图标wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);      //指定窗口类的光标句柄wndClass.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);   //为hbrBackground成员指定一个灰色画刷句柄wndClass.lpszMenuName = NULL;  //用一个以空终止的字符串,指定菜单资源的名字wndClass.lpszClassName = L"ForTheDreamOfGameDevelop";  //用一个以空终止的字符串,指定窗口类的名字。//【2】窗口创建四部曲之二:注册窗口类if( !RegisterClassEx(&wndClass))return -1;//【3】窗口创建四部曲之三:正式创建窗口HWND hwnd = CreateWindow(L"ForTheDreamOfGameDevelop",WINDOW_TITLE,    //创建窗口函数CreateWindowWS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,WINDOW_WIDTH,WINDOW_HEIGHT,NULL,NULL,hInstance,NULL);//【4】窗口创建四步曲之四:窗口的移动、显示与更新//调整窗口显示时的位置,使窗口左上角位于(250,80)处MoveWindow(hwnd,250,80,WINDOW_WIDTH,WINDOW_HEIGHT,true); ShowWindow(hwnd,nShowCmd);  //调用ShowWindow函数来显示窗口UpdateWindow(hwnd);    //对窗口进行更新,就像我们买了房子要装修一样//【5】消息循环过程MSG msg = {0};    //定义并初始化msgwhile(msg.message != WM_QUIT)   //使用while循环,如果消息不是WM_QUIT,就继续循环{if(PeekMessage(&msg,0,0,0,PM_REMOVE)) //查看应用程序消息队列,有消息时将队列中的消息派发出去{TranslateMessage(&msg); //将虚拟键消息转换为字符消息DispatchMessage(&msg);   //分发一个消息给窗口程序}}//【6】窗口类的注销UnregisterClass(L"ForTheDreamOfGameDevelop",wndClass.hInstance);    //程序准备结束,注销窗口类return 0;
}//---------------------------------【WndProc()函数】-------------------------------------
//描述:窗口过程函数WndProc,对窗口消息进行处理
//---------------------------------------------------------------------------------------
LRESULT CALLBACK  WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch(message)    //switch语句开始{case WM_PAINT: //若是客户区重绘消息ValidateRect(hwnd,NULL); //更新客户区的显示break;    case WM_KEYDOWN:if(wParam == VK_ESCAPE)   //如果按下的键是ESCDestroyWindow(hwnd);    //销毁窗口,并发送一条WM_DESTROY消息break;case WM_DESTROY:PostQuitMessage(0);    //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息break;     //跳出该switch语句default:   //若上述case条件都不符合return DefWindowProcW(hwnd,message,wParam,lParam);   //调用默认的窗口过程}return 0;
}
  • GameCore源代码目录

现在我们就有了一个窗口组件了,后面的学习都要在这个窗口组件上进行。RUA~

【读书笔记】《Windows游戏编程之从零开始》(一)相关推荐

  1. mysql数据库权威指南_MySQL_MySQL权威指南读书笔记(三),第二章:MYSQL数据库里面的数 - phpStudy...

    MySQL权威指南读书笔记(三) 第二章:MYSQL数据库里面的数据 用想用好MYSQL,就必须透彻理解MYSQL是如何看待和处理数据的.本章主要讨论了两个问题:一是SQL所能处理的数据值的类型:二是 ...

  2. MongoDB权威指南读书笔记——CRUD

    插入并保存文档 插入是向MongoDB中添加数据的基本方法.可以使用Insert方法向目标集合插入一个文档:db.foo.insert({"bar" : "baz&quo ...

  3. HTTP权威指南读书笔记

    <<HTTP权威指南>>读书笔记 第一部分:Web的基础 第1章:HTTP概述 主要内容 1.什么是HTTP 2.HTTP的基本组件 HTTP HTTP:HTTP(Hypert ...

  4. HTML5权威指南----读书笔记

    <!DOCTYPE html> <html> <head><meta name = 'keywords' content="HTML5权威指南--- ...

  5. 计算机网络和http权威指南 读书笔记

    计算机网络笔记 网络层 网络层向上提供无连接的,尽最大努力交付的数据报服务 网络层不提供数据质量承诺 物理层使用的中间设备叫转发器repeater 数据链路层叫网桥bridge 网络层叫路由器rout ...

  6. MapReduce总结 + 相关Hadoop权威指南读书笔记(未完......欢迎补充,互相学习)

    文章目录 MapReduce概述 MapReduce优缺点 MapReduce核心思想 MapReduce进程 MapReduce编程规范 WordCount 案例实操 本地测试 集群测试 Hadoo ...

  7. android开发读书笔记,android开发权威指南读书笔记

    第17章 Fragment 1.在res目录下增加 layout-sw600dp 目录,用于存放7英寸及以上尺寸屏幕的布局文件.10英寸以上平板用 sw720dp.如果是更小的屏幕,如 480*800 ...

  8. java性能权威指南中文_Java性能权威指南读书笔记--之一

    JIT(即时编译) 解释型代码:程序可移植,相同的代码在任何有适当解释器的机器上,都能运行,但是速度慢. 编译型代码:速度快,电视不同CPU平台的代码无法兼容. java则是使用java的编译器先将其 ...

  9. javascript权威指南读书笔记之二——词法结构

    本章讲述的内容,用通俗的语言来说,就是应该注意的地方,这些也许和我们所学的其他语言类似,也许完全不同,比如一开始就介绍说javascript程序中的每个字符都是用两个字节表示的,但有些程序设计者习惯于 ...

  10. HTTP权威指南读书笔记(一)HTTP概述、URL和资源及报文详解

    一.HTTP概述 1.WEB客户端和服务器. 2.资源:资源可以是各种格式的静态文件,也可以是应用程序. 3.媒体类型 4.URI:统一资源标识符 URL:统一资源定位符. URL的第一部分称为方案: ...

最新文章

  1. html 图片 高度无效_HTML笔记(详细)
  2. nginx搭建文件服务器脚本,基于docker搭建nginx文件服务器的方法步骤
  3. legend函数_ggplot的图例(legend)管理
  4. eth_type_trans的处理流程图
  5. kafka 事务_Kafka的有且仅有一次语义与事务消息
  6. php和python-现在自学php和python那个合适?
  7. 【优化算法】混合蛙跳算法(SLFA)【含Matlab源码 300期】
  8. 【统计学习方法】EM算法原理
  9. 怎么将两个css合并单元格,css table之合并单元格
  10. LOJ#6070. 「2017 山东一轮集训 Day4」基因 解题报告
  11. Android 梯形TextView
  12. jzoj 5850.【NOIP提高组模拟2018.8.25】e 可持久化线段树+lca
  13. 微信JS-SDK实现自定义分享功能,分享给朋友,分享到朋友圈
  14. JavaScript基本原理常识
  15. 360度全景的地拍如何制作?
  16. c++仿函数调用方式
  17. matrix calculator
  18. winform连接blynk 控制开发板
  19. postman使用post传入参数_Postman接口测试之POST、GET请求方法
  20. matlab 经济计量工具箱,matlab计量工具箱Lesage 本程序集合为空间计量经济学 - 下载 - 搜珍网...

热门文章

  1. python基础知识学习(2)
  2. 媒体传输质量指标MDI
  3. session对象不创建会有吗_过了30就不好嫁?测你结婚对象今年会出现吗
  4. 羽毛球双打防守解码,进攻才是王道
  5. Kim Keat Beacon BTO at Toa Payoh in May 2018
  6. java毕业设计网上摄影工作室(附源码、数据库)
  7. python 进程池阻塞和非阻塞_python进程池:multiprocessing.pool
  8. linux cuda 黑屏,Ubuntu下安装Nvidia显卡驱动和Cuda之后黑屏,蓝屏,发生各种错误
  9. JAVA 字符串截取方法
  10. Infopath resource