Chamando funções nativo código gerenciado
O common linguagem tempo de execução fornece serviços de invocação de plataforma ou PInvoke, que permite que o código para chamar funções de estilo C em nativas bibliotecas vinculado dinâmico (DLLs) gerenciado.O mesmo marshaling de dados são usados sistema autônomo para interoperabilidade COM o tempo de execução e para o mecanismo de "Ele apenas funciona" ou IJW.
Para obter mais informações, consulte:
Os exemplos nesta seção apenas ilustram como PInvoke pode ser usado. PInvoke pode simplificar o marshaling de dados personalizados porque fornecem informações de marshaling declarativamente nos atributos em vez de escrever código de marshaling procedimento.
Observação: |
---|
A biblioteca de realizar marshaling fornece uma maneira alternativa para realizar realizar marshaling dados entre ambientes nativo e gerenciado 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++. O nativo coloca função é definida 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 é necessário para gravar DLLImport declarações de atributo para as APIs não gerenciadas, o programa usa. Apenas incluem o arquivo de cabeçalho e o 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 fixar ou copiar itens de dados porque isso é concluído explicitamente pelo desenvolvedor).
Ele ilustra claramente problemas de desempenho.Nesse caso, o fato de que você está traduzindo da seqüência de caracteres Unicode para uma seqüência de caracteres ANSI e que você ter a alocação de memória do atendente e desalocação.Nesse caso, um desenvolvedor escrever o código usando IJW seria concretizar essa chamada _putws e usando PtrToStringChars seria melhor para o desempenho.
Se você telefonar várias APIs não gerenciadas usando os mesmos dados, empacotamento de uma vez e passando a cópia empacotada é muito mais eficiente do que re-marshaling cada vez.
Desvantagens de IJW
O marshaling deve ser especificado explicitamente no código em vez de por atributos (que geralmente têm padrões apropriados).
O código de marshaling é in-line, onde é mais invasiva no fluxo da lógica do aplicativo.
Como as APIs de marshaling explícitas retornam IntPtr tipos de portabilidade de 32 bit para 64 bit, você deve usar por extra ToPointer chamadas.
O método específico exposto pelo C++ é o método mais eficiente, explícito, o custo da alguma complexidade adicional.
Se o aplicativo usa principalmente os tipos de dados não gerenciados ou se ele chama mais APIs não gerenciadas de APIs do .NET estrutura, recomendamos que você use o recurso IJW.Para chamar uma API não gerenciada ocasional em um aplicativo gerenciado na maioria das vezes, a opção é 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 possui o título PInvoke teste e contém o texto Hello World!.
As informações de marshaling também são usadas pelo PInvoke para Pesquisar funções na DLL.Na user32.dll não é na verdade nenhuma função MessageBox, mas CharSet = CharSet::Ansi habilita PInvoke usar MessageBoxA, a versão ANSI, em vez de MessageBoxW, que é a versão Unicode.Em geral, recomendamos que você use as versões do Unicode das APIs não gerenciadas como que elimina a tradução sobrecarga do formato nativo Unicode de objetos de cadeia de caracteres do .NET estrutura em ANSI.
Quando não usar PInvoke
Usar PInvoke não é apropriado para todas as funções de estilo C em DLLs.Por exemplo, suponha que exista é uma função MakeSpecial mylib.dll declarada da seguinte maneira:
char * MakeSpecial(char * pszString);
Se usarmos PInvoke em um aplicativo do Visual C++, 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 pelo MakeSpecial.Outras funções chamadas por meio de PInvoke retornam um ponteiro para um buffer interno não tem que ser desalocada pelo usuário.Nesse caso, usando o recurso IJW é a escolha óbvia.
Limitações do PInvoke
Não é possível retornar o mesmo ponteiro exato de um nativo função levou sistema autônomo um parâmetro.Se um nativo função retorna o ponteiro foi empacotado para ele PInvoke, podem ensue exceções e corrupção de memória.
__declspec(dllexport)
char* fstringA(char* param) {
return param;
}
O exemplo a seguir apresenta esse problema, e mesmo que o programa possa parecer dar 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 marshaling
Com PInvoke, sem o marshaling é necessária entre gerenciado e tipos de C++ nativo primitivo com o mesmo formulário. Por exemplo, o marshaling não é necessário entre Int32 e int ou entre Double e duas vezes.
No entanto, você deve realizar realizar marshaling tipos que não têm o mesmo formulário.Isso inclui tipos char, cadeia de caracteres e estrutura.A tabela a seguir mostra os mapeamentos usados pelo empacotador para vários tipos:
wtypes.h |
Visual C++ |
O Visual C++ com/CLR |
Common Language Runtime |
---|---|---|---|
ALÇA |
void * |
void * |
IntPtr, UIntPtr |
BYTE |
unsigned char |
unsigned char |
Byte |
CURTO |
short |
short |
Int16 |
PALAVRA |
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 em 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 deve ser empacotado; nesse caso, eles devem ser ordenados cadeias de caracteres ANSI para o lado nativo.
Você pode especificar informações de marshaling para argumentos individuais de uma função nativa usando o atributo MarshalAs.Há várias opções para marshaling de uma string * argumento: BStr, ANSIBStr, TBStr, LPStr, LPWStr e LPTStr.O padrão é LPStr.
Neste exemplo, a seqüência de caractere é empacotada sistema autônomo uma seqüência de caractere do Unicode de dois byte, LPWStr.A saída é a primeira letra de Hello World!sistema autônomo o segundo byte da seqüência de caracteres empacotado é nulo e coloca interpretará isso sistema autônomo o marcador de participante da seqüência.
// 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 sistema::tempo de execução::InteropServices.O atributo pode ser usado com outros tipos de dados, sistema autônomo matrizes.
sistema autônomo mencionado anteriormente no tópico, a biblioteca de marshaling fornece um método novo e otimizado de empacotamento de dados entre ambientes nativo e gerenciado.Para obter mais informações, consulte Overview of Marshaling in C++.
Considerações sobre desempenho
PInvoke tem uma sobrecarga de entre 10 e 30 x86 instruções por telefonar.Em adição a esse custo fixo, marshaling cria adicional sobrecarga.Não há nenhum custo de marshaling entre tipos blittable que têm a mesma representação em código gerenciado e.Por exemplo, não há nenhum custo a tradução entre int e Int32.
Para obter um melhor desempenho, tem menos chamadas de PInvoke realizar realizar marshaling tantos dados quanto possível, em vez de mais chamadas realizar realizar marshaling menos dados por telefonar.