Alapértelmezett rendezési viselkedés

Az interop-rendezés olyan szabályokon működik, amelyek meghatározzák, hogyan viselkednek a metódusparaméterekhez társított adatok a felügyelt és a nem felügyelt memória között. Ezek a beépített szabályok szabályozzák az olyan rendezési tevékenységeket, mint az adattípus-átalakítások, hogy a hívó módosíthatja-e a neki átadott adatokat, és visszaküldheti-e ezeket a módosításokat a hívónak, és hogy a rendező milyen körülmények között biztosít teljesítményoptimalizálást.

Ez a szakasz az interop marshalling szolgáltatás alapértelmezett viselkedési jellemzőit azonosítja. Részletes információkat tartalmaz a tömbök, logikai típusok, karaktertípusok, meghatalmazottak, osztályok, objektumok, sztringek és struktúrák elrendezéséről.

Feljegyzés

Az általános típusok rendezése nem támogatott. További információ: Interoperating Using Generic Types.

Memóriakezelés az interop marshallerrel

Az interop marshaller mindig megkísérli felszabadítani a nem felügyelt kód által lefoglalt memóriát. Ez a viselkedés megfelel a COM memóriakezelési szabályainak, de eltér a Natív C++-t szabályozó szabályoktól.

Zavart okozhat, ha natív C++ viselkedésre számít (nincs memória felszabadítása) a platformhívások használatakor, ami automatikusan felszabadítja a memóriát a mutatók számára. A C++ DLL-ből például az alábbi nem felügyelt metódus meghívása nem szabadít fel automatikusan memóriát.

Nem felügyelt aláírás

BSTR MethodOne (BSTR b) {  
     return b;  
}  

Ha azonban platformhívási prototípusként definiálja a metódust, cserélje le az egyes BSTR-típusokat egy String típusra, és hívja meg MethodOnea közös nyelvi futtatókörnyezetet, amely kétszer próbál felszabadítani b. A rendezési viselkedést sztringtípusok helyett típusok használatával IntPtr módosíthatja.

A futtatókörnyezet mindig a Windows CoTaskMemFree metódusát, más platformokon pedig az ingyenes metódust használja a memória felszabadításához. Ha a memóriát, amellyel dolgozik, nem a CoTaskMemAlloc metódussal foglalta le Windowson vagy más platformokon a Malloc metódussal, akkor intPtrt kell használnia, és manuálisan kell felszabadítania a memóriát a megfelelő módszerrel. Hasonlóképpen elkerülheti az automatikus memóriakiszabadítást olyan helyzetekben, amikor a memória soha nem szabadítható fel, például amikor a GetCommandLine függvényt használja Kernel32.dll, amely a kernelmemóriára mutató mutatót ad vissza. A memória manuális felszabadításával kapcsolatos részletekért tekintse meg a pufferek mintáját.

Alapértelmezett rendezés osztályokhoz

Az osztályok csak COM interop-okkal rendezhetők, és mindig illesztőként vannak rendezve. Bizonyos esetekben az osztály marsallásához használt felületet osztály-interfésznek nevezzük. Az osztály felületének az Ön által választott felülettel való felülírásáról további információt az osztály felületének bemutatása című témakörben talál.

Osztályok átadása a COM-nak

Amikor egy felügyelt osztályt átad a COM-nak, az interop marshaller automatikusan com-proxyval burkolja az osztályt, és átadja a proxy által létrehozott osztályfelületet a COM metódushívásnak. A proxy ezután vissza delegálja az osztály felületének összes hívását a felügyelt objektumra. A proxy olyan egyéb interfészeket is elérhetővé tesz, amelyeket az osztály nem kifejezetten implementál. A proxy automatikusan implementálja az olyan interfészeket, mint az IUnknown és az IDispatch az osztály nevében.

Osztályok átadása a .NET-kódnak

A társosztályokat általában nem használják metódusargumentumként a COM-ban. Ehelyett a rendszer általában egy alapértelmezett felületet ad át a társosztály helyett.

Amikor egy felületet átad egy felügyelt kódnak, az interop marshaller feladata, hogy a felületet a megfelelő burkolóval burkolja, és átadja a burkolót a felügyelt metódusnak. Annak meghatározása, hogy melyik burkolót kell használni, nehéz lehet. A COM-objektumok minden példánya egyetlen, egyedi burkolóval rendelkezik, függetlenül attól, hogy hány felületet valósít meg az objektum. Például egyetlen COM-objektum, amely öt különböző felületet implementál, csak egy burkolóval rendelkezik. Ugyanez a burkoló mind az öt felületet elérhetővé teszi. Ha a COM-objektum két példánya jön létre, akkor a burkoló két példánya jön létre.

Ahhoz, hogy a burkoló egész élettartama során ugyanazt a típust fenntartsa, az interop marshallernek azonosítania kell a megfelelő burkolót, amikor az objektum által közzétett felület először áthalad a rendezőn. A rendező azonosítja az objektumot az objektum által implementálott felületek egyikével.

A rendező például azt határozza meg, hogy az osztályburkolót kell használni a felügyelt kódba átadott felület burkolásához. Amikor az illesztő először áthalad a rendezőn, a rendező ellenőrzi, hogy az interfész egy ismert objektumból származik-e. Ez az ellenőrzés két esetben fordul elő:

  • Egy felületet egy másik felügyelt objektum implementál, amelyet máshol ad át a COM-nak. A rendező képes könnyen azonosítani a felügyelt objektumok által közzétett interfészeket, és képes megegyezni az implementációt biztosító felügyelt objektummal. Ezután a rendszer átadja a felügyelt objektumot a metódusnak, és nincs szükség burkolóra.

  • Egy már burkolt objektum implementálja a felületet. Annak megállapításához, hogy ez a helyzet-e, a rendező lekérdezi az objektumot az IUnknown felületéhez, és összehasonlítja a visszaadott felületet más, már burkolt objektumok felületeivel. Ha az interfész megegyezik egy másik burkolóval, az objektumok azonos identitással rendelkeznek, és a meglévő burkoló át lesz adva a metódusnak.

Ha egy interfész nem ismert objektumból származik, a rendező a következőket teszi:

  1. A rendező lekérdezi az objektumot az IProvideClassInfo2 interfészhez. Ha meg van adva, a rendező az IProvideClassInfo2.GetGUID által visszaadott CLSID-t használja az interfészt biztosító társosztály azonosításához. A CLSID-vel a rendező megkeresheti a burkolót a beállításjegyzékből, ha a szerelvényt korábban regisztrálták.

  2. A rendező lekérdezi az IProvideClassInfo felület felületét. Ha meg van adva, a rendező az IProvideClassInfo.GetClassinfo által visszaadott ITypeInfo használatával határozza meg a felületet felfedő osztály CLSID-azonosítóját. A rendező a CLSID használatával megkeresheti a burkoló metaadatait.

  3. Ha a rendező továbbra sem tudja azonosítani az osztályt, az egy System.__ComObject nevű általános burkolóosztályba burkolja a felületet.

Meghatalmazottak alapértelmezett rendezése

A felügyelt meghatalmazott com-felületként vagy függvénymutatóként van rendezve a hívási mechanizmus alapján:

  • Platformhívás esetén a meghatalmazott alapértelmezés szerint nem felügyelt függvénymutatóként van rendezve.

  • COM-interop esetén a meghatalmazott alapértelmezés szerint _Delegate típusú COM-felületként van rendezve. A _Delegate felület az Mscorlib.tlb típusú kódtárban van definiálva, és tartalmazza a Delegate.DynamicInvoke metódust, amely lehetővé teszi a delegált által hivatkozott metódus meghívását.

Az alábbi táblázat a felügyelt delegált adattípus rendezési beállításait mutatja be. Az MarshalAsAttribute attribútum számos UnmanagedType enumerálási értéket biztosít a marshal delegáltak számára.

Számbavétel típusa A nem felügyelt formátum leírása
UnmanagedType.FunctionPtr Nem felügyelt függvénymutató.
UnmanagedType.Interface Az Mscorlib.tlb-ben meghatározott _Delegate típusú interfész.

Vegye figyelembe az alábbi példakódot, amelyben a metódusok DelegateTestInterface egy COM-típusú kódtárba vannak exportálva. Figyelje meg, hogy csak a ref (vagy ByRef) kulcsszóval megjelölt meghatalmazottak lesznek átadva In/Out paraméterként.

using System;  
using System.Runtime.InteropServices;  
  
public interface DelegateTest {  
void m1(Delegate d);  
void m2([MarshalAs(UnmanagedType.Interface)] Delegate d);
void m3([MarshalAs(UnmanagedType.Interface)] ref Delegate d);
void m4([MarshalAs(UnmanagedType.FunctionPtr)] Delegate d);
void m5([MarshalAs(UnmanagedType.FunctionPtr)] ref Delegate d);
}  

Típustár-ábrázolás

importlib("mscorlib.tlb");  
interface DelegateTest : IDispatch {  
[id(…)] HRESULT m1([in] _Delegate* d);  
[id(…)] HRESULT m2([in] _Delegate* d);  
[id(…)] HRESULT m3([in, out] _Delegate** d);  
[id()] HRESULT m4([in] int d);  
[id()] HRESULT m5([in, out] int *d);  
   };  

A függvénymutatók késleltethetők, ahogyan bármely más nem felügyelt függvénymutató is késleltethető.

Ebben a példában, amikor a két meghatalmazott rendezve UnmanagedType.FunctionPtrvan, az eredmény egy int mutató egy int. Mivel a delegálási típusok rendezve vannak, itt egy üres (void*üres) mutató látható, int amely a memóriában lévő meghatalmazott címe. Más szóval ez az eredmény a 32 bites Windows rendszerekre jellemző, mivel int itt a függvénymutató mérete látható.

Feljegyzés

A nem felügyelt kód által birtokolt felügyelt meghatalmazottra mutató függvénymutatóra mutató hivatkozás nem akadályozza meg, hogy a közös nyelvi futtatókörnyezet szemétgyűjtést végezzen a felügyelt objektumon.

A következő kód például helytelen, mert a cb metódusnak SetChangeHandler átadott objektumra mutató hivatkozás nem tartja cb életben a Test metódus élettartamát. Miután összegyűjtötte az cb objektumot, az átadott SetChangeHandler függvénymutató már nem érvényes.

public class ExternalAPI {  
   [DllImport("External.dll")]  
   public static extern void SetChangeHandler(  
      [MarshalAs(UnmanagedType.FunctionPtr)]ChangeDelegate d);  
}  
public delegate bool ChangeDelegate([MarshalAs(UnmanagedType.LPWStr) string S);  
public class CallBackClass {  
   public bool OnChange(string S){ return true;}  
}  
internal class DelegateTest {  
   public static void Test() {  
      CallBackClass cb = new CallBackClass();  
      // Caution: The following reference on the cb object does not keep the
      // object from being garbage collected after the Main method
      // executes.  
      ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));
   }  
}  

A váratlan szemétgyűjtés kompenzálásához a hívónak gondoskodnia kell arról, hogy az cb objektum életben maradjon, amíg a nem felügyelt függvénymutató használatban van. Igény szerint a nem felügyelt kód értesítheti a felügyelt kódot, ha a függvénymutatóra már nincs szükség, ahogy az alábbi példa is mutatja.

internal class DelegateTest {  
   CallBackClass cb;  
   // Called before ever using the callback function.  
   public static void SetChangeHandler() {  
      cb = new CallBackClass();  
      ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));  
   }  
   // Called after using the callback function for the last time.  
   public static void RemoveChangeHandler() {  
      // The cb object can be collected now. The unmanaged code is
      // finished with the callback function.  
      cb = null;  
   }  
}  

Alapértelmezett rendezés értéktípusokhoz

A legtöbb értéktípus, például az egész számok és a lebegőpontos számok, titkosak, és nem igényelnek rendezést. Más nem titkos típusok eltérő reprezentációkkal rendelkeznek a felügyelt és nem felügyelt memóriában, és rendezést igényelnek. A többi típus esetében is explicit formázásra van szükség az együttműködés határán.

Ez a szakasz a következő formázott értéktípusokról nyújt tájékoztatást:

A formázott típusok leírása mellett ez a témakör azonosítja azokat a rendszerérték-típusokat , amelyek szokatlan rendezési viselkedést adnak.

A formázott típus olyan összetett típus, amely olyan információkat tartalmaz, amelyek explicit módon vezérli a tagok elrendezését a memóriában. A tagelrendezés adatai az StructLayoutAttribute attribútum használatával lesznek megadva. Az elrendezés a következő LayoutKind számbavételi értékek egyike lehet:

  • LayoutKind.Auto

    Azt jelzi, hogy a közös nyelvi futtatókörnyezet szabadon átrendezheti a típus tagjait a hatékonyság érdekében. Ha azonban egy értéktípust nem felügyelt kódnak ad át, a tagok elrendezése kiszámítható. Egy ilyen struktúra megkísérlése automatikusan kivételt okoz.

  • LayoutKind.Sequential

    Azt jelzi, hogy a típus tagjait nem felügyelt memóriában kell elhelyezni, ugyanabban a sorrendben, amelyben azok megjelennek a felügyelt típusdefinícióban.

  • LayoutKind.Explicit

    Azt jelzi, hogy a tagok az egyes mezőkhöz megadottak szerint FieldOffsetAttribute vannak kialakítva.

A platformhívásban használt értéktípusok

Az alábbi példában a PointRect StructLayoutAttribute használatával a tagok elrendezésére vonatkozó információkat adnak meg.

Imports System.Runtime.InteropServices  
<StructLayout(LayoutKind.Sequential)> Public Structure Point  
   Public x As Integer  
   Public y As Integer  
End Structure  
<StructLayout(LayoutKind.Explicit)> Public Structure Rect  
   <FieldOffset(0)> Public left As Integer  
   <FieldOffset(4)> Public top As Integer  
   <FieldOffset(8)> Public right As Integer  
   <FieldOffset(12)> Public bottom As Integer  
End Structure  
using System.Runtime.InteropServices;  
[StructLayout(LayoutKind.Sequential)]  
public struct Point {  
   public int x;  
   public int y;  
}
  
[StructLayout(LayoutKind.Explicit)]  
public struct Rect {  
   [FieldOffset(0)] public int left;  
   [FieldOffset(4)] public int top;  
   [FieldOffset(8)] public int right;  
   [FieldOffset(12)] public int bottom;  
}  

Ha nem felügyelt kódra van rendezve, ezek a formázott típusok C stílusú struktúrákként vannak rendezve. Így egyszerűen hívhat meg strukturált argumentumokat tartalmazó nem felügyelt API-t. A struktúrák és RECT struktúrák POINT például a következőképpen továbbíthatók a Microsoft Windows API PtInRect függvényének:

BOOL PtInRect(const RECT *lprc, POINT pt);  

A struktúrákat a következő platformhívási definícióval adhatja át:

Friend Class NativeMethods
    Friend Declare Auto Function PtInRect Lib "User32.dll" (
        ByRef r As Rect, p As Point) As Boolean
End Class
internal static class NativeMethods
{
   [DllImport("User32.dll")]
   internal static extern bool PtInRect(ref Rect r, Point p);
}

Az Rect értéktípust referencia alapján kell átadni, mert a nem felügyelt API azt várja, hogy RECT egy mutatót adjon át a függvénynek. Az Point értéktípust az érték adja át, mert a nem felügyelt API elvárja a POINT verem átadását. Ez a finom különbség nagyon fontos. A rendszer mutatóként továbbítja a hivatkozásokat a nem felügyelt kódnak. Az értékek a verem nem felügyelt kódjának lesznek átadva.

Feljegyzés

Ha egy formázott típus szerkezetként van rendezve, csak a típuson belüli mezők érhetők el. Ha a típus metódusokat, tulajdonságokat vagy eseményeket is tartalmazó, nem felügyelt kódból nem érhetők el.

Az osztályok C stílusú struktúrákként nem felügyelt kódra is rendezhetők, feltéve, hogy rögzített tagelrendezésük van. Az osztály tagelrendezési információi is meg lesznek adva az StructLayoutAttribute attribútummal. A rögzített elrendezésű értéktípusok és a rögzített elrendezésű osztályok közötti fő különbség az, ahogyan a rendszer nem felügyelt kódra rendezi őket. Az értéktípusok érték szerint vannak átadva (a veremen), ezért a hívó nem látja a hívó által a típus tagjainak módosításait. A referenciatípusok továbbítása hivatkozással történik (a verem a típusra mutató hivatkozást ad át); következésképpen a hívó minden olyan módosítást lát, amelyet a hívó egy adott típusú tagon végzett.

Feljegyzés

Ha egy referenciatípus nem titkos típusok tagjaival rendelkezik, az átalakítás kétszer szükséges: az első alkalommal, amikor argumentumot ad át a nem felügyelt oldalnak, a második alkalommal pedig a hívásból való visszatéréskor. A hozzáadott többletterhelés miatt a be- és kimenő paramétereket explicit módon kell alkalmazni egy argumentumra, ha a hívó látni szeretné a hívó által végrehajtott módosításokat.

Az alábbi példában az SystemTime osztály szekvenciális tagelrendezéssel rendelkezik, és átadható a Windows API GetSystemTime függvénynek.

<StructLayout(LayoutKind.Sequential)> Public Class SystemTime  
   Public wYear As System.UInt16  
   Public wMonth As System.UInt16  
   Public wDayOfWeek As System.UInt16  
   Public wDay As System.UInt16  
   Public wHour As System.UInt16  
   Public wMinute As System.UInt16  
   Public wSecond As System.UInt16  
   Public wMilliseconds As System.UInt16  
End Class  
[StructLayout(LayoutKind.Sequential)]  
   public class SystemTime {  
   public ushort wYear;
   public ushort wMonth;  
   public ushort wDayOfWeek;
   public ushort wDay;
   public ushort wHour;
   public ushort wMinute;
   public ushort wSecond;
   public ushort wMilliseconds;
}  

A GetSystemTime függvény a következőképpen van definiálva:

void GetSystemTime(SYSTEMTIME* SystemTime);  

A GetSystemTime egyenértékű platformhívási definíciója a következő:

Friend Class NativeMethods
    Friend Declare Auto Sub GetSystemTime Lib "Kernel32.dll" (
        ByVal sysTime As SystemTime)
End Class
internal static class NativeMethods
{
   [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
   internal static extern void GetSystemTime(SystemTime st);
}

Figyelje meg, hogy az SystemTime argumentum nem hivatkozási argumentumként van begépelve, mert SystemTime osztály, nem értéktípus. Az értéktípusoktól eltérően az osztályok mindig hivatkozás alapján kerülnek átadásra.

Az alábbi példakód egy másik Point osztályt mutat be, amelynek neve SetXYegy metódus. Mivel a típus szekvenciális elrendezéssel rendelkezik, átadható nem felügyelt kódnak, és strukturáltként rendezhető. A SetXY tag azonban nem hívható meg nem felügyelt kódból, annak ellenére, hogy az objektumot hivatkozással továbbítja.

<StructLayout(LayoutKind.Sequential)> Public Class Point  
   Private x, y As Integer  
   Public Sub SetXY(x As Integer, y As Integer)  
      Me.x = x  
      Me.y = y  
   End Sub  
End Class  
[StructLayout(LayoutKind.Sequential)]  
public class Point {  
   int x, y;  
   public void SetXY(int x, int y){
      this.x = x;  
      this.y = y;  
   }  
}  

A COM Interopban használt értéktípusok

A formázott típusok a COM interop metódushívások számára is átadhatók. Valójában a típustárba exportált értéktípusok automatikusan struktúrákká alakulnak. Ahogy az alábbi példa is mutatja, az Point értéktípus típusdefinícióvá (typedef) válik a névvel Point. A típustár más részein található értéktípusra mutató Point hivatkozásokat a typedef értékre cseréli a Point rendszer.

Típustár-ábrázolás

typedef struct tagPoint {  
   int x;  
   int y;  
} Point;  
interface _Graphics {  
   …  
   HRESULT SetPoint ([in] Point p)  
   HRESULT SetPointRef ([in,out] Point *p)  
   HRESULT GetPoint ([out,retval] Point *p)  
}  

A COM-felületeken való rendezéskor ugyanazok a szabályok használatosak, mint a platformhívási hívásokra való hivatkozásra. Ha például az értéktípus egy példányát a Point .NET-keretrendszer a COM-nak adja át, akkor az Point érték szerint lesz átadva. Ha az Point értéktípust hivatkozással adja át, a verem egy mutatót Point ad át. Az interop marshaller egyik irányban sem támogatja a magasabb szintű indirekt (**) indirekt szintet.

Feljegyzés

Az Explicit értékre beállított enumerálási értékkel rendelkező LayoutKind struktúrák nem használhatók a COM-interopban, mert az exportált típustár nem tud explicit elrendezést kifejezni.

Rendszerérték-típusok

A System névtér több értéktípust is használ, amelyek a futtatókörnyezeti primitív típusok dobozos formáját jelölik. Az értéktípus-struktúra System.Int32 például a ELEMENT_TYPE_I4 dobozos formáját jelöli. Ahelyett, hogy struktúrákként rendezi ezeket a típusokat, mint más formázott típusok, ugyanúgy helyezi el őket, mint az általuk megadott primitív típusok. A System.Int32 ezért ELEMENT_TYPE_I4 van rendezve, nem pedig egy hosszú típusú tagot tartalmazó struktúraként. Az alábbi táblázat a rendszernévtér azon értéktípusainak listáját tartalmazza, amelyek a primitív típusok dobozos ábrázolásai.

Rendszerérték típusa Elem típusa
System.Boolean ELEMENT_TYPE_BOOLEAN
System.SByte ELEMENT_TYPE_I1
System.Byte ELEMENT_TYPE_UI1
System.Char ELEMENT_TYPE_CHAR
System.Int16 ELEMENT_TYPE_I2
System.UInt16 ELEMENT_TYPE_U2
System.Int32 ELEMENT_TYPE_I4
System.UInt32 ELEMENT_TYPE_U4
System.Int64 ELEMENT_TYPE_I8
System.UInt64 ELEMENT_TYPE_U8
System.Single ELEMENT_TYPE_R4
System.Double ELEMENT_TYPE_R8
System.String ELEMENT_TYPE_STRING
System.IntPtr ELEMENT_TYPE_I
System.UIntPtr ELEMENT_TYPE_U

A rendszernévtér néhány más értéktípusát eltérően kezeli a rendszer . Mivel a nem felügyelt kód már rendelkezik jól bevált formátumokkal ezekhez a típusokhoz, a rendező speciális szabályokkal rendelkezik a rendezéshez. Az alábbi táblázat a rendszernévtér speciális értéktípusát, valamint a nem felügyelt típust sorolja fel.

Rendszerérték típusa IDL-típus
System.DateTime DATE
System.Decimal DECIMÁLIS
System.Guid GUID
System.Drawing.Color OLE_COLOR

Az alábbi kód a Stdole2 típusú kódtárban a DÁTUM, GUID, TIZEDES és OLE_COLOR nem felügyelt típusok definícióját mutatja be.

Típustár-ábrázolás

typedef double DATE;  
typedef DWORD OLE_COLOR;  
  
typedef struct tagDEC {  
    USHORT    wReserved;  
    BYTE      scale;  
    BYTE      sign;  
    ULONG     Hi32;  
    ULONGLONG Lo64;  
} DECIMAL;  
  
typedef struct tagGUID {  
    DWORD Data1;  
    WORD  Data2;  
    WORD  Data3;  
    BYTE  Data4[ 8 ];  
} GUID;  

Az alábbi kód a megfelelő definíciókat jeleníti meg a felügyelt IValueTypes felületen.

Public Interface IValueTypes  
   Sub M1(d As System.DateTime)  
   Sub M2(d As System.Guid)  
   Sub M3(d As System.Decimal)  
   Sub M4(d As System.Drawing.Color)  
End Interface  
public interface IValueTypes {  
   void M1(System.DateTime d);  
   void M2(System.Guid d);  
   void M3(System.Decimal d);  
   void M4(System.Drawing.Color d);  
}  

Típustár-ábrázolás

[…]  
interface IValueTypes : IDispatch {  
   HRESULT M1([in] DATE d);  
   HRESULT M2([in] GUID d);  
   HRESULT M3([in] DECIMAL d);  
   HRESULT M4([in] OLE_COLOR d);  
};  

Lásd még