Sdílet prostřednictvím


Výchozí chování při zařazování

Interop marshalling se řídí 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 jsou transformace datových typů, možnosti volaného měnit data, která mu byla předána, a vrátit tyto změny volajícímu, a za jakých okolností marshaller poskytuje výkonové optimalizace.

Tato část identifikuje výchozí charakteristiky chování služby interoperabilního zpracování dat. 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í interoperabilního maršálování

Zprostředkovatel komunikace interop vždy se pokouší 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í následující nespravované metody z DLL knihovny C++ například automaticky neuvolní žádnou paměť.

Nespravovaný podpis

BSTR MethodOne (BSTR b) {
     return b;
}

Pokud však definujete metodu jako prototyp vyvolání platformy, nahraďte každý BSTR typ typem String a pak volejte MethodOne, běhové prostředí společného jazyka se pokusí uvolnit b dvakrát. Chování při sřazování můžete změnit pomocí IntPtr typů, nikoli String typů.

Modul runtime vždy používá metodu CoTaskMemFree ve Windows a free 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 metodou 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í GetCommandLine funkce z Kernel32.dll, která vrací ukazatel na paměť jádra. Podrobnosti o ručním uvolnění paměti najdete v Buffers Sample.

Výchozí zařazování tříd

Třídy mohou být přenášeny pouze prostřednictvím COM interop a jsou vždy přenášeny 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 rozhraním podle vlastního 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ředkovatel komunikace automaticky třídu zabalí do proxy COM a předá rozhraní třídy vytvořené proxy COM 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 na místě třídy.

Předávání tříd do kódu .NET

Třídy coclasses se obvykle nepoužívají jako argumenty metod v prostředí COM. Místo třídy coclass se 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 obal. Stejná obálka poskytuje všech pět rozhraní. Pokud jsou vytvořeny dvě instance objektu COM, vytvoří se dvě instance obálky.

Aby obálka udržela stejný typ po celou dobu své životnosti, musí marshaler spolupráce identifikovat správnou obálku při prvním průchodu rozhraní vystaveného objektem marshallerem. Marshaller identifikuje objekt tak, že se podívá na jedno z rozhraní, která objekt implementuje.

Například marshaller rozhodne, že obálka třídy má obalit rozhraní, které vstoupilo do řízeného kódu. Když je rozhraní poprvé předáno prostředníkem, tento prostředník 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 spojit rozhraní se spravovaným objektem, který poskytuje jeho 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 na jeho IUnknown rozhraní a porovná vrácené rozhraní s rozhraními jiných objektů, které jsou již obaleny. 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 na rozhraní IProvideClassInfo2. Pokud je k dispozici, marshaller použije CLSID vrácený z IProvideClassInfo2.GetGUID k identifikaci třídy coclass poskytující rozhraní. S pomocí CLSID může marshaller najít obálku v registru, pokud bylo sestavení již dříve registrováno.

  2. Marshaller dotazuje rozhraní pro IProvideClassInfo rozhraní. Pokud je k dispozici, marshaller použije ITypeInfo vrácenou z IProvideClassInfo.GetClassinfo k určení CLSID třídy, která vystavuje 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 je zařazen jako COM rozhraní nebo jako ukazatel funkce, na základě použitého mechanismu volání.

  • Pro vyvolání platformy je delegát ve výchozím nastavení zařazován jako nespravovaný ukazatel funkce.

  • Při využití COM interop je delegát ve výchozím nastavení přenášen jako rozhraní 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.

Následující tabulka uvádí možnosti marshalling 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 neřízené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áty 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 dereferencován, stejně jako jakýkoli jiný nespravovaný ukazatel funkce.

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 přenášeny, int zde představuje 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ý metodě SetChangeHandler, neudrží cb aktivní po dobu trvání metody Test. Jakmile je objekt cb uvolněn z paměti, ukazatel funkce, který je předán 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é kompenzovat neočekávaný sběr odpadu, volající musí zajistit, aby objekt cb byl naživu, dokud je používán 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é neblittovatelné typy mají odlišné reprezentace ve spravované a nespravované paměti a vyžadují marshalling. 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 běhové prostředí CLR (Common Language Runtime) umožňuje změnit pořadí členů typu pro efektivitu. 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.

  • TypRozložení.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 specifikací FieldOffsetAttribute u každé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 rozhraní API PtInRect 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í rozhraní 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é API očekává, že ukazatel na RECT bude předán 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 (na zásobníku), proto volající nevidí žádné změny členů typu provedené volaným. 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: nejprve při předání argumentu na nespravovanou stranu a podruhé při návratu z volání. Kvůli této přidané zátěži se parametry In/Out musí explicitně použít u argumentu, pokud volající chce vidět změny provedené volaným.

V následujícím příkladu má třída SystemTime sekvenční rozložení členů a lze ji předat funkci API systému Windows GetSystemTime.

<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 je definována GetSystemTime takto:

void GetSystemTime(SYSTEMTIME* SystemTime);

Ekvivalentní definice 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. I když je objekt předán odkazem, SetXY člena nelze volat z nespravovaného kódu.

<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 metod COM interop. 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 přenosu hodnot a odkazů na volání platformy se používají při přenosu prostřednictvím COM rozhraní. Pokud je například instance Point typu hodnoty předána z rozhraní .NET Framework do modelu COM, předává se Point 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í nastavenou hodnotu výčtu LayoutKind, Explicit nelze použít v zprostředkování komunikace modelu COM, protože exportovaná knihovna typů nemůže vyjádřit explicitní rozložení.

Typy systémových hodnot

Jmenný prostor System má několik hodnotových typů, které představují zabalenou formu primitivních typů modulu runtime. Například struktura typu System.Int32 hodnoty představuje krabicovou formu ELEMENT_TYPE_I4. Místo toho, abyste zařazovali tyto typy jako struktury, jako jsou jiné formátované typy, zařazujete je stejným způsobem jako primitivní typy, které zaobalují. 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 System 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 System oboru názvů se zpracovávají 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 System oboru názvů a také nespravovaný typ, na který jsou zařazovány.

Typ systémové hodnoty Typ IDL
System.DateTime DATE (Datum)
System.Decimal DESETINNÉ ČÍSLO
System.Guid Identifikátor 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é