Delen 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 zoals transformaties van gegevenstypen, of een ontvanger 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.

Opmerking

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 veroorzaakt worden als u systeemeigen C++-gedrag verwacht (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 platform-invocat prototype, vervangt u elk BSTR type door een String type en roept u MethodOne aan, probeert de Common Language Runtime b tweemaal vrij te maken. U kunt het marshallgedrag wijzigen door IntPtr typen in plaats van String typen te gebruiken.

De runtime gebruikt altijd de CoTaskMemFree methode in Windows en free de methode op andere platforms om geheugen vrij te maken. Als het geheugen waarmee u werkt niet is toegewezen met de CoTaskMemAlloc methode in Windows of de malloc methode op andere platforms, moet u een IntPtr gebruiken en het geheugen handmatig vrijmaken met de juiste methode. Op dezelfde manier kunt u het automatisch vrijmaken van geheugen vermijden in situaties waarin geheugen nooit moet worden vrijgemaakt, zoals wanneer u de GetCommandLine functie uit 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 gemarshalled door COM-Interop en worden altijd gemarshalled als interfaces. In sommige gevallen wordt de interface die wordt gebruikt voor het marshalen van de klasse 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 doorsturen naar 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 de wrapper hetzelfde type gedurende zijn levensduur te laten behouden, moet de interop-marshaller de juiste wrapper identificeren wanneer een interface die door het object beschikbaar wordt gesteld voor het eerst via de marshaller wordt doorgegeven. 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 onthuld door beheerde objecten en kan de interface afstemmen op 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 IUnknown de 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 in het register vinden als de assembly eerder is geregistreerd.

  2. De marshaller vraagt de interface voor de IProvideClassInfo interface op. Indien opgegeven, gebruikt de marshaller de ITypeInfo die is geretourneerd door IProvideClassInfo.GetClassinfo om de CLSID van de class te bepalen die de interface blootstelt. 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 gemarshalled als COM-interface of als functieaanwijzer, op basis van het aanroepmechanisme.

  • Voor platformaanroepen wordt een delegate standaard verwerkt als een onbeheerd function pointer.

  • Voor COM-interoperabiliteit wordt een gemachtigde standaard aangeduid als een COM-interface van het type _Delegate . De _Delegate interface wordt gedefinieerd in de typebibliotheek Mscorlib.tlb en bevat de Delegate.DynamicInvoke methode, waarmee u de methode kunt aanroepen waarnaar de gemachtigde 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 van DelegateTestInterface worden geëxporteerd naar een COM-typebibliotheek. Merk op dat alleen delegates die zijn gemarkeerd met het ref trefwoord (of ByRef) worden doorgegeven als in- en 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 gedereferentieerd, net zoals elke andere niet-beheerde functieaanwijzer kan worden gedereferentieerd.

In dit voorbeeld, wanneer de twee gedelegeerden worden gemarshald als UnmanagedType.FunctionPtr, is het resultaat een int en een verwijzing naar een int. Omdat gedelegeerdentypen worden gemarshalled, vertegenwoordigt int hier een pointer naar void (void*), dit is het adres van de delegate 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.

Opmerking

Een verwijzing naar de functie-aanwijzer naar een beheerde gedelegeerde die wordt bewaard door onbeheerde code, verhindert niet dat de Common Language Runtime garbage-collection 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, cb niet langer in leven houdt dan de levensduur van de Test methode. Zodra het cb object is opgehaald door de vuilnisopruimer, 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 garbage collection 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 kommagetallen, zijn blittable en vereisen geen marshalling. Andere niet-blittable typen hebben verschillende representaties in beheerd en onbeheerd geheugen en vereisen marshalling. Voor andere typen is expliciete opmaak vereist over de interoperatiegrens heen.

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 de member wordt verstrekt met behulp van het StructLayoutAttribute kenmerk. De indeling kan een van de volgende LayoutKind opsommingswaarden zijn:

  • LayoutKind.Auto

    Geeft aan dat de gemeenschappelijke taalruntime de vrijheid heeft 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 ordenen 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 geordend volgens de FieldOffsetAttribute bij elk veld opgegeven specificaties.

Waardetypen die worden gebruikt in Platform Invoke

In het volgende voorbeeld geven de Point- en Rect-typen informatie over de indeling van leden met behulp van 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 gemarshalled naar onbeheerde code, worden deze geformatteerde typen gemarshalled als C-stijl structuren. Dit biedt een eenvoudige manier om een niet-beheerde API aan te roepen die structuurargumenten heeft. Structuren zoals POINT en RECT kunnen bijvoorbeeld als volgt worden doorgegeven aan de Microsoft Windows API-functie PtInRect:

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

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

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 als referentie worden doorgegeven, omdat de niet-beheerde API verwacht dat er een pointer naar RECT aan de functie wordt doorgegeven. Het Point waardetype wordt per waarde doorgegeven omdat de niet-geëxecuteerde API verwacht dat de POINT op de stack wordt geplaatst. Dit subtiele verschil is erg belangrijk. Verwijzingen worden als aanwijzers doorgegeven aan niet-beheerde code. Waarden worden doorgegeven aan onbeheerde code op de stack.

Opmerking

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 gemarshalled naar niet-beheerde code als C-structuren, mits ze een vaste ledenindeling 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 op de stack geplaatst); als gevolg daarvan zijn alle wijzigingen die door de ontvanger aan de blittable-type leden van een type worden aangebracht, zichtbaar voor de oproeper.

Opmerking

Als een referentietype leden heeft van niet-blittable typen, is conversie twee keer vereist: de eerste keer dat een argument wordt doorgegeven aan de niet-beheerde kant en de tweede keer bij de retour van het aanroepen. 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 Windows API-functie 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 GetSystemTime functie is als volgt gedefinieerd:

void GetSystemTime(SYSTEMTIME* SystemTime);

De equivalente aanroepdefinitie 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 aan niet-beheerde code worden doorgegeven en gemarshalled 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-interoperabiliteitsmethodes. 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.

Typebibliotheekweergave

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 het marshallen van waardes en verwijzingen bij platformaanroepen worden ook toegepast bij het marshallen door middel van COM-interfaces. Wanneer bijvoorbeeld een exemplaar van het Point waardetype wordt doorgegeven van de .NET Framework naar COM, wordt de Point waarde als waarde doorgegeven. Als het Point-waardetype door verwijzing wordt doorgegeven, wordt een pointer naar een Point op de stack doorgegeven. De interop marshaller ondersteunt geen hogere niveaus van indirectie (Point **) in geen van beide richtingen.

Opmerking

Structuren waaraan de LayoutKind opsommingswaarde is ingesteld Explicit , kunnen niet worden gebruikt in COM-interop 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 structuur van het waardetype System.Int32 vertegenwoordigt bijvoorbeeld de vakkenvorm van ELEMENT_TYPE_I4. In plaats van deze typen te marshallen als structuren, zoals andere geformatteerde types, worden ze gemarshald op dezelfde manier als de primitieve typen die ze omvatten. System.Int32 wordt daarom als ELEMENT_TYPE_I4 verwerkt in plaats van als een structuur die één lid van het type long bevat. De volgende tabel bevat een lijst met de waardetypen in de System naamruimte die in een vak worden weergegeven van primitieve typen.

Systeemwaardetype 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 System naamruimte 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 System namespace, evenals het niet-beheerde type waarnaar ze worden geconverteerd.

Systeemwaardetype 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 stdole2-typebibliotheek.

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