Como realizar marshaling de estruturas usando PInvoke
Este documento explica como as funções nativas que aceitam structs no estilo C podem ser chamadas de funções gerenciadas usando o P/Invoke. Embora recomendemos que você use os recursos de Interoperabilidade do C++ em vez de P/Invoke porque P/Invoke fornece pouco relatório de erros em tempo de compilação, não é fortemente tipado e pode ser entediante para implementar, se a API não gerenciada for empacotada como uma DLL e o código-fonte não estiver disponível, P/Invoke será a única opção. Caso contrário, confira os seguintes documentos:
Por padrão, as estruturas nativas e gerenciadas são dispostas de forma diferente na memória, portanto, passar estruturas com êxito pelo limite gerenciado/não gerenciado requer etapas extras para preservar a integridade dos dados.
Este documento explica as etapas necessárias para definir equivalentes gerenciados de estruturas nativas e como as estruturas resultantes podem ser passadas para funções não gerenciadas. Este documento pressupõe que estruturas simples – aquelas que não contêm cadeias de caracteres nem ponteiros – sejam usadas. Para obter informações sobre a interoperabilidade não blittable, confira Usar a interoperabilidade C++ (PInvoke implícito). P/Invoke não pode ter tipos não blittable como um valor retornado. Os tipos blittable têm a mesma representação em código gerenciado e não gerenciado. Para obter mais informações, confira Tipos blittable e não blittable.
O marshaling de estruturas simples e blittable no limite gerenciado/não gerenciado primeiro requer que as versões gerenciadas de cada estrutura nativa sejam definidas. Essas estruturas podem ter qualquer nome legal; não há nenhuma relação entre a versão nativa e gerenciada das duas estruturas que não sejam o layout de dados. Portanto, é vital que a versão gerenciada contenha campos do mesmo tamanho e na mesma ordem que a versão nativa. (Não há nenhum mecanismo para garantir que as versões gerenciadas e nativas da estrutura sejam equivalentes, portanto, as incompatibilidades não se tornarão aparentes até o tempo de execução. É responsabilidade do programador garantir que as duas estruturas tenham o mesmo layout de dados.)
Como os membros de estruturas gerenciadas às vezes são reorganizados para fins de desempenho, é necessário usar o atributo StructLayoutAttribute para indicar que a estrutura é definida sequencialmente. Também é uma boa ideia definir explicitamente a configuração de empacotamento de estrutura como a mesma usada pela estrutura nativa. (Embora, por padrão, o Visual C++ use um empacotamento de estrutura de 8 bytes para ambos os códigos gerenciados.)
Em seguida, use DllImportAttribute para declarar pontos de entrada que correspondem a quaisquer funções não gerenciadas que aceitem a estrutura, mas usem a versão gerenciada da estrutura nas assinaturas de função, que é um ponto discutível se você usar o mesmo nome para ambas as versões da estrutura.
Agora, o código gerenciado pode passar a versão gerenciada da estrutura para as funções não gerenciadas como se fossem funções gerenciadas. Essas estruturas podem ser passadas por valor ou por referência, conforme demonstrado no exemplo a seguir.
Módulos gerenciados e não gerenciados
O código a seguir consiste em um módulo gerenciado e um não gerenciado. O módulo não gerenciado é uma DLL que define uma estrutura chamada Location e uma função chamada GetDistance que aceita duas instâncias da estrutura Location. O segundo módulo é um aplicativo de linha de comando gerenciado que importa a função GetDistance, mas a define em termos de um equivalente gerenciado da estrutura Location, MLocation. Na prática, o mesmo nome provavelmente seria usado para ambas as versões da estrutura. No entanto, um nome diferente é usado aqui para demonstrar que o protótipo DllImport é definido em termos da versão gerenciada.
Nenhuma parte da DLL é exposta ao código gerenciado usando a diretiva tradicional #include. Na verdade, a DLL é acessada somente em tempo de execução, portanto, problemas com funções importadas com DllImport não serão detectados no tempo de compilação.
Exemplo: módulo DLL não gerenciado
// TraditionalDll3.cpp
// compile with: /LD /EHsc
#include <iostream>
#include <stdio.h>
#include <math.h>
#define TRADITIONALDLL_EXPORTS
#ifdef TRADITIONALDLL_EXPORTS
#define TRADITIONALDLL_API __declspec(dllexport)
#else
#define TRADITIONALDLL_API __declspec(dllimport)
#endif
#pragma pack(push, 8)
struct Location {
int x;
int y;
};
#pragma pack(pop)
extern "C" {
TRADITIONALDLL_API double GetDistance(Location, Location);
TRADITIONALDLL_API void InitLocation(Location*);
}
double GetDistance(Location loc1, Location loc2) {
printf_s("[unmanaged] loc1(%d,%d)", loc1.x, loc1.y);
printf_s(" loc2(%d,%d)\n", loc2.x, loc2.y);
double h = loc1.x - loc2.x;
double v = loc1.y = loc2.y;
double dist = sqrt( pow(h,2) + pow(v,2) );
return dist;
}
void InitLocation(Location* lp) {
printf_s("[unmanaged] Initializing location...\n");
lp->x = 50;
lp->y = 50;
}
Exemplo: módulo de aplicativo de linha de comando gerenciado
// MarshalStruct_pi.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;
[StructLayout(LayoutKind::Sequential, Pack=8)]
value struct MLocation {
int x;
int y;
};
value struct TraditionalDLL {
[DllImport("TraditionalDLL3.dll")]
static public double GetDistance(MLocation, MLocation);
[DllImport("TraditionalDLL3.dll")]
static public double InitLocation(MLocation*);
};
int main() {
MLocation loc1;
loc1.x = 0;
loc1.y = 0;
MLocation loc2;
loc2.x = 100;
loc2.y = 100;
double dist = TraditionalDLL::GetDistance(loc1, loc2);
Console::WriteLine("[managed] distance = {0}", dist);
MLocation loc3;
TraditionalDLL::InitLocation(&loc3);
Console::WriteLine("[managed] x={0} y={1}", loc3.x, loc3.y);
}
[unmanaged] loc1(0,0) loc2(100,100)
[managed] distance = 141.42135623731
[unmanaged] Initializing location...
[managed] x=50 y=50