2021-03-29:加密与解密

发布于:2024-06-28 ⋅ 阅读:(15) ⋅ 点赞:(0)

前段时间导师分配的任务主要是看《加密与解密》这本书,“书本写的很详细,认真看会看懂的!”  是的啊,书本写的很详细,可是作为一个没基础的小白看起来还是挺吃力的,概念一个接一个的出现,虽然看书的过程挺艰难的,但还是想抱着试一试的心态学下去,也不知道自己能够坚持多久,希望可以久一点吧!文章内容有些杂乱无章,还请各位大佬多多指教!
(PS:文中有些部分并非原创,如若侵权,留言定删!)



1.知识普及

1.逆向工程:根据已有的产物和结果,通过分析来推导出具体的实现方法。对软件来说,“可执行程序->反编译->源代码”的过程就是逆向工程。

2.静态分析技术:根据反汇编得到的程序清单进行分析,最常用的方法是从提示信息入手进行分析。

3.动态分析技术:

(1)对软件进行粗跟踪,即大块大块的跟踪,便于主要分析软件中我们所关心的那部分模块;

(2)对关键部分进行细跟踪,针对性的对关键部分细跟踪,将对下一次分析有很大的帮助。

4.ASCII与Unicode字符集

(1)字符集:一个系统支持的所有抽象字符的集合;

(2)字符:各种文字和符号的总称,包括各种文字、标点符号、图形符号、数字等等;

(3)ASCII:美国信息交换标准代码。使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。标准ASCII 码也叫基础ASCII码,编码的取值范围实际上是00h-7Fh, 包括26个小写字母、26个大写字母、10个数字、32个符号、33个控制代码及空格,共128个代码。由于计算机通常用字节(byte)这个8位的存储单位来进行信息交换,不同的计算机厂商对ASCII 进行了扩充,增加了128个附加字符,它们的值在127以上的部分是不统一的,取值范围变成了00h~0FFh。例如,ANSI、Symbol、OEM等字符集,其中ANSI是系统预设的标准文字存储格式。

ASCII字符速查表:http://ascii.911cha.com/

(4)Unicode是ASCII字符编码的一个扩展,只不过在Windows中用2字节对其进行编码,因此也被称为宽字节(Widechars)。Unicode是一种双字节编码机制的字符集,使用0~65535的双字节无符号整数对每个字符进行编码。在Unicode中,所有字符均为16位,起中所有的7位ASCII码都被扩充为16位(PS:高位扩充的是零)。

5.Win32 AP函数

(1)API函数(Application Programming Interface,即应用程序编程接口):提供应用程序运行所需要的局昂口管理、图形设备接口、内存管理等服务功能,将这些功能以函数库的形式组织在一起,形成了Windows应用程序编程接口,简称“Win API”。Win API子系统负责将API调用转换成Windows操作系统核心,他的上面是windows应用程序,如图所示。

image.png

windows应用程序与操作系统的关系

windows运转的核心是动态链接。windows提供了丰富的应用程序可利用的函数调用忙着写函数的采用动态链接库(DLL)实现。windows的主要部分只需要在3个动态链接库中实现,它们分别代表了windows的3个主要子系统,叫做Kernel、User和GDI。

Kernel(由KERNEL32.DLL实现):操作系统核心功能服务,管理内核,包括进程与线程控制、内存管理、文件访问等等。

User(由USER32.DLL实现):负责处理用户接口,管理用户操作,包括键盘和鼠标输入、窗口和菜单管理等。

GDI(由GDI32.DLL实现):图形设备接口,允许程序在屏幕和打印机上显示文本和图形。

6.Windows消息机制

windows是一个消息(Message)驱动式系统,windows消息提供在应用程序与应用程序之间、应用程序与windows系统之间进行通信的手段。windows系统中有两种消息队列,一种是系统消息队列,另外一种是英语程序消息队列。

7.虚拟内存

虚拟内存并不是真正的内存,他通过映射(Map)的方法使可用虚拟地址(Virtuall Address)达到4GB,每个应用程序可以获得2GB的虚拟地址,剩下的2GB留给操作系统自用。windows是一个分时的多任务操作系统,CPU时间在被分成一个个时间片后分配给不同的程序。

image.png

2.windowsAPI编程

参考链接:BeYoNDCoDE-CSDN博客

最近看的一些文章中老是提到SDK和API,那么SDK和API到底是什么呢?下面我们从概念来简单讲解一下!

(1)SDK,一般指软件开发工具包,一些软件工程师为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件时的开发工具的集合,简而言之就是一些特定功能的软件包。

(2)API,应用编程接口,操作系统留给应用程序调用的接口,应用程序通过调用操作系统的 API而使操作系统去执行应用程序的命令(动作)。

(3)联系:SDK是已经设计好的具有某些功能的软件包,全封闭的,而API是SDK的一个接口,便于需要这些功能的程序员调用这个SDK。SDK包含了使用API的必需资料,所以也常把仅使用API来编写Windows应用程序的开发方式叫做“SDK编程”。

windows API编程是指调用windows的接口函数来进行程序的编写。

2.1.数据类型

float, long, short, int, char这些使我们在C/C++中都能够常见的几种数据类型,在SDK编程中,微软将其重新定义了一下。

typedef float FLOAT;
typedef long LONG;
typedef short SHORT
typedef int INT;
typedef char CHAR;

下面的几种数据类型,在SDK编程定义中将其写法简化了一下。比如定义一个无符号整型,可以直接写UINT a,而不用写unsigned int a。

typedef unsigned int UINT;
typedef unsigned int UINT32;
typedef signed int INT32;
typedef unsigned long DWORD;
typedef unsigned short WORD;

下面来看几个绕圈圈的数据类型。long->LONG_PTR->LPARAM,可以看出米虽然LPARAM是由LONG_PTR定义,但是long又定义了LONG_PTR,因此可以推出LPARAM就是long类型的。

typedef LONG_PTR LPARAM;
typedef UINT_PTR WPARAM;

typedef long LONG_PTR;

如果仅仅是的上面的定义,在64位运行的程序时将会出现一些问题,微软为了便于程序在32位和64位下都可以运行将上面LONG_PRT的定义替换了一下,如下所示:如果定义了 _WIN64这个宏 那么就定义 LONG_PTR 为 __int64,否则定义LONG_PTR为long。为了方便程序在不同平台、不同编码运行,类似于下面的这种定义有很多,后面若是涉及到了再进行具体的讲解。

#if defined(_WIN64)
    typedef __int64 LONG_PTR;
#else
    typedef long LONG_PTR;
#endif

最后看两个常见的数据类型:

#define   VOID void
#define CONST   const

2.2.编码之Unicode和ASCII

编码

大小

支持

ASCII

1个字节

英文

Unicode

2个字节

所有语言

UTF-8

2个字节

所有语言

ASCII和Unicode编码最大区别就是大小,最早只有127个字母被编码到计算机里,也就是大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码,而要处理中文、日文等其他语言的话,一个字节显然是不够的,因此Unicode应运而生, 把所有语言都统一到一套编码当中了。微软目前的SDK中保持了2套API,一套用于Unicode编码处理字符的程序的编写,一套用于采用ASCII编码处理字符的程序的编写。 例如MessageBox它其实不是一个函数名,而是一个宏定义,具体定义如下:

#ifdef UNICODE
        #define MessageBox  MessageBoxW //宽字节(Unicode)
#else
        #define MessageBox  MessageBoxA //ASCII
#endif 

如果定义了宽字节(Unicode),那么MessageBox就定义为MessageBoxW;如果定义了ASCII,那么MessageBox就定义为MessageBoxA;当我们调用宏定义MessageBox其实是在调用MessageBoxW或MessageBoxA,这三个称谓在程序中均可以使用,但是在调用MessageBoxA和MessageBoxW时需要注意调用参数的的字节数。比如MessageBoxA的话,你传给它的参数字符都必须是单字节,也就是ASCII, 在程序中就是char,如果使用MessageBoxW的话,字符都必须使用Unicode,程序中就是 wchar_t,但是如果需要的程序源代码编译出来的字符均采用ASCII编码的程序,那么需要改动的地方就会比较多。因此建议直接采用不带后缀的宏定义Messagebox,其他函数的定义也类似。

理论说了一大堆,下面我们上一个简单的例子看看吧!

//程序【1】
int APIENTRY WinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR     lpCmdLine,
    int       nCmdShow)
{

    MessageBoxA(NULL, "Hello xor0ne", "Title", MB_OK);
    return 0;
}

MessageBoxA的第二和第三个参数均是单字节版本,而WinMain函数的第三个参数也是单字节LPSTR,满足MessageBoxA的调用,因此运行成功。如果将程序的运行设置为Uncode依旧可以运行成功,因为指定的参数和函数需要的参数字节相匹配,无论程序运行的字符集是怎样的,都可以编译成功。如果将函数换成MessageBoxW将会运行不成功,因为此函数需要的Unicode字符,因此无法编译成功,报错原因为“ "const char *" 类型的实参与 "LPCWSTR" 类型的形参不兼容”,可以理解为MessageBoxW需要参数为Unicode的字符串,而传入的是ASCII,与需要不匹配,因此无法编译;但如果在字符串前面加上一个L将可以编译运行,关于这个L我们稍后讲解。将函数换成MessageBox也无法编译通过,因为此时程序的编译字符集为Unicode,MessageBox将执行默认定义MessageBoxW,参数不匹配,编译不通过。而当字符集变为ASCII后,MessageBox将执行默认定义MessageBoxWA,参数类型匹配,编译通过。总结一下,需要注意的点有两个:

(1)传入的参数,

(2)程序编译的字符集

无论是调用哪一个MessageBox函数,本质都是保持参数与函数的调用字符集保持一致。

2.3.字符和字符指针

前面我们讲到在程序【1】中的MessageBoxW函数的第二和第三个参数字符前面加一个L便可以通过,这是因为加了一个L表示传入的参数为Unicode字符,正好与MessageBoxW参数的字符集相匹配,所以编译通过。前面我们讲到微软中有两套API,一套用于Unicode编码处理字符的程序的编写,一套用于采用ASCII编码处理字符的程序的编写,例如:Messagebox只是一个宏定义,其实他是由MessageboA或MessageboxW,为了程序的正常运行,我们一般建议写Messagebox函数,这样windows将会根据编译环境字符集自动调用MessageboxA或MessageboxW,这只是函数中关于ASCII和Unicode编码的两种形式,那么数据类型中是否也有类似的表现形式呢?答案是肯定的!下面我们讲解一些数据类型中关于两套编码的表现形式。

如下, 我们分别定义了两种编码下的char类型,主要区别是char定义的是用一个字节来存储的字符变量,而wchra_t定义的是由两个字节来存储的字符变量。'b'前面的L表示这里的字符b是Unicode字符。

char   c = 'b'; //ASCII
wchra_t  wc = L'b'; //Unicode,将'b'这个Unicode字符保存到wc这个unicode字符变量中

//类似,字符数组的定义如下:
char  c[10];
wchar_t wc[10];
//带有初始化字符数组的声明
char c[] = "beyondcode";
wchar_t wc[] = L"beyondcode";

//字符指针
char  c[] = "hello beyondcode"; //定义一个字符数组
wchar_t  wc[] = L"hello beyondcode"; //定义一个宽字节字符数组
char   *p = c; //定义一个字符指针,指向刚才的字符数组
wchar_t *wp = wc; //定义一个宽字节字符指针,指向刚才的宽字节字符数组
//通过指针来改变上面定义的数组,把数组的第一个字符通过指针改变成大写
p[0] = 'H';
wp[0] = L'H';

//如果像下面一样在指针定义前面添加了const的话,
const  char  *p = c;
const  wchar_t  *wp = wc;

上面都是属于C/C++中的知识,并没有涉及太多Windows中的数据类型,那么各位朋友们在Windows编程中看到的满到处都是的 TCHAR,LPSTR, LPCSTR, LPWSTR, LPCWSTR, LPTSTR, LPCTSTR 这些数据类型又是怎么回事呢? 别急,咱接着往下看。

typedef char  CHAR;
typedef wchar_t  WCHAR;

//利用上面定义的CHAR和WCHAR定义字符指针
typedef  CHAR  *LPSTR;
typedef  WCHAR  *LPWSTR;

通过上面两个定义我们可以发现LPSTR->CHAR*->char*,即LPSTR等价于char*,也就是字符指针(ASCII编码),同理LPWSTR->WCHAR*->wchar_t*,LPWSTR也是字符指针(Unicode编码)。如果我们要定义一个字符变量,那么后面一系列对它调用的相关函数都得使用对应的字符集编码,一不留神就会类似于前面Messagebox产生编码错误,这对于程序员编写无疑是麻烦的,那么有没有简单一点的方法呢?嘿嘿,都问到这里了,那肯定是有的嘛!方法类似于Messagebox,使用一个宏定义,可以根据编译环境字符集的不同进行自动转换,具体代码如下:如果定义了UNICODE这个宏,那么TCHAR代表的就是WCHAR,如果没有的话,那么TCHAR代表的就是char。

#ifdef  UNICODE
typedef  WCHAR  TCHAR;
#else
typedef  char TCHAR;

同理LPSTR和LPWSTR也定义了一个宏定义变量LPTSTR。根据LPSTR->CHAR*->char*和LPWSTR->WCHAR*->wchar_t*可以推出LPSTR就是字符指针的一个宏定义。

#ifdef   UNICODE
typedef  LPWSTR   LPTSTR;
#else
typedef  LPSTR LPTSTR;

现在我们已经知道TCHAR是字符变量的一个宏定义,当编译环境字符集为ASCII时,TCHAR表示char,变量定义如下方式1;当编译环境字符集为UNICODE时,TCHAR表示wchar_t,变量定义如下方式2。如果编译环境字符集不变,而变量定义的方式交换一下,由于字符集并没有匹配将导致编译不通过,因此在使用的TCHAR需要根据当前编译环境字符集进行正确的变量定义,如果编译环境发生变化,那么随之需要改变的定义也将有很多。

TCHAR TC = 'a'; //ASCII,方式1
TCHAR TC = L'a'; //Unicode,方式2

为了避免产生这种错误,建议使用宏  _T() ;只要将字符或者字符串常量放在_T()这个宏里面,那么这个宏就能根据当前的环境决定是否在字符或字符串前面加L,如下面:无需改变代码,即可编译出ASCII和Unicode两套编码程序。

TCHAR tc = _T('A');

根据我们讲解得到相关知识对程序【1】进行相关的更改,变成Unicode版本的程序。如下,除了函数MessageBoxA及其参数需要变成Unicode版本的MessageBoxA外,Winmain及其参数也需要变成Uncidoe编码版本。

//程序【2】
#include <windows.h>
int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd )
{
    MessageBoxW( NULL, L"Hello Beyondcode", L"Title", MB_OK );
    return 0;
}

程序【1】和【2】分别对应着ASCII和Unicode编码版本的,当我们需要编写一个通过版本时,则需要将所有函数及其对应的参数都换成通用版本,即宏定义版本,代码如下程序【3】。WinMain被改成了_tWinMain ,_tWinMain也是WinMain的一个宏,可以根据编辑环境字符集定义成WinMain或wWinMain。

/* BY beyondcode */
#include <windows.h>
#include <tchar.h>/*需要包含,因为_tWinMain和_T()这些宏是被定义在里面的*/
int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd )
{
    MessageBox( NULL, _T("Hello Beyondcode"), _T("Title"), MB_OK );
return 0;
}

2.4.SDK程序

2.4.1.计算并输出1到10的和。

SDK编程中的输出与C/C++中有所不同,当我们以窗口函数Messagebox进行输出时候,传入Messagebox函数的变量为字符串类型,而我们直接计算出和的类型为整型,在这之间可利用函数wsprintf进行格式化转换,将整型和转换到字符变量中,然后再传入到Messagebox中。多说一句,wsprintf类似于Messagebox,也是一个宏定义,定义代码如下。

#ifdef UNICODE
#define wsprintf  wsprintfW
#else
#define wsprintf  wsprintfA
#endif // !UNICODE

计算并输出1到10的和程序如下:

#include <tchar.h>

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
    int sum = 0;
    for( int i = 1; i<=10; i++ )
        sum += i;
    TCHAR strSum[256] = { 0 };
    wsprintf( strSum, _T("%d"), sum );  //将sum格式化赋值给strSum
    MessageBox( NULL, strSum, _T("Title"), MB_OK ); //调窗口函数进行输出
    return 0;
}

代码分析:

(1)sum存储1~10的和,通用字符数组strSum用来将存放格式化后的sum;

(2)wsprintf为格式化字符串函数,它的第一个参数是LPTSTR类型的,指定经过格式化的字符串存放的地方,第二个参数是指定以什么格式来格式化后面的数据,这里我们要格式化一个整数,所以指定%d,这个和printf这些函数是一样的, 后面的参数就是我们要格式化的数据了。

(3)PS:为什么wsprintf传入的第一个参数可以是TCHAR类型数组strSum?

前面我们讲到LPTSTR是LPSTR和LPWSTR的宏定义,而LPSTR->CHAR*->char*和LPWSTR->WCHAR*->wchar_t*,即LPTSTR实际上就是字符指针的宏定义;而数组名代表数组元素类型的指针,TCHAR数组的元素类型就是TCHAR,因此wsprintf传入的第一个参数strSum代表TCHAR类型的指针,即字符类型的指针,因此strSum可以作为wsprintf第一个参数传入。

(4) _T()宏:只要将字符或者字符串常量放在_T()这个宏里面,那么这个宏就能根据当前的环境决定是否在字符或字符串前面加L,便于代码在不同的编译环境中编译。

2.4.2.正经的SDK程序

参考链接:https://www.cnblogs.com/beyond-code/archive/2009/03/26/beyondcode.html

建议看完另外一篇文章:浅谈SDK,再看下面的比喻,将会更容易理解。

编写SDK程序的大致步骤如下:

第一步:注册窗口类

第二步:创建窗口

第三步:消息循环

第四步:编写窗口消息处理函数

上面所说的,听起来都比较专业,下面我就解释一下,什么是注册窗口类呢?注册窗口类就是使用一个 窗口类结构体(WNDCLASSEX) 来描述一类窗口,这类窗口具有相同的属性,也就是你在结构体WNDCLASSEX中指定的那些值。只要是用这个窗口类创建的窗口都具有这些特性。至于WNDCLASS能描述哪些特性,下面会具体讲,这里你只要了解是用一个名叫WNDCLASSEX的结构体来描述一个窗口的类别。

创建窗口应该比较好理解吧,就是创建一个具体的窗口,好像是一句废话嘛。也就是说这个窗口是根据一个窗口类而创建的,不是凭空而造的。意思是你要创建一个窗口,那么必须要有一个已经注册的窗口类。

对于前两步,我打一个比方,就好比你要造一辆车,那们第一步首先是干什么? 当然是设计图纸啦,图纸上就有说明这种车有哪些特性。然后第二步才是根据这个图纸来创建一个具体的看得见的车。所以我上面说的注册窗口类就好比设计窗口的图纸,然后就是根据这个窗口的图纸来创建一个具体的窗口。都说成这样了,应该明了了吧~~

至于消息循环,就是创建的窗口随时都有可能发生很多事情,那么发生的这些事情怎么通知你呢?比如窗口最小化了,窗口大小改变了,怎么通知你呢?  其实就就是通过消息循环不断的取得窗口所发生的事情,然后以消息的形式发送给我们后面要介绍的窗口消息处理函数。

消息处理函数呢就是我们程序员负责编写代码对具体的消息进行具体的处理,当然你也可以不处理,交给系统的默认处理函数来处理。

对于这两步,我也打一个比方。消息循环就好比汽车的一个总传感器,它源源不断的将汽车内部所发生的事情以消息的形式通过仪表板传达给开车的人,开车的人根据具体的事情而采取具体的操作,当然你也可以不操作,无动于衷,对于windows消息来说,不操作倒没有什么,而对于开车的人来说,不操作的后果就不好说了。 在这里,这个总传感器就相当于SDK程序的消息循环,不断的发送消息,而开车的人就相当于窗口消息处理函数,负责处理各种消息。

总结:

(1)注册窗口类,分为窗口类的设计与注册,先设计定义一个需要的窗口类,然后在系统中对该类进行注册(RegisterClass),告诉系统类的存在,PS:如果是系统类则不需要注册直接使用即可;

(2)创建窗口类:对之前设计好的类的每个属性进行具体化的赋值,创建一个实例化的类(CreateWindow);

(3)消息循环:GetMessage不断地获取消息队列中的消息,只要GetMessage所取得的消息不是WM_QUIT的话,那么GetMessage的返回值就不是0,那么循环就一直进行。在循环内部,将GetMessage取得的消息传递给TranslateMessage和DispatchMessage两个API函数进行处理.其中DispatchMessage就是将消息发送给了对应的窗口的窗口消息处理函数进行处理。至于TranslateMessage呢,则进行一些消息的转换。

MSG msg;

while( GetMessage( &msg, NULL, 0, 0 ) )
{
    TranslateMessage( &msg );
    DispatchMessage( &msg );
}

(4)编写窗口消息处理函数:对消息循环中DispatchMessage传递过来的函数进行处理,需要处理那些消息,那么你就编写处理那些消息的代码,对于你不处理的消息,则统统交给一个叫DefWindowProc的API函数进行默认的处理。

//【6】窗口过程,窗口过程函数
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     HDC         hdc ;
     PAINTSTRUCT ps ;
     RECT        rect ;
     switch (message)
     {   
     case WM_PAINT:
          hdc = BeginPaint (hwnd, &ps) ;
          GetClientRect (hwnd, &rect) ;
          DrawText (hdc, TEXT ("你好,欢迎你来到VC之路!"), -1, &rect,
                    DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
          EndPaint (hwnd, &ps) ;
          return 0 ;
          
     case WM_DESTROY:
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

如上,我们处理了两个消息分别是WM_PAINT和WM_DESTROY。

(1)WM_PAINT:windows在重画当前窗口时将会发送该消息;

(2)WM_DESTROY:在窗口呗销毁时发送给窗口消息处理函数。

(3)PostQuitMessage:这个API函数的功能就是发送一个WM_QUIT的消息。当GetMessage获取到WM_QUIT消息后返回FALSE,消息循环结束,程序终止。

下面我们给出一个示例窗口函数:如有兴趣,见浅谈SDK的具体分析。

#include "stdafx.h" //注意,这个向导产生的头文件不能去掉 
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
 
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("HelloWin") ; //每一个窗口类在注册时需要指定一个窗口处理函数(Window Procedure),这个函数是一个回调函数,就是用来处理消息的。
     HWND         hwnd ; //窗口类句柄->要发送到的窗口句柄
     MSG          msg ; //定义消息名字
     WNDCLASS     wc ; //【1】建立窗口类-->设计窗口
    
    //设计自己的窗口属性
     wc.style         = CS_HREDRAW | CS_VREDRAW ; //参数1:窗口类风格
     wc.lpfnWndProc   = WndProc ; //参数2:指向该窗口类的消息处理函数的指针。窗口处理函数,处理消息
     wc.cbClsExtra    = 0 ; //参数3.4:窗口类附加数据,指定用本窗口类建立的所有窗口结构分配的额外字节数。
     wc.cbWndExtra    = 0 ;
     wc.hInstance     = hInstance ; //拥有窗口类的实例句柄hInstance,本成员可使Windows连接到正确的程序。
     wc.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ; //最小窗口图标
     wc.hCursor       = LoadCursor (NULL, IDC_ARROW) ; //定义该窗口产生的光标形状
     wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; //用来着色窗口背景的刷子 
     wc.lpszMenuName  = NULL ; //指向菜单资源名的指针
     wc.lpszClassName = szAppName ; // 指向窗口类名的指针
 
    //当对WNDCLASS结构域一一赋值后,注册窗口类,调用的API函数为RegisterClass
     if (!RegisterClass (&wc)) 
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"), 
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     //【2】创建窗口
     //createwindow函数创建窗口,前提是需要提供一个已注册的窗口类(Window Class)
     hwnd = CreateWindow (szAppName,                  // window class name
                                                      //-->登记的窗口类名,这个类名刚才咱们在注册窗口时已经定义过了。
                          TEXT ("欢迎你的到来!"), // window caption->窗口的标题。
                          WS_OVERLAPPEDWINDOW,        // window style->窗口的风格
                          CW_USEDEFAULT,              // initial x position->程序运行后窗口在屏幕中的坐标值。
                          CW_USEDEFAULT,              // initial y position
                          CW_USEDEFAULT,              // initial x size->窗口初始化时(即程序初运行时)窗口的大小,即长度与宽度。
                          CW_USEDEFAULT,              // initial y size
                          NULL,                       // parent window handle->在创建窗口时指定其父窗口,这里没有所以为0
                          NULL,                       // window menu handle->指明窗口的菜单
                          hInstance,                  // program instance handle->拥有窗口类的实例句柄hInstance
                          NULL) ;                     // creation parameters->附加数据,一般都是0。
    
    //【3】显示和更新窗口-->产生WM_PAINT消息
     ShowWindow (hwnd, iCmdShow) ; /*显示窗口:
                                    调用API函数ShowWindow显示API函数CreateWindow创建完的窗口
                                    参数hwnd是要发送到的窗口句柄,即告诉ShowWindow()显示哪一个窗口
                                    第二个参数则告诉它如何显示这个窗口,即窗口显示的大小控制
                                    */
     UpdateWindow (hwnd) ; /*重画窗口:
                            WinMain()调用完ShowWindow后,还需要调用函数UpdateWindow,最终把窗口显示了出来。
                            调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即使窗口得到更新.
                            */
    //【4】创建消息循环-->WinMain不断地取出消息,分配给对应的窗口函数
    while (GetMessage (&msg, NULL, 0, 0)) /*从消息队列中取出一个消息
                                            参数msg:要接收消息的MSG结构的地址;
                                            第二个参数表示窗口句柄,NULL则表示要获取该应用程序创建的所有窗口的消息;
                                            第三,四参数指定消息范围。
                                            只有当收到的消息是WM_QUIT时,GetMessage才返回FALSE,结束消息循环,从而终止应用程序。 
                                            均为NULL时就表示获取所有消息。
                                            */
    {
        TranslateMessage (&msg) ; /*翻译消息-->将虚拟键消息(输入事件)转换为字符消息.*/
        DispatchMessage (&msg) ; /*要求Windows将消息传送给在MSG结构中为窗口所指定的窗口过程。
                                    将消息发送到到与MSG结构中的窗口句柄关联的窗口。如果窗口句柄是HWND_TOPMOST.
                                    如果窗口句柄是NULL,DispatchMessage不做任何事。
                                    */
    }
    
    //【5】终止应用程序
    /*一旦WinMain函数进入消息循环,终止应用程序的唯一办法就是使用PostQuitMessage把消息WM_QUIT发送到应用程序队列。
    当GetMessage函数检索到WM_QUIT消息,它就返回NULL,并退出消息外循环。
    通常,当主窗口正在删除时(即窗口已接收到一条WM_DESTROY消息),应用程序主窗口的窗口函数就发送一条WM_QUIT消息。*/
    return msg.wParam ;/*表示从PostQuitMessage返回的值
                        当WinMain函数把控制返回到Windows时,应用程序就终止了。
                        应用程序的启动消息循环前要检查引导出消息循环的每一步,以确保每个窗口已注册,
                        每个窗口都已创建。如存在一个错误,应用程序应返回控制权,并显示一条消息*/
}
 
//【6】窗口过程,窗口过程函数
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     HDC         hdc ;
     PAINTSTRUCT ps ;
     RECT        rect ;
     switch (message)
     {   
     case WM_PAINT:
          hdc = BeginPaint (hwnd, &ps) ;
          GetClientRect (hwnd, &rect) ;
          DrawText (hdc, TEXT ("你好,欢迎你来到VC之路!"), -1, &rect,
                    DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
          EndPaint (hwnd, &ps) ;
          return 0 ;
          
     case WM_DESTROY:
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}