Praktik terbaik interoperabilitas asli
.NET memberi Anda berbagai cara untuk menyesuaikan kode interoperabilitas asli Anda. Artikel ini menyertakan panduan yang diikuti tim .NET Microsoft untuk interoperabilitas asli.
Panduan Umum
Panduan di bagian ini berlaku untuk semua skenario interop.
- ✔️ DO gunakan
[LibraryImport]
, jika memungkinkan, saat menargetkan .NET 7+.- Ada kasus saat menggunakan
[DllImport]
sudah sesuai. Penganalisis kode dengan ID SYSLIB1054 memberi tahu Anda kapan itu masalahnya.
- Ada kasus saat menggunakan
- ✔️ GUNAKAN penamaan dan kapitalisasi yang sama untuk metode dan parameter Anda sebagai metode asli yang ingin Anda panggil.
- ✔️ PERTIMBANGKAN menggunakan penamaan dan kapitalisasi yang sama untuk nilai konstanta.
- ✔️ GUNAKAN jenis .NET yang memetakan paling dekat dengan jenis asli. Misalnya, dalam C#, gunakan
uint
saat jenis aslinya adalahunsigned int
. - ✔️ DO lebih suka mengekspresikan jenis asli tingkat yang lebih tinggi menggunakan struktur .NET daripada kelas.
- ✔️ DO lebih suka menggunakan penunjuk fungsi, dibandingkan dengan
Delegate
jenis, saat meneruskan panggilan balik ke fungsi yang tidak dikelola di C#. - ✔️ Penggunaan
[In]
DO dan[Out]
atribut pada parameter array. - ✔️ DO hanya menggunakan
[In]
atribut dan[Out]
pada jenis lain ketika perilaku yang Anda inginkan berbeda dari perilaku default. - ✔️ PERTIMBANGKAN menggunakan System.Buffers.ArrayPool<T> untuk mengumpulkan buffer array asli Anda.
- ✔️ PERTIMBANGKAN untuk membungkus deklarasi P/Invoke Anda di kelas dengan nama dan kapitalisasi yang sama dengan pustaka asli Anda.
- Ini memungkinkan atribut atau
[LibraryImport]
Anda[DllImport]
untuk menggunakan fitur bahasa C#nameof
untuk meneruskan nama pustaka asli dan memastikan bahwa Anda tidak salah mengeja nama pustaka asli.
- Ini memungkinkan atribut atau
- ✔️ DO menggunakan
SafeHandle
handel untuk mengelola masa pakai objek yang merangkum sumber daya yang tidak dikelola. Untuk informasi selengkapnya, lihat Membersihkan sumber daya yang tidak dikelola. - ❌ HINDARI finalizer untuk mengelola masa pakai objek yang merangkum sumber daya yang tidak dikelola. Untuk informasi selengkapnya, lihat Menerapkan metode Buang.
Pengaturan atribut LibraryImport
Penganalisis kode, dengan ID SYSLIB1054, membantu memandu Anda dengan LibraryImportAttribute
. Dalam kebanyakan kasus, penggunaan LibraryImportAttribute
memerlukan deklarasi eksplisit daripada mengandalkan pengaturan default. Desain ini disengaja dan membantu menghindari perilaku yang tidak diinginkan dalam skenario interop.
Pengaturan atribut DllImport
Pengaturan | Default | Rekomendasi | Detail |
---|---|---|---|
PreserveSig | true |
Pertahankan default | Ketika ini secara eksplisit diatur ke false, nilai gagal HRESULT yang ditampilkan akan diubah menjadi pengecualian (dan nilai yang ditampilkan dalam definisi menjadi null sebagai hasilnya). |
SetLastError | false |
Bergantung pada API | Atur ini ke true jika API menggunakan GetLastError dan gunakan Marshal.GetLastWin32Error untuk mendapatkan nilainya. Jika API menetapkan kondisi yang menunjukkan bahwa API memiliki kesalahan, dapatkan kesalahan sebelum melakukan panggilan lain untuk menghindari penimpaannya secara tidak sengaja. |
CharSet | Ditentukan pengkompilasi (ditentukan dalam dokumentasi tataan karakter) | Secara eksplisit menggunakan CharSet.Unicode atau CharSet.Ansi ketika string atau karakter ada dalam definisi |
Ini menentukan perilaku marshalling string dan apa yang dilakukan ExactSpelling ketika false . Perhatikan bahwa CharSet.Ansi sebenarnya UTF8 di Unix.
Sebagian besar waktu Windows menggunakan Unicode sementara Unix menggunakan UTF8. Lihat informasi selengkapnya tentang dokumentasi tentang tataan karakter. |
ExactSpelling | false |
true |
Atur ini ke true dan dapatkan sedikit manfaat perf karena runtime tidak akan mencari nama fungsi alternatif dengan akhiran "A" atau "W" tergantung pada nilai pengaturan CharSet ("A" untuk CharSet.Ansi dan "W" untuk CharSet.Unicode ). |
Parameter string
string
disematkan dan digunakan langsung oleh kode asli (daripada disalin) ketika diteruskan oleh nilai (bukan ref
atau out
) dan salah satu dari yang berikut:
- LibraryImportAttribute.StringMarshalling didefinisikan sebagai Utf16.
- Argumen secara eksplisit ditandai sebagai
[MarshalAs(UnmanagedType.LPWSTR)]
. - DllImportAttribute.CharSet adalah Unicode.
❌ JANGAN gunakan [Out] string
parameter. Parameter string yang diteruskan oleh nilai dengan atribut [Out]
dapat mendestabilisasi runtime jika string adalah string yang ditahan. Lihat informasi selengkapnya tentang penahanan string dalam dokumentasi untuk String.Intern.
✔️ PERTIMBANGKAN larik char[]
atau byte[]
dari ArrayPool
saat kode asli diharapkan untuk mengisi buffer karakter. Ini memerlukan penerusan argumen sebagai [Out]
.
Panduan khusus DllImport
✔️ PERTIMBANGKAN untuk mengatur properti CharSet
di [DllImport]
sehingga runtime mengetahui pengodean string yang diharapkan.
✔️ PERTIMBANGKAN untuk menghindari parameter StringBuilder
. marshalling StringBuilder
selalu membuat salinan buffer asli. Dengan demikian, itu bisa sangat tidak efisien. Ambil skenario umum memanggil API Windows yang mengambil string:
- Buat
StringBuilder
dari kapasitas yang diinginkan (mengalokasikan kapasitas terkelola) {1}. - Memohon:
- Alokasikan buffer asli {2}.
- Menyalin konten jika
[In]
(default untukStringBuilder
parameter). - Menyalin buffer asli ke dalam array terkelola yang baru dialokasikan jika
[Out]
{3}(juga default untukStringBuilder
).
-
ToString()
mengalokasikan array terkelola lainnya {4}.
Itu alokasi {4} untuk mendapatkan string dari kode asli. Yang terbaik yang dapat Anda lakukan untuk membatasi ini adalah menggunakan StringBuilder
kembali dalam panggilan lain, tetapi ini masih hanya menyimpan satu alokasi. Jauh lebih baik menggunakan dan menyimpan buffer karakter dari ArrayPool
. Anda kemudian dapat turun hanya ke alokasi untuk ToString()
pada panggilan berikutnya.
Masalah lain dengan StringBuilder
adalah selalu menyalin buffer yang ditampilkan kembali ke null pertama. Jika string yang diteruskan kembali tidak dihentikan atau merupakan string dua kali dihentikan null, P/Invoke Anda sepertinya salah.
Jika Anda memang menggunakan StringBuilder
, satu gotcha terakhir adalah bahwa kapasitas tidak menyertakan null tersembunyi, yang selalu diperkirakan dalam interop. Umum bagi orang untuk salah dalam hal ini karena sebagian besar API menginginkan ukuran buffer termasuk null. Ini dapat mengakibatkan alokasi yang terbuang/tidak perlu. Selain itu, gotcha ini mencegah runtime mengoptimalkan marshalling StringBuilder
untuk meminimalkan salinan.
Untuk informasi selengkapnya tentang marshalling string, lihat Marshalling Default untuk String dan Menyesuaikan marshalling string.
Khusus Windows Untuk string
[Out]
yang akan digunakan CLRCoTaskMemFree
secara default ke string bebas atauSysStringFree
untuk string yang ditandai sebagaiUnmanagedType.BSTR
. Untuk sebagian besar API dengan buffer string output: Jumlah karakter yang diteruskan harus menyertakan null. Jika nilai yang ditampilkan kurang dari jumlah karakter yang diteruskan, panggilan telah berhasil dan nilainya adalah jumlah karakter tanpa null berikutnya. Jika tidak, hitungan adalah ukuran buffer yang diperlukan termasuk karakter null.
- Teruskan 5, dapatkan 4: String panjangnya 4 karakter dengan null berikutnya.
- Teruskan 5, dapatkan 6: String panjangnya 5 karakter, memerlukan 6 karakter buffer untuk menahan null. Jenis Data Windows untuk String
Parameter dan bidang Boolean
Sulit sekali menggunakan Boolean. Secara default, .NET bool
di-marshall ke Windows BOOL
, di mana nilainya adalah nilai 4-byte. Namun, _Bool
, dan jenis bool
dalam C dan C++ adalah satu byte. Ini dapat menyebabkan kesulitan untuk melacak bug karena setengah nilai yang ditampilkan akan dibuang, yang hanya akan berpotensi mengubah hasilnya. Untuk informasi selengkapnya tentang marshalling nilai bool
.NET ke jenis C atau C++ bool
, lihat dokumentasi tentang menyesuaikan marshalling bidang boolean.
GUID
GUID dapat digunakan langsung dalam tanda tangan. Banyak API Windows mengambil alias jenis GUID&
seperti REFIID
. Saat tanda tangan metode berisi parameter referensi, tempatkan ref
kata kunci atau [MarshalAs(UnmanagedType.LPStruct)]
atribut pada deklarasi parameter GUID.
GUID | By-ref GUID |
---|---|
KNOWNFOLDERID |
REFKNOWNFOLDERID |
❌ JANGAN Gunakan [MarshalAs(UnmanagedType.LPStruct)]
untuk apa pun selain ref
parameter GUID.
Jenis blittable
Jenis blittable adalah jenis yang memiliki representasi tingkat bit yang sama dalam kode terkelola dan asli. Dengan demikian mereka tidak perlu dikonversi ke format lain untuk di-marshall ke dan dari kode asli, dan karena ini meningkatkan performa mereka harus lebih disukai. Beberapa jenis tidak dapat di-blittable tetapi diketahui berisi konten yang dapat di-blittable. Jenis-jenis ini memiliki pengoptimalan yang sama dengan jenis blittable ketika tidak terkandung dalam jenis lain, tetapi tidak dianggap blittable ketika berada di bidang structs atau untuk tujuan UnmanagedCallersOnlyAttribute
.
Jenis blittable ketika marshalling runtime diaktifkan
Jenis blittable:
-
byte
, ,sbyte
short
,ushort
,int
,uint
,long
,ulong
, ,single
,double
- struktur dengan tata letak tetap yang hanya memiliki jenis nilai yang dapat di-blittable untuk bidang instans
- tata letak tetap memerlukan
[StructLayout(LayoutKind.Sequential)]
atau[StructLayout(LayoutKind.Explicit)]
- structs
LayoutKind.Sequential
secara default
- tata letak tetap memerlukan
Jenis dengan konten yang blittable:
- array satu dimensi yang tidak berlapis dari jenis primitif yang blittable (misalnya,
int[]
) - kelas dengan tata letak tetap yang hanya memiliki jenis nilai yang dapat di-blittable untuk bidang instans
- tata letak tetap memerlukan
[StructLayout(LayoutKind.Sequential)]
atau[StructLayout(LayoutKind.Explicit)]
- kelas
LayoutKind.Auto
secara default
- tata letak tetap memerlukan
TIDAK blittable:
bool
TERKADANG blittable:
char
Jenis dengan konten yang TERKADANG blittable:
string
Ketika jenis blittable diteruskan oleh referensi dengan in
, ref
, atau out
, atau ketika jenis dengan konten blittable diteruskan oleh nilai, mereka hanya disematkan oleh marshaller alih-alih disalin ke buffer perantara.
char
blittable dalam array satu dimensi atau jika merupakan bagian dari jenis yang berisinya yang ditandai secara eksplisit [StructLayout]
dengan CharSet = CharSet.Unicode
.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct UnicodeCharStruct
{
public char c;
}
string
berisi konten yang dapat di-blittable jika tidak terkandung dalam jenis lain dan sedang diteruskan oleh nilai (bukan ref
atau out
) sebagai argumen dan salah satu hal berikut:
- StringMarshalling didefinisikan sebagai Utf16.
- Argumen secara eksplisit ditandai sebagai
[MarshalAs(UnmanagedType.LPWSTR)]
. - CharSet adalah Unicode.
Anda dapat melihat apakah jenis blittable atau berisi konten yang blittable dengan mencoba membuat GCHandle
yang disematkan. Jika jenisnya bukan string atau dianggap blittable, GCHandle.Alloc
akan menampilkan ArgumentException
.
Jenis blittable saat marshalling runtime dinonaktifkan
Ketika marshalling runtime dinonaktifkan, aturan jenis mana yang blittable secara signifikan lebih sederhana. Semua jenis yang merupakan jenis C# unmanaged
dan tidak memiliki bidang apa pun yang ditandai dengan [StructLayout(LayoutKind.Auto)]
adalah blittable. Semua jenis yang bukan jenis C# unmanaged
tidak blittable. Konsep jenis dengan konten yang blittable, seperti array atau string, tidak berlaku saat marshalling runtime dinonaktifkan. Jenis apa pun yang tidak dianggap blittable oleh aturan yang disebutkan di atas tidak didukung saat marshalling runtime dinonaktifkan.
Aturan ini berbeda dari sistem bawaan terutama dalam situasi di mana bool
dan char
digunakan. Saat marshalling dinonaktifkan, bool
diteruskan sebagai nilai 1-byte dan tidak dinormalisasi dan char
selalu diteruskan sebagai nilai 2-byte. Saat marshalling runtime diaktifkan, bool
dapat memetakan ke nilai 1, 2, atau 4 byte dan selalu dinormalisasi, dan char
memetakan ke nilai 1 atau 2 byte tergantung pada CharSet
.
✔️ BUAT struktur Anda blittable jika memungkinkan.
Untuk informasi selengkapnya, lihat:
Menjaga objek terkelola tetap hidup
GC.KeepAlive()
akan memastikan objek tetap berada dalam cakupan hingga metode KeepAlive tercapai.
HandleRef
memungkinkan marshaller untuk menjaga objek tetap hidup selama P/Invoke. Ini dapat digunakan alih-alih IntPtr
dalam tanda tangan metode.
SafeHandle
secara efektif menggantikan kelas ini dan harus digunakan sebagai gantinya.
GCHandle
memungkinkan penyematan objek terkelola dan mendapatkan penunjuk asli ke objek tersebut. Pola dasarnya adalah:
GCHandle handle = GCHandle.Alloc(obj, GCHandleType.Pinned);
IntPtr ptr = handle.AddrOfPinnedObject();
handle.Free();
Penyematan bukan default untuk GCHandle
. Pola utama lainnya adalah untuk meneruskan referensi ke objek terkelola melalui kode asli dan kembali ke kode terkelola, biasanya dengan panggilan balik. Berikut adalah polanya:
GCHandle handle = GCHandle.Alloc(obj);
SomeNativeEnumerator(callbackDelegate, GCHandle.ToIntPtr(handle));
// In the callback
GCHandle handle = GCHandle.FromIntPtr(param);
object managedObject = handle.Target;
// After the last callback
handle.Free();
Jangan lupa bahwa GCHandle
perlu dibebaskan secara eksplisit untuk menghindari kebocoran memori.
Jenis data Windows umum
Berikut adalah daftar jenis data yang umum digunakan dalam API Windows dan jenis C# mana yang akan digunakan saat memanggil kode Windows.
Jenis berikut berukuran sama pada Windows 32-bit dan 64-bit, terlepas dari namanya.
Width | Windows | C# | Alternatif |
---|---|---|---|
32 | BOOL |
int |
bool |
8 | BOOLEAN |
byte |
[MarshalAs(UnmanagedType.U1)] bool |
8 | BYTE |
byte |
|
8 | UCHAR |
byte |
|
8 | UINT8 |
byte |
|
8 | CCHAR |
byte |
|
8 | CHAR |
sbyte |
|
8 | CHAR |
sbyte |
|
8 | INT8 |
sbyte |
|
16 | CSHORT |
short |
|
16 | INT16 |
short |
|
16 | SHORT |
short |
|
16 | ATOM |
ushort |
|
16 | UINT16 |
ushort |
|
16 | USHORT |
ushort |
|
16 | WORD |
ushort |
|
32 | INT |
int |
|
32 | INT32 |
int |
|
32 | LONG |
int |
Lihat CLong dan CULong . |
32 | LONG32 |
int |
|
32 | CLONG |
uint |
Lihat CLong dan CULong . |
32 | DWORD |
uint |
Lihat CLong dan CULong . |
32 | DWORD32 |
uint |
|
32 | UINT |
uint |
|
32 | UINT32 |
uint |
|
32 | ULONG |
uint |
Lihat CLong dan CULong . |
32 | ULONG32 |
uint |
|
64 | INT64 |
long |
|
64 | LARGE_INTEGER |
long |
|
64 | LONG64 |
long |
|
64 | LONGLONG |
long |
|
64 | QWORD |
long |
|
64 | DWORD64 |
ulong |
|
64 | UINT64 |
ulong |
|
64 | ULONG64 |
ulong |
|
64 | ULONGLONG |
ulong |
|
64 | ULARGE_INTEGER |
ulong |
|
32 | HRESULT |
int |
|
32 | NTSTATUS |
int |
Jenis berikut, menjadi pointer, mengikuti lebar platform. Gunakan IntPtr
/UIntPtr
untuk ini.
Jenis Pointer yang Bertanda (gunakan IntPtr ) |
Jenis Penunjuk yang Tidak Bertanda (gunakan UIntPtr ) |
---|---|
HANDLE |
WPARAM |
HWND |
UINT_PTR |
HINSTANCE |
ULONG_PTR |
LPARAM |
SIZE_T |
LRESULT |
|
LONG_PTR |
|
INT_PTR |
Windows PVOID
, yang merupakan C void*
, dapat di-marshall sebagai IntPtr
atau UIntPtr
, tetapi lebih baik void*
jika memungkinkan.
Sebelumnya jenis bawaan yang didukung
Ada instans langka saat dukungan bawaan untuk jenis dihapus.
UnmanagedType.HString
Dukungan UnmanagedType.IInspectable
marshal bawaan dihapus dalam rilis .NET 5. Anda harus mengkombinasikan ulang biner yang menggunakan jenis marshalling ini dan yang menargetkan kerangka kerja sebelumnya. Masih mungkin untuk me-marshal jenis ini, tetapi Anda harus me-marshal secara manual, seperti yang ditunjukkan oleh contoh kode berikut. Kode ini ke depannya akan bekerja dan juga kompatibel dengan kerangka kerja sebelumnya.
public sealed class HStringMarshaler : ICustomMarshaler
{
public static readonly HStringMarshaler Instance = new HStringMarshaler();
public static ICustomMarshaler GetInstance(string _) => Instance;
public void CleanUpManagedData(object ManagedObj) { }
public void CleanUpNativeData(IntPtr pNativeData)
{
if (pNativeData != IntPtr.Zero)
{
Marshal.ThrowExceptionForHR(WindowsDeleteString(pNativeData));
}
}
public int GetNativeDataSize() => -1;
public IntPtr MarshalManagedToNative(object ManagedObj)
{
if (ManagedObj is null)
return IntPtr.Zero;
var str = (string)ManagedObj;
Marshal.ThrowExceptionForHR(WindowsCreateString(str, str.Length, out var ptr));
return ptr;
}
public object MarshalNativeToManaged(IntPtr pNativeData)
{
if (pNativeData == IntPtr.Zero)
return null;
var ptr = WindowsGetStringRawBuffer(pNativeData, out var length);
if (ptr == IntPtr.Zero)
return null;
if (length == 0)
return string.Empty;
return Marshal.PtrToStringUni(ptr, length);
}
[DllImport("api-ms-win-core-winrt-string-l1-1-0.dll")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
private static extern int WindowsCreateString([MarshalAs(UnmanagedType.LPWStr)] string sourceString, int length, out IntPtr hstring);
[DllImport("api-ms-win-core-winrt-string-l1-1-0.dll")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
private static extern int WindowsDeleteString(IntPtr hstring);
[DllImport("api-ms-win-core-winrt-string-l1-1-0.dll")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
private static extern IntPtr WindowsGetStringRawBuffer(IntPtr hstring, out int length);
}
// Example usage:
[DllImport("api-ms-win-core-winrt-l1-1-0.dll", PreserveSig = true)]
internal static extern int RoGetActivationFactory(
/*[MarshalAs(UnmanagedType.HString)]*/[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(HStringMarshaler))] string activatableClassId,
[In] ref Guid iid,
[Out, MarshalAs(UnmanagedType.IUnknown)] out object factory);
Pertimbangan jenis data lintas platform
Ada jenis dalam bahasa C/C++ yang memiliki garis lintang dalam bagaimana mereka didefinisikan. Saat menulis interop lintas platform, kasus dapat muncul di mana platform berbeda dan dapat menyebabkan masalah jika tidak dipertimbangkan.
C/C++ long
C/C++ long
dan C# long
belum tentu berukuran sama.
Jenis long
dalam C/C++ didefinisikan untuk memiliki "setidaknya 32" bit. Ini berarti ada jumlah minimum bit yang diperlukan, tetapi platform dapat memilih untuk menggunakan lebih banyak bit jika diinginkan. Tabel berikut mengilustrasikan perbedaan bit yang disediakan untuk jenis data C/C++ long
antar platform.
Platform | 32-bit | 64-bit |
---|---|---|
Windows | 32 | 32 |
macOS/*nix | 32 | 64 |
Sebaliknya, C# long
selalu 64 bit. Untuk alasan ini, yang terbaik adalah menghindari penggunaan C# long
untuk menginteropsi C/C++ long
.
(Masalah dengan C/C++ long
ini tidak ada untuk C/C++ char
, , short
, int
dan long long
karena masing-masing 8, 16, 32, dan 64 bit pada semua platform ini.)
Di .NET 6 dan versi yang lebih baru, gunakan jenis CLong
dan CULong
untuk interop dengan C/C++ long
dan jenis data unsigned long
. Contoh berikut adalah untuk CLong
, tetapi Anda dapat menggunakan CULong
untuk mengabstraksi unsigned long
dengan cara yang sama.
// Cross platform C function
// long Function(long a);
[DllImport("NativeLib")]
extern static CLong Function(CLong a);
// Usage
nint result = Function(new CLong(10)).Value;
Saat menargetkan .NET 5 dan versi yang lebih lama, Anda harus mendeklarasikan tanda tangan Windows dan non-Windows terpisah untuk menangani masalah.
static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
// Cross platform C function
// long Function(long a);
[DllImport("NativeLib", EntryPoint = "Function")]
extern static int FunctionWindows(int a);
[DllImport("NativeLib", EntryPoint = "Function")]
extern static nint FunctionUnix(nint a);
// Usage
nint result;
if (IsWindows)
{
result = FunctionWindows(10);
}
else
{
result = FunctionUnix(10);
}
Structs
Struct terkelola dibuat pada tumpukan dan tidak dihapus sampai metode ditampilkan. Setelah itu, berdasarkan definisi, mereka "disematkan" (tidak akan dipindahkan oleh GC). Anda juga dapat mengambil alamat dalam blok kode yang tidak aman jika kode asli tidak akan menggunakan pointer melewati akhir metode saat ini.
Struct blittable berkinerja lebih baik karena mereka hanya dapat digunakan langsung oleh lapisan marshalling. Cobalah untuk membuat struct yang blittable (misalnya, hindari bool
). Untuk informasi selengkapnya, lihat bagian Jenis Blittable.
Jika struktur blittable, gunakan sizeof()
alih-alih Marshal.SizeOf<MyStruct>()
untuk performa yang lebih baik. Seperti disebutkan di atas, Anda dapat memvalidasi bahwa jenisnya blittable dengan mencoba membuat GCHandle
yang disematkan. Jika jenisnya bukan string atau dianggap blittable, GCHandle.Alloc
akan menampilkan ArgumentException
.
Penunjuk ke struktur dalam definisi harus diteruskan oleh ref
atau menggunakan unsafe
dan *
.
✔️ COCOKKAN dengan struct terkelola sedekat mungkin dengan bentuk dan nama yang digunakan dalam dokumentasi atau header platform resmi.
✔️ GUNAKAN C# sizeof()
alih-alih Marshal.SizeOf<MyStruct>()
untuk struktur yang blittable untuk meningkatkan performa.
❌ JANGAN bergantung pada representasi internal struktur data yang diekspos oleh pustaka runtime .NET apabila dokumentasi tidak secara eksplisit menjabarkannya.
❌ HINDARI menggunakan kelas untuk mengekspresikan jenis asli yang kompleks melalui pewarisan.
❌ HINDARI menggunakan bidang System.Delegate
atau System.MulticastDelegate
untuk mewakili bidang pointer fungsi dalam struktur.
Karena System.Delegate dan System.MulticastDelegate tidak memiliki tanda tangan yang diperlukan, mereka tidak menjamin bahwa delegasi yang diteruskan akan cocok dengan tanda tangan yang diharapkan kode asli. Selain itu, dalam .NET Framework dan .NET Core, marshalling struct yang berisi System.Delegate
atau System.MulticastDelegate
dari representasi aslinya ke objek terkelola dapat mengacaukan runtime jika nilai bidang dalam representasi asli bukan penunjuk fungsi yang membungkus delegasi terkelola. Dalam .NET 5 dan versi yang lebih baru, marshalling bidang System.Delegate
atau System.MulticastDelegate
dari representasi asli ke objek terkelola tidak didukung. Gunakan jenis delegasi tertentu alih-alih System.Delegate
atau System.MulticastDelegate
.
Buffer Tetap
Array seperti INT_PTR Reserved1[2]
harus dinaungi ke dua bidang IntPtr
, Reserved1a
dan Reserved1b
. Ketika array asli adalah jenis primitif, kita dapat menggunakan kata kunci fixed
untuk menulisnya sedikit lebih bersih. Misalnya, SYSTEM_PROCESS_INFORMATION
terlihat seperti ini di header asli:
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
BYTE Reserved1[48];
UNICODE_STRING ImageName;
...
} SYSTEM_PROCESS_INFORMATION
Dalam C#, kita dapat menulisnya seperti ini:
internal unsafe struct SYSTEM_PROCESS_INFORMATION
{
internal uint NextEntryOffset;
internal uint NumberOfThreads;
private fixed byte Reserved1[48];
internal Interop.UNICODE_STRING ImageName;
...
}
Namun, ada beberapa gotcha dengan buffer tetap. Buffer tetap dari jenis yang tidak blittable tidak akan di-marshall dengan benar, sehingga array di tempat perlu diperluas ke beberapa bidang individual. Selain itu, di .NET Framework dan .NET Core sebelum 3.0, jika struct yang berisi bidang buffer tetap ditumpuk dalam struktur yang tidak blittable, bidang buffer tetap tidak akan dinaungi dengan benar ke kode asli.