Compartir a través de


Cómo: serializar estructuras mediante PInvoke

En este documento se explica cómo se puede llamar a las funciones nativas que aceptan estructuras de estilo C desde las funciones administradas mediante P/Invoke. Aunque se recomienda usar las características de interoperabilidad de C++ en lugar de P/Invoke, debido a que P/Invoke proporciona informes de errores en tiempo de compilación, no tiene seguridad de tipos y puede ser tedioso el implementar, si la API no administrada se empaqueta como DLL y el código fuente no está disponible, entonces P/Invoke es la única opción. De lo contrario, consulte lo siguiente documentación:

De forma predeterminada, las estructuras nativas y administradas se diseñan de forma diferente en la memoria, por lo que pasar correctamente las estructuras a través del límite administrado o no administrado requiere de pasos adicionales para conservar la integridad de los datos.

En este documento se explican los pasos necesarios para definir equivalentes administrados de estructuras nativas y cómo se pueden pasar las estructuras resultantes a las funciones no administradas. En este documento se da por supuesto que se usan estructuras simples (las que no contienen cadenas o punteros). Para obtener información sobre la interoperabilidad que no puede transferirse en bloque de bits, consulte Uso de la interoperabilidad de C++ (PInvoke implícito). P/Invoke no puede tener tipos que no pueden transferirse en bloque de bits como un valor devuelto. Los tipos que pueden transferirse en bloque de bits tienen la misma representación en código administrado y no administrado. Para obtener más información, consulte Tipos que pueden y no pueden transferirse en bloque de bits.

La serialización de estructuras simples y que se pueden transferir en bloque a través del límite administrado o no requiere primero que se definan las versiones administradas de cada estructura nativa. Estas estructuras pueden tener cualquier nombre legal; no hay ninguna relación entre la versión nativa y la versión administrada de las dos estructuras distintas de su diseño de datos. Por lo tanto, es fundamental que la versión administrada contenga campos del mismo tamaño y en el mismo orden que la versión nativa. (No hay ningún mecanismo para garantizar que las versiones administradas y las versiones nativas de la estructura sean equivalentes, por lo que las incompatibilidades no se volverán evidentes hasta el tiempo de ejecución. Es responsabilidad del programador asegurarse de que las dos estructuras tienen el mismo diseño de datos).

Dado que los miembros de las estructuras administradas a veces se reorganizan con fines de rendimiento, es necesario usar el atributo StructLayoutAttribute para indicar que la estructura se diseña secuencialmente. También es una buena idea establecer explícitamente la configuración de empaquetado de estructura para que sea la misma que la usada por la estructura nativa. (Aunque de forma predeterminada, Visual C++ usa un empaquetado de estructura de 8 bytes para ambos códigos administrados).

  1. A continuación, use DllImportAttribute para declarar los puntos de entrada que correspondan a cualquier función no administrada que acepte la estructura, pero use la versión administrada de la estructura en las firmas de función, que es un punto de moot si usa el mismo nombre para ambas versiones de la estructura.

  2. Ahora, el código administrado puede pasar la versión administrada de la estructura a las funciones no administradas como si fueran realmente funciones administradas. Estas estructuras se pueden pasar tanto por valor como por referencia, como se muestra en el ejemplo siguiente.

Módulos administrados y no administrados

El código siguiente consta de un módulo administrado y uno no administrado. El módulo no administrado es un archivo DLL que define una estructura llamada Location y una función denominada GetDistance que acepta dos instancias de la estructura Location. El segundo módulo es una aplicación de línea de comandos administrada que importa la función GetDistance, pero la define en términos de un equivalente administrado de la estructura Location, MLocation. En la práctica, probablemente se usaría el mismo nombre para ambas versiones de la estructura; sin embargo, aquí se usa un nombre diferente para demostrar que el prototipo DllImport se define en términos de la versión administrada.

Tenga en cuenta que ninguna parte de la DLL se expone al código administrado mediante la directiva tradicional #include. De hecho, solo se accede al archivo DLL en tiempo de ejecución, por lo que los problemas con las funciones importadas con DllImport no se pueden detectar en el tiempo de compilación.

Ejemplo: módulo DLL no administrado

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

Ejemplo: módulo de aplicación de línea de comandos administrado

// 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

Consulte también

Usar un elemento PInvoke explícito en C++ (Atributo DllImport)