上文讲了其他的文件,这次就把主用户代码文件testSDK.cpp讲一下。
全局变量、声明、定义
第1-18行:
// testSDK.cpp : 定义应用程序的入口点。
//
#include "stdafx.h"
#include "testSDK.h"
#define MAX_LOADSTRING 100
// 全局变量:
HINSTANCE hInst; // 当前实例
TCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
头文件
先是两个#include包含头文件,这两个文件之前都讲过的;
宏定义
接着是一个#define,这个定义了一个宏MAX_LOADSTRING,关于这个define宏,我们暂时只需要记住一点,就是下面代码中出现的整词匹配的MAX_LOADSTRING,就会直接替换为100;define宏还有一些高级用法,有兴趣可以自行搜索。
宏定义并不是必须的,如果代码中多次使用到同一个常量,为了便于修改,就可以使用宏定义。
全局变量
下面定义了3个全局变量hInst,szTitle,szWindowClass,全局变量的意思就是接下来整个代码文件中任何地方都可以直接使用这些变量。
对于全局变量,个人建议加上g_前缀,以便与局部变量进行区分,比如g_hInst,g_szTitle这样。
HINSTANCE hInst;
这个需要解释的就是变量类型HINSTANCE,这个类型其实是VS自己定义的变量类型,真实类型是void*,是一个指针类型。
TCHAR szTitle[MAX_LOADSTRING];和TCHAR szWindowClass[MAX_LOADSTRING];
TCHAR自适应Unicode和非Unicode编译,当Unicode编译时,TCHAR被识别为wchar_t,非unicode编译则识别为char;
而MAX_LOADSTRING会被替换为100,变成TCHAR szTitle[100];
对比第一个变量"HINSTANCE hInst;"的声明,发现多了一对中括号[],这个[]表示声明的变量是数组变量,[100]就表示有100个TCHAR在szTitle变量中;
数组的使用方式是szTitle[n],n的索引从0开始,到数组数量减一结尾。
比如szTitle 有100个元素,那么第一个元素就是szTitle[0],结尾的元素就是szTitle[99];当单独使用szTitle不带中括号的时候,表示使用过的是这一块内存的起始指针;
在c/c++/vc中,数组的声明必须确定数组大小,比如TCHAR szTitle[100];,但是不能这样写:
int nSize = 100;
char szTitle[nSize];
编译无法通过,会报错。
字符串属于特殊的数组,必须以0作为最后一个元素。比如定义一个字符串:
char szTitle[] = "testSDK";
那么每个元素对应如下:
索引 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
内容 | 't' | 'e' | 's' | 't' | 'S' | 'D' | 'K' | 0 |
szWindowClass同上。
函数声明
4个函数的声明:MyRegisterClass,InitInstance,WndProc,About。
在开头把函数声明一下的好处是,在下面的代码中可以随时使用。
如果函数没有声明的话,那么只能在函数定义之后才能使用。
main函数
程序入口点,最重要的函数。
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此放置代码。
MSG msg;
HACCEL hAccelTable;
// 初始化全局字符串
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_TESTSDK, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TESTSDK));
// 主消息循环:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
函数定义
使用了一些vs自家的封装,我还原一下:
int __stdcall WinMain(void* hInstance,
void* hPrevInstance,
char* lpCmdLine,
int nCmdShow)
{
...
}
int表示函数结束的时候需要return返回一个int类型的值;
__stdcall是一种函数协议,这些函数协议表示函数的参数传递顺序,以及堆栈的平衡是由函数调用方还是函数自身来做。这个对于用户来说暂时不需要关心,有兴趣的同样可以搜索(关键字:stdcall cdecl fastcall)。
WinMain就是函数名称,后面括号表示函数需要的参数,一个需要四个参数:
hInstance表示当前进程模块的INSTANCE,可以认为是一个标识符,大概类似于句柄;
hPrevInstance字面意思是上一个进程模块的句柄,然而可能只是为了兼容旧版本系统而留的,实际是0;
lpCmdLine指向一个字符串,表示启动进程的参数。如果没有传递参数,该字符串就是空串。
nCmdShow是一个整数,表示该程序运行之后是什么状态,显示/隐藏/最小化/最大化/常规等,具体数值定义是:
#define SW_HIDE 0
#define SW_SHOWNORMAL 1
#define SW_NORMAL 1
#define SW_SHOWMINIMIZED 2
#define SW_SHOWMAXIMIZED 3
#define SW_MAXIMIZE 3
#define SW_SHOWNOACTIVATE 4
#define SW_SHOW 5
#define SW_MINIMIZE 6
#define SW_SHOWMINNOACTIVE 7
#define SW_SHOWNA 8
#define SW_RESTORE 9
#define SW_SHOWDEFAULT 10
#define SW_FORCEMINIMIZE 11
#define SW_MAX 11
函数体
UNREFERENCED_PARAMETER
UNREFERENCED_PARAMETER是一个vs自己定义的宏,从字面意思可以看到,这个宏包起来的参数下面不会用到。使用这个宏把未使用的参数包起来,可以避免编译的时候出现"未使用的参数"的警告warning。
MSG结构体
函数接着定义了一个MSG类型的变量msg,MSG是一个结构体,主要用于记录消息相关内容,结构体含有以下成员:
struct MSG
{
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
}
结构体就相当于是一个衣柜,里面的各个成员就相当于衣柜的各个隔间和抽屉。
MSG结构体的使用方法是msg.hwnd,msg.message,这样子的。
HACCEL加速键
继续看下面,定义了一个HACCEL hAccelTable;变量,这个HACCEL类型姑且先当作一个标识符句柄,用于指向一个加速键的表。
LoadString
LoadString是Windows提供的一个API函数,用于载入资源中的字符串表的某一项内容。
int LoadString(
HINSTANCE hInstance,
UINT uID,
LPTSTR lpBuffer,
int nBufferMax
);
这是这个函数的定义,根据MSDN的解释,第一个参数是上文提到的模块句柄;第二个参数是资源ID(在资源的字符串表中定义);第三个参数是字符串空间的指针,用于接收读取到的字符串;第四个参数表示第三个参数指向的空间有多少个字符char或宽字符wchar_t,读取到的字符数将不会超过这个数值,超出部分被截断。
函数返回读取到的字符数,不包括字符串结尾的0;所以假如要读取一个字符串"testSDK",长度是7,那么需要的空间至少是8。
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_TESTSDK, szWindowClass, MAX_LOADSTRING);
意思就是把ID为IDS_APP_TITLE的资源读取到szTitle中,把ID为IDC_TESTSDK的资源读取到szWindowClass中。
注册窗口类
调用了MyRegisterClass函数注册一个窗口类,后面会详细解说该函数。
创建窗口
调用了InitInstance函数创建一个窗口,后面详细解说该函数。
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
这里用了一个判断,如果InitInstance函数返回FALSE(表示:错误/假/非法)的话,那么就变成了:
if (!FALSE)
{
return FALSE;
}
感叹号"!"表示"非",那么"非FALSE",就等于"TRUE(表示:正确/真/合法)",那么就变成
if (TRUE)
{
return FALSE;
}
条件成立,就会执行大括号内的"return FALSE;",导致WinMain返回错误,程序中止。
所以这个判断最终的意思是,如果InitInstance函数成功创建了窗口,那么就继续执行下面的代码;如果InitInstance创建窗口出现错误,那么下面的代码也就没有继续执行的必要了。
LoadAccelerators
该函数是Windows提供的一个API函数,用于载入一个加速键表,以便在后面使用,将资源中定义的快捷键生效。
需要注意的是第二个参数,使用了MAKEINTRESOURCE宏。这个宏其实并没有进行什么操作,只是进行了一个强制类型转换,因为LoadAccelerators函数的第二个参数需要字符串指针类型,而我们定义的ID是整数型的。比如:
#define IDC_TESTSDK 109
我的IDC_TESTSDK定义是整数的109,换算成十六进制就是0x 6D,进行强制转换成字符串指针之后变成0x0000006D,其实还是0x6D,对于LoadAccelerators函数来说,接收到的是同样的内容,只是编译器要求必须使用字符串指针。
通过解析这个MAKEINTRESOURCE宏,可以把这个函数调用换成这样:
hAccelTable = LoadAccelerators(hInstance, (LPCSTR)IDC_TESTSDK);
我直接使用了"(LPCSTR)"来进行一个强制类型转换,是一样的效果。
消息循环
while循环
消息循环使用了一个while循环,循环条件是"GetMessage(&msg, NULL, 0, 0)",即是根据GetMessage的返回结果来确定继续循环还是结束循环。假如GetMessage一直返回TRUE值,那么该循环会一直继续下去。那么什么时候GetMessage返回FALSE呢?当GetMessage接收到一个WM_QUIT消息就会返回FALSE,于是while循环就结束。
GetMessage
该函数是Windows提供的一个API函数,用于接收本线程收到的消息,可以接收系统下发的消息,也可以接收其他线程与进程发送的消息。
接收到的消息,会把消息的相关内容存放在msg结构体中,所以该函数的第一个参数我们传递了一个msg的指针(就是"&msg")进去。'&'符号在这里表示取后面变量msg的地址指针。这个涉及到两个方面的知识,一个是函数的"多返回值",还有一个是"指针和内存"。是相对深入的内容,后面再专门讲解。
当没有消息可以接收的时候,该函数会一直等待。当收到WM_QUIT消息就会返回FALSE。
TranslateAccelerator
该函数是Windows提供的一个API函数,用于接收热键消息。
这个函数是作为一个if语句的判断条件出现的,该判断语句以TranslateAccelerator的返回值来判断是否执行if判断体内容,具体可以参考上文"创建窗口"部分关于if的讲解。
TranslateAccelerator有三个参数,第一个是窗口句柄,就是前面创建的窗口句柄,GetMessage函数会填充到msg.hwnd中;第二个参数是加速键表句柄hAccelTable,就是前面LoadAccelerators获取到的;第三个参数是消息结构体的指针地址&msg。其实既然第三个参数把msg指针传递进去了,那么第一个参数msg.hwnd实际上可以省略掉,不知道为何保留着。
TranslateAccelerator函数内部会根据msg的各项内容,确定是否按键消息,该按键消息又是否是hAccelTable加速表中注册过的,符合条件的话,函数会调用热键处理函数并等待热键处理函数结束后才返回,返回值为非0值(一般是1),表示该消息已经由TranslateAccelerator函数处理过了;而如果不是按键消息,或者不是加速键表中注册过的按键消息,又或者出现了其他错误导致函数失败,函数将返回0,表示函数没有处理该消息,需要下发下去继续处理。
TranslateMessage
该函数是Windows提供的一个API函数,用于转换按键消息,将虚拟按键消息WM_KEYDOWN/WM_SYSKEYDOWN与WM_KEYUP/WM_SYSKEYUP转换为字符消息WM_CHAR,并把新生成的WM_CHAR字符消息加入到消息队列中。
DispatchMessage
该函数是Windows提供的一个API函数,用于将没人认领的消息下发给窗口处理函数并等待窗口处理函数的处理结果。
WinMain函数返回
当GetMessage收到WM_QUIT消息之后,while循环结束,这里可以处理一些资源释放或保存操作。然后就返回,退出程序。
return (int) msg.wParam;
一般我都是直接返回0。