??xml version="1.0" encoding="utf-8" standalone="yes"?> 因此我认为,.NET Framework 1.0 ?1.1 版类库中存在M Windows 所没有的功能限刉不为怪。毕竟,32
位的 WindowsQ不何U版本)是一个成熟的操作pȝQؓq大客户服务了十多年。相比之下,.NET Framework 却是一个新事物?/p>
随着来多的开发h员将生应用E序转到托管代码Q开发h员更频繁地研I底层操作系l以图找Z些关键功能显得很自然 ?臛_目前是如此?/p>
值得庆幸的是Q公paq行?(CLR) ?interop 功能Q称为^台调?
(P/Invoke)Q非常完善。在本专栏中Q我重点介l如何实际?P/Invoke 来调?Windows API 函数。当?CLR ?
COM Interop 功能ӞP/Invoke 当作名词使用Q当指该功能的用时Q则其当作动词使用。我q不打算直接介绍 COM
InteropQ因为它?P/Invoke h更好的可讉K性,却更加复杂,q有点自相矛盾,q得将 COM Interop
作ؓ专栏主题来讨Z太简明扼要?/p>
走进 P/Invoke 首先从考察一个简单的 P/Invoke CZ开始。让我们看一看如何调?Win32 MessageBeep 函数Q它的非托管声明如以下代码所C: Z调用 MessageBeepQ您需要在 C# 中将以下代码dC个类或结构定义中Q? 令h惊讶的是Q只需要这D代码就可以使托代码调用非托管?MessageBeep
API。它不是一个方法调用,而是一个外部方法定义。(另外Q它接近于一个来?C ?C#
允许的直接端口,因此以它vҎ介绍一些概忉|有帮助的。)来自托管代码的可能调用如下所C: h意,现在 MessageBeep Ҏ被声明ؓ static。这?P/Invoke Ҏ所要求的,因ؓ在该 Windows API
中没有一致的实例概念。接下来Q还要注意该Ҏ被标Cؓ extern。这是提C编译器该方法是通过一个从 DLL
导出的函数实现的Q因此不需要提供方法体?/p>
说到~少Ҏ体,您是否注意到 MessageBeep 声明q没有包含一个方法体Q与大多数算法由中间语言 (IL)
指ol成的托方法不同,P/Invoke Ҏ只是元数据,实时 (JIT) ~译器在q行旉过它将托管代码与非托管?DLL
函数q接h。执行这U到非托世界的q接所需的一个重要信息就是导出非托管Ҏ?DLL 的名U。这一信息是由 MessageBeep
Ҏ声明之前?DllImport 自定义属性提供的。在本例中,可以看到QMessageBeep 非托?API 是由 Windows 中的
User32.dll 导出的?/p>
到现在ؓ止,关于调用 MessageBeep 剩两个话题没有介绍Q请回顾一下,调用的代码与以下所CZ码片D非常相| 最后这两个话题是与数据送处?(data marshaling) 和从托管代码到非托管函数的实际方法调用有关的话题。调用非托管
MessageBeep 函数可以由找C用域内的extern MessageBeep
声明的Q何托代码执行。该调用cM于Q何其他对静态方法的调用。它与其他Q何托方法调用的共同之处在于带来了数据封送处理的需要? C# 的规则之一是它的调用语法只能访?CLR 数据cdQ例?System.UInt32 ?System.Boolean。C#
昄不识?Windows API 中用的Z C 的数据类型(例如 UINT ?BOOLQ,q些cd只是 C
语言cd的类型定义而已。所以当 Windows API 函数 MessageBeep 按以下方式编写时 外部Ҏ必M?CLR cd来定义,如您在前面的代码片段中所看到的。需要用与基础 API 函数cd不同但与之兼容的 CLR cd?P/Invoke 较难使用的一个方面。因此,在本专栏的后面我用完整的章节来介绍数据送处理? 样式 ?C# 中对 Windows API q行 P/Invoke 调用是很单的。但如果cd拒绝使您的应用程序发出嘟壎ͼ应该x设法调用 Windows 使它q行q项工作Q是吗? 是的。但是与选择的方法有养I而且关系甚大Q通常Q如果类库提供某U途径来实现您的意图,则最好?API 而不要直接调用非托管代码Q因?
CLR cd?Win32 之间在样式上有很大的不同。我可以关于这个问题的归结Z句话。当您进?P/Invoke
Ӟ不要使应用程序逻辑直接属于M外部Ҏ或其中的构g。如果您遵@q个规则,从长q看l常会省去许多的ȝ?/p>
?1 中的代码昄了我所讨论?MessageBeep 外部Ҏ的最附加代码。图 1
中ƈ没有M显著的变化,而只是对无包装的外部Ҏq行一些普通的改进Q这可以使工作更加轻松一些。从剙开始,您会注意C个名?Sound
的完整类型,它专用于 MessageBeep。如果我需要?Windows API 函数 PlaySound
来添加对播放波Ş的支持,则可以重?Sound
cd。然而,我不会因公开单个公共静态方法的cd而生气。毕竟这只是应用E序代码而已。还应该注意刎ͼSound
是密的Qƈ定义了一个空的私有构造函数。这些只是一些细节,目的是用户不会错误C Sound zcL者创建它的实例?/p>
?1 中的代码的下一个特征是QP/Invoke 出现位置的实际外部方法是 Sound 的私有方法。这个方法只是由公共
MessageBeep Ҏ间接公开Q后者接?BeepTypes
cd的参数。这个间接的额外层是一个很关键的细节,它提供了以下好处。首先,应该在类库中引入一个未来的 beep 托管ҎQ可以重复地通过公共
MessageBeep Ҏ来用托?APIQ而不必更改应用程序中的其余代码?/p>
该包装方法的W二个好处是Q当您进?P/Invoke 调用Ӟ您放弃了免受讉K冲突和其他低U破坏的权利Q这通常是由 CLR
提供的。缓冲方法可以保护您的应用程序的其余部分免受讉K冲突及类似问题的影响Q即使它不做M事而只是传递参敎ͼ。该~冲Ҏ由 P/Invoke
调用引入的Q何潜在的错误本地化?/p>
私有外部方法隐藏在公共包装后面的第三同时也是最后的一个好处是Q提供了向该Ҏd一些最的 CLR 样式的机会。例如,在图 1
中,我将 Windows API 函数q回?Boolean p|转换成更?CLR 的异常。我q定义了一个名?BeepTypes
的枚丄型,它的成员对应于同?Windows API 一起用的定义倹{由?C#
不支持定义,因此可以使用托管枚Dcd来避免数向整个应用E序代码扩散?/p>
包装Ҏ的最后一个好处对于简单的 Windows API 函数Q如
MessageBeepQ诚然是微不道的。但是当您开始调用更复杂的非托管函数Ӟ您会发现Q手动将 Windows API 样式转换成对 CLR
更加友好的方法所带来的好处会来多。越是打在整个应用E序中重?interop
功能Q越是应该认真地考虑包装的设计。同时我认ؓQ在非面向对象的静态包装方法中使用?CLR 友好的参Cq不可以?/p>
DLL Import 属?/p>
现在是更深入地进行探讨的时候了。在Ҏ代码进?P/Invoke 调用ӞDllImportAttribute
cd扮演着重要的角艌ӀDllImportAttribute 的主要作用是l?CLR 指示哪个 DLL 导出您想要调用的函数。相?DLL
的名U被作ؓ一个构造函数参C递给 DllImportAttribute?/p>
如果您无法肯定哪?DLL 定义了您要用的 Windows API 函数QPlatform SDK 文档ؓ您提供最好的帮助资源。在
Windows API 函数主题文字临近l尾的位|,SDK 文档指定?C 应用E序要用该函数必须链接?.lib
文g。在几乎所有的情况下,?.lib 文gh与定义该函数的系l?DLL 文g相同的名U。例如,如果该函数需?C 应用E序链接?
Kernel32.libQ则该函数就定义?Kernel32.dll 中。您可以?MessageBeep 中找到有?MessageBeep
?Platform SDK 文档主题。在该主题结֤Q您会注意到它指出库文g?User32.libQ这表明 MessageBeep 是从
User32.dll 中导出的?/p>
可选的 DllImportAttribute 属?/p>
除了指出宿主 DLL 外,DllImportAttribute q包含了一些可选属性,其中四个特别有趣QEntryPoint、CharSet、SetLastError ?CallingConvention?/p>
EntryPoint 在不希望外部托管Ҏh?DLL 导出相同的名U的情况下,可以讄该属性来指示导出?DLL
函数的入口点名称。当您定义两个调用相同非托管函数的外部方法时Q这特别有用。另外,?Windows 中还可以通过它们的序号值绑定到导出?
DLL 函数。如果您需要这样做Q则诸如“#1”?#8220;#129”?EntryPoint 值指C?DLL 中非托管函数的序号D不是函数名?/p>
CharSet 对于字符集,q所有版本的 Windows 都是同样创徏的。Windows 9x pd产品~少重要?Unicode
支持Q?Windows NT ?Windows CE pd则一开始就使用 Unicode。在q些操作pȝ上运行的 CLR Unicode
用于 String ?Char 数据的内部表C。但也不必担??当调?Windows 9x API 函数ӞCLR
会自动进行必要的转换Q将其从 Unicode转换?ANSI?/p>
如果 DLL 函数不以M方式处理文本Q则可以忽略 DllImportAttribute ?CharSet 属性。然而,?Char
?String 数据是等式的一部分Ӟ应该?CharSet 属性设|ؓ CharSet.Auto。这样可以 CLR Ҏ宿主 OS
使用适当的字W集。如果没有显式地讄 CharSet 属性,则其默认gؓ CharSet.Ansi。这个默认值是有缺点的Q因为对于在
Windows 2000、Windows XP ?Windows NT® 上进行的 interop
调用Q它会消极地影响文本参数送处理的性能?/p>
应该昑ּ地选择 CharSet.Ansi ?CharSet.Unicode ?CharSet D不是?CharSet.Auto
的唯一情况是:您显式地指定了一个导出函敎ͼ而该函数特定于这两种 Win32 OS 中的某一U。ReadDirectoryChangesW
API 函数是q样的一个例子,它只存在于基?Windows NT 的操作系l中Qƈ且只支持 UnicodeQ在q种情况下,您应该显式地使用
CharSet.Unicode?/p>
有时QWindows API 是否有字W集关系q不明显。一U决不会有错的确认方法是?Platform SDK 中检查该函数?C
语言头文件。(如果您无法肯定要看哪个头文gQ则可以查看 Platform SDK 文档中列出的每个 API 函数的头文g。)如果您发现该
API 函数实定义Z个映到?A ?W l尾的函数名的宏Q则字符集与您尝试调用的函数有关pRWindows API 函数的一个例子是?
WinUser.h 中声明的 GetMessage APIQ您也许会惊讶地发现它有 A ?W 两种版本?/p>
SetLastError 错误处理非常重要Q但在编E时l常被遗忘。当您进?P/Invoke 调用Ӟ也会面其他的挑??处理托管代码?Windows API 错误处理和异怹间的区别。我可以l您一点徏议?/p>
如果您正在?P/Invoke 调用 Windows API 函数Q而对于该函数Q您使用 GetLastError
来查找扩展的错误信息Q则应该在外部方法的 DllImportAttribute 中将 SetLastError 属性设|ؓ
true。这适用于大多数外部Ҏ?/p>
q会D CLR 在每ơ调用外部方法之后缓存由 API 函数讄的错误。然后,在包装方法中Q可以通过调用cd?
System.Runtime.InteropServices.Marshal cd中定义的 Marshal.GetLastWin32Error
Ҏ来获取缓存的错误倹{我的徏议是查这些期望来?API
函数的错误|qؓq些值引发一个可感知的异常。对于其他所有失败情况(包括Ҏ没意料到的p|情况Q,则引发在
System.ComponentModel 命名I间中定义的 Win32ExceptionQƈ?
Marshal.GetLastWin32Error q回的g递给它。如果您回头看一下图 1 中的代码Q您会看到我?extern
MessageBeep Ҏ的公共包装中采用了q种Ҏ?/p>
CallingConvention 我将在此介绍的最后也可能是最不重要的一?DllImportAttribute 属性是
CallingConvention。通过此属性,可以l?CLR
指示应该哪U函数调用约定用于堆栈中的参数。CallingConvention.Winapi
的默认值是最好的选择Q它在大多数情况下都可行。然而,如果该调用不起作用,则可以检?Platform SDK 中的声明头文Ӟ看看您调用的
API 函数是否是一个不W合调用U定标准的异?API?/p>
通常Q本机函敎ͼ例如 Windows API 函数?C- q行?DLL
函数Q的调用U定描述了如何将参数推入U程堆栈或从U程堆栈中清除。大多数 Windows API
函数都是首先函数的最后一个参数推入堆栈,然后p调用的函数负责清理该堆栈。相反,许多 C-q行?DLL
函数都被定义为按照方法参数在Ҏ{中出现的序其推入堆栈Q将堆栈清理工作交给调用者?/p>
q运的是Q要?P/Invoke 调用工作只需要让外围讑֤理解调用U定卛_。通常Q从默认?
CallingConvention.Winapi 开始是最好的选择。然后,?C q行?DLL 函数和少数函CQ可能需要将U定更改?
CallingConvention.Cdecl?/p>
数据送处?/p>
数据送处理是 P/Invoke h挑战性的斚w。当在托和非托代码之间传递数据时QCLR
遵@许多规则Q很有开发h员会l常遇到它们直至可将q些规则C。除非您是一名类库开发h员,否则在通常情况下没有必要掌握其l节。ؓ了最有效地在
CLR 上?P/InvokeQ即使只偶尔需?interop 的应用程序开发h员仍然应该理解数据封送处理的一些基知识?/p>
在本月专栏的剩余部分中,我将讨论单数字和字符串数据的数据送处理。我从最基本的数字数据封送处理开始,然后介绍单的指针送处理和字符串封送处理?/p>
送数字和逻辑标量 Windows OS 大部分是?C ~写的。因此,Windows API 所用到的数据类型要么是 C cdQ要么是通过cd定义或宏定义重新标记?C cd。让我们看看没有指针的数据封送处理。简单v见,首先重点讨论的是数字和布倹{?/p>
当通过值向 Windows API 函数传递参数时Q需要知道以下问题的{案Q? 数据从根本上讲是整型的还是Q点型的? 如果数据是整型的Q则它是有符Lq是无符LQ? 如果数据是整型的Q则它的位数是多? 如果数据是Q点型的,则它是单_ֺ的还是双_ֺ的? 有时{案很明显,但有时却不明显。Windows API 以各U方式重新定义了基本?C 数据cd。图 2 列出?C ?Win32 的一些公共数据类型及其规范,以及一个具有匹配规范的公共语言q行库类型? 通常Q只要您选择一个其规范与该参数?Win32 cd相匹配的 CLR cdQ您的代码就能够正常工作。不q也有一些特例。例如,?
Windows API 中定义的 BOOL cd是一个有W号?32 位整型。然而,BOOL 用于指示 Boolean ?true ?
false。虽然您不用?BOOL 参数作ؓ System.Int32 值封送,但是如果使用 System.Boolean
cdQ就会获得更合适的映射。字W类型的映射cM?BOOLQ因为有一个特定的 CLR cd (System.Char) 指出字符的含义?/p>
在了解这些信息之后,逐步介绍CZ可能是有帮助的。依焉?beep 主题作ؓ例子Q让我们来试一?Kernel32.dll 低
BeepQ它会通过计算机的扬声器发生嘟声。这个方法的 Platform SDK 文档可以?Beep 中找到。本?API
按以下方式进行记录: 在参数封送处理方面,您的工作是了解什?CLR 数据cd?Beep API 函数所使用?DWORD ?BOOL
数据cd相兼宏V回一下图 2 中的图表Q您看?DWORD 是一?32 位的无符h数|如同 CLR cd
System.UInt32。这意味着您可以?UInt32 g为送往 Beep 的两个参数。BOOL
q回值是一个非常有的情况Q因图表告诉我们Q在 Win32 中,BOOL 是一?32 位的有符h数。因此,您可以?
System.Int32 g为来?Beep 的返回倹{然而,CLR 也定义了 System.Boolean cd作ؓ Boolean
值的语义Q所以应该用它来替代。CLR 默认?System.Boolean 值封送ؓ 32 位的有符h数。此处所昄的外部方法定义是用于
Beep 的结?P/Invoke ҎQ? 指针参数 许多 Windows API
函数指针作为它们的一个或多个参数。指针增加了送数据的复杂性,因ؓ它们增加了一个间接层。如果没有指针,您可以通过值在U程堆栈中传递数据。有了指
针,则可以通过引用传递数据,Ҏ是将该数据的内存地址推入U程堆栈中。然后,函数通过内存地址间接讉K数据。用托代码表C此附加间接层的方式有多
U?/p>
?C# 中,如果方法参数定义ؓ ref ?outQ则数据通过引用而不是通过g递。即使您没有使用 Interop
也是q样Q但只是从一个托方法调用到另一个托方法。例如,如果通过 ref 传?System.Int32
参数Q则在线E堆栈中传递的是该数据的地址Q而不是整数值本w。下面是一个定义ؓ通过引用接收整数值的Ҏ的示例: q里QFlipInt32 Ҏ获取一?Int32 值的地址、访问数据、对它求反,然后求反过的Dl原始变量。在以下代码中,FlipInt32 Ҏ会将调用E序的变?x 的g 10 更改?-10Q? 在托代码中可以重用q种能力Q将指针传递给非托代码。例如,FileEncryptionStatus API 函数?32 位无W号位掩码的形式q回文g加密状态。该 API 按以下所C方式进行记录: h意,该函数ƈ不用它的返回D回状态,而是q回一?Boolean
|指示调用是否成功。在成功的情况下Q实际的状态值是通过W二个参数返回的。它的工作方式是调用E序向该函数传递指向一?DWORD
变量的指针,而该 API 函数用状态值填充指向的内存位置。以下代码片D|CZ一个调用非托管 FileEncryptionStatus
函数的可能外部方法定义: 该定义?out 关键字来?UInt32 状态值指C?by-ref 参数。这里我也可以选择 ref
关键字,实际上在q行时会产生相同的机器码。out 关键字只是一?by-ref 参数的规范,它向 C#
~译器指C所传递的数据只在被调用的函数外部传递。相反,如果使用 ref 关键字,则编译器会假定数据可以在被调用的函数的内部和外部传递?/p>
托管代码?out ?ref 参数的另一个很好的斚w是,地址作ؓ by-ref
参数传递的变量可以是线E堆栈中的一个本地变量、一个类或结构的元素Q也可以是具有合适数据类型的数组中的一个元素引用。调用程序的q种灉|性?
by-ref 参数成ؓ送缓冲区指针以及单数值指针的一个很好的L。只有在我发?ref ?out
参数不符合我的需要的情况下,我才会考虑指针封送ؓ更复杂的 CLR cdQ例如类或数l对象)?/p>
如果您不熟悉 C 语法或者调?Windows API
函数Q有时很隄道一个方法参数是否需要指针。一个常见的指示W是看参数类型是否是以字?P ?LP 开头的Q例?LPDWORD ?
PINT。在q两个例子中QLP ?P 指示参数是一个指针,而它们指向的数据cd分别?DWORD ?
INT。然而,在有些情况下Q可以直接?C 语言语法中的星号 (*) ?API 函数定义为指针。以下代码片D展CZq方面的CZQ? 可以看到Q上q函数的唯一一个参数是指向 DWORD 变量的指针? 当通过 P/Invoke 送指针时Qref ?out 只用于托代码中的值类型。当一个参数的 CLR cd使用 struct
关键字定义时Q可以认参数是一个值类型。Out ?ref
用于送指向这些数据类型的指针Q因为通常值类型变量是对象或数据,而在托管代码中ƈ没有对值类型的引用。相反,当封送引用类型对象时Qƈ不需?
ref ?out 关键字,因ؓ变量已经是对象的引用了?/p>
如果您对引用cd和值类型之间的差别不是很熟悉,h?2000 q?12 ?发行?MSDN® MagazineQ在 .NET
专栏的主题中可以扑ֈ更多信息。大多数 CLR cd都是引用cdQ然而,除了 System.String ?
System.ObjectQ所有的基元cdQ例?System.Int32 ?System.BooleanQ都是值类型?/p>
送不透明 (Opaque) 指针Q一U特D情?/p>
有时?Windows API 中,Ҏ传递或q回的指针是不透明的,q意味着该指针g技术角度讲是一个指针,但代码却不直接用它。相反,代码该指针q回l?Windows 以便随后q行重用?/p>
一个非常常见的例子是句柄的概c在 Windows 中,内部数据l构Q从文g到屏q上的按钮)在应用程序代码中都表CZؓ句柄。句柄其实就是不透明的指针或有着指针宽度的数|应用E序用它来表C内部的 OS 构造?/p>
数情况下,API 函数也将不透明指针定义?PVOID ?LPVOID cd。在 Windows API 的定义中Q这些类型意思就是说该指针没有类型?/p>
当一个不透明指针q回l您的应用程序(或者您的应用程序期望得C个不透明指针Q时Q您应该参数或q回值封送ؓ CLR 中的一U特D类??
System.IntPtr。当您?IntPtr cdӞ通常不?out ?ref 参数Q因?IntPtr
意ؓ直接持有指针。不q,如果您将一个指针封送ؓ一个指针,则对 IntPtr 使用 by-ref 参数是合适的?/p>
?CLR cdpȝ中,System.IntPtr cd有一个特D的属性。不像系l中的其他基cdQIntPtr
q没有固定的大小。相反,它在q行时的大小是依底层操作pȝ的正常指针大而定的。这意味着?32 位的 Windows 中,IntPtr
变量的宽度是 32 位的Q而在 64 位的 Windows 中,实时~译器编译的代码会将 IntPtr 值看?64
位的倹{当在托代码和非托代码之间封送不透明指针Ӟq种自动调节大小的特点十分有用?/p>
误住,Mq回或接受句柄的 API 函数其实操作的就是不透明指针。您的代码应该将 Windows 中的句柄送成 System.IntPtr 倹{?/p>
您可以在托管代码中将 IntPtr 值强制{换ؓ 32 位或 64 位的整数|或将后者强制{换ؓ前者。然而,当?Windows
API
函数Ӟ因ؓ指针应是不透明的,所以除了存储和传递给外部Ҏ外,不能它们另做它用。这U?#8220;只限存储和传?#8221;规则的两个特例是当您需要向外部Ҏ传?
null 指针值和需要比?IntPtr g null 值的情况。ؓ了做到这一点,您不能将零强制{换ؓ System.IntPtrQ而应该在
IntPtr cd上?Int32.Zero 静态公共字D,以便获得用于比较或赋值的 null 倹{?/p>
送文?/p>
在编E时l常要对文本数据q行处理。文本ؓ interop 刉了一些麻烦,q有两个原因。首先,底层操作pȝ可能使用 Unicode
来表C字W串Q也可能使用 ANSI。在极少数情况下Q例?MultiByteToWideChar API 函数的两个参数在字符集上是不一致的?/p>
W二个原因是Q当需要进?P/Invoke Ӟ要处理文本还需要特别了解到 C ?CLR 处理文本的方式是不同的。在 C
中,字符串实际上只是一个字W值数l,通常?null 作ؓl束W。大多数 Windows API 函数是按照以下条件处理字W串的:对于
ANSIQ将其作为字W值数l;对于 UnicodeQ将其作为宽字符值数l?/p>
q运的是QCLR 被设计得相当灉|Q当送文本时问题得以L解决Q而不用在?Windows API 函数期望从您的应用程序得到的是什么。这里是一些需要记住的主要考虑事项Q? 是您的应用程序向 API 函数传递文本数据,q是 API 函数向您的应用程序返回字W串数据Q或者二者兼有? 您的外部Ҏ应该使用什么托类型? API 函数期望得到的是什么格式的非托字W串Q?/p>
我们首先解答最后一个问题。大多数 Windows API 函数都带?LPTSTR ?LPCTSTR
倹{(从函数角度看Q它们分别是可修改和不可修改的缓冲区Q包含以 null
l束的字W数l?#8220;C”代表常数Q意味着使用该参C息不会传递到函数外部。LPTSTR 中的“T”表明该参数可以是 Unicode ?
ANSIQ取决于您选择的字W集和底层操作系l的字符集。因为在 Windows API 中大多数字符串参数都是这两种cd之一Q所以只要在
DllImportAttribute 中选择 CharSet.AutoQCLR 按默认的方式工作? 然而,有些 API 函数或自定义?DLL 函数采用不同的方式表C字W串。如果您要用C个这L函数Q就可以采用
MarshalAsAttribute 修饰外部Ҏ的字W串参数Qƈ指明一U不同于默认 LPTSTR 的字W串格式。有?
MarshalAsAttribute 的更多信息,请参阅位?MarshalAsAttribute Class ?Platform SDK
文档主题?/p>
现在让我们看一下字W串信息在您的代码和非托函C间传递的方向。有两种方式可以知道处理字符串时信息的传递方向。第一个也是最可靠的一个方法就
是首先理解参数的用途。例如,您正调用一个参敎ͼ它的名称cM CreateMutex q带有一个字W串Q则可以惛_该字W串信息是从应用E序?
API 函数传递的。同Ӟ如果您调?GetUserNameQ则该函数的名称表明字符串信息是从该函数向您的应用程序传递的?/p>
除了q种比较合理的方法外Q第二种查找信息传递方向的方式是查找 API 参数cd中的字母“C”。例如,GetUserName API
函数的第一个参数被定义?LPTSTR cdQ它代表一个指?Unicode ?ANSI 字符串缓冲区的长指针。但?CreateMutex
的名U参数被cd化ؓ LTCTSTR。请注意Q这里的cd定义是一LQ但增加一个字?#8220;C”来表明缓冲区为常敎ͼAPI 函数不能写入?/p>
一旦明了文本参数是只用作输入q是用作输入/输出Q就可以定使用哪种 CLR cd作ؓ参数cd。这里有一些规则。如果字W串参数只用作输入,则?System.String cd。在托管代码中,字符串是不变的,适合用于不会被本?API 函数更改的缓冲区?/p>
如果字符串参数可以用作输入和/或输出,则?System.StringBuilder cd。StringBuilder
cd是一个很有用的类库类型,它可以帮助您有效地构建字W串Q也正好可以缓冲区传递给本机函数Q由本机函数为您填充字符串数据。一旦函数调用返回,您只
需要调?StringBuilder 对象?ToString 可以得C?String 对象?/p>
GetShortPathName API 函数能很好地用于昄什么时候?String、什么时候?StringBuilderQ因为它只带有三个参敎ͼ一个输入字W串、一个输出字W串和一个指明输出缓冲区的字W长度的参数?/p>
?3 所CZؓ加注释的非托?GetShortPathName
函数文档Q它同时指出了输入和输出字符串参数。它引出了托的外部Ҏ定义Q也如图 3 所C。请注意W一个参数被送ؓ
System.StringQ因为它是一个只用作输入的参数。第二个参数代表一个输出缓冲区Q它使用?System.StringBuilder?/p>
结 本月专栏所介绍?P/Invoke 功能_调用 Windows 中的许多 API 函数。然而,如果您大量用?
interopQ则会最l发现自己封送了很复杂的数据l构Q甚臛_能需要在托管代码中通过指针直接讉K内存。实际上Q本Z码中?interop
可以是一个将l节和低U比特藏在里面的真正的潘多拉盒子。CLR、C# 和托?C++ 提供了许多有用的功能Q也总后我会在本专栏介l高U的
P/Invoke 话题?/p>
同时Q只要您觉得 .NET Framework cd无法播放您的声音或者ؓ您执行其他一些功能,您可以知道如何向原始而优U?Windows API L一些帮助?/p>
Figure 1 MessageBeep, Interop Done Well 使用C/C++语言开发Y件的E序员经常碰到这L问题Q有时候是E序~译没有问题Q但是链接的时候L报告函数不存在(l典?span lang="EN-US">LNK 2001错误Q,有时候是E序~译和链接都没有错误Q但是只要调用库中的函数׃出现堆栈异常。这些现象通常是出现在C?span lang="EN-US">C++的代码合用的情况下或 ?span lang="EN-US">C++E序中用第三方的库的情况下Q不是用C++语言开发的Q,其实q都是函数调用约定(Calling ConventionQ和函数名修饎ͼDecorated NameQ规则惹的祸。函数调用方式决定了函数参数入栈的顺序,是由调用者函数还是被调用函数负责清除栈中的参数等问题Q而函数名修饰规则军_了编译器?
用何U名字修饰方式来区分不同的函敎ͼ如果函数之间的调用约定不匚w或者名字修C匚w׃产生以上的问题。本文分别对C?span lang="EN-US">C++q两U编E语a的函数调 用约定和函数名修饰规则进行详l的解释Q比较了它们的异同之处,qD例说明了以上问题出现的原因?
函数调用U定Q?span lang="EN-US">Calling ConventionQ?/span> 函数调用U定不仅军_了发生函数调用时函数参数的入栈顺序,q决定了是由调用者函数还是被调用函数负责清除栈中的参敎ͼq原堆栈。函数调用约定有很多?
式,除了常见?span lang="EN-US">__cdeclQ?span lang="EN-US">__fastcall?span lang="EN-US">__stdcall之外Q?span lang="EN-US">C++的编译器q支?span lang="EN-US">thiscall方式Q不?span lang="EN-US">C/C++~译器还支持 naked call方式。这么多函数调用U定常常令许多程序员很迷惑,到底它们是怎么回事Q都是在什么情况下使用呢?下面分别介l这几种函数调用U定?span lang="EN-US"> ~译器的命o行参数是/Gd?span lang="EN-US">__cdecl方式?span lang="EN-US">C/C++~译器默认的函数调用U定Q所有非C++成员函数和那些没有用__stdcall?span lang="EN-US"> __fastcall声明的函数都默认?span lang="EN-US">__cdecl方式Q它使用C函数调用方式Q函数参数按照从叛_左的序入栈Q函数调用者负责清除栈中的参数Q由 于每ơ函数调用都要由~译器生清除(q原Q堆栈的代码Q所以?span lang="EN-US">__cdecl方式~译的程序比使用__stdcall方式~译的程序要大很多,但是 __cdecl调用方式是由函数调用者负责清除栈中的函数参数Q所以这U方式支持可变参敎ͼ比如printf?span lang="EN-US">windows?span lang="EN-US">API wsprintf是__cdecl调用方式。对?span lang="EN-US">C函数Q?span lang="EN-US">__cdecl方式的名字修饰约定是在函数名U前d一个下划线Q对?span lang="EN-US">C++函数Q除非特别 ?span lang="EN-US">extern "C"Q?span lang="EN-US">C++函数使用不同的名字修饰方式?span lang="EN-US"> ~译器的命o行参数是/Gr?span lang="EN-US">__fastcall函数调用U定在可能的情况下用寄存器传递参敎ͼ通常是前两个
DWORDcd的参数或较小的参C?span lang="EN-US">ECX?span lang="EN-US">EDX寄存器传递,其余参数按照从右向左的顺序入栈,被调用函数在q回之前负责清除栈中的参数。编译器使用
两个@修饰函数名字Q后跟十q制数表C的函数参数列表大小Q例如:@function_name@number。需要注意的?span lang="EN-US">__fastcall函数?用约定在不同的编译器上可能有不同的实玎ͼ比如16位的~译器和32位的~译器,另外Q在使用内嵌汇编代码Ӟq要注意不能和编译器使用的寄存器有冲H?span lang="EN-US"> 4.thiscall thiscall只用?span lang="EN-US">C++成员函数的调用,函数参数按照从右向左的顺序入栈,cd例的this指针通过ECX寄存器传递。需要注意的?span lang="EN-US">thiscall不是C++的关键字Q不能?span lang="EN-US">thiscall声明函数Q它只能q译器使用?span lang="EN-US"> 5.naked call 采用前面几种函数调用U定的函敎ͼ~译器会在必要的时候自动在函数开始添加保?span lang="EN-US">ESIQ?span lang="EN-US">EDIQ?span lang="EN-US">EBXQ?span lang="EN-US">EBP寄存器的代码Q在退出函数时恢复q些寄存?的内容,使用naked call方式声明的函C会添加这L代码Q这也就是ؓ什么称其ؓnaked的原因吧?span lang="EN-US">naked call不是cd修饰W,故必d_declspec共同使用?span lang="EN-US"> VC的编译环境默认是使用__cdecl调用U定Q也可以在编译环境的Project Setting...菜单Q?span lang="EN-US">C/C++ Q?span lang="EN-US">Code Generationw择讄函数调用U定。也可以直接在函数声明前d关键?span lang="EN-US">__stdcall?span lang="EN-US">__cdecl?span lang="EN-US">__fastcall{单独确定函
数的调用方式。在Windowspȝ上开发Y件常用到WINAPI宏,它可以根据编译设|翻译成适当的函数调用约定,?span lang="EN-US">WIN32中,它被定义?span lang="EN-US"> __stdcall?span lang="EN-US"> 函数名字修饰Q?span lang="EN-US">Decorated NameQ方?/span> 函数的名字修饎ͼDecorated
NameQ就是编译器在编译期间创建的一个字W串Q用来指明函数的定义或原型?span lang="EN-US">LINKE序或其他工h旉要指定函数的名字修饰来定位函数的正确位置?
多数情况下程序员q不需要知道函数的名字修饰Q?span lang="EN-US">LINKE序或其他工具会自动区分他们。当Ӟ在某些情况下需要指定函数的名字修饰Q例如在C++E序中, Z?span lang="EN-US">LINKE序或其他工兯够匹配到正确的函数名字,必Mؓ重蝲函数和一些特D的函数Q如构造函数和析构函数Q指定名字装饰。另一U需要指定函数的
名字修饰的情冉|在汇~程序中调用C?span lang="EN-US">C++的函数。如果函数名字,调用U定Q返回值类型或函数参数有Q何改变,原来的名字修饰就不再有效Q必L定新?
名字修饰?span lang="EN-US">C?span lang="EN-US">C++E序的函数在内部使用不同的名字修饰方式,下面分别介l这两种方式?span lang="EN-US"> 1. C~译器的函数名修饰规?span lang="EN-US"> 对于__stdcall调用U定Q编译器和链接器会在输出函数名前加上一个下划线前缀Q函数名后面加上一?span lang="EN-US">“@”W号和其参数的字节数Q例?span lang="EN-US">_functionname@number?span lang="EN-US">__cdecl调用U定仅在输出函数名前加上一个下划线前缀Q例?span lang="EN-US">_functionname?span lang="EN-US">__fastcall调用U定在输出函数名前加上一?span lang="EN-US">“@”W号Q后面也是一?span lang="EN-US">“@”W号和其参数的字节数Q例?span lang="EN-US">@functionname@number?span lang="EN-US"> C++的函数名修饰规则有些复杂Q但是信息更充分Q通过分析修饰名不仅能够知道函数的调用方式Q返回值类型,参数个数甚至参数cd。不?span lang="EN-US"> __cdeclQ?span lang="EN-US">__fastcallq是__stdcall调用方式Q函C饰都是以一?span lang="EN-US">“?”开始,后面紧跟函数的名字,再后面是参数表的开始标识和
按照参数cd代号拼出的参数表。对?span lang="EN-US">__stdcall方式Q参数表的开始标识是“@@YG”Q对?span lang="EN-US">__cdecl方式则是“@@YA”Q对?span lang="EN-US">__fastcall方式则是“@@YI”。参数表的拼写代号如下所C: int Function1(char *var1,unsigned long); 其函C饰名?span lang="EN-US">“?Function1@@YGHPADK@Z”Q而对于函数声明: void Function2(); 其函C饰名则ؓ“?Function2@@YGXXZ” ?
对于C++的类成员函数Q其调用方式?span lang="EN-US">thiscallQ,函数的名字修C非成员的C++函数E有不同Q首先就是在函数名字和参数表之间插入?span lang="EN-US">“@”字符引导的类名;其次是参数表的开始标识不同,公有Q?span lang="EN-US">publicQ成员函数的标识?span lang="EN-US">“@@QAE”,保护Q?span lang="EN-US">protectedQ成员函数的标识?span lang="EN-US">“@@IAE”,U有Q?span lang="EN-US">privateQ成员函数的标识?span lang="EN-US">“@@AAE”Q如果函数声明用了const关键字,则相应的标识应分别ؓ“@@QBE”Q?span lang="EN-US">“@@IBE”?span lang="EN-US">“@@ABE”。如果参数类型是cd例的引用Q则使用“AAV class CTest 对于成员函数FunctionQ其函数修饰名ؓ“?Function@CTest@@AAEXH@Z”Q字W串“@@AAE”表示q是一个私有函数。成员函?span lang="EN-US">CopyInfo只有一个参敎ͼ是对c?span lang="EN-US">CTest?span lang="EN-US">const引用参数Q其函数修饰名ؓ“?CopyInfo@CTest@@IAEXABV1@@Z”?span lang="EN-US"> DrawText是一个比较复杂的函数声明Q不仅有字符串参敎ͼq有l构体参数和HDC句柄参数Q需要指出的?span lang="EN-US">HDC实际上是一?span lang="EN-US">HDC__l构cd的指 针,q个参数的表C就?span lang="EN-US">“PAUHDC__@@”Q其完整的函C饰名?span lang="EN-US">“?DrawText@CTest@@QAEJPAUHDC__@@JPBDUtagRGBQUAD@@E_N@Z”?span lang="EN-US">InsightClass是一个共有的const函数Q它的成员函数标识是“@@QBE”Q完整的修饰名就?span lang="EN-US">“?InsightClass@CTest@@QBEJK@Z”?span lang="EN-US"> 无论?span lang="EN-US">C函数名修饰方式还?span lang="EN-US">C++函数名修饰方式均不改变输出函数名中的字符大小写,q和PASCAL调用U定不同Q?span lang="EN-US">PASCALU定输出的函数名无Q何修C全部大写?span lang="EN-US"> 3.查看函数的名字修?span lang="EN-US"> 有两U方式可以检查你的程序中的函数的名字修饰Q用编译输出列表或使用Dumpbin工具。?span lang="EN-US">/FAcQ?span lang="EN-US">/FAs?span lang="EN-US">/FAcs命o行参数可以让~译?输出函数或变量名字列表。?span lang="EN-US">dumpbin.exe
/SYMBOLS命o也可以获?span lang="EN-US">obj文g?span lang="EN-US">lib文g中的函数或变量名字列表。此外,q可以?span lang="EN-US"> undname.exe 修饰名转换为未修饰形式?span lang="EN-US"> 函数调用U定和名字修饰规则不匚w引v的常见问?/span> 函数调用时如果出现堆栈异常,十有八九是由于函数调用约定不匚w引v的。比如动态链接库a有以下导出函敎ͼ long MakeFun(long lFun); 动态库生成的时候采用的函数调用U定?span lang="EN-US">__stdcallQ所以编译生成的a.dll中函?span lang="EN-US">MakeFun的调用约 定是_stdcallQ也是函数调用时参C叛_左入栈,函数q回时自p原堆栈。现在某个程序模?span lang="EN-US">b要引?span lang="EN-US">a中的MakeFunQ?span lang="EN-US">b?span lang="EN-US">a一样?span lang="EN-US"> C++方式~译Q只?span lang="EN-US">b模块的函数调用方式是__cdeclQ由?span lang="EN-US">b包含?span lang="EN-US">a提供的头文g?span lang="EN-US">MakeFun函数声明Q所?span lang="EN-US">MakeFun?span lang="EN-US">b模块中被其它 调用MakeFun的函数认为是__cdecl调用方式Q?span lang="EN-US">b模块中的q些函数在调用完MakeFun当然要帮着恢复堆栈啦,可是MakeFun已经在结?时自己恢复了堆栈Q?span lang="EN-US">b模块中的函数q样多此一丑ְ引v了栈指针错误Q从而引发堆栈异常。宏观上的现象就是函数调用没有问题(因ؓ参数传递顺序是一?的)Q?span lang="EN-US">MakeFun也完成了自己的功能,只是函数q回后引发错误。解决的Ҏ也很单,只要保证两个模块的在~译时设|相同的函数调用U定p了?
在了解了函数调用U定和函数的名修饰规则之后,再来看在C++E序中?span lang="EN-US">C语言~译的库时经常出现的LNK 2001错误很单了。还以上面例子的两个模块ZQ这一ơ两个模块在~译的时候都采用__stdcall调用U定Q但?span lang="EN-US">a.dll使用C语言的语法编 译的Q?span lang="EN-US">C语言方式Q,所?span lang="EN-US">a.dll的蝲入库a.lib?span lang="EN-US">MakeFun函数的名字修饰就?span lang="EN-US">“_MakeFun@4”?span lang="EN-US">b包含?span lang="EN-US">a提供的头文g?span lang="EN-US">MakeFun函数声明Q但是由?span lang="EN-US">b采用的是C++语言~译Q所?span lang="EN-US">MakeFun?span lang="EN-US">b模块中被按照C++的名字修饰规则命名ؓ“?MakeFun@@YGJJ@Z”Q编译过E相安无事,链接E序?span lang="EN-US">c++的链接器到a.lib中去?span lang="EN-US">“?MakeFun@@YGJJ@Z”Q但?span lang="EN-US">a.lib中只?span lang="EN-US">“_MakeFun@4”Q没?span lang="EN-US">“?MakeFun@@YGJJ@Z”Q于是链接器报告: error LNK2001: unresolved external symbol ?MakeFun@@YGJJ@Z 解决的方法和单,是要让b模块知道q个函数?span lang="EN-US">C语言~译的,extern
"C"可以做到q一炏V一个采?span lang="EN-US">C语言~译的库应该考虑C用这个库的程序可能是C++E序Q?span lang="EN-US">C++~译器)Q所以在设计头文件时应该注意q一炏V通常应该q样声明头文Ӟ #ifdef _cplusplus q样C++的编译器q?span lang="EN-US">MakeFun的修饰名?span lang="EN-US">“_MakeFun@4”Q就不会有链接错误了?span lang="EN-US"> 许多Z明白Qؓ什么我使用的编译器都是VC的编译器q会产生“error LNKBOOL MessageBeep(
UINT uType // beep type
);[DllImport("User32.dll")]
static extern Boolean MessageBeep(UInt32 beepType);MessageBeep(0);
[DllImport("User32.dll")]
static extern Boolean MessageBeep(UInt32 beepType);BOOL MessageBeep( UINT uType )
•
•
•
•
BOOL Beep(
DWORD dwFreq, // Frequency
DWORD dwDuration // Duration in milliseconds
);[DllImport("Kernel32.dll", SetLastError=true)]
static extern Boolean Beep(
UInt32 frequency, UInt32 duration);void FlipInt32(ref Int32 num){
num = -num;
}Int32 x = 10;
FlipInt32(ref x);BOOL FileEncryptionStatus(
LPCTSTR lpFileName, // file name
LPDWORD lpStatus // encryption status
);[DllImport("Advapi32.dll", CharSet=CharSet.Auto)]
static extern Boolean FileEncryptionStatus(String filename,
out UInt32 status);void TakesAPointer(DWORD* pNum);
•
•
•
namespace Wintellect.Interop.Sound{
using System;
using System.Runtime.InteropServices;
using System.ComponentModel;
sealed class Sound{
public static void MessageBeep(BeepTypes type){
if(!MessageBeep((UInt32) type)){
Int32 err = Marshal.GetLastWin32Error();
throw new Win32Exception(err);
}
}
[DllImport("User32.dll", SetLastError=true)]
static extern Boolean MessageBeep(UInt32 beepType);
private Sound(){}
}
enum BeepTypes{
Simple = -1,
Ok = 0x00000000,
IconHand = 0x00000010,
IconQuestion = 0x00000020,
IconExclamation = 0x00000030,
IconAsterisk = 0x00000040
}
}Figure 2 Non-Pointer Data Types
Win32 Types Specification CLR Type
char, INT8, SBYTE, CHARâ?nbsp;
8-bit signed integer
System.SByte
short, short int, INT16, SHORT
16-bit signed integer
System.Int16
int, long, long int, INT32, LONG32, BOOLâ?nbsp;, INT
32-bit signed integer
System.Int32
__int64, INT64, LONGLONG
64-bit signed integer
System.Int64
unsigned char, UINT8, UCHARâ?nbsp;, BYTE
8-bit unsigned integer
System.Byte
unsigned short, UINT16, USHORT, WORD, ATOM, WCHARâ?nbsp;, __wchar_t
16-bit unsigned integer
System.UInt16
unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT
32-bit unsigned integer
System.UInt32
unsigned __int64, UINT64, DWORDLONG, ULONGLONG
64-bit unsigned integer
System.UInt64
float, FLOAT
Single-precision floating point
System.Single
double, long double, DOUBLE
Double-precision floating point
System.Double
â?nbsp;In Win32 this type is an integer with a specially assigned meaning; in contrast, the CLR provides a specific type devoted to this meaning.
Figure 3 GetShortPathName Declarations
// ** Documentation for Win32 GetShortPathName() API Function
// DWORD GetShortPathName(
// LPCTSTR lpszLongPath, // file for which to get short path
// LPTSTR lpszShortPath, // short path name (output)
// DWORD cchBuffer // size of output buffer
// );
[DllImport("Kernel32", CharSet = CharSet.Auto)]
static extern Int32 GetShortPathName(
String path, // input string
StringBuilder shortPath, // output string
Int32 shortPathLength); // StringBuilder.Capacity
]]>
1.__cdecl
2.__fastcall
3.__stdcall
~译器的命o行参数是/GzQ?span lang="EN-US">__stdcall?span lang="EN-US">PascalE序的缺省调用方式,大多?span lang="EN-US">Windows?span lang="EN-US">API也是__stdcall调用U定?span lang="EN-US"> __stdcall函数调用U定函数参C叛_左入栈,除非使用指针或引用类型的参数Q所有参数采用传值方式传递,p调用函数负责清除栈中的参数。对
?span lang="EN-US">C函数Q?span lang="EN-US">__stdcall的名UC饰方式是在函数名字前d下划U,在函数名字后d@和函数参数的大小Q例如:_functionname@number
2. C++~译器的函数名修饰规?span lang="EN-US">
X--void
D--char
E--unsigned char
F--short
H--int
I--unsigned int
J--long
K--unsigned longQ?span lang="EN-US">DWORDQ?span lang="EN-US">
M--float
N--double
_N--bool
U--struct
....
?针的方式有些特别Q用PA表示指针Q用PB表示constcd的指针。后面的代号表明指针cdQ如果相同类型的指针q箋出现Q以“
{
......
private:
void Function(int);
protected:
void CopyInfo(const CTest &src);
public:
long DrawText(HDC hdc, long pos, const TCHAR* text,
RGBQUAD color, BYTE bUnder, bool bSet);
long InsightClass(DWORD dwClass) const;
......
};
extern "C" {
#endif
long MakeFun(long lFun);
#ifdef _cplusplus
}
#endif
]]>
我们知道Q在计算机内部,所有的信息最l都表示Z个二q制的字W串。每一个二q制位(bitQ有0?两种状态,因此八个二进制位可以组合出 256U状态,q被UCؓ一个字节(byteQ。也是_一个字节一共可以用来表C?56U不同的状态,每一个状态对应一个符P是256个符P? 0000000?1111111?/p>
上个世纪60q代Q美国制定了一套字W编码,对英语字W与二进制位之间的关p,做了l一规定。这被称为ASCII码,一直沿用至今?/p>
ASCII码一p定了128个字W的~码Q比如空?#8220;SPACE”?2Q二q制00100000Q,大写的字母A?5Q二q制01000001Q。这128个符P包括32个不能打印出来的控制W号Q,只占用了一个字节的后面7位,最前面?位统一规定??/p>
2、非ASCII~码
p?28个符L码就够了Q但是用来表C其他语aQ?28个符h不够的。比如,在法语中Q字母上Ҏ注音W号Q它无法用ASCII码表C? 于是Q一些欧z国家就军_Q利用字节中闲置的最高位~入新的W号。比如,法语中的é的编码ؓ130Q二q制10000010Q。这样一来,q些Ƨ洲国家? 用的~码体系Q可以表C最?56个符受?/p>
但是Q这里又出现了新的问题。不同的国家有不同的字母Q因此,哪怕它们都使用256个符L~码方式Q代表的字母却不一栗比如,130在法语编? 中代表了éQ在希伯来语~码中却代表了字母Gimel (ג)Q在俄语~码中又会代表另一个符受但是不怎样Q所有这些编码方式中Q??27表示的符h一LQ不一L只是128?55的这一Dc?/p>
至于亚洲国家的文字,使用的符号就更多了,汉字多?0万左叟뀂一个字节只能表C?56U符P肯定是不够的Q就必须使用多个字节表达一个符受? 比如Q简体中文常见的~码方式是GB2312Q用两个字节表CZ个汉字,所以理Z最多可以表C?56x256=65536个符受?/p>
中文~码的问题需要专文讨论,q篇W记不涉及。这里只指出Q虽焉是用多个字节表示一个符P但是GBcȝ汉字~码与后文的Unicode和UTF-8是毫无关pȝ?/p>
3.Unicode
正如上一节所_世界上存在着多种~码方式Q同一个二q制数字可以被解释成不同的符受因此,要想打开一个文本文Ӟ必ȝ道它的编码方式,否则用错误的~码方式解读Q就会出Cؕ码。ؓ什么电子邮件常常出Cؕ码?是因ؓ发信人和收信Z用的~码方式不一栗?/p>
可以惌Q如果有一U编码,世界上所有的W号都纳入其中。每一个符号都l予一个独一无二的编码,那么q问题׃消失。这是UnicodeQ就像它的名字都表示的,q是一U所有符L~码?/p>
Unicode当然是一个很大的集合Q现在的规模可以容纳100多万个符受每个符L~码都不一P比如QU+0639表示阿拉伯字母AinQU+0041表示p的大写字母AQU+4E25表示汉字“?#8221;。具体的W号对应表,可以查询unicode.orgQ或者专门的汉字对应?/a>?
4. Unicode的问?/strong>
需要注意的是,Unicode只是一个符号集Q它只规定了W号的二q制代码Q却没有规定q个二进制代码应该如何存储?/p>
比如Q汉?#8220;?#8221;的unicode是十六进制数4E25Q{换成二进制数?5位(100111000100101Q,也就是说q个W号的表C需?个字节。表C其他更大的W号Q可能需?个字节或?个字节,甚至更多?/p>
q里有两个严重的问题,W一个问题是Q如何才能区别unicode和asciiQ计机怎么知道三个字节表示一个符P而不是分别表CZ个符? 呢?W二个问题是Q我们已l知道,英文字母只用一个字节表C就够了Q如果unicodel一规定Q每个符L三个或四个字节表C,那么每个英文字母前都? 然有二到三个字节?Q这对于存储来说是极大的费Q文本文件的大小会因此大Z三倍,q是无法接受的?/p>
它们造成的结果是Q?Q出Cunicode的多U存储方式,也就是说有许多种不同的二q制格式Q可以用来表Cunicode?Qunicode在很长一D|间内无法推广Q直C联网的出现?/p>
5.UTF-8
互联|的普及Q强烈要求出CU统一的编码方式。UTF-8是在互联网上用最q的一Uunicode的实现方式。其他实现方式还包括UTF-16和UTF-32Q不q在互联|上基本不用?strong>重复一遍,q里的关pLQUTF-8是Unicode的实现方式之一?/strong>
UTF-8最大的一个特点,是它是一U变长的~码方式。它可以使用1~4个字节表CZ个符PҎ不同的符可变化字节长度?/p>
UTF-8的编码规则很单,只有二条Q?/p>
1Q对于单字节的符P字节的第一位设?Q后?位ؓq个W号的unicode码。因此对于英语字母,UTF-8~码和ASCII码是相同的?/p>
2Q对于n字节的符Pn>1Q,W一个字节的前n位都设ؓ1Q第n+1位设?Q后面字节的前两位一律设?0。剩下的没有提及的二q制位,全部个符Lunicode码?/p>
下表ȝ了编码规则,字母x表示可用~码的位?/p>
UnicodeW号范围 | UTF-8~码方式
(十六q制) | Q二q制Q?br> --------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
下面Q还是以汉字“?#8221;ZQ演C如何实现UTF-8~码?/p>
已知“?#8221;的unicode?E25Q?00111000100101Q,Ҏ上表Q可以发?E25处在W三行的范围内(0000 0800-0000 FFFFQ,因此“?#8221;的UTF-8~码需要三个字节,x式是“1110xxxx 10xxxxxx 10xxxxxx”。然后,?#8220;?#8221;的最后一个二q制位开始,依次从后向前填入格式中的xQ多出的位补0。这样就得到了,“?#8221;的UTF-8~码? “11100100 10111000 10100101”Q{换成十六q制是E4B8A5?/p>
6. Unicode与UTF-8之间的{?/strong>
通过上一节的例子Q可以看?#8220;?#8221;的Unicode码是4E25QUTF-8~码是E4B8A5Q两者是不一L。它们之间的转换可以通过E序实现?/p>
在Windowsq_下,有一个最单的转化ҎQ就是用内|的C本小E序Notepad.exe。打开文g后,点击“文g”菜单中的“另存?#8221;命oQ会跛_一个对话框Q在最底部有一?#8220;~码”的下拉条?/p>
里面有四个选项QANSIQUnicodeQUnicode big endian ?UTF-8?/p>
1QANSI是默认的~码方式。对于英文文件是ASCII~码Q对于简体中文文件是GB2312~码Q只针对Windows体中文版Q如果是J体中文版会采用Big5码)?/p>
2QUnicode~码指的是UCS-2~码方式Q即直接用两个字节存入字W的Unicode码。这个选项用的little endian格式?/p>
3QUnicode big endian~码与上一个选项相对应。我在下一节会解释little endian和big endian的涵义?/p>
4QUTF-8~码Q也是上一节谈到的~码Ҏ?/p>
选择?#8221;~码方式“后,点击”保存“按钮Q文件的~码方式q刻{换好了?/p>
7. Little endian和Big endian
上一节已l提刎ͼUnicode码可以采用UCS-2格式直接存储。以汉字”?#8220;ZQUnicode码是4E25Q需要用两个字节存储Q一个字? ?EQ另一个字节是25。存储的时候,4E在前Q?5在后Q就是Big endian方式Q?5在前Q?E在后Q就是Little endian方式?/p>
q两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,h国里爆发了内战,战争起因是h们争论,吃鸡蛋时I竟是从大头(Big- Endian)敲开q是从小?Little-Endian)敲开。ؓ了这件事情,前后爆发了六ơ战争,一个皇帝送了命,另一个皇帝丢了王位?/p>
因此Q第一个字节在前,是”大头方式“QBig endianQ,W二个字节在前就?#8221;头方式“QLittle endianQ?/p>
那么很自然的Q就会出C个问题:计算机怎么知道某一个文件到底采用哪一U方式编码?
Unicode规范中定义,每一个文件的最前面分别加入一个表C编码顺序的字符Q这个字W的名字叫做”零宽度非换行I格“QZERO WIDTH NO-BREAK SPACEQ,用FEFF表示。这正好是两个字节,而且FF比FE??/p>
如果一个文本文件的头两个字节是FE FFQ就表示该文仉用大头方式;如果头两个字节是FF FEQ就表示该文仉用小头方式?/p>
8. 实例
下面QD一个实例?/p>
打开”C?#8220;E序Notepad.exeQ新Z个文本文Ӟ内容是一?#8221;?#8220;字,依次采用ANSIQUnicodeQUnicode big endian ?UTF-8~码方式保存?/p>
然后Q用文本~辑软gUltraEdit?/a>?#8221;十六q制功能“Q观察该文g的内部编码方式?/p>
1QANSIQ文件的~码是两个字节“D1 CF”Q这正是“?#8221;的GB2312~码Q这也暗CGB2312是采用大头方式存储的?/p>
2QUnicodeQ编码是四个字节“FF FE 25 4E”Q其?#8220;FF FE”表明是小头方式存储,真正的编码是4E25?/p>
3QUnicode big endianQ编码是四个字节“FE FF 4E 25”Q其?#8220;FE FF”表明是大头方式存储?/p>
4QUTF-8Q编码是六个字节“EF BB BF E4 B8 A5”Q前三个字节“EF BB BF”表示q是UTF-8~码Q后三个“E4B8A5”是“?#8221;的具体编码,它的存储序与编码顺序是一致的?/p>
9. 延阅读 * The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character SetsQ关于字W集的最基本知识Q?/p>
* RFC3629QUTF-8, a transformation format of ISO 10646Q如果实现UTF-8的规定)
]]>
1. 如果拯两个BSTR
假如我们一个BSTRQ这个时候我希望复制一份BSTRQƈ丢弃之前的BSTR。通常我们会这么写Q?br>
{
CComBSTR bstrValue = sVal.data();
return bstrValue;
}
int main()
{
CComBSTR vValue = StringToBSTR("value");
return 0;
}
当然Q上面这个程序没有Q何问题,不会有Q何内存泄漏的可能。但是,你有没有上面代码里都发生了什么了Q?br>{案很简单,在函数StringToBSTR里面Q讲bstrValueq回的时候,会调用CComBSTR::Copy()Q在Copy()里面会调用
::SysAllocStringByteLen()
q个函数。而后在给vValue赋值的时候,?会调用一?br>::SysAllocString()
显而易见,开销很大?br>
那么Q我们将怎么改进q段代码了?
{
CComBSTR bstrValue = sVal.data();
return bstrValue.Detach();
}
int main()
{
CComBSTR vValue.Attach(StringToBSTR("value"));
return 0;
}
2. 如何使用CComSafeArray
?
用CComSafeArray的一个最大的好处Q就是它会自动释攑օ素是VARIANT和BSTR。也是_如果你的cd是VARIANTQ它会自动调
?:VariantClear()。如果你的类型是BSTRQ他会自动调?:SysStringFree()Ҏ。但是用它的时候,同样要小心?br>2.1 成对使用::SafeArrayAccessData()?:SafeArrayUnaccessData()
我们有时候会q样使用CComSafeArray的元素:
{
CComSafeArray<double> pSafeArray(3);
double * pVal = NULL;
::SafeArrayAccessData(pSafeArray.m_psa, (void**)&pVal);
//handle the elements through the pVal;
}
hr 0x8002000d 内存已锁定? HRESULT
如果你不仔细查,那么造成CComSafeArray没有释放?br>2.2 从CComSafeArray转ؓ成CComVariant
有时候我们用CComVariant包装SAFEARRY。你会这样写代码Q?br>
{
CComSafeArray<double> pSafeArray(3);
//fill the safearray
CComVariant v = pSafeArray.Detach();
}
{
CComSafeArray<double> pSafeArray(3);
//fill the safearray
CComVariant v = pSafeArray.m_psa;
}
使用上面三个wrapperc,实可以很方便我们编E,也能避免很多memory leak。但是,使用他们同样要小心,不然Q同样会造成性能损失Q或者,更糟p的Q内存泄漏?img src ="http://m.shnenglu.com/netboy/aggbug/73846.html" width = "1" height = "1" />
]]>
函数风格QFunction-styleQ强制{型用这L语法Q?
T(exdivssion) // cast exdivssion to be of type T
q两UŞ式之间没有本质上的不同,它纯_就是一个把括号攑֜哪的问题。我把这两种形式UCؓ旧风|old-styleQ的强制转型?br>
使用标准C++的类型{换符Qstatic_cast、dynamic_cast、reinterdivt_cast、和const_cast?br>1. static_cast
用法Qstatic_cast < type-id > ( exdivssion )
该运符把exdivssion转换为type-idcdQ但没有q行时类型检查来保证转换的安全性。它主要有如下几U用法:
①用于类层次l构中基cd子类之间指针或引用的转换?br> q行上行转换Q把子类的指针或引用转换成基c表C)是安全的Q?br> q行下行转换Q把基类指针或引用{换成子类表示Q时Q由于没有动态类型检查,所以是不安全的?br>②用于基本数据类型之间的转换Q如把int转换成charQ把int转换成enum。这U{换的安全性也要开发h员来保证?br>③把I指针{换成目标cd的空指针?br>④把Mcd的表辑ּ转换成voidcd?br>注意Qstatic_cast不能转换掉exdivssion的const、volitale、或者__unaligned属性?br>
2. dynamic_cast
用法Qdynamic_cast < type-id > ( exdivssion )
该运符把exdivssion转换成type-idcd的对象。Type-id必须是类的指针、类的引用或者void *Q?br>如果type-id是类指针cdQ那么exdivssion也必L一个指针,如果type-id是一个引用,那么exdivssion也必L一个引用?br>dynamic_cast主要用于cdơ间的上行{换和下行转换Q还可以用于cM间的交叉转换?br>在类层次间进行上行{换时Qdynamic_cast和static_cast的效果是一LQ?br>在进行下行{换时Qdynamic_casthcd查的功能Q比static_cast更安全?br>class B{
public:
int m_iNum;
virtual void foo();
};
class D:public B{
public:
char *m_szName[100];
};
void func(B *pb){
D *pd1 = static_cast(pb);
D *pd2 = dynamic_cast(pb);
}
在上面的代码D中Q如果pb指向一个Dcd的对象,pd1和pd2是一LQƈ且对q两个指针执行Dcd的Q何操作都是安全的Q?br>但是Q如果pb指向的是一个Bcd的对象,那么pd1是一个指向该对象的指针,对它q行Dcd的操作将是不安全的(如访问m_szNameQ,
而pd2是一个空指针?br>另外要注意:B要有虚函敎ͼ否则会编译出错;static_cast则没有这个限制?br>q是׃q行时类型检查需要运行时cd信息Q而这个信息存储在cȝ虚函数表Q?br>关于虚函数表的概念,详细可见Q中Q只有定义了虚函数的cL有虚函数表,
没有定义虚函数的cL没有虚函数表的?br>另外Qdynamic_castq支持交叉{换(cross castQ。如下代码所C?br>class A{
public:
int m_iNum;
virtual void f(){}
};
class B:public A{
};
class D:public A{
};
void foo(){
B *pb = new B;
pb->m_iNum = 100;
D *pd1 = static_cast(pb); //compile error
D *pd2 = dynamic_cast(pb); //pd2 is NULL
delete pb;
}
在函数foo中,使用static_castq行转换是不被允许的Q将在编译时出错Q而?dynamic_cast的{换则是允许的Q结果是I指针?br>
3. reindivter_cast
用法Qreindivter_cast (exdivssion)
type-id必须是一个指针、引用、算术类型、函数指针或者成员指针?br>它可以把一个指针{换成一个整敎ͼ也可以把一个整数{换成一个指针(先把一个指针{换成一个整敎ͼ
在把该整数{换成原类型的指针Q还可以得到原先的指针|?br>该运符的用法比较多?br>4. const_cast
用法Qconst_cast (exdivssion)
该运符用来修改cd的const或volatile属性。除了const 或volatile修饰之外Q?type_id和exdivssion的类型是一L?br>帔R指针被{化成非常量指针,q且仍然指向原来的对象;
帔R引用被{换成非常量引用,q且仍然指向原来的对象;帔R对象被{换成非常量对象?br>Voiatile和constc试。D如下一例:
class B{
public:
int m_iNum;
}
void foo(){
const B b1;
b1.m_iNum = 100; //comile error
B b2 = const_cast(b1);
b2. m_iNum = 200; //fine
}
上面的代码编译时会报错,因ؓb1是一个常量对象,不能对它q行改变Q?br>使用const_cast把它转换成一个常量对象,可以对它的数据成员L改变。注意:b1和b2是两个不同的对象?br>
== ===========================================
== dynamic_cast .vs. static_cast
== ===========================================
class B { ... };
class D : public B { ... };
void f(B* pb)
{
D* pd1 = dynamic_cast(pb);
D* pd2 = static_cast(pb);
}
If pb really points to an object of type D, then pd1 and pd2 will get the same value. They will also get the same value if pb == 0.
If pb points to an object of type B and not to the complete D class, then dynamic_cast will know enough to return zero. However, static_cast relies on the programmer’s assertion that pb points to an object of type D and simply returns a pointer to that supposed D object.
即dynamic_cast可用于承体pM的向下{型,卛_基类指针转换为派生类指针Q比static_cast更严格更安全。dynamic_cast在执行效率上比static_cast要差一些,但static_cast在更宽上范围内可以完成映,q种不加限制的映伴随着不安全性。static_cast覆盖的变换类型除cdơ的静态导航以外,q包括无映射变换、窄化变?q种变换会导致对象切?丢失信息)、用VOID*的强制变换、隐式类型变换等...
== ===========================================
== static_cast .vs. reinterdivt_cast
== ================================================
reinterdivt_cast是ؓ了映到一个完全不同类型的意思,q个关键词在我们需要把cd映射回原有类型时用到它。我们映到的类型仅仅是Z故弄玄虚和其他目的,q是所有映中最危险的?q句话是C++~程思想中的原话)
static_cast ?reinterdivt_cast 操作W修改了操作数类型。它们不是互逆的Q?static_cast 在编译时使用cd信息执行转换Q在转换执行必要的检?诸如指针界计算, cd?. 其操作数相对是安全的。另一斚wQreinterdivt_cast 仅仅是重新解释了l出的对象的比特模型而没有进行二q制转换Q?例子如下Q?br>
int n=9; double d=static_cast < double > (n);
上面的例子中, 我们一个变量从 int 转换?double?q些cd的二q制表达式是不同的?要将整数 9 转换?双精度整?9Qstatic_cast 需要正地为双_ֺ整数 d 补比特位。其l果?9.0。而reinterdivt_cast 的行为却不同:
int n=9;
double d=reinterdivt_cast (n);
q次, l果有所不同. 在进行计以? d 包含无用? q是因ؓ reinterdivt_cast 仅仅是复?n 的比特位?d, 没有q行必要的分?
因此, 你需要}慎?reinterdivt_cast.
]]>
int a=256;
printf("%d\n", sizeof(++a));
printf("%d\n", a);
那么到底打印的是多少呢?
应该??56Q我想第一个答案大家应该已l没有问题了Q但是ؓ什么在++a以后Qa的数D是没有发生变化呢Q因为sizeofQ)是一个运符Q在其中的所有的q算都是无效的,所?+aҎ没有运行?br>
上面的一个例子提醒我们,虽然sizeof看这单,但是其中q是有很多的问题值得讨论的,呵呵?br>
一、sizeof的概c
sizeof是C语言的一U单目操作符Q如C语言的其他操作符++?-{。它q不是函数。sizeof操作W以字节形式l出了其操作数的存储大小。操作数可以是一个表辑ּ或括在括号内的类型名。操作数的存储大由操作数的cd军_。
二、sizeof的用方法
1、用于数据类型
sizeof使用形式QsizeofQtypeQ
数据cd必须用括h住。如sizeofQintQ。
2、用于变量
sizeof使用形式QsizeofQvar_nameQ或sizeof var_name
变量名可以不用括h住。如sizeof (var_name)Qsizeof var_name{都是正Ş式。带括号的用法更普遍Q大多数E序员采用这UŞ式。
注意Qsizeof操作W不能用于函数类型,不完全类型或位字Dc不完全cd指具有未知存储大的数据cdQ如未知存储大小的数l类型、未知内容的l构或联合类型、voidcd{。
如sizeof(max)若此时变量max定义为int max(),sizeof(char_v) 若此时char_v定义为char char_v [MAX]且MAX未知Qsizeof(void)都不是正Ş式。
三、sizeof的结果
sizeof操作W的l果cd是size_tQ它在头文g
中typedef为unsigned intcd。该cd保证能容U_现所建立的最大对象的字节大小。
1、若操作数具有类型char、unsigned char或signed charQ其l果{于1。
ANSI C正式规定字符cd?字节。
2、int、unsigned int 、short int、unsigned short 、long int 、unsigned long ?nbsp; float、double、long doublecd的sizeof 在ANSI C中没有具体规定,大小依赖于实玎ͼ一般可能分别ؓ2???2?4????0。
3、当操作数是指针Ӟsizeof依赖于编译器。例如Microsoft C/C++7.0中,nearcL针字节数?Qfar、hugecL针字节数?。一般Unix的指针字节数?。
4、当操作数具有数l类型时Q其l果是数l的d节数。
5、联合类型操作数的sizeof是其最大字节成员的字节数。结构类型操作数的sizeof是这U类型对象的d节数Q包括Q何垫补在内。
让我们看如下l构Q
struct {char b; double x;} a;
在某些机器上sizeofQaQ?12Q而一般sizeofQcharQ? sizeofQdoubleQ?9。
q是因ؓ~译器在考虑寚w问题Ӟ在结构中插入IZ以控制各成员对象的地址寚w。如doublecd的结构成员x要放在被4整除的地址。
6、如果操作数是函C的数lŞ参或函数cd的Ş参,sizeofl出其指针的大小。
四、sizeof与其他操作符的关pR
sizeof的优先?U,??{?U运符优先U高。它可以与其他操作符一L成表辑ּ。如i*sizeofQintQ;其中i为intcd变量。
五、sizeof的主要用途
1、sizeof操作W的一个主要用途是与存储分配和I/Opȝ那样的例E进行通信。例如:
void *mallocQsize_t sizeQ?
size_t fread(void * ptr,size_t size,size_t nmemb,FILE * stream)。
2、sizeof的另一个的主要用途是计算数组中元素的个数。例如:
void * memsetQvoid * s,int c,sizeof(s)Q。
六、徏议
׃操作数的字节数在实现时可能出现变化,在涉及到操作数字节大时用sizeof来代替常量计?br>
=============================================================
本文主要包括二个部分Q第一部分重点介绍在VC中,怎么样采用sizeof来求l构的大,以及Ҏ出现的问题,q给决问题的ҎQ第二部分ȝ出VC中sizeof的主要用法?
1?sizeof应用在结构上的情?
L下面的结构:
struct MyStruct
{
double dda1;
char dda;
int type
};
对结构MyStruct采用sizeof会出C么结果呢Qsizeof(MyStruct)为多呢Q也怽会这hQ?
sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13
但是当在VC中测试上面结构的大小Ӟ你会发现sizeof(MyStruct)?6。你知道Z么在VC中会得出q样一个结果吗Q?
其实Q这是VC对变量存储的一个特D处理。ؓ了提高CPU的存储速度QVC对一些变量的起始地址做了"寚w"处理。在默认情况下,VC规定各成员变量存攄起始地址相对于结构的起始地址的偏U量必须变量的类型所占用的字节数的倍数。下面列出常用类型的寚w方式(vc6.0,32位系l??
cd
寚w方式Q变量存攄起始地址相对于结构的起始地址的偏U量Q?
Char
偏移量必Mؓsizeof(char)?的倍数
int
偏移量必Mؓsizeof(int)?的倍数
float
偏移量必Mؓsizeof(float)?的倍数
double
偏移量必Mؓsizeof(double)?的倍数
Short
偏移量必Mؓsizeof(short)?的倍数
各成员变量在存放的时候根据在l构中出现的序依次甌I间Q同时按照上面的寚w方式调整位置Q空~的字节VC会自动填充。同时VCZ保l构的大ؓl构的字节边界数Q即该结构中占用最大空间的cd所占用的字节数Q的倍数Q所以在为最后一个成员变量申L间后Q还会根据需要自动填充空~的字节?
下面用前面的例子来说明VC到底怎么h存放l构的?
struct MyStruct
{
double dda1;
char dda;
int type
}Q?
Z面的l构分配I间的时候,VCҎ成员变量出现的顺序和寚w方式Q先为第一个成员dda1分配I间Q其起始地址跟结构的起始地址相同Q刚好偏U量0刚好为sizeof(double)的倍数Q,该成员变量占用sizeof(double)=8个字节;接下来ؓW二个成员dda分配I间Q这时下一个可以分配的地址对于l构的v始地址的偏U量?Q是sizeof(char)的倍数Q所以把dda存放在偏U量?的地Ҏ_齐方式,该成员变量占?nbsp; sizeof(char)=1个字节;接下来ؓW三个成员type分配I间Q这时下一个可以分配的地址对于l构的v始地址的偏U量?Q不?sizeof (int)=4的倍数Qؓ了满_齐方式对偏移量的U束问题QVC自动填充3个字节(q三个字节没有放什么东西)Q这时下一个可以分配的地址对于l构的v始地址的偏U量?2Q刚好是sizeof(int)=4的倍数Q所以把type存放在偏U量?2的地方,该成员变量占用sizeof (int)=4个字节;q时整个l构的成员变量已l都分配了空_ȝ占用的空间大ؓQ?+1+3+4=16Q刚好ؓl构的字节边界数Q即l构中占用最大空间的cd所占用的字节数sizeof(double)=8Q的倍数Q所以没有空~的字节需要填充。所以整个结构的大小为:sizeof (MyStruct)=8+1+ 3+4=16Q其中有3个字节是VC自动填充的,没有放Q何有意义的东ѝ?
下面再D个例子,交换一下上面的MyStruct的成员变量的位置Q它变成下面的情况Q?
struct MyStruct
{
char dda;
double dda1;
int type
}Q?
q个l构占用的空间ؓ多大呢?在VC6.0环境下,可以得到sizeof(MyStruc)?4。结合上面提到的分配I间的一些原则,分析下VC怎么样ؓ上面的结构分配空间的。(单说明)
struct MyStruct
{
char dda;//偏移量ؓ0Q满_齐方式,dda占用1个字节;
double dda1;//下一个可用的地址的偏U量?Q不是sizeof(double)=8
//的倍数Q需要补?个字节才能偏移量变?Q满_?
//方式Q,因此VC自动填充7个字节,dda1存放在偏U量?
//的地址上,它占?个字节?
int typeQ?/下一个可用的地址的偏U量?6Q是sizeof(int)=4的?
//敎ͼ满int的对齐方式,所以不需要VC自动填充Qtype?
//攑֜偏移量ؓ16的地址上,它占?个字节?
}Q?/所有成员变量都分配了空_I间ȝ大小?+7+8+4=20Q不是结?
//的节边界敎ͼ即结构中占用最大空间的cd所占用的字节数sizeof
//(double)=8Q的倍数Q所以需要填?个字节,以满结构的大小?
//sizeof(double)=8的倍数?
所以该l构ȝ大小为:sizeof(MyStruc)?+7+8+4+4=24。其中ȝ?+4=11个字节是VC自动填充的,没有放Q何有意义的东ѝ?
VC对结构的存储的特D处理确实提高CPU存储变量的速度Q但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以讑֮变量的对齐方式?
#pragma pack(n)
VC 中提供了#pragma pack(n)来设定变量以n字节寚w方式。n字节寚w是说变量存攄起始地址的偏U量有两U情况:W一、如果n大于{于该变量所占用的字节数Q那么偏U量必须满默认的对齐方式,W二、如果n于该变量的cd所占用的字节数Q那么偏U量为n的倍数Q不用满默认的寚w方式。结构的d也有个U束条gQ分下面两种情况Q如果n大于所有成员变量类型所占用的字节数Q那么结构的d必Mؓ占用I间最大的变量占用的空间数的倍数Q?nbsp;
否则必须为n的倍数。下面D例说明其用法?
#pragma pack(push) //保存寚w状?
#pragma pack(4)//讑֮?字节寚w
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)//恢复寚w状?
以上l构的大ؓ16Q下面分析其存储情况Q首先ؓm1分配I间Q其偏移量ؓ0Q满x们自p定的寚w方式Q?字节寚wQ,m1占用1个字节。接着开始ؓ m4分配I间Q这时其偏移量ؓ1Q需要补?个字节,q样使偏U量满为n=4的倍数Q因为sizeof(double)大于nQ?m4占用8个字节。接着为m3分配I间Q这时其偏移量ؓ12Q满ؓ4的倍数Qm3占用4个字节。这时已lؓ所有成员变量分配了I间Q共分配?6个字节,满为n的倍数。如果把上面?pragma pack(4)改ؓ#pragma pack(16)Q那么我们可以得到结构的大小?4。(误者自己分析)
2?sizeof用法ȝ
在VC中,sizeof有着许多的用法,而且很容易引起一些错误。下面根据sizeof后面的参数对sizeof的用法做个ȝ?
AQ?nbsp; 参数为数据类型或者ؓ一般变量。例如sizeof(int),sizeof(long){等。这U情况要注意的是不同pȝpȝ或者不同编译器得到的结果可能是不同的。例如intcd?6位系l中?个字节,?2位系l中?个字节?
BQ?nbsp; 参数为数l或指针。下面D例说?
int a[50]; //sizeof(a)=4*50=200; 求数l所占的I间大小
int *a=new int[50];// sizeof(a)=4; aZ个指针,sizeof(a)是求指针
//的大??2位系l中Q当然是?个字节?
CQ?nbsp; 参数为结构或cRSizeof应用在类和结构的处理情况是相同的。但有两炚w要注意,W一、结构或者类中的静态成员不对结构或者类的大生媄响,因ؓ静态变量的存储位置与结构或者类的实例地址无关?
W二、没有成员变量的l构或类的大ؓ1Q因为必M证结构或cȝ每一
个实例在内存中都有唯一的地址?
下面举例说明Q?
Class Test{int a;static double c};//sizeof(Test)=4.
Test *s;//sizeof(s)=4,sZ个指针?
Class test1{ };//sizeof(test1)=1;
DQ?nbsp; 参数为其他。下面D例说明?
int func(char s[5]);
{
cout<<sizeof(s);//q里输?Q本来sZ个数l,但由于做为函
//数的参数在传递的时候系l处理ؓ一个指针,所
//以sizeof(s)实际上ؓ求指针的大小?
return 1;
}
sizeof(func("1234"))=4//因ؓfunc的返回类型ؓintQ所以相当于
//求sizeof(int).
以上为sizeof的基本用法,在实际的使用中要注意分析VC的分配变量的分配{略Q这L话可以避免一些错误?img src ="http://m.shnenglu.com/netboy/aggbug/73842.html" width = "1" height = "1" />
]]>