Windows_API编程入门教程.pdf
Windows API 编程入门教程学习各种高级外挂制作技术,马上去百度搜索(魔鬼作坊),点击第一个站进入,快速成为做挂达人。大家好再次自我介绍一下我是 beyondcode,这次心血来潮,计划着做一系列关于WindowsAPI 编程的教程,用于帮助一些在Windows API 编程上有疑惑的,纳闷的,迷惑的新手朋友们。先解释一些术语或名词吧SDK 是 Software Development Kit 的简写,也就是软件开发包的意思,其中就包含了我们写程序要用到的一些头文件,库,工具,帮助文档之类的。Windows API 编程是指调用Windows 的接口函数来进行程序的编写,例如 MessageBox 就是一个 API 函数或者说接口函数。怎么说都可以,自己理解就行。如果你连这个都不太懂,我想也不会搜到这篇文章了吧为什么做这个系列教程呢,请听我一一道来先,最近遇到一些事一些人,让我真的感觉在这方面的引导入门文章真的很是匮乏,加上Windows SDK 头文件中那些复杂,庞大,烦人 的宏定义与数据类型定义,对于一个新手来说(我所说的新手不单只刚接触编程的,还特指那些在其他语言领域有比较高造诣的朋友)一个纯 SDK 写的 helloworld 程序都算是一个有些困难和挑战的任务了吧。本着帮助别人,高兴自己的原则,我有了这个打算,当然对自己以前所学,所经历做一次回忆,也是这次计划的一部分。声明一下,本系列教程是面向广大初次接触WIN32 SDK 程序编写的新手朋友们的,如果你是高手,一笑而过吧当然,除了一笑而过,也多谢你们提出指正文章中的错误,以免我误人子弟啊 谢谢Ok 废话不多说,进入正题,今天第一篇,讲什么?对于一个新人来说,第一次接触SDK编程或者说API 编程,什么最迷惑你们的,我们讲它,我觉得 Windows SDK 中那 烦人 的数据类型定义和宏定义应该算这个很角色吧。其实微软的本意也是善良的,为了减轻程序员的负担,和为了编程的方便,才花了那么多心思与精力定义出了这么一大套数据类型与宏定义,这也是我为什么在之前说它烦人都是加上引号的原因,因为他不是真的烦人,熟练了,你不但不觉得它烦,反而离不开它了,呵呵,日久深情也就是这么来的。呵呵先看几个数据类型定义吧typedef float FLOAT;typedef long LONG;typedef short SHORTtypedef int INT;typedef char CHAR;float,long,short,int,char 这几个数据类型都是大家熟悉的C/C+的数据类型吧,微软将他们重新定义了一下,很简单,就是改变名字为大写了,这样做的目的大概是微软为了编码的方便吧,输入法大小写都不用切换了,多人性化呀呵呵。再看几个数据类型定义的例子typedef unsigned int UINT;typedef unsigned int UINT32;typedef signed int INT32;typedef unsigned long DWORD;typedef unsigned short WORD;这些数据类型的定义就稍微有实质性作用一些了,注意观察,他们都比较短了,不用写那么长了,而且也还比较直观,如果我要定义一个无符号整形,我就不用写unsigned int a;这么长了,只需UINT a;多简单,多明了,所以我说其实不烦人吧。其中 DWORD算是 SDK 程序中可以经常看见的一个数据类型了,经常被使用,很多新手也就不明白,这是什么数据类型啊,现在看到了吧,其实就是无符号长整形unsigned long,给他取了个外号而已没什么技术含量,所以不用怕,程序中究竟是写unsigned long 还是DWORD 都看你自己心情,因为他们都代表同一种数据类型。下面再介绍2 个很重要的,经常被使用到的,无处不在的数据类型WPARAM,LPARAM先看看他们定义吧typedef LONG_PTR LPARAM;typedef UINT_PTR WPARAM;先告诉你,这2个数据类型很重要,不是危言耸听,以后你写SDK 程序就知道了,看他们的定义如上,有些迷糊?别,我们一步一步分析,我们分析 LPARAM。首先定义 LPARAM为LONG_PTR 也就是用LPARAM 的地方也就可以写成LONG_PTR,LONG_PTR 又是被定义成什么的呢?typedef long LONG_PTR;看到了吗?也就是 long 所以归根结底,LPARAM就是 long 型,所有 LPARAM 型的变量,你都可以直接使用long 数据类型代替。不过不推荐这样,至于为什么,各位思考思考呢以上这些数据类型是参考MSDN 中的说明,或者可以查看WinDef.h 这个头文件查看这些Windows 数据类型的定义,那么也请各位自己推推看LARAM和 WPARAM 的真面目吧 各位朋友在推导的过程中可能发现LONG_PTR 的定义是这样写的#if defined(_WIN64)typedef _int64 LONG_PTR;#elsetypedef long LONG_PTR;#endif这是什么意思呢,能看懂英文都能知道这在定义些什么,如果定义了_WIN64 这个宏那么就定义LONG_PTR为_int64,否则定义LONG_PTR 为 long。很简单吧也就是说如果_WIN64 这个宏在前面被定义了,那么这里的LONG_PTR 就被定义为 _int64 这个在64 位编程下的数据类型,否则就定义为long 型,这样说应该比较好理解了吧。在这里,各位就不必深究 _int64 了,在目前的主流32 位编程下很少使用它啦。理解就ok 了。这样定义是微软为了程序员编写的程序能在32 位与 64 位下都能编译而采用的伎俩。有关这些Windows 的数据类型,想查看他们的真面目,其实很简单,在VC6.0,VS2008这些集成开发环境里面,你只需要在一个数据类型上面点击右键,在弹出菜单中选择GotoDefination或者是 查看定义 就可以看到了,如果看到的还不是最终面目,在继续上面步骤。直到看到它的本质数据类型为止。通过这样,新手对于Windows 的这些复杂的数据类型定义也就有了根本的认识,不再是迷迷糊糊,在以后的编程中也就不会出现不知道用哪种数据类型或者哪些数据类型之间可以相互转换的情况了。不过还需要多多观察与练习才是啊下面再来看一看windows 中定义的一些宏#defineVOID void#define CONSTconst2 个最简单的宏,也是只变成大写而已,难道又是为了方便程序员不切换输入法?还真的人性化呀。Windows SDK 中的宏定义是最庞大的,最复杂的,但也是最灵活的,为什么这样说,先不告诉你,我会在以后的系列文章中一点一点的讲解,累积,因为太多了,也比较复杂,我们就采取在需要用到的时候才讲解它,目前看来还没这个必要了解那么多,就了解上面2 个很简单的好了,像其他如:WINAPICALLBACKGetWindowText这些宏现在讲了不但记不住还会增加你们的负担。,我们就在以后要用到的时候再做讲解。到这里第一篇系列文章的内容也就差不多了。新手朋友们哪些地方迷惑的,提出来,我可以考虑是否加在后续的文章中进行解说。本SDK 系列入门教程需要你们的支持。谢谢。今天,开始第二篇文章,这章我准备介绍一下Windows 平台下编程中Unicode 编码和 ASCII编码的相关问题。不知 道各 位新手 朋友 们遇到 这样 的问题 没有 呢,新 建一 个 Windows 应用 程序,调 用MessageBox 这个函数,准备让它弹出一段提示文本,可是编译器在编译的时候却报错说,不能将const char*或者const char转换为const wchar_t*之类的提示呢,很多刚接触Windows API 编程的朋友们在这里可能就卡住了,不知如何下手解决了,其实,这就是Unicode 编码和 ASCII 编码的问题了。我下面就会一一道来关于 Unicode 和 ASCII 具体的编码是怎么的,我这里就不详细介绍了,也介绍不了,如果需要深入了解,网上有很多这方面的专门文章,我这里就只对Unicode 编码和 ASCII 编码在Windows 平台下的编程相关的内容进行介绍。我们都知道Unicode 和 ASCII 最大的区别就是Unicode 采用 2 个字节来存储一个字符,不管是英文,汉字,还是其他国家的文字,都有能用2 个字节来进行编码,而ASCII 采用一个字节存储一个字符,所以对于英文的编码,那是足够的了,可是对于汉字的编码,则必须采用一些特殊的方法,用2 个 ASCII 字符来表示一个汉字。我们在写程序的过程中,势必要和字符打交道,要输入,获取,显示字符,到底是选用Unicode字符呢还是ASCII 字符呢,这都是各位自己的权利。但为了程序的通用性和符合目前操作系统的主流趋势,Unicode 编码是被推荐的。由于Unicode 字符要比ASCII 字符占用的空间大一倍,编译出来的程序在体积上和占用的内存上必定要大一些,不过这并不是什么很大的问题。所以微软目前的SDK 中保留了2套 API,一套用于采用Unicode 编码处理字符的程序的编写,一套用于采用ASCII 编码处理字符的程序的编写。例如,我们上面提到的MessageBox,它其实不是一个函数名,而是一个宏定义,我们先来看看它是怎么被定义的,再来讨论它。#ifdef UNICODE#define MessageBoxMessageBoxW#else#define MessageBoxMessageBoxA#endif看到了吗?很简单是不是,如果定义了UNICODE这个宏那么就定义MessageBox 为MessageBoxW,如 果 没 有 定 义 UNICODE这 个 宏,那 么 就 定 义MessageBox为MessageBoxA,MessageBox 后面的W 和 A 就是代表宽字节(Unicode)和 ASCII,这样,其实存在于 SDK 中的函数是MessageBoxW 和 MessageBoxA 这两个函数.MessageBox 只是一个宏而已。所以在程序中,这3 个名字你都可以使用,只不过需要注意的是,使用MessageBoxA 的话,那么你要注意传给它的参数,字符都必须是单字节,也就是 ASCII,在程序中就是char,如果使用 MessageBoxW 的话,那么,字符都必须使用Unicode,程序中就是wchar_t。但是这样有个非常不方便的地方那就是,如果你使用W 后缀系列的函数的话,那么你的程序使用的字符就是Unicode 字符编码的,但是如果你需要用这个程序的源代码编译出字符采用ASCII 编码的程序,那么需要改动的地方就太大了。凡是涉及到字符操作的地方都需要改变。那么,有没有比较好的办法不做更改就可以用同样的代码编译出 ASCII 版本的程序呢。当然有,就是我们在编程的时候尽量使用不带后缀的宏定义,如上例,就使用MessageBox,其中的参数也不明确使用char 还是 wchar_t 而是使用微软给我们定义的TCHAR 字符数据类型,它的定义和上面MessageBox 函数的定义差不多,都是根据是否定义了UNICODE 这个宏来判断是将TCHAR 定义为 char 还是 wchar_t,所以这样一来,这个TCHAR 的数据类型就是可变的了,它根据工程的设置而定义为相应的最终字符类型,这样我们的程序就可以不做任何更改就可以轻松的编译出另外一个版本的了。是不是非常方便。前面 2 篇文章纯文字的介绍比较多,因为很多是概念性的,需要理解,后面的文章我准备配合一些小示例程序,使用一些简单的API 函数,遇到的相关的概念在一并介绍的方法进行。所以,前 2 篇文章如果各位朋友不是很能理解,不用担心,影响不是很大,经过后面的学习,你就会慢慢的理解前面所说的内容了。下面我罗列一些我们在Windows 平台下编程经常使用到的和字符或字符串有关的数据类型。char和wchar_t这两个类型大家绝对不会陌生吧,一个是单字节的字符类型,一个是宽字节的字符类型(也就是 Unicode 字符)。charc=b;wcha_twc=Lb;上面我就分别定义了2 个变量 c和 wc,相信第一个定义大家都看的懂,就是定一个字符变量 c,其中保存了 b这个字符。那么第二个呢?我相信还是很多人都看的懂,要是你看不懂也没关系,现在就告诉你,也是定义一个字符变量wc,只不过这个字符变量是Unicode字符变量,用2 个字节来保存一个字符,而上面的c 这个字符变量只有一个字节来保存,那么在 b前面的 L 又是什么意思呢,它就表示这里的b这个字符是一个Unicode 字符,所以第二个定义的意思就是将Lb 这个 Unicode 字符保存到wc 这个 Unicode 字符变量中。如果我要定义一个字符数组怎么定义呢?用分别用单字节的char和宽字节的wchar_t 来定义就应该是:charc10;wchar_t wc10;如果是要带初始化的字符数组的声明,我们来看看怎么写char c=beyondcode;wchar_t wc=Lbeyondcode;看到了吗,宽字节的操作其实和单字节的字符操作一样吧,只是在前面加上L 表示是宽字节的字符或者字符串。上面都是属于C/C+中的知识,并没有涉及太多Windows 中的数据类型,那么各位朋友们在 Windows 编程中看到的满到处都是的TCHAR,LPSTR,LPCSTR,LPWSTR,LPCWSTR,LPTSTR,LPCTSTR 这些数据类型又是怎么回事呢?别急,我们一步一步的来,最后我会联系到那上面去的。上面的你都知道或者是理解了的话,那我们继续,除了可以声明一个字符数组,我还可以定义一个字符指针变量来指向一个字符数组,当然这个字符数组可以是Unicode 的宽字节字符数组,也可以是单字节字符数组,如下:charc=hello beyondcode;/定义一个字符数组wchar_twc=Lhello beyondcode;/定义一个宽字节字符数组char*p=c;/定义一个字符指针,指向刚才的字符数组wchar_t*wp=wc;/定义一个宽字节字符指针,指向刚才的宽字节字符数组这样之后,我就可以通过指针来改变刚才我们定义的2 个数组,例如:p0=H;wp0=LH;把上面 2 个数组的第一个字符通过指针改变成大写。这里是可以通过指针来修改的,因为我没有定义指针为常量指针,也就是没有加const 修饰符。如果我像下面这样定义的话,那么就不能通过这些指针来改变他们所指向的数据了,而是只有读取他们。constchar*p=c;constwchar_t*wp=wc;上面将的都是C/C+的基础知识,有点啰嗦,为了照顾新手朋友们嘛,下面我们就来看看Windows 是怎么定义它的数据类型的首先,定义了CHAR,WCHAR 的这 2 个字符数据类型,就是我们上面讨论的两个字符数据类型改了一下名字而已。现在你还不昏吧typedef charCHAR;typedef wchar_tWCHAR;然后,用刚才定义的CHAR,WCHAR 这 2 个字符数据类型去定义了一系列其他字符指针类型。typedefCHAR*LPSTR;typedefWCHAR*LPWSTR;这样一定义之后,LPSTR 的就是CHAR*,而 CHAR又是 char,所以 LPSTR 的本质就是char*,也就是我们上面熟悉的不能再熟悉的字符指针,那 LPWSTR 不用我推导,相信你也推导出来了吧。不过我还是推导一下,LPWSTR 是 WCHAR*,WCHAR 是 wchar_t,这样 LPWSTR 就是wchar_t*,也就是我们上面讨论的宽字节字符指针。上面这些定义都是在 WinNT.h 这个头文件中定义的,读者朋友们有兴趣在这个头文件里面去挖掘挖掘吧,上面 2 个定义我只是提取了重要的部分,其实在里面他还定义了其他很多别名.看了 LPSTR,LPWSTR 是怎么一回事之后,我们再接再厉,看看LPCSTR,LPCWSTR这 2个数据类型又是怎么一回事呢,老规矩,先看windows 的定义。typedefCONSTCHAR*LPCSTR;typedefCONSTWCHAR*LPCWSTR;和上面的比较,名字中就多了一个大写的C,这个 C 的含义就代表是const 修饰符,也就是我们上面所说的常量指针,指向的内容不能通过这个指针被改变,但可以读取。定义中的大写的 CONST 也是一个宏,我在第一篇文章中就讲过了,代换出来也就是const,所以请读者自己推导一下这两个数据类型的本质是什么。所以,在 windows 平台下的编程过程中,凡是可以使用char*的地方,你都可以使用LPSTR来代替,凡是可以使用wchar_t*的地方,你都可以使用LPWSTR 来代替,至于怎么用,还是那句老话,看你个人心情,只不过Windows 的 API 函数中关于字符串的都是使用LP 这种数据类型。但是你还是可以给他传递char*或者wchar_t*,只要他们的本质是一样的,那怎么不可以呢 下面,我们来看一看一些示例。charc=c;和 CHAR c=c;是一样的wchar_t wc=Lw;和 WCHAR wc=Lw;是一样的char*p和 LPSTR p 是一样的wchar_t*wp和 LPWSTR wp是一样的再来看看动态内存分配怎么写的呢char*p=new char10;/动态分配了十个字符也可以写成CHAR*p=new CHAR10;LPSTR p=new CHAR10;LPSTR p=new char10;宽字节的再来一次wchar_t*wp=new wchar_t10;也可以写成下面这些形式WCHAR*wp=new WCHAR10;LPWSTRwp=new WCHAR10;LPWSTRwp=new wchar_t10;上面定义的这些字符指针p,wp 都没有用const 修饰符,所以可以通过他们来修改他们所指向的内容。这里留给读者一个问题,怎么定义有const 修饰符的字符指针呢,都可以用什么形式来写呢,写得越多越好哟。通过上面这些,我想你大概已经了解了LPSTR,LPCSTR,LPWSTR,LPCWSTR 这四个数据类型了,他们无非就是:LPSTR-char*LPCSTR-const char*LPWSTR-wchar_t*LPCWSTR-const wchar_t*下面我提一个问题,如果你在你的程序中使用的字符串都是通过LPWSTR,LPCWSTR这种宽字节(Unicode)字符指针来进行操作的,那么在 Unicode 环境下编译,完全没有问题,如果这时你 需要编译一套 ASCII 版本的 程序,那你会怎么办呢?你说将 用 LPWSTR和LPCWSTR 的地方全部换成LPSTR 和 LPCSTR,再将字符串前面的L 去掉就可以了,对,这是一种方法,但是!所有人在这里都应该知道我要说但是,这也太麻烦了吧。难道没有通用点的方法吗?有!所有人在这里也都知道我会说有,呵呵。那就是使用微软的通用数据类型,说通用数据类型有点太专业了,其实也就那样,请听我慢慢分析来。我在上一篇文章中说过,凡是涉及字符串操作的API 函数有 2 套,一个 A 系列的,一套 W 系列的,还有一套宏,能根据不同的工程环境定义成不同的API 函数名。那么在字符类型上微软也使用几乎同样的技术,定义了一套宏能根据不同的工程环境定义成不同的字符数据类型。我上面就提到过的TCHAR,LPTSTR,LPCTSTR 就是这样的类型。首先说说 TCHAR,它是被这样定义的:#ifdefUNICODEtypedefWCHARTCHAR;#elsetypedefcharTCHAR看到了吗?它也是根据UNICODE 这个宏被定义没有,如果被定义了,那么 TCHAR 代表的数据类型就是WCHAR,也就是 wchar_t,如果没被定义,那么TCHAR就代表的是char同样 LPTSTR,LPCTSTR 也是这样的,考虑到篇幅,我就只列出LPTSTR 来给大家看看了#ifdefUNICODEtypedefLPWSTRLPTSTR;#elsetypedefLPSTR LPTSTR;这个是我简化了的定义,真实面目有些复杂,不过意思也是如此,有兴趣可以自己看看,在WinNT.h 这个头文件中。下面再次解释一下上面这个LPTSTR 的定义,还是老样子,根据UNICODE 这个宏被定义与否来决定怎么定义LPTSTR,如果是定义了UNICODE 这个宏,表示当前工程环境是Unicode 环境,那么 LPTSTR 就被定义为了LPWSTR,LPWSTR 就是我们前面所讲的wchar_t*,所以此时LPTSTR 代表的数据类型就是wchar_t*,如果这时的工程没有定义UNICODE 这个宏,那么就定义LPTSTR 为 LPSTR,而 LPSTR 就是我们前面所说的char*,所以这是的LPTSTR 就代表 char*。懂了吗?各位,我都觉得自己有些啰嗦了不好意思 然后还有一个宏需要讲一下,由于我们使用通用数据类型,那么我事先就不知道我的源代码需要在 Unicode 下编译还是在ASCII 环境下编译,所以如下这种情况TCHAR tc=a;或者是TCHAR tc=La;是否合适呢?前面我已经说过了字符或字符串常量前面加L 代表这是宽字节的字符或字符串,将一个宽字节字符赋值给一个TCHAR 数据类型的变量tc,什么情况下是正确的呢?各位思考一下呢?如果当前工程是Unicode 环境,那么TCHAR 数据类型就是wchar_t 的宽字节类型,所以tc就是宽字节字符变量,那么上面第二个赋值语句就是正确的,而第一个就是错误的。如果反过来,当前的工程是ASCII 环境,那么TCHAR 代表的是char 这种数据类型,那么第一个赋值语句就是正确的,而第二个就是错误的了。分析了这么多,我就是要讲一个宏_T(),只要将字符或者字符串常量放在_T()这个宏里面,那么这个宏就能根据当前的环境决定是否在字符或字符串前面加L,如下面:TCHAR tc=_T(A);如果这么写,在不需要改写源代码的情况下,就可以编译出Unicode 和 ASCII 两套程序而只需要改变工程的环境而已。这篇文章的内容大概就这么多了,关于后续文章的内容安排,我会适当采纳各位朋友的留言来进行安排。在这里我介绍的是Windows 平台下的和字符串操作有关的数据类型,至于 MFC 中的 CString类,c+标准库中的string,我就不做讲解了。大家好,还是我beyondcode,再次见面,前面介绍的那么多理论知识,你们都懂了吗?就算还没有彻底领悟,但至少还是有那么一点意识了吧,知道有那么一回事了吧。这一篇我打算通过一个小小小例子,来回忆一下我们以前介绍的相关知识,如Windows 的数据类型,特别是和字符和字符串操作相关的数据类型,还有就是Unicode 和 ASCII 在 API 函数上的具体体现。另外,SDK 编程交流群已经建立,很多朋友踊跃参加,系列文章和群的发展离不开你们。群号:81543028。Ok,我们正式开始,我打算从一个简单的SDK 程序开始,别怕,就几行代码而已/*BYbeyondcode*/#include int WINAPI WinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTR lpCmdLine,int nShowCmd)MessageBoxA(NULL,Hello beyondcode,Title,MB_OK);return 0;复制代码程序你已经看到了,这恐怕就是一个最简单的带窗口的SDK 程序了吧,如果你能写出代码行数比这个还少,又带窗口显示字符串的SDK 程序,欢迎交流,呵呵,开个玩笑。程序倒是简单,可是我还是要问一问,这个程序,你通过观察我在字符串的处理,还是在API 函数的调用,还是主函数的参数写法,你能看出什么问题呢?.对,就是我全部明确指出是单字节版本的,WinMain 的第三个参数是LPTSTR 类型,调用的MessageBox 是带 A 后缀的单字节版本,字符串常量Hello beyondcode和Title 都没有使用L 前缀。那么第二个问题来了,如果我告诉你我现在的工程环境是使用 Unicode 字符集(工程使用的字符集可以在【项目】-工程属性弹出的属性页中的【配置属性】中的【常规】左边的【字符集】中设置),那么我上面的程序能正常通过编译吗?当然能,因为我已经试过了,不信你也可以试试,可是为什么呢?这是因为我指定的参数和函数需要的参数都是单字节版本的,也就是说他们相互匹配。要是我这里将MessageBoxA 改成 MessageBoxW 呢?就会出错吧,因为MessageBoxW 的第二个,和第三个参数是需要LPCWSTR,通过上一篇学习,我们知道也就是const wchar_t*,而我给出的两个字符串常量却没有用L 前缀.也就是说他们是单字节的,传给宽字节版本的MessageBoxW 当然就类型不匹配了啊,所以就通不过编译了吧。通过上面的学习,我再出一个问题,如果我此时的工程环境是使用Unicode 字符集,而这里我不用 MessageBoxA,也不用 MessageBoxW,而是用 MessageBox,其他的都不变,结果会怎么样呢?不能理解的可以加群讨论哟好了,单字节版本的程序,我们已经看到了,我们再来看看我们怎么才能把它改成宽字节版本的呢?其实需要改的地方不多,也就5处 WinMain 改成 wWinMain,WinMain 的第三个参数改成LPWSTR,MessageBoxA 改成 W,两个字符串常量加L 就 ok 了。/*BY beyondcode*/#include intWINAPIwWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPWSTRlpCmdLine,int nShowCmd)MessageBoxW(NULL,LHello Beyondcode,LTitle,MB_OK);return 0;复制代码如果我想写一个代码比较通用的版本,也就是可以不用改动代码,就能编译出Unicode 和 ASCII 的两个版本的程序,我应该怎么写呢?其实就是我上一篇重点讨论的,凡是涉及到字符串的都不明确指出是Unicode 还是 ASCII 版本的,调用的API 函数凡是涉及到字符串参数的都不明确指出调用是A 后缀的还是W 后缀的函数,而是调用没有后缀的函数,如上面的MessageBox,这样就能写出代码比较通用的程序了。那么我们现在来把我们上面的程序改一改,让它通用/*BY beyondcode*/#include#include intWINAPI_tWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPTSTRlpCmdLine,int nShowCmd)MessageBox(NULL,_T(Hello Beyondcode),_T(Title),MB_OK);return 0;复制代码WinMain 被改成了 _tWinMain,_tWinMain 也是一个宏,根据UNICODE 这个宏被设置与否而被定义成WinMain 或 wWinMain,和 LPTSTR 是一样的,这里还需要注意的是要包含 tchar.h 这个头文件,因为 _tWinMain和_T()这些宏是被定义在里面的。经过上面我们就写出了第一个SDK 的可以编译出两个版本的比较通用的程序代码了。是不是有点成就感了呢。下面,我们继续在上面的程序中加一些功能,让它计算1 到 10 的和,然后把结果显示给我们看,这个地方,很多SDK 初学者就不知所措了,因为一个和是一个整数,怎么显示这个整数给我们呢,通过对话框?MessageBox,可是 MessageBox 显示的是字符串。而我们这里又不是控制台程序可以使用printf 之类的格式化输出函数来输出数字,也不能使用cout 之类的 C+对象来输出,那我们怎么办呢?通过对话框来显示结果是不错的选择,但是对话框需要的是字符串,那我们就把我们的结果格式化到一个字符串里面,然后传送给MessageBox让它显示出来。那么就需要用到格式化字符串函数,下面我们就介绍wsprintf 这个函数#ifdefUNICODE#define wsprintfwsprintfW#else#define wsprintfwsprintfA#endif/!UNICODE复制代码说它是函数,是不确切的。因为它实际是一个宏,根据环境被定义成不同的函数名wsprintfW 或者 wsprintfA,而我们为了程序的通用性,直接使用wsprintf,传递的参数凡是涉及到字符串常量的我们都是用_T()宏,字符串指针的我们都使用LPTSTR 和 LPCTSTR。下面我就先贴出添加了功能的程序代码,然后在做分析,你可以先不看分析,自己看一看代码,不懂的猜一猜它的意思。/*BY beyondcode*/#include#include intWINAPI_tWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPTSTRlpCmdLine,int nShowCmd)int sum=0;for(int i=1;i=10;i+)sum+=i;TCHAR strSum256=0;wsprintf(strSum,_T(%d),sum);MessageBox(NULL,strSum,_T(Title),MB_OK);return 0;复制代码怎么样,也还不算复杂吧,计算1 到 10 的那部分不用我讲了吧,最后的结果存放在 sum 这个变量里,我们现在的目的就是要让它显示在MessageBox 弹出的对话框上面。首先我们定义一个字符数组,我使用的是通用类型TCHAR,然后把它全部初始化为0。接着调用格式化字符函数wsprintf,它的第一个参数是LPTSTR 类型的,指定经过格式化的字符串存放的地方,第二个参数是指定以什么格式来格式化后面的数据,这里我们要格式化一个整数,所以指定%d,这个和 printf 这些函数是一样的,后面的参数就是我们要格式化的数据了。这里还有一点可能有些新手朋友们不太懂,那就是,wsprintf 要求的第一个参数是LPTSTR,而我传递的是一个TCHAR 的数组名,这里我就在啰嗦一次咯,我们假设我们的环境是UNICODE 的,那么LPTSTR 相当于什么类型呢?上一篇就讲过的啊,就是wchar_t*就是宽字符指针,而我们知道数组名就是代表这个数组元素类型的指针,那么这里TCHAR 数组的元素类型就是TCHAR,在 Unicode 环境下,TCHAR 就是 wchar_t 也就是说strSum 代表的是 wchar_t 类型的指针,也就是wchar_t*,所以看到了吗,他们是一样的类型。通过上面的wsprintf 函数的调用strSum 这个字符数组中就包含了计算结果的字符串表示,然后我们通过MessageBox 讲这个字符数组中的内容显示出来。在这里MessageBox 的第二个参数类型是LPCTSTR,也就是const wchar_t*,而我们上面说过我们的strSum 代表的是wchar_t*,这里同样可以传递给它又是为什么呢?这是因为阿,这里strSum 在传递给MessageBox 的时候就隐式转换成了const wchar_t*了。有关格式化字符串的函数还有如下,详细用法各位可以查看MSDN,和上面所介绍的都差不多sprintf单字节版本的C/C+库函数swprintf宽字节版本的C/C+库函数而我们上面的wsprintf 和上面两个函数看起来很相似,大家不要搞混淆了啊,wsprintf 最前面的 w 不是代表Wide,宽字节的意思了,而是Windows 的 W,代表是windows 的 API 函数了,其实它是一个宏这在上面已经说过了,真正的 API 函数其实是wsprintfA 和 wsprintfW这两个,在不严格的情况下通常我们也说wsprintf 是函数,只要大家懂就行了OK,这一篇文章就到这里了,源代码就这么点,我也不上传了,各位有兴趣自己敲一下。下一篇,我讲会就怎么自己创建窗口进行介绍,谢谢大家的支持。上一篇,讲了一个简单的SDK 程序的多种版本的编写,弹出了一个窗口,显示了我们计算1 到 10 的结果,计算的程序不是重点,重点在于,一:让大家认识到Unicode 版本的程序和 ASCII 版本的程序在编程方面的区别,以及怎么样编写出通用代码的程序。二:怎么样运用 API 或者 c+库函数格式化非字符数据到一个字符串中显示出来。不过,那个相当简单的程序,还算不上是一个正儿八经的SDK 程序,也就是说还不是一个纯爷们儿,因为我们并亲自完成一个SDK 程序的经典步骤。而是调用了一个MessageBox API函数,这个函数虽然使用简单,但是在它的内部,那可是相当复杂啊。怎么个复杂法,具体的我不知道,但是我知道的是一个SDK 程序的经典步骤它是都用到了的,什么是编写SDK 程序的经典步骤呢?新手朋友们听好了哦,现在我就告诉你。第一步:注册窗口类第二步:创建窗口第三步:消息循环第四步:编写窗口消息处理函数上面我所说的,听起来都比较专业,下面我就解释一下,什么是注册窗口类呢?注册窗口类就是使用一个窗口类结构体(WNDCLASSEX)来描述一类窗口,这类窗口具有相同的属性,也就是你在结构体WNDCLASSEX中指定的那些值。只要是用这个窗口类创建的窗口都具有这些特性。至于 WNDCLASS 能描述哪些特性,下面会具体讲,这里你只要了解是用一个名叫 WNDCLASSEX的结构体来描述一个窗口的类别。创建窗口应该比较好理解吧,就是创建一个具体的窗口,好像是一句废话嘛。也就是说这个窗口是根据一个窗口类而创建的,不是凭空而造的。意思是你要创建一个窗口,那么必须要有一个已经注册的窗口类。对于前两步,我打一个比方,就好比你要造一辆车,那们第一步首先是干什么?当然是设计图纸啦,图纸上就有说明这种车有哪些特性。然后第二步才是根据这个图纸来创建一个具体的看得见的车。所以我上面说的注册窗口类就好比设计窗口的图纸,然后就是根据这个窗口的图纸来创建一个具体的窗口。都说成这样了,应该明了了吧至于消息循环,就是创建的窗口随时都有可能发生很多事情,那么发生的这些事情怎么通知你呢?比如窗口最小化了,窗口大小改变了,怎么通知你呢?其实就就是通过消息循环不断的取得窗口所发生的事情,然后以消息的形式发送给我们后面要介绍的窗口消息处理函数。消息处理函数呢就是我们程序员负责编写代码对具体的消息进行具体的处理,当然你也可以不处理,交给系统的默认处理函数来处理。对于这两步,我也打一个比方。消息循环就好比汽车的一个总传感器,它源源不断的将汽车内部所发生的事情以消息的形式通过仪表板传达给开车的人,开车的人根据具体的事情而采取具体的操作,当然你也可以不操作,无动于衷,对于windows 消息来说,不操作倒没有什么,而对于开车的人来说,不操作的后果就不好说了。在这里,这个总传感器就相当于SDK 程序的消息循环,不断的发送消息,而开车的人就相当于窗口消息处理函数,负责处理各种消息。明白了吧,还不明白的话就看看下面的具体的程序吧,也需还有最后一丝希望可以让你恍然大悟。讲了正儿八经的SDK 程序的经典步骤后,我们进入正式的代码阶段,通过代码结合上面所讲理论进一步巩固知识。我讲逐步讲解并逐步编写一个自己注册窗口类,创建窗口,带消息循环,并自己编写消息处理过程的程序。首先给出程序框架/*BY beyondcode*/#include#include LRESULTCALLBACKWinMessageProc(HWNDhwnd,UINTmsg,WPARAMwParam,LPARAM lParam