许多非托管函数需要您将结构(Visual Basic 中的用户定义类型)的成员或在托管代码中定义的类成员作为参数传递给函数。在使用平台调用将结构或类传递到非托管代码时,必须提供用来保留原始布局和对齐方式的附加信息。本主题将介绍 属性,它用于定义格式化类型。对于托管结构和类,您可以从 LayoutKind 枚举提供的若干可预知的布局行为中进行选择。
本主题中提供的概念的核心是结构类型和类类型之间的重要区别。结构是值类型,类是引用类型 - 类始终提供至少一级内存间接寻址(指向某一值的指针)。因为非托管函数经常要求间接寻址,所以这一区别是十分重要的,如下表中第一列的签名所示。其余列中的托管结构和类声明显示在声明中可以调整的间接寻址级别的程度。
非托管签名 | 托管声明: 无间接寻址
| 托管声明: 一级间接寻址
|
---|---|---|
DoWork(MyStruct x); 要求零级间接寻址。 | DoWork(ByVal x As MyStruct) 增加零级间接寻址。 | 不可能,因为已有一级间接寻址。 |
DoWork(MyStruct* x); 要求一级间接寻址。 | DoWork(ByRef x As MyStruct) 增加一级间接寻址。 | DoWork(ByVal x As MyStruct) 增加零级间接寻址。 |
DoWork(MyStruct** x); 要求二级间接寻址。 | 不可能,因为不能使用 ByRef ByRef。 | DoWork(ByRef x As MyStruct) 增加一级间接寻址。 |
该表描述用于平台调用声明的以下原则:
- 在非托管函数不要求任何间接寻址时使用按值传递的结构。
- 在非托管函数要求一级间接寻址时使用按引用传递或按类传递的结构。
- 在非托管函数要求二级间接寻址时使用按引用传递的类。
声明和传递结构
以下示例将显示如何在托管代码中定义 Point
和 Rect
结构,并将这些类型作为参数传递给 User32.dll 文件中的 PtInRect 函数。PtInRect 具有以下非托管签名:
BOOL PtInRect(const RECT *lprc, POINT pt);
请注意,由于函数需要指向 RECT 类型的指针,必须通过引用来传递 Rect 结构。
[Visual Basic]
Imports System.Runtime.InteropServices
<StructLayout(LayoutKind.Sequential)> Public Structure Point
Public x As Integer
Public y As Integer
End Structure
Public Structure <StructLayout(LayoutKind.Explicit)> Rect
<FieldOffset(0)> Public left As Integer
<FieldOffset(4)> Public top As Integer
<FieldOffset(8)> Public right As Integer
<FieldOffset(12)> Public bottom As Integer
End Structure
Class Win32API
Declare Auto Function PtInRect Lib "user32.dll" _
(ByRef r As Rect, p As Point) As Boolean
End Class
[C#] 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);
}
声明和传递类
只要类具有固定的成员布局,就可以将类的成员传递给非托管的 DLL 函数。以下示例将说明如何将 MySystemTime
类的成员(按连续的顺序定义)传递给 User32.dll 文件中的 GetSystemTime。GetSystemTime 具有以下非托管签名:
void GetSystemTime(SYSTEMTIME* SystemTime);
与值类型不同,类始终具有至少一级间接寻址。
[Visual Basic]
Imports System.Runtime.InteropServices
Imports Microsoft.VisualBasic
<StructLayout(LayoutKind.Sequential)> Public Class MySystemTime
Public wYear As Short
Public wMonth As Short
Public wDayOfWeek As Short
Public wDay As Short
Public wHour As Short
Public wMinute As Short
Public wSecond As Short
Public wMiliseconds As Short
End Class
Public Class Win32
Declare Auto Sub GetSystemTime Lib "Kernel32.dll"(sysTime _
As MySystemTime)
Declare Auto Function MessageBox Lib "User32.dll"(hWnd As Integer, _
txt As String, caption As String, Typ As Integer) As Integer
End Class
Public Class TestPlatformInvoke
Public Shared Sub Main()
Dim sysTime As New MySystemTime()
Win32.GetSystemTime(sysTime)
Dim dt As String
dt = "System time is:" & ControlChars.CrLf & _
"Year: " & sysTime.wYear & _
ControlChars.CrLf & "Month: " & sysTime.wMonth & _
ControlChars.CrLf & "DayOfWeek: " & sysTime.wDayOfWeek & _
ControlChars.CrLf & "Day: " & sysTime.wDay
Win32.MessageBox(0, dt, "Platform Invoke Sample", 0)
End Sub
End Class
[C#] [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);
}
现在才知道,原来调用时还可以传类,而且在API中要求为二级指针时还必需按引用传递类(ref),在一级指针时可以按引用传调结构,而又因为
指向类的变量本身就是引用,所以要按值传送指向类的变量.而只有在API为非指针时才能且只能使用结构传递.
另外,只有类才完全按声明定义顺序的.结构需要用LayoutKind指明,具体还不清楚,需要继续学习.