Bagikan melalui


Menyusun Kelas, Struktur, dan Gabungan

Kelas dan struktur serupa dalam Kerangka Kerja .NET. Keduanya dapat memiliki bidang, properti, dan peristiwa. Kelas, struktur, dan gabungan juga dapat memiliki metode statis dan nonstatis. Salah satu perbedaan penting adalah bahwa struktur adalah jenis nilai dan kelas adalah jenis referensi.

Tabel berikut mencantumkan opsi penyusunan untuk kelas, struktur, dan gabungan; menjelaskan penggunaannya; dan menyediakan tautan ke sampel pemanggilan platform yang sesuai.

Tipe Deskripsi Sampel
Kelas berdasarkan nilai. Meneruskan kelas dengan anggota bilangan bulat sebagai parameter Masuk/Keluar, seperti kasus terkelola. Sampel SysTime
Struktur berdasarkan nilai. Meneruskan struktur sebagai parameter Masuk. Sampel struktur
Struktur berdasarkan referensi. Meneruskan struktur sebagai parameter Masuk/Keluar. Sampel OSInfo
Struktur dengan struktur berlapis (flattened). Meneruskan kelas yang mewakili struktur dengan struktur berlapis dalam fungsi yang tidak dikelola. Struktur diratakan menjadi satu struktur besar dalam prototipe yang dikelola. Sampel FindFile
Struktur dengan pointer ke struktur lain. Meneruskan struktur yang berisi pointer ke struktur kedua sebagai anggota. Sampel Struktur
Array struktur dengan bilangan bulat berdasarkan nilai. Meneruskan array struktur yang hanya berisi bilangan bulat sebagai parameter Masuk/Keluar. Anggota array dapat diubah. Sampel Array
Array struktur dengan bilangan bulat dan string dengan referensi. Meneruskan array struktur yang berisi bilangan bulat dan string sebagai parameter Keluar. Fungsi yang dipanggil mengalokasikan memori untuk array. Sampel OutArrayOfStructs
Gabungan dengan jenis nilai. Meneruskan gabungan dengan jenis nilai (bilangan bulat dan ganda). Sampel gabungan
Gabungan dengan jenis campuran. Meneruskan gabungan dengan jenis campuran (bilangan bulat dan string). Sampel gabungan
Struktur dengan tata letak khusus platform. Meneruskan jenis dengan definisi pengemasan asli. Sampel platform
Nilai nol dalam struktur. Meneruskan referensi nol (Tidak ada dalam Visual Basic) sebagai ganti referensi ke jenis nilai. Sampel HandleRef

Sampel struktur

Sampel ini menunjukkan cara meneruskan struktur yang menunjuk ke struktur kedua, meneruskan struktur dengan struktur tersemat, dan meneruskan struktur dengan array tersemat.

Sampel Struktur menggunakan fungsi tidak terkelola berikut, yang ditunjukkan dengan deklarasi fungsi aslinya:

  • TestStructInStruct diekspor dari PinvokeLib.dll.

    int TestStructInStruct(MYPERSON2* pPerson2);
    
  • TestStructInStruct3 diekspor dari PinvokeLib.dll.

    void TestStructInStruct3(MYPERSON3 person3);
    
  • TestArrayInStruct diekspor dari PinvokeLib.dll.

    void TestArrayInStruct(MYARRAYSTRUCT* pStruct);
    

PinvokeLib.dll adalah pustaka tidak terkelola kustom yang berisi implementasi untuk fungsi yang terdaftar sebelumnya dan empat struktur: MYPERSON, MYPERSON2, MYPERSON3, dan MYARRAYSTRUCT. Struktur ini berisi elemen-elemen berikut:

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;

Struktur MyPerson, MyPerson2, MyPerson3, dan MyArrayStruct yang dikelola memiliki karakteristik sebagai berikut:

  • MyPerson hanya berisi anggota string. Bidang CharSet mengatur string ke format ANSI saat diteruskan ke fungsi yang tidak dikelola.

  • MyPerson2 berisi IntPtr ke struktur MyPerson. Jenis IntPtr menggantikan pointer asli ke struktur tidak terkelola karena aplikasi .NET Framework tidak menggunakan pointer kecuali kode ditandai tidak aman.

  • MyPerson3 berisi MyPerson sebagai struktur tersemat. Struktur tersemat dalam struktur lain dapat diratakan dengan menempatkan elemen struktur tersemat langsung ke dalam struktur utama, atau dapat dibiarkan sebagai struktur tersemat, seperti yang dilakukan dalam sampel ini.

  • MyArrayStruct berisi array bilangan bulat. Atribut MarshalAsAttribute mengatur nilai enumerasi UnmanagedType ke ByValArray, yang digunakan untuk menunjukkan jumlah elemen dalam array.

Untuk semua struktur dalam sampel ini, atribut StructLayoutAttribute diterapkan untuk memastikan bahwa anggota disusun dalam memori secara berurutan, dalam urutan kemunculannya.

Kelas NativeMethods berisi prototipe terkelola untuk metode TestStructInStruct, TestStructInStruct3, dan TestArrayInStruct yang dipanggil oleh kelas App. Setiap prototipe mendeklarasikan satu parameter, sebagai berikut:

  • TestStructInStruct mendeklarasikan referensi untuk jenis MyPerson2 sebagai parameternya.

  • TestStructInStruct3 mendeklarasikan jenis MyPerson3 sebagai parameternya dan meneruskan parameter berdasarkan nilai.

  • TestArrayInStruct mendeklarasikan referensi untuk jenis MyArrayStruct sebagai parameternya.

Struktur sebagai argumen ke metode diteruskan dengan nilai kecuali parameter berisi kata kunci ref (ByRef dalam Visual Basic). Misalnya, metode TestStructInStruct meneruskan referensi (nilai alamat) ke objek berjenis MyPerson2 ke kode yang tidak dikelola. Untuk memanipulasi struktur yang ditunjuk MyPerson2, sampel membuat buffer dengan ukuran tertentu dan mengembalikan alamatnya dengan menggabungkan metode Marshal.AllocCoTaskMem dan Marshal.SizeOf. Selanjutnya, sampel menyalin konten struktur yang dikelola ke buffer yang tidak dikelola. Terakhir, sampel menggunakan metode Marshal.PtrToStructure untuk menyusun data dari buffer yang tidak dikelola ke objek yang dikelola dan metode Marshal.FreeCoTaskMem untuk mengosongkan blok memori yang tidak dikelola.

Mendeklarasikan Prototipe

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

Fungsi Panggilan

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

Sampel FindFile

Sampel ini menunjukkan cara meneruskan struktur yang berisi struktur tersemat kedua ke fungsi yang tidak dikelola. Sampel ini juga menunjukkan cara menggunakan atribut MarshalAsAttribute untuk mendeklarasikan array dengan panjang tetap dalam struktur. Dalam sampel ini, elemen struktur tersemat ditambahkan ke struktur induk. Untuk sampel struktur tersemat yang tidak diratakan, lihat Sampel Struktur.

Sampel FindFile menggunakan fungsi tidak terkelola berikut, yang ditunjukkan dengan deklarasi fungsi aslinya:

  • FindFirstFile diekspor dari Kernel32.dll.

    HANDLE FindFirstFile(LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData);
    

Struktur asli yang diteruskan ke fungsi berisi elemen-elemen berikut:

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;

Dalam sampel ini, kelas FindData berisi anggota data yang sesuai untuk setiap elemen dari struktur asli dan struktur yang disematkan. Di tempat dua buffer karakter asli, kelas menggantikan string. MarshalAsAttribute mengatur UnmanagedType enumerasi ke ByValTStr, yang digunakan untuk mengidentifikasi sebaris, array karakter dengan panjang tetap yang muncul dalam struktur yang tidak dikelola.

Kelas NativeMethods berisi prototipe terkelola dari metode FindFirstFile, yang meneruskan kelas FindData sebagai parameter. Parameter harus dideklarasikan dengan atribut InAttribute dan OutAttribute karena kelas, yang merupakan jenis referensi, diteruskan sebagai parameter Masuk secara default.

Mendeklarasikan Prototipe

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

Fungsi Panggilan

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

Sampel gabungan

Sampel ini menunjukkan cara meneruskan struktur yang hanya berisi jenis nilai, dan struktur yang berisi jenis nilai dan string sebagai parameter ke fungsi tidak terkelola yang mengharapkan penggabungan. Gabungan mewakili lokasi memori yang dapat digunakan bersama dua atau lebih variabel.

Sampel Gabungan menggunakan fungsi tidak terkelola berikut, yang ditunjukkan dengan deklarasi fungsi aslinya:

  • TestUnion diekspor dari PinvokeLib.dll.

    void TestUnion(MYUNION u, int type);
    

PinvokeLib.dll adalah pustaka tidak terkelola khusus yang berisi implementasi untuk fungsi yang terdaftar sebelumnya dan dua gabungan, MYUNION dan MYUNION2. Gabungan berisi elemen-elemen berikut:

union MYUNION
{
    int number;
    double d;
}

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

Dalam kode terkelola, gabungan didefinisikan sebagai struktur. Struktur MyUnion berisi dua jenis nilai sebagai anggotanya: bilangan bulat dan ganda. Atribut StructLayoutAttribute diatur untuk mengontrol posisi yang tepat dari setiap anggota data. Atribut FieldOffsetAttribute memberikan posisi fisik bidang dalam representasi gabungan yang tidak dikelola. Perhatikan bahwa kedua anggota memiliki nilai offset yang sama, sehingga anggota dapat menentukan bagian memori yang sama.

MyUnion2_1 dan MyUnion2_2 masing-masing berisi jenis nilai (bilangan bulat) dan string. Dalam kode terkelola, jenis nilai dan jenis referensi tidak diizinkan untuk tumpang tindih. Sampel ini menggunakan kelebihan muatan metode untuk memungkinkan pemanggil menggunakan kedua jenis saat memanggil fungsi tidak terkelola yang sama. Tata letak MyUnion2_1 bersifat eksplisit dan memiliki nilai offset yang tepat. Sebaliknya, MyUnion2_2 memiliki tata letak berurutan, karena tata letak eksplisit tidak diizinkan dengan jenis referensi. Atribut MarshalAsAttribute mengatur enumerasi UnmanagedType ke ByValTStr, yang digunakan untuk mengidentifikasi sebaris, array karakter dengan panjang tetap yang muncul dalam representasi gabungan yang tidak dikelola.

Kelas NativeMethods berisi prototipe untuk metode TestUnion dan TestUnion2. TestUnion2 kelebihan muatan untuk mendeklarasikan MyUnion2_1 atau MyUnion2_2 sebagai parameter.

Mendeklarasikan Prototipe

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

Fungsi Panggilan

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

Sampel platform

Dalam beberapa skenario, tata letak struct dan union dapat berbeda bergantung pada platform yang ditargetkan. Misalnya, pertimbangkan jenis STRRET saat ditentukan dalam skenario COM:

#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 di atas dideklarasikan dengan header Windows yang memengaruhi tata letak memori jenisnya. Saat ditentukan dalam lingkungan terkelola, detail tata letak ini diperlukan untuk beroperasi dengan benar menggunakan kode asli.

Definisi terkelola yang benar dari jenis ini dalam proses 32-bit adalah:

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

Pada proses 64-bit, ukuran dan bidang offset berbeda. Tata letak yang benar adalah:

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

Kegagalan untuk mempertimbangkan tata letak asli dengan benar dalam skenario interop dapat mengakibatkan crash acak atau lebih buruk lagi, perhitungan yang salah.

Secara default, rakitan .NET dapat berjalan di runtime .NET versi 32-bit dan 64-bit. Aplikasi harus menunggu hingga runtime memutuskan definisi mana yang akan digunakan sebelumnya.

Cuplikan kode berikut menunjukkan contoh cara memilih antara definisi 32-bit dan 64-bit pada runtime.

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

Sampel SysTime

Sampel ini menunjukkan cara meneruskan pointer ke kelas ke fungsi tidak terkelola yang mengharapkan pointer ke struktur.

Sampel SysTime menggunakan fungsi tidak terkelola berikut ini, yang ditunjukkan dengan deklarasi fungsi aslinya:

  • GetSystemTime diekspor dari Kernel32.dll.

    VOID GetSystemTime(LPSYSTEMTIME lpSystemTime);
    

Struktur asli yang diteruskan ke fungsi berisi elemen-elemen berikut:

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

Dalam sampel ini, kelas SystemTime berisi elemen struktur asli yang direpresentasikan sebagai anggota kelas. Atribut StructLayoutAttribute diatur untuk memastikan bahwa anggota diatur dalam memori secara berurutan, dalam urutan kemunculannya.

Kelas NativeMethods berisi prototipe terkelola dari metode GetSystemTime, yang meneruskan kelas SystemTime sebagai parameter Masuk/Keluar secara default. Parameter harus dideklarasikan dengan atribut InAttribute dan OutAttribute karena kelas, yang merupakan jenis referensi, diteruskan sebagai parameter Masuk secara default. Agar pemanggil menerima hasil, atribut arah ini harus diterapkan secara eksplisit. Kelas App membuat instans baru dari kelas SystemTime dan mengakses bidang datanya.

Sampel Kode

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

Sampel OutArrayOfStructs

Sampel ini menunjukkan cara meneruskan array struktur yang berisi bilangan bulat dan string sebagai parameter Keluar ke fungsi yang tidak dikelola.

Sampel ini menunjukkan cara memanggil fungsi asli dengan menggunakan kelas Marshal dan dengan menggunakan kode yang tidak aman.

Sampel ini menggunakan fungsi pembungkus dan pemanggilan platform yang ditentukan dalam PinvokeLib.dll, juga disediakan dalam file sumber. Ini menggunakan fungsi TestOutArrayOfStructs dan struktur MYSTRSTRUCT2. Struktur tersebut berisi elemen-elemen berikut:

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

Kelas MyStruct berisi objek string karakter ANSI. Bidang CharSet menentukan format ANSI. MyUnsafeStruct, adalah struktur yang berisi jenis IntPtr, bukan string.

Kelas NativeMethods berisi metode prototipe TestOutArrayOfStructs yang kelebihan muatan. Jika suatu metode mendeklarasikan pointer sebagai parameter, kelas harus ditandai dengan kata kunci unsafe. Karena Visual Basic tidak dapat menggunakan kode tidak aman, metode kelebihan muatan, pengubah tidak aman, dan struktur MyUnsafeStruct tidak diperlukan.

Kelas App mengimplementasikan metode UsingMarshaling, yang melakukan semua tugas yang diperlukan untuk meneruskan array. Array ditandai dengan kata kunci out (ByRef dalam Visual Basic) untuk menunjukkan bahwa data berpindah dari yang dipanggil ke pemanggil. Implementasinya menggunakan metode kelas Marshal berikut:

  • PtrToStructure untuk menyusun data dari buffer yang tidak dikelola ke objek yang dikelola.

  • DestroyStructure untuk merilis memori yang disediakan untuk string dalam struktur.

  • FreeCoTaskMem untuk merilis memori yang dicadangkan untuk array.

Seperti yang disebutkan sebelumnya, C# mengizinkan kode tidak aman dan Visual Basic tidak. Dalam sampel C#, UsingUnsafePointer adalah implementasi metode alternatif yang menggunakan pointer, bukan kelas Marshal untuk mengembalikan array yang berisi struktur MyUnsafeStruct.

Mendeklarasikan Prototipe

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

Fungsi Panggilan

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

Lihat juga