Partilhar via


Chamar funções nativas do código gerenciado

O common language runtime fornece serviços de invocação de plataforma ou PInvoke, que permite código gerenciado para chamar funções de estilo c em nativas bibliotecas de vínculo dinâmico (DLLs). O mesmo empacotamento de dados é usado para interoperabilidade de COM o tempo de execução e para o "simplesmente funciona" ou IJW, mecanismo.

For more information, see:

Os exemplos nesta seção é apenas ilustram como PInvoke pode ser usado. PInvokepode simplificar o empacotamento de dados personalizados, porque você fornecer informações de empacotamento declarativamente em atributos em vez de escrever o código de empacotamento de procedimento.

ObservaçãoObservação

A biblioteca de marshaling fornece uma maneira alternativa para empacotar dados entre ambientes nativos e gerenciados de forma otimizada. Consulte Overview of Marshaling in C++ para obter mais informações sobre a biblioteca de marshaling. A biblioteca de marshaling é útil apenas para dados e não para funções.

PInvoke e o atributo DllImport

O exemplo a seguir mostra o uso de PInvoke em um programa do Visual C++. Coloca a função nativa é definido em Msvcrt. dll. O atributo DllImport é usado para a declaração de coloca.

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

O exemplo a seguir é equivalente ao exemplo anterior, mas usa 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);
}

Vantagens de IJW

  • Não há necessidade de escrever DLLImport declarações de atributo para APIs não gerenciadas usa o programa. Basta inclua o arquivo de cabeçalho e um link com a biblioteca de importação.

  • O mecanismo IJW é ligeiramente mais rápido (por exemplo, os stubs IJW não precisará verificar a necessidade de pin ou copiar itens de dados porque isso é feito explicitamente pelo desenvolvedor).

  • Ele ilustra claramente os problemas de desempenho. Nesse caso, o fato de que você esteja traduzindo a partir de uma seqüência de caracteres Unicode para uma seqüência de caracteres ANSI e que você tem uma alocação de memória e desalocação. Nesse caso, um desenvolvedor escrever o código usando IJW seria perceber que a chamada _putws e o uso de PtrToStringChars seria o melhor desempenho.

  • Se você chamar muitas APIs não gerenciadas usando os mesmos dados, empacotamento, uma vez e passando a cópia empacotada é muito mais eficiente do que re-marshaling a cada vez.

Desvantagens de IJW

  • O empacotamento deve ser especificado explicitamente no código em vez de por atributos (que geralmente têm padrões apropriados).

  • O código de empacotamento é embutido, onde é mais invasiva no fluxo da lógica do aplicativo.

  • Porque as APIs de empacotamento explícitas retornam IntPtr tipos de portabilidade de 32 bits para 64 bits, você deve usar extra ToPointer chamadas.

O método específico exposto pelo C++ é o método mais eficiente, explícito, ao custo de alguma complexidade adicional.

Se o aplicativo usa principalmente os tipos de dados não gerenciado ou se ele chama APIs não gerenciadas mais do que.APIs do NET Framework, recomendamos que você use o recurso IJW. Para chamar uma API não gerenciada ocasional em um aplicativo gerenciado na maioria das vezes, a escolha é mais sutil.

PInvoke com APIs do Windows

PInvoke é conveniente para chamar funções no Windows.

Neste exemplo, um programa do Visual C++ interopera com a função MessageBox que faz parte da API do 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);
}

A saída é uma caixa de mensagem que tem o título de teste de PInvoke e contém o texto Olá mundo!.

As informações de empacotamento também são usadas pelo PInvoke para procurar funções na DLL. No User32. dll não existe na verdade nenhuma função de MessageBox, mas o CharSet = CharSet::Ansi habilita PInvoke para usar o MessageBoxA, a versão ANSI, em vez de MessageBoxW, que é a versão Unicode. Em geral, é recomendável que você use versões Unicode da APIs não gerenciadas, porque o que elimina a tradução de sobrecarga do formato nativo Unicode do.Objetos de string do NET Framework para ANSI.

Quando não usar PInvoke

Não é apropriado para todas as funções do estilo c em DLLs de usar PInvoke. Por exemplo, suponha que exista uma função MakeSpecial na mylib.dll declarada da seguinte maneira:

char * MakeSpecial(char * pszString);

Se usarmos PInvoke em um aplicativo do Visual C++, podemos pode escrever algo semelhante à seguinte:

[DllImport("mylib")]

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

A dificuldade aqui é que nós não é possível excluir a memória para a seqüência de caracteres não-gerenciada retornada por MakeSpecial. Outras funções chamadas por meio de PInvoke retornam um ponteiro para um buffer interno que não tem que ser desalocada pelo usuário. Nesse caso, usando o recurso IJW é a escolha óbvia.

Limitações de PInvoke

Não é possível retornar o mesmo ponteiro exato de uma função nativa que você tirou como um parâmetro. Se uma função nativa retorna o ponteiro que foi empacotado para ele pelo PInvoke, exceções e corrupção de memória pode ser ensue.

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

O exemplo a seguir apresenta esse problema, e mesmo que o programa possa parecer dar a saída correta, a saída é proveniente de memória que tinha sido liberada.

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

Argumentos de empacotamento.

Com PInvoke, sem o empacotamento é necessária entre gerenciados e primitivo de C++ nativo tipos com o mesmo formulário. Por exemplo, o empacotamento não é necessário entre Int32 e int ou entre Double e double.

No entanto, você deve empacotar tipos que não têm o mesmo formulário. Isso inclui tipos char, string e struct. A tabela a seguir mostra os mapeamentos usados pelo empacotador para diversos tipos:

wtypes.h

Visual C++

Visual C++ com o /clr

Common language runtime

ALÇA

void *

void *

IntPtr, UIntPtr

BYTE

caracteres não assinados

caracteres não assinados

Byte

CURTO

short

short

Int16

WORD

unsigned short

unsigned short

UInt16

INT

int

int

Int32

UINT

unsigned int

unsigned int

UInt32

LONGO

long

long

Int32

BOOL

long

bool

Boolean

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 ^

String

LPWSTR

wchar_t *

String ^ [in], StringBuilder ^ [in, out]

String ^ [in], StringBuilder ^ [in, out]

LPCWSTR

Const wchar_t *

String ^

String

FLOAT

float

float

Single

DUPLO

double

double

Double

O empacotador fixa automaticamente a memória alocada no heap de tempo de execução se seu endereço é passado para uma função não gerenciada. Fixando impede que o coletor de lixo mover o bloco de memória alocado durante a compactação.

No exemplo mostrado anteriormente neste tópico, o parâmetro CharSet de DllImport Especifica gerenciadas como cadeias de caracteres devem ser empacotados; Nesse caso, eles devem ser empacotados para seqüências de caracteres ANSI para o lado nativo.

Você pode especificar informações de empacotamento para os argumentos individuais de uma função nativa usando o atributo MarshalAs. Há diversas opções para empacotamento de uma seqüência de caracteres * argumento: BStr, ANSIBStr, TBStr, LPStr, LPWStr e LPTStr. O padrão é LPStr.

Neste exemplo, a seqüência de caracteres é empacotada como uma seqüência de caracteres do Unicode de byte duplo, LPWStr. A saída é a primeira letra de Hello World! porque o segundo byte da seqüência de caracteres empacotado é nulo e coloca interpretará isso como o marcador de fim da seqüência de caracteres.

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

O atributo MarshalAs está no namespace System::Runtime::InteropServices. O atributo pode ser usado com outros tipos de dados, tais como matrizes.

Como mencionado anteriormente, no tópico, a biblioteca de marshaling fornece um método novo e otimizado de empacotamento de dados entre ambientes nativos e gerenciados. For more information, see Overview of Marshaling in C++.

Performance Considerations

PInvoke tem uma sobrecarga de entre 10 e 30 x86 de instruções por chamada. Além de para esse custo fixo, empacotamento cria uma sobrecarga adicional. Não há nenhum custo de empacotamento entre tipos blittable que têm a mesma representação em código gerenciado e não gerenciado. Por exemplo, não há nenhum custo para traduzir entre int e Int32.

Para obter melhor desempenho, tem menos chamadas de PInvoke empacotar tantos dados quanto possível, em vez de mais chamadas empacotar menos dados por chamada.

Consulte também

Outros recursos

Nativo e.NET interoperabilidade