Yapı hazırlamayı özelleştirme

Bazen yapılar için varsayılan sıralama kuralları tam olarak ihtiyacınız olan şey değildir. .NET çalışma zamanları, yapınızın düzenini ve alanların nasıl düzenlendiğini özelleştirmeniz için birkaç uzantı noktası sağlar. Yapı yerleşimini özelleştirme tüm senaryolarda desteklenir, ancak alan marshalling’ini özelleştirme yalnızca çalışma zamanı marshalling’inin etkin olduğu senaryolarda desteklenir. Çalışma zamanı hazırlama devre dışı bırakıldıysa, tüm alan hazırlamaları el ile yapılmalıdır.

Uyarı

Bu makale, kaynak tarafından oluşturulan birlikte çalışabilirlik için marshalling’in özelleştirilmesini kapsamaz. P/Invokes için kaynak tarafından üretilen birlikte çalışmayı veya COM kullanıyorsanız, marshalling’i özelleştirme konusuna bakın.

Yapı düzenini özelleştirme

.NET, alanların belleğe nasıl yerleştirileceğini özelleştirmenize olanak tanımak için System.Runtime.InteropServices.StructLayoutAttribute özniteliğini ve System.Runtime.InteropServices.LayoutKind numaralandırma türünü sağlar. Aşağıdaki kılavuz yaygın sorunlardan kaçınmanıza yardımcı olacaktır.

✔️ Mümkün olduğunda kullanmayı LayoutKind.Sequential GÖZ ÖNÜNDE BULUNDURUN.

✔️ LayoutKind.Explicit öğesini marshalling işleminde yalnızca yerel yapınız da örneğin bir birleşim gibi açık bir yerleşime sahipse kullanın.

❌ Devralma yoluyla karmaşık yerel türleri ifade etmek için sınıfları kullanmaktan KAÇıNıN.

❌ .NET Core 3.0’dan önceki çalışma zamanlarını hedeflemeniz gerekiyorsa, Windows dışı platformlarda yapıları marshal ederken LayoutKind.Explicit kullanmaktan KAÇININ. 3.0 öncesi .NET Core çalışma zamanı, Intel veya AMD 64 bit Windows olmayan sistemlerde yerel işlevlere değere göre açık yapıların geçirilmesini desteklemez. Ancak çalışma zamanı, tüm platformlarda açıkça tanımlanmış yapıların referansla aktarılmasını destekler.

Sabit boyutlu arabellekler ve satır içi diziler

Bir yapıda sabit boyutlu bir arabellek tanımlamanız gerekiyorsa, ek alan ayırmak için özelliğini kullanmayın StructLayoutAttribute.Size . Size özelliği, türün yönetilmeyen düzenini (Marshal.StructureToPtr çağrılırken kullanılan düzen) kontrol eder ve yalnızca blittable türlerde yönetilen düzeni etkiler. Kesilebilir olmayan türler için çalışma zamanı yalnızca gerçek alanlara göre alan ayırır ve bu da fazladan alan kullanmayı denerseniz bellek bozulmasına neden olabilir.

❌ Yönetilen türlerde sabit boyutlu arabellekler oluşturmak için StructLayoutAttribute.Size kullanmaktan KAÇININ.

✔️ Modern .NET'te (C# 12 ve üzeri) sabit boyutlu arabellekleri tanımlamak için mutlaka System.Runtime.CompilerServices.InlineArrayAttribute kullanın.

Aşağıdaki örnek, bellek bozulmasına neden olabilecek yanlış yaklaşımı gösterir:

// ❌ DON'T: This is dangerous and can corrupt memory
[StructLayout(LayoutKind.Explicit, Size = 100)]
class Bad100ByteBuffer
{
    [FieldOffset(0)]
    private byte b;

    unsafe void UseBuffer()
    {
        fixed (byte* p = &b)
        {
            // Writing beyond the first byte corrupts memory
        }
    }
}

Bunun yerine, uygun boyutta bir arabellek oluşturmak için InlineArrayAttribute kullanın:

// ✔️ DO: Use InlineArrayAttribute for fixed-size buffers
class Good100ByteBuffer
{
    [InlineArray(100)]
    private struct Buffer100
    {
        private byte _element0;
    }

    private Buffer100 buffer;

    unsafe void UseBuffer()
    {
        fixed (byte* p = &buffer[0])
        {
            // The buffer is guaranteed to be 100 bytes
        }
    }
}

Satır içi diziler hakkında daha fazla bilgi için bkz. C# dil başvurusu.

Boole alanı sıralamasını özelleştirme

Yerel kodun birçok farklı Boole gösterimi vardır. Yalnızca Windows'ta bile Boole değerlerini temsil etmenin üç yolu vardır. Çalışma zamanı yapınızın yerel tanımını bilmiyor, bu nedenle en iyisi Boole değerlerinizi nasıl sıralayabileceğinizi tahmin etmektir. .NET çalışma zamanı, Boolean alanınızın nasıl işleneceğini belirtmenin bir yolunu sunar. Aşağıdaki örnekler, .NET bool öğesinin farklı yerel Boolean türleri olarak nasıl düzenleneceğini gösterir.

Boolean değerler, aşağıdaki örnekte gösterildiği gibi varsayılan olarak yerel 4 baytlık Win32 BOOL değeri olarak marshal edilir:

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

Açıkça belirtmek isterseniz, yukarıdakiyle aynı davranışı elde etmek için UnmanagedType.Bool değerini kullanabilirsiniz:

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

Aşağıdaki UnmanagedType.U1 veya UnmanagedType.I1 değerlerini kullanarak, çalışma zamanına b alanını 1 baytlık yerel bool türü olarak düzenlemesini söyleyebilirsiniz.

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

Windows'ta, çalışma zamanına Boole değerinizi 2 baytlık bir VARIANT_BOOL değerine dönüştürmesini belirtmek için UnmanagedType.VariantBool değerini kullanabilirsiniz:

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

Uyarı

VARIANT_BOOL, çoğu bool türünden şu bakımdan farklıdır: VARIANT_TRUE = -1 ve VARIANT_FALSE = 0. Buna ek olarak, eşit VARIANT_TRUE olmayan tüm değerler false olarak kabul edilir.

Dizi alanı sıralamasını özelleştirme

.NET ayrıca dizi marshalling’ini özelleştirmek için birkaç yöntem sunar.

Varsayılan olarak, .NET dizileri öğelerin bitişik bir listesine yönelik bir işaretçi olarak düzenler:

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

COM API’leriyle çalışıyorsanız, dizileri SAFEARRAY* nesneleri olarak marshaling yapmanız gerekebilir. System.Runtime.InteropServices.MarshalAsAttribute ve UnmanagedType.SafeArray değerlerini, çalışma zamanına bir diziyi SAFEARRAY* olarak düzenlemesini belirtmek için kullanabilirsiniz:

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

SAFEARRAY içindeki öğe türünü özelleştirmeniz gerekiyorsa, SAFEARRAY öğesinin tam öğe türünü özelleştirmek için MarshalAsAttribute.SafeArraySubType ve MarshalAsAttribute.SafeArrayUserDefinedSubType alanlarını kullanabilirsiniz.

Diziyi yerinde marshal etmeniz gerekiyorsa, marshaller’a diziyi yerinde marshal etmesini söylemek için UnmanagedType.ByValArray değerini kullanabilirsiniz. Bu düzenlemeyi kullanırken, çalışma zamanının yapı için doğru şekilde alan ayırabilmesi amacıyla dizideki öğe sayısını belirten MarshalAsAttribute.SizeConst alanına da bir değer sağlamanız gerekir.

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

Uyarı

.NET, değişken uzunlukta bir dizi alanının C99 esnek dizi üyesi olarak marshalledilmesini desteklemez.

Dize alanı sıralamasını özelleştirme

.NET, dize alanlarını sıralamak için çok çeşitli özelleştirmeler de sağlar.

Varsayılan olarak, .NET bir dizeyi null olarak sonlandırılan bir dizeye işaretçi olarak sıralar. Kodlama, System.Runtime.InteropServices.StructLayoutAttribute içindeki StructLayoutAttribute.CharSet alanının değerine bağlıdır. Öznitelik belirtilmezse, kodlama varsayılan olarak ANSI kodlaması olur.

[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.
};

Farklı alanlar için farklı kodlamalar kullanmanız gerekiyorsa ya da yalnızca struct tanımınızda daha açık olmayı tercih ediyorsanız, bir System.Runtime.InteropServices.MarshalAsAttribute özniteliğinde UnmanagedType.LPStr veya UnmanagedType.LPWStr değerlerini kullanabilirsiniz.

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.
};

DIZElerinizi UTF-8 kodlamasını kullanarak sıralamak istiyorsanız, içindeki UnmanagedType.LPUTF8Strdeğerini kullanabilirsinizMarshalAsAttribute.

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

Uyarı

UnmanagedType.LPUTF8Str kullanmak için .NET Framework 4.7 (veya sonraki sürümler) ya da .NET Core 1.1 (veya sonraki sürümler) gerekir. .NET Standard 2.0'da kullanılamaz.

COM API'leriyle çalışıyorsanız, bir dizeyi BSTR olarak sıralamanız gerekebilir. UnmanagedType.BStr değerini kullanarak bir dizgeyi BSTR olarak düzenleyebilirsiniz.

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

Bir WinRT tabanlı API kullanırken, bir dizeyi HSTRING olarak sıralamanız gerekebilir. UnmanagedType.HString değerini kullanarak bir dizeyi HSTRING olarak dönüştürebilirsiniz. HSTRING marshalling yalnızca yerleşik WinRT desteğine sahip çalışma zamanlarında desteklenir. WinRT desteği .NET 5'te kaldırıldığındanHSTRING, .NET 5 veya daha yeni sürümlerde marshalling desteklenmez.

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

API'niz, dizeyi doğrudan yapı içinde iletmenizi gerektiriyorsa UnmanagedType.ByValTStr değerini kullanabilirsiniz. ByValTStr tarafından düzenlenen bir dizenin kodlamasının, CharSet özniteliğine göre belirlendiğini unutmayın. Ayrıca, MarshalAsAttribute.SizeConst alanı üzerinden bir dize uzunluğunun iletilmesini gerektirir.

[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.
};

Ondalık alan sıralamasını özelleştirme

Windows üzerinde çalışıyorsanız yerel CY veya CURRENCY yapıyı kullanan bazı API'lerle karşılaşabilirsiniz. Varsayılan olarak, .NET decimal türü yerel DECIMAL yapısına dönüştürülür. Ancak, marshaller'a bir decimal değerini yerel bir CY değerine dönüştürmesi talimatını vermek için, UnmanagedType.Currency değeriyle birlikte bir MarshalAsAttribute kullanabilirsiniz.

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

Birleşimler

Birleşim, aynı belleğin üzerinde farklı veri türleri içerebilen bir veri türüdür. Bu, C dilindeki yaygın bir veri biçimidir. Bir birleşim, .NET'te LayoutKind.Explicit kullanılarak ifade edilebilir. .NET bir birleşim tanımlarken yapıların kullanılması önerilir. Sınıfların kullanılması düzen sorunlarına neden olabilir ve öngörülemeyen davranışlara neden olabilir.

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;
    }
}

Marshal System.Object

Windows'ta, object türündeki alanları yerel koda sıralama düzeniyle aktarabilirsiniz. Bu alanları üç türden birine göre sıralayabilirsiniz:

Varsayılan olarak, object türünde bir alan, nesneyi saran bir IUnknown* olarak serileştirilir.

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

Bir nesne alanını IDispatch* olarak serileştirmek istiyorsanız, UnmanagedType.IDispatch değerine sahip bir MarshalAsAttribute ekleyin.

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

Bunu VARIANT olarak işlemek istiyorsanız, UnmanagedType.Struct değerine sahip bir MarshalAsAttribute ekleyin.

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

Aşağıdaki tablo, obj alanının farklı çalışma zamanı türlerinin, VARIANT içinde depolanan çeşitli türlerle nasıl eşlendiğini açıklamaktadır:

.NET Türü VARIANT Türü
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