Serviços de Invocação de Plataforma - Platform Invocation Services

Platform Invocation Services , comumente referido como P / Invoke , é um recurso de implementações de Common Language Infrastructure , como o Common Language Runtime da Microsoft , que permite que o código gerenciado chame o código nativo .

O código gerenciado, como C # ou VB.NET, fornece acesso nativo a classes, métodos e tipos definidos nas bibliotecas que compõem o .NET Framework. Embora o .NET Framework forneça um amplo conjunto de funcionalidades, pode não ter acesso a muitas bibliotecas de sistema operacional de nível inferior normalmente escritas em código não gerenciado ou bibliotecas de terceiros também escritas em código não gerenciado. P / Invoke é a técnica que um programador pode usar para acessar funções nessas bibliotecas. As chamadas para funções dentro dessas bibliotecas ocorrem declarando a assinatura da função não gerenciada dentro do código gerenciado, que serve como a função real que pode ser chamada como qualquer outro método gerenciado. A declaração faz referência ao caminho do arquivo da biblioteca e define os parâmetros de função e retorno em tipos gerenciados que são mais prováveis ​​de serem empacotados implicitamente de e para os tipos não gerenciados pelo common language run-time (CLR). Quando os tipos de dados não gerenciados tornam-se muito complexos para uma conversão implícita simples de e para tipos gerenciados, a estrutura permite ao usuário definir atributos na função, retorno e / ou os parâmetros para refinar explicitamente como os dados devem ser empacotados para não para levar a exceções ao tentar fazê-lo implicitamente. Existem muitas abstrações de conceitos de programação de nível inferior disponíveis para programadores de código gerenciado, em comparação com a programação em linguagens não gerenciadas. Como resultado, um programador com experiência apenas em código gerenciado precisará aprimorar conceitos de programação como ponteiros, estruturas e passagem por referência para superar alguns dos obstáculos mais básicos, mas comuns, no uso de P / Invoke.

Arquitetura

Visão geral

Duas variantes de P / Invoke atualmente em uso são:

Explícito

  • O código nativo é importado por meio de bibliotecas vinculadas dinâmicas (DLLs)
  • Metadados incorporados no assembly do chamador definem como o código nativo deve ser chamado e os dados acessados ​​( geralmente requer especificadores de origem atribuídos para ajudar o compilador na geração de cola marechal )
    • Esta definição é a parte "explícita"

Implícito

  • Ao usar C ++ / CLI , um aplicativo pode usar simultaneamente o heap gerenciado (por meio de ponteiros de rastreamento) e qualquer região de memória nativa, sem a declaração explícita. (Implícito)
  • Um benefício principal, nesse caso, é que, se as estruturas de dados nativas subjacentes mudarem, desde que a nomenclatura seja compatível, uma alteração significativa é evitada.
    • isto é, adicionar / remover / reordenar estruturas em um cabeçalho nativo será suportado de forma transparente, desde que os nomes dos membros da estrutura também não tenham mudado.

Detalhes

Ao usar P / Invoke, o CLR lida com o carregamento de DLL e a conversão dos tipos anteriores não gerenciados em tipos CTS (também conhecido como empacotamento de parâmetro ). Para fazer isso, o CLR :

  • Localiza a DLL que contém a função.
  • Carrega a DLL na memória.
  • Localiza o endereço da função na memória e coloca seus argumentos na pilha , empacotando os dados conforme necessário.

P / Invoke é útil para usar DLLs C ou C ++ padrão (não gerenciadas) . Ele pode ser usado quando um programador precisa ter acesso à extensa API do Windows , já que muitas funções fornecidas pelas bibliotecas do Windows não possuem wrappers disponíveis . Quando uma API Win32 não é exposta pelo .NET Framework, o wrapper para essa API deve ser escrito manualmente.

Armadilhas

Escrever wrappers P / Invoke pode ser difícil e sujeito a erros. Usar DLLs nativas significa que o programador não pode mais se beneficiar da segurança de tipo e da coleta de lixo como normalmente é fornecida no ambiente .NET. Quando são usados ​​incorretamente, podem causar problemas como falhas de segmentação ou vazamentos de memória . Pode ser difícil obter as assinaturas exatas das funções legadas para uso no ambiente .NET , o que pode resultar em tais problemas. Para este propósito, existem ferramentas e sites para obter tais assinaturas, ajudando a prevenir problemas de assinatura. [1]

Outras armadilhas incluem:

  • Alinhamento de dados incorreto de tipos definidos pelo usuário na linguagem gerenciada: existem diferentes maneiras de os dados serem alinhados, dependendo dos compiladores ou das diretivas do compilador em C e deve-se tomar cuidado para informar explicitamente ao CLR como alinhar os dados para tipos não blittable . Um exemplo comum é quando se tenta definir o tipo de dados em .NET para representar uma união em C . Duas variáveis ​​diferentes se sobrepõem na memória, e definir essas duas variáveis ​​em um tipo no .NET faria com que elas estivessem em locais diferentes na memória, portanto, atributos especiais devem ser usados ​​para corrigir o problema.
  • Interferência com a localização de dados pelo coletor de lixo da linguagem gerenciada: se uma referência for local para um método em .NET e for passada para uma função nativa, quando o método gerenciado retornar, o coletor de lixo pode reivindicar essa referência. Deve-se ter cuidado para que a referência do objeto seja fixada , evitando que seja coletada ou movida pelo coletor de lixo, o que resultaria em um acesso inválido pelo módulo nativo.

Ao usar C ++ / CLI, a CIL emitida fica livre para interagir com objetos localizados no heap gerenciado e, simultaneamente, com qualquer local de memória nativa endereçável. Um objeto residente de heap gerenciado pode ser chamado, modificado ou construído, usando simples "objeto-> campo;" notação para atribuir valores ou especificar chamadas de método. Ganhos de desempenho significativos resultam da eliminação de qualquer troca de contexto desnecessária, os requisitos de memória são reduzidos (pilhas menores).

Isso traz novos desafios:

  • O código está sujeito a Double Thunking se não for especificamente endereçado
  • O problema do Loader Lock

Essas referências especificam soluções para cada um desses problemas, se forem encontradas. Um benefício principal é a eliminação da declaração da estrutura, a ordem da declaração do campo e os problemas de alinhamento não estão presentes no contexto do C ++ Interop.

Exemplos

Exemplos básicos

Este primeiro exemplo simples mostra como obter a versão de uma DLL específica :

Assinatura da função DllGetVersion na API do Windows :

HRESULT DllGetVersion
(
    DLLVERSIONINFO* pdvi
)

P / Invocar o código C # para invocar a função DllGetVersion :

[StructLayout(LayoutKind.Sequential)]
private struct DLLVERSIONINFO {
    public int cbSize;
    public int dwMajorVersion;
    public int dwMinorVersion;
    public int dwBuildNumber;
    public int dwPlatformID;
}
[DllImport("shell32.dll")]
static extern int DllGetVersion(ref DLLVERSIONINFO pdvi);

O segundo exemplo mostra como extrair um ícone em um arquivo:

Assinatura da função ExtractIcon na API do Windows:

HICON ExtractIcon
(      
    HINSTANCE hInst,
    LPCTSTR lpszExeFileName,
    UINT nIconIndex
);

P / Invocar o código C # para invocar a função ExtractIcon :

[DllImport("shell32.dll")]
static extern IntPtr ExtractIcon(
    IntPtr hInst, 
    [MarshalAs(UnmanagedType.LPStr)] string lpszExeFileName, 
    uint nIconIndex);

Este próximo exemplo complexo mostra como compartilhar um evento entre dois processos na plataforma Windows :

Assinatura da função CreateEvent :

 HANDLE CreateEvent(
     LPSECURITY_ATTRIBUTES lpEventAttributes,
     BOOL bManualReset,
     BOOL bInitialState,
     LPCTSTR lpName
 );

P / Invoque o código C # para invocar a função CreateEvent :

[DllImport("kernel32.dll", SetLastError=true)]
static extern IntPtr CreateEvent(
    IntPtr lpEventAttributes, 
    bool bManualReset,
    bool bInitialState, 
    [MarshalAs(UnmanagedType.LPStr)] string lpName);

Um exemplo mais complexo

// native declaration
typedef struct _PAIR 
{ 
	DWORD Val1; 
	DWORD Val2; 
} PAIR, *PPAIR;
// Compiled with /clr; use of #pragma managed/unmanaged can lead to double thunking;
// avoid by using a stand-alone .cpp with .h includes.
// This would be located in a .h file.

template<>
inline CLR_PAIR^ marshal_as<CLR_PAIR^, PAIR> (const PAIR&Src) {    // Note use of de/referencing. It must match your use.
	CLR_PAIR^ Dest = gcnew CLR_PAIR;
	Dest->Val1 = Src.Val1;
	Dest->Val2 = Src.Val2;
	return Dest;
};
CLR_PAIR^ mgd_pair1;
CLR_PAIR^ mgd_pair2;
PAIR native0,*native1=&native0;

native0 = NativeCallGetRefToMemory();

// Using marshal_as. It makes sense for large or frequently used types.
mgd_pair1 = marshal_as<CLR_PAIR^>(*native1);

// Direct field use
mgd_pair2->Val1 = native0.Val1;
mgd_pair2->val2 = native0.val2;

return(mgd_pair1); // Return to C#

Ferramentas

Existem várias ferramentas que são projetadas para auxiliar na produção de assinaturas P / Invoke.

Escrever um aplicativo utilitário que importe arquivos de cabeçalho C ++ e arquivos DLL nativos e produza um assembly de interface automaticamente acaba sendo bastante difícil. O principal problema com a produção de tal importador / exportador para assinaturas P / Invoke é a ambigüidade de alguns tipos de parâmetro de chamada de função C ++.

Brad Abrams tem o seguinte a dizer sobre o assunto: The P / Invoke Problem .

O problema está nas funções C ++ como as seguintes:

__declspec(dllexport) void MyFunction(char *params);

Que tipo devemos usar para os parâmetros de parâmetro em nossa assinatura P / Invoke? Isso pode ser uma string C ++ terminada em nulo, ou pode ser uma matriz char ou pode ser um parâmetro char de saída . Portanto, devemos usar string , StringBuilder , char [] ou ref char  ?

Independentemente desse problema, existem algumas ferramentas disponíveis para simplificar a produção de assinaturas P / Invoke.

Uma das ferramentas listadas abaixo, o xInterop C ++ .NET Bridge , resolveu esse problema implementando várias substituições do mesmo método C ++ no mundo .NET. Os desenvolvedores podem então escolher o correto para fazer a chamada.

PInvoke.net

PInvoke.net é um wiki que contém assinaturas P / Invoke para um grande número de APIs padrão do Windows. É propriedade da Redgate Software e tem cerca de 50000 acessos por mês.

As assinaturas são produzidas manualmente pelos usuários do wiki. Eles podem ser pesquisados ​​usando um addin gratuito para o Microsoft Visual Studio .

PInvoker

PInvoker é um aplicativo que importa DLLs nativas e arquivos C ++ .h e exporta DLLs de interoperabilidade P / Invoke totalmente formadas e compiladas . Ele supera o problema de ambigüidade envolvendo parâmetros de função de ponteiro nativos em classes de interface .NET específicas do PInvoker. Em vez de usar tipos de parâmetro .NET padrão nas definições de método P / Invoke ( char [] , string , etc.), ele usa essas classes de interface nas chamadas de função P / Invoke.

Por exemplo, se considerarmos o código de exemplo acima, PInvoker produziria uma função .NET P / Invoke aceitando uma classe de interface .NET envolvendo o ponteiro char * nativo . A construção desta classe pode ser a partir de uma string ou de um array char [] . A estrutura de memória nativa real para ambos é a mesma, mas os respectivos construtores de classe de interface para cada tipo preencherão a memória de maneiras diferentes. A responsabilidade de decidir qual tipo .NET precisa ser passado para a função é, portanto, passada para o desenvolvedor.

Microsoft Interop Assistant

O Microsoft Interop Assistant é uma ferramenta gratuita disponível com binários e código-fonte disponíveis para download no CodePlex . Ele é licenciado sob a Licença Pública Limitada da Microsoft (Ms-LPL).

Tem duas partes:

  • Um conversor que pega pequenas seções de código de arquivo de cabeçalho C ++ nativo contendo definições de estrutura e método. Em seguida, ele produz código C # P / Invoke para você copiar e colar em seus aplicativos.
  • Um banco de dados pesquisável de definições convertidas de constantes, métodos e estruturas da API do Windows.

Como essa ferramenta produz código-fonte C # em vez de uma dll compilada, o usuário está livre para fazer as alterações necessárias no código antes de usá-lo. Portanto, o problema de ambigüidade é resolvido pelo aplicativo que escolhe um tipo específico do .NET para usar na assinatura do método P / Invoke e, se necessário, o usuário pode alterá-lo para o tipo requerido.

P / Invoke Wizard

O P / Invoke Wizard usa um método semelhante ao Microsoft Interop Assistant, pois aceita código de arquivo C ++ .h nativo e produz código C # (ou VB.NET) para você colar no código do aplicativo .NET.

Ele também tem opções para a estrutura que você deseja atingir: .NET Framework para desktop ou .NET Compact Framework para dispositivos inteligentes Windows Mobile (e Windows CE).

Ponte xInterop C ++ .NET

xInterop C ++ .NET Bridge é um aplicativo do Windows para criar um wrapper C # para DLLs C ++ nativas e uma ponte C ++ para acessar assemblies .NET. Ele vem com uma biblioteca C # /. NET que envolve as classes C ++ padrão, como string, iostream, etc. , Classes e objetos C ++ podem ser acessados ​​no .NET.

Esta ferramenta gera DLLs de wrapper C # com código-fonte de DLLs C ++ nativos existentes e os arquivos de cabeçalho associados que são exigidos pela ferramenta para construir uma DLL de wrapper C #. As assinaturas P / Invoke e o empacotamento de dados são gerados pelo aplicativo. O wrapper C # resultante tem a interface semelhante da contraparte C ++ com o tipo de parâmetro convertido para o código .NET.

Esta ferramenta reconhece classes de modelo que não são exportadas da DLL C ++ e instancia a classe de modelo e a exporta em uma DLL suplementar e a interface C ++ correspondente pode ser usada em .NET.

Veja também

Referências

links externos