Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
.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
Delegatekullanmayı 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#nameofdil özelliğini kullanmasına olanak tanır.
- Bu, veya
- ✔️ Yönetilmeyen kaynakları kapsayan nesnelerin yaşam ömrünü yönetmek için
SafeHandletanı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):
- LibraryImportAttribute.StringMarshalling olarak Utf16tanımlanır.
- Argüman açıkça
[MarshalAs(UnmanagedType.LPWSTR)]olarak işaretlenir. - DllImportAttribute.CharSet, Unicode'e eşittir.
❌ 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:
- Arzu edilen kapasitede bir
StringBuilderoluşturun (yönetilen kapasiteyi ayırır). {1} - Çağırmak:
- Yerel bir arabellek {2}ayırır.
- if
[In](parametreStringBuilderiçin varsayılan) içindekileri kopyalar. - Yerel arabelleği, yeni ayrılmış yönetilen bir diziye kopyalar (
[Out]{3}aynı zamandaStringBuilderiçin varsayılan değerdir).
-
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 olarakCoTaskMemFreeveyaSysStringFreeolarak işaretlenmiş dizeler içinUnmanagedType.BSTRkullanı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.Sequentialolarak varsayılan olarak tanımlanır
- sabit düzen
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.Autovarsayılan olarak
- sabit düzen
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:
- StringMarshalling olarak Utf16tanımlanır.
- Argüman açıkça
[MarshalAs(UnmanagedType.LPWSTR)]olarak 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 ç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.
Ö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.