Dela via


Anropa interna funktioner från hanterad kod

Den vanliga språkkörningen tillhandahåller Plattformsanropstjänster, eller PInvoke, som gör det möjligt för hanterad kod att anropa C-formatfunktioner i interna dynamiska länkade bibliotek (DLL:er). Samma datakorsning används som för COM-interoperabilitet med körtiden och för mekanismen "It Just Works" eller IJW.

Mer information finns i:

Exemplen i det här avsnittet visar bara hur PInvoke kan användas. PInvoke kan förenkla anpassad dataöverföring eftersom du tillhandahåller deklarativ marskalkreringsinformation genom attribut i stället för att skriva kod för procedurmässig dataöverföring.

Anmärkning

Marshaling-biblioteket är ett alternativt sätt att samla in data mellan interna och hanterade miljöer på ett optimerat sätt. För mer information om marskalkningsbiblioteket, se Översikt över marskalkning i C++. Marshaling-biblioteket kan endast användas för data och inte för funktioner.

PInvoke och attributet DllImport

I följande exempel visas användningen av PInvoke i ett Visual C++-program. Den inbyggda funktionen puts definieras i msvcrt.dll. Attributet DllImport används för deklarationen av 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);
}

Följande exempel motsvarar föregående exempel, men använder 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);
}

Fördelar med IJW

  • Du behöver inte skriva DLLImport attributdeklarationer för de ohanterade API:er som programmet använder. Inkludera bara huvudfilen och länken med importbiblioteket.

  • IJW-mekanismen är något snabbare (till exempel behöver IJW-stubs inte kontrollera behovet av att fästa eller kopiera dataobjekt eftersom det görs uttryckligen av utvecklaren).

  • Det illustrerar tydligt prestandaproblem. I det här fallet är det faktum att du översätter från en Unicode-sträng till en ANSI-sträng och att du har en hantering av minnesallokering och frigöring. I det här fallet skulle en utvecklare som skriver koden med IJW inse att anrop _putws och användning PtrToStringChars skulle vara bättre för prestanda.

  • Om du anropar många ohanterade API:er med samma data är det mycket effektivare att konvertera dem en gång och skicka den konverterade kopian än att ommarsera varje gång.

Nackdelar med IJW

  • Marshaling måste anges uttryckligen i kod i stället för med attribut (som ofta har lämpliga standardvärden).

  • Koden för marskalkering är infogad, där den är mer invasiv i flödet av programlogik.

  • Eftersom explicita API:er för marshall returnerar IntPtr-typer för portabilitet mellan 32-bitars och 64-bitars system måste du använda ytterligare ToPointer-anrop.

Den specifika metod som exponeras av C++ är den mer effektiva, explicita metoden, på bekostnad av ytterligare komplexitet.

Om programmet huvudsakligen använder ohanterade datatyper eller om det anropar fler ohanterade API:er än .NET Framework-API:er rekommenderar vi att du använder IJW-funktionen. Om du vill anropa ett tillfälligt ohanterat API i ett mestadels hanterat program, är beslutet mer eftertänksamt.

PInvoke med Windows-API:er

PInvoke är praktiskt för att anropa funktioner i Windows.

I det här exemplet interoperates ett Visual C++-program med funktionen MessageBox som ingår i Win32-API:et.

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

Utdata är en meddelanderuta som har rubriken PInvoke Test och innehåller texten Hello World!.

Marskalkeringsinformationen används också av PInvoke för att söka efter funktioner i DLL-filen. I user32.dll finns det i själva verket ingen MessageBox-funktion, men CharSet=CharSet::Ansi gör det möjligt för PInvoke att använda MessageBoxA, ANSI-versionen, i stället för MessageBoxW, som är Unicode-versionen. I allmänhet rekommenderar vi att du använder Unicode-versioner av ohanterade API:er eftersom det eliminerar översättningskostnaderna från det interna Unicode-formatet för .NET Framework-strängobjekt till ANSI.

När du inte ska använda PInvoke

Att använda PInvoke är inte lämpligt för alla C-formatfunktioner i DLL:er. Anta till exempel att det finns en funktion MakeSpecial i mylib.dll deklarerad på följande sätt:

char * MakeSpecial(char * pszString);

Om vi använder PInvoke i ett Visual C++-program kan vi skriva något som liknar följande:

[DllImport("mylib")]
extern "C" String * MakeSpecial([MarshalAs(UnmanagedType::LPStr)] String ^);

Svårigheten här är att vi inte kan ta bort minnet för den ohanterade sträng som returneras av MakeSpecial. Andra funktioner som anropas via PInvoke returnerar en pekare till en intern buffert som inte behöver frigöras av användaren. I det här fallet är det självklara valet att använda IJW-funktionen.

Begränsningar för PInvoke

Du kan inte returnera samma exakta pekare från en intern funktion som du tog som parameter. Om en intern funktion returnerar pekaren som har konverterats till den av PInvoke kan minnesfel och undantag uppstå.

__declspec(dllexport)
char* fstringA(char* param) {
   return param;
}

Följande exempel uppvisar det här problemet, och även om programmet kan verka ge rätt utdata, kommer utdata från minnet som hade frigjorts.

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

Argument för marskalkering

Med PInvokebehövs ingen marshaling mellan hanterade och C++-inbyggda primitiva typer med samma formulär. Till exempel krävs ingen marshaling mellan Int32 och int eller mellan Double och double.

Du måste dock hantera typer som inte har samma form. Detta inkluderar char-, sträng- och strukturella typer. I följande tabell visas de mappningar som används av marshallern för olika typer.

wtypes.h Visual C++ Visual C++ med /clr Körning av vanligt språk
HANDTAG tomrum* tomrum* IntPtr, UIntPtr
byte osignerat tecken osignerat tecken byte
KORT kort kort Int16
ORD osignerad kort osignerad kort UInt16
INT Int Int Int32
UINT osignerad int osignerad int UInt32
LÅNG lång lång Int32
BOOL lång Bool Boolesk
DWORD lång osignerad lång osignerad UInt32
ULONG lång osignerad lång osignerad UInt32
RÖDING röding röding Öring
LPSTR röding* Sträng ^ [in], StringBuilder ^ [in, ut] Sträng ^ [in], StringBuilder ^ [in, ut]
LPCSTR const char * Sträng^ Sträng
LPWSTR wchar_t * Sträng ^ [in], StringBuilder ^ [in, ut] Sträng ^ [in], StringBuilder ^ [in, ut]
LPCWSTR const wchar_t * Sträng^ Sträng
FLYT flyta/sväva flyta/sväva Singel
Dubbel dubbel dubbel dubbel

Marshaler låser automatiskt minne som allokerats på körningshögen om dess adress skickas till en ohanterad funktion. Genom att fästa hindrar du skräpinsamlaren från att flytta det allokerade minnesblocket under komprimering.

I exemplet som visades tidigare i det här avsnittet anger parametern CharSet för DllImport hur hanterade strängar ska konverteras. I det här fallet bör de konverteras till ANSI-strängar för den interna sidan.

Du kan ange marshaling-information för enskilda argument för en intern funktion med hjälp av attributet MarshalAs. Det finns flera alternativ för att konvertera argumentet String * : BStr, ANSIBStr, TBStr, LPStr, LPWStr och LPTStr. Standardvärdet är LPStr.

I det här exemplet hanteras strängen som en dubbelbyte Unicode-teckensträng, LPWStr. Utdata är den första bokstaven i Hello World! eftersom den andra byten för den marshalled strängen är null och funktionen puts tolkar detta som en slutmarkör för strängen.

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

Attributet MarshalAs finns i namnområdet System::Runtime::InteropServices. Attributet kan användas med andra datatyper, till exempel matriser.

Som tidigare nämnts i avsnittet tillhandahåller marshaling-biblioteket en ny optimerad metod för att konvertera data mellan interna och hanterade miljöer. Mer information finns i Översikt över marskalkning i C++.

Prestandaöverväganden

PInvoke har en overhead på mellan 10 och 30 x86 instruktioner per samtal. Förutom den här fasta kostnaden skapar marshaling ytterligare omkostnader. Det finns ingen marshaling-kostnad mellan blittable-typer som har samma representation i hanterad och ohanterad kod. Det finns till exempel ingen kostnad för att översätta mellan int och Int32.

För bättre prestanda kan du ha färre PInvoke-anrop som samlar in så mycket data som möjligt, i stället för fler anrop som samlar in mindre data per anrop.

Se även

Native och .NET-interoperabilitet