Share via


Standaard marshallinggedrag

Interop marshalling werkt op regels die bepalen hoe gegevens die zijn gekoppeld aan methodeparameters zich gedragen wanneer deze worden doorgegeven tussen beheerd en onbeheerd geheugen. Deze ingebouwde regels bepalen dergelijke marshallactiviteiten als transformaties van gegevenstypen, of een beheerder gegevens kan wijzigen die eraan worden doorgegeven en die wijzigingen kan retourneren aan de beller, en onder welke omstandigheden de marshaller prestatieoptimalisaties biedt.

In deze sectie worden de standaardgedragskenmerken van de interop marshallingservice geïdentificeerd. Het bevat gedetailleerde informatie over marshalling matrices, Booleaanse typen, tekentypen, gemachtigden, klassen, objecten, tekenreeksen en structuren.

Notitie

Marshalling van algemene typen wordt niet ondersteund. Zie Werken met algemene typen voor meer informatie.

Geheugenbeheer met de interop marshaller

De interop marshaller probeert altijd geheugen vrij te maken dat is toegewezen door onbeheerde code. Dit gedrag voldoet aan com-regels voor geheugenbeheer, maar verschilt van de regels die systeemeigen C++ bepalen.

Verwarring kan optreden als u verwacht systeemeigen C++-gedrag (geen geheugen vrijmaken) bij het gebruik van platformaanroepen, waardoor automatisch geheugen vrijkomt voor aanwijzers. Als u bijvoorbeeld de volgende onbeheerde methode aanroept vanuit een C++-DLL, wordt er niet automatisch geheugen vrijgemaakt.

Onbeheerde handtekening

BSTR MethodOne (BSTR b) {  
     return b;  
}  

Als u echter de methode definieert als een prototype voor het aanroepen van een platform, vervangt u elk BSTR-type door een String type en roept u MethodOneaan, probeert de algemene taalruntime tweemaal vrij te maken b . U kunt het marshallgedrag wijzigen met behulp van typen in plaats van IntPtr tekenreekstypen.

De runtime maakt altijd gebruik van de CoTaskMemFree-methode in Windows en de gratis methode op andere platforms om geheugen vrij te maken. Als het geheugen waarmee u werkt niet is toegewezen met de methode CoTaskMemAlloc in Windows of malloc op andere platforms, moet u een IntPtr gebruiken en het geheugen handmatig vrij maken met behulp van de juiste methode. Op dezelfde manier kunt u automatische geheugenvrijgeving voorkomen in situaties waarin het geheugen nooit vrij mag worden gemaakt, zoals wanneer u de functie GetCommandLine van Kernel32.dll gebruikt, die een aanwijzer naar kernelgeheugen retourneert. Zie het voorbeeld buffers voor meer informatie over het handmatig vrijmaken van geheugen.

Standaard marshalling voor klassen

Klassen kunnen alleen worden ge marshalld door COM-interop en worden altijd marshalled als interfaces. In sommige gevallen wordt de interface die wordt gebruikt om de klasse te marshalen, de klasse-interface genoemd. Zie Inleiding tot de klasse-interface voor informatie over het overschrijven van de klasse-interface met een interface van uw keuze.

Klassen doorgeven aan COM

Wanneer een beheerde klasse wordt doorgegeven aan COM, verpakt de interop marshaller de klasse automatisch met een COM-proxy en geeft de klasse-interface die door de proxy wordt geproduceerd door aan de COM-methode-aanroep. De proxy delegeert vervolgens alle aanroepen van de klasse-interface terug naar het beheerde object. De proxy maakt ook andere interfaces beschikbaar die niet expliciet door de klasse zijn geïmplementeerd. De proxy implementeert automatisch interfaces zoals IUnknown en IDispatch namens de klasse.

Klassen doorgeven aan .NET-code

Coklassen worden doorgaans niet gebruikt als methodeargumenten in COM. In plaats daarvan wordt meestal een standaardinterface doorgegeven in plaats van de coklasse.

Wanneer een interface wordt doorgegeven aan beheerde code, is de interop marshaller verantwoordelijk voor het verpakken van de interface met de juiste wrapper en het doorgeven van de wrapper aan de beheerde methode. Het kan lastig zijn om te bepalen welke wrapper moet worden gebruikt. Elk exemplaar van een COM-object heeft één unieke wrapper, ongeacht hoeveel interfaces het object implementeert. Eén COM-object dat vijf afzonderlijke interfaces implementeert, heeft bijvoorbeeld slechts één wrapper. Met dezelfde wrapper worden alle vijf de interfaces weergegeven. Als er twee exemplaren van het COM-object worden gemaakt, worden er twee exemplaren van de wrapper gemaakt.

Om hetzelfde type gedurende zijn levensduur te behouden, moet de interoperabiliteits marshaller de juiste wrapper identificeren wanneer een interface die door het object wordt weergegeven voor het eerst wordt doorgegeven via de marshaller. De marshaller identificeert het object door te kijken naar een van de interfaces die het object implementeert.

De marshaller bepaalt bijvoorbeeld dat de klasse-wrapper moet worden gebruikt om de interface te verpakken die is doorgegeven aan beheerde code. Wanneer de interface voor het eerst wordt doorgegeven door de marshaller, controleert de marshaller of de interface afkomstig is van een bekend object. Deze controle vindt plaats in twee situaties:

  • Een interface wordt geïmplementeerd door een ander beheerd object dat elders is doorgegeven aan COM. De marshaller kan gemakkelijk interfaces identificeren die worden weergegeven door beheerde objecten en kan overeenkomen met de interface met het beheerde object dat de implementatie biedt. Het beheerde object wordt vervolgens doorgegeven aan de methode en er is geen wrapper nodig.

  • Een object dat al is verpakt, implementeert de interface. Om te bepalen of dit het geval is, voert de marshaller een query uit op het object voor de IUnknown-interface en vergelijkt de geretourneerde interface met de interfaces van andere objecten die al zijn verpakt. Als de interface hetzelfde is als die van een andere wrapper, hebben de objecten dezelfde identiteit en wordt de bestaande wrapper doorgegeven aan de methode.

Als een interface niet afkomstig is van een bekend object, doet de marshaller het volgende:

  1. De marshaller vraagt het object op voor de IProvideClassInfo2-interface . Indien opgegeven, gebruikt de marshaller de CLSID die is geretourneerd van IProvideClassInfo2.GetGUID om de coklasse te identificeren die de interface levert. Met de CLSID kan de marshaller de wrapper uit het register vinden als de assembly eerder is geregistreerd.

  2. De marshaller voert een query uit op de interface voor de IProvideClassInfo-interface . Indien opgegeven, gebruikt de marshaller de ITypeInfo die is geretourneerd van IProvideClassInfo.GetClassinfo om de CLSID van de klasse te bepalen die de interface weergeeft. De marshaller kan de CLSID gebruiken om de metagegevens voor de wrapper te vinden.

  3. Als de marshaller de klasse nog steeds niet kan identificeren, wordt de interface verpakt met een algemene wrapperklasse met de naam System.__ComObject.

Standaard marshalling voor gemachtigden

Een beheerde gemachtigde wordt ge marshalld als com-interface of als functiepointer, op basis van het aanroepende mechanisme:

  • Voor het aanroepen van het platform wordt een gemachtigde standaard aangeduid als een onbeheerde functieaanwijzer.

  • Voor COM-interoperabiliteit wordt een gemachtigde standaard aangeduid als een COM-interface van het type _Delegate . De _Delegate-interface is gedefinieerd in de typebibliotheek Mscorlib.tlb en bevat de Delegate.DynamicInvoke methode, waarmee u de methode kunt aanroepen die de gedelegeerde verwijst.

In de volgende tabel ziet u de marshallopties voor het gegevenstype beheerde gemachtigde. Het MarshalAsAttribute kenmerk biedt verschillende UnmanagedType opsommingswaarden voor marshal delegates.

Opsommingstype Beschrijving van niet-beheerde indeling
UnmanagedType.FunctionPtr Een onbeheerde functieaanwijzer.
UnmanagedType.Interface Een interface van het type _Delegate, zoals gedefinieerd in Mscorlib.tlb.

Bekijk de volgende voorbeeldcode waarin de methoden worden DelegateTestInterface geëxporteerd naar een COM-typebibliotheek. U ziet dat alleen gemachtigden die zijn gemarkeerd met het trefwoord ref (of ByRef) worden doorgegeven als in/uit-parameters.

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

Weergave van typebibliotheek

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

Een functieaanwijzer kan worden gededucteerd, net zoals elke andere niet-beheerde functieaanwijzer kan worden gededucteerd.

In dit voorbeeld is het UnmanagedType.FunctionPtrresultaat een int en een verwijzing naar een int. Omdat gedelegeerdentypen worden ge marshalld, int vertegenwoordigt u hier een aanwijzer naar een ongeldige waarde (void*). Dit is het adres van de gemachtigde in het geheugen. Met andere woorden, dit resultaat is specifiek voor 32-bits Windows-systemen, omdat int hier de grootte van de functie aanwijzer vertegenwoordigt.

Notitie

Een verwijzing naar de functieverwijzing naar een beheerde gemachtigde die wordt bewaard door onbeheerde code verhindert niet dat de algemene taalruntime garbagecollection uitvoert op het beheerde object.

De volgende code is bijvoorbeeld onjuist omdat de verwijzing naar het cb object, die wordt doorgegeven aan de SetChangeHandler methode, niet cb langer leeft dan de levensduur van de Test methode. Zodra het cb object garbage is verzameld, is de doorgegeven SetChangeHandler functie-aanwijzer niet meer geldig.

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

Om onverwachte garbagecollection te compenseren, moet de aanroeper ervoor zorgen dat het cb object actief blijft zolang de onbeheerde functiepointer in gebruik is. U kunt de niet-beheerde code desgewenst laten weten wanneer de functie-aanwijzer niet meer nodig is, zoals in het volgende voorbeeld wordt weergegeven.

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

Standaard marshalling voor waardetypen

De meeste waardetypen, zoals gehele getallen en getallen met drijvende komma, zijn belicht en vereisen geen marshalling. Andere niet-belichte typen hebben verschillende representaties in beheerd en onbeheerd geheugen en vereisen marshalling. Voor andere typen is expliciete opmaak vereist voor de interoperatiegrens.

Deze sectie bevat informatie over de volgende opgemaakte waardetypen:

Naast het beschrijven van opgemaakte typen identificeert dit onderwerp Systeemwaardetypen die ongebruikelijk marshallgedrag hebben.

Een opgemaakt type is een complex type dat informatie bevat die expliciet de indeling van de leden in het geheugen bepaalt. De informatie over de indeling van het lid wordt verstrekt met behulp van het StructLayoutAttribute kenmerk. De indeling kan een van de volgende LayoutKind opsommingswaarden zijn:

  • LayoutKind.Auto

    Geeft aan dat de algemene taalruntime vrij is om de leden van het type opnieuw te ordenen voor efficiëntie. Wanneer een waardetype echter wordt doorgegeven aan onbeheerde code, is de indeling van de leden voorspelbaar. Een poging om een dergelijke structuur te marshal veroorzaakt automatisch een uitzondering.

  • LayoutKind.Sequentiële

    Geeft aan dat de leden van het type moeten worden ingedeeld in onbeheerd geheugen in dezelfde volgorde waarin ze worden weergegeven in de definitie van het beheerde type.

  • LayoutKind.Explicit

    Geeft aan dat de leden zijn ingedeeld volgens de FieldOffsetAttribute opgegeven velden.

Waardetypen die worden gebruikt in Platform Invoke

In het volgende voorbeeld bieden de en Rect typen informatie over de Point indeling van leden met behulp van de 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;  
}  

Wanneer marshall naar onbeheerde code, worden deze opgemaakte typen ge marshalld als C-stijl structuren. Dit biedt een eenvoudige manier om een niet-beheerde API aan te roepen die structuurargumenten heeft. De en RECT structuren kunnen bijvoorbeeld POINT als volgt worden doorgegeven aan de functie Microsoft Windows API PtInRect:

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

U kunt structuren doorgeven met behulp van de volgende definitie voor het aanroepen van het platform:

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

Het Rect waardetype moet worden doorgegeven via een verwijzing, omdat de niet-beheerde API verwacht dat een aanwijzer RECT wordt doorgegeven aan de functie. Het Point waardetype wordt doorgegeven door een waarde omdat de niet-beheerde API verwacht dat de POINT stack wordt doorgegeven. Dit subtiele verschil is erg belangrijk. Verwijzingen worden als aanwijzers doorgegeven aan niet-beheerde code. Waarden worden doorgegeven aan onbeheerde code op de stack.

Notitie

Wanneer een opgemaakt type als structuur wordt ingedeeld, zijn alleen de velden binnen het type toegankelijk. Als het type methoden, eigenschappen of gebeurtenissen bevat, zijn ze niet toegankelijk vanuit onbeheerde code.

Klassen kunnen ook worden ge marshalld naar onbeheerde code als C-stijl structuren, mits ze een vaste lidindeling hebben. De informatie over de indeling van leden voor een klasse wordt ook geleverd met het StructLayoutAttribute kenmerk. Het belangrijkste verschil tussen waardetypen met vaste indeling en klassen met vaste indeling is de manier waarop ze worden ge marshalld naar onbeheerde code. Waardetypen worden doorgegeven door waarde (op de stapel) en daarom worden wijzigingen die zijn aangebracht aan de leden van het type door de aanroeper, niet gezien door de aanroeper. Verwijzingstypen worden doorgegeven door verwijzing (een verwijzing naar het type wordt doorgegeven aan de stack); bijgevolg worden alle wijzigingen die zijn aangebracht aan de leden van het type van een type door de beller aangebracht, door de beller gezien.

Notitie

Als een verwijzingstype leden heeft van niet-belichte typen, is conversie twee keer vereist: de eerste keer dat een argument wordt doorgegeven aan de onbeheerde zijde en de tweede keer bij terugkeer vanuit de aanroep. Vanwege deze extra overhead moeten in/uit-parameters expliciet worden toegepast op een argument als de beller wijzigingen wil zien die door de aanroeper zijn aangebracht.

In het volgende voorbeeld heeft de klasse een SystemTime sequentiële lidindeling en kan deze worden doorgegeven aan de functie Windows API 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;
}  

De functie GetSystemTime wordt als volgt gedefinieerd:

void GetSystemTime(SYSTEMTIME* SystemTime);  

De equivalente definitie voor het aanroepen van het platform voor GetSystemTime is als volgt:

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

U ziet dat het SystemTime argument niet is getypt als verwijzingsargument omdat SystemTime het een klasse is, geen waardetype. In tegenstelling tot waardetypen worden klassen altijd doorgegeven door verwijzing.

In het volgende codevoorbeeld ziet u een andere Point klasse met een methode met de naam SetXY. Omdat het type een sequentiële indeling heeft, kan het worden doorgegeven aan niet-beheerde code en ge marshalld als een structuur. Het SetXY lid kan echter niet worden aangeroepen vanuit onbeheerde code, ook al wordt het object doorgegeven door verwijzing.

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

Waardetypen die worden gebruikt in COM Interop

Opgemaakte typen kunnen ook worden doorgegeven aan com-interoperabiliteitsmethode-aanroepen. Wanneer u naar een typebibliotheek exporteert, worden waardetypen automatisch geconverteerd naar structuren. Zoals in het volgende voorbeeld wordt weergegeven, wordt het Point waardetype een typedefinitie (typedef) met de naam Point. Alle verwijzingen naar het Point waardetype elders in de typebibliotheek worden vervangen door de Point typedef.

Weergave van typebibliotheek

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

Dezelfde regels die worden gebruikt voor marshal-waarden en verwijzingen naar aanroepen van platforms worden gebruikt bij het marshallen via COM-interfaces. Wanneer bijvoorbeeld een exemplaar van het Point waardetype wordt doorgegeven van .NET Framework aan COM, wordt de Point waarde doorgegeven. Als het Point waardetype wordt doorgegeven door verwijzing, wordt een aanwijzer naar een Point doorgegeven op de stack. De interop marshaller ondersteunt geen hogere niveaus van indirectie (punt **) in beide richtingen.

Notitie

Structuren die de LayoutKind opsommingswaarde hebben ingesteld op Expliciet , kunnen niet worden gebruikt in COM-interoperabiliteit omdat de geëxporteerde typebibliotheek geen expliciete indeling kan uitdrukken.

Systeemwaardetypen

De System naamruimte heeft verschillende waardetypen die de vakkenvorm van de primitieve runtimetypen vertegenwoordigen. De waardetypestructuur System.Int32 vertegenwoordigt bijvoorbeeld de vakkenvorm van ELEMENT_TYPE_I4. In plaats van deze typen te marshallen als structuren, zoals andere opgemaakte typen zijn, marshal je ze op dezelfde manier als de primitieve typen die ze boxen. System.Int32 wordt daarom als ELEMENT_TYPE_I4 in plaats van als een structuur die één lid van het type lang bevat. De volgende tabel bevat een lijst met de waardetypen in de systeemnaamruimte die in vakken worden weergegeven van primitieve typen.

Type systeemwaarde Elementtype
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

Sommige andere waardetypen in de systeemnaamruimte worden anders verwerkt. Omdat de niet-beheerde code al goed gedefinieerde indelingen voor deze typen heeft, heeft de marshaller speciale regels om ze te marshallen. De volgende tabel bevat de speciale waardetypen in de systeemnaamruimte , evenals het niet-beheerde type waarnaar ze zijn marshalled.

Type systeemwaarde IDL-type
System.DateTime DATUM
System.Decimal DECIMAAL
System.Guid GUID
System.Drawing.Color OLE_COLOR

De volgende code toont de definitie van de niet-beheerde typen DATE, GUID, DECIMAL en OLE_COLOR in de typebibliotheek Stdole2.

Weergave van typebibliotheek

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;  

De volgende code toont de bijbehorende definities in de beheerde IValueTypes interface.

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

Weergave van typebibliotheek

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

Zie ook