Ajánlott natív együttműködési eljárások

A .NET különböző módszereket kínál a natív együttműködési kód testreszabására. Ez a cikk a Microsoft .NET-csapatai által a natív együttműködésre vonatkozó útmutatást tartalmazza.

Általános útmutatás

Az ebben a szakaszban található útmutató az összes interop-forgatókönyvre vonatkozik.

  • ✔️ A DO ugyanazt az elnevezést és nagybetűsítést használja a metódusokhoz és paraméterekhez, mint a meghívni kívánt natív metódus.
  • ✔️ FONTOLJA meg ugyanazt az elnevezést és nagybetűsítést az állandó értékekhez.
  • ✔️ A DO a natív típushoz legközelebbi .NET-típusokat használja. A C#-ban például akkor érdemes használniuint, ha a natív típus.unsigned int
  • ✔️ A DO inkább a magasabb szintű natív típusok kifejezését részesíti előnyben .NET-strukturálással, nem pedig osztályokkal.
  • ✔️ DO-használat [In] és [Out] attribútumok tömbparamétereken.
  • ✔️ A DO csak akkor használja [In] és [Out] attribútumokat más típusokon, ha a kívánt viselkedés eltér az alapértelmezett viselkedéstől.
  • ✔️ FONTOLJA meg a natív tömbpufferek készletének használatát System.Buffers.ArrayPool<T> .
  • ✔️ FONTOLJA meg, hogy a P/Invoke deklarációkat egy olyan osztályba csomagolja, amelynek neve és nagybetűsítése megegyezik a natív kódtár nevével.
    • Ez lehetővé teszi, hogy az [DllImport] attribútumok a C# nameof nyelvi funkcióval adják át a natív kódtár nevét, és győződjön meg arról, hogy nem hibásan adta meg a natív kódtár nevét.

DllImport attribútumbeállítások

Beállítás Alapértelmezett Ajánlás Részletek
PreserveSig true Alapértelmezett érték megtartva Ha ez explicit módon hamis értékre van állítva, a sikertelen HRESULT visszatérési értékek kivételekké lesznek alakítva (és a definíció visszatérési értéke null értékű lesz.
SetLastError false Az API-tól függ Állítsa ezt igazra, ha az API a GetLastErrort használja, és a Marshal.GetLastWin32Error használatával kéri le az értéket. Ha az API olyan feltételt állít be, amely azt jelzi, hogy hiba történt, kérje le a hibát, mielőtt más hívásokat indít, hogy véletlenül ne írja felül.
CharSet Fordító által definiált (a karakterkészlet dokumentációjában megadott) Explicit módon használjon CharSet.Unicode sztringeket CharSet.Ansi vagy karaktereket a definícióban Ez határozza meg a sztringek rendezési viselkedését, és azt, hogy mikor mit ExactSpelling csinál false. Vegye figyelembe, hogy CharSet.Ansi valójában UTF8 a Unix. A Windows legtöbbször Unicode-t, míg a Unix UTF8-at használ. További információt a charsets dokumentációjában talál.
ExactSpelling false true Állítsa ezt igaz értékre, és nyerjen némi előnyt, mivel a futtatókörnyezet nem keres alternatív függvényneveket az "A" vagy a "W" utótaggal a CharSet beállítás értékétől függően ("A" és CharSet.Ansi "W" for CharSet.Unicode).

Sztringparaméterek

Ha a CharSet Unicode, vagy az argumentum explicit módon van megjelölve[MarshalAs(UnmanagedType.LPWSTR)], és a sztringet érték (nem vagy outnemref) adja át, a sztringet a program közvetlenül a natív kód fogja rögzíteni és használni (a másolás helyett).

❌ NE használjon [Out] string paramétereket. Az attribútummal [Out] érték szerint átadott sztringparaméterek destabilizálhatják a futtatókörnyezetet, ha a sztring internált sztring. További információ a sztringek közötti internálásról a dokumentációban String.Intern.

✔️ FONTOLJA meg a tulajdonság beállítását[DllImport], CharSet hogy a futtatókörnyezet tudja a várt sztringkódolást.

✔️ FONTOLJA meg char[] vagy byte[] tömbök olyankor ArrayPool , amikor a natív kód várhatóan kitölt egy karakterpuffert. Ehhez meg kell adni az argumentumot a következőként [Out]: .

✔️ FONTOLJA meg a paraméterek elkerülését StringBuilder . StringBuilder a rendezés mindig létrehoz egy natív pufferpéldányt. Ezért rendkívül hatékony lehet. Egy sztringet használó Windows API meghívásának tipikus forgatókönyve:

  1. Hozzon létre egy StringBuilder kívánt kapacitást (lefoglalja a felügyelt kapacitást). {1}
  2. Hivatkozhat:
    1. Natív puffert {2}foglal le.
    2. Másolja a tartalmat, ha [In](egy paraméter alapértelmezett StringBuilder értéke).
    3. Másolja a natív puffert egy újonnan lefoglalt felügyelt tömbbe, ha [Out]{3}(szintén az alapértelmezett )StringBuilder.
  3. ToString() egy újabb felügyelt tömböt {4}foglal le.

Ez a {4} kiosztások egy sztring natív kódból való kinyeréséhez. Ennek korlátozásához a legjobb, ha újra felhasználja a StringBuilder másik hívásban, de ez továbbra is csak egy foglalást ment. Sokkal jobb a karakterpuffer használata és gyorsítótárazása.ArrayPool Ezután egyszerűen lekérheti a későbbi hívások lefoglalását ToString() .

A másik probléma StringBuilder az, hogy a visszatérési puffert mindig az első null értékre másolja. Ha a visszaadott sztring nem szűnik meg, vagy dupla null értékű, akkor a P/Invoke függvény a legjobb esetben helytelen.

Ha mégis használjaStringBuilder, az egyik utolsó gotcha az, hogy a kapacitás nemtartalmaz rejtett null értéket, amely mindig az interop függvényben van elszámolva. Gyakran előfordul, hogy a felhasználók tévednek, mivel a legtöbb API a puffer méretét szeretné, beleértve a null értéket is. Ez pazarlást/felesleges foglalásokat eredményezhet. Emellett ez a gotcha megakadályozza, hogy a futtatókörnyezet optimalizálja StringBuilder a rendezést a másolatok minimalizálása érdekében.

A sztringek rendezéséről további információt a sztringek alapértelmezett rendezési és a sztringek testreszabása című témakörben talál.

A Windows-specifikus sztringek esetében [Out] a CLR alapértelmezés szerint a sztringek felszabadítására vagy SysStringFree aként megjelölt UnmanagedType.BSTRsztringek használatára szolgálCoTaskMemFree. A kimeneti sztringpufferrel rendelkező API-k többségénél: Az átadott karakterszámnak tartalmaznia kell a null értéket. Ha a visszaadott érték kisebb, mint az átadott karakterszám, a hívás sikeres volt, és az érték a záró null érték nélküli karakterek száma. Ellenkező esetben a szám a puffer szükséges mérete, beleértve a null karaktert is.

  • Pass in 5, get 4: A sztring 4 karakter hosszú egy záró null.
  • Pass in 5, get 6: A sztring 5 karakter hosszú, 6 karakteres pufferre van szükség a null tárolásához. Windows-adattípusok sztringekhez

Logikai paraméterek és mezők

A logikai értékek könnyen elronthatóak. A .NET bool alapértelmezés szerint windowsosra BOOLvan rendezve, ahol ez egy 4 bájtos érték. A C és bool c_Bool++ típusok azonban egyetlen bájtból állnak. Ez megnehezítheti a hibák nyomon követését, mivel a visszatérési érték fele el lesz vetve, ami csak potenciálisan módosítja az eredményt. A .NET-értékek bool C vagy C++ bool típusúra történő beállításával kapcsolatos további információkért tekintse meg a logikai mezők rendezési testreszabásának dokumentációját.

Guid

A GRAFIKUS GUID-k közvetlenül az aláírásokban használhatók. Sok Windows API olyan típusú aliasokat vesz fel GUID& , mint a REFIID. Ha a metódus aláírása hivatkozási paramétert tartalmaz, helyezzen el egy kulcsszót ref vagy egy [MarshalAs(UnmanagedType.LPStruct)] attribútumot a GUID paraméterdeklarációban.

GUID By-ref GUID
KNOWNFOLDERID REFKNOWNFOLDERID

❌ NE használja [MarshalAs(UnmanagedType.LPStruct)] a GUID-paramétereken kívül ref másra.

Blittable típusok

A Blittable-típusok olyan típusok, amelyek a felügyelt és natív kódban azonos bitszintű megjelenítéssel rendelkeznek. Ezért nem kell más formátumra konvertálni őket, hogy natív kódra és natív kódra legyenek rendezve, és mivel ez javítja a teljesítményt, előnyben kell részesíteni őket. Egyes típusok nem titkosak, de ismert, hogy titkos tartalmúak. Ezek a típusok hasonló optimalizálásokkal rendelkeznek, mint a titkos típusok, ha nem találhatók meg egy másik típusban, de nem tekinthetők titkosnak, ha a szerkezetek mezőiben UnmanagedCallersOnlyAttributevagy a .

Blittable típusok, ha a futtatókörnyezet-rendezés engedélyezve van

Titkos típusok:

  • byte, sbyte, short, ushort, intuint, long, ulong, , singledouble
  • Rögzített elrendezésű szerkezetek, amelyek csak a példánymezőkhöz tartozó, nem módosítható értéktípusokkal rendelkeznek
    • rögzített elrendezéshez vagy [StructLayout(LayoutKind.Sequential)][StructLayout(LayoutKind.Explicit)]
    • a szerkezetek LayoutKind.Sequential alapértelmezés szerint

Titkos tartalmú típusok:

  • nem beágyazott, egydimenziós tömbök egydimenziós, kétliteres primitív típusokból (például int[])
  • rögzített elrendezésű osztályok, amelyek csak a példánymezőkhöz tartozó írásvédett értéktípusokkal rendelkeznek
    • rögzített elrendezéshez vagy [StructLayout(LayoutKind.Sequential)][StructLayout(LayoutKind.Explicit)]
    • LayoutKind.Auto az osztályok alapértelmezés szerint

NEM titkos:

  • bool

NÉHA blittable:

  • char

A SOMETIMES titkos tartalmú típusok:

  • string

Ha a blittable típusok hivatkozással invannak átadva a , refvagy out, vagy, ha a blittable tartalmú típusok érték szerint vannak átadva, a rendező egyszerűen rögzíti őket ahelyett, hogy egy köztes pufferbe másolták őket.

char egydimenziós tömbben található, vagy ha egy olyan típus része, amely tartalmazza, azzal kifejezetten megjelölve [StructLayout]CharSet = CharSet.Unicodevan.

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

string akkor tartalmaz titkosjeles tartalmat, ha az nem egy másik típusban található, és a rendszer argumentumként adja át, és ezzel van megjelölve [MarshalAs(UnmanagedType.LPWStr)] vagy megadva [DllImport]CharSet = CharSet.Unicode .

Egy rögzített típus létrehozásával GCHandlemegállapíthatja, hogy egy típus titkos vagy titkos tartalmú-e. Ha a típus nem sztring, vagy nem tekinthető titkos kódnak, GCHandle.Alloc a rendszer egy ArgumentException.

Titkos típusok, ha a futtatókörnyezet-rendezés le van tiltva

Ha a futtatókörnyezet-rendezés le van tiltva, jelentősen egyszerűbbek azok a szabályok, amelyek esetében a típusok ki vannak kapcsolva. Minden olyan típus, amely C# unmanaged típusú, és nem rendelkezik olyan mezővel, amely jelöléssel [StructLayout(LayoutKind.Auto)] rendelkezik, nem jelölhető. A nem C# unmanaged típusú típusok nem teljesek. A titkos tartalmú típusok( például tömbök vagy sztringek) fogalma nem érvényes a futtatókörnyezet-rendezés letiltásakor. Ha a futásidejű rendezés le van tiltva, a fent említett szabály által nem minősített típus nem támogatott.

Ezek a szabályok eltérnek a beépített rendszertől, elsősorban olyan helyzetekben, ahol bool és char amelyeket használnak. Ha a rendezés le van tiltva, a rendszer 1 bájtos értékként adja át, bool és nem normalizálja, és char mindig 2 bájtos értékként adja át. Ha a futtatókörnyezet-rendezés engedélyezve van, 1, bool 2 vagy 4 bájtos értékre képezheti le a rendszer, és mindig normalizálható, és char a függvénytől függően 1 vagy 2 bájtos értékre képezheti le a rendszer.CharSet

✔️ DO, hogy a struktúrákat, ha lehetséges.

További információkért lásd:

Felügyelt objektumok életben tartása

GC.KeepAlive() biztosíthatja, hogy egy objektum a hatókörben maradjon, amíg el nem éri a KeepAlive metódust.

HandleRef lehetővé teszi, hogy a rendező életben tartson egy objektumot a P/Invoke időtartamig. A metódus-aláírások helyett IntPtr használható. SafeHandle hatékonyan helyettesíti ezt az osztályt, és inkább azt kell használni.

GCHandle lehetővé teszi egy felügyelt objektum rögzítését és a natív mutató lekérését. Az alapminta a következő:

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

A rögzítés nem az alapértelmezett beállítás.GCHandle A másik fő minta egy felügyelt objektumra mutató hivatkozás natív kódon keresztüli átadására és a felügyelt kódra való visszahívásra szolgál, általában visszahívással. A minta a következő:

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();

Ne felejtse el, hogy GCHandle kifejezetten szabaddá kell tenni a memóriavesztés elkerülése érdekében.

Gyakori Windows-adattípusok

Az alábbiakban felsoroljuk a Windows API-kban gyakran használt adattípusokat, valamint a Windows-kódba való behíváskor használandó C#-típusokat.

A következő típusok mérete megegyezik a 32 bites és a 64 bites Windows esetében, a nevük ellenére.

Szélesség Windows C# Alternatív megoldás
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 Lásd CLong és CULong.
32 LONG32 int
32 CLONG uint Lásd CLong és CULong.
32 DWORD uint Lásd CLong és CULong.
32 DWORD32 uint
32 UINT uint
32 UINT32 uint
32 ULONG uint Lásd CLong és 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 következő típusok, mint a mutatók, a platform szélességét követik. Ezekhez használható IntPtr/UIntPtr .

Aláírt mutatótípusok (használat IntPtr) Nem aláírt mutatótípusok (használat UIntPtr)
HANDLE WPARAM
HWND UINT_PTR
HINSTANCE ULONG_PTR
LPARAM SIZE_T
LRESULT
LONG_PTR
INT_PTR

A Windows PVOID, amely egy C void*, rendezhető, IntPtrUIntPtrvagy, de inkább void* , ha lehetséges.

Windows-adattípusok

Adattípus-tartományok

Korábban beépített támogatott típusok

Ritkán fordul elő, hogy egy típus beépített támogatása el lesz távolítva.

UnmanagedType.IInspectable A UnmanagedType.HString .NET 5 kiadásban eltávolítottuk a beépített marshal-támogatást. Újrafordítási bináris fájlokat kell használnia, amelyek ezt a rendezési típust használják, és amelyek egy korábbi keretrendszert céloznak meg. Ezt a típust továbbra is lehet végrehajtani, de manuálisan kell végrehajtania, ahogy az alábbi kódpéldában látható. Ez a kód tovább fog működni, és kompatibilis a korábbi keretrendszerekkel is.

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

Platformfüggetlen adattípussal kapcsolatos szempontok

A C/C++ nyelvben vannak olyan típusok, amelyek a definiálásuk során szélességi körekkel rendelkeznek. Platformfüggetlen interop írása esetén olyan esetek merülhetnek fel, amelyekben a platformok eltérnek, és ha nem veszik figyelembe, problémákat okozhatnak.

C/C++ long

A C/C++ long és a C# long nem feltétlenül azonos méretűek.

A long C/C++ típus úgy van definiálva, hogy "legalább 32" bit legyen. Ez azt jelenti, hogy a szükséges bitek száma minimális, de a platformok dönthetnek úgy, hogy szükség esetén több bitet használnak. Az alábbi táblázat a C/C++ long adattípushoz megadott bitek közötti különbségeket mutatja be a platformok között.

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

Ezzel szemben a C# long mindig 64 bites. Ezért érdemes elkerülni a C# long használatát a C/C++ longbillentyűkombinációval való együttműködéshez.

(Ez a C/C++ long probléma nem létezik a C/C++ charesetében, shortintés long long mivel ezek mindegyik platformon 8, 16, 32 és 64 bitesek.)

A .NET 6-os és újabb verzióiban a C/C++ long és CULongunsigned long az adattípusok közötti együttműködéshez használja az CLong és a típusokat. Az alábbi példa a következőkre mutatCLong, de hasonló módon absztrakcióra unsigned long is használhatóCULong.

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

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

A .NET 5-ös és korábbi verzióinak megcélzásakor külön Windows- és nem Windows-aláírásokat kell deklarálnia a probléma kezeléséhez.

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

Struktúrák

A felügyelt szerkezetek a veremen jönnek létre, és a metódus visszatéréséig nem lesznek eltávolítva. Definíció szerint ezek "rögzítettek" (a GC nem fogja áthelyezni). Ha a natív kód nem használja a mutatót az aktuális metódus végén, egyszerűen felveheti a címet nem biztonságos kódblokkokban.

A strukturált szerkezetek sokkal hatékonyabbak, mivel egyszerűen közvetlenül használhatók a rendezési réteg által. Próbálja meg strukturáltsá tenni a szerkezeteket (például kerülje el bool). További információkért lásd a Blittable Types szakaszt.

Ha a szerkezet nem megfelelő, használja sizeof() a jobb teljesítmény helyett Marshal.SizeOf<MyStruct>() . Ahogy fentebb említettük, a rögzített típus létrehozásával GCHandleellenőrizheti, hogy a típus nem látható-e. Ha a típus nem sztring, vagy nem tekinthető titkos kódnak, GCHandle.Alloc a rendszer egy ArgumentException.

A definíciók szerkezetére mutató mutatókat át kell adniref, vagy használni és használni *unsafe kell.

✔️ A DO a lehető legszorosabban illeszkedik a felügyelt szerkezethez a hivatalos platform dokumentációjában vagy fejlécében használt alakzathoz és nevekhez.

✔️ NE használja a C#-ot Marshal.SizeOf<MyStruct>()sizeof() a nem strukturálható struktúrákhoz a teljesítmény javítása érdekében.

❌ NE használjon osztályokat összetett natív típusok öröklés útján történő kifejezéséhez.

❌ KERÜLJE, hogy függvénymutató-mezőket System.DelegateSystem.MulticastDelegate jelöljön a struktúrákban.

System.MulticastDelegate Mivel System.Delegate nem rendelkezik szükséges aláírással, nem garantálják, hogy az átadott meghatalmazott megegyezik a natív kód által elvárt aláírással. Emellett a .NET-keretrendszer és a .NET Core esetén a felügyelt objektumok natív reprezentációját System.Delegate tartalmazó vagy System.MulticastDelegate azokból származó szerkezetek beállítása destabilizálhatja a futtatókörnyezetet, ha a natív ábrázolásban szereplő mező értéke nem egy felügyelt delegáltat burkoló függvénymutató. A .NET 5-ös és újabb verzióiban egy vagy System.MulticastDelegate több mező natív ábrázolásból felügyelt objektumba történő rendezése System.Delegate nem támogatott. Használjon egy adott delegálttípust ahelyett System.Delegate vagy System.MulticastDelegate.

Rögzített pufferek

Egy olyan tömböt, mint INT_PTR Reserved1[2] két mezőre kell rendeződni IntPtr , Reserved1a és Reserved1b. Ha a natív tömb egy primitív típus, a fixed kulcsszóval egy kicsit tisztábban írhatjuk. Például így SYSTEM_PROCESS_INFORMATION néz ki a natív fejlécben:

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

A C#-ban így írhatjuk:

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

Vannak azonban rögzített pufferekkel rendelkező gotcha-k. A nem titkos típusok rögzített pufferei nem lesznek megfelelően rendezve, ezért a helyben lévő tömböt több egyéni mezőre kell kiterjeszteni. Emellett a 3.0 előtti .NET-keretrendszer és a .NET Core esetén, ha egy rögzített puffermezőt tartalmazó struktúra egy nem titkos szerkezetbe van ágyazva, a rögzített puffermező nem lesz megfelelően rendezve a natív kódhoz.