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 MethodOne
aan, 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:
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.
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.
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);
};