使用平台调用(P/Invoke)

来源:互联网 发布:淘宝换货延长收货时间 编辑:程序博客网 时间:2024/05/17 09:14

使用平台调用

 

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 的非托管编程接口的数量在持续减少,因此,在准备调用函数前检查一下是否有等价的托管函数,通常会节省大量时间。

0 0