大学上了几年,懂一点Windows编程. 常有人问入门的问题, 发现自己在做重复劳动, 于是想写几个文章, 介绍一下基础的东西. 就算编程之余一点消遣~
我大概讲如下几个最入门的东西, 复杂一点的, 我也不敢讲, 大学几年学的东西比别人稍稍快那么一点, 却误导了一批又一批同学. 况且编程这东西, 入了门也就容易了. 应该是分为4篇, 也有可能5 篇. 我会精简篇幅. 概况通用的东西, 细节方面点到为之. 如果能看懂, 就算不会写BinPlayer, 也至少知道个大概了.如果哪里讲的不清楚, 可以给我发邮件, 也可以在这里回复.
- 概述 UNICODE 和数据类型
- HANDLE, INSTANCE, Window, 和创建窗口.
- MESSAGE, 鼠标, 键盘, 文件操作.
- COMMON CONTROL, GDI, 资源文件.
对于参考资料上:我只给大家推荐两本书.
1. Windows via C/C++ - Jeffrey Richter
这本书我强烈推荐. 不过这本书不会讲最最基础的东西. 比如我在这讲的东西几乎都没讲, 除了UNICODE.
2. Programming Windows, 5th Edition - Charles Petzold
这本书很出名, 但是最新版是在98年出的. 这么多年过去了, 里面有很多东西都是有问题的. 不过里面的东西比较基础.
概述
我就不大谈特谈Windows编程的历史,未来什么什么的了.
WIN32 API是Windows应用编程的最底层.当然我这里所说的WIN32开发也包括COM编程.
编出来的程序就是大家常见的exe,dll之类.可以原生地被Windows操作系统调用运行,不需要辅助程序.
WIN32 API编程有时候又叫SDK编程,也有叫Windows API编程.它和大家常用的.NET有着本质的区别.跟MFC和VB6之类的也有一定的区别.这个技术在十多年前是Windows编程的基础,后来又有了VC,VB,Delphi等其他技术.如今大学计算机专业中, 知道和掌握这种技术的人越来越少了.
优势上讲:
WIN32 API编程原生地调用系统API,如果排除一些特殊情况, 使用WIN32 API编程出来的程序的运行效率是最高的.
另外,Windows SDK 提供了完全的WIN32 API的C和C++开发所需的头文件和库文件.
当然也有劣势:
入门困难这个是最大的问题.其次是界面上开发略显复杂.
但是只要了解一点里面的机制,再拥有了MSDN文档,可以说一切就受你控制了. 大多数时候MSDN给的说明都很全面,但是有些地方还是有问题的,这时候用搜索引擎之类的查一下就OK.
Windows API到底是个啥, 怎么才能用?
简单明了的说. 操作系统提供了一系列函数和功能.
你如果要C/C++语言调用这些函数, 就必须要有这些提供函数信息的.h文件和.lib文件. 这些都包含Windows SDK中. VS2008已经包含了精简的Windows SDK.
这些函数的具体信息可以在MSDN中找到.
如最基本的函数CreateWindow. 就可以在下面的网页中找到相应信息.
http://msdn.microsoft.com/en-us/library/ms632679.aspx
(注, 看不懂没关系. 用起来会比看上去的要简单)
第一个程序,命令行的hello world.
是的,命令行的hello world.
使用VS2008建立新的Win32 Console工程, 在向导中注明创建空的功能, 然后创建一个main.cpp文件.(如果你不知道这步如何做, 那么请尝试去做. 如果尝试了还是不会做, 你还是不要继续看了 ) 输入并且运行以下代码.
#include
#include
int _tmain () {
wprintf(TEXT("Hello World"));
return 0;
}
看上去和C语言的Hello World几乎一样,只是函数变了.
其实原来的C语言的程序在这里也完全可以编译和运行,但是这里为什么要使用这些函数呢?如果你有这么想,那我觉得你还是有前途的.如果没有,你还是回去考研吧,只有高学历才能救的了你.
回答,是因为UNICODE
UNICODE
UNICODE是什么应该多少知道一点.
下面我们简述Windows 下的UNICODE.
Windows NT开始, Windows 的API就通过UNICODE实现.而之前的Windows 9x是使用ANSI码实现. 这就是为什么Windows XP只要下载语言包就能够更换语言, 而Windows 98则不行.
不过WIN32 的API是同时支持ANSI模式和UNICODE模式的.不过在NT上, ANSI模式的函数是通过转换编码后调用UNICODE模式函数来实现功能, 而在9x中反之. 所以现在而言, 编程中使用UNICODE既能够保证字符的显示, 又能够稍稍提高效率.
如果你想知道这里的UNICODE使用的是那种编码的话, 那么我可以告诉你这里使用的是UTF-16. 不是在网络中常用的是UTF-8. UTF-8有UTF-8的优势, 比如在显示英文时比较小, 同时能兼容ANSI. 但是UTF-8长度是不固定的, 而UTF-16中长度是固定的2字节, 16位.
在C/C++中, 一个 char 是一个字节, 不过Windows编程中的UNICODE字符是WHCAR, 为两个字节. 相对应的, C语言中的printf输出打印char字符, 这里就得使用wprintf输出WHCAR字符. 不过值得说明的是, WCHAR本身是这样定义的.
typedef wchar_t WCHAR;
如何定义WCHAR字符串呢?
WCHAR hello[16] = L"hello world";
这里的L是一个宏, VS能够在编译的时候将其编译成WCHAR字符.
也许你已经注意到在刚才hello world中使用了TEXT,而没有使用L.解释这个之前, 我先隆重介绍一下 TCHAR.
TCHAR 是什么呢, 也是一个typedef.
#ifdef _UNICODE
typedef wchar_t TCHAR;
#else
typedef char TCHAR;
#endif
如果定义了_UNICODE, 那么TCHAR就是16位的, 否则就是8位的.
这个_UNICODE不需要自己在代码中定义, VS的工程属性里面有一个叫Character Set, VS2008在默认情况下是Use Unicode Character Set, 这样在编译的时候, 编译参数就带有/D "_UNICODE".
上面曾经介绍道CreateWindow这个API. 其实CreateWindow并不是一个函数, 而是一个宏. 它使用类似上面的方法来对应CreateWindowA (ANSI) 和CreateWindowW (UNICODE)两个函数.
几乎所以的常用API都是这样: 一个xxxA, 一个xxxW. 一般来说, 两者只有在字符串上的参数有区别.有些C/C++的标准库的函数则使用了_t 作为前缀 (比如 _tmain, 对应的就是 main 和 wmain. 另外, 那个Hello World程序里面的 wprintf最好写成 _tprintf)
使用TCHAR比使用WCHAR有什么好处呢?
你如果想更换字符集, 只要随时去掉或者添加_UNICODE参数即可.
因为系统API会跟着变为A版和W版, 字符也应该跟着变.
于是也就有了TEXT() 这个宏 (就像TEXT("HELLO") ). 它会在UNICODE的时候变为L (L"HELLO"),在非UNICODE的时候则变为空气~ ("HELLO").
(不过遗憾的是, 并不是所有函数都有A版和W版, 有些被MS认为已经过时的函数就没有W版)
如何在不改变数据的情况下转化WCHAR和CHAR呢?
首先,强转是不行的,也就是(WCHAR) CHAR 是不行的.
其次要知道, ANSI 模式下, 不同语言的字符可能拥有一样的表达方式. 英文语言环境系统下打开中文的WinRAR这样的软件看到的是乱码. 就是这个原因.
MS提供了两个函数, MultiByteToWideChar 和WideCharToMultiByte. 我在这里给出两个函数的详细说明.
当然我还是鼓励大家自己看MSDN, 因为如果走上WIN32和COM编程的路, MSDN可能是天天要看的.
使用示例:WideCharToMultiByte(CP_ACP , NULL, WCHAR *, -1, CHAR *, 1024, NULL, FALSE);
第一个参数CP_ACP是一个UINT(这是一个宏, 代表unsigned int), 因为ANSI模式的编码是各种各样的. 这个参数就告诉他是使用哪种编码. 不过这里的CP_ACP意义特殊, 表示使用当前系统的语言选项. 这个在Windows “控制面板”上的”区域和语言选项”上的最后一页可以设定. 另外, GB2312是936.
第二个参数一般是NULL, 有特殊需求是有用.
第三个是被转换UNICODE字符的指针.
第四个是被转换UNICODE字符的长度, 如果是-1, 系统会自动检查.
第五个是转换后ANSI字符串的目标地址. 注意, 这里必须要事先分配好内存, 比如使用数组. 光声明个CHAR * 然后传过来是新手常见的错误. (事实上, 如果你是可以这么做的, 但是需要第六个参数配合).
第六个是第五个参数的缓存大小. ( 如果是0, 这个函数会返回需要的长度. 这样就可以做所谓的 ”Safe Operation” ,很多Windows 函数都支持这种功能 )
第七八个参数是设定默认字符使用的, 一般设为NULL, 和FALSE.
相比这个就简单一些, MultiByteToWideChar(CP_ACP, NULL, CHAR *, -1, WCHAR *, 1024); 参数也基本一致.
其他:
使用_t前缀的宏是常见的.不过不是唯一的.
有些函数:如C语言中string.h里面的常见函数:strcmp, strcpy等,在这里就对应lstrcmp, lstrcpy. 不过这些函数是作为系统函数存在. 他们的A版不是string.h里面的函数. 换句话说, 在非UNICODE的时候lstrcmp会调用lstrcmpA 而不会调用strcmp.
Windows.h是Windows编程中的核心头文件.
tchar.h则是提供UNICODE和非UNICODE的字符宏的主要文件之一.
Windows 数据类型和变量命名.
命名方式
这是一个永恒的话题. 也许每个老师都愿意和你探讨探讨. 不过自从我看到下面这句话我就再也不考虑这个问题了. 虽然讲的不是一个东西.
Should array indices start at 0 or 1? My compromise of 0.5 was rejected without. - Stan Kelly-Bootle.
Windows在编写的时候大量使用了匈牙利命名法, 这个几乎在每个Windows API中都多少能看出来点. 不过这并不意味着你也必须使用, 你当然可以使用骆驼命名法和帕斯卡命名法. 比较他不影响你的程序.
数据类型
会影响你程序的是数据类型的宏. 让然, 我这里就把typedef也当为宏.
也许你已经注意到了, 我使用CHAR 来表示 char. CHAR是一个宏.
不仅是CHAR , 常见的数据和类型都有这样的宏.
简单罗列一下
VOID, BOOL, TRUE, FALSE
UINT, INT, ULONG, LONG, LONGLONG,
CHAR, WCHAR, BYTE
HANDLE, HRESULT, HWND, HMODULE 等
为什么要使用这个呢?
要知道不同的编译器, 在对待不同的数据类型是不一样的.同样的使用ANSI C写的代码, 在不同的CPU上, 和不同的编译器上是不一样的. (不过很多人认为与操作系统也有关, 据我说知, 是无关的). 这经常造成功能出现偏差. 举个简单的例子, 早期的int 被作为16位处理, 而现在int 在一般被处理为4字节. 但是INT定义为32位__int32. 另外, 使用BOOL和TRUE, FALSE似乎能帮助你把程序从C++移植到C~.
这里面值得一提的有以下几点.
TRUE: TRUE是1, 所以while (TRUE == 2) { 我是不会运行的; }. 请尽量使用FALSE作为比较对象.
LONGLONG: 有的编译器认识 long long, 甚至long long long. LONGLONG这里是__int64. 这足以应付常见的大数了. 在COM编程中大量使用LONGLONG,它已经能够达到很高的精度了.
BYTE: 不要默认char 是一字节. BYTE才是一字节.
HANDLE, HRESULT, HWND, HMODULE 这样的类型是Windows编程特有的, 他们大多数和DWORD有着某种关系, 但是不要将他们混用. 编译器默认是不会认为他们等价的.
总结和建议:
使用UNICODE编程.
使用TCHAR, TEXT()定义字符串, 使用_tprintf(), lstrcpy()这样的函数.
尽量不要直接使用xxxW和xxxA这样的函数.
使用BOOL, TRUE, INT, LONGLONG 等代替原来的数据类型.
c ++编程示例程序
ReplyDelete级联构造函数和析构函数调用示例代码