Volání nativních funkcí 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.
Další informace naleznete v části:
Použití explicitního volání PInvoke v jazyce C++ (atribut DllImport)
Použití zprostředkovatele komunikace C++ (implicitní služba PInvoke)
Příklady v tomto oddíle ukazují, jak lez použít PInvoke.PInvoke umí zjednodušit vlastní zařazování dat, protože poskytnete 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 Přehled zařazování v jazyku C++.Knihovna zařazování je použitelná pouze pro data, ne pro funkce.
Atribut PInvoke a 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 souboru 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 souboru a spojení s importovanou knihovnou.
Mechanismus IJW je mírně rychlejší (například zástupné procedury IJW nepotřebují 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 tomto případě fakt, že provádíte převod Unicode řetězce na řetězec ANSI a máte přidělení a odebrání dodatečné paměti.V tomto případě by vývojář píšící kód s pomocí IJW zjistil, že volání _putws a použití PtrToStringChars by bylo lepší pro výkon.
Pokud voláte mnoho nespravovaných API rozhraní používajících stejná data, je provedení jednoho zařazení a předání zařazené kopie mnohem efektivnější než provádění zařazení vždy znovu.
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 v toku aplikační logiky invazivnější.
Protože explicitní zařazování rozhraní API vrací typy IntPtr pro přenositelnost z 32bitové na 64bitovou verzi, musíte použít další volání ToPointer.
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 volá více nespravované rozhraní API než rozhraní API .NET Framework, doporučujeme použití funkce IJW.Příležitostné volání nespravovaného API rozhraní ve většině spravovaných aplikacích je složitější.
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 k 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ázvem MessageBoxA místo Unicode verze funkce MessageBoxW.Obecně doporučujeme používat Unicode verze nespravovaného rozhraní API, protože to eliminuje režie na překlad 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 v knihovnách DLL.Předpokládejme, že je funkce MakeSpecial v DLL knihovně mylib.dll deklarována takto:
char * MakeSpecial(char * pszString);
Pokud používáme PInvoke v aplikaci Visual C++, pak 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 metodou 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 tento problém, kdy přesto, že se zdá být výstup programu správný, výstup přichází z uvolněné paměti.
// 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 jazyka C++ stejného formuláře.Například není požadováno žádné zařazování mezi Int32 a int nebo mezi Double a double.
Musíte 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í používaná zařazováním pro různé typy:
wtypes.h |
Visual C++ |
Visual C++ s /clr |
Common language runtime |
---|---|---|---|
POPISOVAČ |
void * |
void * |
IntPtr, UIntPtr |
BYTE |
unsigned char |
unsigned char |
Byte |
KRÁTKÝ |
short |
short |
Int16 |
WORD |
unsigned short |
unsigned short |
UInt16 |
INT |
int |
int |
Int32 |
UINT |
unsigned int |
unsigned int |
UInt32 |
DLOUHÝ |
long |
long |
Int32 |
BOOL |
long |
bool |
Logická |
DWORD |
unsigned long |
unsigned long |
UInt32 |
ULONG |
unsigned long |
unsigned long |
UInt32 |
CHAR |
char |
char |
Char |
LPCSTR |
char * |
Řetězec ^ [in], StringBuilder ^ [in, out] |
Řetězec ^ [in], StringBuilder ^ [in, out] |
LPCSTR |
const char * |
Řetězec ^ |
Řetězec |
LPWSTR |
wchar_t * |
Řetězec ^ [in], StringBuilder ^ [in, out] |
Řetězec ^ [in], StringBuilder ^ [in, out] |
LPCWSTR |
const wchar_t * |
Řetězec ^ |
Řetězec |
FLOAT |
float |
float |
Jednoduché |
DOUBLE |
double |
double |
Double |
Zařazování automaticky připíná paměť přidělenou haldě modulu runtime, pokud je adresa předávána nespravované funkci.Připínání zabraňuje systému uvolňování paměti přesunout přidělený bloku paměti během komprimace.
V příkladu zmíněném výše v tomto tématu určuje parametr CharSet parametru DllImport, jak by měly být zařazeny spravované řetězce; v tomto případě by měl být zařazen do řetězce ANSI na nativní straně.
Můžete specifikovat zařazovací informace pro jednotlivé argumenty nativní funkce pomocí atributu MarshalAs.Existuje několik možností pro zařazování řetězce * argument: 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 Hello World! protože druhý bajt zařazeného řetězce je null a vloží interprety jako značku ukončení ř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 v oboru názvů 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 Přehled zařazování v jazyku 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 přenositelných datových typů, 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ší mít méně volání PInvoke, která zařazují největší možné množství údajů, než více volání, která zařazují pro každé volání méně údajů.