不同架构的字节顺序释疑

来源:互联网 发布:java开源文件管理系统 编辑:程序博客网 时间:2024/06/05 01:18

交换字节

在计算领域存在两种不同的字节顺序处理方法(或者说是endian格式)。endian格式指定了如何在内存中存储多字节数值中的各个字节;Big-endian的字节顺序处理方式表示存储多字节数据的时候权重最大的字节放在前面。Little-endian的字节顺序处理方式则表示存储多字节数据的时候权重最小的字节放在前面。PowerPC处理器使用big-endian的字节顺序处理方式,x86 处理器家族则使用little-endian的字节顺序处理方式。根据约定,多字节的数据在网络上发送的时候,使用big-endian的字节处理方式。

如果您的应用程序假定数据是某种endian格式,而实际上数据使用的是另一种格式,则程序就会不正确地解析数据。您需要对代码中负责从磁盘或者网络读取多字节的数据(16位,32位,或者64位),或者将多字节数据写入到磁盘或网络的例程进行分析,因为这些例程对于字节顺序格式相当敏感。有两个常见的处理字节顺序差别的方法:在必要的时候进行字节交换,或者使用XML或其它与字节顺序无关的数据格式,比如Core Foundation框架中的格式(CFPreferences,CFPropertyList,CFXMLParser)。

采用字节交换的方法,还是使用与字节顺序无关的数据格式存储数据,取决于您在应用程序中如何使用数据。如果您需要支持某个现有的文件格式,则二进制兼容的解决方案是首先接受应用程序中已经在使用的big-endian文件格式,然后书写字节交换代码,在x86系统上读写文件时使用。如果不需要支持老的文件,则可以考虑重新设计文件格式,以使用XML(extended markuplanguage,即扩展的标志语言),XDR(external data representation,即外部数据表示),或者NSCoding(Objective C)来表示数据。

本章接下来的部分将描述为什么字节顺序会带来问题,给出交换字节的指导原则,描述Mac OS X中提供的字节交换API,并且对大多数与字节顺序有关的情况提供解决方案。

本部分包括如下主要内容:

为什么字节顺序会带来问题
交换字节的指导原则
字节交换例程
字节交换策略
为字节交换数据书写和安装一个回调函数
相关信息

 

为什么字节顺序会带来问题

这个部分的例子的设计目的是更为详尽地向您展示字节顺序为什么会带来问题。请看一下列表3-1中定义的C语言的数据结构。它包含一个四个字节的整形数,一个字符串,以及一个双字节的整形数。列表中的代码还初始化了这个结构。

 

列表3-1 :  包含多字节和单字节的数据结构

 

typedef struct {

 

    uint32_t myOptions;

 

    char     myStringArray [7];

 

    short    myVariable;

 

} myDataStructure;

 

 

 

myDataStructure aStruct;

 

 

 

aStruct.myOptions = 0xfeedface;

 

strcpy(aStruct.myStringArray, "safari");

 

aStruct.myVariable = 0x1234;

 

 

请对比一下(参见图 3-1)在big-endian和little-endian系统上是如何将这个结构存储在内存中的。在big-endian系统中,每个数据字节在内存中的地址是随着各个字节的权重从大到小递增;而在little-endian系统中,每个数据字节在内存中的地址是随着各个字节的权重从小到大递增。

 

图 3-1 :  Big-endian系统和little-endian系统的的字节组织顺序对比

 

在您查看图3-1时,请注意下面几点:

§ 多字节数据,比如图中显示的32位和6位长的变量,在big-endian和little-endian系统中存储方式是不一样的。在图中您可以看到,big-endian 系统在存储数据时将权重最大的字节存放在最低的内存地址上,Little-endian系统在存储数据时则将权重最大的字节存放在最高的内存地址上。因此,在big-endian系统上,myOptions变量(0xce)权重最小的字节被存放在地址为0x00000003的内存单元上,而在little-endian 系统上则存放在地址为0x00000000的内存单元上。

§ 单字节数据,比如myStringArray字符数组中的char值,在两种系统中都存在同一个内存位置上,无论其字节顺序格式是什么。

§ 每个系统都进行字节填充,以保持4字节数据是对齐的。在图中,填充字节用包含星号(*)的带阴影的方框来表示。

如果您希望在使用某个架构的系统上读取数据,而该数据是在使用不同架构的系统中写入的,并且您需要按字节访问数据,则多字节数据在内存中的字节顺序就会带来影响。举例来说,如果您的应用程序需要访问myOptions变量的第二个字节,则当您从采用反向字节顺序的系统中读取数据的时候,结果就会得到myOptions变量的第一个字节,而不是第二个字节。

假定由列表 3-1的代码初始化过的实例数据是在一个little-endian系统中生成,并存储到磁盘上,而且该数据是按照字节—地址的顺序写入磁盘的,数据在内存中的排列状态就如图 3-1所示。问题在于,即使在一个big-endian系统中解析这些数据进行解析,其字节顺序仍然是little-endian格式的。这个差别会导致求值不正确。在这个例子中,myOptions变量的值应该是0xfeedface,但是因为不正确的字节排列顺序,这个变量的求值结果为0xcefaedfe。

请注意:big-endian和little-endian这两个术语来自Jonathan Swift在十八世纪的嘲讽作品Gulliver’s Travels。 Blefuscu帝国的国民被根据吃鸡蛋的方式划分为两个部分:一部分在吃鸡蛋的时候从鸡蛋的大端(big end)开始,而另一部分则从鸡蛋的小端(little end)开始。

 

 

交换字节的指导原则

下面的指导原则和本章梢后提供的策略可以帮助您确定程序中的字节交换代码是否合适。

§ 在内存中,以本地的字节顺序保存数据。只在从磁盘读取数据或者将数据写入磁盘的时候进行字节交换。

§ 在可能的情况下,让编译器为您实现这些工作。举例来说,当您使用诸如Core Foundation中的CFSwapInt16BigToHost这样的函数时,编译器会确定该函数是否为您的目标处理器做过处理。如果该代码什么都没有做,则不会被调用。让编译器实现这个工作,比您自行使用#ifdef语句进行控制要更加有效。

§ 如果您必须访问一个大的文件,则请考虑将数据以某种方式进行排列,以减少您必须进行的字节交换操作。举例来说,您可以将最经常被访问的数据连续地排列在文件中。这样,您只需要读取一块数据,并对之进行字节交换,而不用操作整个数据文件。

§ 只有在必须的时候,才使用__BIG_ENDIAN__和__LITTLE_ENDIAN__宏。不要使用检查特定处理器类型的宏,比如__i386__和__ppc__。

§ 选择一个持续的字节顺序方法,并坚持使用这个方法。也就是说,如果您按照一定的规则从磁盘读取数据或者向磁盘写入数据,请选择您希望使用的endian格式。这样可以减少检查数据字节顺序的必要,从而减少可能的字节顺序交换。

§ 记住哪些函数返回big-endian的数据,并正确处理该数据。这类函数包括DNSServiceDiscovery函数(端口是网络字节顺序),以及ColorSync描述函数(所有数据都是big-endian)。 IconFamilyElement 和IconFamilyResource 数据类型(也包括IconFamilyPtr和IconFamilyHandle类型)总是big-endian格式的。可能有一些其它的函数和数据类型没有列举在这里,请从相应的API参考资料中查找函数的返回数据信息。

§ 请记住,字节交换会带来性能上的开销,因此只在有必要的时候交换字节。

字节交换例程

下面列举了提供字节交换例程的API。绝大多数情况下,最好使用与您的编程框架相匹配的例程。在下面列举的API中,Core Foundation和Foundation API提供了浮点数值字节交换例程,其它API则没有。

§ POSIX (Portable Operating System Interface,即可移植的操作系统接口)中的字节顺序处理函数(ntohl,htonl,ntohs,和htons)在man页面中有所描述,可以在Terminal或者Xcode中查看。

§ Darwin字节顺序处理函数和宏在<libkern/OSByteOrder.h>头文件中定义。虽然这个头文件是在libkern中定义的,但是在高级别的应用程序中使用也是可以接受的。

§ Core Foundation的字节顺序处理函数在<CoreFoundation/CFByteOrder.h>头文件中定义,其描述信息位于字节顺序工具参考部分。如果需要如何使用这些函数的详细信息,请参见内存管理一文中的字节交换部分。

§ Foundation的字节顺序处理函数在<Foundation/NSByteOrder.h>头文件中定义,其描述信息位于Objective C的Foundation参考部分。

§ Core Endian API在<CarbonCore/Endian.h>头文件中定义。这个头文件中的字节交换函数在QuickTime参考文档中进行描述。这个API中的函数名称都以Endian作为前缀。您可以通过QuickTime参考中的函数的字母索引 部分找到这些函数的描述信息。

请注意:在您使用字节交换例程的时候,编译器会对您的代码进行优化,以使这些例程只在代码运行所在的架构需要的时候才被执行。

 

 

字节交换策略

交换字节的策略取决于数据的格式,不存在可以处理所有字节顺序差别的通用例程。单字节的字符串完全不需要交换,32位的数量型需要对四个字节进行颠倒交换,16位的数量型则需要在两个字节之间进行颠倒交换。任何需要交换数据的程序都必须知道相应数据的类型,源数据的endian顺序,以及当前宿主系统的endian顺序。

本部分针对下面这些数据类型列出了各种字节交换的策略,内容上按照字母顺序进行组织:

§  “常数”

§  “定制的苹果事件数据”

§  “定制的资源数据”

§  “浮点数”

§  “整型数”

§  “网络相关的数据”

§  “OSType-to-String的转换”

§  “Unicode文本文件”

常数

常数作为编译完成的执行文件的一部分,其字节顺序是宿主系统的字节顺序。只有当常数成了不在本地维护的数据的一部分,或者需要在不同的宿主系统之间进行转移,才需要进行字节交换。在大多数的情况下,您可以在前期进行字节交换,或者通过移位或其它简单的操作符,让预处理器进行必要的数学运算。

如果您定义和使用的结构必须以某种特定的endian格式存在于内存中,则可以使用libkern/OSByteOrder.h头文件定义的OSSwapConst宏和OSSwap*Const变量来进行处理。这些宏可以在高级别的应用程序中使用。

定制的苹果事件数据

苹果事件(Apple Event)是一种遵循苹果事件进程间消息传递协议(Apple Event Interprocess Messaging Protocol)的高级事件。苹果事件管理器(Apple Event Manager)负责在同一个电脑的应用程序,或者在远程电脑上的应用程序之间发送苹果事件。您也可以定义自己的苹果事件数据类型,然后通过苹果事件管理器的API发送和接收苹果事件。

Mac OS X会为您管理系统定义的苹果事件数据类型,并为当前正在执行的代码正确地处理这些类型。您不需要执行任何特殊的任务。当您的应用程序从苹果事件抽取系统定义的数据时,系统会在向您的应用程序发送事件之前,将数据进行字节交换。您需要按照本地的endian格式来解析苹果事件中的系统定义数据类型。类似地,如果您将一个本地endian格式的数据放在一个即将发送的苹果事件中,而且该数据是系统定义的数据类型,则接收方也能够以其本地endian格式解析这个数据。

然而,对于您自行定义的定制苹果事件,必须考虑字节顺序的差异。通过下面的任何一种方法都可以完成这个工作:

§  书写字节交换回调函数(也称为flipper),并提供给系统。每当系统确定您的苹果事件需要进行字节交换的时候,就激活您的flipper程序,以确保数据的接收方能够收到正确endian格式的数据。如果需要细节信息,请参见“书写和安装字节交换数据的回调函数”。

§  不管具体的架构是什么,选用一种的endian格式。这样,在读写定制的苹果事件数据的时候,就可以使用big-to-host和host-to-big例程,比如Core Foundation的字节顺序工具(Byte Order Utilities)中的CFSwapInt16BigToHostCFSwapInt16HostToBig函数。

定制的资源数据

在Mac OS X上推荐的提供资源的方法是在应用程序包中提供一些文件,来定义诸如图像文件,声音,本地化文本,以及归档的用户界面这样的资源。而本文的这个部分讨论的资源数据是指Carbon支持的资源管理器风格(ResourceManager-style)的资源文件中定义的资源。资源管理器在Mac OS X之前就已经推出了。如果您的应用程序使用了资源管理器风格的资源文件,则应该考虑将它们向应用程序包中的Mac OS X风格的资源转换。

资源通常包含描述菜单,窗口,控件,对话框,声音,字体,和图标数据。系统定义了很多标准的资源类型(比如'movv',用于指定一个QuickTime电影,还有'MENU',用于定义菜单),您可以创建自己的私有资源类型,在自己的应用程序中使用。通过资源管理器的API,您可以定义资源数据类型,以及获取和设置资源数据。

Mac OS X在内存中跟踪资源,使您的应用程序可以读写资源。应用程序和系统软件根据资源的类型为资源解析数据。虽然在通常情况下,您会让操作系统自行读取资源(比如应用程序图标),但是您也可以直接调用资源管理器函数来读写资源。

Mac OS X为您管理系统定义的资源,并为当前正在运行的代码正确地处理这些资源。也就是说,如果您的应用程序运行在一个x86系统上,则Mac OS X会进行相应的字节交换,使应用程序的图标,菜单,以及其它标准资源正确显示。您不需要任何特殊的工作。但是如果您为自己的应用程序定义了私有的资源,则从磁盘读或者向磁盘写资源的时候需要考虑不同架构之间的字节顺序差异。

您可以使用下面的策略来处理资源管理器风格的定制资源数据。请注意,这些策略和用于处理定制苹果事件数据的策略是相同的:

§  为系统提供一个字节交换回调函数。每当系统确定您的资源数据必须进行字节交换的时候,就调用该函数。如果需要细节信息,请参见“书写和安装字节交换数据的回调函数”。

§  不管具体架构是什么,总是使用同样的endian 格式来写数据。这样,在读写您自己定制的资源数据的时候,就可以使用big-to-host和host-to-big例程,比如Core Foundation的字节顺序工具(Byte Order Utilities)中的CFSwapInt16BigToHostCFSwapInt16HostToBig函数。

请注意:如果您正在修改采用预装载位(preload bit)标识资源的代码,则应该将预装载位从所有需要经过字节交换处理的资源中删除。 在Mac OS X中,预装载位几乎总是没有必要的。如果不能删除预装载位,则应该在资源读取完成之后才对资源数据进行字节交换。您不能用flipper回调函数来自动进行字节交换,因为在Mac OS X上,预装载位会导致资源在运行任何应用程序代码之前,首先被读取。

浮点数

Core Foundation定义了一套函数以及两个特殊的数据类型来帮助您处理浮点数。这些函数使您可以用某种方式对32位和64位的浮点数进行编码,从而使您可以在必要的时候进行解码和字节交换。列表3-2向您说明如何对64位的浮点数进行编码,而列表 3-3说明如何解码。

 

列表3-2 :  对一个浮点数进行编码

 

Float64             myFloat64;

 

CFSwappedFloat64    swappedFloat;

 

// Encode the floating-point value.

 

swappedFloat = CFConvertFloat64HostToSwapped(myFloat64);

 

 

CFSwappedFloat32CFSwappedFloat64数据类型将浮点数包含在一个规范表示中。CFSwappedFloat 数据类型本身不是一个浮点数,不应该直接被使用。然而您可以将它发送给另一个进程,将它存储在磁盘,或者通过网络进行发送。由于该格式可以通过转换函数和规范格式进行相互转换,因此不需要显式地进行处理字节交换问题。如果需要的话,在格式转换的过程中就会自动处理这个问题。

 

列表3-3 :  对一个浮点数进行解码

 

Float64             myFloat64;

 

CFSwappedFloat64    swappedFloat;

 

// Decode the floating-point value.

 

myFloat64 = CFConvertFloat64SwappedToHost(swappedFloat);

 

 

NSByteOrder.h头文件中也定义了一些函数,与这里讨论的Core Foundation定义的函数相类似。

整型数

系统库中的字节访问函数,比如OSReadLittleInt16OSWriteLittleInt16,提供了基本的字节交换功能。如果本地的endian格式和目标的endian格式不同,这些函数就进行字节交换。它们在libkern/OSByteOrder.h头文件中定义。

请注意:OSReadXXXOSWriteXXX类型函数要比OSSwapXXX类型的函数或者其它较为高级的框架中的函数性能好。

Core Foundation为字节交换提供了三个优化过的基本函数—CFSwapInt16CFSwapInt32,和CFSwapInt64。所有其它的字节交换函数都使用这三个基本函数来完成它们的工作。一般地说,您不必直接使用这些基本函数。

基本的字节交换函数是无条件地进行交换,更高级别的交换函数则不同,它们以下面的方式来进行工作:当字节不需要交换的时候—换句话说,当源和宿主系统的字节顺序相同的时候,函数什么都不做。对于整形数类型,这些函数的形式是CFSwapXXXBigToHost和CFSwapXXXLittleToHost,CFSwapXXXHostToBig,和CFSwapXXXHostToLittle,其中XXX是数据类型,比如Int32。举例来说,在一个little-endian的机器上,您可以用CFSwapInt16BigToHost函数来从网络上读取一个16位的整数,网络上数据的字节顺序是网络字节顺序(big-endian)。列表 3-4演示这个过程。

 

列表3-4 :  将一个16位的整数从big-endian转换为主机系统的endian格式

 

SInt16  bigEndian16;

 

SInt16  swapped16;

 

// Swap a 16-bit value read from the network.

 

swapped16 = CFSwapInt16BigToHost(bigEndian16);

 

 

假定整形数是一个数据结构中的成员,列表3-5 中演示如何完成相应的字节交换。

 

列表3-5 :  将一个整数从little-endian转换为主机系统的endian格式

 

// Byte swap the values if necessary.

 

aStruct.int1 = CFSwapInt32LittleToHost(aStruct.int1)

 

aStruct.int2 = CFSwapInt32LittleToHost(aStruct.int2)

 

 

字节交换代码只有在必要的时候才交换字节。如果宿主系统是big-endian架构,则例子代码中使用的函数就会对每个成员进行字节交换;而当代码运行在little-endian的机器上时,字节交换代码则什么都不做—编译器对代码进行了优化。

网络相关的数据

与网络相关的数据(IP地址,端口号,等等)通常使用big-endian格式(也称为网络字节顺序),因此在网络和x86系统之间进行通讯的时候,您可能需要进行字节交换。在向网络传递数据,或者从网络接收数据的时候,您可能永远都不需要调整PowerPC代码。在x86系统上,您则必须仔细查看网络通讯代码,确保自己总是以正确的字节顺序发送与网络有关的数据。您还必须正确处理从网络上接收的数据,将数值通过字节交换处理为适合于宿主系统的微处理器的endian格式。

您可以使用下面这些POSIX函数来在网络字节顺序和宿主系统的字节顺序之间进行转换(其它字节交换函数,比如OSByteOrder.h和CFByteOrder.h头文件中定义的那些函数,也可以用于处理网络数据)。

§  网络到宿主系统:

uint32_t ntohl (uint32_t netlong);

uint16_t ntohs (uint16_t netshort);

§  宿主系统到网络:

uint32_t htonl (uint32_t hostlong);

uint16_t htons (uint16_t hostshort);

这些函数的信息技术在man页面中,可以在Terminal或者Xcode查看。

sockaddr_in结构中的sin_saddr.s_addrsin_port成员的字节顺序应该总是网络字节顺序。通过阅读man页面文档,您可以找出适合于BSD网络通讯函数的每个参数的endian格式。

OSType-to-String的转换

您可以使用UTCreateStringForOSType UTGetOSTypeFromString 函数来在类型为OSType的值和CFString对象(CFStringRef数据类型)之间相互转换。这些函数在统一类型标识符概述一文中讨论,它们的定义位于UTType.h头文件中,该头文件是LaunchServices框架的一部分。

当您使用四字符的标识符时,请记住“abcd” != 'abcd',而应该是'abcd' == 0x61626364。您必须将'abcd’当作一个整形数,而不是字符串数据,因为'abcd'是一个32位整数的便利方式(一个FourCharCode数据类型就是一个UInt32数据类型)。编译器并不自动进行字节交换。如果您需要处理每个单独的字符,则可以使用移位操作符。

举例来说,如果您现在用标准的C printf风格的语法打印一个OSType或者FourCharCode 类型的值,则请用下面的方式:

 

 

printf("%c%c%c%c", (char) (val >> 24), (char) (val >> 16), 

 

                    (char) (val >> 8), (char) val)

 

 

而不要用下面这种方式:

 

 

printf("%4.4s", (const char*) &val)

 

 

Unicode文本文件

Mac OS X通常使用UTF-16 来编码Unicode;一个UniChar数据类型是一个双字节的值。和所有的多字节数据一样,Unicode字符对于微处理器使用的字节顺序是敏感的。Unicode标准提到,如果没有字节顺序标志(BOM),则Unicode数据文件中的数据将被作为big-endian格式来处理。虽然BOM并不是强制要有,但您还是应该使用这个标志,以确保在一个架构上写成的文件,在另一个架构上可以被读取。写在文件开头的字节顺序标志可以通知读取数据的程序该数据在写入的时候使用的是何种字节顺序。然后程序就可以根据这个信息进行相应的操作,以使Unicode文本的字节顺序和宿主系统相兼容。

表3-1列出了UTF-8,UTF-16,和UTF-32的标准字节顺序标志(请注意,UTF-8 BOM不仅用于endian问题,而且也作为一个标识,指明该文件是UTF-8编码的)。

 

表3-1 :  字节顺序标志

字节顺序标志

编码形式

EF BB BF

UTF-8

FF FE

UTF-16/UCS-2, little endian

FE FF

UTF-16/UCS-2, big endian

FF FE 00 00

UTF-32/UCS-4, little endian

00 00 FE FF

UTF-32/UCS-4, big endian

 

在实践中,当您的应用程序读取一个文件的时候,如果按照下面这些步骤进行,则既不需要寻找字节顺序标志,也不需要进行字节交换:

1.          用mmap调用将文件映射到内存,得到一个指向文件内容(或者字符串)的指针。

将整个文件读进内存可以保证最好的性能,而且是下一个步骤的前提。

2.          调用CFStringCreateWithBytes函数,并将isExternalRepresentation参数设置为真,生成一个CFString,或者调用CFStringCreateWithExternalRepresentation函数来生成一个CFString,传入kCFStringEncodingUnicode(UTF-16)或者kCFStringEncodingUTF8(UTF-8)作为编码的值。

上述的每个函数都会解析BOM标志,并进行任何必要的字节交换。请注意,BOM不应该在内存中使用,它的使用仅仅是为了数据传送(文件,剪贴板,等等)。

总而言之,关于Unicode文件,如果您遵循下面的指导原则,则您的应用程序可以执行得最好:

§  在读取外部的UTF-16或者UTF-8 编码的文件时,接受BOM标志。

§  在内部使用本地endian的UniChar数据类型。

§  在将UTF-16写入到文件中的时候,生成一个BOM标志。理想情况下,您只需要为使用little-endian格式的架构生成一个BOM,但是为使用big-endian格式的架构生成BOM也是可以接受的。.

§  当您将数据放到剪贴板的时候,请确保'utxt' 数据没有包含BOM。只有'ut16'的数据应该包含BOM。如果您使用Cocoa来将一个NSString放到粘贴板(pasteboard),则不必关心BOM的问题。

如果需要更多信息,请参见“UTF & BOM”部分,从Unicode网站上可以得到:

http://www.unicode.org/faq/utf_bom.html

苹果事件管理器提供了一些文本常数,您可以用这些常数来指定数据的类型。在Mac OS X v10.4中,只推荐两个文本常数:

§  typeUTF16ExternalRepresentation, 这个常数指定Unicode文本的16位外部表示,带有一个可选的字节顺序标志(BOM)。这个常数的存在保证了数据中有一个BOM,或者是UTF-16 big-endian格式。

§  typeUTF8Text,这个常数指定了8位Unicode编码(UTF-8编码)

typeUnicodeText常数表示utxt文本数据,字节顺序为本地字节顺序格式,带有一个可选的BOM。这个常数并不指定一个显式的Unicode编码,或者字节顺序定义。

Scrap Manager提供了flavor类型常数kScrapFlavorTypeUTF16External ,这个常数指定了Unicode文本的16位外部表示,带有可选的字节顺序标志(BOM)。

 

书写和安装一个字节交换数据的回调函数

为了处理定制的资源数据,粘贴板数据,以及苹果事件数据,您可以向系统提供一个字节交换回调函数,这种函数也称为flipper。在您安装字节交换回调函数的时候,需要定义与该函数相关的数据类型属于哪个域。有两种数据域—苹果事件域和资源域。资源数据域指的是定制的粘贴板和资源。如果回调函数可以应用于某个域(苹果事件域和资源域),您也就可以对该函数所属的域进行指定。

Core Endian API中定义了一个回调函数,您可以将它用于为定制的资源和苹果事件数据进行字节交换。您必须为希望进行字节交换的每一个数据类型提供一个回调函数。CoreEndianFlipProc回调函数的原型如下:

 

 

typedef CALLBACK_API (OSStatus, CoreEndianFlipProc)

 

    (OSType dataDomain,

 

    OSType dataType,

 

    short id,

 

    void *dataPtr,

 

    UInt32 dataSize,

 

    Boolean currentlyNative,

 

    void *refcon

 

);

 

 

回调函数读取下面这些参数:

§ dataDomain—这是一个OSType类型的值,指定flipper回调函数应用的域。kCoreEndianResourceManagerDomain表示该域是资源域还是粘贴板数据域。kCoreEndianAppleEventManagerDomain则表示该域是苹果事件数据域。

§ dataType—这个参数表示即将通过回调函数进行字节交换操作的数据的类型。它是一个四字符编码,表示资源类型,粘贴板类型,或者苹果事件。

§ id—数据类型的资源id。如果dataDomain参数的值不为kCoreEndianResourceManagerDomain,则这个参数会被忽略。

§ dataPtr—作为输入,指向即将被调整的数据;作为输出,则指向已经字节交换完成的数据。

§ dataSize—dataPtr参数指向的数据的长度。

§ currentlyNative—这是一个Boolean值,表示字节交换的方向。值为真表示dataPtr参数指向的数据使用的是当前执行码的字节顺序。在PowerPC系统中,真值表示该数据是big-endian格式的。而在x86系统中,真值表示该数据是little-endian格式。

§ refcon—一个32位的数值,包含或者指向回调函数所需要的数据。

这个回调函数返回一个表示字节交换是否成功的结果码。如果在字节交换中没有发生错误,则应该返回noErr;否则返回恰当的结果码来指示错误状态—errCoreEndianDataTooShortForFormat,errCoreEndianDataTooLongForFormat,或者errCoreEndianDataDoesNotMatchFormat。您返回的结果码会通过恰当的管理器(资源管理器(ResError)或者苹果事件管理器)传回给调用者。

非数值的类型(比如字符串,字节流,等等)不需要进行字节交换。 您只需要为那些需要字节交换的数据类型提供回调函数,在那些类型中,字(word)或者长字(long word)的字节顺序相当重要(如果需要处理Unicode字符串的推荐方法,请见“Unicode 文本文件部分”)。

您的回调函数应该仔细检查包含数据和需要字节交换的数据结构:

§ 检查所有的计数变量和长度,以使数组的索引和正确的值相关联

§ 检查所有的整形数和长整形数,这样当您将它们读取到兼容类型的变量中时,就可以对那些值进行正确地操作(比如数字运算,求偏移量,和移位操作)

Core Endian API提供了下面这些函数,可以在您的回调函数中使用:

§ CoreEndianInstallFlipper 为指定的数据类型(定制的资源或苹果事件)安装回调函数。为应用程序定义的资源数据类型安装字节交换回调函数之后,任何时候调用资源管理器函数对该资源类型进行操作,系统就会在适合的时候激活相应的回调函数(如果您的回调函数对粘贴板数据进行操作,系统也会在合适的时机对它进行调用)。类似地,如果您指定了苹果事件作为回调函数的域,则任何时候您调用苹果事件管理器函数对该数据类型进行操作,都会使系统在适合的时候调用该回调函数。

§ CoreEndianGetFlipper 得到为指定的数据类型安装的回调函数。您可以调用这个函数来确定给定的数据类型是否安装了flipper。

§ CoreEndianFlipData 激活与指定的数据类型相关联的数据类型。您应该不需要调用这个函数,因为系统会在任何需要的时候对回调函数进行调用。

作为一个例子,让我们看看列表3-6定义的定制资源类型('PREF')的回调函数。MyPreferences结构用于将预置数据存贮在磁盘上。该结构包含很多值,其中包括两个RGBColor类型的实例,和一个RGBColor数组。

 

列表3-6 :  定制资源的声明

 

#define kMyPreferencesType      'PREF'

 

 

 

struct MyPreferences {

 

                SInt32          fPrefsVersion;

 

 

 

                Boolean         fHighlightLinks;

 

                Boolean         fUnderlineLinks;

 

 

 

                RGBColor        fHighlightColor;

 

                RGBColor        fUnderlineColor;

 

                SInt16          fZoomValue;

 

 

 

                char            fCString[32];

 

 

 

                SInt16          fCount;

 

                RGBColor        fPalette[];

 

};

 

 

您可以书写一个对RGBColor数据结构中的字节进行交换的函数,来对RGBColor数据类型进行处理。请参见列表3-7所示的 MyRGBSwap 函数。该函数调用了Core Endian的宏 EndianS16_Swap 来对RGBColor结构中的每个值进行字节交换。它并不需要检查当前运行系统的字节顺序格式,因为该函数只有在RGBColor类型中的值需要进行字节交换的时候才会被调用。 MyRGBSwap 函数在字节交换的回调函数(如列表 3-8所示)中被调用,该回调函数用于处理定制的'PREF'资源(在列表 3-6中定义)。

 

列表3-7 :  RGBColor数据的flipper函数

 

static void MyRGBSwap (RGBColor *p)

 

{

 

    p->red = Endian16_Swap(p->red);

 

    p->blue = Endian16_Swap(p->blue);

 

    p->green = Endian16_Swap(p->green);

 

}

 

 

列表3-8显示一个用于处理定制的'PREF' 资源的字节交换回调函数。在列表的后面紧跟着对编号了的代码行的解释。请注意,这个flipper对形式错误或者长度不同于预期的数据进行检查。如果传入flipper例程的数据的长度比需要被调整的类型的正常长度短,或者(举个例子)包含某些垃圾数据,而不是数组的长度,则flipper必须非常小心,不要对传入的数据进行越界读写,而是应该返回错误。

 

列表3-8 :  定制的'PREF' 资源的flipper函数

 

#define kCurrentVersion    0x00010400

 

 

 

static OSStatus MyFlipPreferences (OSType dataDomain,

// 1

                    OSType dataType, 

// 2

                    short id, 

// 3

                    void * dataPtr, 

// 4

                    UInt32 dataSize, 

// 5

                    Boolean currentlyNative,

// 6

                    void* refcon)

// 7

{

 

    UInt32  versionNumber;

 

 

 

    OSStatus status = noErr;

 

    MyPreferences* toFlip = (MyPreferences*) dataPtr;     

// 8

    int count, i;

 

 

 

    if (dataSize < sizeof(MyPreferences))

 

        return errCoreEndianDataTooShortForFormat;

// 9

    if (currentlyNative) 

// 10

    {

 

        count = toFlip->fCount;

 

        versionNumber = toFlip->fPrefsVersion;

 

        toFlip->fPrefsVersion = Endian32_Swap (toFlip->fPrefsVersion);

 

        toFlip->fCount = Endian16_Swap (toFlip->fCount);

 

        toFlip->fZoomValue = Endian16_Swap (toFlip->fZoomValue);

 

    }  

 

    else 

// 11

    {

 

        toFlip->fPrefsVersion = Endian32_Swap (toFlip->fPrefsVersion);

 

        versionNumber = toFlip->fPrefsVersion;

 

        toFlip->fCount = Endian16_Swap (toFlip->fCount);

 

        toFlip->fZoomValue = Endian16_Swap (toFlip->fZoomValue);

 

        count = toFlip->fCount;

 

    }

 

    if (versionNumber != kCurrentVersion)

// 12

                return errCoreEndianDataDoesNotMatchFormat;

 

 

 

    MyRGBSwap (&toFlip->fHighlightColor);

// 13

    MyRGBSwap (&toFlip->fUnderlineColor);

// 14

 

 

    if (dataSize < sizeof(MyPreferences) + count * sizeof(RGBColor))

 

        return errCoreEndianDataTooShortForFormat; 

// 15

 

 

    for(i = 0; i < count; i++)

 

    {  

 

        MyRGBSwap (&toFlip->fPalette[i]);

// 16

    }

 

 

 

    return status;   

// 17

}

 

 

下面解释代码做了些什么:

1.          系统将向回调函数传入即将被应用的域。在用CoreEndianInstallFlipper函数安装回调函数之前,要定义域。

2.          系统将向回调函数传入为数据定义的资源类型。在这个例子中,资源类型为'PREF'。

3.          系统将向回调函数传入数据类型的资源ID。如果数据不是一种资源,则该值为0。

4.          系统将向回调函数传入一个需要经过字节交换的资源数据的指针。在这个例子中,指针指向一个MyPreferences数据结构。

5.          系统将向回调函数传入数据的长度,指明上一个步骤描述的指针指向的数据的长度。

6.          如果传给回调函数的缓冲区中的数据的字节顺序和当前执行代码的字节顺序一样,则系统将向回调函数传入真(true)。在基于PowerPC的Macintosh上,当currentlyNative为真时,数据是big-endian顺序的。而在采用Intel微处理器的Macintosh上,currentlyNative为真表示数据是little-endian顺序。您的回调函数需要知道这个值,因为如果它通过数据缓冲区中的某个值来确定如何处理缓冲区中的数据(比如,代码中显示的count变量),则您必须知道回调函数使用这个值之前是否需要调整。

7.          系统将向回调函数传入一个指向应用程序专用的数据的指针。在这个例子中,回调函数并不要求传入任何应用程序专用的数据。

8.          为MyPreferences数据类型定义一个变量,并将数据指针的内容分配给新定义的toFlip变量。

9.          检查结构中的静态长度(static-length)部分。如果该长度小于应该有的长度,则例程返回errCoreEndianDataTooLongForFormat错误。

10.      如果currentlyNative参数为真,则将count变量的值存储到一个局部变量中,然后对MyPreferences数据结构中的其它值进行字节交换。您需要在交换之前存储count变量的值,因为您稍后在函数的一个循环中需要这个值。currentlyNative参数为真的事实,说明在当前执行代码中,count变量的值不需要进行字节交换。然而,如果要存储到磁盘上,则该值需要进行字节交换。

这里使用适合的Core Endian宏来对值进行字节交换。

11.      如果currentlyNative参数的值为假(false),则在将count变量的值存储在局部变量之前,需要对MyPreferences数据结构的值进行调整。currentlyNative参数为假的事实说明count变量的值在被回调函数使用之前必须经过字节交换。

12.      检查数据结构的版本,确保应用程序支持该版本。如果该版本不被支持,则回调函数不进行字节交换,并返回errCoreEndianDataDoesNotMatchFormat错误。

13.      调用 MyRGBSwap 函数(如列表 3-7所示)对数据结构中的fHighlightColor成员进行字节交换。

14.      调用 MyRGBSwap 函数对数据结构中的fUnderlineColor成员进行字节交换。

15.      检查数据长度,确保该长度小于应有的数据长度。如果不是的话,则例程返回errCoreEndianDataTooLongForFormat错误。

16.      对fPalette数组中的元素进行遍历,调用 MyRGBSwap 函数对数组中的数据进行字节交换。

17.      返回noErr,表示成功完成对数据的调整。

上面的例子虽然执行了一些错误检查代码,但是并没有包含所有可能的错误处理代码。在您书写一个flipper的时候,可能需要包含那样的代码。

请注意:回调函数并没有对MyPreferences数据结构中的Boolean值进行改变,因为那些值是单字节的。回调函数还忽略了C字符串。

您可以通过调用CoreEndianInstallFlipper函数来安装字节交换回调函数。回调函数的安装应该是在应用程序调用其初始化例程,或者打开资源的时候。举例来说,您可以通过下面的代码来安装列表 3-8显示的flipper回调函数:

 

 

OSStatus status = noErr;

 

status = CoreEndianInstallFlipper (kCoreEndianResourceManagerDomain,

 

                        kMyPreferencesType,

 

                        MyFlipPreferences,

 

                        NULL);

 

 

当currentlyNative参数为假,且在装载资源的时候,或者该参数为真,且将资源设置为被写入的时候,系统会调用相应资源类型和数据域的回调函数。举例来说,在应用程序执行下面代码的任何时候,就会导致相应的字节交换回调函数被调用:

 

 

MyPreferences** hPrefs = (MyPreferences**) GetResource ('PREF', 128);

 

 

在数据完成了字节交换的时候,您可以根据自己的需要进行修改。

当资源管理器从磁盘中读取资源的时候,会在字节交换例程表中寻找资源类型(比如'PREF')。如果该资源类型安装了回调函数,则资源管理器就会调用该回调函数。在资源管理器将资源写入磁盘的时候,也执行类似的动作。它会找到合适的例程,并调用相应的回调函数来将资源转换为big-endian字节顺序。

当您要从安装了粘贴板数据回调函数的应用程序中拷贝或者拖拽定制数据时,系统会在合适的时机调用相应的回调函数。如果您将数据拷贝或者拖拽到本地程序中,回调函数不被调用;如果您将数据拷贝或者拖拽到非本地程序中,系统就会调用您的回调函数,来为定制数据进行字节交换。如果您从非本地的应用程序中取得定制数据,粘贴或放置到自己的应用程序中,并且该定制数据安装了一个回调函数,则系统会在粘贴或者放置数据的时候调用相应的回调函数。如果该定制数据是从另一个本地程序拷贝或拖拽得到,则回调函数不会被调用。

请注意,不同的粘贴板API使用不同的类型指示符。Scrap Manager和Drag Manager使用OSTypes类型,Pasteboard Manager使用Uniform Type Identifiers (UTI),而NSPasteboard使用其自己的类型机制。在上述的每一种情况中,系统会将相应的类型转换为OSType类型,并检查原始类型是否安装了字节交换的回调函数。

苹果事件数据在通过网络进行发送时,通常要转换为网络字节顺序。只有当您定义的定制数据类型通过网络发送到另一台机器上,或者其它机器向您的应用程序发送苹果事件数据,您安装的回调函数才会被调用。网络中的苹果事件的字节顺序是big-endian格式。

在某些情况下,系统不会正常地调用字节交换回调函数,这时您可以调用CoreEndiaFlipData函数来为指定的数据类型和域激活已安装的回调函数。

 

原创粉丝点击