Usar código C/C++ em um projeto escrito em C#

Platform Invocation Services (PInvoke) permite que código gerenciados chame funções não gerenciadas que foram implementadas em uma DLL.
Esse artigo irá mostrar o que você precisa fazer para ser capaz de chamar essas funções a partir do C#. Os atributos discutidos nesse artigo irão permitir que você chame essas funções e que os tipos de dados sejam guiados de forma correta.

Existe duas maneiras pelas quais o código C# pode chamar diretamente código não gerenciado:

Para ambas as técnicas, você deve fornecer ao compilador C# uma declaração da função não gerenciada, e precisa também fornecer ao compilador C# a descrição de como guiar os parâmetros e valores de retorno entre os código.
Esse artigo consiste dos seguintes tópicos:

O artigo inclui os seguintes exemplos:

Chamando um exportador de DLL Export diretamente do código C#

Para declarar um método implementado em uma DLL exportada, faça o seguinte:

  • Declare o método com as palavra chaves static e extern do C#.
  • Anexe o atributo DllImport ao método. O atributo DllImport permite que você especifique o nome da DLL que contém o método. A prática comum é nomear o método C# com o mesmo nome do método exportada, mas você também pode usar um diferente nome.
  • Opcionalmente, especifique informações de guia para os parâmetros do método, que irão sobrescrever o guia padrão do framework .NET.

Exemplo 1

Esse exemplo mostra como usar o atributo DllImport para exibir uma mensagem chamando o método puts de msvcrt.dll.

// PInvokeTest.cs 
using System; 
using System.Runtime.InteropServices; 
 
class PlatformInvokeTest 
{ 
    [DllImport("msvcrt.dll")] 
    public static extern int puts(string c); 
    [DllImport("msvcrt.dll")] 
    internal static extern int _flushall(); 
 
    public static void Main()  
    { 
        puts("Test"); 
        _flushall(); 
    } 
}

Saída

Test

Discussão do código

O exemplo anterior mostra os requisitos mínimos para declarar um método C# que é implementado em uma DLL não gerenciada. O método PlatformInvokeTest.puts é declarada com o modificadores static e extern e possui o atributo DllImport que informa ao compilador que a implementação está em  msvcrt.dll, usando o nome padrão puts. Para usar um nome diferente para o método C# como putstring, você deve usar a opção EntryPoint do atributo DllImport, dessa forma:

[DllImport("msvcrt.dll", EntryPoint="puts")]

Para mais informações sobre a sintaxe do atributo DllImport, veja DllImportAttribute Class.

Guia padrão e especificação de guias personalizados para parâmetros de métodos não gerenciados

Quando se chama uma função não gerenciada no código C#, o common language runtime – CLR deve guiar os parâmetros e valores de retorno.
Para cada tipo do framework .NET existe um tipo não gerenciado padrão, que o common language runtime usará como guia em uma chamada entre funções gerenciadas e não gerenciadas. Por exemplo, o guia padrão para valores strings em C# é o tipo LPTSTR (um ponteiro para um buffer char TCHAR). Você pode sobrescrever o guia padrão usando o atributo MarshalAs na declaração C# da função não gerenciada.

Exemplo 2

Esse exemplo usa o atributo DllImport para exibir uma string. Também mostra como sobrescrever o guia padrão dos parâmetros da função usando o atributo MarshalAs.

// Marshal.cs 
using System; 
using System.Runtime.InteropServices; 
 
class PlatformInvokeTest 
{ 
    [DllImport("msvcrt.dll")] 
    public static extern int puts( 
        [MarshalAs(UnmanagedType.LPStr)] 
        string m); 
    [DllImport("msvcrt.dll")] 
    internal static extern int _flushall(); 
 
 
    public static void Main()  
    { 
        puts("Hello World!"); 
        _flushall(); 
    } 
}

Saída

Quando você executa essa exemplo, a string,

Hello World!

será exibido no terminal.

Discussão do código

No exemplo anterior, o guia padrão para o parâmetro da função puts foi sobrescrito do padrão LPTSTR para LPSTR.
O atributo MarshalAs pode ser usado para parâmetros, valores de retorno e campos de structs e classes. Para configurar o guia do valor de retorno de um método, ponha o atributo MarshalAs em um bloco de atributos do método com o atributo return sobrescrito. Por exemplo, para explicitamente ajustar o guia para o valor do retorno do método put:

... 
[DllImport("msvcrt.dll")]  
[return : MarshalAs(UnmanagedType.I4)] 
public static extern int puts(  
...

Para mais informações sobre a sintaxe do atributo MarshalAs, veja MarshalAsAttribute Class.

Nota   Os atributos In e Out podem ser usados para anotar parâmetros de métodos não gerenciados. Eles se comportam de maneira similar aos modificadores  in e out em arquivos de código fonte MIDL. Note que o atributo  Out é diferente do modificador de parâmetro do C# out. Para mais informações sobre os atributos In e Out, veja InAttribute Class e OutAttribute Class.

Especificando guias personalizados para estruturas definidas pelo usuário

Você pode especificar guias personalizados para campos de structs e classes passadas para ou de funções não gerenciadas. Você faz isso adicionando atributos MarshalAs aos campos da struct ou da classe. Você pode também usar o atributo StructLayout para configurar o layout da struct, opcionalmente controlar o guia de membros string e ajustar o tamanho do pacote.

Exemplo 3

Esse exemplo demonstra como especificar um guia personalizado para uma struct..
Considere a seguinte estrutura escrita em C:

typedef struct tagLOGFONT  
{  
   LONG lfHeight;  
   LONG lfWidth;  
   LONG lfEscapement;  
   LONG lfOrientation;  
   LONG lfWeight;  
   BYTE lfItalic;  
   BYTE lfUnderline;  
   BYTE lfStrikeOut;  
   BYTE lfCharSet;  
   BYTE lfOutPrecision;  
   BYTE lfClipPrecision;  
   BYTE lfQuality;  
   BYTE lfPitchAndFamily;  
   TCHAR lfFaceName[LF_FACESIZE];  
} LOGFONT; 

No C#, você pode descrever a struct acima usando os atributos StructLayout e MarshalAs da seguinte forma:

// logfont.cs 
// compile with: /target:module 
using System; 
using System.Runtime.InteropServices; 
 
[StructLayout(LayoutKind.Sequential)] 
public class LOGFONT  
{  
    public const int LF_FACESIZE = 32; 
    public int lfHeight;  
    public int lfWidth;  
    public int lfEscapement;  
    public int lfOrientation;  
    public int lfWeight;  
    public byte lfItalic;  
    public byte lfUnderline;  
    public byte lfStrikeOut;  
    public byte lfCharSet;  
    public byte lfOutPrecision;  
    public byte lfClipPrecision;  
    public byte lfQuality;  
    public byte lfPitchAndFamily; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=LF_FACESIZE)] 
    public string lfFaceName;  
}

Para mais informações sobre a sintaxe do atributo StructLayout, veja StructLayoutAttribute Class.
A estrutura pode então ser usada no código C# da seguinte forma:

// pinvoke.cs 
// compile with: /addmodule:logfont.netmodule 
using System; 
using System.Runtime.InteropServices; 
  
class PlatformInvokeTest 
{    
      [DllImport("gdi32.dll", CharSet=CharSet.Auto)] 
      public static extern IntPtr CreateFontIndirect( 
            [In, MarshalAs(UnmanagedType.LPStruct)] 
            LOGFONT lplf   // characteristics 
            ); 
  
      [DllImport("gdi32.dll")] 
      public static extern bool DeleteObject( 
            IntPtr handle 
            ); 
  
      public static void Main()  
      { 
            LOGFONT lf = new LOGFONT(); 
            lf.lfHeight = 9; 
            lf.lfFaceName = "Arial"; 
            IntPtr handle = CreateFontIndirect(lf); 
  
            if (IntPtr.Zero == handle) 
            { 
                  Console.WriteLine("Can't creates a logical font."); 
            } 
            else 
            { 
                   
                  if (IntPtr.Size == 4) 
                        Console.WriteLine("{0:X}", handle.ToInt32()); 
                  else 
                        Console.WriteLine("{0:X}", handle.ToInt64());          
 
                  // Delete the logical font created. 
                  if (!DeleteObject(handle)) 
                       Console.WriteLine("Can't delete the logical font"); 
            } 
      } 
}

Exemplo de execução

C30A0AE5

Discussão de código

No exemplo acima, o método CreateFontIndirect está usando um parâmetro do tipo LOGFONT. Os atributos MarshalAs e In são usados para qualificar o parâmetro. O programa exibe o valor numérico retornado pelo método como uma string hexadecimal em maiúsculas.

Registro de metódos de callback

Para registrar um callback gerenciado que chama uma função não gerenciada, declare um delegate com a mesma lista de argumentos e passe uma instância dele através do PInvoke. No lado não gerenciado isso corresponde a um ponteiro para uma função. para mais informações sonre PInvokecallback, veja A Closer Look at Platform Invoke.
Por exemplo, considere a seguinte função não gerenciada, MyFunction, que requer um callback como um dos argumentos:

typedef void (__stdcall *PFN_MYCALLBACK)(); 
int __stdcall MyFunction(PFN_ MYCALLBACK callback);

Para chamar MyFunction no código gerenciado, declare o delegate, anexe um importador de DLL à declaração da função, e opcionalmente guie os parâmetros e o valor de retorno:

public delegate void MyCallback(); 
[DllImport("MYDLL.DLL")] 
public static extern void MyFunction(MyCallback callback);

Além disso, certifique-se de o tempo de vida da instância do delegate cubra o tempo de vida do código não gerenciado; caso contrário, o delegate ficará indisponível quando for removido da memória pelo coletor de lixo.
Traduzido de msdn.microsoft.com