Megosztás:


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 összehangolási tevékenységeket, mint például az adattípus átalakításokat, hogy a hívott fél módosíthatja-e a neki átadott adatokat, és visszaküldheti-e ezeket a módosításokat a hívónak, valamint hogy milyen körülmények között biztosít a közvetí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, delegátumok, osztályok, objektumok, karakterláncok és struktúrák kezeléséről.

Megjegyzés:

Az általános típusok adatösszekapcsolása nem támogatott. További információért lásd: 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 String típusra, és hívja meg MethodOne-t, a közös nyelvi futtatókörnyezet megpróbálja kétszer felszabadítani a b-t. Marshalolási viselkedést a String típusok helyett IntPtr típusok használatával módosíthatja.

A futtatókörnyezet mindig a CoTaskMemFree metódust használja Windows-on, és a free metódust más platformokon a memória felszabadításához. Ha a memóriát, amellyel dolgozik, nem a CoTaskMemAlloc metódussal Windows rendszeren vagy malloc metódussal más platformokon foglalta le, a megfelelő módszer használatával manuálisan kell IntPtr felszabadítania a memóriát. 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 ha a függvényt a GetCommandLine Kernel32.dllhasználja, 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 segítségével továbbíthatók, és mindig interfészként vannak továbbítva. 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-ra

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 a IUnknown és a IDispatch az osztály nevében.

Osztályok továbbítása a .NET-kódhoz

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 coclass 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óelem egész élettartama során ugyanazt a típust megőrizze, az interop marstellernek azonosítania kell a megfelelő burkolóelemet, amikor az objektum által közzétett interfész első alkalommal áthalad a marstelleren. A helyfoglaló úgy azonosítja az objektumot, hogy megvizsgálja az objektum által implementált felületek egyikét.

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 interfész először áthalad az anyagmozgatón, az anyagmozgató ellenőrzi, hogy az interfész egy ismert objektumtó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önnyen képes azonosítani a felügyelt objektumok által feltárt interfészeket, és összeilleszteni őket 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 szerializáló lekérdezi az objektumot az IUnknown interfészéről, és összehasonlítja a visszaadott interfészt más objektumok interfészeivel, amelyeket már beburkoltak. 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 marshaler 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 segítségével a marshaler megkeresheti a csomagolót a beállításjegyzékből, feltéve, hogy az assembly korábban már regisztrálva lett.

  2. Az összeállító lekérdezi az IProvideClassInfo interfészt. Ha meg van adva, a rendező az ITypeInfoIProvideClassInfo.GetClassinfo által visszaadott értéket használja az interfészt felfedő osztály CLSID-jének meghatározásához. 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 delegált COM-felületként vagy függvénymutatóként van kezelve a hívási mechanizmus alapján.

  • Platformhívás esetén egy delegált alapértelmezés szerint nem felügyelt függvénymutatóként van továbbítva.

  • COM-interop esetén egy delegált alapértelmezés szerint COM-felületként _Delegate kerül átvitelre. Az _Delegate interfész 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ípuskönyvtá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 összevonva UnmanagedType.FunctionPtr, az eredmény egy int és egy mutató egy int-re. Mivel a delegált típusok átkonvertálása történik, a int itt egy void típusú mutatót képvisel (void*), ami a memóriában lévő delegált címét jelenti. 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ó.

Megjegyzés:

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

Például a következő kód helytelen, mert a cb objektumra mutató hivatkozás, amelyet a SetChangeHandler metódusnak adtak át, nem tartja életben a cb-t a Test metódus élettartamán túl. Miután az cb objektumot begyűjti a szemétgyűjtő, az 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, blittelhető, és nem igényelnek marshallozást. Más nem blittelhető típusok eltérő reprezentációkkal rendelkeznek a felügyelt és nem felügyelt memóriában, és átalakítá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 el vannak rendezve.

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

Az alábbi példában a Point és Rect típusok a StructLayoutAttribute segítségével adják meg a tagok elrendezésére vonatkozó információkat.

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

Amikor ezek a formázott típusok nem felügyelt kóddá vannak átadva, C-stílusú struktúrák formájában jelennek meg. Így egyszerűen hívhat meg strukturált argumentumokat tartalmazó nem felügyelt API-t. A POINT és RECT struktúrák például az alábbiak szerint továbbíthatók a Microsoft Windows API PtInRect függvénynek:

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 hivatkozások mutatóként kerülnek továbbításra a nem felügyelt kódnak. Az értékek a verem nem felügyelt kódjának lesznek átadva.

Megjegyzé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ípusokat hivatkozással továbbítják (a verem egy típusra mutató hivatkozást ad át); következésképpen a hívó fél által a blittable típusú tagokon végrehajtott minden módosítást a hívó lát.

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

Az GetSystemTime egyenértékű platformhívásának 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 van egy SetXY nevű metódusa. 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árban található összes Point értéktípusra mutató hivatkozást a Point typedefel cserélik le.

Típuskönyvtá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ó egyesítéskor ugyanazokat a szabályokat használják, mint az értékek és hivatkozások platformmeghívási hívásokra való egyesítésekor. Ha például az Point értéktípus egy példányát a .NET-keretrendszerből a COM-nak adja át, akkor az Point érték szerint lesz átadva. Ha az Point értéktípust hivatkozással adja át, egy mutató Point kerül a veremre. Az interop marshaller egyik irányban sem támogatja a magasabb szintű indirekciót (Point **).

Megjegyzés:

Az LayoutKind enumerálási értékkel Explicit rendelkező 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 ezeket a típusokat struktúrákként kezelné, mint a többi formázott típust, ugyanúgy kezeli őket, mint az általuk magukba foglalt primitív típusokat. A System.Int32 ezért ELEMENT_TYPE_I4 helyett egyetlen hosszú tagot tartalmazó struktúraként van rendezve. Az alábbi táblázat a névtérben szereplő System értéktípusok 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 névtér más System értéktípusai eltérően vannak kezelve. 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 felsorolja a névtér speciális System értéktípusát, valamint azt a nem felügyelt típust, amelybe rendezve vannak.

Rendszerérték típusa IDL-típus
System.DateTime DÁTUM
System.Decimal Decimális
System.Guid GUID
System.Drawing.Color OLE_COLOR

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

Típuskönyvtá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ípuskönyvtá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