Bagikan melalui


Mengawal jenis

Marshalling adalah proses mengubah jenis ketika mereka perlu berpindah antara kode terkelola dan kode asli.

Marshalling diperlukan karena jenis dalam kode terkelola dan tidak terkelola berbeda. Dalam kode terkelola, misalnya, Anda mempunyai string, sementara string yang tidak dikelola dapat berupa pengodean .NET string (UTF-16), pengodean Halaman Kode ANSI, UTF-8, diakhiri dengan null, ASCII, dll. Secara default, subsistem P/Invoke berusaha melakukan hal yang tepat berdasarkan perilaku default, yang dijelaskan dalam artikel ini. Namun, untuk situasi di mana Anda memerlukan kontrol ekstra, Anda dapat menggunakan atribut MarshalAs untuk menentukan jenis yang diharapkan di sisi yang tidak dikelola. Misalnya, jika Anda ingin mengirim string sebagai string UTF-8 yang berakhir dengan null, Anda dapat melakukannya seperti ini:

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

// or

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

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

Aturan default untuk pengaturan data jenis umum

Umumnya, waktu berjalan mencoba melakukan "hal yang benar" ketika melakukan marshalling agar Anda tidak perlu melakukan banyak pekerjaan. Tabel berikut menjelaskan bagaimana setiap jenis data diproses secara default saat digunakan dalam parameter atau kolom. Bilangan bulat dan jenis karakter lebar tetap C99/C++11 digunakan untuk memastikan bahwa tabel berikut ini benar untuk semua platform. Anda dapat menggunakan tipe asli apa pun yang memiliki persyaratan alignment dan ukuran yang sama dengan tipe ini.

Tabel pertama ini menjelaskan pemetaan untuk jenis di mana proses marshalling sama untuk P/Invoke dan marshalling 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 charset.
System.Char Baik char* atau char16_t* tergantung pada pengodean P/Invoke atau struktur. Lihat dokumentasi pengkodean karakter.
nint System.IntPtr intptr_t
nuint System.UIntPtr uintptr_t
Jenis Penunjuk .NET (mis. void*) void*
Jenis berasal dari System.Runtime.InteropServices.SafeHandle void*
Jenis 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 marshalling memiliki default yang berbeda ketika Anda melakukan marshalling dalam bentuk parameter atau struktur.

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

Tabel berikut ini menyertakan aturan marshalling default yang hanya untuk Windows. Pada platform non-Windows, Anda tidak dapat melakukan marshal jenis ini.

Jenis .NET Jenis Asli (Parameter) Jenis Asli (Bidang)
System.Object VARIANT IUnknown*
System.Array Antarmuka COM Tidak diizinkan tanpa [MarshalAs] atribut
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 diubah sebagai parameter dan bukan sebagai kolom. Jenis-jenis ini tercantum dalam tabel berikut:

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

Jika pengaturan default ini tidak melakukan apa yang Anda inginkan, Anda dapat menyesuaikan cara parameter diproses. Artikel marshalling parameter memandu Anda tentang cara menyesuaikan bagaimana berbagai jenis parameter dimarshalling.

Pengaturan default marshalling dalam konteks COM

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

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

Marshalling kelas dan struktur data

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

using System;
using System.Runtime.InteropServices;

Win32Interop.GetSystemTime(out Win32Interop.SystemTime systemTime);

Console.WriteLine(systemTime.Year);

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

    [StructLayout(LayoutKind.Sequential)]
    internal ref 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;
    }
}

Kode sebelumnya menunjukkan contoh sederhana pemanggilan ke dalam GetSystemTime() fungsi. Bit yang menarik ada di baris 13. Atribut menentukan bahwa bidang kelas harus dipetakan secara berurutan ke struktur di sisi lain (tidak terkelola). Ini berarti bahwa penamaan bidang tidak penting, hanya urutannya yang penting, karena perlu 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, *LPSYSTEMTIME;

Terkadang pengaturan standar untuk marshalling struktur Anda tidak melakukan apa yang Anda butuhkan. Artikel Menyesuaikan struktur marshalling mengajarkan Anda cara menyesuaikan bagaimana struktur Anda diproses.