Výchozí chování při zařazování
Sřazování zprostředkovatele funguje s pravidly, která určují, jak se data přidružená k parametrům metody chovají při předávání mezi spravovanou a nespravovanou pamětí. Tato předdefinovaná pravidla řídí takové aktivity zařazování jako transformace datového typu, zda volaný může měnit data předaná do volajícího a vracet tyto změny volajícímu, a za jakých okolností marshaller poskytuje optimalizaci výkonu.
Tato část identifikuje výchozí charakteristiky chování služby zařazování zprostředkovatele komunikace. Obsahuje podrobné informace o zařazování polí, logických typech, typech znaků, delegátech, třídách, objektech, řetězcích a strukturách.
Poznámka:
Seřazování obecných typů se nepodporuje. Další informace naleznete v tématu Spolupráce pomocí obecných typů.
Správa paměti pomocí zprostředkovatele komunikace
Zprostředkovatele komunikace se vždy pokusí uvolnit paměť přidělenou nespravovaným kódem. Toto chování odpovídá pravidlům správy paměti modelu COM, ale liší se od pravidel, která řídí nativní jazyk C++.
Záměna může nastat, pokud při použití vyvolání platformy očekáváte nativní chování jazyka C++ (bez uvolnění paměti), které automaticky uvolní paměť pro ukazatele. Voláním následující nespravované metody z knihovny DLL jazyka C++ například automaticky nevyvolá žádnou paměť.
Nespravovaný podpis
BSTR MethodOne (BSTR b) {
return b;
}
Pokud však definujete metodu jako prototyp vyvolání platformy, nahraďte každý typ BSTR typem String a voláním MethodOne
, common language runtime se pokusí uvolnit b
dvakrát. Chování při zařazování můžete změnit pomocí IntPtr typů namísto typů String .
Modul runtime vždy používá metodu CoTaskMemFree ve Windows a bezplatnou metodu na jiných platformách k uvolnění paměti. Pokud paměť, se kterou pracujete, nebyla přidělena metodou CoTaskMemAlloc v systému Windows nebo malloc na jiných platformách, musíte použít IntPtr a uvolnit paměť ručně pomocí příslušné metody. Podobně se můžete vyhnout automatickému uvolnění paměti v situacích, kdy by paměť neměla být nikdy uvolněna, například při použití funkce GetCommandLine z Kernel32.dll, která vrací ukazatel na paměť jádra. Podrobnosti o ručním uvolnění paměti najdete v ukázce vyrovnávací paměti.
Výchozí zařazování tříd
Třídy mohou být zařazovány pouze zprostředkovatele komunikace modelu COM a jsou vždy zařazovány jako rozhraní. V některých případech se rozhraní používané k zařazování třídy označuje jako rozhraní třídy. Informace o přepsání rozhraní třídy s rozhraním podle vašeho výběru naleznete v tématu Úvod do rozhraní třídy.
Předávání tříd modelu COM
Když je spravovaná třída předána modelu COM, zprostředkovatele komunikace automaticky zabalí třídu proxy com a předá rozhraní třídy vytvořené proxy do volání metody COM. Proxy pak deleguje všechna volání na rozhraní třídy zpět na spravovaný objekt. Proxy také zveřejňuje další rozhraní, která nejsou explicitně implementována třídou. Proxy automaticky implementuje rozhraní, jako je IUnknown a IDispatch jménem třídy.
Předávání tříd do kódu .NET
Třídy spolutřídy se obvykle nepoužívají jako argumenty metody v modelu COM. Místo toho se místo třídy třídy obvykle předává výchozí rozhraní.
Při předání rozhraní do spravovaného kódu je interop marshaller zodpovědný za zabalení rozhraní se správnou obálkou a předání obálky spravované metodě. Určení obálky, kterou použít, může být obtížné. Každá instance objektu COM má jednu jedinečnou obálku bez ohledu na to, kolik rozhraní objekt implementuje. Například jeden objekt COM, který implementuje pět různých rozhraní má pouze jeden obálka. Stejný obálka zveřejňuje všech pět rozhraní. Pokud jsou vytvořeny dvě instance objektu COM, vytvoří se dvě instance obálky.
Aby obálka zachovala stejný typ po celou dobu jeho životnosti, musí zprostředkovatele komunikace identifikovat správnou obálku při prvním předání rozhraní vystaveného objektem marshallerem. Marshaller identifikuje objekt tak, že se podívá na jedno z rozhraní, která objekt implementuje.
Například marshaller určuje, že obálka třídy by měla být použita k zabalení rozhraní, které bylo předáno do spravovaného kódu. Když je rozhraní poprvé předáno marshaller, marshaller zkontroluje, zda rozhraní pochází ze známého objektu. Tato kontrola probíhá ve dvou situacích:
Rozhraní je implementováno jiným spravovaným objektem, který byl předán modelu COM jinde. Marshaller dokáže snadno identifikovat rozhraní vystavená spravovanými objekty a dokáže shodovat rozhraní se spravovaným objektem, který poskytuje implementaci. Spravovaný objekt se pak předá metodě a nevyžaduje se žádná obálka.
Objekt, který je již zabalený, implementuje rozhraní. Chcete-li zjistit, zda se jedná o tento případ, marshaller dotazuje objekt pro jeho rozhraní IUnknown a porovná vrácené rozhraní s rozhraními jiných objektů, které jsou již zabaleny. Pokud je rozhraní stejné jako jiné obálky, objekty mají stejnou identitu a existující obálka se předá metodě.
Pokud rozhraní není ze známého objektu, marshaller provede toto:
Marshaller dotazuje objekt pro IProvideClassInfo2 rozhraní. Pokud je k dispozici, marshaller použije CLSID vrácený z IProvideClassInfo2.GetGUID k identifikaci třídy coclass poskytující rozhraní. S CLSID může marshaller vyhledat obálku z registru, pokud bylo sestavení dříve registrováno.
Marshaller dotazuje rozhraní pro IProvideClassInfo rozhraní. Pokud je k dispozici, marshaller používá ITypeInfo vrácený z IProvideClassInfo.GetClassinfo k určení CLSID třídy vystavení rozhraní. Marshaller může použít CLSID k vyhledání metadat obálky.
Pokud marshaller stále nemůže identifikovat třídu, zabalí rozhraní s obecnou obálkovou třídou s názvem System.__ComObject.
Výchozí zařazování delegátů
Spravovaný delegát se zařadí jako rozhraní MODELU COM nebo jako ukazatel funkce na základě volajícího mechanismu:
Pro vyvolání platformy je delegát ve výchozím nastavení zařazován jako nespravovaný ukazatel funkce.
U zprostředkovatele komunikace modelu COM je delegát ve výchozím nastavení zařazován jako rozhraní modelu COM typu _Delegate . Rozhraní _Delegate je definováno v knihovně typů Mscorlib.tlb a obsahuje metodu Delegate.DynamicInvoke , která umožňuje volat metodu, na kterou delegát odkazuje.
V následující tabulce jsou uvedeny možnosti zařazování pro datový typ spravovaného delegáta. Atribut MarshalAsAttribute poskytuje několik UnmanagedType hodnot výčtu pro zařazování delegátů.
Typ výčtu | Popis nespravovaného formátu |
---|---|
UnmanagedType.FunctionPtr | Nespravovaný ukazatel funkce. |
UnmanagedType.Interface | Rozhraní typu _Delegate, jak je definováno v mscorlib.tlb. |
Představte si následující příklad kódu, ve kterém jsou metody DelegateTestInterface
exportovány do knihovny typů modelu COM. Všimněte si, že jako parametry In/Out se předávají pouze delegáti označené klíčovým slovem Ref (nebo ByRef).
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);
}
Reprezentace knihovny typů
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);
};
Ukazatel funkce může být dereferenced, stejně jako jakýkoli jiný nespravovaný ukazatel funkce může být dereferenced.
V tomto příkladu jsou dva delegáti zařazovány jako UnmanagedType.FunctionPtr, výsledek je int
a ukazatel na int
. Vzhledem k tomu, že typy delegátů jsou zařazovány, int
představuje zde ukazatel na void (void*
), což je adresa delegáta v paměti. Jinými slovy, tento výsledek je specifický pro 32bitové systémy Windows, protože int
zde představuje velikost ukazatele funkce.
Poznámka:
Odkaz na ukazatel funkce na spravovaný delegát uchovávaný nespravovaným kódem nebrání modulu CLR (Common Language Runtime) provádět uvolňování paměti spravovaného objektu.
Například následující kód je nesprávný, protože odkaz na cb
objekt, předaný SetChangeHandler
metodě, se nezachová nad cb
rámec životnosti Test
metody. cb
Jakmile je objekt uvolněn z paměti, ukazatel funkce předaný SetChangeHandler
již není platný.
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));
}
}
Aby bylo možné nahradit neočekávané uvolňování paměti, volající musí zajistit, aby cb
byl objekt stále aktivní, pokud se používá nespravovaný ukazatel funkce. Volitelně můžete nechat nespravovaný kód upozornit spravovaný kód, když už ukazatel funkce není potřeba, jak ukazuje následující příklad.
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;
}
}
Výchozí zařazování pro typy hodnot
Většina hodnotových typů, jako jsou celá čísla a čísla s plovoucí desetinou čárkou, je blitelná a nevyžaduje zařazování. Jiné nespravovatelné typy mají odlišné reprezentace ve spravované a nespravované paměti a vyžadují zařazování. I jiné typy vyžadují explicitní formátování napříč hranicemi spolupráce.
Tato část obsahuje informace o následujících formátovaných typech hodnot:
Kromě popisu formátovaných typů toto téma identifikuje typy systémových hodnot, které mají neobvyklé chování při zařazování.
Formátovaný typ je komplexní typ, který obsahuje informace, které explicitně řídí rozložení jejích členů v paměti. Informace o rozložení člena jsou k dispozici pomocí atributu StructLayoutAttribute . Rozložení může být jedna z následujících LayoutKind hodnot výčtu:
LayoutKind.Auto
Označuje, že modul CLR (Common Language Runtime) umožňuje změnit pořadí členů typu pro zajištění efektivity. Pokud je však typ hodnoty předán nespravovanému kódu, je rozložení členů předvídatelné. Pokus o zařazení takové struktury automaticky způsobí výjimku.
LayoutKind.Sekvenční
Označuje, že členy typu mají být rozloženy v nespravované paměti ve stejném pořadí, v jakém se zobrazují v definici spravovaného typu.
LayoutKind.Explicit
Označuje, že členy jsou rozloženy podle FieldOffsetAttribute dodaného pole.
Typy hodnot používané při vyvolání platformy
V následujícím příkladu Point
a Rect
typy poskytují informace o rozložení člena pomocí StructLayoutAttribute.
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;
}
Při zařazování do nespravovaného kódu jsou tyto formátované typy zařazovány jako struktury ve stylu jazyka C. To poskytuje snadný způsob volání nespravovaného rozhraní API, které má argumenty struktury. Tyto struktury lze například POINT
RECT
předat funkci PtInRect rozhraní API systému Microsoft Windows následujícím způsobem:
BOOL PtInRect(const RECT *lprc, POINT pt);
Struktury můžete předat pomocí následující definice vyvolání platformy:
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);
}
Typ Rect
hodnoty musí být předán odkazem, protože nespravované rozhraní API očekává ukazatel na předaný RECT
funkci. Typ Point
hodnoty je předán hodnotou, protože nespravované rozhraní API očekává POINT
předání v zásobníku. Tento drobný rozdíl je velmi důležitý. Odkazy se předávají nespravovanému kódu jako ukazatele. Hodnoty se předávají nespravovanému kódu v zásobníku.
Poznámka:
Pokud je formátovaný typ zařazován jako struktura, jsou přístupná pouze pole v daném typu. Pokud typ obsahuje metody, vlastnosti nebo události, jsou nepřístupné z nespravovaného kódu.
Třídy lze také zařaďovat do nespravovaného kódu jako struktury ve stylu jazyka C za předpokladu, že mají pevné rozložení členů. Informace o rozložení člena pro třídu jsou k dispozici také s atributem StructLayoutAttribute . Hlavní rozdíl mezi typy hodnot s pevným rozložením a třídami s pevným rozložením je způsob, jakým jsou zařazovány do nespravovaného kódu. Typy hodnot se předávají podle hodnoty (v zásobníku) a v důsledku toho se volajícím nezobrazí žádné změny členů typu volaného. Odkazové typy jsou předány odkazem (odkaz na typ je předán v zásobníku); v důsledku toho se volajícímu zobrazí všechny změny členů typu volaného.
Poznámka:
Pokud má odkazový typ členy nespravovatelných typů, je převod vyžadován dvakrát: při prvním předání argumentu do nespravované strany a druhý čas při návratu z volání. Kvůli této přidané režii se parametry in/Out musí explicitně použít u argumentu, pokud volající chce vidět změny provedené volaným uživatelem.
V následujícím příkladu SystemTime
má třída rozložení sekvenčního členu a lze ji předat funkci GetSystemTime rozhraní API systému Windows.
<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;
}
Funkce GetSystemTime je definována takto:
void GetSystemTime(SYSTEMTIME* SystemTime);
Ekvivalentní definice vyvolání platformy pro GetSystemTime je následující:
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);
}
Všimněte si, že SystemTime
argument není zadán jako argument odkazu, protože SystemTime
je třída, nikoli typ hodnoty. Na rozdíl od typů hodnot se třídy vždy předávají odkazem.
Následující příklad kódu ukazuje jinou Point
třídu, která má volanou SetXY
metodu . Vzhledem k tomu, že typ má sekvenční rozložení, lze ho předat nespravovanému kódu a zařaďovat ho jako strukturu. SetXY
Člen však není možné volat z nespravovaného kódu, i když je objekt předán odkazem.
<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;
}
}
Typy hodnot používané v interoperabilitě modelu COM
Formátované typy lze také předat voláním metody zprostředkovatele komunikace modelu COM. Ve skutečnosti při exportu do knihovny typů se typy hodnot automaticky převedou na struktury. Jak ukazuje následující příklad, Point
typ hodnoty se stane definicí typu (typedef) s názvem Point
. Všechny odkazy na Point
typ hodnoty jinde v knihovně typů jsou nahrazeny Point
typedef.
Reprezentace knihovny typů
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)
}
Stejná pravidla používaná k zařazování hodnot a odkazů na volání volání platformy se používají při zařazování prostřednictvím rozhraní MODELU COM. Pokud je například instance Point
typu hodnoty předána z rozhraní .NET Framework do modelu COM, Point
předává se hodnotou. Point
Pokud je typ hodnoty předán odkazem, předá se ukazatel na Point
zásobník. Zařazovač vzájemné spolupráce nepodporuje vyšší úrovně nepřímých spojení (bod **) v obou směrech.
Poznámka:
Struktury, které mají hodnotu výčtu nastavenou LayoutKind na Explicit , nelze použít v zprostředkovatele komunikace modelu COM, protože exportovaná knihovna typů nemůže vyjádřit explicitní rozložení.
Typy systémových hodnot
Obor System názvů má několik hodnotových typů, které představují krabicovou formu primitivních typů modulu runtime. Například struktura typu System.Int32 hodnoty představuje krabicovou formu ELEMENT_TYPE_I4. Místo zařazování těchto typů jako struktur, protože jiné formátované typy jsou, zařazujete je stejným způsobem jako primitivní typy, které jsou v rámečku. System.Int32 je proto zařazován jako ELEMENT_TYPE_I4 namísto struktury obsahující jeden člen typu long. Následující tabulka obsahuje seznam hodnotových typů v systémovém oboru názvů, které jsou boxované reprezentace primitivních typů.
Typ systémové hodnoty | Typ prvku |
---|---|
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 |
Některé jiné typy hodnot v systémovém oboru názvů jsou zpracovávány odlišně. Vzhledem k tomu, že nespravovaný kód již má pro tyto typy dobře zavedené formáty, má marshaller speciální pravidla pro jejich zařazování. Následující tabulka uvádí speciální typy hodnot v oboru názvů systému a také nespravovaný typ, na který jsou zařazovány.
Typ systémové hodnoty | Typ IDL |
---|---|
System.DateTime | DATE (Datum) |
System.Decimal | DESETINNÝCH |
System.Guid | GUID |
System.Drawing.Color | OLE_COLOR |
Následující kód ukazuje definici nespravovaných typů DATE, GUID, DECIMAL a OLE_COLOR v knihovně typů Stdole2.
Reprezentace knihovny typů
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;
Následující kód ukazuje odpovídající definice ve spravovaném IValueTypes
rozhraní.
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);
}
Reprezentace knihovny typů
[…]
interface IValueTypes : IDispatch {
HRESULT M1([in] DATE d);
HRESULT M2([in] GUID d);
HRESULT M3([in] DECIMAL d);
HRESULT M4([in] OLE_COLOR d);
};