Sdílet prostřednictvím


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 Přehled zařazování v jazyce 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);
}

ms235282.collapse_all(cs-cz,VS.110).gifVý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í.

ms235282.collapse_all(cs-cz,VS.110).gifNevý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)

popisovač

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á

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í řetězec * 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ý byte zařazeného řetězce je prázdný a vkládání to interpretuje jako značka 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 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 Přehled zařazování v jazyce 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í .

Viz také

Další zdroje

Nativní a vzájemná funkční spolupráce rozhraní .NET