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.
- Kullanımın
- ✔️ 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 int
kullanı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.
- Bu, veya
- ✔️ DO, yönetilmeyen kaynakları kapsülleyen nesnelerin yaşam ömrünü yönetmek için tanıtıcıları kullanır
SafeHandle
. Daha fazla bilgi için bkz . Yönetilmeyen kaynakları temizleme. - ❌ Yönetilmeyen kaynakları kapsülleyen nesnelerin yaşam ömrünü yönetmek için AVOID sonlandırıcıları. Daha fazla bilgi için bkz . Dispose yöntemi uygulama.
LibraryImport öznitelik ayarları
Kimlik SYSLIB1054 sahip bir kod çözümleyicisi ile LibraryImportAttribute
size 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 false ne 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 out
değ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:
- LibraryImportAttribute.StringMarshalling olarak Utf16tanımlanır.
- Bağımsız değişken açıkça olarak
[MarshalAs(UnmanagedType.LPWSTR)]
işaretlenir. - DllImportAttribute.CharSetUnicode.
❌ 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:
- İstenen kapasiteden bir
StringBuilder
oluşturun (yönetilen kapasiteyi ayırır). {1} - Çağırmak:
- Yerel bir arabellek {2}ayırır.
- if
[In]
(parametreStringBuilder
için varsayılan) içindekileri kopyalar. - Yerel arabelleği yeni ayrılmış yönetilen bir diziye kopyalar (
[Out]
{3} aynı zamanda için varsayılanStringBuilder
değerdir).
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 ArrayPool
bir 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
olarakUnmanagedType.BSTR
iş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 BOOL
için hazırlanır. Ancak, _Bool
C 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 REFIID
tü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 UnmanagedCallersOnlyAttribute
kesilebilir olarak kabul edilmezler.
Çalışma zamanı hazırlama etkinleştirildiğinde kesilebilir türler
Kesilebilir türler:
byte
, ,short
sbyte
, ,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
- sabit düzen için
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
- sabit düzen için
NOT blittable:
bool
BAZEN kesilebilir:
char
BAZEN kesilebilir içeriği olan türler:
string
Blittable türleri , ref
veya out
ile başvuruya in
gö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.
char
tek boyutlu bir dizide blittable veya içeren bir türün parçasıysa ile CharSet = CharSet.Unicode
açı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 out
değ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ş GCHandle
bir 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 ArgumentException
oluş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ı CharSet
olarak 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 GCHandle
varsayı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 UIntPtr
olarak IntPtr
sıralanabilir, ancak mümkün olduğunda tercih void*
edilebilir.
Ö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++ long
ile 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++ int
char
short
long 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 long
CULong
türlerini kullanınCLong
. Aşağıdaki örnek içindir CLong
, ancak bunu kullanarak benzer bir şekilde soyutlayabilirsiniz CULong
unsigned 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ş GCHandle
bir 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 ArgumentException
oluş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.Delegate
System.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.MulticastDelegate
yerine System.Delegate
belirli bir temsilci türü kullanın.
Sabit Arabellekler
gibi INT_PTR Reserved1[2]
bir dizi, ve Reserved1b
olmak ü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.