C#调用非托管函数
当“平台调用”调用非托管函数时,它将依次执行以下操作:
1. 查找包含该函数的 DLL。
2. 将该 DLL 加载到内存中。
3. 查找函数在内存中的地址并将其参数推到堆栈上,以封送所需的数据。
只在第一次调用函数时,才会查找和加载 DLL 并查找函数在内存中的地址
4. 将控制权转移给非托管函数。
平台调用会向托管调用方引发由非托管函数生成的异常。
GDI32.dll
用于设备输出的图形设备接口 (GDI) 函数,例如用于绘图和字体管理的函数。
Kernel32.dll
用于内存管理和资源处理的低级别操作系统函数。
User32.dll
用于消息处理、计时器、菜单和通信的 Windows 管理函数。
DllImportAttribute.EntryPoint
以下列出了重命名 DLL 函数的可能原因:
· 避免使用区分大小写的 API 函数名
· 符合现行的命名标准
· 提供采用不同数据类型的函数(通过声明同一 DLL 函数的多个版本)
· 简化对包含 ANSI 和 Unicode 版本的 API 的使用
DllImportAttribute.CharSet
控制字符串封送处理并确定平台调用在 DLL 中查找函数名的方式。
[DllImport("dllname", CharSet=CharSet.Ansi)]
[DllImport("dllname", CharSet=CharSet.Unicode)]
[DllImport("dllname", CharSet=CharSet.Auto)]
StructLayoutAttribute
在使用平台调用将结构或类传递到非托管代码时,必须提供用来保留原始布局和对齐方式的附加信息。StructLayoutAttribute 属性,它用于定义格式化类型。对于托管结构和类,您可以从 LayoutKind 枚举提供的若干可预知的布局行为中进行选择。
平台调用声明的以下原则:
· 在非托管函数不要求任何间接寻址时使用按值传递的结构。
· 在非托管函数要求一级间接寻址时使用按引用传递或按类传递的结构。
· 在非托管函数要求二级间接寻址时使用按引用传递的类。
Sample;
1: BOOL PtInRect(const RECT *lprc, POINT pt);
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct Point {
public int x;
public int y;
}
[StructLayout(LayoutKind.Explicit)]
public struct Rect {
[FieldOffset(0)] public int left;
[FieldOffset(4)] public int top;
[FieldOffset(8)] public int right;
[FieldOffset(12)] public int bottom;
}
class Win32API {
[DllImport("User32.dll")]
public static extern bool PtInRect(ref Rect r, Point p);
}
[StructLayout(LayoutKind.Sequential)]
public class MySystemTime {
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
}
class Win32API {
[DllImport("Kernel32.dll")]
public static extern void GetSystemTime(MySystemTime st);
}
3: BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam)
表示此函数需要回调的线索之一是存在 lpEnumFunc 参数。如果参数采用指向回调函数的指针,其名称中通常会有 lp(长指针)前缀与 Func 后缀的组合。
实现回调函数
回调函数是托管应用程序中可帮助非托管 DLL 函数完成任务的代码。对回调函数的调用将从托管应用程序中,通过一个 DLL 函数,间接地传递给托管实现。首先,必须在文档中查阅该函数,确定该函数是否需要回调。接着,必须在托管应用程序中创建回调函数。最后,调用该 DLL 函数,并将指向回调函数的指针当作参数进行传递。
1. 开始实现之前,先查看 EnumWindows 函数的签名。EnumWindows 具有以下签名: BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam)
2. 表示此函数需要回调的线索之一是存在 lpEnumFunc 参数。如果参数采用指向回调函数的指针,其名称中通常会有 lp(长指针)前缀与 Func 后缀的组合。有关 Win32 函数的文档,请参见 Microsoft Platform SDK。
3. 创建托管回调函数。该示例声明一个名为 CallBack 的委托类型,此委托类型采用两个参数:hwnd 和 lparam。第一个参数是窗口的句柄;第二个参数由应用程序定义。在此版本中,这两个参数都必须是整数。
回调函数通常会返回非零值来表示成功,返回零来表示失败。本示例将返回值显式设置为 true,以继续进行枚举。
4. 创建一个委托,并将其作为参数传递给 EnumWindows 函数。平台调用会自动将委托转换为常见的回调格式。
5. 确保在回调函数完成其工作之前,垃圾回收器不会回收委托。如果委托作为参数进行传递,或者所包含的委托作为结构中的字段进行传递,则该委托在调用期间不会被回收。因此,正如下面的枚举示例所示,回调函数会在调用返回前完成其工作,而无需托管调用方执行额外的操作。
然而,如果可以在调用返回后调用回调函数,则托管调用方必须采取相应的措施来确保委托在回调函数完成其工作之前不会被回收。
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;
}
}
Marshal 类
Marshal类提供了一个方法集,这些方法用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法。
Marshal.Copy方法
Marshal.Copy方法将数据从托管数组复制到非托管内存指针,或从非托管内存指针复制到托管数组。