Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
De Common Language Runtime biedt Platform Invocation Services, of PInvoke, waarmee beheerde code C-functies kan aanroepen in native dynamisch gekoppelde bibliotheken (DLL's). Dezelfde data marshaling wordt gebruikt als voor COM-interoperabiliteit met de runtime en voor het mechanisme 'It Just Works', of IJW.
Voor meer informatie, zie:
In de voorbeelden in deze sectie wordt geïllustreerd hoe PInvoke kan worden gebruikt.
PInvoke kan aangepaste data marshaling vereenvoudigen omdat u marshalinginformatie declaratief in attributen verstrekt in plaats van procedurele marshalingcode te schrijven.
Opmerking
De marshaling-bibliotheek biedt een alternatieve manier om gegevens te marshalen tussen systeemeigen en beheerde omgevingen op een geoptimaliseerde wijze. Zie Overzicht van Marshaling in C++ voor meer informatie over de marshaling-bibliotheek. De marshaling-bibliotheek is alleen bruikbaar voor gegevens en niet voor functies.
PInvoke en het kenmerk DllImport
In het volgende voorbeeld ziet u het gebruik van PInvoke in een Visual C++-programma. De systeemeigen functie puts wordt gedefinieerd in msvcrt.dll. Het kenmerk DllImport wordt gebruikt voor de declaratie van puts.
// platform_invocation_services.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;
[DllImport("msvcrt", CharSet=CharSet::Ansi)]
extern "C" int puts(String ^);
int main() {
String ^ pStr = "Hello World!";
puts(pStr);
}
Het volgende voorbeeld is gelijk aan het vorige voorbeeld, maar gebruikt IJW.
// platform_invocation_services_2.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;
#include <stdio.h>
int main() {
String ^ pStr = "Hello World!";
char* pChars = (char*)Marshal::StringToHGlobalAnsi(pStr).ToPointer();
puts(pChars);
Marshal::FreeHGlobal((IntPtr)pChars);
}
Voordelen van IJW
Het is niet nodig om kenmerkdeclaraties te schrijven
DLLImportvoor de niet-beheerde API's die het programma gebruikt. Neem alleen het headerbestand op en koppel deze aan de importbibliotheek.Het IJW-mechanisme is iets sneller (de IJW-stubs hoeven bijvoorbeeld niet te controleren of er gegevensitems moeten worden vastgemaakt of gekopieerd, omdat dat expliciet door de ontwikkelaar wordt gedaan).
Het illustreert duidelijk prestatieproblemen. In dit geval is het van belang dat u vertaalt van een Unicode-tekenreeks naar een ANSI-tekenreeks en dat u een bijbehorende geheugentoewijzing en -deallocatie hebt. In dit geval realiseert een ontwikkelaar die de code schrijft met behulp van IJW dat bellen
_putwsen gebruikenPtrToStringCharsbeter is voor prestaties.Als u veel onbeheerde API's aanroept met dezelfde gegevens, is het efficiënter om ze eenmaal te serialiseren en de geserialiseerde kopie door te geven dan ze telkens opnieuw te serialiseren.
Nadelen van IJW
Marshaling moet expliciet worden opgegeven in code in plaats van door attributen (die vaak gepaste standaardwaarden hebben).
De marshaling-code is inline, waar deze binnen de toepassingslogica ingrijpender is.
Omdat de expliciete marshaling-API's typen retourneren
IntPtrvoor 32-bits naar 64-bits overdraagbaarheid, moet u extraToPointeraanroepen gebruiken.
De specifieke methode die door C++ wordt weergegeven, is de efficiëntere, expliciete methode, ten koste van extra complexiteit.
Als de toepassing voornamelijk onbeheerde gegevenstypen gebruikt of als deze meer niet-beheerde API's aanroept dan .NET Framework-API's, raden we u aan de functie IJW te gebruiken. Als u een incidentele onbeheerde API wilt aanroepen in een voornamelijk beheerde toepassing, is de keuze subtieler.
PInvoke met Windows-API's
PInvoke is handig voor het aanroepen van functies in Windows.
In dit voorbeeld werkt een Visual C++-programma samen met de functie MessageBox die deel uitmaakt van de Win32-API.
// platform_invocation_services_4.cpp
// compile with: /clr /c
using namespace System;
using namespace System::Runtime::InteropServices;
typedef void* HWND;
[DllImport("user32", CharSet=CharSet::Ansi)]
extern "C" int MessageBox(HWND hWnd, String ^ pText, String ^ pCaption, unsigned int uType);
int main() {
String ^ pText = "Hello World! ";
String ^ pCaption = "PInvoke Test";
MessageBox(0, pText, pCaption, 0);
}
De uitvoer is een berichtvak met de titel PInvoke Test en bevat de tekst Hallo wereld!.
De marshaling informatie wordt ook gebruikt door PInvoke om functies in het DLL-bestand op te zoeken. In user32.dll er in feite geen functie MessageBox is, maar CharSet=CharSet::Ansi stelt PInvoke in staat om MessageBoxA, de ANSI-versie, te gebruiken in plaats van MessageBoxW, wat de Unicode-versie is. Over het algemeen raden we u aan Unicode-versies van niet-beheerde API's te gebruiken, omdat hierdoor de vertaaloverhead van de systeemeigen Unicode-indeling van .NET Framework-tekenreeksobjecten naar ANSI wordt geëlimineerd.
Wanneer niet PInvoke gebruiken
Het gebruik van PInvoke is niet geschikt voor alle C-stijlfuncties in DLL's. Stel dat er een functie MakeSpecial is in mylib.dll als volgt gedeclareerd:
char * MakeSpecial(char * pszString);
Als we PInvoke gebruiken in een Visual C++-toepassing, kunnen we iets schrijven dat er ongeveer als volgt uitziet:
[DllImport("mylib")]
extern "C" String * MakeSpecial([MarshalAs(UnmanagedType::LPStr)] String ^);
Het probleem hier is dat we het geheugen voor de onbeheerde tekenreeks die door MakeSpecial wordt geretourneerd, niet kunnen verwijderen. Andere functies die via PInvoke worden aangeroepen, retourneren een aanwijzer naar een interne buffer die niet door de gebruiker hoeft te worden toegewezen. In dit geval is het gebruik van de functie IJW de voor de hand liggende keuze.
Beperkingen van PInvoke
U kunt dezelfde exacte aanwijzer niet retourneren vanuit een systeemeigen functie die u als parameter hebt gebruikt. Als een native functie de aanwijzer retourneert die door PInvoke is gemarshaled, kunnen geheugenbeschadiging en uitzonderingen optreden.
__declspec(dllexport)
char* fstringA(char* param) {
return param;
}
Het volgende voorbeeld vertoont dit probleem en hoewel het programma de juiste uitvoer lijkt te geven, komt de uitvoer uit het geheugen dat is vrijgemaakt.
// platform_invocation_services_5.cpp
// compile with: /clr /c
using namespace System;
using namespace System::Runtime::InteropServices;
#include <limits.h>
ref struct MyPInvokeWrap {
public:
[ DllImport("user32.dll", EntryPoint = "CharLower", CharSet = CharSet::Ansi) ]
static String^ CharLower([In, Out] String ^);
};
int main() {
String ^ strout = "AabCc";
Console::WriteLine(strout);
strout = MyPInvokeWrap::CharLower(strout);
Console::WriteLine(strout);
}
MarshalingArgumenten
Met PInvoke is er geen marshaling nodig tussen managed en native C++ primitieve typen met dezelfde vorm. Er is bijvoorbeeld geen marshaling vereist tussen Int32 en int, of tussen Double en double.
U moet echter marshaltypen hebben die niet hetzelfde formulier hebben. Dit omvat teken-, tekenreeks- en structuurtypen. De volgende tabel toont de koppelingen die de marshaler voor verschillende typen gebruikt:
| wtypes.h | Visual C++ | Visual C++ met /clr | Algemene taalruntime |
|---|---|---|---|
| HANDVAT | leegte* | leegte* | IntPtr, UIntPtr |
| byte | teken zonder teken | teken zonder teken | Byte |
| KORT | kort | kort | Int16 |
| WOORD | ongesignaalde short | ongesignaalde short | UInt16 |
| INT | int (integer) | int (integer) | Int32 |
| UINT | niet-ondertekende int | niet-ondertekende int | UInt32 |
| LANG | lang | lang | Int32 |
| BOOL | lang | Bool | Booleaan |
| DWORD (een 32-bit geheel getal vaak gebruikt in programmeren) | ongetekend lang | ongetekend lang | UInt32 |
| ULONG | ongetekend lang | ongetekend lang | UInt32 |
| VERKOLEN | verkolen | verkolen | Verkolen |
| LPSTR | verkolen* | Tekenreeks ^ [in], StringBuilder ^ [in, uit] | Tekenreeks ^ [in], StringBuilder ^ [in, uit] |
| LPCSTR | const char * | Snaar^ | Snaar / Touwtje |
| LPWSTR | wchar_t * | Tekenreeks ^ [in], StringBuilder ^ [in, uit] | Tekenreeks ^ [in], StringBuilder ^ [in, uit] |
| LPCWSTR | const wchar_t * | Snaar^ | Snaar / Touwtje |
| Drijven | zweven | zweven | Enkel |
| Dubbel | dubbel | dubbel | Dubbel |
De marshaler maakt automatisch geheugen vast die is toegewezen aan de runtime-heap als het adres wordt doorgegeven aan een onbeheerde functie. Vastmaken voorkomt dat de garbagecollector het toegewezen geheugenblok tijdens compressie verplaatst.
In het voorbeeld dat eerder in dit onderwerp is weergegeven, geeft de parameter CharSet van DllImport aan hoe strings die worden beheerd moeten worden geïnstrueerd; in dit geval moeten ze worden geïnstrueerd naar ANSI-tekenreeksen voor de native omgeving.
U kunt marshaling-informatie opgeven voor afzonderlijke argumenten van een systeemeigen functie met behulp van het kenmerk MarshalAs. Er zijn verschillende opties voor marshaling van een tekenreeks * argument: BStr, ANSIBStr, TBStr, LPStr, LPWStr en LPTStr. De standaardwaarde is LPStr.
In dit voorbeeld wordt de tekenreeks geconverteerd naar een Unicode-tekenreeks met dubbele byte, LPWStr. De uitvoer is de eerste letter van Hello World! omdat de tweede byte van de verwerkende tekenreeks een nulbyte is en de functie 'puts' dit interpreteert als het einde van de tekenreeks.
// platform_invocation_services_3.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;
[DllImport("msvcrt", EntryPoint="puts")]
extern "C" int puts([MarshalAs(UnmanagedType::LPWStr)] String ^);
int main() {
String ^ pStr = "Hello World!";
puts(pStr);
}
Het kenmerk MarshalAs bevindt zich in de naamruimte System::Runtime::InteropServices. Het kenmerk kan worden gebruikt met andere gegevenstypen, zoals matrices.
Zoals eerder in het onderwerp is vermeld, biedt de marshaling-bibliotheek een nieuwe, geoptimaliseerde methode voor het marshalen van gegevens tussen systeemeigen en beheerde omgevingen. Zie Overzicht van Marshaling in C++voor meer informatie.
Prestatieoverwegingen
PInvoke heeft een overhead van 10 tot 30 x86 instructies per oproep. Naast deze vaste kosten zorgt marshaling voor extra overhead. Er zijn geen marshalingkosten tussen blittable-typen met dezelfde weergave in beheerde en onbeheerde code. Er zijn bijvoorbeeld geen kosten verbonden aan het vertalen tussen int en Int32.
Voor betere prestaties hebt u minder PInvoke-functieaanroepen die zoveel mogelijk gegevens verwerken, in plaats van meer aanroepen die per keer minder gegevens verwerken.