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 MethodOne
a 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:
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.
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.
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 Point
Rect
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 SetXY
egy 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);
};