Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
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
_putwshívása és aPtrToStringCharshaszná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
IntPtrtípusokat adnak vissza a 32 bites és 64 bites hordozhatóság érdekében, továbbiToPointerhí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.