Volání nativní funkce ze spravovaného kódu
Modul CLR (Common Language Runtime) poskytuje platformu vyvolání služby, jinak PInvoke, což umožňuje spravovat kód napsaný v jazyce C u volaných funkcí s nativně propojenými dynamickými knihovnami (DLL). Stejné zařazování dat se používá také u vzájemné funkční spolupráce modelu COM s modulem runtime a pro mechanismus "Prostě to funguje" nebo mechanismus IJW.
Více informací naleznete:
Příklady v tomto oddíle ukazují jak lez použít PInvoke. Vzhledem k tomu, že PInvoke umí zjednodušit vlastní zařazování dat, které obsahuje zařazovací informace deklarativně v atributech namísto v ručně psaném kódu procesního zařazování.
Poznámka
Zařazovací knihovna poskytuje alternativní způsob optimálního zařazování dat mezi nativními a spravovanými prostředími. Další informace o zařazovací knihovně naleznete v tématu Overview of Marshaling in C++. Knihovna zařazování je použitelná pouze pro data a ne pro funkce.
PInvoke a atribut DllImport
Následující příklad ukazuje použití PInvoke v programu napsaném v aplikaci Visual C++. Nativní funkce vkládání je definována v DLL knihovně msvcrt.dll. Atribut DllImport se používá pro deklaraci vkládání.
// 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);
}
Následující příklad je ekvivalentní k předchozímu příkladu, s rozdílem použití 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);
}
Výhody systému souborů IJW
Není nutné deklarovat atribut DLLImport pro nespravované rozhraní API, které program používá. Obsahuje pouze hlavičku soubou a spojení na importovanou knihovnu.
Mechanismus IJW je mírně rychlejší (například stubs IJW nepotřebuje kontrolovat připnuté nebo kopírované datové položky, protože to explicitně provádí vývojář).
To jasně ukazuje problémy s výkonem. V případě, že provádíte převod Unicode řetězce na řetězec ANSI, je nutné mít obsluhu k přidělené a odebrané paměti. V tomto případě by vývojář psal kód s pomocí IJW a při volání _putws za pomocí PtrToStringChars by bylo dosaženo lepšího výkonu.
Pokud zavoláte mnoho neudržovaných API rozhraní, které používají stejná data, je provádění jednoho zařazování a jednoho předávání zařazené kopie mnohem efektivnější než provádění operace znovu zařazení.
Nevýhody systému souborů IJW
Zařazování musí být zadané explicitně přímo v kódu namísto pomocí atributů (které mají často vhodné výchozí nastavení).
Kód zařazování je vložený tam, kde je potřeba vetšího nároku na aplikační logiku.
Protože explicitní zařazování rozhraní API vrací IntPtr typy, které jsou přenositelné z 32 bitového na 64 bitový, tak musíte použít extra ToPointer volání.
Konkrétní metoda použitá v jazyce C++ je efektivnější, explicitní metoda, která má na druhou stranu některé další složitosti.
Pokud aplikace používá převážně nespravované datové typy nebo pokud více volá nespravované API rozhraní než rozhraní .NET Framework API, doporučujeme použití funkce IJW. Příležitostné volání nespravovaného API rozhraní ve většině spravovaných aplikacích, je vyjímečné.
PInvoke pomocí rozhraní API systému Windows
PInvoke je vhodné pro volání funkcí v systému Windows.
V tomto příkladu spolupracuje program Visual C++ s funkcí MessageBox, která je součástí rozhraní API systému Win32.
// 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);
}
Výstupem je okno se zprávou, které má název PInvoke Test a obsahuje text Hello World!.
Informace zařazování se také používá u PInvoke při vyhledávání funkcí v knihovně DLL. V DLL knihovně user32.dll ve skutečnosti není funkce MessageBox ale CharSet = CharSet::Ansi, který umožňuje PInvoke použití ANSI verze funkce s názve MessageBoxA místo Unicode verze funkce MessageBoxW. Obecně doporučujeme používat Unicode verze nespravovaného rozhraní API, to totiž eliminuje překlad řetězce z nativního Unicode formátu, používaného u rozhraní .NET Framework, na ANSI formát.
Kdy se nepoužívá PInvoke
PInvoke není vhodná pro všechny funkce jazyka C z knihoven DLL. Předpokládejme, že je funkce MakeSpecial v DLL knohovně mylib.dll deklarována takto:
char * MakeSpecial(char * pszString);
Pokud používáme PInvoke v aplikaci Visual C++, pak Vám doporučujeme psát následující:
[DllImport("mylib")]
extern "C" String * MakeSpecial([MarshalAs(UnmanagedType::LPStr)] String ^);
Zde je problémem to, že nemůžeme odstranit paměť pro nespravovaný řetězec vrácený funkcí MakeSpecial. Jiné funkce volané prostřednictvím PInvoke vrací ukazatel na vnitřní vyrovnávací paměť, která nemá být odebrána uživatelem. V tomto případe je použití funkce IJW jasnou volbou.
Omezení PInvoke
Nemůžete vracet stejný ukazatel, který je předáván nativní funkci jako parametr. Pokud nativní funkce vrátí ukazatel, který byl zařazen podle PInvoke, může následovat poškození paměti a výjimky.
__declspec(dllexport)
char* fstringA(char* param) {
return param;
}
Následující příklad ukazuje problém, kdy výstup přichází ze zdánlivě uvolněné paměti, ale přesto je zobrazovaný výstup programem správný.
// 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);
}
Argumenty zařazování
S PInvoke, není nutné žádné zařazování mezi spravovanými a primitivními nativními typy stejné formy jazyka C++. Například není požadováno žádné zařazování mezi Int32 a int nebo mezi Double a double.
Musí se však zařazovat typy, které nemají stejnou formu. Jedná se o typy char, string a struct. V následující tabulce jsou uvedena mapování, která používají zařazování pro různé typy:
wtypes.h |
Visual C++ |
Visual C++ s /clr |
Modul CLR (Common Language Runtime) |
---|---|---|---|
HANDLE |
void * |
void * |
IntPtr, UIntPtr |
BYTE |
unsigned char |
unsigned char |
Byte |
SHORT |
short |
short |
Int16 |
Word |
unsigned short |
unsigned short |
UInt16 |
INT |
int |
int |
Int32 |
UINT |
unsigned int |
unsigned int |
UInt32 |
LONG |
long |
long |
Int32 |
BOOL |
long |
bool |
Logická hodnota |
DWORD |
unsigned long |
unsigned long |
UInt32 |
ULONG |
unsigned long |
unsigned long |
UInt32 |
CHAR |
char |
char |
Char |
LPCSTR |
char * |
String ^ [in], StringBuilder ^ [in, out] |
String ^ [in], StringBuilder ^ [in, out] |
LPCSTR |
const char * |
String ^ |
řetězec |
LPWSTR |
wchar_t * |
String ^ [in], StringBuilder ^ [in, out] |
String ^ [in], StringBuilder ^ [in, out] |
LPCWSTR |
const wchar_t * |
String ^ |
řetězec |
FLOAT |
float |
float |
Jednoduché |
DOUBLE |
double |
double |
Double |
Zařazování automaticky přepíná paměť přidělenou na modul runtime haldu, pokud je adresa předáváná nespravované funkci. Přepínání zabraňuje systému uvolňování paměti při přesunutí přiděleného bloku paměti během komprimace.
V příkladu zmíněném výše v tomto tématu, určuje jak by měl být zařazen spravovaný datový typ String u CharSet parametru DllImport; v tomto případě by měl být zařazen na nativní straně řetězce ANSI.
Můžete specifikovat zařazovací informace pro jednotlivé argumenty nativní funkce, určené pomocí atributu MarshalAs. Existuje několik možností pro zařazování argumentu datového typu String *: BStr, ANSIBStr, TBStr, LPStr, LPWStr a LPTStr. Výchozí hodnota je LPStr.
V tomto příkladu je řetězec zařazen jako dvoubajtový Unicode řetězec znaků, LPWStr. Výstupem je první písmeno z věty Hello World! protože druhý byte zařazeného řetězce je prázdný a vkládání je interpretováno jako konec řetězce.
// 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);
}
Atribut MarshalAs je ve jmenném prostoru System::Runtime::InteropServices. Atribut lze použít s jinými datovými typy jako jsou například pole (array).
Jak bylo zmíněno výše v tomto tématu, zařazovací knihovna poskytuje novou, optimalizovanou metodu zařazování dat mezi nativními a spravovanými prostředky. Další informace naleznete v tématu Overview of Marshaling in C++.
Důležité informace o výkonu
PInvoke má režii mezi 10 a 30 x86 pokyny za volání. Vedle těchto pevně stanovených nákladů, vytváří zařazování další režii. U datových typů typu blittable, které zabírají stejné množství spravovaného a nespravovaného kódu, nejsou žádné zařazovací náklady. Například převod mezi int a Int32 je bez nákladů.
Pro lepší výkon je vhodnější volat méně PInvoke, která zařazuje co nejvíc údajů, než pro každé zařazování provádět zvláštní volání .