Aracılığıyla paylaş


Yerel birlikte çalışabilirlik en iyi yöntemleri

.NET, yerel birlikte çalışabilirlik kodunuzu özelleştirmek için çeşitli yollar sunar. Bu makale, Microsoft'un .NET ekiplerinin yerel birlikte çalışabilirlik için izlediği yönergeleri içerir.

Genel kılavuz

Bu bölümdeki yönergeler tüm birlikte çalışma senaryoları için geçerlidir.

  • ✔️ DO, .NET 7+ hedeflendiğinde mümkünse kullanın [LibraryImport].
    • Kullanımın [DllImport] uygun olduğu durumlar vardır. Kimlik SYSLIB1054 sahip bir kod çözümleyicisi, durumun ne zaman olduğunu size bildirir.
  • ✔️ DO, çağırmak istediğiniz yerel yöntemle yöntemleriniz ve parametreleriniz için aynı adlandırma ve büyük harf kullanımını kullanır.
  • ✔️ Sabit değerler için aynı adlandırma ve büyük harf kullanımını GÖZ ÖNÜNDE BULUNDURUN.
  • ✔️ DO, yerel türe en yakın eşleyen .NET türlerini kullanın. Örneğin, C# dilinde yerel tür olduğunda unsigned intkullanınuint.
  • ✔️ DO, sınıflar yerine .NET yapılarını kullanarak daha üst düzey yerel türleri ifade etmeyi tercih eder.
  • ✔️ DO, C# dilinde yönetilmeyen işlevlere Delegate geri çağırma geçirirken türlerden farklı olarak işlev işaretçileri kullanmayı tercih eder.
  • ✔️ DIZI parametrelerinde DO kullanımı [In] ve [Out] öznitelikleri.
  • ✔️ DO, ve [Out] özniteliklerini yalnızca istediğiniz davranış varsayılan davranıştan farklı olduğunda diğer türlerde kullanır[In].
  • ✔️ Yerel dizi arabelleklerinizi havuza almak için kullanmayı System.Buffers.ArrayPool<T> GÖZ ÖNÜNDE BULUNDURUN.
  • ✔️ P/Invoke bildirimlerinizi yerel kitaplığınızla aynı ada ve büyük harfe çevirmeye sahip bir sınıfa sarmalama DÜŞÜNÜN.
    • Bu, veya [DllImport] özniteliklerinizin [LibraryImport] yerel kitaplığın adını geçirmek ve yerel kitaplığın adını yanlış yazmadığınızdan emin olmak için C# nameof dil özelliğini kullanmasına olanak tanır.

LibraryImport öznitelik ayarları

Kimlik SYSLIB1054 sahip bir kod çözümleyicisi ile LibraryImportAttributesize yol gösterir. Çoğu durumda, kullanımı LibraryImportAttribute varsayılan ayarlara güvenmek yerine açık bir bildirim gerektirir. Bu tasarım kasıtlıdır ve birlikte çalışma senaryolarında istenmeyen davranışları önlemeye yardımcı olur.

DllImport öznitelik ayarları

Ayar Varsayılan Öneri Ayrıntılar
PreserveSig true Varsayılanı koru Bu açıkça false olarak ayarlandığında, başarısız HRESULT dönüş değerleri özel durumlara dönüştürülür (ve sonuç olarak tanımdaki dönüş değeri null olur).
SetLastError false API'ye bağlıdır API GetLastError kullanıyorsa ve değeri almak için Marshal.GetLastWin32Error kullanıyorsa bunu true olarak ayarlayın. API bir hata olduğunu belirten bir koşul ayarlarsa, yanlışlıkla üzerine yazılmasını önlemek için başka çağrılar yapmadan önce hatayı alın.
CharSet Derleyici tanımlı (karakter kümesi belgelerinde belirtilir) Tanımda dizelerin veya CharSet.Ansi karakterlerin mevcut olduğu durumlarda veya açıkça kullanın CharSet.Unicode Bu, dizelerin sıralama davranışını ve olduğunda falsene ExactSpelling yaptığını belirtir. CharSet.Ansi Unix'te utf8 olduğunu unutmayın. Windows çoğu zaman Unicode kullanırken Unix UTF8 kullanır. Karakter kümeleriyle ilgili belgeler hakkında daha fazla bilgi edinin.
ExactSpelling false true Bu değeri true olarak ayarlayın ve çalışma zamanı ayarın değerine CharSet bağlı olarak "A" veya "W" soneki olan alternatif işlev adlarını aramayacağı için CharSet.Ansi küçük bir performans avantajı elde edin ("için A" ve "W" ).CharSet.Unicode

Dize parametreleri

değeri (veya outdeğilref) ve aşağıdakilerden herhangi biri tarafından geçirildiğinde, A string sabitlenir ve doğrudan yerel kod (kopyalanan değil) tarafından kullanılır:

❌ PARAMETRELERI KULLANMAYIN [Out] string . özniteliğiyle değer tarafından [Out] geçirilen dize parametreleri, dize bir dize ise çalışma zamanının istikrarını bozabilir. için belgelerde dize stajyeri hakkında daha fazla bilgi bulabilirsiniz String.Intern.

✔️ Yerel kodun ArrayPool bir karakter arabelleği doldurması beklendiğinde' den CONSIDER char[] veya byte[] dizileri. Bu, bağımsız değişkenin olarak [Out]geçirilmesini gerektirir.

DllImport'a özgü kılavuz

✔️ çalışma zamanının beklenen dize kodlamasını CharSet bilmesi için özelliğini [DllImport] ayarlamayı GÖZ ÖNÜNDE BULUNDURUN.

✔️ Parametrelerden kaçınmayı StringBuilder GÖZ ÖNÜNDE BULUNDURUN. StringBuilder marshalling her zaman yerel bir arabellek kopyası oluşturur. Bu nedenle, son derece verimsiz olabilir. Dize alan bir Windows API'sini çağırmaya ilişkin tipik senaryoyu inceleyin:

  1. İstenen kapasiteden bir StringBuilder oluşturun (yönetilen kapasiteyi ayırır). {1}
  2. Çağırmak:
    1. Yerel bir arabellek {2}ayırır.
    2. if [In](parametre StringBuilder için varsayılan) içindekileri kopyalar.
    3. Yerel arabelleği yeni ayrılmış yönetilen bir diziye kopyalar ([Out]{3}aynı zamanda için varsayılan StringBuilderdeğerdir).
  3. ToString() başka bir yönetilen dizi {4}ayırır.

Bu, {4} yerel koddan bir dize almak için ayırmaları ifade eder. Bunu sınırlamak için yapabileceğiniz en iyi yöntem, öğesini başka bir çağrıda yeniden kullanmaktır StringBuilder , ancak bu yine de yalnızca bir ayırmayı kaydeder. 'den ArrayPoolbir karakter arabelleği kullanmak ve önbelleğe almak çok daha iyidir. Ardından, sonraki çağrılarda yalnızca ayırmaya ToString() gidebilirsiniz.

Diğer sorun StringBuilder , dönüş arabelleğinin her zaman ilk null değere geri kopyalanıyor olmasıdır. Geri geçirilen dize sonlandırılmamışsa veya çift null ile sonlandırılmış bir dizeyse, P/Invoke'unuz en iyi durumda yanlıştır.

kullanırsanız StringBuilder, kapasitenin her zaman birlikte çalışmada hesaba dahil edilen gizli bir null içermemesi son bir gotcha değeridir. Çoğu API null da dahil olmak üzere arabellek boyutunu istediğinden, insanların bunu yanlış anları yaygındır. Bu, boşa/gereksiz ayırmalara neden olabilir. Buna ek olarak, bu gotcha çalışma zamanının kopyaları en aza indirmek için sıralamayı iyileştirmesini StringBuilder engeller.

Dize sıralama hakkında daha fazla bilgi için bkz . Dizeler için Varsayılan Sıralama ve Dize sıralamasını özelleştirme.

Windows'a Özgü CLR'nin varsayılan olarak boş dizeler veya SysStringFree olarak UnmanagedType.BSTRişaretlenmiş dizeler için kullanacağı CoTaskMemFree dizeler için[Out]. Çıkış dizesi arabelleği olan çoğu API için: Geçirilen karakter sayısı null değerini içermelidir. Döndürülen değer geçirilen karakter sayısından küçükse çağrı başarılı olmuştur ve değer sonunda null olmayan karakter sayısıdır. Aksi takdirde sayı, null karakter de dahil olmak üzere arabellek için gerekli boyuttur.

  • 5 değerini geçirin, 4 değerini alın: Dize 4 karakter uzunluğunda ve sonunda null var.
  • 5'i geçirin, 6'yı alın: Dize 5 karakter uzunluğundadır, null değerini tutmak için 6 karakterlik bir arabelleğe ihtiyaç duyar. Dizeler için Windows Veri Türleri

Boole parametreleri ve alanları

Boole'ları mahvetmek kolaydır. Varsayılan olarak, .NET bool , 4 baytlık bir değer olan bir Windows BOOLiçin hazırlanır. Ancak, _BoolC ve C++ içindeki ve bool türleri tek bir bayttır. Bu, döndürülen değerin yarısı atılacağından hataların izlenmesi zor olabilir ve bu da yalnızca sonucu değiştirebilir. .NET bool değerlerini C veya C++ bool türlerine sıralama hakkında daha fazla bilgi için boole alan hazırlamayı özelleştirme belgelerine bakın.

Guıd

GUID'ler doğrudan imzalarda kullanılabilir. Birçok Windows API'sinde gibi REFIIDtür diğer adları kullanılırGUID&. Yöntem imzası bir başvuru parametresi içeriyorsa, GUID parametre bildirimine bir ref[MarshalAs(UnmanagedType.LPStruct)] anahtar sözcük veya öznitelik yerleştirin.

GUID Başv GUID
KNOWNFOLDERID REFKNOWNFOLDERID

❌GUID parametreleri dışında ref bir şey için KULLANMAYIN[MarshalAs(UnmanagedType.LPStruct)].

Blittable türleri

Blittable türleri, yönetilen ve yerel kodda aynı bit düzeyi gösterimine sahip türlerdir. Bu nedenle, yerel koda ve yerel koddan sıralanacak başka bir biçime dönüştürülmeleri gerekmez ve bu da performansı artırdığından tercih edilmeleri gerekir. Bazı türler kesilebilir değildir, ancak kesilebilir içerik içerdiği bilinmektedir. Bu türler, başka bir türde yer almadığında blittable türleriyle benzer iyileştirmelere sahiptir, ancak yapı alanlarında veya amaçları doğrultusunda UnmanagedCallersOnlyAttributekesilebilir olarak kabul edilmezler.

Çalışma zamanı hazırlama etkinleştirildiğinde kesilebilir türler

Kesilebilir türler:

  • byte, , shortsbyte, , ushort, int, uint, long, ulong, single,double
  • örnek alanları için yalnızca kesilebilir değer türlerine sahip sabit düzenli yapılar
    • sabit düzen için [StructLayout(LayoutKind.Sequential)] veya gerekir [StructLayout(LayoutKind.Explicit)]
    • yapılar LayoutKind.Sequential varsayılan olarak

Kesilebilir içeriği olan türler:

  • iç içe olmayan, tek boyutlu blittable ilkel türleri dizileri (örneğin, int[])
  • örnek alanları için yalnızca blittable değer türlerine sahip sabit düzenli sınıflar
    • sabit düzen için [StructLayout(LayoutKind.Sequential)] veya gerekir [StructLayout(LayoutKind.Explicit)]
    • sınıflar LayoutKind.Auto varsayılan olarak

NOT blittable:

  • bool

BAZEN kesilebilir:

  • char

BAZEN kesilebilir içeriği olan türler:

  • string

Blittable türleri , refveya outile başvuruya ingöre geçirildiğinde veya blittable içeriği olan türler değere göre geçirildiğinde, arabelleğe kopyalanmaları yerine yalnızca sıralayıcı tarafından sabitlenirler.

chartek boyutlu bir dizide blittable veya içeren bir türün parçasıysa ile CharSet = CharSet.Unicodeaçıkça işaretlenir[StructLayout].

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct UnicodeCharStruct
{
    public char c;
}

string, başka bir türde yer almıyorsa ve değer (veya outdeğilref) tarafından bağımsız değişken olarak geçiriliyorsa ve aşağıdakilerden herhangi biri olarak geçiriliyorsa, blittable içeriği içerir:

  • StringMarshalling olarak Utf16tanımlanır.
  • Bağımsız değişken açıkça olarak [MarshalAs(UnmanagedType.LPWSTR)]işaretlenir.
  • CharSet Unicode'dur.

Sabitlenmiş GCHandlebir oluşturmayı deneyerek bir türün bölünebilir mi yoksa kesilebilir içerik mi içerdiğini görebilirsiniz. Tür bir dize değilse veya kesilebilir olarak kabul edilirse, GCHandle.Alloc bir ArgumentExceptionoluşturur.

Çalışma zamanı hazırlama devre dışı bırakıldığında kesilebilir türler

Çalışma zamanı hazırlama devre dışı bırakıldığında, türlerin kesilebilir olduğu kurallar önemli ölçüde daha basittir. C# unmanaged türü olan ve ile [StructLayout(LayoutKind.Auto)] işaretlenen hiçbir alanı olmayan tüm türler kesilebilir. C# unmanaged türü olmayan tüm türler kesilebilir değildir. Diziler veya dizeler gibi kesilebilir içeriği olan türler kavramı, çalışma zamanı hazırlama devre dışı bırakıldığında uygulanmaz. Çalışma zamanı hazırlama devre dışı bırakıldığında, yukarıda belirtilen kural tarafından kesilebilir olarak kabul edilmeyen herhangi bir tür desteklenmez.

Bu kurallar, temel olarak ve char kullanılan durumlarda bool yerleşik sistemden farklıdır. Sıralama devre dışı bırakıldığında, bool 1 baytlık bir değer olarak geçirilir ve normalleştirilemez ve char her zaman 2 bayt değeri olarak geçirilir. Çalışma zamanı hazırlama etkinleştirildiğinde, 1, bool 2 veya 4 baytlık bir değerle eşlenebilir ve her zaman normalleştirilir ve char öğesine bağlı CharSetolarak 1 veya 2 baytlık bir değerle eşlenebilir.

✔️ MÜMKÜN olduğunda yapılarınızı kesilebilir hale getirin.

Daha fazla bilgi için bkz.

Yönetilen nesneleri canlı tutma

GC.KeepAlive() , KeepAlive yöntemine isabet edene kadar nesnenin kapsamda kalmasını sağlar.

HandleRef , bir P/Invoke süresi boyunca bir nesneyi canlı tutmasına izin verir. Yöntem imzaları yerine IntPtr kullanılabilir. SafeHandle etkin bir şekilde bu sınıfın yerini alır ve bunun yerine kullanılmalıdır.

GCHandle yönetilen bir nesneyi sabitlemeye ve yerel işaretçiyi buna almaya izin verir. Temel desen:

GCHandle handle = GCHandle.Alloc(obj, GCHandleType.Pinned);
IntPtr ptr = handle.AddrOfPinnedObject();
handle.Free();

Sabitleme, için GCHandlevarsayılan değer değildir. Diğer önemli desen, yerel kod aracılığıyla yönetilen bir nesneye başvuru geçirmek ve genellikle geri çağırma ile yönetilen koda geri dönmektir. Desen şu şekildedir:

GCHandle handle = GCHandle.Alloc(obj);
SomeNativeEnumerator(callbackDelegate, GCHandle.ToIntPtr(handle));

// In the callback
GCHandle handle = GCHandle.FromIntPtr(param);
object managedObject = handle.Target;

// After the last callback
handle.Free();

Bellek sızıntılarını önlemek için bunun GCHandle açıkça serbest bırakılması gerektiğini unutmayın.

Yaygın Windows veri türleri

Windows API'lerinde yaygın olarak kullanılan veri türlerinin ve Windows koduna çağrı yapılırken kullanılacak C# türlerinin listesi aşağıdadır.

Aşağıdaki türler, adlarına rağmen 32 bit ve 64 bit Windows'ta aynı boyuttadır.

Width Windows C# Alternatif
32 BOOL int bool
8 BOOLEAN byte [MarshalAs(UnmanagedType.U1)] bool
8 BYTE byte
8 UCHAR byte
8 UINT8 byte
8 CCHAR byte
8 CHAR sbyte
8 CHAR sbyte
8 INT8 sbyte
16 CSHORT short
16 INT16 short
16 SHORT short
16 ATOM ushort
16 UINT16 ushort
16 USHORT ushort
16 WORD ushort
32 INT int
32 INT32 int
32 LONG int Bkz CLong . ve CULong.
32 LONG32 int
32 CLONG uint Bkz CLong . ve CULong.
32 DWORD uint Bkz CLong . ve CULong.
32 DWORD32 uint
32 UINT uint
32 UINT32 uint
32 ULONG uint Bkz CLong . ve CULong.
32 ULONG32 uint
64 INT64 long
64 LARGE_INTEGER long
64 LONG64 long
64 LONGLONG long
64 QWORD long
64 DWORD64 ulong
64 UINT64 ulong
64 ULONG64 ulong
64 ULONGLONG ulong
64 ULARGE_INTEGER ulong
32 HRESULT int
32 NTSTATUS int

Aşağıdaki türler, işaretçiler olarak platformun genişliğini izler. Bunlar için kullanın IntPtr/UIntPtr .

İmzalı İşaretçi Türleri (kullanın IntPtr) İmzasız İşaretçi Türleri (kullanın UIntPtr)
HANDLE WPARAM
HWND UINT_PTR
HINSTANCE ULONG_PTR
LPARAM SIZE_T
LRESULT
LONG_PTR
INT_PTR

C void*olan bir WindowsPVOID, veya UIntPtrolarak IntPtr sıralanabilir, ancak mümkün olduğunda tercih void* edilebilir.

Windows Veri Türleri

Veri Türü Aralıkları

Önceden yerleşik olarak desteklenen türler

Bir tür için yerleşik destek kaldırıldığında nadir örnekler vardır.

UnmanagedType.HString ve UnmanagedType.IInspectable yerleşik sıralama desteği .NET 5 sürümünde kaldırıldı. Bu marshalling türünü kullanan ve önceki bir çerçeveyi hedefleyen ikili dosyaları yeniden derlemeniz gerekir. Bu türü sıralamak yine de mümkündür, ancak aşağıdaki kod örneğinde gösterildiği gibi el ile hazırlamanız gerekir. Bu kod ilerlerken çalışır ve önceki çerçevelerle de uyumludur.

public sealed class HStringMarshaler : ICustomMarshaler
{
    public static readonly HStringMarshaler Instance = new HStringMarshaler();

    public static ICustomMarshaler GetInstance(string _) => Instance;

    public void CleanUpManagedData(object ManagedObj) { }

    public void CleanUpNativeData(IntPtr pNativeData)
    {
        if (pNativeData != IntPtr.Zero)
        {
            Marshal.ThrowExceptionForHR(WindowsDeleteString(pNativeData));
        }
    }

    public int GetNativeDataSize() => -1;

    public IntPtr MarshalManagedToNative(object ManagedObj)
    {
        if (ManagedObj is null)
            return IntPtr.Zero;

        var str = (string)ManagedObj;
        Marshal.ThrowExceptionForHR(WindowsCreateString(str, str.Length, out var ptr));
        return ptr;
    }

    public object MarshalNativeToManaged(IntPtr pNativeData)
    {
        if (pNativeData == IntPtr.Zero)
            return null;

        var ptr = WindowsGetStringRawBuffer(pNativeData, out var length);
        if (ptr == IntPtr.Zero)
            return null;

        if (length == 0)
            return string.Empty;

        return Marshal.PtrToStringUni(ptr, length);
    }

    [DllImport("api-ms-win-core-winrt-string-l1-1-0.dll")]
    [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
    private static extern int WindowsCreateString([MarshalAs(UnmanagedType.LPWStr)] string sourceString, int length, out IntPtr hstring);

    [DllImport("api-ms-win-core-winrt-string-l1-1-0.dll")]
    [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
    private static extern int WindowsDeleteString(IntPtr hstring);

    [DllImport("api-ms-win-core-winrt-string-l1-1-0.dll")]
    [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
    private static extern IntPtr WindowsGetStringRawBuffer(IntPtr hstring, out int length);
}

// Example usage:
[DllImport("api-ms-win-core-winrt-l1-1-0.dll", PreserveSig = true)]
internal static extern int RoGetActivationFactory(
    /*[MarshalAs(UnmanagedType.HString)]*/[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(HStringMarshaler))] string activatableClassId,
    [In] ref Guid iid,
    [Out, MarshalAs(UnmanagedType.IUnknown)] out object factory);

Platformlar arası veri türüyle ilgili dikkat edilmesi gerekenler

C/C++ dilinde tanımlanma biçiminde enlem içeren türler vardır. Platformlar arası birlikte çalışma yazarken, platformların farklı olduğu durumlar ortaya çıkabilir ve dikkate alınmadığında sorunlara neden olabilir.

C/C++ long

C/C++ long ve C# long aynı boyutta olmayabilir.

long C/C++ içindeki türün "en az 32" bit olması için tanımlanır. Bu, gerekli bitlerin en az sayıda olduğu anlamına gelir, ancak platformlar isterseniz daha fazla bit kullanmayı seçebilir. Aşağıdaki tabloda, platformlar arasındaki C/C++ long veri türü için sağlanan bitlerdeki farklar gösterilmektedir.

Platform 32 bit 64 bit
Windows 32 32
macOS/*nix 32 64

Buna karşılık, C# long her zaman 64 bittir. Bu nedenle, C/C++ longile birlikte çalışma için C# long kullanmaktan kaçınmak en iyisidir.

(C/C++ long ile ilgili bu sorun, tüm bu platformlarda sırasıyla 8, 16, 32 ve 64 bit olduğundan C/C++ intcharshortlong long , , ve için mevcut değildir.)

.NET 6 ve sonraki sürümlerinde, C/C++ long ve veri türleriyle birlikte çalışma için ve unsigned longCULong türlerini kullanınCLong. Aşağıdaki örnek içindir CLong, ancak bunu kullanarak benzer bir şekilde soyutlayabilirsiniz CULongunsigned long .

// Cross platform C function
// long Function(long a);
[DllImport("NativeLib")]
extern static CLong Function(CLong a);

// Usage
nint result = Function(new CLong(10)).Value;

.NET 5 ve önceki sürümleri hedeflerken, sorunu çözmek için ayrı Windows ve Windows dışı imzalar bildirmeniz gerekir.

static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

// Cross platform C function
// long Function(long a);

[DllImport("NativeLib", EntryPoint = "Function")]
extern static int FunctionWindows(int a);

[DllImport("NativeLib", EntryPoint = "Function")]
extern static nint FunctionUnix(nint a);

// Usage
nint result;
if (IsWindows)
{
    result = FunctionWindows(10);
}
else
{
    result = FunctionUnix(10);
}

Yapılar

Yönetilen yapılar yığında oluşturulur ve yöntem dönene kadar kaldırılmaz. Ardından tanım gereği "sabitlenir" (GC tarafından taşınmaz). Yerel kod geçerli yöntemin sonundan sonra işaretçiyi kullanmazsa adresi güvenli olmayan kod bloklarında da alabilirsiniz.

Blittable yapıları, doğrudan marshalllama katmanı tarafından kullanılabildikleri için çok daha yüksek performanslıdır. Yapıların kesilebilir olmasını sağlamaya çalışın (örneğin, kaçının bool). Daha fazla bilgi için Blittable Türleri bölümüne bakın.

Yapı kesilebilirse, daha iyi performans için yerine Marshal.SizeOf<MyStruct>() kullanınsizeof(). Yukarıda belirtildiği gibi, sabitlenmiş GCHandlebir oluşturmayı deneyerek türün bölünebilir olduğunu doğrulayabilirsiniz. Tür bir dize değilse veya blittable olarak kabul edilirse, GCHandle.Alloc bir ArgumentExceptionoluşturur.

Tanımlardaki yapıların işaretçileri tarafından ref geçirilmeli veya ve *kullanılmalıdırunsafe.

✔️ DO, yönetilen yapıyı resmi platform belgelerinde veya üst bilgisinde kullanılan şekil ve adlarla mümkün olduğunca yakın eşleştirin.

✔️ DO, performansı geliştirmek için kesilebilir yapılar yerine Marshal.SizeOf<MyStruct>() C# sizeof() kullanın.

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

❌Yapılardaki işlev işaretçisi alanlarını göstermek için veya System.MulticastDelegate alanlarını kullanmaktan System.Delegate KAÇıNıN.

System.Delegate Gerekli bir imzaya sahip olmadığından ve System.MulticastDelegate olmadığından, geçirilen temsilcinin yerel kodun beklediği imzayla eşleşeceğini garanti etmemektedir. Buna ek olarak, .NET Framework ve .NET Core'da, yerel gösteriminden veya içeren System.DelegateSystem.MulticastDelegate bir yapıyı yönetilen nesneye sıralamak, yerel gösterimdeki alanın değeri yönetilen temsilciyi sarmalayan bir işlev işaretçisi değilse çalışma zamanının istikrarını bozabilir. .NET 5 ve sonraki sürümlerde, yerel bir System.Delegate gösterimden yönetilen nesneye bir veya System.MulticastDelegate alanının sıralanması desteklenmez. veya System.MulticastDelegateyerine System.Delegate belirli bir temsilci türü kullanın.

Sabit Arabellekler

gibi INT_PTR Reserved1[2] bir dizi, ve Reserved1bolmak üzere iki IntPtr alana Reserved1a göre sıraya alınmalıdır. Yerel dizi ilkel bir tür olduğunda, biraz daha temiz yazmak için anahtar sözcüğünü fixed kullanabiliriz. Örneğin, SYSTEM_PROCESS_INFORMATION yerel üst bilgide şöyle görünür:

typedef struct _SYSTEM_PROCESS_INFORMATION {
    ULONG NextEntryOffset;
    ULONG NumberOfThreads;
    BYTE Reserved1[48];
    UNICODE_STRING ImageName;
...
} SYSTEM_PROCESS_INFORMATION

C# dilinde şu şekilde yazabiliriz:

internal unsafe struct SYSTEM_PROCESS_INFORMATION
{
    internal uint NextEntryOffset;
    internal uint NumberOfThreads;
    private fixed byte Reserved1[48];
    internal Interop.UNICODE_STRING ImageName;
    ...
}

Ancak, sabit arabellekleri olan bazı gotcha'lar vardır. Kesilebilir olmayan türlerdeki sabit arabellekler doğru şekilde sıralanmayacağı için yerinde dizinin birden çok alana genişletilmesi gerekir. Buna ek olarak, .NET Framework ve .NET Core'da 3.0'ın öncesinde, sabit arabellek alanı içeren bir yapı bölünebilir olmayan bir yapı içinde iç içe yerleştirilmişse, sabit arabellek alanı yerel koda doğru şekilde düzenlenmez.