Freigeben über


"Marshalling von Klassen, Strukturen und Verbänden"

Klassen und Strukturen sind in .NET Framework ähnlich. Beide können Felder, Eigenschaften und Ereignisse aufweisen. Sie können auch statische und nicht statische Methoden haben. Ein wichtiger Unterschied besteht darin, dass Strukturen Werttypen und Klassen Referenztypen sind.

In der folgenden Tabelle sind Marshallingoptionen für Klassen, Strukturen und Gewerkschaften aufgeführt; beschreibt ihre Nutzung; und stellt einen Link zum entsprechenden Plattform-Aufrufbeispiel bereit.

Typ BESCHREIBUNG Beispiel
Klasse nach Wert. Übergibt eine Klasse mit ganzzahligen Membern als In/Out-Parameter, z. B. den verwalteten Fall. SysTime-Beispiel
Struktur nach Wert. Übermittelt Strukturen als Eingabeparameter. Beispiel für Strukturen
Struktur nach Bezug. Übergibt Strukturen als In/Out-Parameter. OSInfo-Beispiel
Struktur mit geschachtelten Strukturen (abgeflacht). Überträgt eine Klasse, die eine Struktur mit geschachtelten Strukturen innerhalb der nicht verwalteten Funktion darstellt. Die Struktur wird im verwalteten Prototyp auf eine einzige große Struktur reduziert. FindFile-Beispiel
Struktur mit einem Zeiger auf eine andere Struktur. Übergibt eine Struktur, die einen Zeiger an eine zweite Struktur als Element enthält. Beispiel für Strukturen
Array von Strukturen mit ganzen Zahlen nach Wert. Übergibt ein Array von Strukturen, die nur ganze Zahlen als In/Out-Parameter enthalten. Elemente des Arrays können geändert werden. Array-Beispiel
Array von Strukturen mit ganzen Zahlen und Strings als Referenz. Übergibt ein Array von Strukturen, die ganze Zahlen und Zeichenfolgen als Out-Parameter enthalten. Die aufgerufene Funktion weist Speicher für das Array zu. OutArrayOfStructs-Beispiel
Vereinigungen mit Werttypen. Übergibt Vereinigungen mit Werttypen (ganze Zahl und Double). Unions-Beispiel
Vereinigungen mit gemischten Typen. Überträgt Vereinigungen mit gemischten Typen (Ganzzahl und Zeichenfolge). Unions-Beispiel
Struktur mit plattformspezifischem Layout. Übergibt einen Typ mit systemeigenen Verpackungsdefinitionen. Plattformbeispiel
Nullwerte in der Struktur. Übergibt einen Nullverweis (Nothing in Visual Basic) anstelle eines Verweises auf einen Werttyp. HandleRef-Beispiel

Beispiel für Strukturen

In diesem Beispiel wird veranschaulicht, wie man eine Struktur übergibt, die auf eine zweite Struktur zeigt, wie man eine Struktur mit einer eingebetteten Struktur übergibt und wie man eine Struktur mit einem eingebetteten Array übergibt.

Im Beispiel "Struktur" werden die folgenden nicht verwalteten Funktionen verwendet, die mit der ursprünglichen Funktionsdeklaration angezeigt werden:

  • TestStructInStruct exportiert aus PinvokeLib.dll.

    int TestStructInStruct(MYPERSON2* pPerson2);
    
  • TestStructInStruct3 wurde aus PinvokeLib.dllexportiert.

    void TestStructInStruct3(MYPERSON3 person3);
    
  • TestArrayInStruct exportiert aus PinvokeLib.dll.

    void TestArrayInStruct(MYARRAYSTRUCT* pStruct);
    

PinvokeLib.dll ist eine benutzerdefinierte nicht verwaltete Bibliothek, die Implementierungen für die zuvor aufgeführten 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 MyPerson, MyPerson2, , MyPerson3und MyArrayStruct Strukturen weisen die folgenden Merkmale auf:

  • MyPerson enthält nur Zeichenfolgenmitglieder. Das CharSet-Feld legt die Zeichenfolgen auf das ANSI-Format fest, wenn sie an die nicht verwaltete Funktion übergeben wird.

  • MyPerson2 enthält einen IntPtr für die MyPerson Struktur. Der IntPtr-Typ ersetzt den ursprünglichen Zeiger auf die nicht verwaltete Struktur, da .NET Framework-Anwendungen keine Zeiger verwenden, es sei denn, der Code ist als unsicher gekennzeichnet.

  • MyPerson3 enthält MyPerson als eine eingebettete Struktur. Eine in eine andere Struktur eingebettete Struktur kann vereinfacht werden, indem die Elemente der eingebetteten Struktur direkt in die Hauptstruktur eingefügt werden, oder sie kann wie in diesem Beispiel als eingebettete Struktur verbleiben.

  • MyArrayStruct enthält ein Array mit ganzen Zahlen. Das MarshalAsAttribute Attribut legt den UnmanagedType Enumerationswert auf ByValArray fest, der verwendet wird, um die Anzahl der Elemente im Array anzugeben.

Für alle Strukturen in diesem Beispiel wird das StructLayoutAttribute Attribut angewendet, um sicherzustellen, dass die Elemente sequenziell im Arbeitsspeicher angeordnet sind, in der Reihenfolge, in der sie angezeigt werden.

Die NativeMethods-Klasse enthält verwaltete Prototypen für die TestStructInStruct, TestStructInStruct3 und TestArrayInStruct Methoden, die von der App-Klasse aufgerufen werden. Jeder Prototyp deklariert einen einzelnen Parameter wie folgt:

  • TestStructInStruct deklariert einen Verweis auf den Typ MyPerson2 als Parameter.

  • TestStructInStruct3 deklariert den Typ MyPerson3 als Parameter und übergibt den Parameter nach Wert.

  • TestArrayInStruct deklariert einen Verweis auf den Typ MyArrayStruct als Parameter.

Strukturen als Argumente für Methoden werden nach Wert übergeben, es sei denn, der Parameter enthält das Schlüsselwort Ref (ByRef in Visual Basic). Die Methode TestStructInStruct übergibt einen Verweis (den Wert einer Adresse) auf ein Objekt vom Typ MyPerson2 an nicht verwalteten Code. Um die Struktur zu manipulieren, auf die MyPerson2 verweist, erstellt das Beispiel einen Puffer mit einer angegebenen Größe und gibt dessen Adresse zurück, indem es die Methoden Marshal.AllocCoTaskMem und Marshal.SizeOf kombiniert. Als Nächstes kopiert das Beispiel den Inhalt der verwalteten Struktur in den nicht verwalteten Puffer. Schließlich verwendet das Beispiel die Marshal.PtrToStructure Methode zum Marshallen von Daten aus dem nicht verwalteten Puffer zu einem verwalteten Objekt und der Marshal.FreeCoTaskMem Methode zum Freigeben des nicht verwalteten Speicherblocks.

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($"{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($"{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

In diesem Beispiel wird veranschaulicht, wie eine Struktur übergeben wird, die eine zweite eingebettete Struktur an eine nicht verwaltete Funktion enthält. Außerdem wird veranschaulicht, wie das MarshalAsAttribute Attribut verwendet wird, um ein Array mit fester Länge innerhalb der Struktur zu deklarieren. In diesem Beispiel werden die eingebetteten Strukturelemente zur übergeordneten Struktur hinzugefügt. Ein Beispiel für eine eingebettete Struktur, die nicht flach ist, finden Sie unter "Structures Sample".

Im FindFile-Beispiel wird die folgende nicht verwaltete Funktion verwendet, die mit der ursprünglichen Funktionsdeklaration angezeigt wird:

  • FindFirstFile exportiert aus Kernel32.dll.

    HANDLE FindFirstFile(LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData);
    

Die ursprüngliche Struktur, die an die Funktion übergeben wird, 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 ein entsprechendes Datenelement für jedes Element der ursprünglichen Struktur und der eingebetteten Struktur. Anstelle von zwei ursprünglichen Zeichenpuffern ersetzt die Klasse Zeichenfolgen. MarshalAsAttribute legt die UnmanagedType Enumeration auf ByValTStr fest, die verwendet wird, um Inline-Zeichenarrays mit fester Länge zu identifizieren, die in den nicht verwalteten Strukturen vorkommen.

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, da Klassen, die Referenztypen 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

In diesem Beispiel wird veranschaulicht, wie Strukturen übergeben werden, die nur Werttypen enthalten, und Strukturen, die einen Werttyp und eine Zeichenfolge als Parameter an eine nicht verwaltete Funktion übergeben, die eine Union erwartet. Eine Union repräsentiert einen Speicherort, der von zwei oder mehr Variablen gemeinsam genutzt werden kann.

Im Beispiel "Unions" wird die folgende nicht verwaltete Funktion verwendet, die mit der ursprünglichen Funktionsdeklaration dargestellt wird:

  • TestUnion wurde aus PinvokeLib.dllexportiert.

    void TestUnion(MYUNION u, int type);
    

PinvokeLib.dll ist eine benutzerdefinierte nicht verwaltete Bibliothek, die eine Implementierung für die zuvor aufgeführte Funktion und zwei Gewerkschaften, MYUNION und MYUNION2 enthält. Die Gewerkschaften enthalten die folgenden Elemente:

union MYUNION
{
    int number;
    double d;
}

union MYUNION2
{
    int i;
    char str[128];
};

Im verwalteten Code werden Gewerkschaften als Strukturen definiert. Die MyUnion Struktur enthält zwei Werttypen als Member: eine ganze Zahl und eine Doppelzahl. Das StructLayoutAttribute Attribut wird so festgelegt, dass die genaue Position jedes Datenelements gesteuert wird. Das FieldOffsetAttribute Attribut stellt die physische Position von Feldern innerhalb der nicht verwalteten Darstellung einer Vereinigung bereit. Beachten Sie, dass beide Member über dieselben Offsetwerte verfügen, sodass die Elemente denselben Speicherabschnitt definieren können.

MyUnion2_1 und MyUnion2_2 enthalten einen Werttyp (ganze Zahl) bzw. eine Zeichenfolge. In verwaltetem Code dürfen Werttypen und Verweistypen nicht überlappen. In diesem Beispiel wird Methodenüberladung verwendet, um dem Aufrufer die Verwendung beider Typen beim Aufrufen derselben nicht verwalteten Funktion zu ermöglichen. Das Layout von MyUnion2_1 ist explizit und weist einen genauen Offsetwert auf. Weist dagegen MyUnion2_2 ein sequenzielles Layout auf, da explizite Layouts nicht mit Verweistypen zulässig sind. Das MarshalAsAttribute Attribut legt die UnmanagedType Enumeration auf ByValTStr fest, die verwendet wird, um die Inline-Zeichenarrays mit fester Länge zu identifizieren, die in der nicht verwalteten Darstellung der Union angezeigt werden.

Die NativeMethods Klasse enthält die Prototypen für die TestUnion und TestUnion2 Methoden. 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 Szenarien können sich struct- und union-Layouts je nach Zielplattform unterscheiden. Berücksichtigen Sie beispielsweise 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>

Die obigen struct Werden mit Windows-Headern deklariert, die das Speicherlayout des Typs beeinflussen. Wenn sie in einer verwalteten Umgebung definiert sind, sind diese Layoutdetails erforderlich, um ordnungsgemäß mit systemeigenem Code zu arbeiten.

Die richtige verwaltete Definition dieses Typs in einem 32-Bit-Prozess lautet:

[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 richtige Layout ist:

[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 das systemeigene Layout in einem Interoperabilitätsszenario nicht ordnungsgemäß berücksichtigt wird, kann dies zu zufälligen Abstürzen oder schlimmeren fehlerhaften Berechnungen führen.

Standardmäßig können .NET-Assemblys sowohl in einer 32-Bit- als auch in einer 64-Bit-Version der .NET-Laufzeit ausgeführt werden. Die App muss bis zur Laufzeit warten, um zu entscheiden, welche der vorherigen Definitionen verwendet werden sollen.

Der folgende Codeausschnitt zeigt ein Beispiel für die Auswahl 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

In diesem Beispiel wird veranschaulicht, wie ein Zeiger an eine Klasse an eine nicht verwaltete Funktion übergeben wird, die einen Zeiger auf eine Struktur erwartet.

Im SysTime-Beispiel wird die folgende nicht verwaltete Funktion verwendet, die mit der ursprünglichen Funktionsdeklaration angezeigt wird:

  • GetSystemTime wurde aus Kernel32.dllexportiert.

    VOID GetSystemTime(LPSYSTEMTIME lpSystemTime);
    

Die ursprüngliche Struktur, die an die Funktion übergeben wird, 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 ursprünglichen Struktur, die als Klassenmitglieder dargestellt werden. Das StructLayoutAttribute Attribut wird festgelegt, um sicherzustellen, dass die Elemente sequenziell im Arbeitsspeicher angeordnet sind, in der Reihenfolge, in der sie angezeigt werden.

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, da Klassen, die Referenztypen 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 ihre 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

In diesem Beispiel wird gezeigt, wie Sie ein Array von Strukturen übergeben, das ganze Zahlen und Zeichenfolgen als Out-Parameter an eine nicht verwaltete Funktion enthält.

In diesem Beispiel wird veranschaulicht, wie eine systemeigene Funktion mithilfe der Marshal Klasse und mithilfe unsicherer Code aufgerufen wird.

In diesem Beispiel werden eine Wrapperfunktion und plattformübergreifende Aufrufe verwendet, die in PinvokeLib.dlldefiniert sind, die auch in den Quelldateien bereitgestellt werden. Sie 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 mit ANSI-Zeichen. Das CharSet Feld gibt das ANSI-Format an. MyUnsafeStructist eine Struktur, die einen IntPtr Typ anstelle einer Zeichenfolge enthält.

Die NativeMethods Klasse enthält die überladene TestOutArrayOfStructs Prototypmethode. Wenn eine Methode einen Zeiger als Parameter deklariert, sollte die Klasse mit dem unsafe Schlüsselwort gekennzeichnet werden. Da Visual Basic keinen unsicheren Code verwenden kann, sind die überladene Methode, der unsichere Modifizierer und die MyUnsafeStruct Struktur nicht erforderlich.

Die App Klasse implementiert die UsingMarshaling Methode, die alle zum Übergeben des Arrays erforderlichen Aufgaben ausführt. Das Array ist mit dem out Schlüsselwort (ByRef in Visual Basic) gekennzeichnet, um anzugeben, dass Daten vom Angerufenen an den Aufrufer übergeben werden. Die Implementierung verwendet die folgenden Marshal Klassenmethoden:

  • PtrToStructure zum Marshallen von Daten aus dem nicht verwalteten Puffer in ein verwaltetes Objekt.

  • DestroyStructure um den Speicher freizugeben, der für Zeichenfolgen in der Struktur reserviert ist.

  • FreeCoTaskMem um den für das Array reservierten Speicher freizugeben.

Wie bereits erwähnt, lässt C# unsicheren Code zu, aber Visual Basic nicht. Im C#-Beispiel ist UsingUnsafePointer eine alternative Methodenimplementierung, die Zeiger anstelle der Marshal Klasse verwendet, um das Array, das die MyUnsafeStruct Struktur enthält, zurückzugeben.

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 {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 {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

Siehe auch