Bagikan melalui


Mengawal jenis

Pengawalan adalah proses transformasi jenis ketika mereka harus menyeberang di antara kode terkelola dan kode asli.

Pengawalan diperlukan karena jenis dalam kode terkelola dan tidak terkelola berbeda. Dalam kode terkelola, misalnya, Anda memiliki string, sementara string yang tidak dikelola dapat berupa pengodean .NET string (UTF-16), pengodean Halaman Kode ANSI, UTF-8, dihentikan secara null, ASCII, dll. Secara default, subsistem P/Invoke mencoba melakukan hal yang benar berdasarkan perilaku default, yang dijelaskan dalam artikel ini. Namun, untuk situasi di mana Anda membutuhkan kontrol ekstra, Anda dapat menggunakan atribut MarshalAs untuk menentukan apa jenis yang diharapkan di sisi yang tidak dikelola. Misalnya, jika Anda ingin string dikirim sebagai string UTF-8 yang dihentikan null, Anda dapat melakukannya seperti ini:

[LibraryImport("somenativelibrary.dll")]
static extern int MethodA([MarshalAs(UnmanagedType.LPStr)] string parameter);

// or

[LibraryImport("somenativelibrary.dll", StringMarshalling = StringMarshalling.Utf8)]
static extern int MethodB(string parameter);

Jika Anda menerapkan atribut System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute ke rakitan ini, aturan di bagian berikut tidak berlaku. Untuk informasi tentang bagaimana nilai .NET diekspos ke kode asli saat atribut ini diterapkan, lihat pengawalan runtime yang dinonaktifkan.

Aturan default untuk pengawalan jenis umum

Umumnya, runtime mencoba melakukan "hal yang benar" ketika mengawal membutuhkan jumlah pekerjaan paling sedikit dari Anda. Tabel berikut ini menjelaskan bagaimana setiap jenis dikawal secara default saat digunakan dalam parameter atau bidang. Bilangan bulat lebar-tetap C99/C++11 dan jenis karakter digunakan untuk memastikan bahwa tabel berikut ini benar untuk semua platform. Anda dapat menggunakan jenis asli apa pun yang memiliki persyaratan penyelarasan dan ukuran yang sama dengan jenis ini.

Tabel pertama ini menjelaskan pemetaan untuk berbagai jenis yang pengawalannya sama baik untuk P/Invoke maupaun pengawalan bidang.

Kata kunci C# Jenis .NET Jenis Asli
byte System.Byte uint8_t
sbyte System.SByte int8_t
short System.Int16 int16_t
ushort System.UInt16 uint16_t
int System.Int32 int32_t
uint System.UInt32 uint32_t
long System.Int64 int64_t
ulong System.UInt64 uint64_t
char System.Char Baik char atau char16_t tergantung pada pengodean P/Invoke atau struktur. Lihat dokumentasi tataan karakter.
System.Char Baik char* atau char16_t* tergantung pada pengodean P/Invoke atau struktur. Lihat dokumentasi tataan karakter.
nint System.IntPtr intptr_t
nuint System.UIntPtr uintptr_t
Jenis Pointer .NET (mis. void*) void*
Jenis yang berasal dari System.Runtime.InteropServices.SafeHandle void*
Jenis yang berasal dari System.Runtime.InteropServices.CriticalHandle void*
bool System.Boolean Jenis Win32 BOOL
decimal System.Decimal Struktur COM DECIMAL
Delegasi .NET Penunjuk fungsi asli
System.DateTime Jenis Win32 DATE
System.Guid Jenis Win32 GUID

Beberapa kategori pengawalan memiliki default yang berbeda jika Anda melakukan pengawalan sebagai parameter atau struktur.

Jenis .NET Jenis Asli (Parameter) Jenis Asli (Bidang)
Array .NET Penunjuk ke awal array representasi asli dari elemen array. Tidak diizinkan tanpa atribut [MarshalAs]
Kelas dengan LayoutKind dari Sequential atau Explicit Penunjuk ke representasi asli dari kelas tersebut Representasi asli dari kelas tersebut

Tabel berikut ini menyertakan aturan pengawalan default yang hanya-Windows. Pada platform non-Windows, Anda tidak dapat mengawal jenis ini.

Jenis .NET Jenis Asli (Parameter) Jenis Asli (Bidang)
System.Object VARIANT IUnknown*
System.Array Antarmuka COM Tidak diizinkan tanpa atribut [MarshalAs]
System.ArgIterator va_list Tidak diizinkan
System.Collections.IEnumerator IEnumVARIANT* Tidak diizinkan
System.Collections.IEnumerable IDispatch* Tidak diizinkan
System.DateTimeOffset int64_t mewakili jumlah kutu sejak tengah malam pada 1 Januari 1601 int64_t mewakili jumlah kutu sejak tengah malam pada 1 Januari 1601

Beberapa jenis hanya dapat dikawal sebagai parameter dan bukan sebagai bidang. Jenis-jenis ini tercantum dalam tabel berikut:

Jenis .NET Jenis Asli (Hanya Parameter)
System.Text.StringBuilder Baik char* atau char16_t* tergantung pada CharSet dari P/Invoke. Lihat dokumentasi tataan karakter.
System.ArgIterator va_list (hanya pada Windows x86/x64/arm64)
System.Runtime.InteropServices.ArrayWithOffset void*
System.Runtime.InteropServices.HandleRef void*

Jika default ini tidak melakukan apa yang Anda inginkan, Anda dapat menyesuaikan bagaimana parameter dikawal. Artikel pengawalan parameter memandu Anda cara menyesuaikan bagaimana berbagai jenis parameter dikawal.

Pengawalan default dalam skenario COM

Ketika Anda memanggil metode pada objek COM di .NET, runtime .NET mengubah aturan pengawalan default agar sesuai dengan semantik COM umum. Tabel berikut mencantumkan aturan yang digunakan runtime bahasa umum .NET dalam skenario COM:

Jenis .NET Jenis Asli (panggilan metode COM)
System.Boolean VARIANT_BOOL
StringBuilder LPWSTR
System.String BSTR
Jenis delegasi _Delegate* dalam .NET Framework. Tidak diizinkan dalam .NET Core dan .NET 5+.
System.Drawing.Color OLECOLOR
Array .NET SAFEARRAY
System.String[] SAFEARRAY dari banyak BSTR

Pengawalan kelas dan struktur

Aspek lain dari pengawalan jenis adalah cara meneruskan struct ke metode yang tidak dikelola. Misalnya, beberapa metode yang tidak dikelola memerlukan struct sebagai parameter. Dalam kasus ini, Anda perlu membuat struktur yang sesuai atau kelas di bagian terkelola dunia untuk menggunakannya sebagai parameter. Namun, hanya mendefinisikan kelas tidak cukup, Anda juga perlu menginstruksikan pengawal cara memetakan bidang di kelas ke struktur yang tidak dikelola. Di sini atribut StructLayout menjadi berguna.

[LibraryImport("kernel32.dll")]
static partial void GetSystemTime(out SystemTime systemTime);

[StructLayout(LayoutKind.Sequential)]
struct SystemTime
{
    public ushort Year;
    public ushort Month;
    public ushort DayOfWeek;
    public ushort Day;
    public ushort Hour;
    public ushort Minute;
    public ushort Second;
    public ushort Millisecond;
}

public static void Main(string[] args)
{
    SystemTime st = new SystemTime();
    GetSystemTime(st);
    Console.WriteLine(st.Year);
}

Kode sebelumnya menunjukkan contoh sederhana panggilan ke dalam fungsi GetSystemTime(). Bit yang menarik ada di baris 4. Atribut menentukan bahwa bidang kelas harus dipetakan secara berurutan ke struktur di sisi lain (yang tidak terkelola). Artinya penamaan bidang tidak penting, hanya urutannya yang penting, karena harus sesuai dengan struktur yang tidak dikelola, yang ditunjukkan dalam contoh berikut:

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

Terkadang pengawalan default untuk struktur Anda tidak melakukan apa yang Anda perlukan. Artikel Menyesuaikan pengawalan struktur mengajarkan Anda cara menyesuaikan bagaimana struktur Anda dikawal.