??xml version="1.0" encoding="utf-8" standalone="yes"?>
http://support.microsoft.com/kb/925488
下蝲安装补丁QNDP20-KB925488-X86.mspQ即?/p>
有的pȝ安装不上Q提C?#8220;此Y件更C计算Z的windows安装E序不兼容,要更新?zhn)的计机请从windows下蝲中心(j)下蝲q安装程序可在发行版?.1或更高版本,q安装程序可在发行版?.1或更高版本?#8221;
需要更新客L(fng)的windows installerE序
http://support.microsoft.com/kb/893803
因此我认为,.NET Framework 1.0 ?1.1 版类库中存在M Windows 所没有的功能限刉不为怪。毕竟,32 位的 WindowsQ不何U版本)(j)是一个成熟的操作pȝQؓ(f)q大客户服务?jin)十多年。相比之下,.NET Framework 却是一个新事物?/p>
随着来多的开发h员将生应用E序转到托管代码Q开发h员更频繁地研I底层操作系l以图找Z些关键功能显得很自然 ?臛_目前是如此?/p>
值得?jin)幸的是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 作ؓ(f)专栏主题来讨Z太简明扼要?/p>
走进 P/Invoke
首先从考察一个简单的 P/Invoke CZ开始。让我们看一看如何调?Win32 MessageBeep 函数Q它的非托管声明如以下代码所C:(x)
BOOL MessageBeep(
UINT uType // beep type
);
Z(jin)调用 MessageBeepQ?zhn)需要在 C# 中将以下代码dC个类或结构定义中Q?
[DllImport("User32.dll")]
static extern Boolean MessageBeep(UInt32 beepType);
令h惊讶的是Q只需要这D代码就可以使托代码调用非托管?MessageBeep API。它不是一个方法调用,而是一个外部方法定义。(另外Q它接近于一个来?C ?C# 允许的直接端口,因此以它vҎ(gu)介绍一些概忉|有帮助的。)(j)来自托管代码的可能调用如下所C:(x)
MessageBeep(0);
h意,现在 MessageBeep Ҏ(gu)被声明ؓ(f) static。这?P/Invoke Ҏ(gu)所要求的,因ؓ(f)在该 Windows API 中没有一致的实例概念。接下来Q还要注意该Ҏ(gu)被标Cؓ(f) extern。这是提C编译器该方法是通过一个从 DLL 导出的函数实现的Q因此不需要提供方法体?/p>
说到~少Ҏ(gu)体,(zhn)是否注意到 MessageBeep 声明q没有包含一个方法体Q与大多数算法由中间语言 (IL) 指o(h)l成的托方法不同,P/Invoke Ҏ(gu)只是元数据,实时 (JIT) ~译器在q行旉过它将托管代码与非托管?DLL 函数q接h。执行这U到非托世界的q接所需的一个重要信息就是导出非托管Ҏ(gu)?DLL 的名U。这一信息是由 MessageBeep Ҏ(gu)声明之前?DllImport 自定义属性提供的。在本例中,可以看到QMessageBeep 非托?API 是由 Windows 中的 User32.dll 导出的?/p>
到现在ؓ(f)止,关于调用 MessageBeep 剩两个话题没有介绍Q请回顾一下,调用的代码与以下所CZ码片D非常相|(x)
[DllImport("User32.dll")]
static extern Boolean MessageBeep(UInt32 beepType);
最后这两个话题是与数据送处?(data marshaling) 和从托管代码到非托管函数的实际方法调用有关的话题。调用非托管 MessageBeep 函数可以由找C用域内的extern MessageBeep 声明的Q何托代码执行。该调用cM于Q何其他对静态方法的调用。它与其他Q何托方法调用的共同之处在于带来?jin)数据封送处理的需要?
C# 的规则之一是它的调用语法只能访?CLR 数据cdQ例?System.UInt32 ?System.Boolean。C# 昄不识?Windows API 中用的Z C 的数据类型(例如 UINT ?BOOLQ,q些cd只是 C 语言cd的类型定义而已。所以当 Windows API 函数 MessageBeep 按以下方式编写时
BOOL MessageBeep( UINT uType )
外部Ҏ(gu)必M?CLR cd来定义,如?zhn)在前面的代码片段中所看到的。需要用与基础 API 函数cd不同但与之兼容的 CLR cd?P/Invoke 较难使用的一个方面。因此,在本专栏的后面我用完整的章节来介绍数据送处理?
样式
?C# 中对 Windows API q行 P/Invoke 调用是很单的。但如果cd拒绝使?zhn)的应用程序发出嘟壎ͼ应该x设法调用 Windows 使它q行q项工作Q是吗?
是的。但是与选择的方法有养I而且关系甚大Q通常Q如果类库提供某U途径来实现?zhn)的意图,则最好?API 而不要直接调用非托管代码Q因? CLR cd?Win32 之间在样式上有很大的不同。我可以关于这个问题的归结Z句话。当(zhn)进?P/Invoke Ӟ不要使应用程序逻辑直接属于M外部Ҏ(gu)或其中的构g。如果?zhn)遵@q个规则,从长q看l常?x)省去许多的ȝ?ch)?/p>
?1 中的代码昄?jin)我所讨论?MessageBeep 外部Ҏ(gu)的最附加代码。图 1 中ƈ没有M显著的变化,而只是对无包装的外部Ҏ(gu)q行一些普通的改进Q这可以使工作更加轻松一些。从剙开始,(zhn)会(x)注意C个名?Sound 的完整类型,它专用于 MessageBeep。如果我需要?Windows API 函数 PlaySound 来添加对播放波Ş的支持,则可以重?Sound cd。然而,我不?x)因公开单个公共静态方法的cd而生气。毕竟这只是应用E序代码而已。还应该注意刎ͼSound 是密的Qƈ定义?jin)一个空的私有构造函数。这些只是一些细节,目的是用户不会(x)错误C Sound zcL者创建它的实例?/p>
?1 中的代码的下一个特征是QP/Invoke 出现位置的实际外部方法是 Sound 的私有方法。这个方法只是由公共 MessageBeep Ҏ(gu)间接公开Q后者接?BeepTypes cd的参数。这个间接的额外层是一个很关键的细节,它提供了(jin)以下好处。首先,应该在类库中引入一个未来的 beep 托管Ҏ(gu)Q可以重复地通过公共 MessageBeep Ҏ(gu)来用托?APIQ而不必更改应用程序中的其余代码?/p>
该包装方法的W二个好处是Q当(zhn)进?P/Invoke 调用Ӟ(zhn)放弃了(jin)免受讉K冲突和其他低U破坏的权利Q这通常是由 CLR 提供的。缓冲方法可以保护?zhn)的应用程序的其余部分免受讉K冲突?qing)类似问题的影响Q即使它不做M事而只是传递参敎ͼ(j)。该~冲Ҏ(gu)由 P/Invoke 调用引入的Q何潜在的错误本地化?/p>
私有外部方法隐藏在公共包装后面的第三同时也是最后的一个好处是Q提供了(jin)向该Ҏ(gu)d一些最的 CLR 样式的机?x)。例如,在图 1 中,我将 Windows API 函数q回?Boolean p|转换成更?CLR 的异常。我q定义了(jin)一个名?BeepTypes 的枚丄型,它的成员对应于同?Windows API 一起用的定义倹{由?C# 不支持定义,因此可以使用托管枚Dcd来避免数向整个应用E序代码扩散?/p>
包装Ҏ(gu)的最后一个好处对于简单的 Windows API 函数Q如 MessageBeepQ诚然是微不道的。但是当(zhn)开始调用更复杂的非托管函数Ӟ(zhn)会(x)发现Q手动将 Windows API 样式转换成对 CLR 更加友好的方法所带来的好处会(x)来多。越是打在整个应用E序中重?interop 功能Q越是应该认真地考虑包装的设计。同时我认ؓ(f)Q在非面向对象的静态包装方法中使用?CLR 友好的参Cq不可以?/p>
DLL Import 属?/p>
现在是更深入地进行探讨的时候了(jin)。在Ҏ(gu)代码进?P/Invoke 调用ӞDllImportAttribute cd扮演着重要的角艌ӀDllImportAttribute 的主要作用是l?CLR 指示哪个 DLL 导出(zhn)想要调用的函数。相?DLL 的名U被作ؓ(f)一个构造函数参C递给 DllImportAttribute?/p>
如果(zhn)无法肯定哪?DLL 定义?jin)(zhn)要用?Windows API 函数QPlatform SDK 文档ؓ(f)(zhn)提供最好的帮助资源。在 Windows API 函数主题文字临近l尾的位|,SDK 文档指定?C 应用E序要用该函数必须链接?.lib 文g。在几乎所有的情况下,?.lib 文gh与定义该函数的系l?DLL 文g相同的名U。例如,如果该函数需?C 应用E序链接? Kernel32.libQ则该函数就定义?Kernel32.dll 中。?zhn)可以?MessageBeep 中找到有?MessageBeep ?Platform SDK 文档主题。在该主题结֤Q?zhn)会(x)注意到它指出库文g?User32.libQ这表明 MessageBeep 是从 User32.dll 中导出的?/p>
可选的 DllImportAttribute 属?/p>
除了(jin)指出宿主 DLL 外,DllImportAttribute q包含了(jin)一些可选属性,其中四个特别有趣QEntryPoint、CharSet、SetLastError ?CallingConvention?/p>
EntryPoint 在不希望外部托管Ҏ(gu)h?DLL 导出相同的名U的情况下,可以讄该属性来指示导出?DLL 函数的入口点名称。当(zhn)定义两个调用相同非托管函数的外部方法时Q这特别有用。另外,?Windows 中还可以通过它们的序号值绑定到导出? DLL 函数。如果?zhn)需要这样做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 ?x)自动进行必要的转换Q将其从 Unicode转换?ANSI?/p>
如果 DLL 函数不以M方式处理文本Q则可以忽略 DllImportAttribute ?CharSet 属性。然而,?Char ?String 数据是等式的一部分Ӟ应该?CharSet 属性设|ؓ(f) CharSet.Auto。这样可以 CLR Ҏ(gu)宿主 OS 使用适当的字W集。如果没有显式地讄 CharSet 属性,则其默认gؓ(f) CharSet.Ansi。这个默认值是有缺点的Q因为对于在 Windows 2000、Windows XP ?Windows NT® 上进行的 interop 调用Q它?x)消极地影响文本参数送处理的性能?/p>
应该昑ּ地选择 CharSet.Ansi ?CharSet.Unicode ?CharSet D不是?CharSet.Auto 的唯一情况是:(x)(zhn)显式地指定?jin)一个导出函敎ͼ而该函数特定于这两种 Win32 OS 中的某一U。ReadDirectoryChangesW API 函数是q样的一个例子,它只存在于基?Windows NT 的操作系l中Qƈ且只支持 UnicodeQ在q种情况下,(zhn)应该显式地使用 CharSet.Unicode?/p>
有时QW(xu)indows API 是否有字W集关系q不明显。一U决不会(x)有错的确认方法是?Platform SDK 中检查该函数?C 语言头文件。(如果(zhn)无法肯定要看哪个头文gQ则可以查看 Platform SDK 文档中列出的每个 API 函数的头文g。)(j)如果(zhn)发现该 API 函数实定义Z个映到?A ?W l尾的函数名的宏Q则字符集与(zhn)尝试调用的函数有关pRWindows API 函数的一个例子是? WinUser.h 中声明的 GetMessage APIQ?zhn)也许会(x)惊讶地发现它?A ?W 两种版本?/p>
SetLastError 错误处理非常重要Q但在编E时l常被遗忘。当(zhn)进?P/Invoke 调用Ӟ也会(x)面(f)其他的挑??处理托管代码?Windows API 错误处理和异怹间的区别。我可以l?zhn)一点徏议?/p>
如果(zhn)正在?P/Invoke 调用 Windows API 函数Q而对于该函数Q?zhn)使?GetLastError 来查找扩展的错误信息Q则应该在外部方法的 DllImportAttribute 中将 SetLastError 属性设|ؓ(f) true。这适用于大多数外部Ҏ(gu)?/p>
q会(x)D CLR 在每ơ调用外部方法之后缓存由 API 函数讄的错误。然后,在包装方法中Q可以通过调用cd? System.Runtime.InteropServices.Marshal cd中定义的 Marshal.GetLastWin32Error Ҏ(gu)来获取缓存的错误倹{我的徏议是(g)查这些期望来?API 函数的错误|qؓ(f)q些值引发一个可感知的异常。对于其他所有失败情况(包括Ҏ(gu)没意料到的p|情况Q,则引发在 System.ComponentModel 命名I间中定义的 Win32ExceptionQƈ? Marshal.GetLastWin32Error q回的g递给它。如果?zhn)回头看一下图 1 中的代码Q?zhn)会(x)看到我?extern MessageBeep Ҏ(gu)的公共包装中采用了(jin)q种Ҏ(gu)?/p>
CallingConvention 我将在此介绍的最后也可能是最不重要的一?DllImportAttribute 属性是 CallingConvention。通过此属性,可以l?CLR 指示应该哪U函数调用约定用于堆栈中的参数。CallingConvention.Winapi 的默认值是最好的选择Q它在大多数情况下都可行。然而,如果该调用不起作用,则可以检?Platform SDK 中的声明头文Ӟ看看(zhn)调用的 API 函数是否是一个不W合调用U定标准的异?API?/p>
通常Q本机函敎ͼ例如 Windows API 函数?C- q行?DLL 函数Q的调用U定描述?jin)如何将参数推入U程堆栈或从U程堆栈中清除。大多数 Windows API 函数都是首先函数的最后一个参数推入堆栈,然后p调用的函数负责清理该堆栈。相反,许多 C-q行?DLL 函数都被定义为按照方法参数在Ҏ(gu){中出现的序其推入堆栈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员会(x)l常遇到它们直至可将q些规则C。除非?zhn)是一名类库开发h员,否则在通常情况下没有必要掌握其l节。ؓ(f)?jin)最有效地在 CLR 上?P/InvokeQ即使只偶尔需?interop 的应用程序开发h员仍然应该理解数据封送处理的一些基知识?/p>
在本月专栏的剩余部分中,我将讨论单数字和字符串数据的数据送处理。我从最基本的数字数据封送处理开始,然后介绍单的指针送处理和字符串封送处理?/p>
送数字和逻辑标量
Windows OS 大部分是?C ~写的。因此,W(xu)indows API 所用到的数据类型要么是 C cdQ要么是通过cd定义或宏定义重新标记?C cd。让我们看看没有指针的数据封送处理。简单v见,首先重点讨论的是数字和布?yu)(dng)倹{?/p>
当通过值向 Windows API 函数传递参数时Q需要知道以下问题的{案Q?
• |
数据从根本上讲是整型的还是Q点型的? |
• |
如果数据是整型的Q则它是有符L(fng)q是无符L(fng)Q? |
• |
如果数据是整型的Q则它的位数是多? |
• |
如果数据是Q点型的,则它是单_ֺ的还是双_ֺ的? |
有时{案很明显,但有时却不明显。Windows API 以各U方式重新定义了(jin)基本?C 数据cd。图 2 列出?C ?Win32 的一些公共数据类型及(qing)其规范,以及(qing)一个具有匹配规范的公共语言q行库类型?
通常Q只要?zhn)选择一个其规范与该参数?Win32 cd相匹配的 CLR cdQ?zhn)的代码就能够正常工作。不q也有一些特例。例如,? Windows API 中定义的 BOOL cd是一个有W号?32 位整型。然而,BOOL 用于指示 Boolean ?true ? false。虽然?zhn)不用?BOOL 参数作ؓ(f) System.Int32 值封送,但是如果使用 System.Boolean cdQ就?x)获得更合适的映射。字W类型的映射cM?BOOLQ因为有一个特定的 CLR cd (System.Char) 指出字符的含义?/p>
在了(jin)解这些信息之后,逐步介绍CZ可能是有帮助的。依焉?beep 主题作ؓ(f)例子Q让我们来试一?Kernel32.dll 低 BeepQ它?x)通过计算机的扬声器发生嘟声。这个方法的 Platform SDK 文档可以?Beep 中找到。本?API 按以下方式进行记录:(x)
BOOL Beep(
DWORD dwFreq, // Frequency
DWORD dwDuration // Duration in milliseconds
);
在参数封送处理方面,(zhn)的工作是了(jin)解什?CLR 数据cd?Beep API 函数所使用?DWORD ?BOOL 数据cd相兼宏V回一下图 2 中的图表Q?zhn)看?DWORD 是一?32 位的无符h数|如同 CLR cd System.UInt32。这意味着(zhn)可以?UInt32 g为送往 Beep 的两个参数。BOOL q回值是一个非常有的情况Q因图表告诉我们Q在 Win32 中,BOOL 是一?32 位的有符h数。因此,(zhn)可以? System.Int32 g为来?Beep 的返回倹{然而,CLR 也定义了(jin) System.Boolean cd作ؓ(f) Boolean 值的语义Q所以应该用它来替代。CLR 默认?System.Boolean 值封送ؓ(f) 32 位的有符h数。此处所昄的外部方法定义是用于 Beep 的结?P/Invoke Ҏ(gu)Q?
[DllImport("Kernel32.dll", SetLastError=true)]
static extern Boolean Beep(
UInt32 frequency, UInt32 duration);
指针参数
许多 Windows API 函数指针作为它们的一个或多个参数。指针增加了(jin)送数据的复杂性,因ؓ(f)它们增加?jin)一个间接层。如果没有指针,(zhn)可以通过值在U程堆栈中传递数据。有?jin)? 针,则可以通过引用传递数据,Ҏ(gu)是将该数据的内存地址推入U程堆栈中。然后,函数通过内存地址间接讉K数据。用托代码表C此附加间接层的方式有多 U?/p>
?C# 中,如果方法参数定义ؓ(f) ref ?outQ则数据通过引用而不是通过g递。即使?zhn)没有使?Interop 也是q样Q但只是从一个托方法调用到另一个托方法。例如,如果通过 ref 传?System.Int32 参数Q则在线E堆栈中传递的是该数据的地址Q而不是整数值本w。下面是一个定义ؓ(f)通过引用接收整数值的Ҏ(gu)的示例:(x)
void FlipInt32(ref Int32 num){
num = -num;
}
q里QFlipInt32 Ҏ(gu)获取一?Int32 值的地址、访问数据、对它求反,然后求反过的Dl原始变量。在以下代码中,FlipInt32 Ҏ(gu)?x)将调用E序的变?x 的g 10 更改?-10Q?
Int32 x = 10;
FlipInt32(ref x);
在托代码中可以重用q种能力Q将指针传递给非托代码。例如,FileEncryptionStatus API 函数?32 位无W号位掩码的形式q回文g加密状态。该 API 按以下所C方式进行记录:(x)
BOOL FileEncryptionStatus(
LPCTSTR lpFileName, // file name
LPDWORD lpStatus // encryption status
);
h意,该函数ƈ不用它的返回D回状态,而是q回一?Boolean |指示调用是否成功。在成功的情况下Q实际的状态值是通过W二个参数返回的。它的工作方式是调用E序向该函数传递指向一?DWORD 变量的指针,而该 API 函数用状态值填充指向的内存位置。以下代码片D|CZ(jin)一个调用非托管 FileEncryptionStatus 函数的可能外部方法定义:(x)
[DllImport("Advapi32.dll", CharSet=CharSet.Auto)]
static extern Boolean FileEncryptionStatus(String filename,
out UInt32 status);
该定义?out 关键字来?UInt32 状态值指C?by-ref 参数。这里我也可以选择 ref 关键字,实际上在q行时会(x)产生相同的机器码。out 关键字只是一?by-ref 参数的规范,它向 C# ~译器指C所传递的数据只在被调用的函数外部传递。相反,如果使用 ref 关键字,则编译器?x)假定数据可以在被调用的函数的内部和外部传递?/p>
托管代码?out ?ref 参数的另一个很好的斚w是,地址作ؓ(f) by-ref 参数传递的变量可以是线E堆栈中的一个本地变量、一个类或结构的元素Q也可以是具有合适数据类型的数组中的一个元素引用。调用程序的q种灉|性? by-ref 参数成ؓ(f)送缓冲区指针以及(qing)单数值指针的一个很好的L(fng)。只有在我发?ref ?out 参数不符合我的需要的情况下,我才?x)考虑指针封送ؓ(f)更复杂的 CLR cdQ例如类或数l对象)(j)?/p>
如果(zhn)不熟?zhn)?C 语法或者调?Windows API 函数Q有时很隄道一个方法参数是否需要指针。一个常见的指示W是看参数类型是否是以字?P ?LP 开头的Q例?LPDWORD ? PINT。在q两个例子中QLP ?P 指示参数是一个指针,而它们指向的数据cd分别?DWORD ? INT。然而,在有些情况下Q可以直接?C 语言语法中的星号 (*) ?API 函数定义为指针。以下代码片D展CZ(jin)q方面的CZQ?
void TakesAPointer(DWORD* pNum);
可以看到Q上q函数的唯一一个参数是指向 DWORD 变量的指针?
当通过 P/Invoke 送指针时Qref ?out 只用于托代码中的值类型。当一个参数的 CLR cd使用 struct 关键字定义时Q可以认参数是一个值类型。Out ?ref 用于送指向这些数据类型的指针Q因为通常值类型变量是对象或数据,而在托管代码中ƈ没有对值类型的引用。相反,当封送引用类型对象时Qƈ不需? ref ?out 关键字,因ؓ(f)变量已经是对象的引用?jin)?/p>
如果(zhn)对引用cd和值类型之间的差别不是很熟(zhn),h?2000 q?12 ?发行?MSDN® MagazineQ在 .NET 专栏的主题中可以扑ֈ更多信息。大多数 CLR cd都是引用cdQ然而,除了(jin) System.String ? System.ObjectQ所有的基元cdQ例?System.Int32 ?System.BooleanQ都是值类型?/p>
送不透明 (Opaque) 指针Q一U特D情?/p>
有时?Windows API 中,Ҏ(gu)传递或q回的指针是不透明的,q意味着该指针g技术角度讲是一个指针,但代码却不直接用它。相反,代码该指针q回l?Windows 以便随后q行重用?/p>
一个非常常见的例子是句柄的概c(din)在 Windows 中,内部数据l构Q从文g到屏q上的按钮)(j)在应用程序代码中都表CZؓ(f)句柄。句柄其实就是不透明的指针或有着指针宽度的数|应用E序用它来表C内部的 OS 构造?/p>
数情况下,API 函数也将不透明指针定义?PVOID ?LPVOID cd。在 Windows API 的定义中Q这些类型意思就是说该指针没有类型?/p>
当一个不透明指针q回l?zhn)的应用程序(或者?zhn)的应用程序期望得C个不透明指针Q时Q?zhn)应该参数或q回值封送ؓ(f) CLR 中的一U特D类?? System.IntPtr。当(zhn)?IntPtr cdӞ通常不?out ?ref 参数Q因?IntPtr 意ؓ(f)直接持有指针。不q,如果(zhn)将一个指针封送ؓ(f)一个指针,则对 IntPtr 使用 by-ref 参数是合适的?/p>
?CLR cdpȝ中,System.IntPtr cd有一个特D的属性。不像系l中的其他基cdQIntPtr q没有固定的大小。相反,它在q行时的大小是依底层操作pȝ的正常指针大而定的。这意味着?32 位的 Windows 中,IntPtr 变量的宽度是 32 位的Q而在 64 位的 Windows 中,实时~译器编译的代码?x)?IntPtr 值看?64 位的倹{当在托代码和非托代码之间封送不透明指针Ӟq种自动调节大小的特点十分有用?/p>
误住,Mq回或接受句柄的 API 函数其实操作的就是不透明指针。?zhn)的代码应该?Windows 中的句柄送成 System.IntPtr 倹{?/p>
(zhn)可以在托管代码中将 IntPtr 值强制{换ؓ(f) 32 位或 64 位的整数|或将后者强制{换ؓ(f)前者。然而,当?Windows API 函数Ӟ因ؓ(f)指针应是不透明的,所以除?jin)存储和传递给外部Ҏ(gu)外,不能它们另做它用。这U?#8220;只限存储和传?#8221;规则的两个特例是当?zhn)需要向外部Ҏ(gu)传? null 指针值和需要比?IntPtr g null 值的情况。ؓ(f)?jin)做到这一点,(zhn)不能将零强制{换ؓ(f) System.IntPtrQ而应该在 IntPtr cd上?Int32.Zero 静态公共字D,以便获得用于比较或赋值的 null 倹{?/p>
送文?/p>
在编E时l常要对文本数据q行处理。文本ؓ(f) interop 刉了(jin)一些麻?ch),q有两个原因。首先,底层操作pȝ可能使用 Unicode 来表C字W串Q也可能使用 ANSI。在极少数情况下Q例?MultiByteToWideChar API 函数的两个参数在字符集上是不一致的?/p>
W二个原因是Q当需要进?P/Invoke Ӟ要处理文本还需要特别了(jin)解到 C ?CLR 处理文本的方式是不同的。在 C 中,字符串实际上只是一个字W值数l,通常?null 作ؓ(f)l束W。大多数 Windows API 函数是按照以下条件处理字W串的:(x)对于 ANSIQ将其作为字W值数l;对于 UnicodeQ将其作为宽字符值数l?/p>
q运的是QCLR 被设计得相当灉|Q当送文本时问题得以L解决Q而不用在?Windows API 函数期望从?zhn)的应用程序得到的是什么。这里是一些需要记住的主要考虑事项Q?
• |
是?zhn)的应用程序?API 函数传递文本数据,q是 API 函数向?zhn)的应用程序返回字W串数据Q或者二者兼有? |
• |
(zhn)的外部Ҏ(gu)应该使用什么托类型? |
• |
API 函数期望得到的是什么格式的非托字W串Q?/p> |
我们首先解答最后一个问题。大多数 Windows API 函数都带?LPTSTR ?LPCTSTR 倹{(从函数角度看Q它们分别是可修改和不可修改的缓冲区Q包含以 null l束的字W数l?#8220;C”代表常数Q意味着使用该参C息不?x)传递到函数外部。LPTSTR 中的“T”表明该参数可以是 Unicode ? ANSIQ取决于(zhn)选择的字W集和底层操作系l的字符集。因为在 Windows API 中大多数字符串参数都是这两种cd之一Q所以只要在 DllImportAttribute 中选择 CharSet.AutoQCLR 按默认的方式工作?
然而,有些 API 函数或自定义?DLL 函数采用不同的方式表C字W串。如果?zhn)要用C个这L(fng)函数Q就可以采用 MarshalAsAttribute 修饰外部Ҏ(gu)的字W串参数Qƈ指明一U不同于默认 LPTSTR 的字W串格式。有? MarshalAsAttribute 的更多信息,请参阅位?MarshalAsAttribute Class ?Platform SDK 文档主题?/p>
现在让我们看一下字W串信息在?zhn)的代码和非托函C间传递的方向。有两种方式可以知道处理字符串时信息的传递方向。第一个也是最可靠的一个方法就 是首先理解参数的用途。例如,(zhn)正调用一个参敎ͼ它的名称cM CreateMutex q带有一个字W串Q则可以惛_该字W串信息是从应用E序? API 函数传递的。同Ӟ如果(zhn)调?GetUserNameQ则该函数的名称表明字符串信息是从该函数向?zhn)的应用程序传递的?/p>
除了(jin)q种比较合理的方法外Q第二种查找信息传递方向的方式是查找 API 参数cd中的字母“C”。例如,GetUserName API 函数的第一个参数被定义?LPTSTR cdQ它代表一个指?Unicode ?ANSI 字符串缓冲区的长指针。但?CreateMutex 的名U参数被cd化ؓ(f) LTCTSTR。请注意Q这里的cd定义是一L(fng)Q但增加一个字?#8220;C”来表明缓冲区为常敎ͼAPI 函数不能写入?/p>
一旦明了(jin)文本参数是只用作输入q是用作输入/输出Q就可以定使用哪种 CLR cd作ؓ(f)参数cd。这里有一些规则。如果字W串参数只用作输入,则?System.String cd。在托管代码中,字符串是不变的,适合用于不会(x)被本?API 函数更改的缓冲区?/p>
如果字符串参数可以用作输入和/或输出,则?System.StringBuilder cd。StringBuilder cd是一个很有用的类库类型,它可以帮助?zhn)有效地构建字W串Q也正好可以缓冲区传递给本机函数Q由本机函数为?zhn)填充字符串数据。一旦函数调用返回,(zhn)只 需要调?StringBuilder 对象?ToString 可以得C?String 对象?/p>
GetShortPathName API 函数能很好地用于昄什么时候?String、什么时候?StringBuilderQ因为它只带有三个参敎ͼ(x)一个输入字W串、一个输出字W串和一个指明输出缓冲区的字W长度的参数?/p>
?3 所CZؓ(f)加注释的非托?GetShortPathName 函数文档Q它同时指出?jin)输入和输出字符串参数。它引出?jin)托的外部?gu)定义Q也如图 3 所C。请注意W一个参数被送ؓ(f) System.StringQ因为它是一个只用作输入的参数。第二个参数代表一个输出缓冲区Q它使用?System.StringBuilder?/p>
结
本月专栏所介绍?P/Invoke 功能_调用 Windows 中的许多 API 函数。然而,如果(zhn)大量用? interopQ则?x)最l发现自己封送了(jin)很复杂的数据l构Q甚臛_能需要在托管代码中通过指针直接讉K内存。实际上Q本Z码中?interop 可以是一个将l节和低U比特藏在里面的真正的潘多拉盒子。CLR、C# 和托?C++ 提供?jin)许多有用的功能Q也总后我?x)在本专栏介l高U的 P/Invoke 话题?/p>
同时Q只要?zhn)觉?.NET Framework cd无法播放(zhn)的声音或者ؓ(f)(zhn)执行其他一些功能,(zhn)可以知道如何向原始而优U?Windows API L一些帮助?/p>
Figure 1 MessageBeep, Interop Done Well
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