Marshallen von Klassen, Strukturen und Unions
Klassen und Strukturen sind in .NET Framework ähnlich. Beide können Felder, Eigenschaften und Ereignisse enthalten. Sie können auch über statische und nicht statische Methoden verfügen. Ein deutlicher Unterschied ist, dass Strukturen Werttypen sind, während Klassen Verweistypen sind.
In der folgende Tabelle werden Marshallingoptionen für Klassen, Strukturen und Unions aufgelistet. Ihre Verwendung wird beschrieben, und es werden Links zu den entsprechenden Plattformaufrufbeispielen bereitgestellt.
Typ | Beschreibung | Beispiel |
---|---|---|
Klasse als Wert. | Übergibt eine Klasse mit ganzzahligen Membern als In/Out-Parameter, wie der verwaltete Fall. | SysTime-Beispiel |
Struktur als Wert. | Übergibt Strukturen als In-Parameter. | Beispiel für Strukturen |
Struktur als Verweis. | Übergibt Strukturen als In/Out-Parameter. | OSInfo-Beispiel |
Struktur mit geschachtelten Strukturen (vereinfacht). | Übergibt eine Klasse, die eine Struktur mit geschachtelten Strukturen in der nicht verwalteten Funktion darstellt. Die Struktur wird zu einer einzigen großen Struktur im verwalteten Prototyp vereinfacht. | FindFile-Beispiel |
Struktur mit einem Zeiger auf eine andere Struktur. | Übergibt eine Struktur, die einen Zeiger auf eine zweite Struktur enthält, als Member. | Beispiel für Strukturen |
Array von Strukturen mit ganzen Zahlen als Wert. | Übergibt ein aus Strukturen bestehendes Array, das nur ganze Zahlen enthält, als In-/Out-Parameter. Member des Arrays können geändert werden. | Beispiel für Arrays |
Array von Strukturen mit ganzen Zahlen und Zeichenfolgen als Verweis. | Übergibt ein Array von Strukturen, die ganze Zahlen und Zeichenfolgen enthalten, als Out-Parameter. Die aufgerufene Funktion weist Speicher für das Array zu. | OutArrayOfStructs-Beispiel |
Unions mit Werttypen. | Übergibt Unions mit Werttypen (Integer und Double). | Unions-Beispiel |
Unions mit gemischten Typen. | Übergibt Unions mit gemischten Typen (Integer und String). | Unions-Beispiel |
Struktur mit plattformspezifischem Layout. | Übergibt einen Typ mit Definitionen für natives Packen. | Plattformbeispiel |
Nullwerte in Struktur. | Übergibt einen NULL-Verweis (Nothing in Visual Basic) anstelle eines Verweises auf einen Werttyp. | HandleRef-Beispiel |
Beispiel für Strukturen
Dieses Beispiel veranschaulicht das Übergeben einer Struktur, die auf eine zweite Struktur zeigt, einer Struktur mit einer eingebetteten Struktur sowie einer Struktur mit einem eingebetteten Array.
Das Beispiel für Strukturen verwendet die folgenden nicht verwalteten Funktionen, die jeweils zusammen mit ihrer ursprünglichen Funktionsdeklaration aufgeführt werden:
TestStructInStruct aus „PinvokeLib.dll“ exportiert.
int TestStructInStruct(MYPERSON2* pPerson2);
TestStructInStruct3 aus „PinvokeLib.dll“ exportiert.
void TestStructInStruct3(MYPERSON3 person3);
TestStructInStruct aus „PinvokeLib.dll“ exportiert.
void TestArrayInStruct(MYARRAYSTRUCT* pStruct);
PinvokeLib.dll ist eine benutzerdefinierte, nicht verwaltete Bibliothek, die Implementierungen für die zuvor aufgelisteten Funktionen und vier Strukturen enthält: MYPERSON, MYPERSON2, MYPERSON3 und MYARRAYSTRUCT. Diese Strukturen enthalten die folgenden Elemente:
typedef struct _MYPERSON
{
char* first;
char* last;
} MYPERSON, *LP_MYPERSON;
typedef struct _MYPERSON2
{
MYPERSON* person;
int age;
} MYPERSON2, *LP_MYPERSON2;
typedef struct _MYPERSON3
{
MYPERSON person;
int age;
} MYPERSON3;
typedef struct _MYARRAYSTRUCT
{
bool flag;
int vals[ 3 ];
} MYARRAYSTRUCT;
Die verwalteten Strukturen MyPerson
, MyPerson2
, MyPerson3
und MyArrayStruct
besitzen folgende Eigenschaften:
MyPerson
enthält nur Zeichenfolgenmember. Das CharSet-Feld legt die Zeichenfolgen auf das ANSI-Format fest, wenn sie an die nicht verwaltete Funktion übergeben werden.MyPerson2
enthält eine IntPtr in derMyPerson
-Struktur. Der IntPtr-Typ ersetzt den ursprünglichen Zeiger auf die nicht verwaltete Struktur, weil .NET Framework-Anwendungen nur dann Zeiger verwenden, wenn der Code als unsicher gekennzeichnet ist.MyPerson3
enthältMyPerson
als eingebettete Struktur. Eine in eine Struktur eingebettete andere Struktur kann vereinfacht werden, indem die Elemente der eingebetteten Struktur direkt in der Hauptstruktur platziert werden. Alternativ kann die Struktur auch eingebettet bleiben, wie in diesem Beispiel gezeigt.MyArrayStruct
enthält ein Array von ganzen Zahlen. Das MarshalAsAttribute-Attribut legt den UnmanagedType-Enumerationswert auf ByValArray fest, womit die Anzahl der Elemente im Array angegeben wird.
Für alle Strukturen in diesem Beispiel wird das StructLayoutAttribute-Attribut angewendet, um sicherzustellen, dass die Member im Speicher sequenziell in der Reihenfolge ihres Auftretens angeordnet werden.
Die NativeMethods
-Klasse enthält verwaltete Prototypen für die Methoden TestStructInStruct
, TestStructInStruct3
und TestArrayInStruct
, die von der App
-Klasse aufgerufen werden. Jeder Prototyp deklariert einen einzelnen Parameter wie folgt:
TestStructInStruct
deklariert einen Verweis auf den TypMyPerson2
als ihren Parameter.TestStructInStruct3
deklariert den TypMyPerson3
als ihren Parameter und übergibt den Parameter als Wert.TestArrayInStruct
deklariert einen Verweis auf den TypMyArrayStruct
als ihren Parameter.
Strukturen als Argumente von Methoden werden als Wert übergeben, es sei denn, der Parameter enthält das Ref-Schlüsselwort (ByRef in Visual Basic). Zum Beispiel übergibt die TestStructInStruct
-Methode einen Verweis (den Wert einer Adresse) auf ein Objekt vom Typ MyPerson2
an nicht verwalteten Code. Um die Struktur zu bearbeiten, auf die MyPerson2
zeigt, erstellt das Beispiel einen Puffer einer angegebenen Größe und gibt dessen Adresse zurück, indem die Methoden Marshal.AllocCoTaskMem und Marshal.SizeOf kombiniert werden. Anschließend kopiert das Beispiel den Inhalt der verwalteten Struktur in den nicht verwalteten Puffer. Zum Schluss verwendet das Beispiel die Marshal.PtrToStructure-Methode für das Marshalling von Daten aus dem nicht verwalteten Puffer in ein verwaltetes Objekt sowie die Marshal.FreeCoTaskMem-Methode, um den nicht verwalteten Speicherblock freizugeben.
Deklarieren von Prototypen
// Declares a managed structure for each unmanaged structure.
[StructLayout(LayoutKind::Sequential, CharSet = CharSet::Ansi)]
public value struct MyPerson
{
public:
String^ first;
String^ last;
};
[StructLayout(LayoutKind::Sequential)]
public value struct MyPerson2
{
public:
IntPtr person;
int age;
};
[StructLayout(LayoutKind::Sequential)]
public value struct MyPerson3
{
public:
MyPerson person;
int age;
};
[StructLayout(LayoutKind::Sequential)]
public value struct MyArrayStruct
{
public:
bool flag;
[MarshalAs(UnmanagedType::ByValArray, SizeConst = 3)]
array<int>^ vals;
};
private ref class NativeMethods
{
public:
// Declares a managed prototype for unmanaged function.
[DllImport("..\\LIB\\PinvokeLib.dll")]
static int TestStructInStruct(MyPerson2% person2);
[DllImport("..\\LIB\\PinvokeLib.dll")]
static int TestStructInStruct3(MyPerson3 person3);
[DllImport("..\\LIB\\PinvokeLib.dll")]
static int TestArrayInStruct(MyArrayStruct% myStruct);
};
// Declares a managed structure for each unmanaged structure.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MyPerson
{
public string first;
public string last;
}
[StructLayout(LayoutKind.Sequential)]
public struct MyPerson2
{
public IntPtr person;
public int age;
}
[StructLayout(LayoutKind.Sequential)]
public struct MyPerson3
{
public MyPerson person;
public int age;
}
[StructLayout(LayoutKind.Sequential)]
public struct MyArrayStruct
{
public bool flag;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public int[] vals;
}
internal static class NativeMethods
{
// Declares a managed prototype for unmanaged function.
[DllImport("..\\LIB\\PinvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern int TestStructInStruct(ref MyPerson2 person2);
[DllImport("..\\LIB\\PinvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern int TestStructInStruct3(MyPerson3 person3);
[DllImport("..\\LIB\\PinvokeLib.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern int TestArrayInStruct(ref MyArrayStruct myStruct);
}
' Declares a managed structure for each unmanaged structure.
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
Public Structure MyPerson
Public first As String
Public last As String
End Structure
<StructLayout(LayoutKind.Sequential)>
Public Structure MyPerson2
Public person As IntPtr
Public age As Integer
End Structure
<StructLayout(LayoutKind.Sequential)>
Public Structure MyPerson3
Public person As MyPerson
Public age As Integer
End Structure
<StructLayout(LayoutKind.Sequential)>
Public Structure MyArrayStruct
Public flag As Boolean
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=3)>
Public vals As Integer()
End Structure
Friend Class NativeMethods
' Declares managed prototypes for unmanaged functions.
<DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
Friend Shared Function TestStructInStruct(
ByRef person2 As MyPerson2) As Integer
End Function
<DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
Friend Shared Function TestStructInStruct3(
ByVal person3 As MyPerson3) As Integer
End Function
<DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
Friend Shared Function TestArrayInStruct(
ByRef myStruct As MyArrayStruct) As Integer
End Function
End Class
Aufrufen von Funktionen
public ref class App
{
public:
static void Main()
{
// Structure with a pointer to another structure.
MyPerson personName;
personName.first = "Mark";
personName.last = "Lee";
MyPerson2 personAll;
personAll.age = 30;
IntPtr buffer = Marshal::AllocCoTaskMem(Marshal::SizeOf(personName));
Marshal::StructureToPtr(personName, buffer, false);
personAll.person = buffer;
Console::WriteLine("\nPerson before call:");
Console::WriteLine("first = {0}, last = {1}, age = {2}",
personName.first, personName.last, personAll.age);
int res = NativeMethods::TestStructInStruct(personAll);
MyPerson personRes =
(MyPerson)Marshal::PtrToStructure(personAll.person,
MyPerson::typeid);
Marshal::FreeCoTaskMem(buffer);
Console::WriteLine("Person after call:");
Console::WriteLine("first = {0}, last = {1}, age = {2}",
personRes.first, personRes.last, personAll.age);
// Structure with an embedded structure.
MyPerson3 person3;// = gcnew MyPerson3();
person3.person.first = "John";
person3.person.last = "Evans";
person3.age = 27;
NativeMethods::TestStructInStruct3(person3);
// Structure with an embedded array.
MyArrayStruct myStruct;// = new MyArrayStruct();
myStruct.flag = false;
myStruct.vals = gcnew array<int>(3);
myStruct.vals[0] = 1;
myStruct.vals[1] = 4;
myStruct.vals[2] = 9;
Console::WriteLine("\nStructure with array before call:");
Console::WriteLine(myStruct.flag);
Console::WriteLine("{0} {1} {2}", myStruct.vals[0],
myStruct.vals[1], myStruct.vals[2]);
NativeMethods::TestArrayInStruct(myStruct);
Console::WriteLine("\nStructure with array after call:");
Console::WriteLine(myStruct.flag);
Console::WriteLine("{0} {1} {2}", myStruct.vals[0],
myStruct.vals[1], myStruct.vals[2]);
}
};
public class App
{
public static void Main()
{
// Structure with a pointer to another structure.
MyPerson personName;
personName.first = "Mark";
personName.last = "Lee";
MyPerson2 personAll;
personAll.age = 30;
IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(personName));
Marshal.StructureToPtr(personName, buffer, false);
personAll.person = buffer;
Console.WriteLine("\nPerson before call:");
Console.WriteLine("first = {0}, last = {1}, age = {2}",
personName.first, personName.last, personAll.age);
int res = NativeMethods.TestStructInStruct(ref personAll);
MyPerson personRes =
(MyPerson)Marshal.PtrToStructure(personAll.person,
typeof(MyPerson));
Marshal.FreeCoTaskMem(buffer);
Console.WriteLine("Person after call:");
Console.WriteLine("first = {0}, last = {1}, age = {2}",
personRes.first, personRes.last, personAll.age);
// Structure with an embedded structure.
MyPerson3 person3 = new MyPerson3();
person3.person.first = "John";
person3.person.last = "Evans";
person3.age = 27;
NativeMethods.TestStructInStruct3(person3);
// Structure with an embedded array.
MyArrayStruct myStruct = new MyArrayStruct();
myStruct.flag = false;
myStruct.vals = new int[3];
myStruct.vals[0] = 1;
myStruct.vals[1] = 4;
myStruct.vals[2] = 9;
Console.WriteLine("\nStructure with array before call:");
Console.WriteLine(myStruct.flag);
Console.WriteLine("{0} {1} {2}", myStruct.vals[0],
myStruct.vals[1], myStruct.vals[2]);
NativeMethods.TestArrayInStruct(ref myStruct);
Console.WriteLine("\nStructure with array after call:");
Console.WriteLine(myStruct.flag);
Console.WriteLine("{0} {1} {2}", myStruct.vals[0],
myStruct.vals[1], myStruct.vals[2]);
}
}
Public Class App
Public Shared Sub Main()
' Structure with a pointer to another structure.
Dim personName As MyPerson
personName.first = "Mark"
personName.last = "Lee"
Dim personAll As MyPerson2
personAll.age = 30
Dim buffer As IntPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(
personName))
Marshal.StructureToPtr(personName, buffer, False)
personAll.person = buffer
Console.WriteLine(ControlChars.CrLf & "Person before call:")
Console.WriteLine("first = {0}, last = {1}, age = {2}",
personName.first, personName.last, personAll.age)
Dim res As Integer = NativeMethods.TestStructInStruct(personAll)
Dim personRes As MyPerson =
CType(Marshal.PtrToStructure(personAll.person,
GetType(MyPerson)), MyPerson)
Marshal.FreeCoTaskMem(buffer)
Console.WriteLine("Person after call:")
Console.WriteLine("first = {0}, last = {1}, age = {2}",
personRes.first,
personRes.last, personAll.age)
' Structure with an embedded structure.
Dim person3 As New MyPerson3()
person3.person.first = "John"
person3.person.last = "Evans"
person3.age = 27
NativeMethods.TestStructInStruct3(person3)
' Structure with an embedded array.
Dim myStruct As New MyArrayStruct()
myStruct.flag = False
Dim array(2) As Integer
myStruct.vals = array
myStruct.vals(0) = 1
myStruct.vals(1) = 4
myStruct.vals(2) = 9
Console.WriteLine(vbNewLine + "Structure with array before call:")
Console.WriteLine(myStruct.flag)
Console.WriteLine("{0} {1} {2}", myStruct.vals(0),
myStruct.vals(1), myStruct.vals(2))
NativeMethods.TestArrayInStruct(myStruct)
Console.WriteLine(vbNewLine + "Structure with array after call:")
Console.WriteLine(myStruct.flag)
Console.WriteLine("{0} {1} {2}", myStruct.vals(0),
myStruct.vals(1), myStruct.vals(2))
End Sub
End Class
FindFile-Beispiel
Dieses Beispiel demonstriert, wie eine Struktur, die eine zweite, eingebettete Struktur enthält, an eine nicht verwaltete Funktion übergeben wird. Außerdem wird veranschaulicht, wie Sie das MarshalAsAttribute-Attribut verwenden, um ein Arrays mit fester Länge innerhalb der Struktur zu deklarieren. In diesem Beispiel werden die Elemente der eingebetteten Struktur der übergeordneten Struktur hinzugefügt. Ein Beispiel für eine nicht vereinfachte eingebettete Struktur finden Sie unter Beispiel für Strukturen.
Das "FindFile"-Beispiel verwendet die folgende nicht verwaltete Funktion, die zusammen mit ihrer ursprünglichen Funktionsdeklaration aufgeführt wird:
FindFirstFile aus „Kernel32.dll“ exportiert.
HANDLE FindFirstFile(LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData);
Die ursprüngliche, an die Funktion übergebene Struktur enthält die folgenden Elemente:
typedef struct _WIN32_FIND_DATA
{
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
TCHAR cFileName[ MAX_PATH ];
TCHAR cAlternateFileName[ 14 ];
} WIN32_FIND_DATA, *PWIN32_FIND_DATA;
In diesem Beispiel enthält die FindData
-Klasse einen entsprechenden Datenmember für jedes Element der Originalstruktur und der eingebetteten Struktur. Anstelle zweier ursprünglicher Zeichenpuffer ersetzt die Klasse Zeichenfolgen. Das MarshalAsAttribute-Attribut legt die UnmanagedType-Enumeration auf ByValTStr fest. Dieser Wert wird zum Identifizieren der Inlinezeichenarrays mit fester Länge verwendet, die innerhalb der nicht verwalteten Strukturen erscheinen.
Die NativeMethods
-Klasse enthält einen verwalteten Prototyp der FindFirstFile
-Methode, der die FindData
-Klasse als Parameter übergibt. Der Parameter muss mit den Attributen InAttribute und OutAttribute deklariert werden, weil Klassen, die Verweistypen sind, standardmäßig als In-Parameter übergeben werden.
Deklarieren von Prototypen
// Declares a class member for each structure element.
[StructLayout(LayoutKind::Sequential, CharSet = CharSet::Auto)]
public ref class FindData
{
public:
int fileAttributes;
// creationTime was an embedded FILETIME structure.
int creationTime_lowDateTime;
int creationTime_highDateTime;
// lastAccessTime was an embedded FILETIME structure.
int lastAccessTime_lowDateTime;
int lastAccessTime_highDateTime;
// lastWriteTime was an embedded FILETIME structure.
int lastWriteTime_lowDateTime;
int lastWriteTime_highDateTime;
int nFileSizeHigh;
int nFileSizeLow;
int dwReserved0;
int dwReserved1;
[MarshalAs(UnmanagedType::ByValTStr, SizeConst = 260)]
String^ fileName;
[MarshalAs(UnmanagedType::ByValTStr, SizeConst = 14)]
String^ alternateFileName;
};
private ref class NativeMethods
{
public:
// Declares a managed prototype for the unmanaged function.
[DllImport("Kernel32.dll", CharSet = CharSet::Auto)]
static IntPtr FindFirstFile(String^ fileName, [In, Out]
FindData^ findFileData);
};
// Declares a class member for each structure element.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class FindData
{
public int fileAttributes = 0;
// creationTime was an embedded FILETIME structure.
public int creationTime_lowDateTime = 0;
public int creationTime_highDateTime = 0;
// lastAccessTime was an embedded FILETIME structure.
public int lastAccessTime_lowDateTime = 0;
public int lastAccessTime_highDateTime = 0;
// lastWriteTime was an embedded FILETIME structure.
public int lastWriteTime_lowDateTime = 0;
public int lastWriteTime_highDateTime = 0;
public int nFileSizeHigh = 0;
public int nFileSizeLow = 0;
public int dwReserved0 = 0;
public int dwReserved1 = 0;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string fileName = null;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string alternateFileName = null;
}
internal static class NativeMethods
{
// Declares a managed prototype for the unmanaged function.
[DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr FindFirstFile(
string fileName, [In, Out] FindData findFileData);
}
' Declares a class member for each structure element.
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
Public Class FindData
Public fileAttributes As Integer = 0
' creationTime was a by-value FILETIME structure.
Public creationTime_lowDateTime As Integer = 0
Public creationTime_highDateTime As Integer = 0
' lastAccessTime was a by-value FILETIME structure.
Public lastAccessTime_lowDateTime As Integer = 0
Public lastAccessTime_highDateTime As Integer = 0
' lastWriteTime was a by-value FILETIME structure.
Public lastWriteTime_lowDateTime As Integer = 0
Public lastWriteTime_highDateTime As Integer = 0
Public nFileSizeHigh As Integer = 0
Public nFileSizeLow As Integer = 0
Public dwReserved0 As Integer = 0
Public dwReserved1 As Integer = 0
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)>
Public fileName As String = Nothing
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=14)>
Public alternateFileName As String = Nothing
End Class
Friend Class NativeMethods
' Declares a managed prototype for the unmanaged function.
Friend Declare Auto Function FindFirstFile Lib "Kernel32.dll" (
ByVal fileName As String, <[In], Out> ByVal findFileData As _
FindData) As IntPtr
End Class
Aufrufen von Funktionen
public ref class App
{
public:
static void Main()
{
FindData^ fd = gcnew FindData();
IntPtr handle = NativeMethods::FindFirstFile("C:\\*.*", fd);
Console::WriteLine("The first file: {0}", fd->fileName);
}
};
public class App
{
public static void Main()
{
FindData fd = new FindData();
IntPtr handle = NativeMethods.FindFirstFile("C:\\*.*", fd);
Console.WriteLine($"The first file: {fd.fileName}");
}
}
Public Class App
Public Shared Sub Main()
Dim fd As New FindData()
Dim handle As IntPtr = NativeMethods.FindFirstFile("C:\*.*", fd)
Console.WriteLine($"The first file: {fd.fileName}")
End Sub
End Class
Unions-Beispiel
Dieses Beispiel veranschaulicht, wie Strukturen, die nur Werttypen enthalten, sowie Strukturen, die einen Werttyp und eine Zeichenfolge enthalten, als Parameter an eine nicht verwaltete Funktion übergeben werden, die eine Union erwartet. Eine Union stellt einen Speicherbereich dar, der von zwei oder mehr Variablen gemeinsam genutzt werden kann.
Das "Unions"-Beispiel verwendet die folgende nicht verwaltete Funktion, die zusammen mit ihrer ursprünglichen Funktionsdeklaration aufgeführt wird:
TestUnion aus „PinvokeLib.dll“ exportiert.
void TestUnion(MYUNION u, int type);
PinvokeLib.dll ist eine benutzerdefinierte, nicht verwaltete Bibliothek, die eine Implementierung für die zuvor aufgelistete Funktion und zwei Unions enthält: MYUNION und MYUNION2. Die Union enthält die folgenden Elemente:
union MYUNION
{
int number;
double d;
}
union MYUNION2
{
int i;
char str[128];
};
In verwaltetem Code sind Unions als Strukturen definiert. Die MyUnion
-Struktur enthält zwei Werttypen als Member: einen Integer- und einen Double-Wert. Das StructLayoutAttribute-Attribut wird festgelegt, um die genaue Position der einzelnen Datenmember zu kontrollieren. Das FieldOffsetAttribute-Attribut stellt die physische Position von Feldern innerhalb der nicht verwalteten Darstellung einer Union bereit. Beachten Sie, dass beide Member denselben Offsetwert besitzen, sodass die Member denselben Speicherbereich definieren können.
MyUnion2_1
und MyUnion2_2
enthalten einen Werttyp (Integer) bzw. eine Zeichenfolge. In verwaltetem Code dürfen Werttypen und Verweistypen nicht überlappen. Dieses Beispiel verwendet das Überladen von Methoden, um dem Aufrufer die Verwendung beider Typen zu ermöglichen, wenn dieselbe nicht verwaltete Funktion aufgerufen wird. Das Layout von MyUnion2_1
ist explizit und besitzt einen genauen Offsetwert. Im Gegensatz dazu verfügt MyUnion2_2
über ein sequenzielles Layout, da explizite Layouts für Verweistypen nicht zulässig sind. Das MarshalAsAttribute-Attribut legt die UnmanagedType-Enumeration auf ByValTStr fest. Dieser Wert wird zum Identifizieren der Inlinezeichenarrays mit fester Länge verwendet, die innerhalb der nicht verwalteten Darstellung der Union erscheinen.
Die NativeMethods
-Klasse enthält die Prototypen für die Methoden TestUnion
und TestUnion2
. TestUnion2
wird überladen, um MyUnion2_1
oder MyUnion2_2
als Parameter zu deklarieren.
Deklarieren von Prototypen
// Declares managed structures instead of unions.
[StructLayout(LayoutKind::Explicit)]
public value struct MyUnion
{
public:
[FieldOffset(0)]
int i;
[FieldOffset(0)]
double d;
};
[StructLayout(LayoutKind::Explicit, Size = 128)]
public value struct MyUnion2_1
{
public:
[FieldOffset(0)]
int i;
};
[StructLayout(LayoutKind::Sequential)]
public value struct MyUnion2_2
{
public:
[MarshalAs(UnmanagedType::ByValTStr, SizeConst = 128)]
String^ str;
};
private ref class NativeMethods
{
public:
// Declares managed prototypes for unmanaged function.
[DllImport("..\\LIB\\PInvokeLib.dll")]
static void TestUnion(MyUnion u, int type);
[DllImport("..\\LIB\\PInvokeLib.dll")]
static void TestUnion2(MyUnion2_1 u, int type);
[DllImport("..\\LIB\\PInvokeLib.dll")]
static void TestUnion2(MyUnion2_2 u, int type);
};
// Declares managed structures instead of unions.
[StructLayout(LayoutKind.Explicit)]
public struct MyUnion
{
[FieldOffset(0)]
public int i;
[FieldOffset(0)]
public double d;
}
[StructLayout(LayoutKind.Explicit, Size = 128)]
public struct MyUnion2_1
{
[FieldOffset(0)]
public int i;
}
[StructLayout(LayoutKind.Sequential)]
public struct MyUnion2_2
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string str;
}
internal static class NativeMethods
{
// Declares managed prototypes for unmanaged function.
[DllImport("..\\LIB\\PInvokeLib.dll")]
internal static extern void TestUnion(MyUnion u, int type);
[DllImport("..\\LIB\\PInvokeLib.dll")]
internal static extern void TestUnion2(MyUnion2_1 u, int type);
[DllImport("..\\LIB\\PInvokeLib.dll")]
internal static extern void TestUnion2(MyUnion2_2 u, int type);
}
' Declares managed structures instead of unions.
<StructLayout(LayoutKind.Explicit)>
Public Structure MyUnion
<FieldOffset(0)> Public i As Integer
<FieldOffset(0)> Public d As Double
End Structure
<StructLayout(LayoutKind.Explicit, Size:=128)>
Public Structure MyUnion2_1
<FieldOffset(0)> Public i As Integer
End Structure
<StructLayout(LayoutKind.Sequential)>
Public Structure MyUnion2_2
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=128)>
Public str As String
End Structure
Friend Class NativeMethods
' Declares managed prototypes for unmanaged function.
<DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
Friend Shared Sub TestUnion(
ByVal u As MyUnion, ByVal type As Integer)
End Sub
<DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
Friend Overloads Shared Sub TestUnion2(
ByVal u As MyUnion2_1, ByVal type As Integer)
End Sub
<DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
Friend Overloads Shared Sub TestUnion2(
ByVal u As MyUnion2_2, ByVal type As Integer)
End Sub
End Class
Aufrufen von Funktionen
public ref class App
{
public:
static void Main()
{
MyUnion mu;// = new MyUnion();
mu.i = 99;
NativeMethods::TestUnion(mu, 1);
mu.d = 99.99;
NativeMethods::TestUnion(mu, 2);
MyUnion2_1 mu2_1;// = new MyUnion2_1();
mu2_1.i = 99;
NativeMethods::TestUnion2(mu2_1, 1);
MyUnion2_2 mu2_2;// = new MyUnion2_2();
mu2_2.str = "*** string ***";
NativeMethods::TestUnion2(mu2_2, 2);
}
};
public class App
{
public static void Main()
{
MyUnion mu = new MyUnion();
mu.i = 99;
NativeMethods.TestUnion(mu, 1);
mu.d = 99.99;
NativeMethods.TestUnion(mu, 2);
MyUnion2_1 mu2_1 = new MyUnion2_1();
mu2_1.i = 99;
NativeMethods.TestUnion2(mu2_1, 1);
MyUnion2_2 mu2_2 = new MyUnion2_2();
mu2_2.str = "*** string ***";
NativeMethods.TestUnion2(mu2_2, 2);
}
}
Public Class App
Public Shared Sub Main()
Dim mu As New MyUnion()
mu.i = 99
NativeMethods.TestUnion(mu, 1)
mu.d = 99.99
NativeMethods.TestUnion(mu, 2)
Dim mu2_1 As New MyUnion2_1()
mu2_1.i = 99
NativeMethods.TestUnion2(mu2_1, 1)
Dim mu2_2 As New MyUnion2_2()
mu2_2.str = "*** string ***"
NativeMethods.TestUnion2(mu2_2, 2)
End Sub
End Class
Plattformbeispiel
In einigen Szenarios können sich die Layouts struct
und union
abhängig von der Zielplattform unterscheiden. Beachten Sie zum Beispiel den STRRET
-Typ, wenn er in einem COM-Szenario definiert ist:
#include <pshpack8.h> /* Defines the packing of the struct */
typedef struct _STRRET
{
UINT uType;
/* [switch_is][switch_type] */ union
{
/* [case()][string] */ LPWSTR pOleStr;
/* [case()] */ UINT uOffset;
/* [case()] */ char cStr[ 260 ];
} DUMMYUNIONNAME;
} STRRET;
#include <poppack.h>
Das obige struct
-Layout wird mit Windows-Headern deklariert, die das Arbeitsspeicherlayout des Typs beeinflussen. Wenn sie in einer verwalteten Umgebung definiert sind, werden diese Layoutdetails benötigt, um ordnungsgemäß mit nativem Code zu interagieren.
Die korrekte verwaltete Definition dieses Typs in einem 32-Bit-Prozess lautet wie folgt:
[StructLayout(LayoutKind.Explicit, Size = 264)]
public struct STRRET_32
{
[FieldOffset(0)]
public uint uType;
[FieldOffset(4)]
public IntPtr pOleStr;
[FieldOffset(4)]
public uint uOffset;
[FieldOffset(4)]
public IntPtr cStr;
}
Bei einem 64-Bit-Prozess unterscheiden sich die Größen- und Feldoffsets. Das korrekte Layout sieht wie folgt aus:
[StructLayout(LayoutKind.Explicit, Size = 272)]
public struct STRRET_64
{
[FieldOffset(0)]
public uint uType;
[FieldOffset(8)]
public IntPtr pOleStr;
[FieldOffset(8)]
public uint uOffset;
[FieldOffset(8)]
public IntPtr cStr;
}
Wenn Sie das native Layout in einem Interopszenario nicht genau beachten, kann dies zu zufälligen Abstürzen oder zu schlechteren falschen Berechnungen führen.
In der Standardeinstellung können .NET-Assemblys sowohl in der 32-Bit- als auch der 64-Bit-Version der .NET-Runtime ausgeführt werden. Die App muss bis zur Laufzeit warten, um zu entscheiden, welche der vorherigen Definitionen verwendet werden soll.
Der folgende Codeausschnitt zeigt ein Beispiel für die Wahl zwischen der 32-Bit- und der 64-Bit-Definition zur Laufzeit.
if (IntPtr.Size == 8)
{
// Use the STRRET_64 definition
}
else
{
Debug.Assert(IntPtr.Size == 4);
// Use the STRRET_32 definition
}
SysTime-Beispiel
Dieses Beispiel demonstriert, wie ein Zeiger auf eine Klasse an eine nicht verwaltete Funktion übergeben wird, die einen Zeiger auf eine Struktur erwartet.
Das "SysTime"-Beispiel verwendet die folgende nicht verwaltete Funktion, die zusammen mit ihrer ursprünglichen Funktionsdeklaration aufgeführt wird:
GetSystemTime aus Kernel32.dll exportiert.
VOID GetSystemTime(LPSYSTEMTIME lpSystemTime);
Die ursprüngliche, an die Funktion übergebene Struktur enthält die folgenden Elemente:
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;
In diesem Beispiel enthält die SystemTime
-Klasse die Elemente der Originalstruktur, die als Klassenelemente dargestellt werden. Das StructLayoutAttribute -Attribut ist so eingerichtet, dass sichergestellt wird, dass die Member im Speicher sequenziell in der Reihenfolge ihres Erscheinens angeordnet sind.
Die NativeMethods
-Klasse enthält einen verwalteten Prototyp der GetSystemTime
-Methode, der die SystemTime
-Klasse standardmäßig als In/Out-Parameter übergibt. Der Parameter muss mit den Attributen InAttribute und OutAttribute deklariert werden, weil Klassen, die Verweistypen sind, standardmäßig als In-Parameter übergeben werden. Damit der Aufrufer die Ergebnisse empfängt, müssen diese direktionalen Attribute explizit angewendet werden. Die App
-Klasse erstellt eine neue Instanz der SystemTime
-Klasse und greift auf deren Datenfelder zu.
Codebeispiele
using namespace System;
using namespace System::Runtime::InteropServices; // For StructLayout, DllImport
[StructLayout(LayoutKind::Sequential)]
public ref class SystemTime
{
public:
unsigned short year;
unsigned short month;
unsigned short weekday;
unsigned short day;
unsigned short hour;
unsigned short minute;
unsigned short second;
unsigned short millisecond;
};
public class NativeMethods
{
public:
// Declares a managed prototype for the unmanaged function using Platform Invoke.
[DllImport("Kernel32.dll")]
static void GetSystemTime([In, Out] SystemTime^ st);
};
public class App
{
public:
static void Main()
{
Console::WriteLine("C++/CLI SysTime Sample using Platform Invoke");
SystemTime^ st = gcnew SystemTime();
NativeMethods::GetSystemTime(st);
Console::Write("The Date is: ");
Console::Write("{0} {1} {2}", st->month, st->day, st->year);
}
};
int main()
{
App::Main();
}
// The program produces output similar to the following:
//
// C++/CLI SysTime Sample using Platform Invoke
// The Date is: 3 21 2010
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public class SystemTime
{
public ushort year;
public ushort month;
public ushort weekday;
public ushort day;
public ushort hour;
public ushort minute;
public ushort second;
public ushort millisecond;
}
internal static class NativeMethods
{
// Declares a managed prototype for the unmanaged function using Platform Invoke.
[DllImport("Kernel32.dll")]
internal static extern void GetSystemTime([In, Out] SystemTime st);
}
public class App
{
public static void Main()
{
Console.WriteLine("C# SysTime Sample using Platform Invoke");
SystemTime st = new SystemTime();
NativeMethods.GetSystemTime(st);
Console.Write("The Date is: ");
Console.Write($"{st.month} {st.day} {st.year}");
}
}
// The program produces output similar to the following:
//
// C# SysTime Sample using Platform Invoke
// The Date is: 3 21 2010
Imports System.Runtime.InteropServices
' Declares a class member for each structure element.
<StructLayout(LayoutKind.Sequential)>
Public Class SystemTime
Public year As Short
Public month As Short
Public weekday As Short
Public day As Short
Public hour As Short
Public minute As Short
Public second As Short
Public millisecond As Short
End Class
Friend Class NativeMethods
' Declares a managed prototype for the unmanaged function.
Friend Declare Sub GetSystemTime Lib "Kernel32.dll" (
<[In](), Out()> ByVal st As SystemTime)
End Class
Public Class App
Public Shared Sub Main()
Console.WriteLine("VB .NET SysTime Sample using Platform Invoke")
Dim st As New SystemTime()
NativeMethods.GetSystemTime(st)
Console.Write($"The Date is: {st.month} {st.day} {st.year}")
End Sub
End Class
' The program produces output similar to the following:
'
' VB .NET SysTime Sample using Platform Invoke
' The Date is: 3 21 2010
OutArrayOfStructs-Beispiel
Dieses Beispiel veranschaulicht, wie ein Array von Strukturen, das Integer- und String-Werte als Out-Parameter enthält, an eine nicht verwaltete Funktionen übergeben wird.
In diesem Beispiel wird veranschaulicht, wie eine systemeigene Funktion unter Verwendung der Marshal-Klasse und mithilfe von unsicherem Code aufgerufen wird.
Dieses Beispiel verwendet eine Wrapperfunktion und Plattformaufrufe, die in PinvokeLib.dll definiert sind und auch in den Quelldateien bereitgestellt werden. Es verwendet die TestOutArrayOfStructs
-Funktion und die MYSTRSTRUCT2
-Struktur. Die Struktur enthält die folgenden Elemente:
typedef struct _MYSTRSTRUCT2
{
char* buffer;
UINT size;
} MYSTRSTRUCT2;
Die MyStruct
-Klasse enthält ein Zeichenfolgenobjekt aus ANSI-Zeichen. Das CharSet-Feld gibt das ANSI-Format an. MyUnsafeStruct
ist eine Struktur, die einen IntPtr-Typ anstelle einer Zeichenfolge enthält.
Die NativeMethods
-Klasse enthält die überladene TestOutArrayOfStructs
-Prototypenmethode. Wenn eine Methode einen Zeiger als Parameter deklariert, sollte die Klasse mit dem unsafe
-Schlüsselwort markiert werden. Da Visual Basic keinen unsicheren Code verwenden kann, sind die überladene Methode, der „unsafe“-Modifizierer und die MyUnsafeStruct
-Struktur nicht erforderlich.
Die App
-Klasse implementiert die UsingMarshaling
-Methode, die alle Aufgaben durchführt, die zum Übergeben des Arrays erforderlich sind. Das Array wird mit dem out
-Schlüsselwort (ByRef
in Visual Basic) markiert, um anzuzeigen, dass Daten vom Aufgerufenen an den Aufrufer übergeben werden. Die Implementierung verwendet die folgenden Marshal-Klassenmethoden:
PtrToStructure für das Marshalling von Daten aus dem nicht verwalteten Speicherblock zu einem verwalteten Objekt.
DestroyStructure zum Freigeben des für Zeichenfolgen in der Struktur reservierten Speichers.
FreeCoTaskMem zum Freigeben des für das Array reservierten Speichers.
Wie zuvor erwähnt, lässt C# unsicheren Code zu, und Visual Basic nicht. In dem C#-Beispiel ist UsingUnsafePointer
eine alternative Methodenimplementierung, die Zeiger anstelle der Marshal-Klasse verwendet, um das Array, das die MyUnsafeStruct
-Struktur enthält, zurück zu übergeben.
Deklarieren von Prototypen
// Declares a class member for each structure element.
[StructLayout(LayoutKind::Sequential, CharSet = CharSet::Ansi)]
public ref class MyStruct
{
public:
String^ buffer;
int size;
};
// Declares a structure with a pointer.
[StructLayout(LayoutKind::Sequential)]
public value struct MyUnsafeStruct
{
public:
IntPtr buffer;
int size;
};
private ref class NativeMethods
{
public:
// Declares managed prototypes for the unmanaged function.
[DllImport("..\\LIB\\PInvokeLib.dll")]
static void TestOutArrayOfStructs(int% size, IntPtr% outArray);
[DllImport("..\\LIB\\PInvokeLib.dll")]
static void TestOutArrayOfStructs(int% size, MyUnsafeStruct** outArray);
};
// Declares a class member for each structure element.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class MyStruct
{
public string buffer;
public int size;
}
// Declares a structure with a pointer.
[StructLayout(LayoutKind.Sequential)]
public struct MyUnsafeStruct
{
public IntPtr buffer;
public int size;
}
internal static unsafe class NativeMethods
{
// Declares managed prototypes for the unmanaged function.
[DllImport("..\\LIB\\PInvokeLib.dll")]
internal static extern void TestOutArrayOfStructs(
out int size, out IntPtr outArray);
[DllImport("..\\LIB\\PInvokeLib.dll")]
internal static extern void TestOutArrayOfStructs(
out int size, MyUnsafeStruct** outArray);
}
' Declares a class member for each structure element.
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
Public Class MyStruct
Public buffer As String
Public someSize As Integer
End Class
Friend Class NativeMethods
' Declares a managed prototype for the unmanaged function.
<DllImport("..\LIB\PinvokeLib.dll", CallingConvention:=CallingConvention.Cdecl)>
Friend Shared Sub TestOutArrayOfStructs(
ByRef arrSize As Integer, ByRef outArray As IntPtr)
End Sub
End Class
Aufrufen von Funktionen
public ref class App
{
public:
static void Main()
{
Console::WriteLine("\nUsing marshal class\n");
UsingMarshaling();
Console::WriteLine("\nUsing unsafe code\n");
UsingUnsafePointer();
}
static void UsingMarshaling()
{
int size;
IntPtr outArray;
NativeMethods::TestOutArrayOfStructs(size, outArray);
array<MyStruct^>^ manArray = gcnew array<MyStruct^>(size);
IntPtr current = outArray;
for (int i = 0; i < size; i++)
{
manArray[i] = gcnew MyStruct();
Marshal::PtrToStructure(current, manArray[i]);
Marshal::DestroyStructure(current, MyStruct::typeid);
//current = (IntPtr)((long)current + Marshal::SizeOf(manArray[i]));
current = current + Marshal::SizeOf(manArray[i]);
Console::WriteLine("Element {0}: {1} {2}", i, manArray[i]->buffer,
manArray[i]->size);
}
Marshal::FreeCoTaskMem(outArray);
}
static void UsingUnsafePointer()
{
int size;
MyUnsafeStruct* pResult;
NativeMethods::TestOutArrayOfStructs(size, &pResult);
MyUnsafeStruct* pCurrent = pResult;
for (int i = 0; i < size; i++, pCurrent++)
{
Console::WriteLine("Element {0}: {1} {2}", i,
Marshal::PtrToStringAnsi(pCurrent->buffer), pCurrent->size);
Marshal::FreeCoTaskMem(pCurrent->buffer);
}
Marshal::FreeCoTaskMem((IntPtr)pResult);
}
};
public class App
{
public static void Main()
{
Console.WriteLine("\nUsing marshal class\n");
UsingMarshaling();
Console.WriteLine("\nUsing unsafe code\n");
UsingUnsafePointer();
}
public static void UsingMarshaling()
{
int size;
IntPtr outArray;
NativeMethods.TestOutArrayOfStructs(out size, out outArray);
MyStruct[] manArray = new MyStruct[size];
IntPtr current = outArray;
for (int i = 0; i < size; i++)
{
manArray[i] = new MyStruct();
Marshal.PtrToStructure(current, manArray[i]);
//Marshal.FreeCoTaskMem((IntPtr)Marshal.ReadInt32(current));
Marshal.DestroyStructure(current, typeof(MyStruct));
current = (IntPtr)((long)current + Marshal.SizeOf(manArray[i]));
Console.WriteLine("Element {0}: {1} {2}", i, manArray[i].buffer,
manArray[i].size);
}
Marshal.FreeCoTaskMem(outArray);
}
public static unsafe void UsingUnsafePointer()
{
int size;
MyUnsafeStruct* pResult;
NativeMethods.TestOutArrayOfStructs(out size, &pResult);
MyUnsafeStruct* pCurrent = pResult;
for (int i = 0; i < size; i++, pCurrent++)
{
Console.WriteLine("Element {0}: {1} {2}", i,
Marshal.PtrToStringAnsi(pCurrent->buffer), pCurrent->size);
Marshal.FreeCoTaskMem(pCurrent->buffer);
}
Marshal.FreeCoTaskMem((IntPtr)pResult);
}
}
Public Class App
Public Shared Sub Main()
Console.WriteLine(vbNewLine + "Using marshal class" + vbNewLine)
UsingMarshaling()
'Visual Basic 2005 cannot use unsafe code.
End Sub
Public Shared Sub UsingMarshaling()
Dim arrSize As Integer
Dim outArray As IntPtr
NativeMethods.TestOutArrayOfStructs(arrSize, outArray)
Dim manArray(arrSize - 1) As MyStruct
Dim current As IntPtr = outArray
Dim i As Integer
For i = 0 To arrSize - 1
manArray(i) = New MyStruct()
Marshal.PtrToStructure(current, manArray(i))
Marshal.DestroyStructure(current, GetType(MyStruct))
current = IntPtr.op_Explicit(current.ToInt64() _
+ Marshal.SizeOf(manArray(i)))
Console.WriteLine("Element {0}: {1} {2}", i, manArray(i).
buffer, manArray(i).someSize)
Next i
Marshal.FreeCoTaskMem(outArray)
End Sub
End Class