Gewusst wie: Marshallen von Strukturen mit PInvoke

Aktualisiert: November 2007

In diesem Abschnitt wird erläutert, wie systemeigene Funktionen, die Zeichenfolgen im C-Format akzeptieren, von verwalteten Funktionen aufgerufen werden können, indem mithilfe von P/Invoke eine Instanz von String bereitgestellt wird. Visual C++-Programmierern wird empfohlen, stattdessen die C++-Interop-Funktionen zu verwenden, da P/Invoke eine sehr eingeschränkte Berichterstattung von Kompilierungsfehlern bietet, nicht typsicher ist und eine aufwändige Implementierung erfordern kann. Wenn die nicht verwaltete API als DLL gepackt und der Quellcode nicht verfügbar ist, stellt P/Invoke die einzige Option dar. Beachten Sie andernfalls die folgenden Themen:

In der Standardeinstellung werden systemeigene und verwaltete Strukturen im Speicher unterschiedlich angelegt. Die ordnungsgemäße Übergabe von Strukturen über verwaltete/nicht verwaltete Grenzen hinweg erfordert somit zusätzliche Schritte, um eine Datenintegrität sicherzustellen.

In diesem Abschnitt wird erläutert, welche Schritte nötig sind, um verwaltete Äquivalente systemeigener Strukturen zu definieren, und wie die sich daraus ergebenden Strukturen an nicht verwaltete Funktionen übergeben werden können. In diesem Abschnitt wird von der Verwendung einfacher Strukturen ausgegangen, die weder Zeichenfolgen noch Zeiger enthalten. Informationen zur nicht blitfähigen Interoperabilität finden Sie unter Verwenden von C++-Interop (implizites PInvoke).

Das Marshallen von einfachen, blitfähigen Strukturen über verwaltete/nicht verwaltete Grenzen hinweg erfordert, dass verwaltete Versionen aller systemeigenen Strukturen definiert sind. Diese Strukturen können einen beliebigen gültigen Namen haben. Es gibt keine Beziehung zwischen der systemeigenen und der verwalteten Version zweier Strukturen außer deren Datenlayout. Daher ist es wichtig, dass die verwaltete Version Felder enthält, die dieselbe Größe haben und sich in derselben Reihenfolge befinden wie in der systemeigenen Version. (Es gibt keinen Mechanismus, der garantiert, dass die verwaltete und die systemeigene Version der Struktur äquivalent sind. Inkompatibilitäten kommen somit erst bei der Ausführung zum Vorschein. Es liegt in der Verantwortung des Programmierers, dass beide Strukturen über das gleiche Datenlayout verfügen.)

Da die Member verwalteter Strukturen aus Gründen der Leistung gelegentlich neu angeordnet werden, ist die Verwendung des Attributs StructLayoutAttribute notwendig, um anzuzeigen, dass die Strukturen sequenziell angelegt werden. Es ist außerdem empfehlenswert, die Ausrichtungseinstellung der Struktur explizit festzulegen, damit diese mit der von der systemeigenen Struktur verwendeten identisch ist (auch wenn Visual C++ in der Standardeinstellung eine 8-Byte-Strukturausrichtung für verwalteten Code verwendet).

  1. Verwenden Sie als Nächstes DllImportAttribute, um Einstiegspunkte zu deklarieren, die allen nicht verwalteten Funktionen entsprechen, welche die Struktur akzeptieren, in den Funktionssignaturen jedoch die verwaltete Version der Struktur verwenden. Dies ist ein strittiger 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 so übergeben, als seien sie tatsächlich verwaltete Funktionen. Diese Strukturen können entweder als Wert oder als Referenz übergeben werden, wie im folgenden Beispiel erläutert wird.

Beispiel

Der folgende Code besteht aus einem nicht verwalteten und einem verwalteten Modul. Das nicht verwaltete Modul ist eine DLL, in der eine Struktur mit dem Namen "Location" sowie eine Funktion mit dem Namen "GetDistance" definiert wird. Diese Funktion akzeptiert zwei Instanzen der Struktur "Location". Das zweite Modul ist eine verwaltete Befehlszeilenanwendung, die die Funktion "GetDistance" importiert, diese jedoch in Ausdrücken eines verwalteten Äquivalents der Struktur "Location" (MLocation) definiert. In der Praxis würde für beide Versionen der Struktur möglicherweise derselbe Name verwendet werden. In diesem Beispiel wird jedoch ein anderer Name verwendet, um zu zeigen, dass der DllImport-Prototyp in Ausdrücken der verwalteten Version definiert wird.

Das verwaltete Modul wird mit /clr kompiliert, es kann jedoch auch /clr:pure verwendet werden.

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

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

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

Weitere Ressourcen

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