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 k uvolnění paměti. Pokud paměť, se kterou pracujete, nebyla přidělena metodou CoTaskMemAlloc , 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:

  1. 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.

  2. 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.

  3. 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 POINTRECT 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 SetXYmetodu . 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);  
};  

Viz také