日期:2014-05-17  浏览次数:20968 次

程序员应该知道的关于Windows API、CRT和STL二三事

程序员应该知道的关于Windows API、CRT和STL二三事
2011年06月08日
  过去用过OpenGL,擅长图形算法,以及数值计算结果的可视化,最近对建立渲染引擎并用CUDA加快渲染速度感兴趣。主要用C++编程,喜欢跟牛人交流技术。我喜欢辛勤的工作,也相信天道酬勤,所以一直想有一个稳定的工作环境,这个环境能够让我发挥我的特长,并迅速成长起来。我的网易博客:blog.163.com/zhaoxin851055@126.联系邮箱:alexstocks@foxmail.com.
  
  
  
  1.本文编写目的
  本文是为了帮助一些人弄清一些关于Windows API, C运行时程序库(CRT), 和标准C++库(STL)的基本概念。有很多人甚至是有经验的程序员在这些概念上是含糊不清的甚至是有错误观点。如果你想知道他们是在什么基础上实现的并且一直没时间弄清楚这些概念,请花费点时间阅读下本文。
  2. 基本概念
  下面这幅图代表了WinAPI、CRT和STL三者之间的关系。
  表 1: Windows API、CRT和C++标准库之间的关系图
  
  相邻的模块之间可以相互通讯。为什么这么说呢?下面依照这幅图从下到上依次说说明。
  2.2. 硬件层
  每个硬件部分都有自己的命令集,操作系统通过这些命令集控制硬件并与硬件通信。不同硬件部分的命令集数量和复杂度差别很大。通常,同样的硬件如果由不同的硬件厂商实现,它们通常会在标准的命令集外提供一些扩展,使用这些扩展通常可以发挥这些硬件的特性。如果每个程序编写者都针对这些硬件重写一遍程序,每个硬件的生产厂家都有很多,那会把码农们累死滴!于是上帝说,要有操作系统,于是人类 有了一个方便的访问硬件的统一平台。
  2.3 操作系统
  操作系统的目的之一就是把底层硬件的特性给封装起来,然后提供一个统一的接口,以便于计算机操作人员控制硬件。现代版本的操作系统不再允许应用程序直接访问硬件,操作系统能直接访问硬件层时,称操作系统处于核心态(kernel mode)。
  一些老版本的操作系统,如MS-DOS,允许编程人员直接访问硬件。虽然这样做可以使他们写出来的程序能够在一段短的时间内应用硬件的某些高级特性,但是长远来说这样做对这个程序是有害的,因为这个程序将不能在这个硬件的新版本上运行。
  2.4 应用程序编程接口(Application Programming Interface)
  现代的应用程序如果想访问硬件,就必须通过操作系统,具体来说就是使用操作系统提供的API。一个API就是一些功能的统一接口,它能把硬件的这些功能抽象地统一起来,让程序员集中精力实现他们想要的功能。应用程序已经不可能绕过现代的操作系统直接访问硬件,一种更普遍的说法就是应用程序以用户态(user mode)运行。MS Windows提供的API是一群C函数的集合。Windows平台的最低层次的开发语言就是C语言。
  2.4.1 平台软件开发包(Platform Software Development Kit)
  MS发布了一个免费的平台软件开发包(简称之为Platform SDK或PSDK)以促使软件开发员在windows平台上开发软件。PSDK包含如下东东:
  1. 包含这些API生命的头文件
  2. 用于连接的相应的库文件(通过它们能够在相应的DLL中定位到这些API函数)
  3. 相关的说明文档
  4. 各种帮助工具
  例如,如果要打开或创建一个文件,我们要调用函数CreateFile,它在头文件"WinBase.h"中声明,通过库文件"Kernel32.lib"在dll文件中进行函数定位。
  Windows API的函数名称依照骆驼命名法命名,因此不同的函数很容易区分。常量和宏的命名一般是大写。在相应的文档里,会详细的说明每个函数所需要的头文件、库文件以及能够运行的相关平台。
  一个Windows应用程序能够调用任何windows的API函数,前提是应用程序能够正确的找到这个函数的签名并且能够链接到相应的库文件,或者通过函数GetProcAddress和函数签名(一般就是函数的名称)直接连接到它在dll库文件中的地址。
  2.5 C运行时程序库
  基于操作系统的API函数,软件厂商实现了C运行时程序库(CRT)。CRT由一些头文件和相应的源文件构成,这些文件实现了一些基础的公共操作,如字符串操作、一些数学运算函数和基本的输入\输出等操作。通常的,如果一家厂商发布了一个C编译器,它会附带一些CRT库。一些国际标准化组织负责制定C语言的标准并实现一些运行时程序库。
  2.5.1 标准和扩展
  理论上,如果一个程序使用标准C语言开发的,并且有一个平台支持其相应的标准C编译器和动态库,那么这个程序就能在这个平台上运行。但是实际上每个C编译器开发厂家都会对C语言做一些有利于自己的扩展,程序员可以很方便的利用这些扩展开发程序,但是付出的代价就是这个程序不再具有可移动性。
  CRT的函数名称一般都是小写的,宏和常数是大写的,而一些扩展则通常具有一定的以下划线作为开头的标识,例如函数_mkdir。针对这些扩展,相应的文档都会详细的予以说明。
  2.6 关于Unicode的认知
  2.6.1 PSDK支持Unicode
  实际上,以上提到的Win32 API的名称并不是它们的真实名称。这些名称仅仅是一些宏,你可以在PSDK的头文件中找到这些宏对用的函数名称。所以,如果PSDK的文档提到一个函数,如CreateFile,开发人员应该意识到它仅仅是一个宏。它的真实名称是CreateFileA和CreateFileW。是的,它代表了"两个"函数名,而不是一个,是同一个函数在不同Win32函数的两个不同的版本。以'A'结尾的函数接受ANSI字符串,即Unicode字符串,即wchar_ts型字符串。两种版本的函数都在模块kernel32.dll中实现,如果你的编程环境是Unicode则,则宏CreateFile在编译是会被CreateFileW代替,否则用CreateFileA代替。
  Windows操作系统有三大家族:MS-DOS/9x-based,Windows CE,Windows NT。
  1. MS-DOS/9x-based系列,包括了Windows 1.0-3.11,95,98和Windows ME都给予MS-DOS操作系统。Windows操作系统的早期版本:1.0-2.0和16-bit操作系统。而其后的操作系统,3.0,95,98和ME则是16位和32位操作系统的混合体。它们实际最低支持16位的操作环境,也能运行一些受限制的32位应用程序。之所以受限制,就是它们只支持ANSI版本的win32函数。现代这个系列的操作系统已经绝迹了,并且微软不再维护这些系统。
  2. Windows NT系列始自于上世界90年代的Window NT 3.1,并且包括了以后的Windows NT 4,Windows 2000,Windows XP,Window Vista,以及这些系统的server版本。Windows NT系列是真正的32位操作系统。它们既支持Unicode版本的win32函数,也支持ANSI版本的win32函数。NT系列的操作系统内部的字符串是Unicode型字符,ANSI型的Win32 API函数实际上是Unicode版本的函数的包装。
  3. Windows CE系列则是针对移动和嵌入式设备开发的系统。它们是32位操作系统。Windows CE只支持Unicode版Win32 API。
  2.6.2 PSDK的字符串解决方案:TCHARs
  为了避免为不同的windows操作系统开发不同版本的PSDK,微软制订了一个统一的字符串类型TCHARs。TCHAR以及其他的相应的宏在头文件WinNT.h中有定义。程序员在程序中不需要为使用char还是wchar_t而纠结,只需要使用宏TCHAR就可以了。根据Unicode环境是否存在,编译器会自动进行相应的转换。同样道理,程序员不需要为使用'A'还是'W'型Win32 API函数纠结。
  // Generic code
  //
  LPCTSTR psz = TEXT("Hello World!");
  TCHAR szDir[MAX_PATH] = { 0 };
  GetCurrentDirectory(MAX_PATH, szDir);
  // 如果UNICODE符号没有被定义
  //
  const char* psz = "Hello World!";
  char szDir[MAX_PATH] = { 0 };