Freigeben über


Gewusst wie: Marshallen von Strukturen mit PInvoke

In diesem Dokument wird erläutert, wie systemeigene Funktionen, die C-Stil-Strukturen akzeptieren, mithilfe von P/Invoke aus verwalteten Funktionen aufgerufen werden können. Obwohl wir empfehlen, die C++-Interop-Features anstelle von P/Invoke zu verwenden, da P/Invoke wenig Kompilierzeitfehlerberichterstattung bereitstellt, nicht typsicher ist und mühsam implementiert werden kann, wenn die nicht verwaltete API als DLL verpackt ist und der Quellcode nicht verfügbar ist, ist P/Invoke die einzige Option. Andernfalls lesen Sie die folgenden Dokumente:

Standardmäßig werden systemeigene und verwaltete Strukturen im Arbeitsspeicher unterschiedlich angeordnet, sodass das erfolgreiche Übergeben von Strukturen über die verwaltete/nicht verwaltete Grenze zusätzliche Schritte erfordert, um die Datenintegrität zu erhalten.

In diesem Dokument werden die Schritte erläutert, die zum Definieren verwalteter Entsprechungen systemeigener Strukturen erforderlich sind und wie die resultierenden Strukturen an nicht verwaltete Funktionen übergeben werden können. In diesem Dokument wird davon ausgegangen, dass einfache Strukturen , die keine Zeichenfolgen oder Zeiger enthalten, verwendet werden. Informationen zur nicht blittbaren Interoperabilität finden Sie unter Verwenden der C++-Interoperabilität (impliziter PInvoke). P/Invoke kann nicht über nicht bemaßbare Typen als Rückgabewert verfügen. Blittable-Typen weisen die gleiche Darstellung in verwaltetem und nicht verwaltetem Code auf. Weitere Informationen finden Sie unter Blittable- und Nicht-Blittable-Typen.

Das Marshallen einfacher, blittbarer Strukturen über die verwaltete/nicht verwaltete Grenze hinweg erfordert zunächst, dass verwaltete Versionen jeder systemeigenen Struktur definiert werden. Diese Strukturen können einen beliebigen rechtlichen Namen haben; es gibt keine Beziehung zwischen der systemeigenen und verwalteten Version der beiden Strukturen, die nicht das Datenlayout sind. Daher ist es wichtig, dass die verwaltete Version Felder enthält, die die gleiche Größe und in der gleichen Reihenfolge wie die systemeigene Version aufweisen. (Es gibt keinen Mechanismus, um sicherzustellen, dass die verwalteten und nativen Versionen der Struktur gleichwertig sind, sodass Inkompatibilitäten erst während der Laufzeit sichtbar werden. Es liegt in der Verantwortung des Programmierers, sicherzustellen, dass die beiden Strukturen dasselbe Datenlayout aufweisen.)

Da die Member verwalteter Strukturen manchmal zu Leistungszwecken neu angeordnet werden, ist es erforderlich, das StructLayoutAttribute Attribut zu verwenden, um anzugeben, dass die Struktur sequenziell angeordnet ist. Es empfiehlt sich auch, die Strukturverpackungseinstellung explizit so festzulegen, dass sie mit der systemeigenen Struktur identisch ist. (Obwohl Visual C++ standardmäßig eine 8-Byte-Struktur für beide verwalteten Code verwendet.)

  1. Als Nächstes können DllImportAttribute Sie Einstiegspunkte deklarieren, die allen nicht verwalteten Funktionen entsprechen, die die Struktur akzeptieren, aber die verwaltete Version der Struktur in den Funktionssignaturen verwenden. Dies ist ein Moot-Punkt, wenn Sie für beide Versionen der Struktur denselben Namen verwenden.

  2. Verwalteter Code kann nun die verwaltete Version der Struktur an die nicht verwalteten Funktionen übergeben, als ob sie tatsächlich verwaltete Funktionen sind. Diese Strukturen können entweder nach Wert oder nach Bezug übergeben werden, wie im folgenden Beispiel gezeigt.

Nicht verwaltete Module und verwaltete Module

Der folgende Code besteht aus einem nicht verwalteten und einem verwalteten Modul. Das nicht verwaltete Modul ist eine DLL, die eine Struktur namens "Location" und eine Funktion namens "GetDistance" definiert, die zwei Instanzen der Location-Struktur akzeptiert. Das zweite Modul ist eine verwaltete Befehlszeilenanwendung, die die GetDistance-Funktion importiert, definiert sie jedoch in Bezug auf eine verwaltete Entsprechung der Standortstruktur MLocation. In der Praxis würde derselbe Name wahrscheinlich für beide Versionen der Struktur verwendet werden; Hier wird jedoch ein anderer Name verwendet, um zu veranschaulichen, dass der DllImport-Prototyp in Bezug auf die verwaltete Version definiert ist.

Beachten Sie, dass kein Teil der DLL mithilfe der herkömmlichen #include-Direktive für den verwalteten Code verfügbar gemacht wird. Tatsächlich wird zur Laufzeit nur auf die DLL zugegriffen, sodass Probleme mit mit DllImport importierten Funktionen zur Kompilierungszeit nicht erkannt werden.

Beispiel: Nicht verwaltetes DLL-Modul

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

Beispiel: Verwaltetes Befehlszeilenanwendungsmodul

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

Siehe auch

Verwenden von explizitem PInvoke in C++ (DllImport-Attribut)