Megosztás a következőn keresztül:


Natív függvények meghívása felügyelt kódból

A közös nyelvi futtatókörnyezet platformhívási szolgáltatásokat vagy PInvoke-t biztosít, amelyek lehetővé teszik a felügyelt kód számára, hogy C-stílusú függvényeket hívjon meg natív dinamikusan csatolt kódtárakban (DLL-ekben). Ugyanazt az adatösszekapcsolást használják, mint a COM-interoperabilitáshoz és a "It Just Works" vagy IJW mechanizmushoz a futtatókörnyezettel.

További információkért lásd:

Az ebben a szakaszban szereplő minták csak azt szemléltetik, hogyan PInvoke használható. PInvoke egyszerűbbé teheti a testre szabott adatmegbírósítást, mert az eljárási marshaling-kód írása helyett deklaratív módon adja meg a marshaling-információkat az attribútumokban.

Megjegyzés:

A marshaling library alternatív módot kínál az adatok natív és felügyelt környezetek közötti, optimalizált módon történő felügyeletére. A marshaling könyvtárról további információt a C++-ban található marshaling áttekintésében talál. A marshaling library csak adatokhoz használható, függvényekhez nem.

A PInvoke és a DllImport attribútum

Az alábbi példa egy Visual C++ programban való használatát PInvoke mutatja be. A natív függvény puts a msvcrt.dll-ban van definiálva. A DllImport attribútum a kitételek deklarálásához használatos.

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

Az alábbi minta egyenértékű az előző mintával, de IJW-t használ.

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

Az IJW előnyei

  • A program által használt nem felügyelt API-khoz nem szükséges attribútumdeklarációkat írni DLLImport . Csak adja meg a fejlécfájlt, és csatolja az importálási kódtárat.

  • Az IJW-mechanizmus valamivel gyorsabb (például az IJW-csonkoknak nem kell ellenőriznie az adatelemek rögzítésének vagy másolásának szükségességét, mert ezt kifejezetten a fejlesztő végzi).

  • Jól szemlélteti a teljesítményproblémákat. Ebben az esetben, amikor Unicode-sztringről ANSI-sztringre alakít, ezzel memória foglalása és felszabadítása történik. Ebben az esetben a kódot IJW használatával író fejlesztő rájönne, hogy a _putws hívása és a PtrToStringChars használata jobb lenne a teljesítmény szempontjából.

  • Ha sok nem kezelt API-t hív meg ugyanazokkal az adatokkal, sokkal hatékonyabb az adatokat egyszer marshallozni és a marshallozott másolatot továbbítani, mint minden alkalommal újra marshallozni.

Az IJW hátrányai

  • A marshalingot explicit módon kell megadni a kódban az attribútumok helyett (amelyek gyakran rendelkeznek megfelelő alapértelmezett értékekkel).

  • A marshaló kód inline módon van elhelyezve, ahol invazívabb az alkalmazás logikájának folyamatában.

  • Mivel az explicit marshaling API-k IntPtr típusokat adnak vissza a 32 bites és 64 bites hordozhatóság érdekében, további ToPointer hívásokat kell használnia.

A C++ által közzétett konkrét módszer a hatékonyabb, explicitebb módszer, némi további összetettség árán.

Ha az alkalmazás főként nem felügyelt adattípusokat használ, vagy ha több nem felügyelt API-t hív meg, mint a .NET-keretrendszer API-kat, javasoljuk, hogy használja az IJW szolgáltatást. Ha egy többnyire felügyelt alkalmazásban időnként nem felügyelt API-t szeretne meghívni, a választás finomabb.

PInvoke Windows API-kkal

A PInvoke kényelmesen hívhat függvényeket a Windowsban.

Ebben a példában egy Visual C++ program együttműködik a Win32 API részét képező MessageBox függvénnyel.

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

A kimenet egy üzenetmező, amelynek a címe PInvoke Test, és tartalmazza a szöveget Hello World!.

A PInvoke a dll-függvények keresésére is használja a marshaling információkat. A user32.dll valójában nincs MessageBox függvény, de CharSet=CharSet::Ansi lehetővé teszi a PInvoke számára, hogy a MessageBoxW helyett az ANSI-verziót, a MessageBoxA-t használja, amely a Unicode-verzió. Általában azt javasoljuk, hogy a nem felügyelt API-k Unicode-verzióit használja, mivel ez kiküszöböli a fordítási többletterhelést a .NET Framework-sztringobjektumok natív Unicode-formátumából az ANSI-be.

Mikor ne használja a PInvoke-t?

A PInvoke használata nem megfelelő a DLL-ek összes C-stílusú függvényéhez. Tegyük fel például, hogy van egy MakeSpecial függvény mylib.dll a következőképpen deklarálva:

char * MakeSpecial(char * pszString);

Ha pInvoke-t használunk Egy Visual C++ alkalmazásban, az alábbihoz hasonlót írhatunk:

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

A probléma az, hogy nem törölhetjük a MakeSpecial által visszaadott nem felügyelt karakterlánc memóriáját. A PInvoke-on keresztül hívott egyéb függvények mutatót adnak vissza egy belső pufferhez, amelyet nem kell felszabadítani a felhasználónak. Ebben az esetben az IJW funkció használata nyilvánvaló választás.

A PInvoke korlátozásai

Nem lehet ugyanazt a pontos mutatót visszaadni egy natív függvényből, amelyet paraméterként vett. Ha egy natív függvény visszaadja a PInvoke által rögzített mutatót, memóriasérülés és kivételek léphetnek fel.

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

Az alábbi minta ezt a problémát mutatja, és bár a program úgy tűnik, hogy a megfelelő kimenetet adja, a kimenet a felszabadított memóriából származik.

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

Érvelések rendszerezése

Ezzel PInvokea módszerrel nincs szükség a felügyelt és a C++ natív primitív, azonos formátumú típusok közötti marsallálásra. Az Int32 és a Int között például nincs szükség marsallálásra, vagy dupla és dupla között.

Azonban olyan típusokat kell kezelnie, amelyek nem rendelkeznek ugyanazzal az alakkal. Ide tartoznak a karakter-, sztring- és szerkezettípusok. Az alábbi táblázat a marshaler által a különböző típusokhoz használt leképezéseket mutatja be:

wtypes.h Visual C++ Visual C++ és /clr Gyakori nyelvi futtatókörnyezet
FOGANTYÚ semmis* semmis* IntPtr, UIntPtr
BÁJT előjelnélküli karakter előjelnélküli karakter Bájt
RÖVID rövid rövid Int16
SZÓ előjel nélküli rövid egész előjel nélküli rövid egész UInt16
INT Int Int Int32
UINT aláíratlan int aláíratlan int UInt32
HOSSZÚ hosszú hosszú Int32
BOOL hosszú Bool Booleán
DWORD előjel nélküli hosszú egész előjel nélküli hosszú egész UInt32
ULONG előjel nélküli hosszú egész előjel nélküli hosszú egész UInt32
TAKARÍTÓ karakter karakter Karakter
LPSTR bejárónő* String ^ [in], StringBuilder ^ [in, out] String ^ [in], StringBuilder ^ [in, out]
LPCSTR const char * Karakterlánc^ Lánc
LPWSTR wchar_t * String ^ [in], StringBuilder ^ [in, out] String ^ [in], StringBuilder ^ [in, out]
LPCWSTR const wchar_t * Karakterlánc^ Lánc
ÚSZIK úsztat úsztat Egyedülálló
DUPLA dupla dupla Kétszer

A marshaler automatikusan rögzíti a futtatókörnyezeti halomra lefoglalt memóriát, ha a címet egy nem felügyelt függvénynek továbbítja. A rögzítés megakadályozza, hogy a szemétgyűjtő a tömörítés során áthelyezze a lefoglalt memóriablokkot.

A jelen témakörben korábban bemutatott példában a DllImport karakterkészlet paramétere határozza meg, hogyan kell a felügyelt sztringeket átirányítani; ebben az esetben az ANSI sztringekre kell őket átirányítani a natív oldal számára.

A MarshalAs attribútummal megadhatja a natív függvények egyes argumentumainak marshaling-információit. A String * argumentum összekapcsolásához több lehetőség áll rendelkezésre: BStr, ANSIBStr, TBStr, LPStr, LPWStr és LPTStr. Az alapértelmezett érték az LPStr.

Ebben a példában a sztringet kétbájtos Unicode-karaktersztringként (LPWStr) helyezik el. A kimenet a Hello World első betűje! mivel a marsallt sztring második bájtja null, és a "puts" ezt a karakterlánc végének jelölőjeként értelmezi.

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

A MarshalAs attribútum a System::Runtime::InteropServices névtérben található. Az attribútum más adattípusokkal, például tömbökkel is használható.

Ahogy a témakör korábbi részében is említettük, a marshaling library egy új, optimalizált módszert biztosít az adatok natív és felügyelt környezetek közötti felügyeletére. További információ: A C++-beli marshaling áttekintése.

Teljesítménnyel kapcsolatos szempontok

A PInvoke többletterhelése hívásonként 10 és 30 x86 utasítás között van. A rögzített költség mellett a rendezés további többletterhelést okoz. A felügyelt és a nem felügyelt kódban azonos reprezentációval rendelkező blittábilis típusok között nincs adat-átalakítási költség. Az int és az Int32 közötti fordításnak például nincs költsége.

A jobb teljesítmény érdekében kevesebb olyan PInvoke-hívással rendelkezzen, amely a lehető legtöbb adatot használja, ahelyett, hogy több hívással kevesebb adatot fogadnál hívásonként.

Lásd még

Natív és .NET-együttműködés