Dela via


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.

Kommentar

Marshalling av generiska typer stöds inte. Mer information finns i Interoperating Using Generic Types (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 plattform som anropar en prototyp ersätter du varje BSTR-typ med en String typ och anropar MethodOne, den vanliga språkkörningen försöker frigöra b två gånger. Du kan ändra marshallingbeteendet med hjälp IntPtr av typer i stället för Strängtyper .

Körningen använder alltid Metoden CoTaskMemFree i Windows och den kostnadsfria metoden på andra plattformar för att frigöra minne. Om minnet du arbetar med inte allokerades med metoden CoTaskMemAlloc i Windows eller malloc på andra plattformar måste du använda en 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 bör frigöras, till exempel när du använder funktionen GetCommandLine från Kernel32.dll, som returnerar en pekare till kernelminnet. Mer information om hur du frigör minne manuellt finns i buffertexemplet.

Standard marshalling för klasser

Klasser kan endast ordnas med COM-interop och är alltid marshalled som gränssnitt. I vissa fall kallas det gränssnitt som används för att konvertera klassen 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. I stället skickas vanligtvis ett standardgränssnitt i stället för samklassen.

När ett gränssnitt skickas till hanterad kod ansvarar interop-marshallern för att omsluta gränssnittet med rätt omslutning och skicka omslutningen till den hanterade metoden. Det kan vara svårt att avgöra vilken omslutning 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. Samma omslutning 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 dess livslängd måste interop-marshallern identifiera rätt omslutning första gången ett gränssnitt som exponeras av objektet skickas genom marshallern. Marshaller identifierar objektet genom att titta på ett av de gränssnitt som objektet implementerar.

Till exempel avgör marshallern att klassomslutningen ska användas för att omsluta gränssnittet 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 för 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 marshaller hitta omslutningen från registret om sammansättningen tidigare har registrerats.

  2. Marshaller frågar gränssnittet för gränssnittet IProvideClassInfo . Om det tillhandahålls använder marshallern ITypeInfo som returneras från IProvideClassInfo.GetClassinfo för att fastställa CLSID för klassen som exponerar 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 ombud

Ett hanterat ombud ordnas som ett COM-gränssnitt eller som en funktionspekare baserat på anropsmekanismen:

  • För plattformsanrop ordnas ett ombud som en ohanterad funktionspekare som standard.

  • För COM-interop ordnas ett ombud som ett COM-gränssnitt av typen _Delegate som standard. Det _Delegate gränssnittet definieras i mscorlib.tlb-typbiblioteket och innehåller Delegate.DynamicInvoke metoden, som gör att du kan anropa den metod 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 ombud som markerats med ref-nyckelordet (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 de delegerade typerna ordnas int representerar det här en pekare till ett tomrum (void*), som är adressen till ombudet 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.

Kommentar

En referens till funktionspekaren till ett hanterat ombud som innehas av ohanterad kod hindrar inte den vanliga språkkörningen 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 är skräpinsamling ä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 marshallingbeteende.

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

    Anger att den vanliga språkkörningen är fri att ordna om medlemmarna av typen för 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

    Anger att medlemmarna är angivna enligt de angivna fälten FieldOffsetAttribute .

Värdetyper som används i plattformsanrop

I följande exempel ger typerna Point och Rect information om medlemslayout med hjälp av 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 funktionen PtInRect för Microsoft Windows API 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 skickas med referens eftersom det ohanterade API:et förväntar sig att RECT en pekare skickas till funktionen. Värdetypen Point skickas av värdet eftersom det ohanterade API:et POINT förväntar sig att skickas på stacken. Denna subtila skillnad är mycket viktig. Referenser skickas till ohanterad kod som pekare. Värden skickas till ohanterad kod i stacken.

Kommentar

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 visas inte eventuella ändringar som görs av medlemmarna av typen av anroparen av anroparen. Referenstyper skickas med referens (en referens till typen skickas på stacken); Därför ses alla ändringar som görs av blittable-typmedlemmar av en typ av anroparen av anroparen av anroparen.

Kommentar

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 detta extra omkostnader måste in-/ut-parametrar uttryckligen tillämpas på ett argument om anroparen vill se ändringar som görs av anroparen.

I följande exempel SystemTime har klassen sekventiell medlemslayout och kan skickas till funktionen GetSystemTime för Windows API.

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

Motsvarande plattformsanropsdefinition 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 kan dock SetXY inte anropas från ohanterad kod, även om objektet skickas med 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.

Skriv biblioteksrepresentation

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 konvertera värden och referenser till plattformsanrop används vid marshalling via COM-gränssnitt. När till exempel en instans av Point värdetypen skickas från .NET Framework till COM Point skickas värdet av värdet. 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.

Kommentar

Strukturer som har LayoutKind uppräkningsvärdet inställt på Explicit 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 rutade formen av de primitiva körningstyperna. 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, kan du konvertera dem på samma sätt som de primitiva typer som de boxas. 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 systemnamnområdet 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 systemnamnområdet hanteras på olika sätt. Eftersom den ohanterade koden redan har väletablerade format för dessa typer har marshallern särskilda regler för att ordna dem. I följande tabell visas de särskilda värdetyperna i systemnamnområdet samt den ohanterade typ som de är uppdelade till.

Systemvärdetyp IDL-typ
System.DateTime DATE
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