Bagikan melalui


Menyesuaikan penyusunan struktur

Terkadang aturan penyusunan default untuk struktur tidak persis seperti yang Anda butuhkan. Runtime .NET menyediakan beberapa titik ekstensi bagi Anda untuk menyesuaikan tata letak struktur Anda dan bagaimana bidang disusun. Menyesuaikan tata letak struktur didukung untuk semua skenario, tetapi menyesuaikan penyusunan bidang hanya didukung untuk skenario saat penyusunan runtime diaktifkan. Jika penyusunan runtime dinonaktifkan, maka setiap penyusunan bidang harus dilakukan secara manual.

Catatan

Artikel ini tidak mencakup penyesuaian marshalling untuk interop yang dihasilkan sumber. Jika Anda menggunakan interop yang dihasilkan sumber untuk P/Invokes atau COM, lihat menyesuaikan marshalling.

Menyesuaikan tata letak struktur

.NET menyediakan atribut System.Runtime.InteropServices.StructLayoutAttribute dan enumerasi System.Runtime.InteropServices.LayoutKind untuk memungkinkan Anda menyesuaikan bagaimana bidang ditempatkan di memori. Panduan berikut akan membantu Anda menghindari masalah umum.

✔️ PERTIMBANGKAN untuk menggunakan LayoutKind.Sequential jika memungkinkan.

✔️ PASTIKAN untuk menggunakan LayoutKind.Explicit saja dalam penyusunan ketika struktur asli Anda juga memiliki tata letak eksplisit, seperti gabungan.

❌ HINDARI menggunakan kelas untuk mengekspresikan jenis asli yang kompleks melalui pewarisan.

❌ HINDARI menggunakan LayoutKind.Explicit saat menyusun struktur pada platform non-Windows jika Anda perlu menargetkan runtime sebelum .NET Core 3.0. Runtime .NET Core sebelum 3.0 tidak mendukung dalam meneruskan struktur eksplisit berdasarkan nilai ke fungsi asli pada sistem non-Windows 64-bit Intel atau AMD. Tetapi, runtime mendukung dalam meneruskan struktur eksplisit dengan referensi di semua platform.

Menyesuaikan penyusunan bidang Boolean

Kode asli memiliki banyak representasi Boolean yang berbeda. Di Windows saja, ada tiga cara untuk merepresentasikan nilai Boolean. Runtime tidak mengetahui definisi asli dari struktur Anda, jadi yang terbaik yang bisa dilakukan runtime adalah menebak cara menyusun nilai Boolean Anda. Runtime .NET menyediakan cara untuk menunjukkan cara menyusun bidang Boolean Anda. Contoh berikut menunjukkan cara menyusun bool .NET ke jenis Boolean asli yang berbeda.

Nilai boolean default untuk menyusun sebagai nilai BOOL Win32 4-byte asli seperti yang ditunjukkan pada contoh berikut:

public struct WinBool
{
    public bool b;
}
struct WinBool
{
    public BOOL b;
};

Jika Anda ingin secara eksplisit, Anda dapat menggunakan nilai UnmanagedType.Bool untuk mendapatkan perilaku yang sama seperti di atas:

public struct WinBool
{
    [MarshalAs(UnmanagedType.Bool)]
    public bool b;
}
struct WinBool
{
    public BOOL b;
};

Dengan menggunakan nilai UnmanagedType.U1 atau UnmanagedType.I1 di bawah, Anda dapat memberi tahu runtime untuk menyusun bidang b sebagai jenis bool asli 1-byte.

public struct CBool
{
    [MarshalAs(UnmanagedType.U1)]
    public bool b;
}
struct CBool
{
    public bool b;
};

Di Windows, Anda dapat menggunakan nilai UnmanagedType.VariantBool untuk memberi tahu runtime guna menyusun nilai Boolean Anda menjadi nilai VARIANT_BOOL 2 byte:

public struct VariantBool
{
    [MarshalAs(UnmanagedType.VariantBool)]
    public bool b;
}
struct VariantBool
{
    public VARIANT_BOOL b;
};

Catatan

VARIANT_BOOL berbeda dari kebanyakan jenis bool dalam VARIANT_TRUE = -1 dan VARIANT_FALSE = 0 tersebut. Selain itu, semua nilai yang tidak sama dengan VARIANT_TRUE dianggap salah.

Menyesuaikan penyusunan bidang array

.NET juga menyertakan beberapa cara untuk menyesuaikan penyusunan array.

Secara default, .NET menyusun array sebagai penunjuk ke daftar elemen yang berdekatan:

public struct DefaultArray
{
    public int[] values;
}
struct DefaultArray
{
    int32_t* values;
};

Jika Anda berinteraksi dengan COM API, Anda mungkin harus menyusun array sebagai objek SAFEARRAY*. Anda dapat menggunakan nilai System.Runtime.InteropServices.MarshalAsAttribute dan UnmanagedType.SafeArray untuk memberi tahu runtime agar menyusun array sebagai SAFEARRAY*:

public struct SafeArrayExample
{
    [MarshalAs(UnmanagedType.SafeArray)]
    public int[] values;
}
struct SafeArrayExample
{
    SAFEARRAY* values;
};

Jika Anda perlu menyesuaikan jenis elemen apa yang ada di SAFEARRAY, Anda dapat menggunakan bidang MarshalAsAttribute.SafeArraySubType dan MarshalAsAttribute.SafeArrayUserDefinedSubType untuk menyesuaikan jenis elemen yang tepat dari SAFEARRAY.

Jika Anda perlu menyusun array di tempat, Anda dapat menggunakan nilai UnmanagedType.ByValArray untuk memberi tahu penyusun agar menyusun array di tempat. Saat Anda menggunakan penyusunan ini, Anda juga harus memberikan nilai ke bidang MarshalAsAttribute.SizeConst untuk jumlah elemen dalam array sehingga runtime dapat mengalokasikan ruang dengan benar untuk struktur.

public struct InPlaceArray
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public int[] values;
}
struct InPlaceArray
{
    int values[4];
};

Catatan

.NET tidak mendukung penyusunan bidang array panjang variabel sebagai Anggota Array Fleksibel C99.

Menyesuaikan penyusunan bidang string

.NET juga menyediakan berbagai macam penyesuaian untuk menyusun bidang string.

Secara default, .NET menyusun string sebagai penunjuk ke string yang diakhiri null. Pengodean bergantung pada nilai bidang StructLayoutAttribute.CharSet di System.Runtime.InteropServices.StructLayoutAttribute. Jika tidak ada atribut yang ditentukan, pengodean akan diatur ke defaultnya, ke pengodean ANSI.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DefaultString
{
    public string str;
}
struct DefaultString
{
    char* str;
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DefaultString
{
    public string str;
}
struct DefaultString
{
    char16_t* str; // Could also be wchar_t* on Windows.
};

Jika Anda perlu menggunakan pengodean yang berbeda untuk bidang yang berbeda atau hanya ingin lebih eksplisit dalam definisi struktur, Anda dapat menggunakan nilai UnmanagedType.LPStr atau UnmanagedType.LPWStr pada atribut System.Runtime.InteropServices.MarshalAsAttribute.

public struct AnsiString
{
    [MarshalAs(UnmanagedType.LPStr)]
    public string str;
}
struct AnsiString
{
    char* str;
};
public struct UnicodeString
{
    [MarshalAs(UnmanagedType.LPWStr)]
    public string str;
}
struct UnicodeString
{
    char16_t* str; // Could also be wchar_t* on Windows.
};

Jika ingin menyusun string Anda menggunakan pengodean UTF-8, Anda dapat menggunakan nilai UnmanagedType.LPUTF8Str di MarshalAsAttribute.

public struct UTF8String
{
    [MarshalAs(UnmanagedType.LPUTF8Str)]
    public string str;
}
struct UTF8String
{
    char* str;
};

Catatan

Menggunakan UnmanagedType.LPUTF8Str memerlukan .NET Framework 4.7 (atau versi yang lebih baru) atau .NET Core 1.1 (atau versi yang lebih baru). Hal ini tidak tersedia di .NET Standard 2.0.

Jika Anda bekerja dengan COM API, Anda mungkin perlu menyusun string sebagai BSTR. Dengan menggunakan nilai UnmanagedType.BStr, Anda dapat menyusun string sebagai BSTR.

public struct BString
{
    [MarshalAs(UnmanagedType.BStr)]
    public string str;
}
struct BString
{
    BSTR str;
};

Saat menggunakan API berbasis WinRT, Anda mungkin perlu menyusun string sebagai HSTRING. Dengan menggunakan nilai UnmanagedType.HString, Anda dapat menyusun string sebagai HSTRING. Penyusunan HSTRING hanya didukung pada runtime dengan dukungan WinRT bawaan. Dukungan WinRT dihapus di .NET 5, jadi penyusunan HSTRING tidak didukung di .NET 5 atau yang lebih baru.

public struct HString
{
    [MarshalAs(UnmanagedType.HString)]
    public string str;
}
struct BString
{
    HSTRING str;
};

Jika API Anda mengharuskan Anda untuk meneruskan string di tempat dalam struktur, Anda dapat menggunakan nilai UnmanagedType.ByValTStr. Perhatikan bahwa pengodean untuk string yang disusun oleh ByValTStr ditentukan dari atribut CharSet. Selain itu, pengodean tersebut mengharuskan panjang string diteruskan oleh bidang MarshalAsAttribute.SizeConst.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DefaultString
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
    public string str;
}
struct DefaultString
{
    char str[4];
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DefaultString
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
    public string str;
}
struct DefaultString
{
    char16_t str[4]; // Could also be wchar_t[4] on Windows.
};

Menyesuaikan penyusunan bidang desimal

Jika Anda bekerja di Windows, Anda mungkin menemukan beberapa API yang menggunakan struktur CY atau CURRENCY asli. Secara default, jenis decimal .NET menyusun ke struktur DECIMAL asli. Tetapi, Anda dapat menggunakan MarshalAsAttribute dengan nilai UnmanagedType.Currency untuk menginstruksikan penyusun agar mengonversi nilai decimal ke nilai CY asli.

public struct Currency
{
    [MarshalAs(UnmanagedType.Currency)]
    public decimal dec;
}
struct Currency
{
    CY dec;
};

Union

Penyatuan adalah jenis data yang dapat berisi berbagai jenis data di atas memori yang sama. Ini adalah bentuk umum data dalam bahasa C. Serikat dapat diekspresikan dalam .NET menggunakan LayoutKind.Explicit. Disarankan untuk menggunakan struktur saat menentukan penyatuan di .NET. Menggunakan kelas dapat menyebabkan masalah tata letak dan menghasilkan perilaku yang tidak dapat diprediksi.

struct device1_config
{
    void* a;
    void* b;
    void* c;
};
struct device2_config
{
    int32_t a;
    int32_t b;
};
struct config
{
    int32_t type;

    union
    {
        device1_config dev1;
        device2_config dev2;
    };
};
public unsafe struct Device1Config
{
    void* a;
    void* b;
    void* c;
}

public struct Device2Config
{
    int a;
    int b;
}

public struct Config
{
    public int Type;

    public _Union Anonymous;

    [StructLayout(LayoutKind.Explicit)]
    public struct _Union
    {
        [FieldOffset(0)]
        public Device1Config Dev1;

        [FieldOffset(0)]
        public Device2Config Dev2;
    }
}

Menyusun System.Object

Di Windows, Anda dapat menyusun bidang berjenis object ke kode asli. Anda dapat mengatur bidang ini ke salah satu dari tiga jenis berikut:

Secara default, bidang yang diketik object akan disusun menjadi IUnknown* yang membungkus objek.

public struct ObjectDefault
{
    public object obj;
}
struct ObjectDefault
{
    IUnknown* obj;
};

Jika Anda ingin menyusun bidang objek ke IDispatch*, tambahkan MarshalAsAttribute dengan nilai UnmanagedType.IDispatch.

public struct ObjectDispatch
{
    [MarshalAs(UnmanagedType.IDispatch)]
    public object obj;
}
struct ObjectDispatch
{
    IDispatch* obj;
};

Jika Anda ingin menyusunnya sebagai VARIANT, tambahkan MarshalAsAttribute dengan nilai UnmanagedType.Struct.

public struct ObjectVariant
{
    [MarshalAs(UnmanagedType.Struct)]
    public object obj;
}
struct ObjectVariant
{
    VARIANT obj;
};

Tabel berikut menjelaskan bagaimana jenis runtime yang berbeda dari peta bidang obj dengan berbagai jenis yang disimpan di VARIANT:

Jenis .NET Jenis VARIAN
byte VT_UI1
sbyte VT_I1
short VT_I2
ushort VT_UI2
int VT_I4
uint VT_UI4
long VT_I8
ulong VT_UI8
float VT_R4
double VT_R8
char VT_UI2
string VT_BSTR
System.Runtime.InteropServices.BStrWrapper VT_BSTR
object VT_DISPATCH
System.Runtime.InteropServices.UnknownWrapper VT_UNKNOWN
System.Runtime.InteropServices.DispatchWrapper VT_DISPATCH
System.Reflection.Missing VT_ERROR
(object)null VT_EMPTY
bool VT_BOOL
System.DateTime VT_DATE
decimal VT_DECIMAL
System.Runtime.InteropServices.CurrencyWrapper VT_CURRENCY
System.DBNull VT_NULL