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.