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 strukturMyPerson
. Jenis IntPtr menggantikan pointer asli ke struktur tidak terkelola karena aplikasi .NET Framework tidak menggunakan pointer kecuali kode ditandai tidak aman.MyPerson3
berisiMyPerson
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 jenisMyPerson2
sebagai parameternya.TestStructInStruct3
mendeklarasikan jenisMyPerson3
sebagai parameternya dan meneruskan parameter berdasarkan nilai.TestArrayInStruct
mendeklarasikan referensi untuk jenisMyArrayStruct
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