使用费托管DLL函数
平台调用时一项服务,使托管代码能够调用动态链接库(DLL)中实现的非托管函数,例如Windows API中的非托管函数。此服务定位并调用导出的函数,并根据需要跨交互操作边界封送其自变量(整数、字符串、数组、结构等),
平台调用调用非托管函数时,将执行以下操作序列:
- 定位包含函数的 DLL。
- 将 DLL 加载到内存中。
- 在内存中定位函数的地址并将其参数推送到堆栈上,从而根据需要封送数据
- 将控制转移到非托管函数。
平台调用将向托管调用方引发托管函数生成的异常
.NET类库对应
标识DLL中的函数
DLL函数的标识由以下元素组成:
- 函数名称或序号
- 可以找到实现的DLL文件的名称
例如,指定User32.dll中的MessageBox函数可标识函数(MessageBox)及其位置(User32.dll,User32或user32)。Windows API可以包含每个处理字符或字符串的函数的两个版本:1字节字符的ANSI版本和2字节字符的Unicode版本,未指定时,由CharSet字段表示的字符集默认为ANSI。
MessageBoxA是MessageBox函数的ANSI入口点;MessageBoxW是Unicode版本??赏ü诵卸嘀置钚泄ぞ吡谐鎏囟―LL(例如user32.dll)的函数名称,例如使用dumpbin /exports user32.dll 或 link /dump /exports user32.dll来获取函数名称。Linux下使用objdump命令
可以将非托管函数重命名为代码内的任意名称,只要将新名称映射到DLL的原始入口点。
创建用于容纳DLL函数的类
将常用的DLL函数包装在托管类中,这是封装平台功能的一种有效方式。在一个类中,为每个要调用的DLL函数定义静态方法。定义中可以包含附加信息,例如传递方法参数使用的字符集和调用约定;如果省略这些信息,则选择默认设置。
在托管代码中创建原型
using System;
using System.Runtime.InteropServices;
internal static class NativeMethods
{
[DLLImport("user32.dll")]
internal static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);
}
包装之后,就可按照调用任何其他类上的静态方法的方式来调用该类上的方法。平台自动处理基础导出函数
为平台调用设计托管类时,应考虑类和DLL函数之间的关系。例如:
- 在现有类内声明DLL函数
- 分别为每个DLL函数创建一个类,使函数相互独立,易于查找
- 为一组相关DLL创建一个类,形成逻辑分组并减少开销
调用DLL函数
尽管调用非托管DLL函数与调用其他托管代码几乎完全相同,但又一些差异会使DLL函数一开始令人感到迷惑。
从平台调用返回的结构必须是在托管代码和非托管代码中表示形式相同的数据类型。这些类型成为blittable类型,因为它们不需要转换。
若要调用返回类型为non-blittable结构的函数,可定义与non-blittable类型大小相同的blittable帮助程序类型,并在函数返回后转换数据。
non-blittable类型和blittable类型
大多数数据类型在托管和非托管内存中具有共同的表示形式,并且不需要互操作封送处理程序进行特殊处理。这种类型称为blittable类型,因为它们在托管和非托管代码之间传递时不需要进行转换。
在非托管环境中,某些托管数据类型要求具有不同的表示形式。必须将这些non-blittable数据类型转换为可以封送的形式。
传递结构
许多未托管代码的函数希望你以函数的形式传递结构成员,或托管代码中定义的类的成员。使用平台调用将结构或类传递给非托管代码时,必须提供其他信息以保留原始布局和对齐方式。
回调函数
回调函数是托管应用程序中的代码,可帮助非托管DLL函数完成一项任务。对回调函数的调用间接从托管应用程序中进行传递、经过DLL函数,再回到托管实现。一些通过平台调用的DLL函数需要托管代码中的回调函数才能正常运行。
若要从托管代码中调用大部分DLL函数,则可以创建函数的托管定义,然后再调用它。
使用需要回调函数的DLL函数还有一些其他步骤。首先,必须通过查看函数的文档来确定该函数是否需要回调。然后,需要在托管应用程序中创建回调函数。最后,调用DLL函数,将指针作为一个参数传递给回调函数。
.Net Framework Application.Call passes a pointer to the callback function -> DLL function -> .Net Framework Application.Implementation of the callback function
回调函数非常适合用于需要重复执行一项任务的情况。另一个常见用法是与枚举函数配合使用,如Windows API中的"EnumFontFamilies","EnumPrinters"和"EnumWindows"。 EnumWindows函数通过计算机上所有现有的窗口进行枚举,调用每个窗口上的回调函数以执行任务。
如何:实现回调函数
下面的过程和示例演示使用平台调用的托管应用程序如何在本地计算机上打印每个窗口的句柄值。 具体而言,过程和示例使用"EnumWindows"函数来逐句通过窗口列表,使用托管回调函数(Callback)来打印窗口句柄的值
- 在进一步实现之前,请查看"EnumWindows"函数的签名,"EnumWindows"具有以下签名:
BOOL EnumWindows(wndenumproc lpEnumFunc, LPARAM lParam)
此函数需要回调的线索之一是存在"lpEnumFunc"自变量。经常可以看到在采用指向回调函数的指针的参数名称中"lp"(长指针)前缀与"Func"后缀结合在一起。
创建托管回调函数,此示例声明一个名为CallBack的委托类型,该类型采用两个自变量("hwnd"和"lparam") ,第一个参数是窗口的句柄;第二个参数是应用程序定义的。在此版本中,这两个自变量都必须是整数?;氐骱ǔ7祷胤橇阒道粗甘境晒?,返回零值来指示失败。此示例将返回值显式设置为"true"以继续进行枚举。
创建一个委托,并将其作为自变量传递到"EnumWindows"函数。平台调用自动将该委托转换为常见的回调格式。
确保在回调函数完成其工作之前,垃圾回收器不会回收委托。当将委托作为参数传递,或传递作为字段包括到结构中的委托时,在调用期间不会对其进行回收。因此,正如下面的枚举示例一样,调用返回并不再需要托管调用方执行任何其他操作之前,回调函数完成其工作。
using System;
using System.Runtime.InteropServices;
public delegate bool CallBack(int hwnd, int lParam);
public class EnumReportApp
{
[DllImport("user32")]
public static extern int EnumWindows(CallBack x, int y);
public static void Main()
{
CallBack myCallBack = new CallBack(EnumReportApp.Report);
EnumWindows(myCallBack, 0);
}
public static bool Report(int hwnd, int lParam)
{
Console.Write("Window handle is ");
Console.WriteLine(hwnd);
return true;
}
}
用平台调用封送数据
若要调用从非托管库中导出的函数,.NET Framework 应用程序需要托管代码中表示非托管函数的函数原型。 若要创建使平台调用能正确封送数据的原型,必须执行以下操作:
将 DllImportAttribute 特性应用于托管代码中的静态函数或方法。
用托管数据类型替换非托管数据类型。
通过应用具有可选字段的特性以及用托管数据类型替换非托管数据类型,可用附有非托管函数的文档来构造等效的托管原型。 有关如何应用 DllImportAttribute 的说明,请参阅使用非托管 DLL 函数。
平台调用 (P/Invoke)
using System;
using System.Runtime.InteropServices;
namespace PInvokeSamples
{
public static class Program
{
// Import the libc shared library and define the method
// corresponding to the native function.
[DllImport("libc.so.6")]
private static extern int getpid();
public static void Main(string[] args)
{
// Invoke the function and get the process ID.
int pid = getpid();
Console.WriteLine(pid);
}
}
}
DllImportAttribute类
Building a cross-platform C++ library to call from .NET Core
Interop.manual.Unix.cs
https://github.com/dotnet/corefx/src/System.Console/src/Interop/Interop.manual.Unix.cs