Comment : marshaler des structures à l'aide de PInvoke
Ce document explique comment les fonctions natives qui acceptent des structs de style C peuvent être appelées à partir de fonctions managées à l’aide de P/Invoke. Bien que nous vous recommandons d’utiliser les fonctionnalités d’interopérabilité C++ au lieu de P/Invoke, car P/Invoke fournit peu de rapports d’erreurs au moment de la compilation, n’est pas de type sécurisé et peut être fastidieux à implémenter, si l’API non managée est empaquetée en tant que DLL et que le code source n’est pas disponible, P/Invoke est la seule option. Sinon, consultez les documents suivants :
Par défaut, les structures natives et managées sont disposées différemment en mémoire. Par conséquent, le passage de structures à travers la limite managée/non managée nécessite des étapes supplémentaires pour préserver l’intégrité des données.
Ce document explique les étapes requises pour définir des équivalents managés de structures natives et la façon dont les structures résultantes peuvent être passées à des fonctions non managées. Ce document suppose que des structures simples ( celles qui ne contiennent pas de chaînes ou de pointeurs) sont utilisées. Pour plus d’informations sur l’interopérabilité non blittable, consultez Utilisation de l’interopérabilité C++ (PInvoke implicite). P/Invoke ne peut pas avoir de types non blittables comme valeur de retour. Les types blittables ont la même représentation dans le code managé et non managé. Pour plus d’informations, consultez Types Blittable et non Blittable.
Le marshaling de structures simples et blittables sur la limite managée/non managée nécessite d’abord que les versions managées de chaque structure native soient définies. Ces structures peuvent avoir n’importe quel nom juridique ; il n’existe aucune relation entre la version native et gérée des deux structures autres que leur disposition de données. Par conséquent, il est essentiel que la version managée contienne des champs de la même taille et dans le même ordre que la version native. (Il n’existe aucun mécanisme pour s’assurer que les versions managées et natives de la structure sont équivalentes, de sorte que les incompatibilités ne deviennent pas apparentes tant que l’exécution n’est pas terminée. Il incombe au programmeur de s’assurer que les deux structures ont la même disposition de données.)
Étant donné que les membres des structures managées sont parfois réorganisés à des fins de performances, il est nécessaire d’utiliser l’attribut StructLayoutAttribute pour indiquer que la structure est disposée de manière séquentielle. Il est également judicieux de définir explicitement le paramètre d’emballage de structure de la même façon que celui utilisé par la structure native. (Bien que par défaut, Visual C++ utilise une structure de 8 octets pour les deux codes managés.)
Ensuite, utilisez DllImportAttribute pour déclarer des points d’entrée qui correspondent à toutes les fonctions non managées qui acceptent la structure, mais utilisez la version managée de la structure dans les signatures de fonction, qui est un point moot si vous utilisez le même nom pour les deux versions de la structure.
Désormais, le code managé peut passer la version managée de la structure aux fonctions non managées comme si elles sont réellement gérées. Ces structures peuvent être passées par valeur ou par référence, comme illustré dans l’exemple suivant.
Modules non managés et non managés
Le code suivant se compose d’un module non managé et managé. Le module non managé est une DLL qui définit une structure appelée Location et une fonction appelée GetDistance qui accepte deux instances de la structure Location. Le deuxième module est une application en ligne de commande managée qui importe la fonction GetDistance, mais la définit en termes d’équivalent managé de la structure Location, MLocation. Dans la pratique, le même nom serait probablement utilisé pour les deux versions de la structure ; Toutefois, un autre nom est utilisé ici pour démontrer que le prototype DllImport est défini en termes de version managée.
Notez qu’aucune partie de la DLL n’est exposée au code managé à l’aide de la directive #include traditionnelle. En fait, la DLL est accessible au moment de l’exécution uniquement. Par conséquent, les problèmes liés aux fonctions importées avec DllImport ne sont pas détectés au moment de la compilation.
Exemple : module DLL non managé
// 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;
}
Exemple : module d’application en ligne de commande managée
// 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
Voir aussi
Utilisation d’un PInvoke explicite en C++ (attribut DllImport)