Yerel birlikte çalışabilirlik en iyi uygulamaları

.NET size 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.

  • ✔️ MÜMKÜNse [LibraryImport] .NET 7+ hedeflerken kullanın.
    • [DllImport] kullanımının uygun olduğu durumlar vardır. SYSLIB1054 numaralı bir kod çözümleyici, bunun 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, C işlevinin bağımsız değişkenleriyle eşleşen P/Invoke ve işlev işaretçisi imzalarını tanımlar.
  • ✔️ DO, yerel türe en uyumlu .NET türlerini kullanın. Örneğin, C# dilinde yerel tür olduğunda uintkullanınunsigned int.
  • ✔️ DO, sınıflar yerine .NET yapıları kullanarak daha üst düzey yerel türleri ifade etmeyi tercih eder.
  • ✔️ DO, C# dilinde yönetilmeyen işlevlere geri çağırma iletirken, UnmanagedCallersOnlyAttribute türler yerine işlev işaretçilerini Delegate kullanmayı tercih edin. Daha fazla bilgi için bkz. GetFunctionPointerForDelegate(Delegate).
  • ✔️ DIZI parametrelerinde DO kullanımı [In] ve [Out] öznitelikleri.
  • ✔️ [In] ve [Out] özniteliklerini yalnızca istediğiniz davranış varsayılan davranıştan farklı olduğunda diğer türlerde kullanın.
  • ✔️ 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 [LibraryImport] özniteliklerinizin [DllImport] 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.
  • ✔️ Yönetilmeyen kaynakları kapsayan nesnelerin yaşam ömrünü yönetmek için SafeHandle tanıtıcıları KULLANIN. Daha fazla bilgi için bkz . Yönetilmeyen kaynakları temizleme.
  • ❌ Yönetilmeyen kaynakları kapsülleyen nesnelerin yaşam ömrünü yönetmek için sonlandırıcılardan KAÇININ. Daha fazla bilgi için bkz . Dispose yöntemi uygulama.

LibraryImport öznitelik ayarları

Kimlik numarası SYSLIB1054 olan bir kod çözümleyicisi size LibraryImportAttribute 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 dizeler veya karakterler mevcutsa, açıkça CharSet.Unicode veya CharSet.Ansi kullanın. Bu, stringlerin veri iletimi davranışını ve ExactSpelling olduğunda false ne yaptığını belirtir. CharSet.Ansi Unix'te utf8 olduğunu unutmayın. Çoğu zaman Windows 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ı CharSet ayarının değerine bağlı olarak "A" veya "W" soneki içeren alternatif işlev adlarını aramayacağı için küçük bir performans avantajı elde edin (burada "A", CharSet.Ansi için ve "W", CharSet.Unicode için geçerlidir).

Dize parametreleri

Bir string değeri iletildiğinde (ne ref ne de out) ve aşağıdakilerden herhangi biri, sabitlenir ve doğrudan yerel kod tarafından kullanılır (kopyalanmaz):

❌ 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. Belgelerde String.Intern için dize bellek tasarrufu hakkında daha fazla bilgi edinebilirsiniz.

✔️ Yerel kodun bir karakter arabelleğini doldurmasının beklendiği durumlarda, char[] veya byte[] dizilerini GÖZ ÖNÜNDE BULUNDURUN. Bu, argümanın [Out] olarak geçirilmesini gerektirir.

DllImport'a özgü kılavuz

✔️ çalışma zamanının beklenen dize kodlamasından haberdar olması için CharSet içinde [DllImport] özelliğini ayarlamayı DÜŞÜNÜN.

✔️ Parametrelerden StringBuilder kaçınmayı düşünün. StringBuilder veri marşalleme 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 ele alın:

  1. Arzu edilen kapasitede 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 StringBuilder için varsayılan değerdir).
  3. ToString() başka bir yönetilen diziyi {4} ayırır.

Bu, {4} yerel koddan bir dize almak için ayırmaları ifade eder. Bu durumu sınırlamak için yapabileceğiniz en iyi şey, StringBuilder öğesini başka bir çağrıda yeniden kullanmaktır, ancak bu yine de yalnızca bir ayırmayı önler. Bir karakter arabelleğini ArrayPool'den kullanmak ve önbelleğe almak çok daha iyidir. Artık sonraki çağrılarda yalnızca ToString() için ayırmaya odaklanabilirsiniz.

Diğer sorun StringBuilder ile, dönüş arabelleğinin her zaman ilk boş değere kadar 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 marşalleme hakkında daha fazla bilgi için bkz. Dizeler için Varsayılan Marşalleme ve Dize marşallemesini özelleştirme.

Windows Specific[Out] dizeleri için CLR, boş dizeler için varsayılan olarak CoTaskMemFree veya SysStringFree olarak işaretlenmiş dizeler için UnmanagedType.BSTR kullanır. Çı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. Windows Dizeler için Veri Türleri

Boole parametreleri ve alanları

Boole'ları mahvetmek kolaydır. Varsayılan olarak, .NET bool Windows BOOL için, 4 baytlık bir değer olarak aktarılır. Ancak, C ve C++'daki _Bool ve bool türleri tek 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 aktarma hakkında daha fazla bilgi için Boole alan aktarmayı ile ilgili belgelere bakın.

Guıd

GUID'ler doğrudan imzalarda kullanılabilir. Birçok Windows API'sinde GUID& gibi REFIID tür diğer adları kullanılır. 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 bir şey için [MarshalAs(UnmanagedType.LPStruct)] KULLANMAYINref.

Blittable türleri

Blittable türleri, yönetilen ve yerel kodda aynı bit düzeyi gösterimine sahip türlerdir. Bu nedenle, yerel kodla çalışacak başka bir biçime dönüştürülmeleri gerekmez ve performansı iyileştirdiği için tercih edilmelidirler. Bazı türler blittable değildir, ancak blittable 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 UnmanagedCallersOnlyAttribute amaçları için blittable olarak kabul edilmezler.

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

Kesilebilir türler:

  • byte, sbyte, short, ushort, int, uint, long, ulong, single, double, nint,nuint
  • yönetilmeyen işaretçiler (örneğin, int*)
  • örnek alanları için yalnızca kesilebilir değer türlerine sahip sabit düzenli yapılar
    • sabit düzen [StructLayout(LayoutKind.Sequential)] veya [StructLayout(LayoutKind.Explicit)] gerektirir
    • yapılar LayoutKind.Sequential olarak varsayılan olarak tanımlanır

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 [StructLayout(LayoutKind.Sequential)] veya [StructLayout(LayoutKind.Explicit)] gerektirir
    • sınıflar LayoutKind.Auto varsayılan olarak

NOT blittable (birleştirilemez):

  • bool

BAZEN doğrudan kopyalanabilir:

  • char

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

  • string

Blittable türleri in, ref veya out ile referansla geçirildiğinde ya da içeriğinde blittable türler barındıran türler değere göre geçirildiğinde, arabelleğe kopyalanmak yerine yalnızca derleyici tarafından sabitlenirler.

char tek boyutlu bir dizide, bir türün parçasıysa ve ile [StructLayout] ile açıkça işaretlenmişse blittable'dır.

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

string, başka bir türde yer almıyorsa ve değer olarak bir argüman şeklinde (ne ref ne de out) geçiriliyorsa ve aşağıdakilerden herhangi biri geçerliyse, blittable içeriği içerir:

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 çoğunlukla bool ve char kullanıldığı durumlarda 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ştirilmez ve char her zaman 2 baytlık bir değer 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.

Genişlik Windows C# (programlama dili) 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çi oldukları için 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

Windows PVOID, ki bu bir C void*'dir, ya IntPtr ya da UIntPtr olarak taşınabilir, ancak mümkün olduğunda void* tercih edilir.

Windows Veri Türleri

Veri Türü Aralıkları

Önceden yerleşik olarak desteklenen türler

Bir tür için yerleşik desteğin kaldırılması nadirdir.

UnmanagedType.HString ve UnmanagedType.IInspectable yerleşik marshaling 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ü düzenlemek hala mümkündür, ancak aşağıdaki kod örneğinde gösterildiği gibi manuel olarak düzenlemeniz 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 esneklik 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.

C/C++ içindeki long türü "en az 32" bit olacak şekilde 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.

Platforma 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++ charshortintlong long , , ve için mevcut değildir.)

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

// 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 olmayan 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, marşallaştırma katmanı tarafından doğrudan kullanılabildikleri için çok daha performanslıdır. Yapıların blittable olmasını sağlamaya çalışın (örneğin, bool gibi şeylerden kaçının). Daha fazla bilgi için Blittable Türleri bölümüne bakın.

Yapı kesilebilirse, daha iyi performans için yerine sizeof() kullanınMarshal.SizeOf<MyStruct>(). 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 ediliyorsa, GCHandle.Alloc bir ArgumentException fırlatır.

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

✔️ 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ı artırmak için blittable yapılar için sizeof() yerine C# Marshal.SizeOf<MyStruct>() kullanın.

❌ Açıkça belgelenmedikçe, .NET çalışma zamanı kitaplıkları tarafından görüntülenen yapı türlerinin iç gösterimine bağlı kalmayın.

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

❌ İşlev işaretçisi alanlarını yapılarda göstermek için System.Delegate veya System.MulticastDelegate alanlarını kullanmaktan KAÇININ.

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. Ayrıca, .NET Framework ve .NET Core'da yerel gösteriminden yönetilen bir nesneye System.Delegate veya System.MulticastDelegate içeren bir yapıyı marşal etmek, eğer yerel gösterimdeki alanın değeri yönetilen bir temsilciyi saran 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 gösterimden yönetilen nesneye System.Delegate veya System.MulticastDelegate alanı sıralanması desteklenmez. veya System.Delegateyerine System.MulticastDelegate belirli bir temsilci türü kullanın.

Sabit Arabellekler

gibi INT_PTR Reserved1[2] bir dizi, ve IntPtrolmak üzere iki Reserved1a alana Reserved1b göre sıraya alınmalıdır. Yerel dizi ilkel bir tür olduğunda, onu biraz daha temiz bir şekilde yazmak için fixed anahtar sözcüğünü 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. Ayrıca, 3.0'ın öncesinde .NET Framework ve .NET Core'da, 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.

P/Invoke hatalarını giderme

Aşağıdaki tabloda yaygın belirtiler olası nedenleri ve önerilen düzeltmelerle eşlenmiştir.

Belirti Olası neden Düzelt
DllNotFoundException Kütüphane çalışma zamanında bulunamadı Kitaplık adını, yolunu ve platformu denetleyin. Yüklemeyi test etmek için kullanın TryLoad . Linux'ta LD_LIBRARY_PATH veya rpath öğesini doğrulayın.
EntryPointNotFoundException Dışarı aktarma adı uyuşmazlığı Yerel dışarı aktarmaları inceleyin (Windows dumpbin /exports, Linux'ta nm -D). C++ isim karışıklığını (eksik extern "C") denetleyin. Açık şekilde ayarlayın EntryPoint .
AccessViolationException İmza uyuşmazlığı, kullanım sonrası kullanım veya eksik sabitleme Yönetilen ve yerel imzaları karşılaştırın. Marshal.SizeOf<T>() ile sizeof yerine yerel yapı boyutlarını kontrol edin. Bellek ömrünü doğrulayın. Sıralama sorununu gidermek için bir blittable imzası kullanma
Sessiz veri bozulması Yanlış yazı tipi boyutu veya kodlaması Sınır günlüğü ekleme. Marshal.SizeOf<T>()'yi yerel sizeof ile karşılaştırın. Bilinen giriş/çıkış çiftleriyle test edin.
Aralıklı çökmeler GC sabitlenmemiş bir nesneyi taşıdı veya bir temsilci topladı Tam kullanım ömrü için kök geri çağırma temsilcileri. GCHandle veya fixed kullanan çağrılar arasında tutulan işaretçiler için.
Serbest bırakma işlemi sırasında yığın bozulması Yanlış ayırıcı Allocator'ı eşleştirin: malloc/free sistemini asla CoTaskMemAlloc/CoTaskMemFree veya Marshal.FreeHGlobal ile karıştırmayın. Kitaplığın kendi ücretsiz işlevini kullanın.

Temsilci koleksiyonunu GC.KeepAlive ile engelle

Bir temsilciyi işlev işaretçisine dönüştürmek için kullandığınızda GetFunctionPointerForDelegate , çöp toplayıcı döndürülen işaretçi ile kaynak temsilci arasındaki ilişkiyi izlemez . Yerel kod işaretçiyi kullanmayı bitirmeden önce temsilci, koleksiyon için uygun hale gelirse uygulama kilitlenir.

Koleksiyonu önlemek için kullanın KeepAlive :

var callback = new MyDelegate((level, msgPtr) =>
{
    string msg = Marshal.PtrToStringUTF8(msgPtr) ?? string.Empty;
    Console.WriteLine($"[{level}] {msg}");
});

IntPtr fnPtr = Marshal.GetFunctionPointerForDelegate(callback);
NativeUsesCallback(fnPtr);
GC.KeepAlive(callback); // Prevent collection — fnPtr does not root the delegate

Yerel kod işlev işaretçisini çağrının ötesinde depoluyorsa (örneğin, kalıcı bir geri çağırma olarak), temsilcinin tüm ömrü boyunca köklenmesi gerekir — genellikle bunu bir static alanında depolayarak.

Dokümantasyon ve yerel başlık dosyaları arasındaki çakışmaları çözme

P/Invoke imzaları yazarken, çevrimiçi API belgeleriyle gerçek yerel üst bilgi dosyaları arasında tutarsızlıklarla karşılaşabilirsiniz. Üst bilgi dosyaları işlev imzaları, yapı düzenleri, tür boyutları ve çağırma kuralları için yetkili kaynaktır. Şüpheniz olduğunda, yalnızca belgelere güvenmek yerine üst bilgide P/Invoke imzalarınızı doğrulayın.