﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-唯C主义-随笔分类-C++</title><link>http://www.cppblog.com/netboy/category/3883.html</link><description /><language>zh-cn</language><lastBuildDate>Sun, 15 Feb 2009 06:54:35 GMT</lastBuildDate><pubDate>Sun, 15 Feb 2009 06:54:35 GMT</pubDate><ttl>60</ttl><item><title>在 C# 中通过 P/Invoke 调用Win32 DLL（转）</title><link>http://www.cppblog.com/netboy/archive/2009/02/15/73850.html</link><dc:creator>王勇良</dc:creator><author>王勇良</author><pubDate>Sun, 15 Feb 2009 02:50:00 GMT</pubDate><guid>http://www.cppblog.com/netboy/archive/2009/02/15/73850.html</guid><wfw:comment>http://www.cppblog.com/netboy/comments/73850.html</wfw:comment><comments>http://www.cppblog.com/netboy/archive/2009/02/15/73850.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/netboy/comments/commentRss/73850.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/netboy/services/trackbacks/73850.html</trackback:ping><description><![CDATA[<p>我 在自己最近的编程中注意到一个趋势，正是这个趋势才引出本月的专栏主题。最近，我在基于 Microsoft&#174; .NET
Framework 的应用程序中完成了大量的 Win32&#174; Interop。我并不是要说我的应用程序充满了自定义的 interop
代码，但有时我会在 .NET Framework 类库中碰到一些次要但又繁絮、不充分的内容，通过调用该 Windows&#174;
API，可以快速减少这样的麻烦。</p>
<p>因此我认为，.NET Framework 1.0 或 1.1 版类库中存在任何 Windows 所没有的功能限制都不足为怪。毕竟，32
位的 Windows（不管何种版本）是一个成熟的操作系统，为广大客户服务了十多年。相比之下，.NET Framework 却是一个新事物。</p>
<p>随着越来越多的开发人员将生产应用程序转到托管代码，开发人员更频繁地研究底层操作系统以图找出一些关键功能显得很自然 — 至少目前是如此。</p>
<p>值得庆幸的是，公共语言运行库 (CLR) 的 interop 功能（称为平台调用
(P/Invoke)）非常完善。在本专栏中，我将重点介绍如何实际使用 P/Invoke 来调用 Windows API 函数。当指 CLR 的
COM Interop 功能时，P/Invoke 当作名词使用；当指该功能的使用时，则将其当作动词使用。我并不打算直接介绍 COM
Interop，因为它比 P/Invoke 具有更好的可访问性，却更加复杂，这有点自相矛盾，这使得将 COM Interop
作为专栏主题来讨论不太简明扼要。</p>
<p>走进 P/Invoke</p>
<p>首先从考察一个简单的 P/Invoke 示例开始。让我们看一看如何调用 Win32 MessageBeep 函数，它的非托管声明如以下代码所示： </p>
<pre class="codeSample">BOOL MessageBeep(<br><br>  UINT uType   // beep type<br><br>);<br><br></pre>
<p>为了调用 MessageBeep，您需要在 C# 中将以下代码添加到一个类或结构定义中： </p>
<pre class="codeSample">[DllImport("User32.dll")]<br><br>static extern Boolean MessageBeep(UInt32 beepType);<br><br></pre>
<p>令人惊讶的是，只需要这段代码就可以使托管代码调用非托管的 MessageBeep
API。它不是一个方法调用，而是一个外部方法定义。（另外，它接近于一个来自 C 而 C#
允许的直接端口，因此以它为起点来介绍一些概念是有帮助的。）来自托管代码的可能调用如下所示： </p>
<pre class="codeSample">MessageBeep(0);<br><br></pre>
<p>请注意，现在 MessageBeep 方法被声明为 static。这是 P/Invoke 方法所要求的，因为在该 Windows API
中没有一致的实例概念。接下来，还要注意该方法被标记为 extern。这是提示编译器该方法是通过一个从 DLL
导出的函数实现的，因此不需要提供方法体。</p>
<p>说到缺少方法体，您是否注意到 MessageBeep 声明并没有包含一个方法体？与大多数算法由中间语言 (IL)
指令组成的托管方法不同，P/Invoke 方法只是元数据，实时 (JIT) 编译器在运行时通过它将托管代码与非托管的 DLL
函数连接起来。执行这种到非托管世界的连接所需的一个重要信息就是导出非托管方法的 DLL 的名称。这一信息是由 MessageBeep
方法声明之前的 DllImport 自定义属性提供的。在本例中，可以看到，MessageBeep 非托管 API 是由 Windows 中的
User32.dll 导出的。</p>
<p>到现在为止，关于调用 MessageBeep 就剩两个话题没有介绍，请回顾一下，调用的代码与以下所示代码片段非常相似： </p>
<pre class="codeSample">[DllImport("User32.dll")]<br><br>static extern Boolean MessageBeep(UInt32 beepType);<br><br></pre>
<p>最后这两个话题是与数据封送处理 (data marshaling) 和从托管代码到非托管函数的实际方法调用有关的话题。调用非托管
MessageBeep 函数可以由找到作用域内的extern MessageBeep
声明的任何托管代码执行。该调用类似于任何其他对静态方法的调用。它与其他任何托管方法调用的共同之处在于带来了数据封送处理的需要。 </p>
<p>C# 的规则之一是它的调用语法只能访问 CLR 数据类型，例如 System.UInt32 和 System.Boolean。C#
显然不识别 Windows API 中使用的基于 C 的数据类型（例如 UINT 和 BOOL），这些类型只是 C
语言类型的类型定义而已。所以当 Windows API 函数 MessageBeep 按以下方式编写时 </p>
<pre class="codeSample">BOOL MessageBeep( UINT uType )<br><br></pre>
<p>外部方法就必须使用 CLR 类型来定义，如您在前面的代码片段中所看到的。需要使用与基础 API 函数类型不同但与之兼容的 CLR 类型是 P/Invoke 较难使用的一个方面。因此，在本专栏的后面我将用完整的章节来介绍数据封送处理。 </p>
<p>样式</p>
<p>在 C# 中对 Windows API 进行 P/Invoke 调用是很简单的。但如果类库拒绝使您的应用程序发出嘟声，应该想方设法调用 Windows 使它进行这项工作，是吗？</p>
<p>是的。但是与选择的方法有关，而且关系甚大！通常，如果类库提供某种途径来实现您的意图，则最好使用 API 而不要直接调用非托管代码，因为
CLR 类型和 Win32 之间在样式上有很大的不同。我可以将关于这个问题的建议归结为一句话。当您进行 P/Invoke
时，不要使应用程序逻辑直接属于任何外部方法或其中的构件。如果您遵循这个小规则，从长远看经常会省去许多的麻烦。</p>
<p>图 1 中的代码显示了我所讨论的 MessageBeep 外部方法的最少附加代码。图 1
中并没有任何显著的变化，而只是对无包装的外部方法进行一些普通的改进，这可以使工作更加轻松一些。从顶部开始，您会注意到一个名为 Sound
的完整类型，它专用于 MessageBeep。如果我需要使用 Windows API 函数 PlaySound
来添加对播放波形的支持，则可以重用 Sound
类型。然而，我不会因公开单个公共静态方法的类型而生气。毕竟这只是应用程序代码而已。还应该注意到，Sound
是密封的，并定义了一个空的私有构造函数。这些只是一些细节，目的是使用户不会错误地从 Sound 派生类或者创建它的实例。</p>
<p>图 1 中的代码的下一个特征是，P/Invoke 出现位置的实际外部方法是 Sound 的私有方法。这个方法只是由公共
MessageBeep 方法间接公开，后者接受 BeepTypes
类型的参数。这个间接的额外层是一个很关键的细节，它提供了以下好处。首先，应该在类库中引入一个未来的 beep 托管方法，可以重复地通过公共
MessageBeep 方法来使用托管 API，而不必更改应用程序中的其余代码。</p>
<p>该包装方法的第二个好处是：当您进行 P/Invoke 调用时，您放弃了免受访问冲突和其他低级破坏的权利，这通常是由 CLR
提供的。缓冲方法可以保护您的应用程序的其余部分免受访问冲突及类似问题的影响（即使它不做任何事而只是传递参数）。该缓冲方法将由 P/Invoke
调用引入的任何潜在的错误本地化。</p>
<p>将私有外部方法隐藏在公共包装后面的第三同时也是最后的一个好处是，提供了向该方法添加一些最小的 CLR 样式的机会。例如，在图 1
中，我将 Windows API 函数返回的 Boolean 失败转换成更像 CLR 的异常。我还定义了一个名为 BeepTypes
的枚举类型，它的成员对应于同该 Windows API 一起使用的定义值。由于 C#
不支持定义，因此可以使用托管枚举类型来避免幻数向整个应用程序代码扩散。</p>
<p>包装方法的最后一个好处对于简单的 Windows API 函数（如
MessageBeep）诚然是微不足道的。但是当您开始调用更复杂的非托管函数时，您会发现，手动将 Windows API 样式转换成对 CLR
更加友好的方法所带来的好处会越来越多。越是打算在整个应用程序中重用 interop
功能，越是应该认真地考虑包装的设计。同时我认为，在非面向对象的静态包装方法中使用对 CLR 友好的参数也并非不可以。</p>
<p>DLL Import 属性</p>
<p>现在是更深入地进行探讨的时候了。在对托管代码进行 P/Invoke 调用时，DllImportAttribute
类型扮演着重要的角色。DllImportAttribute 的主要作用是给 CLR 指示哪个 DLL 导出您想要调用的函数。相关 DLL
的名称被作为一个构造函数参数传递给 DllImportAttribute。</p>
<p>如果您无法肯定哪个 DLL 定义了您要使用的 Windows API 函数，Platform SDK 文档将为您提供最好的帮助资源。在
Windows API 函数主题文字临近结尾的位置，SDK 文档指定了 C 应用程序要使用该函数必须链接的 .lib
文件。在几乎所有的情况下，该 .lib 文件具有与定义该函数的系统 DLL 文件相同的名称。例如，如果该函数需要 C 应用程序链接到
Kernel32.lib，则该函数就定义在 Kernel32.dll 中。您可以在 MessageBeep 中找到有关 MessageBeep
的 Platform SDK 文档主题。在该主题结尾处，您会注意到它指出库文件是 User32.lib；这表明 MessageBeep 是从
User32.dll 中导出的。</p>
<p>可选的 DllImportAttribute 属性</p>
<p>除了指出宿主 DLL 外，DllImportAttribute 还包含了一些可选属性，其中四个特别有趣：EntryPoint、CharSet、SetLastError 和 CallingConvention。</p>
<p>EntryPoint 在不希望外部托管方法具有与 DLL 导出相同的名称的情况下，可以设置该属性来指示导出的 DLL
函数的入口点名称。当您定义两个调用相同非托管函数的外部方法时，这特别有用。另外，在 Windows 中还可以通过它们的序号值绑定到导出的
DLL 函数。如果您需要这样做，则诸如&#8220;#1&#8221;或&#8220;#129&#8221;的 EntryPoint 值指示 DLL 中非托管函数的序号值而不是函数名。</p>
<p>CharSet 对于字符集，并非所有版本的 Windows 都是同样创建的。Windows 9x 系列产品缺少重要的 Unicode
支持，而 Windows NT 和 Windows CE 系列则一开始就使用 Unicode。在这些操作系统上运行的 CLR 将Unicode
用于 String 和 Char 数据的内部表示。但也不必担心 — 当调用 Windows 9x API 函数时，CLR
会自动进行必要的转换，将其从 Unicode转换为 ANSI。</p>
<p>如果 DLL 函数不以任何方式处理文本，则可以忽略 DllImportAttribute 的 CharSet 属性。然而，当 Char
或 String 数据是等式的一部分时，应该将 CharSet 属性设置为 CharSet.Auto。这样可以使 CLR 根据宿主 OS
使用适当的字符集。如果没有显式地设置 CharSet 属性，则其默认值为 CharSet.Ansi。这个默认值是有缺点的，因为对于在
Windows 2000、Windows XP 和 Windows NT&#174; 上进行的 interop
调用，它会消极地影响文本参数封送处理的性能。</p>
<p>应该显式地选择 CharSet.Ansi 或 CharSet.Unicode 的 CharSet 值而不是使用 CharSet.Auto
的唯一情况是：您显式地指定了一个导出函数，而该函数特定于这两种 Win32 OS 中的某一种。ReadDirectoryChangesW
API 函数就是这样的一个例子，它只存在于基于 Windows NT 的操作系统中，并且只支持 Unicode；在这种情况下，您应该显式地使用
CharSet.Unicode。</p>
<p>有时，Windows API 是否有字符集关系并不明显。一种决不会有错的确认方法是在 Platform SDK 中检查该函数的 C
语言头文件。（如果您无法肯定要看哪个头文件，则可以查看 Platform SDK 文档中列出的每个 API 函数的头文件。）如果您发现该
API 函数确实定义为一个映射到以 A 或 W 结尾的函数名的宏，则字符集与您尝试调用的函数有关系。Windows API 函数的一个例子是在
WinUser.h 中声明的 GetMessage API，您也许会惊讶地发现它有 A 和 W 两种版本。</p>
<p>SetLastError 错误处理非常重要，但在编程时经常被遗忘。当您进行 P/Invoke 调用时，也会面临其他的挑战 — 处理托管代码中 Windows API 错误处理和异常之间的区别。我可以给您一点建议。</p>
<p>如果您正在使用 P/Invoke 调用 Windows API 函数，而对于该函数，您使用 GetLastError
来查找扩展的错误信息，则应该在外部方法的 DllImportAttribute 中将 SetLastError 属性设置为
true。这适用于大多数外部方法。</p>
<p>这会导致 CLR 在每次调用外部方法之后缓存由 API 函数设置的错误。然后，在包装方法中，可以通过调用类库的
System.Runtime.InteropServices.Marshal 类型中定义的 Marshal.GetLastWin32Error
方法来获取缓存的错误值。我的建议是检查这些期望来自 API
函数的错误值，并为这些值引发一个可感知的异常。对于其他所有失败情况（包括根本就没意料到的失败情况），则引发在
System.ComponentModel 命名空间中定义的 Win32Exception，并将
Marshal.GetLastWin32Error 返回的值传递给它。如果您回头看一下图 1 中的代码，您会看到我在 extern
MessageBeep 方法的公共包装中就采用了这种方法。</p>
<p>CallingConvention 我将在此介绍的最后也可能是最不重要的一个 DllImportAttribute 属性是
CallingConvention。通过此属性，可以给 CLR
指示应该将哪种函数调用约定用于堆栈中的参数。CallingConvention.Winapi
的默认值是最好的选择，它在大多数情况下都可行。然而，如果该调用不起作用，则可以检查 Platform SDK 中的声明头文件，看看您调用的
API 函数是否是一个不符合调用约定标准的异常 API。</p>
<p>通常，本机函数（例如 Windows API 函数或 C- 运行时 DLL
函数）的调用约定描述了如何将参数推入线程堆栈或从线程堆栈中清除。大多数 Windows API
函数都是首先将函数的最后一个参数推入堆栈，然后由被调用的函数负责清理该堆栈。相反，许多 C-运行时 DLL
函数都被定义为按照方法参数在方法签名中出现的顺序将其推入堆栈，将堆栈清理工作交给调用者。</p>
<p>幸运的是，要让 P/Invoke 调用工作只需要让外围设备理解调用约定即可。通常，从默认值
CallingConvention.Winapi 开始是最好的选择。然后，在 C 运行时 DLL 函数和少数函数中，可能需要将约定更改为
CallingConvention.Cdecl。</p>
<p>数据封送处理</p>
<p>数据封送处理是 P/Invoke 具有挑战性的方面。当在托管和非托管代码之间传递数据时，CLR
遵循许多规则，很少有开发人员会经常遇到它们直至可将这些规则记住。除非您是一名类库开发人员，否则在通常情况下没有必要掌握其细节。为了最有效地在
CLR 上使用 P/Invoke，即使只偶尔需要 interop 的应用程序开发人员仍然应该理解数据封送处理的一些基础知识。</p>
<p>在本月专栏的剩余部分中，我将讨论简单数字和字符串数据的数据封送处理。我将从最基本的数字数据封送处理开始，然后介绍简单的指针封送处理和字符串封送处理。</p>
<p>封送数字和逻辑标量</p>
<p>Windows OS 大部分是用 C 编写的。因此，Windows API 所用到的数据类型要么是 C 类型，要么是通过类型定义或宏定义重新标记的 C 类型。让我们看看没有指针的数据封送处理。简单起见，首先重点讨论的是数字和布尔值。</p>
<p>当通过值向 Windows API 函数传递参数时，需要知道以下问题的答案： </p>
<table border="0" cellpadding="0" cellspacing="0">
    <tbody>
        <tr>
            <td class="listBullet" valign="top">&#8226;</td>
            <td class="listItem">
            <p>数据从根本上讲是整型的还是浮点型的？ </p>
            </td>
        </tr>
        <tr>
            <td class="listBullet" valign="top">&#8226;</td>
            <td class="listItem">
            <p>如果数据是整型的，则它是有符号的还是无符号的？ </p>
            </td>
        </tr>
        <tr>
            <td class="listBullet" valign="top">&#8226;</td>
            <td class="listItem">
            <p>如果数据是整型的，则它的位数是多少？ </p>
            </td>
        </tr>
        <tr>
            <td class="listBullet" valign="top">&#8226;</td>
            <td class="listItem">
            <p>如果数据是浮点型的，则它是单精度的还是双精度的？</p>
            </td>
        </tr>
    </tbody>
</table>
<p>有时答案很明显，但有时却不明显。Windows API 以各种方式重新定义了基本的 C 数据类型。图 2 列出了 C 和 Win32 的一些公共数据类型及其规范，以及一个具有匹配规范的公共语言运行库类型。 </p>
<p>通常，只要您选择一个其规范与该参数的 Win32 类型相匹配的 CLR 类型，您的代码就能够正常工作。不过也有一些特例。例如，在
Windows API 中定义的 BOOL 类型是一个有符号的 32 位整型。然而，BOOL 用于指示 Boolean 值 true 或
false。虽然您不用将 BOOL 参数作为 System.Int32 值封送，但是如果使用 System.Boolean
类型，就会获得更合适的映射。字符类型的映射类似于 BOOL，因为有一个特定的 CLR 类型 (System.Char) 指出字符的含义。</p>
<p>在了解这些信息之后，逐步介绍示例可能是有帮助的。依然采用 beep 主题作为例子，让我们来试一下 Kernel32.dll 低级
Beep，它会通过计算机的扬声器发生嘟声。这个方法的 Platform SDK 文档可以在 Beep 中找到。本机 API
按以下方式进行记录： </p>
<pre class="codeSample">BOOL Beep(<br><br>  DWORD dwFreq,      // Frequency<br><br>  DWORD dwDuration   // Duration in milliseconds<br><br>);<br><br></pre>
<p>在参数封送处理方面，您的工作是了解什么 CLR 数据类型与 Beep API 函数所使用的 DWORD 和 BOOL
数据类型相兼容。回顾一下图 2 中的图表，您将看到 DWORD 是一个 32 位的无符号整数值，如同 CLR 类型
System.UInt32。这意味着您可以使用 UInt32 值作为送往 Beep 的两个参数。BOOL
返回值是一个非常有趣的情况，因为该图表告诉我们，在 Win32 中，BOOL 是一个 32 位的有符号整数。因此，您可以使用
System.Int32 值作为来自 Beep 的返回值。然而，CLR 也定义了 System.Boolean 类型作为 Boolean
值的语义，所以应该使用它来替代。CLR 默认将 System.Boolean 值封送为 32 位的有符号整数。此处所显示的外部方法定义是用于
Beep 的结果 P/Invoke 方法： </p>
<pre class="codeSample">[DllImport("Kernel32.dll", SetLastError=true)]<br><br>static extern Boolean Beep(<br><br>   UInt32 frequency, UInt32 duration);<br><br></pre>
<p>指针参数</p>
<p>许多 Windows API
函数将指针作为它们的一个或多个参数。指针增加了封送数据的复杂性，因为它们增加了一个间接层。如果没有指针，您可以通过值在线程堆栈中传递数据。有了指
针，则可以通过引用传递数据，方法是将该数据的内存地址推入线程堆栈中。然后，函数通过内存地址间接访问数据。使用托管代码表示此附加间接层的方式有多
种。</p>
<p>在 C# 中，如果将方法参数定义为 ref 或 out，则数据通过引用而不是通过值传递。即使您没有使用 Interop
也是这样，但只是从一个托管方法调用到另一个托管方法。例如，如果通过 ref 传递 System.Int32
参数，则在线程堆栈中传递的是该数据的地址，而不是整数值本身。下面是一个定义为通过引用接收整数值的方法的示例： </p>
<pre class="codeSample">void FlipInt32(ref Int32 num){<br><br>   num = -num;<br><br>}<br><br></pre>
<p>这里，FlipInt32 方法获取一个 Int32 值的地址、访问数据、对它求反，然后将求反过的值赋给原始变量。在以下代码中，FlipInt32 方法会将调用程序的变量 x 的值从 10 更改为 -10： </p>
<pre class="codeSample">Int32 x = 10;<br><br>FlipInt32(ref x);<br><br></pre>
<p>在托管代码中可以重用这种能力，将指针传递给非托管代码。例如，FileEncryptionStatus API 函数以 32 位无符号位掩码的形式返回文件加密状态。该 API 按以下所示方式进行记录： </p>
<pre class="codeSample">BOOL FileEncryptionStatus(<br><br>  LPCTSTR lpFileName,  // file name<br><br>  LPDWORD lpStatus     // encryption status<br><br>);<br><br></pre>
<p>请注意，该函数并不使用它的返回值返回状态，而是返回一个 Boolean
值，指示调用是否成功。在成功的情况下，实际的状态值是通过第二个参数返回的。它的工作方式是调用程序向该函数传递指向一个 DWORD
变量的指针，而该 API 函数用状态值填充指向的内存位置。以下代码片段显示了一个调用非托管 FileEncryptionStatus
函数的可能外部方法定义： </p>
<pre class="codeSample">[DllImport("Advapi32.dll", CharSet=CharSet.Auto)]<br><br>static extern Boolean FileEncryptionStatus(String filename, <br><br>   out UInt32 status);<br><br></pre>
<p>该定义使用 out 关键字来为 UInt32 状态值指示 by-ref 参数。这里我也可以选择 ref
关键字，实际上在运行时会产生相同的机器码。out 关键字只是一个 by-ref 参数的规范，它向 C#
编译器指示所传递的数据只在被调用的函数外部传递。相反，如果使用 ref 关键字，则编译器会假定数据可以在被调用的函数的内部和外部传递。</p>
<p>托管代码中 out 和 ref 参数的另一个很好的方面是，地址作为 by-ref
参数传递的变量可以是线程堆栈中的一个本地变量、一个类或结构的元素，也可以是具有合适数据类型的数组中的一个元素引用。调用程序的这种灵活性使得
by-ref 参数成为封送缓冲区指针以及单数值指针的一个很好的起点。只有在我发现 ref 或 out
参数不符合我的需要的情况下，我才会考虑将指针封送为更复杂的 CLR 类型（例如类或数组对象）。</p>
<p>如果您不熟悉 C 语法或者调用 Windows API
函数，有时很难知道一个方法参数是否需要指针。一个常见的指示符是看参数类型是否是以字母 P 或 LP 开头的，例如 LPDWORD 或
PINT。在这两个例子中，LP 和 P 指示参数是一个指针，而它们指向的数据类型分别为 DWORD 或
INT。然而，在有些情况下，可以直接使用 C 语言语法中的星号 (*) 将 API 函数定义为指针。以下代码片段展示了这方面的示例： </p>
<pre class="codeSample">void TakesAPointer(DWORD* pNum);<br><br></pre>
<p>可以看到，上述函数的唯一一个参数是指向 DWORD 变量的指针。 </p>
<p>当通过 P/Invoke 封送指针时，ref 和 out 只用于托管代码中的值类型。当一个参数的 CLR 类型使用 struct
关键字定义时，可以认为该参数是一个值类型。Out 和 ref
用于封送指向这些数据类型的指针，因为通常值类型变量是对象或数据，而在托管代码中并没有对值类型的引用。相反，当封送引用类型对象时，并不需要
ref 和 out 关键字，因为变量已经是对象的引用了。</p>
<p>如果您对引用类型和值类型之间的差别不是很熟悉，请查阅 2000 年 12 月 发行的 MSDN&#174; Magazine，在 .NET
专栏的主题中可以找到更多信息。大多数 CLR 类型都是引用类型；然而，除了 System.String 和
System.Object，所有的基元类型（例如 System.Int32 和 System.Boolean）都是值类型。</p>
<p>封送不透明 (Opaque) 指针：一种特殊情况</p>
<p>有时在 Windows API 中，方法传递或返回的指针是不透明的，这意味着该指针值从技术角度讲是一个指针，但代码却不直接使用它。相反，代码将该指针返回给 Windows 以便随后进行重用。</p>
<p>一个非常常见的例子就是句柄的概念。在 Windows 中，内部数据结构（从文件到屏幕上的按钮）在应用程序代码中都表示为句柄。句柄其实就是不透明的指针或有着指针宽度的数值，应用程序用它来表示内部的 OS 构造。</p>
<p>少数情况下，API 函数也将不透明指针定义为 PVOID 或 LPVOID 类型。在 Windows API 的定义中，这些类型意思就是说该指针没有类型。</p>
<p>当一个不透明指针返回给您的应用程序（或者您的应用程序期望得到一个不透明指针）时，您应该将参数或返回值封送为 CLR 中的一种特殊类型 —
System.IntPtr。当您使用 IntPtr 类型时，通常不使用 out 或 ref 参数，因为 IntPtr
意为直接持有指针。不过，如果您将一个指针封送为一个指针，则对 IntPtr 使用 by-ref 参数是合适的。</p>
<p>在 CLR 类型系统中，System.IntPtr 类型有一个特殊的属性。不像系统中的其他基类型，IntPtr
并没有固定的大小。相反，它在运行时的大小是依底层操作系统的正常指针大小而定的。这意味着在 32 位的 Windows 中，IntPtr
变量的宽度是 32 位的，而在 64 位的 Windows 中，实时编译器编译的代码会将 IntPtr 值看作 64
位的值。当在托管代码和非托管代码之间封送不透明指针时，这种自动调节大小的特点十分有用。</p>
<p>请记住，任何返回或接受句柄的 API 函数其实操作的就是不透明指针。您的代码应该将 Windows 中的句柄封送成 System.IntPtr 值。</p>
<p>您可以在托管代码中将 IntPtr 值强制转换为 32 位或 64 位的整数值，或将后者强制转换为前者。然而，当使用 Windows
API
函数时，因为指针应是不透明的，所以除了存储和传递给外部方法外，不能将它们另做它用。这种&#8220;只限存储和传递&#8221;规则的两个特例是当您需要向外部方法传递
null 指针值和需要比较 IntPtr 值与 null 值的情况。为了做到这一点，您不能将零强制转换为 System.IntPtr，而应该在
IntPtr 类型上使用 Int32.Zero 静态公共字段，以便获得用于比较或赋值的 null 值。</p>
<p>封送文本</p>
<p>在编程时经常要对文本数据进行处理。文本为 interop 制造了一些麻烦，这有两个原因。首先，底层操作系统可能使用 Unicode
来表示字符串，也可能使用 ANSI。在极少数情况下，例如 MultiByteToWideChar API 函数的两个参数在字符集上是不一致的。</p>
<p>第二个原因是，当需要进行 P/Invoke 时，要处理文本还需要特别了解到 C 和 CLR 处理文本的方式是不同的。在 C
中，字符串实际上只是一个字符值数组，通常以 null 作为结束符。大多数 Windows API 函数是按照以下条件处理字符串的：对于
ANSI，将其作为字符值数组；对于 Unicode，将其作为宽字符值数组。</p>
<p>幸运的是，CLR 被设计得相当灵活，当封送文本时问题得以轻松解决，而不用在意 Windows API 函数期望从您的应用程序得到的是什么。这里是一些需要记住的主要考虑事项： </p>
<table border="0" cellpadding="0" cellspacing="0">
    <tbody>
        <tr>
            <td class="listBullet" valign="top">&#8226;</td>
            <td class="listItem">
            <p>是您的应用程序向 API 函数传递文本数据，还是 API 函数向您的应用程序返回字符串数据？或者二者兼有？ </p>
            </td>
        </tr>
        <tr>
            <td class="listBullet" valign="top">&#8226;</td>
            <td class="listItem">
            <p>您的外部方法应该使用什么托管类型？ </p>
            </td>
        </tr>
        <tr>
            <td class="listBullet" valign="top">&#8226;</td>
            <td class="listItem">
            <p>API 函数期望得到的是什么格式的非托管字符串？</p>
            </td>
        </tr>
    </tbody>
</table>
<p>我们首先解答最后一个问题。大多数 Windows API 函数都带有 LPTSTR 或 LPCTSTR
值。（从函数角度看）它们分别是可修改和不可修改的缓冲区，包含以 null
结束的字符数组。&#8220;C&#8221;代表常数，意味着使用该参数信息不会传递到函数外部。LPTSTR 中的&#8220;T&#8221;表明该参数可以是 Unicode 或
ANSI，取决于您选择的字符集和底层操作系统的字符集。因为在 Windows API 中大多数字符串参数都是这两种类型之一，所以只要在
DllImportAttribute 中选择 CharSet.Auto，CLR 就按默认的方式工作。 </p>
<p>然而，有些 API 函数或自定义的 DLL 函数采用不同的方式表示字符串。如果您要用到一个这样的函数，就可以采用
MarshalAsAttribute 修饰外部方法的字符串参数，并指明一种不同于默认 LPTSTR 的字符串格式。有关
MarshalAsAttribute 的更多信息，请参阅位于 MarshalAsAttribute Class 的 Platform SDK
文档主题。</p>
<p>现在让我们看一下字符串信息在您的代码和非托管函数之间传递的方向。有两种方式可以知道处理字符串时信息的传递方向。第一个也是最可靠的一个方法就
是首先理解参数的用途。例如，您正调用一个参数，它的名称类似 CreateMutex 并带有一个字符串，则可以想像该字符串信息是从应用程序向
API 函数传递的。同时，如果您调用 GetUserName，则该函数的名称表明字符串信息是从该函数向您的应用程序传递的。</p>
<p>除了这种比较合理的方法外，第二种查找信息传递方向的方式就是查找 API 参数类型中的字母&#8220;C&#8221;。例如，GetUserName API
函数的第一个参数被定义为 LPTSTR 类型，它代表一个指向 Unicode 或 ANSI 字符串缓冲区的长指针。但是 CreateMutex
的名称参数被类型化为 LTCTSTR。请注意，这里的类型定义是一样的，但增加一个字母&#8220;C&#8221;来表明缓冲区为常数，API 函数不能写入。</p>
<p>一旦明确了文本参数是只用作输入还是用作输入/输出，就可以确定使用哪种 CLR 类型作为参数类型。这里有一些规则。如果字符串参数只用作输入，则使用 System.String 类型。在托管代码中，字符串是不变的，适合用于不会被本机 API 函数更改的缓冲区。</p>
<p>如果字符串参数可以用作输入和/或输出，则使用 System.StringBuilder 类型。StringBuilder
类型是一个很有用的类库类型，它可以帮助您有效地构建字符串，也正好可以将缓冲区传递给本机函数，由本机函数为您填充字符串数据。一旦函数调用返回，您只
需要调用 StringBuilder 对象的 ToString 就可以得到一个 String 对象。</p>
<p>GetShortPathName API 函数能很好地用于显示什么时候使用 String、什么时候使用 StringBuilder，因为它只带有三个参数：一个输入字符串、一个输出字符串和一个指明输出缓冲区的字符长度的参数。</p>
<p>图 3 所示为加注释的非托管 GetShortPathName
函数文档，它同时指出了输入和输出字符串参数。它引出了托管的外部方法定义，也如图 3 所示。请注意第一个参数被封送为
System.String，因为它是一个只用作输入的参数。第二个参数代表一个输出缓冲区，它使用了 System.StringBuilder。</p>
<p>小结</p>
<p>本月专栏所介绍的 P/Invoke 功能足够调用 Windows 中的许多 API 函数。然而，如果您大量用到
interop，则会最终发现自己封送了很复杂的数据结构，甚至可能需要在托管代码中通过指针直接访问内存。实际上，本机代码中的 interop
可以是一个将细节和低级比特藏在里面的真正的潘多拉盒子。CLR、C# 和托管 C++ 提供了许多有用的功能；也许以后我会在本专栏介绍高级的
P/Invoke 话题。</p>
<p>同时，只要您觉得 .NET Framework 类库无法播放您的声音或者为您执行其他一些功能，您可以知道如何向原始而优秀的 Windows API 寻求一些帮助。</p>
<p><span class="clsFigs">Figure 1</span>&nbsp;<span class="clsCap">MessageBeep, Interop Done Well</span><br></p>
<pre class="clsCode">namespace Wintellect.Interop.Sound{<br><br>   using System;<br><br>   using System.Runtime.InteropServices;<br><br>   using System.ComponentModel;<br><br><br><br>   sealed class Sound{<br><br>      public static void MessageBeep(BeepTypes type){<br><br>         if(!MessageBeep((UInt32) type)){<br><br>            Int32 err = Marshal.GetLastWin32Error();<br><br>            throw new Win32Exception(err);<br><br>         }<br><br>      }<br><br><br><br>      [DllImport("User32.dll", SetLastError=true)]<br><br>      static extern Boolean MessageBeep(UInt32 beepType);<br><br><br><br>      private Sound(){}<br><br>   }<br><br><br><br>   enum BeepTypes{ <br><br>      Simple = -1,<br><br>      Ok                = 0x00000000,<br><br>      IconHand          = 0x00000010,<br><br>      IconQuestion      = 0x00000020,<br><br>      IconExclamation   = 0x00000030,<br><br>      IconAsterisk      = 0x00000040<br><br>   }<br><br>}<br><br></pre>
<pre class="clsCode"><span class="clsFigs">Figure 2</span>&nbsp;<span class="clsCap">Non-Pointer Data Types</span><br><br></pre>
<pre class="clsCode">
<table border="1" cellpadding="3" cellspacing="0" width="400">
    <thead>
        <tr valign="top">
            <th>Win32 Types</th><th>Specification</th><th>CLR Type</th>
        </tr>
    </thead>
    <tbody>
        <tr valign="top">
            <td>char, INT8, SBYTE, CHAR&#226;€&nbsp;</td>
            <td>8-bit signed integer</td>
            <td>System.SByte</td>
        </tr>
        <tr valign="top">
            <td>short, short int, INT16, SHORT</td>
            <td>16-bit signed integer</td>
            <td>System.Int16</td>
        </tr>
        <tr valign="top">
            <td>int, long, long int, INT32, LONG32, BOOL&#226;€&nbsp;, INT </td>
            <td>32-bit signed integer</td>
            <td>System.Int32</td>
        </tr>
        <tr valign="top">
            <td>__int64, INT64, LONGLONG</td>
            <td>64-bit signed integer</td>
            <td>System.Int64</td>
        </tr>
        <tr valign="top">
            <td>unsigned char, UINT8, UCHAR&#226;€&nbsp;, BYTE</td>
            <td>8-bit unsigned integer</td>
            <td>System.Byte</td>
        </tr>
        <tr valign="top">
            <td>unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR&#226;€&nbsp;, __wchar_t</td>
            <td>16-bit unsigned integer</td>
            <td>System.UInt16</td>
        </tr>
        <tr valign="top">
            <td>unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT</td>
            <td>32-bit unsigned integer</td>
            <td>System.UInt32</td>
        </tr>
        <tr valign="top">
            <td>unsigned __int64, UINT64, DWORDLONG, ULONGLONG</td>
            <td>64-bit unsigned integer</td>
            <td>System.UInt64</td>
        </tr>
        <tr valign="top">
            <td>float, FLOAT</td>
            <td>Single-precision floating point</td>
            <td>System.Single</td>
        </tr>
        <tr valign="top">
            <td>double, long double, DOUBLE</td>
            <td>Double-precision floating point</td>
            <td>System.Double</td>
        </tr>
        <tr valign="top">
            <td colspan="3">&#226;€&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.</td>
        </tr>
    </tbody>
</table>
</pre>
<pre class="clsCode"><hr></pre>
<pre class="clsCode"><span class="clsFigs"></span></pre>
<pre class="clsCode"><span class="clsFigs">Figure 3</span>&nbsp;<span class="clsCap">GetShortPathName Declarations</span><br></pre>
<pre class="clsCode">// ** Documentation for Win32 GetShortPathName() API Function<br><br>// DWORD GetShortPathName(<br><br>//   LPCTSTR lpszLongPath,      // file for which to get short path <br><br>//   LPTSTR lpszShortPath,      // short path name (output)<br><br>//   DWORD cchBuffer            // size of output buffer<br><br>// );<br><br><br><br>[DllImport("Kernel32", CharSet = CharSet.Auto)]<br><br>static extern Int32 GetShortPathName(<br><br>   String path,                // input string<br><br>   StringBuilder shortPath,    // output string<br><br>   Int32 shortPathLength);     // StringBuilder.Capacity<br></pre><img src ="http://www.cppblog.com/netboy/aggbug/73850.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/netboy/" target="_blank">王勇良</a> 2009-02-15 10:50 <a href="http://www.cppblog.com/netboy/archive/2009/02/15/73850.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>函数调用约定和堆栈 （转）</title><link>http://www.cppblog.com/netboy/archive/2009/02/15/73849.html</link><dc:creator>王勇良</dc:creator><author>王勇良</author><pubDate>Sun, 15 Feb 2009 02:43:00 GMT</pubDate><guid>http://www.cppblog.com/netboy/archive/2009/02/15/73849.html</guid><wfw:comment>http://www.cppblog.com/netboy/comments/73849.html</wfw:comment><comments>http://www.cppblog.com/netboy/archive/2009/02/15/73849.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/netboy/comments/commentRss/73849.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/netboy/services/trackbacks/73849.html</trackback:ping><description><![CDATA[<meta http-equiv="Content-Type" content="text/html; charset="utf-8"">
<meta name="ProgId" content="Word.Document">
<meta name="Generator" content="Microsoft Word 11">
<meta name="Originator" content="Microsoft Word 11">
<link rel="File-List" href="file:///C:%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml"><o:smarttagtype namespaceuri="urn:schemas-microsoft-com:office:smarttags" name="chmetcnv"></o:smarttagtype><!--[if gte mso 9]><xml>
<w:worddocument>
<w:view>Normal</w:view>
<w:zoom>0</w:zoom>
<w:punctuationkerning/>
<w:drawinggridverticalspacing>7.8 磅</w:drawinggridverticalspacing>
<w:displayhorizontaldrawinggridevery>0</w:displayhorizontaldrawinggridevery>
<w:displayverticaldrawinggridevery>2</w:displayverticaldrawinggridevery>
<w:validateagainstschemas/>
<w:saveifxmlinvalid>false</w:saveifxmlinvalid>
<w:ignoremixedcontent>false</w:ignoremixedcontent>
<w:alwaysshowplaceholdertext>false</w:alwaysshowplaceholdertext>
<w:compatibility>
<w:spaceforul/>
<w:balancesinglebytedoublebytewidth/>
<w:donotleavebackslashalone/>
<w:ultrailspace/>
<w:donotexpandshiftreturn/>
<w:adjustlineheightintable/>
<w:breakwrappedtables/>
<w:snaptogridincell/>
<w:wraptextwithpunct/>
<w:useasianbreakrules/>
<w:dontgrowautofit/>
<w:usefelayout/>
</w:compatibility>
<w:browserlevel>MicrosoftInternetExplorer4</w:browserlevel>
</w:worddocument>
</xml><![endif]--><!--[if gte mso 9]><xml>
<w:latentstyles deflockedstate="false" latentstylecount="156">
</w:latentstyles>
</xml><![endif]--><!--[if !mso]><object
classid="clsid:38481807-ca0e-42d2-bf39-b33af135cc4d" id="ieooui"></object>
<style>
st1\:*{behavior:url(#ieooui) }
</style>
<![endif]--><style>
<!--
/* Font Definitions */
@font-face
{font-family:宋体;
panose-1:2 1 6 0 3 1 1 1 1 1;
mso-font-alt:SimSun;
mso-font-charset:134;
mso-generic-font-family:auto;
mso-font-pitch:variable;
mso-font-signature:3 135135232 16 0 262145 0;}
@font-face
{font-family:"\@宋体";
panose-1:2 1 6 0 3 1 1 1 1 1;
mso-font-charset:134;
mso-generic-font-family:auto;
mso-font-pitch:variable;
mso-font-signature:3 135135232 16 0 262145 0;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{mso-style-parent:"";
margin:0cm;
margin-bottom:.0001pt;
text-align:justify;
text-justify:inter-ideograph;
mso-pagination:none;
font-size:10.5pt;
mso-bidi-font-size:12.0pt;
font-family:"Times New Roman";
mso-fareast-font-family:宋体;
mso-font-kerning:1.0pt;}
/* Page Definitions */
@page
{mso-page-border-surround-header:no;
mso-page-border-surround-footer:no;}
@page Section1
{size:595.3pt 841.9pt;
margin:72.0pt 90.0pt 72.0pt 90.0pt;
mso-header-margin:42.55pt;
mso-footer-margin:49.6pt;
mso-paper-source:0;
layout-grid:15.6pt;}
div.Section1
{page:Section1;}
-->
</style><!--[if gte mso 10]>
<style>
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:普通表格;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.0pt;
font-family:"Times New Roman";
mso-fareast-font-family:"Times New Roman";
mso-ansi-language:#0400;
mso-fareast-language:#0400;
mso-bidi-language:#0400;}
</style>
<![endif]-->
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp; <span>&nbsp;&nbsp;</span></span><span style="font-size: 12pt; font-family: 宋体;">使用<span lang="EN-US">C/C++</span>语言开发软件的程序员经常碰到这样的问题：有时候是程序编译没有问题，但是链接的时候总是报告函数不存在（经典的<span lang="EN-US">LNK 2001</span>错误），有时候是程序编译和链接都没有错误，但是只要调用库中的函数就会出现堆栈异常。这些现象通常是出现在<span lang="EN-US">C</span>和<span lang="EN-US">C++</span>的代码混合使用的情况下或 在<span lang="EN-US">C++</span>程序中使用第三方的库的情况下（不是用<span lang="EN-US">C++</span>语言开发的），其实这都是函数调用约定（<span lang="EN-US">Calling Convention</span>）和函数名修饰（<span lang="EN-US">Decorated Name</span>）规则惹的祸。函数调用方式决定了函数参数入栈的顺序，是由调用者函数还是被调用函数负责清除栈中的参数等问题，而函数名修饰规则决定了编译器使
用何种名字修饰方式来区分不同的函数，如果函数之间的调用约定不匹配或者名字修饰不匹配就会产生以上的问题。本文分别对<span lang="EN-US">C</span>和<span lang="EN-US">C++</span>这两种编程语言的函数调 用约定和函数名修饰规则进行详细的解释，比较了它们的异同之处，并举例说明了以上问题出现的原因。
<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><strong><span style="font-size: 12pt; font-family: 宋体;">函数调用约定（<span lang="EN-US">Calling Convention</span>）</span></strong><strong><span style="font-size: 13.5pt; font-family: 宋体;" lang="EN-US"><o:p></o:p></span></strong></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;&nbsp;&nbsp; </span><span style="font-size: 12pt; font-family: 宋体;">函数调用约定不仅决定了发生函数调用时函数参数的入栈顺序，还决定了是由调用者函数还是被调用函数负责清除栈中的参数，还原堆栈。函数调用约定有很多方
式，除了常见的<span lang="EN-US">__cdecl</span>，<span lang="EN-US">__fastcall</span>和<span lang="EN-US">__stdcall</span>之外，<span lang="EN-US">C++</span>的编译器还支持<span lang="EN-US">thiscall</span>方式，不少<span lang="EN-US">C/C++</span>编译器还支持<span lang="EN-US"> naked call</span>方式。这么多函数调用约定常常令许多程序员很迷惑，到底它们是怎么回事，都是在什么情况下使用呢？下面就分别介绍这几种函数调用约定。<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US"><br>
1.__cdecl<o:p></o:p></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;&nbsp;&nbsp; </span><span style="font-size: 12pt; font-family: 宋体;">编译器的命令行参数是<span lang="EN-US">/Gd</span>。<span lang="EN-US">__cdecl</span>方式是<span lang="EN-US">C/C++</span>编译器默认的函数调用约定，所有非<span lang="EN-US">C++</span>成员函数和那些没有用<span lang="EN-US">__stdcall</span>或<span lang="EN-US"> __fastcall</span>声明的函数都默认是<span lang="EN-US">__cdecl</span>方式，它使用<span lang="EN-US">C</span>函数调用方式，函数参数按照从右向左的顺序入栈，函数调用者负责清除栈中的参数，由 于每次函数调用都要由编译器产生清除（还原）堆栈的代码，所以使用<span lang="EN-US">__cdecl</span>方式编译的程序比使用<span lang="EN-US">__stdcall</span>方式编译的程序要大很多，但是<span lang="EN-US"> __cdecl</span>调用方式是由函数调用者负责清除栈中的函数参数，所以这种方式支持可变参数，比如<span lang="EN-US">printf</span>和<span lang="EN-US">windows</span>的<span lang="EN-US">API wsprintf</span>就是<span lang="EN-US">__cdecl</span>调用方式。对于<span lang="EN-US">C</span>函数，<span lang="EN-US">__cdecl</span>方式的名字修饰约定是在函数名称前添加一个下划线；对于<span lang="EN-US">C++</span>函数，除非特别使 用<span lang="EN-US">extern "C"</span>，<span lang="EN-US">C++</span>函数使用不同的名字修饰方式。<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US"><br>
2.__fastcall<o:p></o:p></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;&nbsp;&nbsp; </span><span style="font-size: 12pt; font-family: 宋体;">编译器的命令行参数是<span lang="EN-US">/Gr</span>。<span lang="EN-US">__fastcall</span>函数调用约定在可能的情况下使用寄存器传递参数，通常是前两个<span lang="EN-US">
DWORD</span>类型的参数或较小的参数使用<span lang="EN-US">ECX</span>和<span lang="EN-US">EDX</span>寄存器传递，其余参数按照从右向左的顺序入栈，被调用函数在返回之前负责清除栈中的参数。编译器使用
两个<span lang="EN-US">@</span>修饰函数名字，后跟十进制数表示的函数参数列表大小，例如：<span lang="EN-US">@function_name@number</span>。需要注意的是<span lang="EN-US">__fastcall</span>函数调 用约定在不同的编译器上可能有不同的实现，比如<span lang="EN-US">16</span>位的编译器和<span lang="EN-US">32</span>位的编译器，另外，在使用内嵌汇编代码时，还要注意不能和编译器使用的寄存器有冲突。<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US"><br>
3.__stdcall<br>
&nbsp;<br>
&nbsp;&nbsp;&nbsp; </span><span style="font-size: 12pt; font-family: 宋体;">编译器的命令行参数是<span lang="EN-US">/Gz</span>，<span lang="EN-US">__stdcall</span>是<span lang="EN-US">Pascal</span>程序的缺省调用方式，大多数<span lang="EN-US">Windows</span>的<span lang="EN-US">API</span>也是<span lang="EN-US">__stdcall</span>调用约定。<span lang="EN-US"> __stdcall</span>函数调用约定将函数参数从右向左入栈，除非使用指针或引用类型的参数，所有参数采用传值方式传递，由被调用函数负责清除栈中的参数。对
于<span lang="EN-US">C</span>函数，<span lang="EN-US">__stdcall</span>的名称修饰方式是在函数名字前添加下划线，在函数名字后添加<span lang="EN-US">@</span>和函数参数的大小，例如：<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#95;&#102;&#117;&#110;&#99;&#116;&#105;&#111;&#110;&#110;&#97;&#109;&#101;&#64;&#110;&#117;&#109;&#98;&#101;&#114;">_functionname@number</a><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">4.thiscall<o:p></o:p></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;&nbsp;&nbsp; thiscall</span><span style="font-size: 12pt; font-family: 宋体;">只用在<span lang="EN-US">C++</span>成员函数的调用，函数参数按照从右向左的顺序入栈，类实例的<span lang="EN-US">this</span>指针通过<span lang="EN-US">ECX</span>寄存器传递。需要注意的是<span lang="EN-US">thiscall</span>不是<span lang="EN-US">C++</span>的关键字，不能使用<span lang="EN-US">thiscall</span>声明函数，它只能由编译器使用。<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">5.naked call<o:p></o:p></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;&nbsp;&nbsp; </span><span style="font-size: 12pt; font-family: 宋体;">采用前面几种函数调用约定的函数，编译器会在必要的时候自动在函数开始添加保存<span lang="EN-US">ESI</span>，<span lang="EN-US">EDI</span>，<span lang="EN-US">EBX</span>，<span lang="EN-US">EBP</span>寄存器的代码，在退出函数时恢复这些寄存器 的内容，使用<span lang="EN-US">naked call</span>方式声明的函数不会添加这样的代码，这也就是为什么称其为<span lang="EN-US">naked</span>的原因吧。<span lang="EN-US">naked&nbsp; call</span>不是类型修饰符，故必须和<span lang="EN-US">_declspec</span>共同使用。<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;&nbsp;&nbsp; VC</span><span style="font-size: 12pt; font-family: 宋体;">的编译环境默认是使用<span lang="EN-US">__cdecl</span>调用约定，也可以在编译环境的<span lang="EN-US">Project Setting...</span>菜单－》<span lang="EN-US">C/C++ </span>＝》<span lang="EN-US">Code&nbsp; Generation</span>项选择设置函数调用约定。也可以直接在函数声明前添加关键字<span lang="EN-US">__stdcall</span>、<span lang="EN-US">__cdecl</span>或<span lang="EN-US">__fastcall</span>等单独确定函
数的调用方式。在<span lang="EN-US">Windows</span>系统上开发软件常用到<span lang="EN-US">WINAPI</span>宏，它可以根据编译设置翻译成适当的函数调用约定，在<span lang="EN-US">WIN32</span>中，它被定义为<span lang="EN-US"> __stdcall</span>。<span lang="EN-US">&nbsp;<o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;<o:p></o:p></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 13.5pt; font-family: 宋体;">函数名字修饰（<span lang="EN-US">Decorated Name</span>）方式</span><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US"><o:p></o:p></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;&nbsp;&nbsp; </span><span style="font-size: 12pt; font-family: 宋体;">函数的名字修饰（<span lang="EN-US">Decorated
Name</span>）就是编译器在编译期间创建的一个字符串，用来指明函数的定义或原型。<span lang="EN-US">LINK</span>程序或其他工具有时需要指定函数的名字修饰来定位函数的正确位置。
多数情况下程序员并不需要知道函数的名字修饰，<span lang="EN-US">LINK</span>程序或其他工具会自动区分他们。当然，在某些情况下需要指定函数的名字修饰，例如在<span lang="EN-US">C++</span>程序中， 为了让<span lang="EN-US">LINK</span>程序或其他工具能够匹配到正确的函数名字，就必须为重载函数和一些特殊的函数（如构造函数和析构函数）指定名字装饰。另一种需要指定函数的
名字修饰的情况是在汇编程序中调用<span lang="EN-US">C</span>或<span lang="EN-US">C++</span>的函数。如果函数名字，调用约定，返回值类型或函数参数有任何改变，原来的名字修饰就不再有效，必须指定新的
名字修饰。<span lang="EN-US">C</span>和<span lang="EN-US">C++</span>程序的函数在内部使用不同的名字修饰方式，下面将分别介绍这两种方式。<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">1. C</span><span style="font-size: 12pt; font-family: 宋体;">编译器的函数名修饰规则<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;&nbsp;&nbsp; </span><span style="font-size: 12pt; font-family: 宋体;">对于<span lang="EN-US">__stdcall</span>调用约定，编译器和链接器会在输出函数名前加上一个下划线前缀，函数名后面加上一个<span lang="EN-US">&#8220;@&#8221;</span>符号和其参数的字节数，例如<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#95;&#102;&#117;&#110;&#99;&#116;&#105;&#111;&#110;&#110;&#97;&#109;&#101;&#64;&#110;&#117;&#109;&#98;&#101;&#114;">_functionname@number</a></span>。<span lang="EN-US">__cdecl</span>调用约定仅在输出函数名前加上一个下划线前缀，例如<span lang="EN-US">_functionname</span>。<span lang="EN-US">__fastcall</span>调用约定在输出函数名前加上一个<span lang="EN-US">&#8220;@&#8221;</span>符号，后面也是一个<span lang="EN-US">&#8220;@&#8221;</span>符号和其参数的字节数，例如<span lang="EN-US">@functionname@number</span>。<span lang="EN-US">&nbsp;&nbsp;&nbsp; <br>
&nbsp;<br>
2. C++</span>编译器的函数名修饰规则<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;&nbsp;&nbsp; C++</span><span style="font-size: 12pt; font-family: 宋体;">的函数名修饰规则有些复杂，但是信息更充分，通过分析修饰名不仅能够知道函数的调用方式，返回值类型，参数个数甚至参数类型。不管<span lang="EN-US"> __cdecl</span>，<span lang="EN-US">__fastcall</span>还是<span lang="EN-US">__stdcall</span>调用方式，函数修饰都是以一个<span lang="EN-US">&#8220;?&#8221;</span>开始，后面紧跟函数的名字，再后面是参数表的开始标识和
按照参数类型代号拼出的参数表。对于<span lang="EN-US">__stdcall</span>方式，参数表的开始标识是<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#64;&#64;&#89;&#71;">&#8220;@@YG</a>&#8221;</span>，对于<span lang="EN-US">__cdecl</span>方式则是<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#64;&#64;&#89;&#65;">&#8220;@@YA</a>&#8221;</span>，对于<span lang="EN-US">__fastcall</span>方式则是<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#64;&#64;&#89;&#73;">&#8220;@@YI</a>&#8221;</span>。参数表的拼写代号如下所示：<span lang="EN-US"><br>
X--void&nbsp;&nbsp;&nbsp; <br>
D--char&nbsp;&nbsp;&nbsp; <br>
E--unsigned char&nbsp;&nbsp;&nbsp; <br>
F--short&nbsp;&nbsp;&nbsp; <br>
H--int&nbsp;&nbsp;&nbsp; <br>
I--unsigned int&nbsp;&nbsp;&nbsp; <br>
J--long&nbsp;&nbsp;&nbsp; <br>
K--unsigned long</span>（<span lang="EN-US">DWORD</span>）<span lang="EN-US"> <br>
M--float&nbsp;&nbsp;&nbsp; <br>
N--double&nbsp;&nbsp;&nbsp; <br>
_N--bool<br>
U--struct<br>
....<br>
</span>指 针的方式有些特别，用<span lang="EN-US">PA</span>表示指针，用<span lang="EN-US">PB</span>表示<span lang="EN-US">const</span>类型的指针。后面的代号表明指针类型，如果相同类型的指针连续出现，以<span lang="EN-US">&#8220;<st1:chmetcnv unitname="&#8221;" sourcevalue="0" hasspace="False" negative="False" numbertype="1" tcsc="0" w:st="on">0&#8221;</st1:chmetcnv></span>代替，一个<span lang="EN-US">&#8220;<st1:chmetcnv unitname="&#8221;" sourcevalue="0" hasspace="False" negative="False" numbertype="1" tcsc="0" w:st="on">0&#8221;</st1:chmetcnv></span>代 表一次重复。<span lang="EN-US">U</span>表示结构类型，通常后跟结构体的类型名，用<span lang="EN-US">&#8220;@@&#8221;</span>表示结构类型名的结束。函数的返回值不作特殊处理，它的描述方式和函数参数一样，紧跟着 参数表的开始标志，也就是说，函数参数表的第一项实际上是表示函数的返回值类型。参数表后以<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#64;&#90;">&#8220;@Z</a>&#8221;</span>标识整个名字的结束，如果该函数无参数，则以<span lang="EN-US">&#8220;Z&#8221;</span>标识结束。下面举两个例子，假如有以下函数声明：<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">int Function1(char *var1,unsigned long);<br>
<!--[if !supportLineBreakNewLine]--><br>
<!--[endif]--><o:p></o:p></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;">其函数修饰名为<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#63;&#70;&#117;&#110;&#99;&#116;&#105;&#111;&#110;&#49;&#64;&#64;&#89;&#71;&#72;&#80;&#65;&#68;&#75;&#64;&#90;">&#8220;?Function1@@YGHPADK@Z</a>&#8221;</span>，而对于函数声明：<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">void Function2();<br>
<!--[if !supportLineBreakNewLine]--><br>
<!--[endif]--><o:p></o:p></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;">其函数修饰名则为<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#63;&#70;&#117;&#110;&#99;&#116;&#105;&#111;&#110;&#50;&#64;&#64;&#89;&#71;&#88;&#88;&#90;">&#8220;?Function2@@YGXXZ</a>&#8221; </span>。
<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;<o:p></o:p></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;&nbsp;&nbsp; </span><span style="font-size: 12pt; font-family: 宋体;">对于<span lang="EN-US">C++</span>的类成员函数（其调用方式是<span lang="EN-US">thiscall</span>），函数的名字修饰与非成员的<span lang="EN-US">C++</span>函数稍有不同，首先就是在函数名字和参数表之间插入以<span lang="EN-US">&#8220;@&#8221;</span>字符引导的类名；其次是参数表的开始标识不同，公有（<span lang="EN-US">public</span>）成员函数的标识是<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#64;&#64;&#81;&#65;&#69;">&#8220;@@QAE</a>&#8221;,</span>保护（<span lang="EN-US">protected</span>）成员函数的标识是<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#64;&#64;&#73;&#65;&#69;">&#8220;@@IAE</a>&#8221;,</span>私有（<span lang="EN-US">private</span>）成员函数的标识是<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#64;&#64;&#65;&#65;&#69;">&#8220;@@AAE</a>&#8221;</span>，如果函数声明使用了<span lang="EN-US">const</span>关键字，则相应的标识应分别为<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#64;&#64;&#81;&#66;&#69;">&#8220;@@QBE</a>&#8221;</span>，<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#64;&#64;&#73;&#66;&#69;">&#8220;@@IBE</a>&#8221;</span>和<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#64;&#64;&#65;&#66;&#69;">&#8220;@@ABE</a>&#8221;</span>。如果参数类型是类实例的引用，则使用<span lang="EN-US">&#8220;AAV<st1:chmetcnv unitname="&#8221;" sourcevalue="1" hasspace="False" negative="False" numbertype="1" tcsc="0" w:st="on">1&#8221;</st1:chmetcnv></span>，对于<span lang="EN-US">const</span>类型的引用，则使用<span lang="EN-US">&#8220;ABV<st1:chmetcnv unitname="&#8221;" sourcevalue="1" hasspace="False" negative="False" numbertype="1" tcsc="0" w:st="on">1&#8221;</st1:chmetcnv></span>。下面就以类<span lang="EN-US">CTest</span>为例说明<span lang="EN-US">C++</span>成员函数的名字修饰规则：<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">class CTest<br>
{<br>
......<br>
private:<br>
&nbsp;&nbsp;&nbsp;&nbsp;void Function(int);<br>
protected:<br>
&nbsp;&nbsp;&nbsp;&nbsp;void CopyInfo(const CTest &amp;src);<br>
public:<br>
&nbsp;&nbsp;&nbsp;&nbsp;long DrawText(HDC hdc, long pos, const TCHAR* text,
RGBQUAD color, BYTE bUnder, bool bSet);<br>
&nbsp;&nbsp;&nbsp;&nbsp;long InsightClass(DWORD dwClass) const;<br>
......<br>
};<br>
<!--[if !supportLineBreakNewLine]--><br>
<!--[endif]--><o:p></o:p></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;">对于成员函数<span lang="EN-US">Function</span>，其函数修饰名为<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#63;&#70;&#117;&#110;&#99;&#116;&#105;&#111;&#110;&#64;&#67;&#84;&#101;&#115;&#116;&#64;&#64;&#65;&#65;&#69;&#88;&#72;&#64;&#90;">&#8220;?Function@CTest@@AAEXH@Z</a>&#8221;</span>，字符串<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#64;&#64;&#65;&#65;&#69;">&#8220;@@AAE</a>&#8221;</span>表示这是一个私有函数。成员函数<span lang="EN-US">CopyInfo</span>只有一个参数，是对类<span lang="EN-US">CTest</span>的<span lang="EN-US">const</span>引用参数，其函数修饰名为<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#63;&#67;&#111;&#112;&#121;&#73;&#110;&#102;&#111;&#64;&#67;&#84;&#101;&#115;&#116;&#64;&#64;&#73;&#65;&#69;&#88;&#65;&#66;&#86;&#49;&#64;&#64;&#90;">&#8220;?CopyInfo@CTest@@IAEXABV1@@Z</a>&#8221;</span>。<span lang="EN-US"> DrawText</span>是一个比较复杂的函数声明，不仅有字符串参数，还有结构体参数和<span lang="EN-US">HDC</span>句柄参数，需要指出的是<span lang="EN-US">HDC</span>实际上是一个<span lang="EN-US">HDC__</span>结构类型的指 针，这个参数的表示就是<span lang="EN-US">&#8220;PAUHDC__@@&#8221;</span>，其完整的函数修饰名为<span lang="EN-US">&#8220;?DrawText@CTest@@QAEJPAUHDC__@@<a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#74;&#80;&#66;&#68;&#85;&#116;&#97;&#103;&#82;&#71;&#66;&#81;&#85;&#65;&#68;&#64;&#64;&#69;&#95;&#78;&#64;&#90;">JPBDUtagRG</a><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#66;&#81;&#85;&#65;&#68;&#64;&#64;&#69;&#95;&#78;&#64;&#90;">BQUAD@@E_N@Z</a>&#8221;</span>。<span lang="EN-US">InsightClass</span>是一个共有的<span lang="EN-US">const</span>函数，它的成员函数标识是<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#64;&#64;&#81;&#66;&#69;">&#8220;@@QBE</a>&#8221;</span>，完整的修饰名就是<span lang="EN-US">&#8220;?InsightCla<a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#63;&#73;&#110;&#115;&#105;&#103;&#104;&#116;&#67;&#108;&#97;&#115;&#115;&#64;&#67;&#84;&#101;&#115;&#116;&#64;&#64;&#81;&#66;&#69;&#74;&#75;&#64;&#90;">ss@CTest@@QBEJK@Z</a>&#8221;</span>。<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;">无论是<span lang="EN-US">C</span>函数名修饰方式还是<span lang="EN-US">C++</span>函数名修饰方式均不改变输出函数名中的字符大小写，这和<span lang="EN-US">PASCAL</span>调用约定不同，<span lang="EN-US">PASCAL</span>约定输出的函数名无任何修饰且全部大写。<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">3.</span><span style="font-size: 12pt; font-family: 宋体;">查看函数的名字修饰<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;&nbsp;&nbsp; </span><span style="font-size: 12pt; font-family: 宋体;">有两种方式可以检查你的程序中的函数的名字修饰：使用编译输出列表或使用<span lang="EN-US">Dumpbin</span>工具。使用<span lang="EN-US">/FAc</span>，<span lang="EN-US">/FAs</span>或<span lang="EN-US">/FAcs</span>命令行参数可以让编译器 输出函数或变量名字列表。使用<span lang="EN-US">dumpbin.exe
/SYMBOLS</span>命令也可以获得<span lang="EN-US">obj</span>文件或<span lang="EN-US">lib</span>文件中的函数或变量名字列表。此外，还可以使用<span lang="EN-US"> undname.exe </span>将修饰名转换为未修饰形式。<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;<o:p></o:p></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><strong><span style="font-size: 12pt; font-family: 宋体;">函数调用约定和名字修饰规则不匹配引起的常见问题</span></strong><strong><span style="font-size: 13.5pt; font-family: 宋体;" lang="EN-US"><o:p></o:p></span></strong></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;&nbsp;&nbsp; </span><span style="font-size: 12pt; font-family: 宋体;">函数调用时如果出现堆栈异常，十有八九是由于函数调用约定不匹配引起的。比如动态链接库<span lang="EN-US">a</span>有以下导出函数：<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">long MakeFun(long lFun);<br>
<!--[if !supportLineBreakNewLine]--><br>
<!--[endif]--><o:p></o:p></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;">动态库生成的时候采用的函数调用约定是<span lang="EN-US">__stdcall</span>，所以编译生成的<span lang="EN-US">a.dll</span>中函数<span lang="EN-US">MakeFun</span>的调用约 定是<span lang="EN-US">_stdcall</span>，也就是函数调用时参数从右向左入栈，函数返回时自己还原堆栈。现在某个程序模块<span lang="EN-US">b</span>要引用<span lang="EN-US">a</span>中的<span lang="EN-US">MakeFun</span>，<span lang="EN-US">b</span>和<span lang="EN-US">a</span>一样使用<span lang="EN-US"> C++</span>方式编译，只是<span lang="EN-US">b</span>模块的函数调用方式是<span lang="EN-US">__cdecl</span>，由于<span lang="EN-US">b</span>包含了<span lang="EN-US">a</span>提供的头文件中<span lang="EN-US">MakeFun</span>函数声明，所以<span lang="EN-US">MakeFun</span>在<span lang="EN-US">b</span>模块中被其它 调用<span lang="EN-US">MakeFun</span>的函数认为是<span lang="EN-US">__cdecl</span>调用方式，<span lang="EN-US">b</span>模块中的这些函数在调用完<span lang="EN-US">MakeFun</span>当然要帮着恢复堆栈啦，可是<span lang="EN-US">MakeFun</span>已经在结束 时自己恢复了堆栈，<span lang="EN-US">b</span>模块中的函数这样多此一举就引起了栈指针错误，从而引发堆栈异常。宏观上的现象就是函数调用没有问题（因为参数传递顺序是一样 的），<span lang="EN-US">MakeFun</span>也完成了自己的功能，只是函数返回后引发错误。解决的方法也很简单，只要保证两个模块的在编译时设置相同的函数调用约定就行了。
<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;<o:p></o:p></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;&nbsp;&nbsp; </span><span style="font-size: 12pt; font-family: 宋体;">在了解了函数调用约定和函数的名修饰规则之后，再来看在<span lang="EN-US">C++</span>程序中使用<span lang="EN-US">C</span>语言编译的库时经常出现的<span lang="EN-US">LNK 2001</span>错误就很简单了。还以上面例子的两个模块为例，这一次两个模块在编译的时候都采用<span lang="EN-US">__stdcall</span>调用约定，但是<span lang="EN-US">a.dll</span>使用<span lang="EN-US">C</span>语言的语法编 译的（<span lang="EN-US">C</span>语言方式），所以<span lang="EN-US">a.dll</span>的载入库<span lang="EN-US">a.lib</span>中<span lang="EN-US">MakeFun</span>函数的名字修饰就是<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#95;&#77;&#97;&#107;&#101;&#70;&#117;&#110;&#64;&#52;">&#8220;_MakeFun@4</a>&#8221;</span>。<span lang="EN-US">b</span>包含了<span lang="EN-US">a</span>提供的头文件中<span lang="EN-US">MakeFun</span>函数声明，但是由于<span lang="EN-US">b</span>采用的是<span lang="EN-US">C++</span>语言编译，所以<span lang="EN-US">MakeFun</span>在<span lang="EN-US">b</span>模块中被按照<span lang="EN-US">C++</span>的名字修饰规则命名为<span lang="EN-US">&#8220;?MakeFu<a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#63;&#77;&#97;&#107;&#101;&#70;&#117;&#110;&#64;&#64;&#89;&#71;&#74;&#74;&#64;&#90;">n@@YGJJ@Z</a>&#8221;</span>，编译过程相安无事，链接程序时<span lang="EN-US">c++</span>的链接器就到<span lang="EN-US">a.lib</span>中去找<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#63;&#77;&#97;&#107;&#101;&#70;&#117;&#110;&#64;&#64;&#89;&#71;&#74;&#74;&#64;&#90;">&#8220;?MakeFun@@YGJJ@Z</a>&#8221;</span>，但是<span lang="EN-US">a.lib</span>中只有<span lang="EN-US">&#8220;_Ma<a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#95;&#77;&#97;&#107;&#101;&#70;&#117;&#110;&#64;&#52;">keFun@4</a>&#8221;</span>，没有<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#63;&#77;&#97;&#107;&#101;&#70;&#117;&#110;&#64;&#64;&#89;&#71;&#74;&#74;&#64;&#90;">&#8220;?MakeFun@@YGJJ@Z</a>&#8221;</span>，于是链接器就报告：<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">error LNK2001: unresolved external symbol <a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#63;&#77;&#97;&#107;&#101;&#70;&#117;&#110;&#64;&#64;&#89;&#71;&#74;&#74;&#64;&#90;">?MakeFun@@YGJJ@Z</a><o:p></o:p></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;">解决的方法和简单，就是要让<span lang="EN-US">b</span>模块知道这个函数是<span lang="EN-US">C</span>语言编译的，<span lang="EN-US">extern
"C"</span>可以做到这一点。一个采用<span lang="EN-US">C</span>语言编译的库应该考虑到使用这个库的程序可能是<span lang="EN-US">C++</span>程序（使用<span lang="EN-US">C++</span>编译器），所以在设计头文件时应该注意这一点。通常应该这样声明头文件：<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">#ifdef _cplusplus<br>
extern "C" {<br>
#endif<br>
<br>
long MakeFun(long lFun);<br>
<br>
#ifdef _cplusplus<br>
}<br>
#endif<br>
<!--[if !supportLineBreakNewLine]--><br>
<!--[endif]--><o:p></o:p></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;">这样<span lang="EN-US">C++</span>的编译器就知道<span lang="EN-US">MakeFun</span>的修饰名是<span lang="EN-US"><a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#69;&#50;&#37;&#56;&#48;&#37;&#57;&#67;&#95;&#77;&#97;&#107;&#101;&#70;&#117;&#110;&#64;&#52;">&#8220;_MakeFun@4</a>&#8221;</span>，就不会有链接错误了。<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal" style="text-align: left;" align="left"><span style="font-size: 12pt; font-family: 宋体;" lang="EN-US">&nbsp;&nbsp;&nbsp; </span><span style="font-size: 12pt; font-family: 宋体;">许多人不明白，为什么我使用的编译器都是<span lang="EN-US">VC</span>的编译器还会产生<span lang="EN-US">&#8220;error LNK<st1:chmetcnv unitname="&#8221;" sourcevalue="2001" hasspace="False" negative="False" numbertype="1" tcsc="0" w:st="on">2001&#8221;</st1:chmetcnv></span>错误？其实，<span lang="EN-US">VC</span>的编译器会根据源文件的扩展名选择编译方式，如果文件的扩展名是<span lang="EN-US">&#8220;.C&#8221;</span>，编译器会采用<span lang="EN-US">C</span>的语法编译，如果扩展名是<span lang="EN-US"> &#8220;.cpp&#8221;</span>，编译器会使用<span lang="EN-US">C++</span>的语法编译程序，所以，最好的方法就是使用<span lang="EN-US">extern "C"</span>。<span lang="EN-US"><o:p></o:p></span></span></p>
<p class="MsoNormal"><span lang="EN-US"><o:p>&nbsp;</o:p></span></p><img src ="http://www.cppblog.com/netboy/aggbug/73849.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/netboy/" target="_blank">王勇良</a> 2009-02-15 10:43 <a href="http://www.cppblog.com/netboy/archive/2009/02/15/73849.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>字符编码笔记：ASCII，Unicode和UTF-8（转）</title><link>http://www.cppblog.com/netboy/archive/2009/02/15/73847.html</link><dc:creator>王勇良</dc:creator><author>王勇良</author><pubDate>Sun, 15 Feb 2009 02:38:00 GMT</pubDate><guid>http://www.cppblog.com/netboy/archive/2009/02/15/73847.html</guid><wfw:comment>http://www.cppblog.com/netboy/comments/73847.html</wfw:comment><comments>http://www.cppblog.com/netboy/archive/2009/02/15/73847.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/netboy/comments/commentRss/73847.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/netboy/services/trackbacks/73847.html</trackback:ping><description><![CDATA[<p><strong>1. ASCII码</strong></p>
<p>我们知道，在计算机内部，所有的信息最终都表示为一个二进制的字符串。每一个二进制位（bit）有0和1两种状态，因此八个二进制位就可以组合出
256种状态，这被称为一个字节（byte）。也就是说，一个字节一共可以用来表示256种不同的状态，每一个状态对应一个符号，就是256个符号，从
0000000到11111111。</p>
<p>上个世纪60年代，美国制定了一套字符编码，对英语字符与二进制位之间的关系，做了统一规定。这被称为ASCII码，一直沿用至今。</p>
<p>ASCII码一共规定了128个字符的编码，比如空格&#8220;SPACE&#8221;是32（二进制00100000），大写的字母A是65（二进制01000001）。这128个符号（包括32个不能打印出来的控制符号），只占用了一个字节的后面7位，最前面的1位统一规定为0。</p>
<p><strong>2、非ASCII编码</strong></p>
<p>英语用128个符号编码就够了，但是用来表示其他语言，128个符号是不够的。比如，在法语中，字母上方有注音符号，它就无法用ASCII码表示。
于是，一些欧洲国家就决定，利用字节中闲置的最高位编入新的符号。比如，法语中的&#233;的编码为130（二进制10000010）。这样一来，这些欧洲国家使
用的编码体系，可以表示最多256个符号。</p>
<p>但是，这里又出现了新的问题。不同的国家有不同的字母，因此，哪怕它们都使用256个符号的编码方式，代表的字母却不一样。比如，130在法语编码
中代表了&#233;，在希伯来语编码中却代表了字母Gimel
(ג)，在俄语编码中又会代表另一个符号。但是不管怎样，所有这些编码方式中，0—127表示的符号是一样的，不一样的只是128—255的这一段。</p>
<p>至于亚洲国家的文字，使用的符号就更多了，汉字就多达10万左右。一个字节只能表示256种符号，肯定是不够的，就必须使用多个字节表达一个符号。
比如，简体中文常见的编码方式是GB2312，使用两个字节表示一个汉字，所以理论上最多可以表示256x256=65536个符号。</p>
<p>中文编码的问题需要专文讨论，这篇笔记不涉及。这里只指出，虽然都是用多个字节表示一个符号，但是GB类的汉字编码与后文的Unicode和UTF-8是毫无关系的。</p>
<p><strong>3.Unicode</strong></p>
<p>正如上一节所说，世界上存在着多种编码方式，同一个二进制数字可以被解释成不同的符号。因此，要想打开一个文本文件，就必须知道它的编码方式，否则用错误的编码方式解读，就会出现乱码。为什么电子邮件常常出现乱码？就是因为发信人和收信人使用的编码方式不一样。</p>
<p>可以想象，如果有一种编码，将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码，那么乱码问题就会消失。这就是Unicode，就像它的名字都表示的，这是一种所有符号的编码。</p>
<p>Unicode当然是一个很大的集合，现在的规模可以容纳100多万个符号。每个符号的编码都不一样，比如，U+0639表示阿拉伯字母Ain，U+0041表示英语的大写字母A，U+4E25表示汉字&#8220;严&#8221;。具体的符号对应表，可以查询<a  href="http://www.unicode.org/" target="_blank">unicode.org</a>，或者专门的<a  href="http://www.chi2ko.com/tool/CJK.htm" target="_blank">汉字对应表</a>。 </p>
<p><strong>4. Unicode的问题</strong></p>
<p>需要注意的是，Unicode只是一个符号集，它只规定了符号的二进制代码，却没有规定这个二进制代码应该如何存储。</p>
<p>比如，汉字&#8220;严&#8221;的unicode是十六进制数4E25，转换成二进制数足足有15位（100111000100101），也就是说这个符号的表示至少需要2个字节。表示其他更大的符号，可能需要3个字节或者4个字节，甚至更多。</p>
<p>这里就有两个严重的问题，第一个问题是，如何才能区别unicode和ascii？计算机怎么知道三个字节表示一个符号，而不是分别表示三个符号
呢？第二个问题是，我们已经知道，英文字母只用一个字节表示就够了，如果unicode统一规定，每个符号用三个或四个字节表示，那么每个英文字母前都必
然有二到三个字节是0，这对于存储来说是极大的浪费，文本文件的大小会因此大出二三倍，这是无法接受的。</p>
<p>它们造成的结果是：1）出现了unicode的多种存储方式，也就是说有许多种不同的二进制格式，可以用来表示unicode。2）unicode在很长一段时间内无法推广，直到互联网的出现。</p>
<p><strong>5.UTF-8</strong></p>
<p>互联网的普及，强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种unicode的实现方式。其他实现方式还包括UTF-16和UTF-32，不过在互联网上基本不用。<strong>重复一遍，这里的关系是，UTF-8是Unicode的实现方式之一。</strong></p>
<p>UTF-8最大的一个特点，就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号，根据不同的符号而变化字节长度。</p>
<p>UTF-8的编码规则很简单，只有二条：</p>
<p>1）对于单字节的符号，字节的第一位设为0，后面7位为这个符号的unicode码。因此对于英语字母，UTF-8编码和ASCII码是相同的。</p>
<p>2）对于n字节的符号（n&gt;1），第一个字节的前n位都设为1，第n+1位设为0，后面字节的前两位一律设为10。剩下的没有提及的二进制位，全部为这个符号的unicode码。</p>
<p>下表总结了编码规则，字母x表示可用编码的位。</p>
<blockquote>
<div>
<p>   Unicode符号范围   |        UTF-8编码方式<br>
(十六进制)          |              （二进制）<br>
--------------------+---------------------------------------------<br>
0000 0000-0000 007F | 0xxxxxxx<br>
0000 0080-0000 07FF | 110xxxxx 10xxxxxx<br>
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx<br>
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx</p>
</div>
</blockquote>
<p>下面，还是以汉字&#8220;严&#8221;为例，演示如何实现UTF-8编码。</p>
<p>已知&#8220;严&#8221;的unicode是4E25（100111000100101），根据上表，可以发现4E25处在第三行的范围内（0000
0800-0000 FFFF），因此&#8220;严&#8221;的UTF-8编码需要三个字节，即格式是&#8220;1110xxxx 10xxxxxx
10xxxxxx&#8221;。然后，从&#8220;严&#8221;的最后一个二进制位开始，依次从后向前填入格式中的x，多出的位补0。这样就得到了，&#8220;严&#8221;的UTF-8编码是
&#8220;11100100 10111000 10100101&#8221;，转换成十六进制就是E4B8A5。</p>
<p><strong>6. Unicode与UTF-8之间的转换</strong></p>
<p>通过上一节的例子，可以看到&#8220;严&#8221;的Unicode码是4E25，UTF-8编码是E4B8A5，两者是不一样的。它们之间的转换可以通过程序实现。</p>
<p>在Windows平台下，有一个最简单的转化方法，就是使用内置的记事本小程序Notepad.exe。打开文件后，点击&#8220;文件&#8221;菜单中的&#8220;另存为&#8221;命令，会跳出一个对话框，在最底部有一个&#8220;编码&#8221;的下拉条。</p>
<p><a  href="http://www.ruanyifeng.com/blog/2007/10/bg2007102801.jpg"><img  src="http://www.ruanyifeng.com/blog/2007/10/bg2007102801-thumb.jpg" alt="bg2007102801.jpg" height="227" width="500"></a></p>
<p>里面有四个选项：ANSI，Unicode，Unicode big endian 和 UTF-8。</p>
<p>1）ANSI是默认的编码方式。对于英文文件是ASCII编码，对于简体中文文件是GB2312编码（只针对Windows简体中文版，如果是繁体中文版会采用Big5码）。</p>
<p>2）Unicode编码指的是UCS-2编码方式，即直接用两个字节存入字符的Unicode码。这个选项用的little endian格式。</p>
<p>3）Unicode big endian编码与上一个选项相对应。我在下一节会解释little endian和big endian的涵义。</p>
<p>4）UTF-8编码，也就是上一节谈到的编码方法。</p>
<p>选择完&#8221;编码方式&#8220;后，点击&#8221;保存&#8220;按钮，文件的编码方式就立刻转换好了。</p>
<p><strong>7. Little endian和Big endian</strong></p>
<p>上一节已经提到，Unicode码可以采用UCS-2格式直接存储。以汉字&#8221;严&#8220;为例，Unicode码是4E25，需要用两个字节存储，一个字节
是4E，另一个字节是25。存储的时候，4E在前，25在后，就是Big endian方式；25在前，4E在后，就是Little endian方式。</p>
<p>这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中，小人国里爆发了内战，战争起因是人们争论，吃鸡蛋时究竟是从大头(Big-
Endian)敲开还是从小头(Little-Endian)敲开。为了这件事情，前后爆发了六次战争，一个皇帝送了命，另一个皇帝丢了王位。</p>
<p>因此，第一个字节在前，就是&#8221;大头方式&#8220;（Big endian），第二个字节在前就是&#8221;小头方式&#8220;（Little endian）。</p>
<p>那么很自然的，就会出现一个问题：计算机怎么知道某一个文件到底采用哪一种方式编码？</p>
<p>Unicode规范中定义，每一个文件的最前面分别加入一个表示编码顺序的字符，这个字符的名字叫做&#8221;零宽度非换行空格&#8220;（ZERO WIDTH NO-BREAK SPACE），用FEFF表示。这正好是两个字节，而且FF比FE大1。</p>
<p>如果一个文本文件的头两个字节是FE FF，就表示该文件采用大头方式；如果头两个字节是FF FE，就表示该文件采用小头方式。</p>
<p><strong>8. 实例</strong></p>
<p>下面，举一个实例。</p>
<p>打开&#8221;记事本&#8220;程序Notepad.exe，新建一个文本文件，内容就是一个&#8221;严&#8220;字，依次采用ANSI，Unicode，Unicode big endian 和 UTF-8编码方式保存。</p>
<p>然后，用文本编辑软件<a  href="http://www.google.cn/search?aq=t&amp;oq=UltraEdit&amp;complete=1&amp;hl=zh-CN&amp;newwindow=1&amp;rlz=1B3GGGL_zh-CNCN216CN216&amp;q=ultraedit+%E4%B8%8B%E8%BD%BD&amp;btnG=Google+%E6%90%9C%E7%B4%A2&amp;meta=" target="_blank">UltraEdit中</a>的&#8221;十六进制功能&#8220;，观察该文件的内部编码方式。</p>
<p>1）ANSI：文件的编码就是两个字节&#8220;D1 CF&#8221;，这正是&#8220;严&#8221;的GB2312编码，这也暗示GB2312是采用大头方式存储的。</p>
<p>2）Unicode：编码是四个字节&#8220;FF FE 25 4E&#8221;，其中&#8220;FF FE&#8221;表明是小头方式存储，真正的编码是4E25。</p>
<p>3）Unicode big endian：编码是四个字节&#8220;FE FF 4E 25&#8221;，其中&#8220;FE FF&#8221;表明是大头方式存储。</p>
<p>4）UTF-8：编码是六个字节&#8220;EF BB BF E4 B8 A5&#8221;，前三个字节&#8220;EF BB BF&#8221;表示这是UTF-8编码，后三个&#8220;E4B8A5&#8221;就是&#8220;严&#8221;的具体编码，它的存储顺序与编码顺序是一致的。</p>
<p><strong>9. 延伸阅读</strong></p>
<p>* <a  href="http://www.joelonsoftware.com/articles/Unicode.html" target="_blank">The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets</a>（关于字符集的最基本知识）</p>
<p>* <a  href="http://www.pconline.com.cn/pcedu/empolder/gj/other/0505/616631.html" target="_blank">谈谈Unicode编码</a></p>
<p>* <a  href="http://www.ietf.org/rfc/rfc3629.txt" target="_blank">RFC3629：UTF-8, a transformation format of ISO 10646</a>（如果实现UTF-8的规定）</p><img src ="http://www.cppblog.com/netboy/aggbug/73847.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/netboy/" target="_blank">王勇良</a> 2009-02-15 10:38 <a href="http://www.cppblog.com/netboy/archive/2009/02/15/73847.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>正确的使用CComSafeArray,CComVariant和CComBSTR（转）</title><link>http://www.cppblog.com/netboy/archive/2009/02/15/73846.html</link><dc:creator>王勇良</dc:creator><author>王勇良</author><pubDate>Sun, 15 Feb 2009 02:37:00 GMT</pubDate><guid>http://www.cppblog.com/netboy/archive/2009/02/15/73846.html</guid><wfw:comment>http://www.cppblog.com/netboy/comments/73846.html</wfw:comment><comments>http://www.cppblog.com/netboy/archive/2009/02/15/73846.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/netboy/comments/commentRss/73846.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/netboy/services/trackbacks/73846.html</trackback:ping><description><![CDATA[如果你用C++来编写COM，那么你将必不可少的使用这三个类型。使用这三种wrapper
class毫无疑问会简化我们的编程，使得使用SAFEARRAY,
VARIANT和BSTR简单。但是，使用这三个类型依然需要小心，因为使用不当的话，就会造成内存泄漏，或效率降低。<br><br>1. 如果拷贝两个BSTR<br>假如我们一个BSTR，这个时候我希望复制一份BSTR，并丢弃之前的BSTR。通常我们会这么写：<br>
<div style="border: 1px solid #cccccc; padding: 4px 5px 4px 4px; background-color: #eeeeee; font-size: 13px; width: 98%;"><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #000000;">CComBSTR&nbsp;StringToBSTR(</span><span style="color: #0000ff;">const</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">string</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">&amp;</span><span style="color: #000000;">&nbsp;sVal)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CComBSTR&nbsp;bstrValue&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;sVal.data();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;bstrValue;<br>}<br><br></span><span style="color: #0000ff;">int</span><span style="color: #000000;">&nbsp;main()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CComBSTR&nbsp;vValue&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;StringToBSTR(</span><span style="color: #000000;">"</span><span style="color: #000000;">value</span><span style="color: #000000;">"</span><span style="color: #000000;">);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">;<br>}</span></div>
<br>当然，上面这个程序没有任何问题，不会有任何内存泄漏的可能。但是，你有没有上面代码里都发生了什么了？<br>答案很简单，在函数StringToBSTR里面，讲bstrValue返回的时候，会调用CComBSTR::Copy()，在Copy()里面将会调用<br>&nbsp;::SysAllocStringByteLen()<br>这个函数。而后在给vValue赋值的时候，又 会调用一次<br>::SysAllocString()<br>显而易见，开销很大。<br><br>那么，我们将怎么改进这段代码了？<br>
<div style="border: 1px solid #cccccc; padding: 4px 5px 4px 4px; background-color: #eeeeee; font-size: 13px; width: 98%;"><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #000000;">BSTR&nbsp;StringToBSTR(</span><span style="color: #0000ff;">const</span><span style="color: #000000;">&nbsp;</span><span style="color: #0000ff;">string</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">&amp;</span><span style="color: #000000;">&nbsp;sVal)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CComBSTR&nbsp;bstrValue&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;sVal.data();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;bstrValue.Detach();<br>}<br><br></span><span style="color: #0000ff;">int</span><span style="color: #000000;">&nbsp;main()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CComBSTR&nbsp;vValue.Attach(StringToBSTR(</span><span style="color: #000000;">"</span><span style="color: #000000;">value</span><span style="color: #000000;">"</span><span style="color: #000000;">));<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">return</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">0</span><span style="color: #000000;">;<br>}</span></div>
这样，通过CComBSTR::Detach()，我们将BSTR返回回来，通过CComBSTR::Attach()，我们将BSTR指针存储起来。这样，就减小了两次开销，大大提高了效率，也不会造成内存效率。<br><br>2. 如何使用CComSafeArray<br>使
用CComSafeArray的一个最大的好处，就是它会自动释放元素是VARIANT和BSTR。也就是说，如果你的类型是VARIANT，它会自动调
用::VariantClear()。如果你的类型是BSTR，他会自动调用::SysStringFree()方法。但是使用它的时候，同样要小心。<br>2.1 成对使用::SafeArrayAccessData()和::SafeArrayUnaccessData()<br>我们有时候会这样使用CComSafeArray的元素：<br>
<div style="border: 1px solid #cccccc; padding: 4px 5px 4px 4px; background-color: #eeeeee; font-size: 13px; width: 98%;"><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #0000ff;">void</span><span style="color: #000000;">&nbsp;DoSomething()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CComSafeArray</span><span style="color: #000000;">&lt;</span><span style="color: #0000ff;">double</span><span style="color: #000000;">&gt;</span><span style="color: #000000;">&nbsp;pSafeArray(</span><span style="color: #000000;">3</span><span style="color: #000000;">);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff;">double</span><span style="color: #000000;">&nbsp;</span><span style="color: #000000;">*</span><span style="color: #000000;">&nbsp;pVal&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;NULL;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;::SafeArrayAccessData(pSafeArray</span><span style="color: #000000;">.</span><span style="color: #000000;">m_psa,&nbsp;(</span><span style="color: #0000ff;">void</span><span style="color: #000000;">**</span><span style="color: #000000;">)</span><span style="color: #000000;">&amp;</span><span style="color: #000000;">pVal);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">handle&nbsp;the&nbsp;elements&nbsp;through&nbsp;the&nbsp;pVal;</span><span style="color: #008000;"><br></span><span style="color: #000000;">}</span></div>
因为::SafeArrayAccessData
方法会在SFAEARRAY上给lock加1. 如果上面程序显示调用CComSafeArray::Destroy()函数，你检查它返回来的HRESULT的时候，应该是下面的值：<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; hr&nbsp;&nbsp; &nbsp;0x8002000d 内存已锁定。 &nbsp;&nbsp; &nbsp;HRESULT<br>如果你不仔细检查，那么将造成CComSafeArray没有释放。<br>2.2 从CComSafeArray转为成CComVariant<br>有时候我们使用CComVariant包装SAFEARRY。你会这样写代码：<br>
<div style="border: 1px solid #cccccc; padding: 4px 5px 4px 4px; background-color: #eeeeee; font-size: 13px; width: 98%;"><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #0000ff;">void</span><span style="color: #000000;">&nbsp;DoSomething()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CComSafeArray</span><span style="color: #000000;">&lt;</span><span style="color: #0000ff;">double</span><span style="color: #000000;">&gt;</span><span style="color: #000000;">&nbsp;pSafeArray(</span><span style="color: #000000;">3</span><span style="color: #000000;">);<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">fill&nbsp;the&nbsp;safearray</span><span style="color: #008000;"><br></span><span style="color: #000000;"><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CComVariant&nbsp;v&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;pSafeArray.Detach();<br>}</span></div>
你可能会任务CComVariant会存储pSafeArray的指针。可惜，你错了。<br>CComVariant会调用::SafeArrayCopy
来完成赋值操作。而你的pSafeArray已经调用了Detach()操作，那么它里面的SAFEARRAY就变成了孤儿，没有人去释放它了。<br>那么你应该怎么写了？<br>你可以这么写：<br>
<div style="border: 1px solid #cccccc; padding: 4px 5px 4px 4px; background-color: #eeeeee; font-size: 13px; width: 98%;"><!--<br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>--><span style="color: #0000ff;">void</span><span style="color: #000000;">&nbsp;DoSomething()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CComSafeArray</span><span style="color: #000000;">&lt;</span><span style="color: #0000ff;">double</span><span style="color: #000000;">&gt;</span><span style="color: #000000;">&nbsp;pSafeArray(</span><span style="color: #000000;">3</span><span style="color: #000000;">);<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000;">//</span><span style="color: #008000;">fill&nbsp;the&nbsp;safearray</span><span style="color: #008000;"><br></span><span style="color: #000000;"><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CComVariant&nbsp;v&nbsp;</span><span style="color: #000000;">=</span><span style="color: #000000;">&nbsp;pSafeArray.m_psa;<br>}</span></div>
这样，CComVariant会调用::SafeArrayCopy
来完成复制操作，而CComSafeArray也会保证在析构的时候释放里面的SAFEARRAY。<br><br>使用上面三个wrapper类，确实可以很方便我们编程，也能避免很多memory leak。但是，使用他们同样要小心，不然，同样会造成性能损失，或者，更糟糕的，内存泄漏。<img src ="http://www.cppblog.com/netboy/aggbug/73846.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/netboy/" target="_blank">王勇良</a> 2009-02-15 10:37 <a href="http://www.cppblog.com/netboy/archive/2009/02/15/73846.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>标准C++的类型转换符：static_cast、dynamic_cast、reinterpret_cast和const_cast（转）</title><link>http://www.cppblog.com/netboy/archive/2009/02/15/73843.html</link><dc:creator>王勇良</dc:creator><author>王勇良</author><pubDate>Sun, 15 Feb 2009 02:30:00 GMT</pubDate><guid>http://www.cppblog.com/netboy/archive/2009/02/15/73843.html</guid><wfw:comment>http://www.cppblog.com/netboy/comments/73843.html</wfw:comment><comments>http://www.cppblog.com/netboy/archive/2009/02/15/73843.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/netboy/comments/commentRss/73843.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/netboy/services/trackbacks/73843.html</trackback:ping><description><![CDATA[C 风格（C-style）强制转型如下： <br>(T) exdivssion // cast exdivssion to be of type T <br>函数风格（Function-style）强制转型使用这样的语法： <br>T(exdivssion) // cast exdivssion to be of type T <br><br>这两种形式之间没有本质上的不同，它纯粹就是一个把括号放在哪的问题。我把这两种形式称为旧风格（old-style）的强制转型。<br><br><br>使用标准C++的类型转换符：static_cast、dynamic_cast、reinterdivt_cast、和const_cast。<br>1. static_cast<br>用法：static_cast &lt; type-id &gt; ( exdivssion ) <br>该运算符把exdivssion转换为type-id类型，但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法：<br>①用于类层次结构中基类和子类之间指针或引用的转换。<br>　　进行上行转换（把子类的指针或引用转换成基类表示）是安全的；<br>　　进行下行转换（把基类指针或引用转换成子类表示）时，由于没有动态类型检查，所以是不安全的。<br>②用于基本数据类型之间的转换，如把int转换成char，把int转换成enum。这种转换的安全性也要开发人员来保证。<br>③把空指针转换成目标类型的空指针。<br>④把任何类型的表达式转换成void类型。<br>注意：static_cast不能转换掉exdivssion的const、volitale、或者__unaligned属性。<br><br>2. dynamic_cast<br>用法：dynamic_cast &lt; type-id &gt; ( exdivssion )<br>该运算符把exdivssion转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *；<br>如果type-id是类指针类型，那么exdivssion也必须是一个指针，如果type-id是一个引用，那么exdivssion也必须是一个引用。<br>dynamic_cast主要用于类层次间的上行转换和下行转换，还可以用于类之间的交叉转换。<br>在类层次间进行上行转换时，dynamic_cast和static_cast的效果是一样的；<br>在进行下行转换时，dynamic_cast具有类型检查的功能，比static_cast更安全。<br>class B{<br>public:<br>int m_iNum;<br>virtual void foo();<br>};<br>class D:public B{<br>public:<br>char *m_szName[100];<br>};<br>void func(B *pb){<br>D *pd1 = static_cast(pb);<br>D *pd2 = dynamic_cast(pb);<br>}<br>在上面的代码段中，如果pb指向一个D类型的对象，pd1和pd2是一样的，并且对这两个指针执行D类型的任何操作都是安全的；<br>但是，如果pb指向的是一个B类型的对象，那么pd1将是一个指向该对象的指针，对它进行D类型的操作将是不安全的（如访问m_szName），<br>而pd2将是一个空指针。<br>另外要注意：B要有虚函数，否则会编译出错；static_cast则没有这个限制。<br>这是由于运行时类型检查需要运行时类型信息，而这个信息存储在类的虚函数表（<br>关于虚函数表的概念，详细可见）中，只有定义了虚函数的类才有虚函数表，<br>没有定义虚函数的类是没有虚函数表的。<br>另外，dynamic_cast还支持交叉转换（cross cast）。如下代码所示。<br>class A{<br>public:<br>int m_iNum;<br>virtual void f(){}<br>};<br>class B:public A{<br>};<br>class D:public A{<br>};<br>void foo(){<br>B *pb = new B;<br>pb-&gt;m_iNum = 100;<br>D *pd1 = static_cast(pb); //compile error<br>D *pd2 = dynamic_cast(pb); //pd2 is NULL<br>delete pb;<br>}<br>在函数foo中，使用static_cast进行转换是不被允许的，将在编译时出错；而使用 dynamic_cast的转换则是允许的，结果是空指针。<br><br>3. reindivter_cast<br>用法：reindivter_cast (exdivssion)<br>type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。<br>它可以把一个指针转换成一个整数，也可以把一个整数转换成一个指针（先把一个指针转换成一个整数，<br>在把该整数转换成原类型的指针，还可以得到原先的指针值）。<br>该运算符的用法比较多。<br>4. const_cast <br>用法：const_cast (exdivssion)<br>该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外， type_id和exdivssion的类型是一样的。<br>常量指针被转化成非常量指针，并且仍然指向原来的对象；<br>常量引用被转换成非常量引用，并且仍然指向原来的对象；常量对象被转换成非常量对象。<br>Voiatile和const类试。举如下一例：<br>class B{<br>public:<br>int m_iNum;<br>}<br>void foo(){<br>const B b1;<br>b1.m_iNum = 100; //comile error<br>B b2 = const_cast(b1);<br>b2. m_iNum = 200; //fine<br>}<br>上面的代码编译时会报错，因为b1是一个常量对象，不能对它进行改变；<br>使用const_cast把它转换成一个常量对象，就可以对它的数据成员任意改变。注意：b1和b2是两个不同的对象。<br><br>== ===========================================<br>== dynamic_cast .vs. static_cast <br>== ===========================================<br><br>class B { ... }; <br>class D : public B { ... }; <br><br>void f(B* pb) <br>{ <br>D* pd1 = dynamic_cast(pb); <br>D* pd2 = static_cast(pb); <br>} <br><br><br>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. <br><br><br>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&#8217;s assertion that pb points to an object of type D and simply returns a pointer to that supposed D object. <br><br><br>即dynamic_cast可用于继承体系中的向下转型，即将基类指针转换为派生类指针，比static_cast更严格更安全。dynamic_cast在执行效率上比static_cast要差一些，但static_cast在更宽上范围内可以完成映射，这种不加限制的映射伴随着不安全性。static_cast覆盖的变换类型除类层次的静态导航以外，还包括无映射变换、窄化变换(这种变换会导致对象切片,丢失信息)、用VOID*的强制变换、隐式类型变换等... <br><br><br>== ===========================================<br>== static_cast .vs. reinterdivt_cast <br>== ================================================<br><br><br>reinterdivt_cast是为了映射到一个完全不同类型的意思，这个关键词在我们需要把类型映射回原有类型时用到它。我们映射到的类型仅仅是为了故弄玄虚和其他目的，这是所有映射中最危险的。(这句话是C++编程思想中的原话) <br>static_cast 和 reinterdivt_cast 操作符修改了操作数类型。它们不是互逆的； static_cast 在编译时使用类型信息执行转换，在转换执行必要的检测(诸如指针越界计算, 类型检查). 其操作数相对是安全的。另一方面；reinterdivt_cast 仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换， 例子如下：<br><br><br>int n=9; double d=static_cast &lt; double &gt; (n); <br><br><br>上面的例子中, 我们将一个变量从 int 转换到 double。 这些类型的二进制表达式是不同的。 要将整数 9 转换到 双精度整数 9，static_cast 需要正确地为双精度整数 d 补足比特位。其结果为 9.0。而reinterdivt_cast 的行为却不同: <br><br><br>int n=9; <br>double d=reinterdivt_cast (n);<br><br>这次, 结果有所不同. 在进行计算以后, d 包含无用值. 这是因为 reinterdivt_cast 仅仅是复制 n 的比特位到 d, 没有进行必要的分析. <br><br><br>因此, 你需要谨慎使用 reinterdivt_cast.&nbsp; <br><br><br><img src ="http://www.cppblog.com/netboy/aggbug/73843.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/netboy/" target="_blank">王勇良</a> 2009-02-15 10:30 <a href="http://www.cppblog.com/netboy/archive/2009/02/15/73843.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于sizeof()的简单解析（转）</title><link>http://www.cppblog.com/netboy/archive/2009/02/15/73842.html</link><dc:creator>王勇良</dc:creator><author>王勇良</author><pubDate>Sun, 15 Feb 2009 02:29:00 GMT</pubDate><guid>http://www.cppblog.com/netboy/archive/2009/02/15/73842.html</guid><wfw:comment>http://www.cppblog.com/netboy/comments/73842.html</wfw:comment><comments>http://www.cppblog.com/netboy/archive/2009/02/15/73842.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/netboy/comments/commentRss/73842.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/netboy/services/trackbacks/73842.html</trackback:ping><description><![CDATA[在所有说明之前，给大家出一道题目：<br><br>int a=256;<br><br>printf("%d\n", sizeof(++a));<br><br>printf("%d\n", a);<br><br>那么到底打印的是多少呢？<br><br>应该是4和256，我想第一个答案大家应该已经没有问题了，但是为什么在++a以后，a的数值还是没有发生变化呢？因为sizeof（）是一个运算符，在其中的所有的运算都是无效的，所以++a根本就没有运行。<br><br>上面的一个例子提醒我们，虽然sizeof看这简单，但是其中还是有很多的问题值得讨论的，呵呵。<br><br>一、sizeof的概念　 <br>　　sizeof是C语言的一种单目操作符，如C语言的其他操作符++、--等。它并不是函数。sizeof操作符以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。操作数的存储大小由操作数的类型决定。　 <br><br>二、sizeof的使用方法　 <br>　　1、用于数据类型　 <br><br>　　sizeof使用形式：sizeof（type）　 <br><br>　　数据类型必须用括号括住。如sizeof（int）。　 <br><br>　　2、用于变量　 <br><br>　　sizeof使用形式：sizeof（var_name）或sizeof　var_name　 <br><br>　　变量名可以不用括号括住。如sizeof　(var_name)，sizeof　var_name等都是正确形式。带括号的用法更普遍，大多数程序员采用这种形式。　 <br><br>　　注意：sizeof操作符不能用于函数类型，不完全类型或位字段。不完全类型指具有未知存储大小的数据类型，如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。　 <br><br>　　如sizeof(max)若此时变量max定义为int　max(),sizeof(char_v)　若此时char_v定义为char　char_v　[MAX]且MAX未知，sizeof(void)都不是正确形式。　 <br><br>三、sizeof的结果　 <br>　　sizeof操作符的结果类型是size_t，它在头文件<br><br>中typedef为unsigned　int类型。该类型保证能容纳实现所建立的最大对象的字节大小。　 <br><br>　　1、若操作数具有类型char、unsigned　char或signed　char，其结果等于1。　 <br><br>　　ANSI　C正式规定字符类型为1字节。　 <br><br>2、int、unsigned　int　、short　int、unsigned　short　、long　int　、unsigned　long　、&nbsp; float、double、long　double类型的sizeof　在ANSI　C中没有具体规定，大小依赖于实现，一般可能分别为2、2、2、 2、 4、4、4、8、10。　 <br><br>　　3、当操作数是指针时，sizeof依赖于编译器。例如Microsoft　C/C++7.0中，near类指针字节数为2，far、huge类指针字节数为4。一般Unix的指针字节数为4。　 <br><br>　　4、当操作数具有数组类型时，其结果是数组的总字节数。　 <br><br>　　5、联合类型操作数的sizeof是其最大字节成员的字节数。结构类型操作数的sizeof是这种类型对象的总字节数，包括任何垫补在内。　 <br><br>　　让我们看如下结构：　 <br><br>　　struct　{char　b;　double　x;}　a;　 <br><br>　　在某些机器上sizeof（a）=12，而一般sizeof（char）+　sizeof（double）=9。　 <br><br>　　这是因为编译器在考虑对齐问题时，在结构中插入空位以控制各成员对象的地址对齐。如double类型的结构成员x要放在被4整除的地址。　 <br><br>　　6、如果操作数是函数中的数组形参或函数类型的形参，sizeof给出其指针的大小。　 <br><br>四、sizeof与其他操作符的关系　 <br>　　sizeof的优先级为2级，比/、%等3级运算符优先级高。它可以与其他操作符一起组成表达式。如i*sizeof（int）；其中i为int类型变量。　 <br><br>五、sizeof的主要用途　 <br>　　1、sizeof操作符的一个主要用途是与存储分配和I/O系统那样的例程进行通信。例如：　 <br><br>　　void　*malloc（size_t　size）,　 <br><br>　　size_t　fread(void　*　ptr,size_t　size,size_t　nmemb,FILE　*　stream)。　 <br><br>　　2、sizeof的另一个的主要用途是计算数组中元素的个数。例如：　 <br><br>　　void　*　memset（void　*　s,int　c,sizeof(s)）。　 <br><br>六、建议　 <br>　　由于操作数的字节数在实现时可能出现变化，建议在涉及到操作数字节大小时用sizeof来代替常量计算。<br><br><br>=============================================================<br>本文主要包括二个部分，第一部分重点介绍在VC中，怎么样采用sizeof来求结构的大小，以及容易出现的问题，并给出解决问题的方法，第二部分总结出VC中sizeof的主要用法。 <br><br>1、 sizeof应用在结构上的情况 <br><br>请看下面的结构： <br><br>struct MyStruct <br><br>{ <br><br>double dda1; <br><br>char dda; <br><br>int type <br><br>}; <br><br>对结构MyStruct采用sizeof会出现什么结果呢？sizeof(MyStruct)为多少呢？也许你会这样求： <br><br>sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13 <br><br>但是当在VC中测试上面结构的大小时，你会发现sizeof(MyStruct)为16。你知道为什么在VC中会得出这样一个结果吗？ <br><br>其实，这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度，VC对一些变量的起始地址做了"对齐"处理。在默认情况下，VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式(vc6.0,32位系统)。 <br><br>类型 <br>对齐方式（变量存放的起始地址相对于结构的起始地址的偏移量） <br><br>Char <br>偏移量必须为sizeof(char)即1的倍数 <br><br>int <br>偏移量必须为sizeof(int)即4的倍数 <br><br>float <br>偏移量必须为sizeof(float)即4的倍数 <br><br>double <br>偏移量必须为sizeof(double)即8的倍数 <br><br>Short <br>偏移量必须为sizeof(short)即2的倍数 <br><br><br>各成员变量在存放的时候根据在结构中出现的顺序依次申请空间，同时按照上面的对齐方式调整位置，空缺的字节VC会自动填充。同时VC为了确保结构的大小为结构的字节边界数（即该结构中占用最大空间的类型所占用的字节数）的倍数，所以在为最后一个成员变量申请空间后，还会根据需要自动填充空缺的字节。 <br><br>下面用前面的例子来说明VC到底怎么样来存放结构的。 <br><br>struct MyStruct <br><br>{ <br><br>double dda1; <br><br>char dda; <br><br>int type <br><br>}； <br><br>为上面的结构分配空间的时候，VC根据成员变量出现的顺序和对齐方式，先为第一个成员dda1分配空间，其起始地址跟结构的起始地址相同（刚好偏移量0刚好为sizeof(double)的倍数），该成员变量占用sizeof(double)=8个字节；接下来为第二个成员dda分配空间，这时下一个可以分配的地址对于结构的起始地址的偏移量为8，是sizeof(char)的倍数，所以把dda存放在偏移量为8的地方满足对齐方式，该成员变量占用&nbsp; sizeof(char)=1个字节；接下来为第三个成员type分配空间，这时下一个可以分配的地址对于结构的起始地址的偏移量为9，不是 sizeof (int)=4的倍数，为了满足对齐方式对偏移量的约束问题，VC自动填充3个字节（这三个字节没有放什么东西），这时下一个可以分配的地址对于结构的起始地址的偏移量为12，刚好是sizeof(int)=4的倍数，所以把type存放在偏移量为12的地方，该成员变量占用sizeof (int)=4个字节；这时整个结构的成员变量已经都分配了空间，总的占用的空间大小为：8+1+3+4=16，刚好为结构的字节边界数（即结构中占用最大空间的类型所占用的字节数sizeof(double)=8）的倍数，所以没有空缺的字节需要填充。所以整个结构的大小为：sizeof (MyStruct)=8+1+ 3+4=16，其中有3个字节是VC自动填充的，没有放任何有意义的东西。 <br><br>下面再举个例子，交换一下上面的MyStruct的成员变量的位置，使它变成下面的情况： <br><br>struct MyStruct <br><br>{ <br><br>char dda; <br><br>double dda1;&nbsp; &nbsp;<br><br>int type <br><br>}； <br><br>这个结构占用的空间为多大呢？在VC6.0环境下，可以得到sizeof(MyStruc)为24。结合上面提到的分配空间的一些原则，分析下VC怎么样为上面的结构分配空间的。（简单说明） <br><br>struct MyStruct <br><br>{ <br><br>&nbsp; char dda;//偏移量为0，满足对齐方式，dda占用1个字节； <br><br>double dda1;//下一个可用的地址的偏移量为1，不是sizeof(double)=8 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //的倍数，需要补足7个字节才能使偏移量变为8（满足对齐 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //方式），因此VC自动填充7个字节，dda1存放在偏移量为8 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //的地址上，它占用8个字节。 <br><br>int type；//下一个可用的地址的偏移量为16，是sizeof(int)=4的倍 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //数，满足int的对齐方式，所以不需要VC自动填充，type存 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //放在偏移量为16的地址上，它占用4个字节。 <br><br>}；//所有成员变量都分配了空间，空间总的大小为1+7+8+4=20，不是结构 <br><br>&nbsp;&nbsp; //的节边界数（即结构中占用最大空间的类型所占用的字节数sizeof <br><br>&nbsp;&nbsp; //(double)=8）的倍数，所以需要填充4个字节，以满足结构的大小为 <br><br>&nbsp;&nbsp; //sizeof(double)=8的倍数。 <br><br><br>所以该结构总的大小为：sizeof(MyStruc)为1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的，没有放任何有意义的东西。 <br><br><br>VC对结构的存储的特殊处理确实提高CPU存储变量的速度，但是有时候也带来了一些麻烦，我们也屏蔽掉变量默认的对齐方式，自己可以设定变量的对齐方式。 <br><br>#pragma pack(n)<br>VC&nbsp; 中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况：第一、如果n大于等于该变量所占用的字节数，那么偏移量必须满足默认的对齐方式，第二、如果n小于该变量的类型所占用的字节数，那么偏移量为n的倍数，不用满足默认的对齐方式。结构的总大小也有个约束条件，分下面两种情况：如果n大于所有成员变量类型所占用的字节数，那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数；&nbsp; &nbsp;<br><br>否则必须为n的倍数。下面举例说明其用法。 <br><br>#pragma pack(push) //保存对齐状态 <br><br>#pragma pack(4)//设定为4字节对齐 <br><br>struct test <br><br>{ <br><br>&nbsp; char m1; <br><br>&nbsp; double m4; <br><br>&nbsp; int&nbsp; m3; <br><br>}; <br><br>#pragma pack(pop)//恢复对齐状态 <br><br>以上结构的大小为16，下面分析其存储情况，首先为m1分配空间，其偏移量为0，满足我们自己设定的对齐方式（4字节对齐），m1占用1个字节。接着开始为&nbsp; m4分配空间，这时其偏移量为1，需要补足3个字节，这样使偏移量满足为n=4的倍数（因为sizeof(double)大于n）,m4占用8个字节。接着为m3分配空间，这时其偏移量为12，满足为4的倍数，m3占用4个字节。这时已经为所有成员变量分配了空间，共分配了16个字节，满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16)，那么我们可以得到结构的大小为24。（请读者自己分析） <br><br>2、 sizeof用法总结 <br><br>在VC中，sizeof有着许多的用法，而且很容易引起一些错误。下面根据sizeof后面的参数对sizeof的用法做个总结。 <br><br>A．&nbsp; 参数为数据类型或者为一般变量。例如sizeof(int),sizeof(long)等等。这种情况要注意的是不同系统系统或者不同编译器得到的结果可能是不同的。例如int类型在16位系统中占2个字节，在32位系统中占4个字节。 <br><br>B．&nbsp; 参数为数组或指针。下面举例说明. <br><br>int a[50];&nbsp; //sizeof(a)=4*50=200; 求数组所占的空间大小 <br><br>int *a=new int[50];// sizeof(a)=4; a为一个指针，sizeof(a)是求指针 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //的大小,在32位系统中，当然是占4个字节。 <br><br>C．&nbsp; 参数为结构或类。Sizeof应用在类和结构的处理情况是相同的。但有两点需要注意，第一、结构或者类中的静态成员不对结构或者类的大小产生影响，因为静态变量的存储位置与结构或者类的实例地址无关。 <br><br>第二、没有成员变量的结构或类的大小为1，因为必须保证结构或类的每一 <br><br>个实例在内存中都有唯一的地址。 <br><br>下面举例说明， <br><br>Class Test{int a;static double c};//sizeof(Test)=4. <br><br>Test *s;//sizeof(s)=4,s为一个指针。 <br><br>Class test1{ };//sizeof(test1)=1; <br><br>D．&nbsp; 参数为其他。下面举例说明。 <br><br>&nbsp;&nbsp; int func(char s[5]); <br><br>&nbsp;&nbsp; { <br><br>&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;sizeof(s);//这里将输出4，本来s为一个数组，但由于做为函 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //数的参数在传递的时候系统处理为一个指针，所 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //以sizeof(s)实际上为求指针的大小。 <br><br>&nbsp;&nbsp;&nbsp;&nbsp; return 1; <br><br>} <br><br>sizeof(func("1234"))=4//因为func的返回类型为int，所以相当于 <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //求sizeof(int). <br><br><br>以上为sizeof的基本用法，在实际的使用中要注意分析VC的分配变量的分配策略，这样的话可以避免一些错误。<img src ="http://www.cppblog.com/netboy/aggbug/73842.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/netboy/" target="_blank">王勇良</a> 2009-02-15 10:29 <a href="http://www.cppblog.com/netboy/archive/2009/02/15/73842.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>