首页 > 代码库 > 使用平台调用(P/Invoke)
使用平台调用(P/Invoke)
使用平台调用
P/Invoke,它的全名叫平台调用(platform invoke),用于调用dll 中实现的非托管的单调(flat)编程接口,被称为使用C或C++ 调用约定(calling conventions)。最有名的例子是Win32 编程接口,这是一个巨大的库,它公开了Windows 所有的内置功能。
为了调用单调的非托管编程接口,必须首先定义准备调用的函数,可以分成两步:第一步,用System.Runtime.InteropServices 命名空间下的 DllImport 特性(attribute),能够定义包含想导入函数的 .dll,加上一些其他的可选特性;第二步,用关键字extern,加以 C 风格函数调用的签名,这样,指定了返回类型为F# 类型,和函数的名字,最后是用括号括起来的参数类型和参数名。结果这个函数就能像外部的.NET 方法一样进行调用。
下面的例子演示了如何导入Windows 函数MessageBeep,并调用:
open System.Runtime.InteropServices
// declare a function found in an external dll
[<DllImport("User32.dll")>]
extern boolMessageBeep(uint32 beepType)
// call this method ignoring the result
MessageBeep(0ul) |> ignore
注意
使用平台调用,最棘手的问题就是要找出要调用函数的签名。在http://pinvoke.net 网站上有 C# 和 VB .NET 中常用编程接口的签名的清单,F# 中需要的签名也相类似。这个站点是一个维基百科(wiki),因此可以自由添加 F# 签名。
下面的代码演示了如何使用平台调用,目标函数期望一个指针,有关设置指针需要注意几点。当定义函数时,需要在类型名字的后面加星号(*),表示传递指针;在函数调用之前,还要定义一个可变标识符,表示指针指向的内存区域,它可能不是全局的,但是在顶层,必须是函数定义的一部分。这就是为什么定义函数main,标识符status 是函数定义的一部分;最后,必须使用地址运算符(&&),保证传递给函数的是指针而不是值本身。
提示
编译这段代码总是有警告,因为使用了地址运算符(&&)。要抑制这个警告,可以使用编译器开关--nowarn 51,或者命令#nowarn 51。
openSystem.Runtime.InteropServices
// declare a function found in an external dll
[<DllImport("Advapi32.dll")>]
extern boolFileEncryptionStatus(string filename, uint32* status)
let main() =
//declare a mutable idenifier to be passed to the function
let mutable status = 0ul
// call thefunction, using the address of operator with the
// secondparameter
FileEncryptionStatus(@"C:\test.txt", && status) |>ignore
printfn"%d" status
main()
这个例子的运行结果如下(假设在 C: 盘根目录下有一个文件test.txt,加过密的):
1ul
注意
平台调用也可以运行在 Mono 平台上,语法与 F# 中的完全一样,而难点在于保证要调用的库在所有的目标平台上都可用,且遵循在所有不同的平台上库的不同的命名约定。更多有关解释的细节,请看http://www.mono-project.com/Interop_with_Native_Libraries上的文章。
DllImport 特性有一些有用的函数,能够设置用来控制如何调用非托管的函数。表 14-1 做了汇总。
表 14-1 DllImport 上有用的特性
特性名 | 描述 |
CharSet | 定义了传送字符串数据的字符集,可以是 CharSet.Auto、CharSet.Ansi、CharSet.Unicode |
EntryPoint | 设置调用函数的名字。如果没有给定名字,那么,关键字 extern 后面的名字就作为默认定义的函数名。 |
SetLastError | 这是一个逻辑值,指定是否遇到任何错误都应该传送,因此,通过调用 Marshell.GetLastWin32Error() 方法检查可用性。 |
注意
因为有 COM 组件,没有等价的.NET 的非托管编程接口的数量在持续减少,因此,在准备调用函数前检查一下是否有等价的托管函数,通常会节省大量时间。