Bagikan melalui


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.
  • ✔️ 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 adalah unsigned 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.
  • ✔️ 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:

❌ 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 StringBuilderselalu membuat salinan buffer asli. Dengan demikian, itu bisa sangat tidak efisien. Ambil skenario umum memanggil API Windows yang mengambil string:

  1. Buat StringBuilder dari kapasitas yang diinginkan (mengalokasikan kapasitas terkelola) {1}.
  2. Memohon:
    1. Alokasikan buffer asli {2}.
    2. Menyalin konten jika [In](default untuk StringBuilder parameter).
    3. Menyalin buffer asli ke dalam array terkelola yang baru dialokasikan jika [Out]{3}(juga default untuk StringBuilder).
  3. 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 CLR CoTaskMemFree secara default ke string bebas atau SysStringFree untuk string yang ditandai sebagai UnmanagedType.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, , sbyteshort, 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

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

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.

Jenis Data Windows

Rentang Jenis Data

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, intdan 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.