Condividi tramite


Procedura: effettuare il marshalling di strutture tramite PInvoke

Questo documento illustra in che modo le funzioni native che accettano struct in stile C possono essere chiamate da funzioni gestite tramite P/Invoke. Sebbene sia consigliabile usare le funzionalità di interoperabilità C++ anziché P/Invoke perché P/Invoke fornisce una segnalazione errori in fase di compilazione poco semplice, non è indipendente dai tipi e può essere noiosa da implementare, se l'API non gestita viene inserita in un pacchetto come DLL e il codice sorgente non è disponibile, P/Invoke è l'unica opzione. In caso contrario, vedere i documenti seguenti:

Per impostazione predefinita, le strutture native e gestite vengono disposte in modo diverso in memoria, quindi il passaggio di strutture attraverso il limite gestito/non gestito richiede passaggi aggiuntivi per mantenere l'integrità dei dati.

Questo documento illustra i passaggi necessari per definire equivalenti gestiti di strutture native e come le strutture risultanti possono essere passate alle funzioni non gestite. Questo documento presuppone che vengano utilizzate strutture semplici, ovvero quelle che non contengono stringhe o puntatori. Per informazioni sull'interoperabilità non copiabile, vedere Uso dell'interoperabilità C++ (PInvoke implicito).For information about non-blittable interoperability, see Using C++ Interop (Implicit PInvoke). P/Invoke non può avere tipi non copiabili come valore restituito. I tipi copiabili Blit hanno la stessa rappresentazione nel codice gestito e non gestito. Per altre informazioni, vedere Tipi Blittable e Non Blittable.

Il marshalling di strutture semplici e copiabili blt attraverso il limite gestito/non gestito richiede prima di tutto che le versioni gestite di ogni struttura nativa siano definite. Queste strutture possono avere qualsiasi nome legale; non esiste alcuna relazione tra la versione nativa e gestita delle due strutture diverse dal relativo layout di dati. Pertanto, è fondamentale che la versione gestita contenga campi con le stesse dimensioni e nello stesso ordine della versione nativa. Non esiste alcun meccanismo per garantire che le versioni gestite e native della struttura siano equivalenti, pertanto le incompatibilità non saranno visibili fino al runtime. È responsabilità del programmatore assicurarsi che le due strutture abbiano lo stesso layout di dati.

Poiché i membri delle strutture gestite vengono talvolta riorganiati a scopo di prestazioni, è necessario usare l'attributo StructLayoutAttribute per indicare che la struttura è disposta in sequenza. È anche consigliabile impostare in modo esplicito l'impostazione di compressione della struttura come quella utilizzata dalla struttura nativa. Anche se per impostazione predefinita, Visual C++ usa una struttura a 8 byte per entrambi i tipi di codice gestito.

  1. Successivamente, usare DllImportAttribute per dichiarare i punti di ingresso che corrispondono a qualsiasi funzione non gestita che accetta la struttura, ma usare la versione gestita della struttura nelle firme della funzione, ovvero un punto moot se si usa lo stesso nome per entrambe le versioni della struttura.

  2. Ora il codice gestito può passare la versione gestita della struttura alle funzioni non gestite come se fossero effettivamente funzioni gestite. Queste strutture possono essere passate per valore o per riferimento, come illustrato nell'esempio seguente.

Moduli non gestiti e gestiti

Il codice seguente è costituito da un modulo non gestito e gestito. Il modulo non gestito è una DLL che definisce una struttura denominata Location e una funzione denominata GetDistance che accetta due istanze della struttura Location. Il secondo modulo è un'applicazione della riga di comando gestita che importa la funzione GetDistance, ma la definisce in termini di un equivalente gestito della struttura Location, MLocation. In pratica lo stesso nome sarebbe probabilmente usato per entrambe le versioni della struttura; Tuttavia, qui viene usato un nome diverso per dimostrare che il prototipo DllImport è definito in termini di versione gestita.

Si noti che nessuna parte della DLL viene esposta al codice gestito usando la tradizionale direttiva #include. Infatti, la DLL è accessibile solo in fase di esecuzione, quindi i problemi con le funzioni importate con DllImport non verranno rilevati in fase di compilazione.

Esempio: modulo DLL non gestito

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

Esempio: modulo dell'applicazione della riga di comando gestita

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

Vedi anche

Uso esplicito di PInvoke in C++ (attributo DllImport)