共用方式為


封送處理類別、結構和聯合

.NET Framework 中的類別和結構很類似。 兩者都可以有欄位、屬性和事件。 它們也可以有靜態和非靜態方法。 其中一個顯著差異是結構是實值型別,而類別是參考型別。

下表列出了類別、結構和聯合的封送處理選項,說明了它們的用法,並提供了相應平台調用範例的鏈接。

類型 說明 範例
依值分類。 以整數成員作為 In/Out 參數傳遞類別,例如 Managed 案例。 SysTime 範例
依值結構。 將結構傳遞為 In 參數。 結構範例
依參考結構。 以 In/Out 參數的形式傳遞結構。 OSInfo 範例
具有巢狀結構的結構(扁平化)。 傳遞類別,表示 Unmanaged 函式中具有巢狀結構的結構。 結構在管理的原型中被扁平化為一個大結構。 FindFile 範例
指向另一個結構的結構。 傳遞一個結構,其中包含指向第二個結構的指標作為成員。 結構範例
依值以整數表示的結構陣列。 傳遞只包含整數的結構陣組做為 In/Out 參數。 陣列的成員可以變更。 陣列範例
以參考方式包含整數和字串的結構陣列。 傳遞結構陣列,其中包含整數和字串做為 Out 參數。 呼叫的函式會為陣列分配記憶體。 OutArrayOfStructs 範例
與值類型的聯合。 傳遞具有值類型的聯合(整數和雙精度浮點數)。 等位範例
具有混合型別的聯集。 處理混合型別的聯集(整數和字串)。 等位範例
具有平台專屬佈局的結構。 傳遞具有原生封裝定義的類型。 平臺範例
結構中的空值。 傳遞空參考(在 Visual Basic 中為 Nothing),而不是實值類型的參考。 HandleRef 範例

結構範例

此範例示範如何傳遞一個指向第二個結構的結構、傳遞一個具有內嵌結構的結構,以及傳遞一個具有內嵌陣列的結構。

結構範例會使用下列非受控函式,並顯示其原始函式宣告:

  • TestStructInStruct 從 PinvokeLib.dll匯出。

    int TestStructInStruct(MYPERSON2* pPerson2);
    
  • 從 PinvokeLib.dll匯出 TestStructInStruct3

    void TestStructInStruct3(MYPERSON3 person3);
    
  • TestArrayInStruct 從 PinvokeLib.dll匯出。

    void TestArrayInStruct(MYARRAYSTRUCT* pStruct);
    

PinvokeLib.dll 是自定義 Unmanaged 連結庫,其中包含先前列出的函式和四個結構的實作: MYPERSONMYPERSON2MYPERSON3MYARRAYSTRUCT。 這些結構包含下列元素:

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;

被管理的 MyPersonMyPerson2MyPerson3MyArrayStruct 結構具有下列特性:

  • MyPerson 只包含字串成員。 CharSet 欄位在傳遞至非受控函式時,會將字串設定為 ANSI 格式。

  • MyPerson2包含到IntPtr結構的MyPerson。 該 IntPtr 型別取代了原始指向非受管理結構的指標,因為 .NET Framework 應用程式除非程式碼被標記 為不安全,否則不會使用指標。

  • MyPerson3 MyPerson包含為內嵌結構。 內嵌在另一個結構中的結構可以透過將內嵌結構的元素直接放入主要結構來扁平化,也可以保留為內嵌結構,如此範例所示。

  • MyArrayStruct 包含整數陣列。 MarshalAsAttribute 屬性將 UnmanagedType 列舉值設定為 ByValArray,用來表示陣列中的元素數目。

針對此範例中的所有結構, StructLayoutAttribute 會套用 屬性,以確保成員會依它們出現的順序,循序排列在記憶體中。

NativeMethods 類別包含由 TestStructInStruct 類別呼叫的 TestStructInStruct3TestArrayInStructApp 方法的管理原型。 每個原型都會宣告單一參數,如下所示:

  • TestStructInStructMyPerson2 類型的參考宣告為其參數。

  • TestStructInStruct3 宣告類型 MyPerson3 為其參數,並依值傳遞參數。

  • TestArrayInStructMyArrayStruct 類型的參考宣告為其參數。

作為方法參數的結構會以值傳遞,除非該參數包含 ref (Visual Basic 中的 ByRef )關鍵字。 例如,#D0 方法會將一個物件的參考(位址的值),其類型為 #D1,傳遞給非受控程式碼。 為了操縱MyPerson2指向的結構,此範例會建立指定大小的緩衝區,並以結合Marshal.AllocCoTaskMemMarshal.SizeOf方法返回其位址。 接下來,此範例會將 Managed 結構的內容複製到 Unmanaged 緩衝區。 最後,這個範例會使用 Marshal.PtrToStructure 方法,將非受控緩衝區中的數據封送至受控物件,並使用 Marshal.FreeCoTaskMem 方法來釋放非受控記憶體區塊。

宣告原型

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

呼叫函式

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 範例

此範例示範如何將包含第二個內嵌結構的結構傳遞至 Unmanaged 函式。 它也示範如何使用 MarshalAsAttribute 屬性在 結構內宣告固定長度陣列。 在此範例中,內嵌結構元素會新增至父結構。 如需未經扁平化的嵌入式結構範例,請參閱 結構範例

FindFile 範例會使用下列非受控函式,並附上其原始函式宣告:

  • FindFirstFile 從 Kernel32.dll匯出。

    HANDLE FindFirstFile(LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData);
    

傳遞至函式的原始結構包含下列元素:

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;

在此範例中,FindData 類別包含原始結構和內嵌結構之每個元素的對應資料成員。 取代兩個原始字元緩衝區,類別會取代字串。 MarshalAsAttributeUnmanagedType 列舉設為 ByValTStr,用來識別出現在非管理結構中的固定長度字元陣列。

類別 NativeMethods 包含 方法的 FindFirstFile Managed原型,其會將 類別傳遞 FindData 為參數。 參數必須使用 和 InAttribute 屬性來OutAttribute宣告,因為類別是參考型別,預設會以 In 參數的形式傳遞。

宣告原型

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

呼叫函式

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 範例會使用下列無管理函式,其以原始函式宣告顯示:

  • TestUnion 從 PinvokeLib.dll匯出。

    void TestUnion(MYUNION u, int type);
    

PinvokeLib.dll 是一個自訂的非管理函式庫,包含上述函式的實作及兩個聯集, MYUNION 以及 MYUNION2。 此聯集包含以下元素:

union MYUNION
{
    int number;
    double d;
}

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

在受管理的程式碼中,聯合體被定義為結構。 結構 MyUnion 包含兩個實值型別做為其成員:整數和雙精度浮點數。 屬性 StructLayoutAttribute 設定為控制每個數據成員的精確位置。 FieldOffsetAttribute屬性提供聯集在非托管表示中的欄位物理位置。 請注意,這兩個成員都有相同的位移值,因此成員可以定義相同的記憶體片段。

MyUnion2_1MyUnion2_2 分別包含實值型別 (integer) 和字串。 在 Managed 程式代碼中,不允許實值型別和參考型別重疊。 這個範例使用方法重載,使呼叫端在呼叫相同的非受控函式時可以使用這兩種類型。 MyUnion2_1 的布局明確且具有精確的位移值。 相反地,MyUnion2_2 具有順序佈局,因為參考型別不允許明確佈局。 屬性將此列舉設定為 ByValTStr,用來識別出現在聯集之非受控表示法中的內嵌定長字元陣列。

類別NativeMethods包含和 TestUnion 方法的原型TestUnion2TestUnion2 會多載以宣告 MyUnion2_1MyUnion2_2 做為參數。

宣告原型

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

呼叫函式

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

平臺範例

在某些情況下, structunion 版面配置可能會根據目標平臺而有所不同。 例如,考慮在 COM 案例中定義的 STRRET 類型:

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

上述 struct 由 Windows 標頭宣告,影響該類型的記憶體佈局。 在受控環境中定義時,需要這些佈局細節,才能與原生程式碼正確互作。

在 32 位進程中,此類型的正確 Managed 定義如下:

[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;
}

在 64 位元處理程序中,大小和欄位位移是不同的。 正確的版面配置為:

[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;
}

如果未能正確考慮互操作情境中的原生配置,可能會導致隨機崩潰,甚至可能出現更糟的情況,例如計算不正確。

根據預設,.NET 元件可以在 .NET 運行時間的32位和64位版本中執行。 應用程式必須等到執行時決定要使用哪一種定義。

以下程式碼片段展示了如何在執行時選擇 32 位元與 64 位元定義的範例。

if (IntPtr.Size == 8)
{
    // Use the STRRET_64 definition
}
else
{
    Debug.Assert(IntPtr.Size == 4);
    // Use the STRRET_32 definition
}

SysTime 範例

此範例示範如何將指標傳遞至需要結構指標的 Unmanaged 函式。

SysTime 範例會使用下列非受控函式,其原始函式宣告如下所示:

  • GetSystemTime 從 Kernel32.dll匯出。

    VOID GetSystemTime(LPSYSTEMTIME lpSystemTime);
    

傳遞至函式的原始結構包含下列元素:

typedef struct _SYSTEMTIME {
    WORD wYear;
    WORD wMonth;
    WORD wDayOfWeek;
    WORD wDay;
    WORD wHour;
    WORD wMinute;
    WORD wSecond;
    WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;

在此範例中,SystemTime 類別包含原始結構的元素,並將其表示為類別成員。 StructLayoutAttribute 屬性被設定為確保成員根據其出現的順序,在記憶體中順序排列。

類別 NativeMethods 包含 方法的 GetSystemTime Managed原型,預設會將類別傳遞 SystemTime 為In/Out 參數。 參數必須使用 和 InAttribute 屬性來OutAttribute宣告,因為類別是參考型別,預設會以 In 參數的形式傳遞。 若要讓呼叫者接收結果,必須明確套用這些 方向屬性。 類別 App 會建立 類別的新實例 SystemTime ,並存取其數據欄位。

程式碼範例

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 範例

此範例示範如何將包含整數和字串的結構陣列當做 Out 參數傳遞至 Unmanaged 函式。

此範例示範如何使用 Marshal 類別和不安全程式碼來呼叫原生函數。

此範例會使用 PinvokeLib.dll中定義的包裝函式和平台調用,也提供於原始程序檔中。 它會使用函式 TestOutArrayOfStructsMYSTRSTRUCT2 結構。 結構包含下列元素:

typedef struct _MYSTRSTRUCT2
{
   char* buffer;
   UINT size;
} MYSTRSTRUCT2;

MyStruct 類別包含 ANSI 字元的字串物件。 CharSet 欄位指定 ANSI 格式。 MyUnsafeStruct是包含 IntPtr 型別而非字串的結構。

類別 NativeMethods 包含多載 TestOutArrayOfStructs 原型方法。 如果方法將指標宣告為參數,則類別應該以 unsafe 關鍵詞標示。 因為 Visual Basic 無法使用不安全代碼,所以不需要多載方法、unsafe 修飾詞和MyUnsafeStruct 結構。

類別 App 會實作 UsingMarshaling 方法,這個方法會執行傳遞數位所需的所有工作。 陣列使用out(在 Visual Basic 中是 ByRef)關鍵詞標記,表示數據由被呼叫者傳遞至呼叫者。 實施會使用下列 Marshal 類別方法:

  • 將數據從非托管緩衝區封送處理至托管物件。

  • DestroyStructure 來釋放結構中為字串保留的記憶體。

  • FreeCoTaskMem 釋放為陣列保留的記憶體。

如先前所述,C# 允許不安全的程序代碼,Visual Basic 則不允許。 在 C# 範例中, UsingUnsafePointer 是使用指標而非 Marshal 類別傳回包含 MyUnsafeStruct 結構的陣列的替代方法實作。

宣告原型

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

呼叫函式

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

另請參閱