Standardbeteende för marshalling

Interop-marshalling fungerar enligt regler som avgör hur data som är associerade med metodparametrar fungerar när de passerar mellan hanterat och ohanterat minne. Dessa inbyggda regler styr sådana marshallingaktiviteter som datatypsomvandlingar, om en anropare kan ändra data som skickas till den och returnera dessa ändringar till anroparen och under vilka omständigheter marshaller tillhandahåller prestandaoptimeringar.

Det här avsnittet identifierar standardbeteendeegenskaperna för interop marshalling-tjänsten. Den innehåller detaljerad information om sorteringsmatriser, booleska typer, teckentyper, ombud, klasser, objekt, strängar och strukturer.

Anmärkning

Marshalling av generiska typer stöds inte. Mer information finns i Interoperating Using Generic Types.

Minneshantering med "interop marshaller"

Interop-marshallern försöker alltid frigöra minne som allokeras av ohanterad kod. Det här beteendet följer reglerna för COM-minneshantering, men skiljer sig från de regler som styr intern C++.

Förvirring kan uppstå om du förväntar dig inbyggt C++-beteende (inget ledigt minne) när du använder plattformsanrop, vilket automatiskt frigör minne för pekare. Om du till exempel anropar följande ohanterade metod från en C++ DLL frigörs inget minne automatiskt.

Ohanterad signatur

BSTR MethodOne (BSTR b) {
     return b;
}

Men om du definierar metoden som en plattformsanropsprototyp, ersätter varje BSTR typ med en String typ och anropar MethodOne, försöker Common Language Runtime frigöra b två gånger. Du kan ändra marshallingbeteendet genom att använda IntPtr-typer snarare än String-typer.

Körningen använder alltid metoden CoTaskMemFree på Windows och metoden free på andra plattformar för att frigöra minne. Om det minne som du arbetar med inte allokerades med CoTaskMemAlloc metoden i Windows eller malloc metoden på andra plattformar måste du använda ett IntPtr och frigöra minnet manuellt med lämplig metod. På samma sätt kan du undvika automatisk minnesfriskrivning i situationer där minne aldrig ska frigöras, till exempel när du använder GetCommandLine funktionen från Kernel32.dll, som returnerar en pekare till kernelminnet. Mer information om hur du frigör minne manuellt finns i buffertexemplet.

Standardinställning för marshalling av klasser

Klasser kan endast marshalleras med COM-interop och är alltid marshallerade som gränssnitt. I vissa fall kallas gränssnittet som används för att hantera klassen för klassgränssnittet. Information om hur du åsidosättar klassgränssnittet med ett valfritt gränssnitt finns i Introduktion till klassgränssnittet.

Skicka klasser till COM

När en hanterad klass skickas till COM omsluter interop marshaller automatiskt klassen med en COM-proxy och skickar klassgränssnittet som skapas av proxyn till COM-metodanropet. Proxyn delegerar sedan alla anrop i klassgränssnittet tillbaka till det hanterade objektet. Proxyn exponerar även andra gränssnitt som inte uttryckligen implementeras av klassen. Proxyn implementerar automatiskt gränssnitt som IUnknown och IDispatch för klassens räkning.

Skicka klasser till .NET Code

Samklasser används vanligtvis inte som metodargument i COM. Vanligtvis skickas ett standardgränssnitt i stället för coclassen.

När ett gränssnitt skickas till hanterad kod ansvarar interop-marshaller för att korrekt omsluta gränssnittet och föra över omslutningen till den hanterade metoden. Det kan vara svårt att avgöra vilket omslag som ska användas. Varje instans av ett COM-objekt har en enda unik omslutning, oavsett hur många gränssnitt objektet implementerar. Ett enda COM-objekt som implementerar fem distinkta gränssnitt har till exempel bara en wrapper. Wrappern exponerar alla fem gränssnitten. Om två instanser av COM-objektet skapas skapas två instanser av omslutningen.

För att omslutningen ska behålla samma typ under hela sin livslängd måste interop-marshallern identifiera rätt omslutning första gången ett av objektets exponerade gränssnitt passerar genom marshallern. Marshaller identifierar objektet genom att titta på ett av de gränssnitt som objektet implementerar.

Till exempel bestämmer marshallern att klassomslutningen ska användas för att omsluta det gränssnitt som skickades till hanterad kod. När gränssnittet först skickas genom marshallern kontrollerar marshallern om gränssnittet kommer från ett känt objekt. Den här kontrollen sker i två situationer:

  • Ett gränssnitt implementeras av ett annat hanterat objekt som skickades till COM någon annanstans. Marshaller kan enkelt identifiera gränssnitt som exponeras av hanterade objekt och kan matcha gränssnittet med det hanterade objektet som tillhandahåller implementeringen. Det hanterade objektet skickas sedan till metoden och ingen omslutning behövs.

  • Ett objekt som redan har omslutits implementerar gränssnittet. För att avgöra om så är fallet frågar marshallern objektet för dess IUnknown gränssnitt och jämför det returnerade gränssnittet med gränssnitten för andra objekt som redan är omslutna. Om gränssnittet är detsamma som för en annan omslutning har objekten samma identitet och den befintliga omslutningen skickas till metoden.

Om ett gränssnitt inte kommer från ett känt objekt gör marshallern följande:

  1. Marshaller frågar objektet efter gränssnittet IProvideClassInfo2. Om det tillhandahålls använder marshaller CLSID som returneras från IProvideClassInfo2.GetGUID för att identifiera den samklass som tillhandahåller gränssnittet. Med CLSID kan ombudet hitta omslaget i registret om samlingen tidigare har registrerats.

  2. Marshaller frågar om IProvideClassInfo-gränssnittet. Om uppgiften tillhandahålls använder marshallern den ITypeInfo som returneras från IProvideClassInfo.GetClassinfo för att fastställa CLSID för den klass som exponerar det gränssnittet. Marshaller kan använda CLSID för att hitta metadata för omslutningen.

  3. Om marshallern fortfarande inte kan identifiera klassen omsluter den gränssnittet med en allmän omslutningsklass med namnet System.__ComObject.

Standard marshalling för delegeringar

Ett hanterat delegat ordnas som ett COM-gränssnitt eller som en funktionpekare baserat på anropsmekanismen.

  • För plattformsanrop hanteras en delegering som en ohanterad funktionspekare som standard.

  • För COM-interop marshallas en delegering som ett COM-gränssnitt av typen _Delegate som standardvärde. Gränssnittet _Delegate definieras i mscorlib.tlb-typbiblioteket och innehåller Delegate.DynamicInvoke metoden, som gör att du kan anropa metoden som ombudet refererar till.

I följande tabell visas alternativ för sortering för datatypen hanterad delegat. Attributet MarshalAsAttribute innehåller flera UnmanagedType uppräkningsvärden för marskalksdelegater.

Uppräkningstyp Beskrivning av ohanterat format
UnmanagedType.FunctionPtr En ohanterad funktionspekare.
UnmanagedType.Interface Ett gränssnitt av typen _Delegate, enligt definitionen i Mscorlib.tlb.

Tänk på följande exempelkod där metoderna DelegateTestInterface för exporteras till ett COM-typbibliotek. Observera att endast delegeringar som markerats med nyckelordet ref (eller ByRef) skickas som in/ut-parametrar.

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

Skriv biblioteksrepresentation

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

En funktionspekare kan derefereras, precis som andra ohanterade funktionspekare kan derefereras.

I det här exemplet, när de två ombuden är ordnade som UnmanagedType.FunctionPtr, blir resultatet en int och en pekare till en int. Eftersom delegeringstyperna hanteras int representerar det här en pekare till en tom pekare (void*), som är adressen till delegeringen i minnet. Med andra ord är det här resultatet specifikt för 32-bitars Windows-system, eftersom int det här representerar storleken på funktionspekaren.

Anmärkning

En referens till funktionspekaren mot en hanterad delegering som innehas av ohanterad kod hindrar inte Common Language Runtime från att utföra skräpinsamling på det hanterade objektet.

Följande kod är till exempel felaktig eftersom referensen cb till objektet, som skickas till SetChangeHandler metoden, inte håller cb sig vid liv utöver metodens Test livslängd. När objektet cb har blivit skräpsamlat är funktionspekaren som skickas till SetChangeHandler inte längre giltig.

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

För att kompensera för oväntad skräpinsamling måste anroparen se till att cb objektet hålls vid liv så länge den ohanterade funktionspekaren används. Du kan också låta den ohanterade koden meddela den hanterade koden när funktionspekaren inte längre behövs, vilket visas i följande exempel.

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

Standard marshalling för värdetyper

De flesta värdetyper, till exempel heltal och flyttalsnummer, är blittable och kräver inte marshalling. Andra icke-blittable-typer har olika representationer i hanterat och ohanterat minne och kräver marshalling. Andra typer kräver explicit formatering över gränsen för interoperation.

Det här avsnittet innehåller information om följande formaterade värdetyper:

Förutom att beskriva formaterade typer identifierar det här avsnittet systemvärdetyper som har ovanligt hanteringsbeteende.

En formaterad typ är en komplex typ som innehåller information som uttryckligen styr layouten för dess medlemmar i minnet. Informationen om medlemslayouten tillhandahålls med hjälp av attributet StructLayoutAttribute . Layouten kan vara något av följande LayoutKind uppräkningsvärden:

  • LayoutKind.Auto

    Den gemensamma språkplattformen anger att det är tillåtet att fritt ordna om typens medlemmar för att uppnå effektivitet. Men när en värdetyp skickas till ohanterad kod är medlemmarnas layout förutsägbar. Ett försök att konvertera en sådan struktur orsakar automatiskt ett undantag.

  • LayoutKind.Sekventiell

    Anger att medlemmarna av typen ska anges i ohanterat minne i samma ordning som de visas i definitionen för hanterad typ.

  • LayoutKind.Explicit

    Indikerar att medlemmarna är ordnade enligt den FieldOffsetAttribute som tillhandahålls med varje fält.

Värdetyper som används i plattformsanrop

I följande exempel tillhandahåller typerna Point och Rect information om medlemslayout genom att använda 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;
}

När de här formaterade typerna är uppdelade till ohanterad kod, ordnas de som C-formaterade strukturer. Detta ger ett enkelt sätt att anropa ett ohanterat API som har strukturargument. Strukturerna POINT och RECT kan till exempel skickas till Microsoft Windows API-funktionen PtInRect på följande sätt:

BOOL PtInRect(const RECT *lprc, POINT pt);

Du kan skicka strukturer med hjälp av följande plattformsanropsdefinition:

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

Värdetypen Rect måste överföras som referens eftersom det ohanterade API:et förväntar sig att en pekare till RECT skickas till funktionen. Värdetypen Point skickas som värde eftersom det ohanterade API:et förväntar sig att POINT skickas på stacken. Denna subtila skillnad är mycket viktig. Referenser skickas till ohanterad kod som pekare (pointer). Värden överförs till ohanterad kod på stacken.

Anmärkning

När en formaterad typ är ordnad som en struktur är endast fälten inom typen tillgängliga. Om typen har metoder, egenskaper eller händelser är de otillgängliga från ohanterad kod.

Klasser kan också kopplas till ohanterad kod som C-formatstrukturer, förutsatt att de har en fast medlemslayout. Informationen om medlemslayouten för en klass tillhandahålls också med attributet StructLayoutAttribute . Den största skillnaden mellan värdetyper med fast layout och klasser med fast layout är det sätt på vilket de är kopplade till ohanterad kod. Värdetyper skickas efter värde (på stacken) och därför är eventuella ändringar som görs på typens medlemmar av den som anropas, inte synliga för den som anropar. Referenstyper skickas med referens (en referens till typen skickas på stacken); därför kommer alla ändringar som görs av en anropad metod på medlemmar av en blittable-typ att ses av den som anropat metoden.

Anmärkning

Om en referenstyp har medlemmar av icke-blittable-typer krävs konvertering två gånger: första gången ett argument skickas till den ohanterade sidan och andra gången vid retur från anropet. På grund av den extra omkostnad måste in-/ut-parametrarna explicit appliceras på ett argument om anroparen vill se ändringar som gjorts av anropade.

I det följande exemplet har klassen SystemTime sekventiell medlemslayout och kan skickas till Windows API-funktionen 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;
}

Funktionen GetSystemTime definieras på följande sätt:

void GetSystemTime(SYSTEMTIME* SystemTime);

Den motsvarande plattformsanropsdefinitionen för GetSystemTime är följande:

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

Observera att SystemTime argumentet inte skrivs som ett referensargument eftersom SystemTime det är en klass, inte en värdetyp. Till skillnad från värdetyper skickas klasser alltid med referens.

I följande kodexempel visas en annan Point klass som har en metod som heter SetXY. Eftersom typen har sekventiell layout kan den skickas till ohanterad kod och ordnas som en struktur. Medlemmen är dock inte anropbar från ohanterad kod, även om objektet skickas som referens.

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

Värdetyper som används i COM Interop

Formaterade typer kan också skickas till COM interop-metodanrop. När de exporteras till ett typbibliotek konverteras faktiskt värdetyper automatiskt till strukturer. Som följande exempel visar Point blir värdetypen en typdefinition (typedef) med namnet Point. Alla referenser till Point värdetypen någon annanstans i typbiblioteket ersätts med Point typedef.

Typbiblioteksrepresentation

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

Samma regler som används för att överföra värden och referenser till plattformsanrop används vid marshalling genom COM-gränssnitten. När till exempel en instans av Point värdetypen skickas från .NET Framework till COM, skickas Point som värde. Point Om värdetypen skickas med referens skickas en pekare till en Point på stacken. Interop-marshallern stöder inte högre indirekta nivåer (punkt **) i någon av riktningarna.

Anmärkning

Strukturer som har LayoutKind uppräkningsvärdet inställt Explicit på kan inte användas i COM-interop eftersom det exporterade typbiblioteket inte kan uttrycka en explicit layout.

Systemvärdetyper

Namnområdet System har flera värdetyper som representerar den inkapslade formen av de primitiva typerna i körmiljön. Till exempel representerar värdetypsstrukturen System.Int32 den rutade formen av ELEMENT_TYPE_I4. I stället för att ordna dessa typer som strukturer, som andra formaterade typer är, ordnar du dem på samma sätt som de primitiva typer de är instängda i. System.Int32 är därför ordnad som ELEMENT_TYPE_I4 i stället för som en struktur som innehåller en enda medlem av typen long. Följande tabell innehåller en lista över värdetyperna i namnområdet System som är boxade representationer av primitiva typer.

Systemvärdetyp Elementtyp
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

Vissa andra värdetyper i System namnområdet hanteras på olika sätt. Eftersom den ohanterade koden redan har väletablerade format för sådana typer har marshallern särskilda regler för att hantera dem. I följande tabell visas de särskilda värdetyperna i System-namnområdet samt den ohanterade typ som de marshallas till.

Systemvärdetyp IDL-typ
System.DateTime DATUM
System.Decimal Decimal
System.Guid GUID
System.Drawing.Color OLE_COLOR

Följande kod visar definitionen av de ohanterade typerna DATE, GUID, DECIMAL och OLE_COLOR i stdole2-typbiblioteket.

Skriv biblioteksrepresentation

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;

Följande kod visar motsvarande definitioner i det hanterade IValueTypes gränssnittet.

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

Skriv biblioteksrepresentation

[…]
interface IValueTypes : IDispatch {
   HRESULT M1([in] DATE d);
   HRESULT M2([in] GUID d);
   HRESULT M3([in] DECIMAL d);
   HRESULT M4([in] OLE_COLOR d);
};

Se även