Windows核心编程-CreateFile详解

文件内核对象

各种设备及其常见用途:

设备常见用途
文件永久存储任何数据
目录属性和文件压缩的设置
逻辑磁盘驱动器格式化驱动器
物理磁盘驱动器访问分区表
串口通过电话线传输数据
并口将数据传输至打印机
邮件槽一对多数据传输,通常是通过网络传到另一台运行Windows机器上
命名管道一对一数据传输,通常是通过网络传到另一台运行Windows机器上
匿名管道单机上的一对一数据传输(绝对不会跨网络)
套接字报文或数据流的传输,通常是通过网络传到任何支持套接字的机器上
控制台文本窗口的屏幕缓存

用来打开各种设备的函数:

设备用来打开设备的函数
文件CreateFile(pszName 为路径名或UNC路径名)
目录CreateFile(pszName为路径名或UNC路径名)。如果调用 CreateFile 的时候指定 FILE_FLAG_BACKUP_SEMANTICS标志,那么Windows允许我们打开一个目录。打开目录使我们能够改变目录的属性
逻辑磁盘驱动器CreateFile(pszName 为“\.\x:”)。如果指定的字符串是”\.\x:”的形式,那么Windows允许我们打开一个逻辑磁盘驱动器,其中的x是驱动器的盘符。打开驱动器使我们能够格式化驱动器或检测驱动器媒介的大小
物理磁盘驱动器CreateFile(pszName 为 “\\.\PHYSICALDRIVEx”)。如果指定的字符串是“\\.\PHYSICALDRIVEx”的形式,那么Windows允许我们打开一个物理磁盘驱动器,其中的x是物理驱动器号。例如,读写第一个物理磁盘驱动器的扇区——\\.\PHYSICALDRIVE0。打开物理驱动器使我们能直接访问磁盘分区表。打开物理驱动器有潜在的危险,错误地写入设备可能会导致操作系统的文件系统无法访问磁盘的内容
串口CreateFile(pszName 为”COMx”)
并口CreateFile(pszName, 为”LPTx”)
邮件槽服务器CreateMailslot(pszName, 为 “\\.\mailslot\mailslotname”)
邮件槽客户端CreateFile(pszName 为”\\servername\mailslotname”)
命名管道服务器CreateNamedPipe(pszName 为”\\.\pipe\pipename”)
命名管道客户端CreateFile(pszName 为”\\servername\pipe\pipename”)
匿名管道CreatePipe用来打开服务器和客户端
套接字Socket, accept 或 AcceptEx
控制台CreateConsoleScreenBufferGetStdHandle

以上CreateFile函数都是返回对应设备的句柄,通过设备句柄我们可与对应的设备进行通信,也可以使用对应设备的特有API进行设置。

调用SetCommConfig设置串口的波特率:

BOOL SetCommConfig(
    HANDLE  hCommDev,
    LPCOMMCONFIG    pCC,
    DWORD   dwSize);

邮件槽在等待读取数据时,可以调用SetMailslotInfo来设置一个超时值:

BOOL SetMailslotInfo(
    HANDLE hMailslot,
    DWORD dwReadTimeout);

获取文件类型GetFileType的返回值:

描述
FILE_TYPE_UNKNOWN指定的文件为未知类型
FILE_TYPE_DISK指定的文件是一个磁盘文件
FILE_TYPE_CHAR指定的文件是一个字符文件,一般来说是一个并口设备或控制台
FILE_TYPE_PIPE指定的文件是一个命名管道或匿名管道
1.文件内核对象打开及操作
HANDLE CreateFile(
    PCTSTR  pszName,            //文件设备名
    DWORD   dwDesiredAccess,    //读取方式
    DWORD   dwShareMode,        //共享模式
    PSECURITY_ATTRIBUTES    psa,    //安全属性
    DWORD   dwCreationgDisposition, //
    DWORD   dwFlagsAndAttributes,
    HANDLE  hFileTemplate);
dwDesiredAccess
含义
0不希望从设备读取数据或向设备写入数据。如果只想改变设备的配置(如:修改文件的时间戳),可以传0
GENERIC_READ允许对设备进行只读访问
GENERIC_WRITE允许对设备进行只写访问,该选项并没有隐含 GENERIC_READ标志
dwShareMode
含义
0要求独占对设备的访问。如果设备已经打开,CreateFile 调用会失败;如果成功地打开了设备,后续的 CreateFile 调用会失败
FILE_SHARE_READ如果有其他对象要用该设备,我们要求它们不得修改设备的数据;如果设备已经以写入方式或独占方式打开,那么CreateFile调用会失败
FILE_SHARE_WRITE如果有其他内核对象要使用该设备,则要求它们不得读取设备的数据
FILE_SHARE_DELETE当对文件进行操作的时候,我们不关心文件是否被逻辑删除或移动。在Windows内部,系统会先将文件标记为待删除,然后当该文件所有已打开的句柄都被关闭的时候,再将其真正的删除
dwCreationgDisposition
含义
CREATE_NEW告诉CreateFile创建一个新文件,如果同名文件已经存在,那么 CreateFile调用会失败
CREATE_ALWAYS告诉CreateFile无论同名是否存在都创建新文件,若文件存在,则覆盖
OPEN_EXISTING告诉CreateFile打开一个已有的文件或设备,如果文件或设备不存在,那么CreateFile调用会失败
OPEN_ALWAYS告诉CreateFile打开一个已有的文件,如果文件存在,那么CreateFile会直接打开文件,如果不存在,则会创建一个新文件
TRUNCATE_EXISTING告诉CreateFile打开一个已有的文件并将文件大小截断为0字节,如果文件不存在,那么CreateFile调用会失败
dwFlagsAndAttributes
  • 该参数的用途:
    • 允许我们设置一些标志来微调与设备之间的通信;
    • 如果设备是一个文件,我们还能够设置文件的属性.
含义
通信标志–缓存
FILE_FLAG_NO_BUFFERING该标志表示在访问文件的时候不要使用任何数据缓存
FILE_FLAG_SEQUENTIAL_SCAN指定系统顺序地访问文件,系统从文件读取的数据量会超过我们的要求(减少硬盘访问),指定了FILE_FLAG_NO_BUFFERING标志,该标志不生效
FILE_FLAG_RANDOM_ACCESS该标志表示系统不要提前读取文件数据(指定FILE_FLAG_NO_BUFFERING,则该标志不生效)
FILE_FLAG_WRITE_THROUGH禁止写入文件时,将数据缓存在内存中(减少数据丢失的可能性)
通信标志–其他标志
FILE_FLAG_DELETE_ON_CLOSE文件所有的句柄都被关闭后,删除该文件
FILE_FLAG_BACKUP_SEMANTICS用于备份和恢复软件。在打开或创建任何文件之前,为了确保视图打开文件或创建文件的进程具有所需的访问特权
FILE_FLAG_POSIX_SEMANTICS让CreateFile在创建文件或打开文件时,以区分大小写的方式来查找文件名
FILE_FLAG_OPEN_REPARSE_POINT告诉系统忽略文件的重解析属性(重解析属性允许一个文件系统过滤器对打开文件、读取文件、写入文件以及关闭文件这些行为进行修改)
FILE_FLAG_OPEN_NO_RECALL该标志告诉系统不要将文件内容刚从脱机存储器(offline storage,比如磁带)恢复到联机存储器(即online storage, 如硬盘)
FILE_FLAG_OVERLAPPED该标志告诉系统我们想以异步方式来访问设备
文件设置
FILE_ATTRIBUTE_ARCHIVE应用程序用该标志来将文件标记为待备份或待删除。当CreateFile创建一个新文件时,会自动设置该标志
FILE_ATTRIBUTE_ENCRYPTED文件是经过加密的
FILE_ATTRIBUTE_HIDDEN文件是隐藏的。它不会出现在通常的目录清单中
FILE_ATTRIBUTE_NORMAL文件没有其他属性。只有单独使用的时候,这个标志才有效
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED内容索引服务(content indexing service)不会对文件进行索引
FILE_ATTRIBUTE_OFFLINE文件虽然存在,但文件内容已经被转移到脱机存储中
FILE_ATTRIBUTE_READONLY文件只读
FILE_ATTRIBUTE_SYSTEM文件是操作系统的一部分,专供操作系统使用
FILE_ATTRIBUTE_TEMPORARY文件数据只会使用一小段时间。为了将访问时间降至最低,会尽量将文件数据保存在内存中
2.使用文件设备
  1. 获得文件的大小

    BOOL GetFileSizeEx(
        HANDLE  hFile,                  // 文件句柄
        PLARGE_INTEGER  pliFileSize);   //64位标识文件大小
    
    typedef union _LARGE_INTEGER {
        struct {
            DWORD   LowPart;        //Low 32-bit unsigned value
            LONG    HighPart;       //High 32-bit signed value
        };
        LONGLONG    QuadPart;       //Full 64-bit signed value
    } LARGE_INTEGER, *PLARGE_INTEGER;
  2. 设置文件指针

    每调用一次CreateFile函数都会打开一个文件内核对象用于管理文件,每个内核对象内部都维护了一个文件指针,表示应该在哪里执行下一次同步读取或写入操作。

    BOOL SetFilePointerEx(
        HANDLE  hFile,
        LARGE_INTEGER   liDistanceToMove,    //指针移动的距离(多少字节)
        PLARGE_INTEGER  pliNewFilePointer,  //移动后返回的新指针
        DWORD   dwMoveMethod);            //如何解释liDistanceToMove,即从哪个位置开始移动liDistanceToMove的距离(FILE_BEGIN, FILE_CURRENT, FILE_END)
    • 注意事项:
      • 将文件指针的值设为超过文件当前的大小是正当操作,除非在该位置向文件写入数据或调用 SetEndOfFile 才会改变文件大小;
      • 如果SetFilePointerEx操作的文件是用FILE_FLAG_NO_BUFFERING标志打开的,那么文件指针只能被设置为扇区大小的整数倍;
  3. 设置文件尾

    BOOL SetEndOfFile(HANDLE hFile);

    SetEndOfFile函数会根据文件对象的文件指针当前所在的位置来截断文件的大小或增大文件的大小

    HANDLE hFile = CreateFile(...);
    LARGE_INTEGER liDistanceToMove;
    liDistanceToMove.QuadPart = 1024;
    SetFilePointerEx(hFile, liDistanceToMove, NULL, FILE_BEGIN);
    SetEndOfFile(hFile);
    CloseHandle(hFile);
  • 12
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
第一部分 程序员必读 第一章 对程序错误的处理 在我们开始介绍Microsoft Windows应该提供的许多特性之前,我们首先必须了解Windows的各个函数是如何进行错误处理的。 当你调用一个Windows函数时,它首先要检验你传递给它的的各个参数的有效性,然后再设法执行它的任务。如果你传递了一个无效参数,或者由于某种原因它无法执行这项操作,那么该函数就会返回一个值,指明该函数在某种程度上运行失败了。表1-1列出了大多数Windows函数使用的数据类型的返回值。 表1-1 Windows函数常用的返回值类型 数据类型 表示失败的值 VOID 该函数的运行不可能失败。Windows函数的返回值类型很少 是VOID。 BOLL 如果函数运行失败,那么返回值是0,否则返回的是非0值。最 好对返回值进行测试,以确定它是0还是非0。如果它是TRUE ,则不要测试返回值。 HANDLE 如果函数运行失败,则返回值通常是NULL,否则返回值为 HANDLE,,用于标识你可以操作的一个对象。对于这个返回 值,你应该小心处理,因为有些函数会返回一个句柄 值INVALID_HANDLE_VALUE,它被定义为-1。该函数的 Platform SDK资料将会清楚地说明该函数是返回NULL还 是INVALID_HANDLE_VALID,以便指明函数运行已经失败。 PVOID 如果函数运行失败,则返回值是NULL,否则返回PVOID,以 标识数据块的内存地址。 LONG/DWORD 这是个难以处理的值。返回数量的函数通常返回LONG 或DWORD。如果由于某种原因,函数无法对你想要进行计数 的对象进行计数,那么该函数通常返回0或-1(根据该函数而定) 。如果你调用的函数返回了LONG/DWORD,那么请认真阅 读Platform SDK资料,以确保你能正确检查潜在的错误。 当一个Windows函数返回一个错误代码时,它常常可以用来了解函数为什么会运行失败。Microsoft公司编译了一个所有可能的错误代码的列表,并且为每个错误代码分配了一个32位的号码。 从系统内部来讲,当一个Windows函数检测到一个错误时,它会使用一个称为线程本地存储器的机制,将相应的错误代码号码与调用的线程关联起来。(“线程本地存储器”将在第21章中介绍)。这将使线程能够互相独立地运行,而不会影响各自的错误代码。当函数返回给你时,它的返回值就能指明一个错误已经发生。若要确定这是个什么错误,请调用GetLastError函数: 见原书P4的程序(1) 该函数只返回线程的32位错误代码。 当你拥有32位错误代码的号码时,你必须将该号码转换成更有用的某种对象。WinError.h头文件包含了Microsoft公司定义的错误代码的列表。下面我显示了该列表的某些内容,使你能够看到它的大概样子: 见原书P4的程序(2)和P5的程序 你可以看到,每个错误都有3种表示法:即一个消息ID(这是你可以在源代码中使用的一个宏,以便与GetLastError的返回值进行比较),消息文本(对错误的英文描述)和一个号码(你应该避免使用这个号码,而应该使用消息ID)。请记住,我只选择了WinError.h头文件中的很少一部分内容来向你进行展示,整个文件的长度超过21000行。 当Windows函数运行失败时,你应该立即调用GetLastError函数,否则,如果你调用另一个Windows函数,它的值很可能被改写。 说明 GetLastError能返回线程产生的最后一个错误。如果该线程调用的Windows 函数运行成功,那么最后一个错误代码就不被改写,并且不指明运行成功。有少 数Windows函数并不遵循这一规则,并且它会更改最后的错误代码,但是Platform SDK资料通常指明,当函数运行成功时,该函数会更改最后的错误代码。 Windows 98 许多Windows 98的函数实际上是用Microsoft公司的16位Windows 3.1产 品产生的16位代码来实现的。这种比较老的代码并不通过GetLastError之类函 数来报告错误,而且Microsoft公司并没有在Windows 98中修改16位代码,以 支持这种错误处理方式。对于我们来说,这意味着Windows 98中的许多Win32 函数在运行失败时不能设置最后的错误代码。该函数将返回一个值,指明运行失 败,这样你就能够发现该函数确实已经运行失败。但是你无法确定运行失败的原 因。 有些Windows函数之所以能够成功运行,那是若干个原因产生的结果。例如,创建指明的事件内核对象之所以能够取得成功,原因是你实际上创建了该对象,或者是因为已经存在带有相同名字的事件内核对象。你的应用程序必须知道成功的原因。为了将该信息返回给你,Microsoft公司选择使用最后错误代码机制。这样,当某些函数运行成功时,你就能够通过调用GetLadtError函数来确定其他的一些信息。对于具有这种行为特性的函数来说,Platform SDK资料清楚地说明了GetLastError函数可以这样来使用。请参见该资料,以便找出CreateEvent函数的例子。 当你进行调试的时候,我发现监控线程的最后错误代码是非常有用的。在Microsoft Visual studio 6.0中,Microsoft的调试程序支持一个非常有用的特性,即你可以配置Watch窗口,以便始终都能向你显示线程的最后错误代码的号码和该错误的英文描述。通过选定Watch窗口中的一行,并键入“@err,hr",你就能够做到这一点。观察图1-1,你会看到我已经调用了CreateFile函数。该函数返回INVALID_HANDLE_VALUE(-1)的HANDLE,表示它未能打开指定的文件。但是Watch窗口向我们显示最后错误代码(即如果我调用GetLastErro函数,该函数返回的错误代码)是0x00000002。该Watch窗口又进一步指明错误代码2是指“系统不能找到指定的文件。”你会发现它与WinError.h头文件中的错误代码2所指的字符串是相同的。 图1-1 在Visual Studio 6.0的Watch窗口中键入 “@err,hr",你就可以查看当前线程的最后错误代码。 Visual studio还配有一个小的实用程序,称为Error Lookup。你可以使用Error Lookup将错误代码的号码转换成它的文本描述。 见P7的Error Lookup插图 如果我在我编写的应用程序中发现一个错误,我可能想要向用户显示该错误的文本描述。Windows提供了一个函数,可以将错误代码转换成它的文本描述。该函数称为FormatMessage。请看下面的代码: 见原书P8的程序(1) FormatMessage函数的功能实际上是非常丰富的,在创建向用户显示的字符串信息时,它是人们首选的函数。该函数之所以有这样大的作用,原因之一是它很容易用多种语言来进行操作。该函数能够检测出用户首选的语言(在Regional Settings Control Panel小应用程序中设定),并返回相应的文本。当然,你首先必须自己转换字符串,然后将已转换的消息表资源嵌入你的.exe文件或DLL模块,不过,这时该函数会选定正确的嵌入对象。ErrorShow示例应用程序(本章后面将加以介绍)展示了如何调用该函数,以便将Microsoft公司定义的错误代码转换成它的文本描述。 有些人常常问我,Microsoft公司是否建立了一个主控列表,以显示每个Windows函数可能返回的所有错误代码。可惜,答案是没有这样的列表,而且Microsoft公司将永远不会建立这样的一个列表。因为在创建系统的新版本时,建立和维护该列表实在太困难了。 建立这样一个列表时存在的问题是,你可以调用一个Windows函数,但是该函数能够在内部调用另一个函数,而这另一个函数又可以调用另一个函数,如此类推。由于各种不同的原因,这些函数中的任何一个函数都可能运行失败。有时,当一个函数运行失败时,较高级的函数对它进行恢复,并且仍然可以执行你想执行的操作。为了创建该主控列表,Microsoft公司必须跟踪每个函数的运行路径,并建立所有可能的错误代码的列表。这项工作很困难。当创建系统的新版本时,这些函数的运行路径就会改变。 1.1 你也能够定义自己的错误代码 好了,我已经说明Windows函数是如何向函数的调用者指明发生的错误。Microsoft公司也使你能够将该机制用于你自己的函数。比如说,你编写了一个你希望其他人调用的函数。你的函数可能因为这样或那样的原因而运行失败,你必须向函数的调用者说明它已经运行失败。 若要指明函数运行失败,你只需要设定线程的最后的错误代码,然后让你的函数返回FALSE,INVALID_HANDLE_VALUE,NULL,或者返回任何合适的信息。若要设定线程的最后错误代码,你只需要调用下面的代码: 见原书P8的程序(2) 请将你认为合适的任何32位号码传递给该函数。我设法使用WinError.h中已经存在的代码,只要该代码能够正确地指明我想要报告的错误即可。如果你认为WinError.h中的任何代码都不能正确地反映该错误的性质,那么你可以创建你自己的代码。错误代码是个32位的数字,它可以划分成下表所示的各个域。 位 31-30 29 28 27-16 15-0 内容 严重性 Microsoft/ 保留 设备代码 异常代码 客户 含义 0=成功 0=Microsoft 必须是0 由Microsoft 由Microsoft/ 1=供参考 公司定义的 公司定义 客户定义 2=警告 代码 3=错误 1=客户定义 的代码 这些域将在第24章中详细讲述。现在,你需要知道的重要域是第29位的信息。Microsoft公司规定,他们建立的所有错误代码的这个信息位均使用0。如果你创建自己的错误代码,你必须在这个信息位中输入1。这样,就可以确保你的错误代码与Microsoft公司目前或者将来定义的错误代码不会发生冲突, 1.2 ErrorShow示例应用程序 ErrorShow应用程序“01 ErrorShow.exe"(在图1-2中列出)展示了如何获取错误代码的文本描述的方法。该应用程序的源代码和资源文件位于本书所附光盘上的01-ErrorShow目录下。一般来说,该应用程序用于显示调试程序的Watch窗口和Error Lookup程序是如何运行的。当你启动该程序时,就会出现下面这个窗口。 见原书P9的插图 你可以将任何错误代码键入该编辑控件。当你单击Look Up按钮时,在底部的滚动窗口中就会显示该错误的文本描述。该应用程序唯一令人感兴趣的特性是如何调用FormatMessage函数。下面是我使用该函数的方法: 见原书P10的程序(1) 第一个代码行用于从编辑控件中检索错误代码的号码。然后,建立一个内存块的句柄并将它初始化为NULL。FormatMessage函数在内部对内存块进行分配,并将它的句柄返回给我们。 当调用FormatMessage函数时,我传递了FORMAT_MESSAGE_FROM_SYSTEM标志。该标志告诉FormatMessage函数,我们想要系统定义的错误代码的字符串。我还传递了FORMAT_MESSAGE_ALLOCATE_BUFFER标志,告诉该函数为错误代码的文本描述分配足够大的内存块。该内存块的句柄将在hlocal变量中返回。第三个参数指明我们想要查找的错误代码的号码,第四个参数指明我们想要文本描述使用什么语言。 如果FormatMessage函数运行成功,那么错误代码的文本描述就位于内存块中,我将它拷贝到对话框底部的滚动窗口中。如果FormatMesage函数运行失败,我设法查看NetMsg.dll模块中的消息代码,以了解该错误是否与网络有关。使用NetMsg.dll模块的句柄,我再次调用FormatMessage函数。你会看到,每个DLL(或.exe)都有它自己的一组错误代码,你可以使用Message Compiler(MC.exe)将这组错误代码添加给该模块,并将一个资源添加给该模块。这就是Visual Studio的Error Lookup工具允许你用Modules对话框进行的操作。 图1-2 ErrorShow示例应用程序 见原书P11—16 第2章 UNICODE 随着Microsoft公司的Windows操作系统在全世界日益广泛的流行,对于我们这些软件开发人员来说,将我们的目标瞄准国际上的各个不同市场,已经成为一个越来越重要的问题。美国的软件版本比国际版本提前6个月推向市场,这曾经是个司空见惯的现象。但是,由于各国对Windows操作系统提供了越来越多的支持,因此就更加容易为国际市场生产各种应用软件,从而缩短了软件的美国版本与国际版本推出的时间间隔。 Windows操作系统始终不逾地提供各种支持,以帮助软件开发人员进行应用程序的本地化工作。应用软件可以从各种不同的函数中获得特定国家的信息,并可观察控制面板的设置,以确定用户的首选项。Windows甚至支持不同的字体,以适应我们的应用的需要。 我之所以将这一章放在本书的开头,是因为我考虑到Unicode是开发任何应用程序时要采用的基本步骤。关于Unicode的问题,我在本书的每一章中几乎都要讲到,而且本书中给出的所有示例应用程序都是“用Unicode实现的”。如果你为Microsoft Windows 2000或Microsoft Windows CE开发应用程序,你应该使用Unicode进行开发。如果你为Microsoft Windows 98开发应用程序,你必须对某些问题作出决定。本章也要讲述Windows 98的有关问题。 2.1 字符集 软件的本地化要解决的真正问题,实际上就是如何来处理不同的字符集。多年来,我们许多人一直将文本串作为一系列单字节字符来进行编码,并在结尾处放上一个零。对于我们来说,这已经成了习惯。当我们调用strlen函数时,它在以0结尾的单字节字符数组中返回字符的数目。 问题是,有些文字和书写规则(比如日文中的汉字就是个典型的例子)的字符集中的符号太多了,因此单字节(它提供的符号最多不能超过256个)是根本不敷使用的。为此我们创建了双字节字符集(DBCS),以支持这些文字和书写规则。 2.1.1 单字节与双字节字符集 在双字节字符集中,字符串中的每个字符可以包含一个字节,也可以包含两个字节。例如,日文中的汉字,如果第一个字符在0x81与0x9F之间,或者在0xE0与0xFC之间,那么你就必须观察下一个字节,才能确定字符串中的这个完整的字符。如果要使用双字节字符集,对于程序员来说简直是个很大的难题,因为有些字符只有一个字节宽,而有些字符则是两个字节宽。 如果只是调用strlen函数,那么你无法真正了解字符串中究竟有多少字符,它只能告诉你到达结尾的0之前有多少个字节。ANSI的C运行期库中没有配备相应的函数,使你能够对双字节字符集进行操作。但是,Microsoft Visual C++的运行期库却包含许多函数,如_mbslen,它可以用来操作多字节(既包括单字节也包括双字节)字符串。 为了帮助你对DBCS字符串进行操作,Windows提供了下面的一组帮助函数。 函数 描述 PTSTR CharNext 返回字符串中的下一个字符的地址 (PCTSTR pszCurrentChar); PTSTR CharPrev 返回字符串中的上一个字符的地址 (PCTSTR pszStart, PCTSTR pszCurrentChar); BOOL IsDBCSLendByte 如果该字节是DBCS字符的第一个字节,则返 (BYTE bTestChar); 回TRUE 2.1.2 Unicode:宽字节字符集 Unicode是Apple和Xerox公司于1988年建立的一个技术标准。1991年,成立了一个集团机构负责Unicode的开发和推广应用。该集团由Apple、Compaq、HP、IBM、Microsoft、Oracle、Silicon Graphics、Sybase、Unisys和Xerox等公司组成。(若要了解该集团的全部成员,请通过网址www.Unicode.org查找。)该集团公司负责维护Unicode标准。Unicode的完整描述可以参阅AddisonWesley出版的《Unicode Standard》一书。(该书可以通过网址www.Unicode.org订购。) Unicode提供了一种简单而又一致的表示字符串的方法。Unicode字符串中的所有字符都是16位的字符(两个字节)。它没有专门的字节来指明下一个字节是属于同一个字符的组成部分,还是一个新字符。这意味着你只需要对指针进行递增或递减,就可以遍历字符串中的各个字符。你不再需要调用CharNext,CharPrev和IsDBCSLeadByte之类的函数。 由于Unicode用一个16位的值来表示每个字符,因此总共可以得到65000个字符,这样,它就能够对世界各国的书面文字中的所有字符进行编码。这远远超过了单字节字符集的256个字符的数目。 目前,已经为阿拉伯文、中文拼音、西里尔字母(俄文)、希腊文、西伯莱文、日文、韩文和拉丁文(英文)字母定义了Unicode代码点1。这些字符集中还包含了大量的标点符号、数学符号、技术符号、箭头、装饰标志、区分标志和其他许多字符。如果你将所有这些字母和符号加在一起,总计约达35000个不同的代码点,这样,总计的65000个代码点中,大约还有一半可供将来扩充时使用。 这65536个字符可以分成不同的区域。下面这个表显示了一部分这样的区域以及分配给这些区域的字符。 16位代码 字符 16位代码 字符 0000-007F ASCII 0300-036F 通用区分标志 0080-00FF 拉丁文1字符 0400-04FF 西里尔字母 0100-017F 欧洲拉丁文 0530-058F 亚美尼亚文 0180-01FF 扩充拉丁文 0590-05FF 西伯莱文 0250-02AF 标准拼音 0600-06FF 阿拉伯文 02B0-02FF 修改型字母 0900-097F 梵文 注1. 代码点是指字符集中的一个符号的位置 目前尚未分配的代码点大约还有29000个,不过它们是保留供将来使用的。另外,大约有6000个代码点是保留供你个人使用的。 2. 2 为何应该使用Unicode 当你开发应用程序时,你当然应该考虑利用Unicode的优点。即使现在你不打算对你的应用程序进行本地化,开发时将Unicode放在心上,肯定可以简化将来的代码转换工作。此外,Unicode还具备下列功能: * 可以很容易地在不同语言之间进行数据交换 * 使你能够分配支持所有语言的单个二进制.exe文件或DLL文件 * 提高你的应用程序的运行效率(本章后面还要详细介绍) 2.3 Windows 2000与Unicode Windows 2000是使用Unicode从头进行开发的,用于创建窗口、显示文本、进行字符串操作等的所有核心函数都需要Unicode字符串。如果你调用任何一个Windows函数并给它传递一个ANSI字符串,那么系统首先要将字符串转换成Unicode,然后将Unicode字符串传递给操作系统。如果你希望函数返回ANSI字符串,系统就会首先将Unicode字符串转换成ANSI字符串,然后将结果返回给你的应用程序。所有这些转换操作都是在你看不见的情况下发生的。当然,进行这些字符串的转换需要占用系统的时间和内存开销。 例如,如果你调用CreateWindowEx函数,并传递类名字和窗口标题文本的非Unicode字符串,那么CreateWindowEx必须分配内存块(在你的进程的默认堆中),将非Unicode字符串转换成Unicode字符串,并将结果存储在分配到的内存块中,然后调用Unicode版本的CreateWindowEx函数。 对于用字符串填入缓存的函数来说,系统必须首先将Unicode字符串转换成非Unicode字符串,然后你的应用程序才能处理该字符串。由于系统必须执行所有这些转换操作,因此你的应用程序需要更多的内存,并且运行的速度比较慢。通过从头开始用Unicode来开发应用程序,你就能够使你的应用程序更加有效地运行。 2. 4 Windows 98与Unicode Windows 98不是一种全新的操作系统。它继承了16位Windows操作系统的特性,它不是用来处理Unicode。如果要增加对Unicode的支持,其工作量非常大,因此在该产品的特性列表中没有包括这个支持项目。由于这个原因,Windows 98象它的前任产品一样,几乎都是使用ANSI字符串来进行所有的内部操作的。 你仍然可以编写用于处理Unicode字符和字符串的Windows应用程序,不过,使用Windows函数要难得多。例如,如果你想要调用CreateWindowEx函数并将ANSI字符串传递给它,这个调用的速度非常快,不需要从你进程的默认堆栈中分配缓存,也不需要进行字符串转换。但是,如果你想要调用CreateWindowEx函数并将Unicode字符串传递给它,你就必须明确分配缓存,并调用函数,以便执行从Unicode到ANSI字符串的转换操作。然后你可以调用CreateWindowEx,传递ANSI字符串。当CreateWindowEx函数返回时,你就能释放临时缓存。这比使用Windows 2000上的Unicode要麻烦得多。在本章的后面部分中,我要介绍如何在Windows 98下进行这些转换。 虽然Unicode函数的大多数代码在Windows 98中不起任何作用,但是有少数Unicode函数确实拥有非常有用的实现代码。这些函数是: 见原书的P21 可惜的是,这些函数中有许多函数在Windows 98中会出现各种各样的错误。有些函数无法使用某些字体,有些函数会破坏内存堆栈,有些函数会使打印机驱动程序崩溃,如此等等。如果你要使用这些函数,你必须对它们进行大量的测试。即使这样,你可能仍然无法解决问题。因此你必须向用户说明这些情况。 2. 5 Windows CE与Unicode Windows CE操作系统是为小型设备开发的,这些设备的内存很小,并且不带磁盘存储器。你可能认为,由于Microsoft公司的主要目标是建立一种尽可能小的操作系统,因此它会使用ANSI作为自己的字符集。但是Microsoft公司并不是鼠目寸光。他们懂得,采用Windows CE的设备要在世界各地销售,他们希望降低软件开发成本,这样就能更加容易地开发应用程序。为此,Windows CE本身就是使用Unicode的一种操作系统。 但是,为了使Windows CE尽量做得小一些,Microsoft公司决定完全不支持ANSI Windows函数。因此,如果你要为Windows CE开发应用程序,你必须懂得Unicode,并且在整个应用程序中使用Unicode。 2. 6 需要注意的问题 下面让我们进一步明确一下“Microsoft公司对Unicode支持的情况”: * Windows 2000既支持Unicode,也支持ANSI,因此你可以为它们当中的任何一种开发应用程序 * Windows 98 只支持ANSI,你只能为ANSI开发应用程序 * Windows CE只支持Unicode,你只能为Unicode开发应用程序 虽然Microsoft公司试图让软件开发人员能够非常容易地开发在这3种平台上运行是软件,但是Unicode与ANSI之间的差异使得事情变得困难起来,并且这种差异通常是我遇到的最大的问题之一。请不要误解,Microsoft公司坚定地支持Unicode,并且我也坚决鼓励你使用它。不过你应该懂得,你可能遇到一些问题,需要一定的时间来解决这些问题。我建议你尽可能使用Unicode。如果你运行Windows 98,那么只有在必要时才要转换到ANSI。 不过,还有另一个小问题你应该了解,那就是COM。 2.7 对COM的简单说明 当Microsoft公司将COM从16位Windows转换成Win32时,公司作出了一个决定,即,需要字符串的所有COM接口方法都只能接受Unicode字符串。这是个了不起的决定,因为COM通常用于使不同的组件能够互相之间进行通信,而Unicode则是传递字符串的最佳手段。 如果你为Windows 2000或Windows CE开发应用程序,并且也使用COM,那么你将会如虎添翼。在你的整个源代码中使用Unicode,将使与操作系统进行通信和与COM对象进行通信的操作变成一件轻而易举的事情。 如果你为Windows 98开发应用程序,并且也使用COM,那么你将会遇到一些问题。COM要求你使用Unicode字符串。操作系统的大多数函数要求你使用ANSI字符串。那是多么难办的事情啊!我曾经从事过若干个项目的开发,在这些项目中,我编写了许多代码,仅仅是为了来回进行字符串的转换。 2. 8 如何编写Unicode源代码 Microsoft公司为Unicode设计了Windows API,这样,它可以尽量减少对你的代码的影响。实际上,你可以编写单个源代码文件,以便使用或者不使用Unicode来对它进行编译。你只需要定义两个宏(UNICODE和_UNICODE),就可以修改然后重新编译该源文件。 2. 8.1 C运行期库对Unicode的支持 为了利用Unicode字符串,因此定义了一些数据类型。标准的C头文件String.h已经作了修改,以便定义一个名字为wchar_t的数据类型,它是一个Unicode字符的数据类型: 见原书P23的程序(1) 例如,如果你想要创建一个缓存,用于存放最多为99个字符的Unicode字符串和一个结尾为零的字符,你可以使用下面这个语句: 见原书P23的程序(2) 该语句创建了一个由100个16位值组成的数组。当然,标准的C运行期字符串函数,如strcpy、strchr和strcat等,只能对ANSI字符串进行操作,它们不能正确地处理Unicode字符串。因此,ANSI C也拥有一组补充函数。图2-1显示了一些标准的ANSI C字符串函数,后面是它们的等价Unicode函数。 图2-1 标准的ANSI C字符串函数和它们的等价Unicode函数 见原书P23的程序(3)和P24的程序 请注意,所有的Unicode函数均以wcs开头,wcs是宽字符串的英文缩写。若要调用Unicode函数,只需用前缀wcs来取代任何ANSI字符串函数的前缀str即可。 说明 大多数软件开发人员可能已经不记得这样一个非常重要的问题了,那就 是Microsoft公司提供的C运行期库与ANSI的标准C运行期库是一致的。 ANSI C规定,C运行期库支持Unicode字符和字符串。这意味着你始终都可 以调用C运行期函数,以便对Unicode字符和字符串进行操作,即使你是在 Windows 98上运行,也可以调用这些函数。换句话说,wcscat,wcslen和wcstok 等函数都能够在Windows 98上很好地运行,这些都是你必须关心的操作系统函数。 对于包含了对str函数或wcs函数进行显式调用的代码来说,你无法非常容易地同时为ANSI和Unicode对这些代码进行编译。在本章前面部分的内容中,我说过可以创建同时为ANSI和Unicode进行编译的单个源代码文件。若要建立这种双重功能,你必须包含Tchar.h文件,而不是包含String.h文件。 Tchar.h文件的唯一作用是帮助你创建ANSI/Unicode通用源代码文件。它包含你应该用在源代码中的一组宏,而不应该直接调用str函数或者wcs函数。如果你在编译源代码文件时定义了_UNICODE,这些宏就会引用wcs这组函数。如果你没有定义_UNICODE,那么这些宏将引用str这组宏。 例如,在Tchar.h中有一个宏称为_tcscpy。如果在你包含该头文件时没有定义_UNICODE,那么_tcscpy就会扩展为ANSI的strcpy函数。但是如果定义了_UNICODE,_tcscpy将扩展为Unicode的wcscpy函数。拥有字符串参数的所有C运行期函数都在Tchar.h文件中定义了一个通用宏。如果你使用通用宏,而不是ANSI/Unicode的特定函数名,你就能够顺利地创建可以为ANSI或Unicode进行编译的源代码。 但是,除了使用这些宏之外,还有一些操作你是必须进行的。Tchar.h文件包含了一些其他的宏。 若要定义一个ANSI/Unicode通用的字符串数组,请使用下面的TCHAR数据类型。如果定义了_UNICODE,TCHAR将声明为下面的形式: 见原书P25的程序(1) 如果没有定义_UNICODE,则TCHAR将声明为下面的形式: 见原书P25的程序(2) 使用该数据类型,你可以象下面这样分配一个字符串: 见原书P25的程序(3) 你也可以创建对字符串的指针: 见原书P25的程序(4) 不过上面这行代码存在一个问题。按照默认设置,Microsoft公司的C++编译器能够编译所有的字符串,就象它们是ANSI字符串,而不是Unicode字符串。因此,如果没有定义_UNICODE,该编译器将能正确地编译这一行代码。但是,如果定义了_UNICODE,就会产生一个错误。若要生成一个Unicode字符串而不是ANSI字符串,你必须将该代码行改写为下面的样子: 见原书P25的程序(5) 原义字符串前面的大写字母L,用于告诉编译器该字符串应该作为Unicode字符串来编译。当编译器将字符串置于程序的数据部分中时,它在每个字符之间分散插入零字节。这种变更带来的问题是,现在只有当定义了_UNICODE时,程序才能成功地进行编译。我们需要另一个宏,以便有选择地在原义字符串的前面加上大写字母L。这项工作由_TEXT宏来完成,_TEXT宏也在Tchar.h文件中做了定义。如果定义了_UNICODE,那么_TEXT定义为下面的形式: 见原书P25的程序(6)
### 回答1: Windows API 是一组用于开发 Windows 应用程序的函数、接口和编程实例。精通 Windows API 对于 Windows 应用程序开发者来说非常重要。 首先,函数是 Windows API 提供的可用于实现特定功能的代码块。Windows API 中有各种各样的函数,包括用于创建窗口、绘制图形、处理输入等功能的函数。精通这些函数意味着你能够熟练地使用它们,了解它们的参数和返回值,并在实际应用中有效地调用它们。 接口是 Windows API 提供的一种交互方式,用于与操作系统、硬件设备和其他应用程序进行通信。通过使用接口,开发者可以利用 Windows API 提供的功能和资源。精通 Windows API 接口意味着你能够理解接口的工作原理,编写代码与其他应用程序进行交互,并通过接口实现自己的扩展和功能。 编程实例是使用 Windows API 进行开发的实际案例。这些案例可以帮助开发者理解如何使用 Windows API 的函数和接口来完成特定的任务。在这些实例中,开发者可以学习如何创建窗口、处理消息、显示图形等常见的 Windows 应用程序开发任务。通过研究这些实例,你可以学会如何有效地使用 Windows API 来开发自己的应用程序。 总而言之,精通 Windows API-函数、接口和编程实例,意味着你能够熟练地使用 Windows API 提供的功能,理解接口的工作原理,并能够仿照编程实例编写自己的 Windows 应用程序。这对于任何希望在 Windows 平台上进行应用程序开发的开发者来说都是非常重要的技能。 ### 回答2: 精通 Windows API-函数、接口、编程实例的 PDF 可以帮助开发人员更好地理解和应用 Windows API。下面是一些关于此主题的要点: 1. Windows API是一组功能强大的函数和接口,可让开发人员与 Windows 操作系统进行交互。通过使用 Windows API,开发人员可以访问和控制 Windows 操作系统的各种功能,例如文件操作、网络通信、图形界面等。 2. 函数和接口是 Windows API 的核心组成部分。函数是预先编写好的代码块,可以实现特定的功能。接口是一组以特定方式交互的函数的集合,提供了一种与外部程序或设备进行通信的方式。 3. 精通 Windows API 需要熟悉常用的函数和接口。一些常见的函数包括 CreateFile、ReadFile、WriteFile(用于文件操作)、MessageBox(用于消息框显示)等。常见的接口包括 Winsock(用于网络编程)、GDI(用于图形界面编程)等。 4. 编程实例是通过实际的代码示例来演示如何使用 Windows API 进行编程编程实例可以帮助开发人员更好地理解函数和接口的使用方法,并学会如何将其应用到实际项目中。 5. 学习和理解 Windows API 需要进行实践。除了阅读相关的文档和书籍外,还可以通过编写小型的示例程序来练习和熟悉 Windows API 的使用。 总之,精通 Windows API-函数、接口、编程实例的 PDF 是一个有助于开发人员学习和应用 Windows API 的资源。通过深入学习函数和接口的使用方法,并通过编程实例进行实践,开发人员可以更好地掌握 Windows API 的知识和技能,从而在 Windows 平台上开发出高质量的应用程序。 ### 回答3: 精通Windows API函数、接口和编程实例对于开发Windows应用程序以及进行系统级编程非常重要。Windows API提供了各种函数和接口,使开发者能够直接与Windows操作系统进行交互,并实现更高级的功能。 首先,了解Windows API函数是至关重要的。这些函数可以用于控制窗口、处理消息、操作文件、管理内存等各种任务。例如,CreateWindow函数可以创建一个窗口,SendMessage函数可以发送消息给其他窗口,ReadFile函数可以读取文件内容等。掌握这些函数,可以更好地控制和管理应用程序的行为。 其次,熟悉Windows API接口也是必要的。接口是指一组函数和结构体的集合,用于实现特定功能。例如,Win32 API提供了许多接口,如GDI(图形设备接口)、COM(组件对象模型)等。通过调用这些接口,可以实现图形绘制、多媒体处理、网络通信等功能。 最后,编程实例对于学习和应用Windows API也至关重要。通过实践编写一些常见的应用程序,如文本编辑器、游戏、图形界面等,可以更好地理解API的使用方法和原理。编程实例可以帮助开发者熟悉常见的函数调用序列、参数传递方式、异常处理等技巧,提高编码效率和代码质量。 总之,精通Windows API函数、接口和编程实例对于开发Windows应用程序非常重要。通过学习和实践,可以更好地掌握API的使用方法和原理,提高开发效率和程序质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值