Udostępnij za pośrednictwem


Porady: kierowanie struktur za pomocą funkcji PInvoke

W tym dokumencie wyjaśniono, w jaki sposób funkcje natywne, które akceptują struktury w stylu C, mogą być wywoływane z funkcji zarządzanych przy użyciu funkcji P/Invoke. Mimo że zalecamy używanie funkcji międzyoperacyjności języka C++ zamiast P/Invoke, ponieważ funkcja P/Invoke zapewnia niewielkie raportowanie błędów w czasie kompilacji, nie jest bezpieczna dla typu i może być żmudna do zaimplementowania, jeśli niezarządzany interfejs API jest spakowany jako biblioteka DLL, a kod źródłowy nie jest dostępny, P/Invoke jest jedyną opcją. W przeciwnym razie zapoznaj się z następującymi dokumentami:

Domyślnie struktury natywne i zarządzane są rozmieszczone inaczej w pamięci, dlatego pomyślne przekazywanie struktur przez granicę zarządzaną/niezarządzaną wymaga dodatkowych kroków w celu zachowania integralności danych.

W tym dokumencie wyjaśniono kroki wymagane do zdefiniowania zarządzanych odpowiedników struktur natywnych oraz sposobu przekazywania wynikowych struktur do funkcji niezarządzanych. W tym dokumencie przyjęto założenie, że używane są proste struktury , które nie zawierają ciągów ani wskaźników. Aby uzyskać informacje na temat współdziałania nienależących do blittable, zobacz Using C++ Interop (Implicit PInvoke)( Using C++ Interop (Implicit PInvoke) (Używanie międzyoperacyjności języka C++ (Niejawna funkcja PInvoke). Funkcja P/Invoke nie może mieć typów niezwiązanych z wartością zwracaną. Typy blittable mają tę samą reprezentację w kodzie zarządzanym i niezarządzanym. Aby uzyskać więcej informacji, zobacz Blittable and Non-Blittable Types (Typy nielittable).

Najpierw przeprowadzanie marshalingu prostych struktur blittable w granicach zarządzanych/niezarządzanych wymaga zdefiniowania zarządzanych wersji każdej struktury natywnej. Struktury te mogą mieć dowolną nazwę prawną; nie ma relacji między natywną i zarządzaną wersją dwóch struktur innych niż ich układ danych. Dlatego ważne jest, aby wersja zarządzana zawierała pola o tym samym rozmiarze i w tej samej kolejności co wersja natywna. (Nie ma mechanizmu zapewnienia, że zarządzane i natywne wersje struktury są równoważne, więc niezgodności nie staną się widoczne do czasu wykonywania. Obowiązkiem programisty jest zapewnienie, że obie struktury mają ten sam układ danych).

Ponieważ elementy członkowskie struktur zarządzanych są czasami zmieniane na potrzeby wydajności, należy użyć atrybutu StructLayoutAttribute , aby wskazać, że struktura jest rozmieszczona sekwencyjnie. Dobrym pomysłem jest również jawne ustawienie pakowania struktury tak samo jak to, które jest używane przez strukturę natywną. (Mimo że domyślnie język Visual C++ używa 8-bajtowego pakowania struktury dla kodu zarządzanego).

  1. Następnie użyj polecenia DllImportAttribute , aby zadeklarować punkty wejścia, które odpowiadają wszelkim funkcjom niezarządzanym, które akceptują strukturę, ale użyj zarządzanej wersji struktury w podpisach funkcji, co jest punktem wyjścia, jeśli używasz tej samej nazwy dla obu wersji struktury.

  2. Teraz zarządzany kod może przekazać zarządzaną wersję struktury do funkcji niezarządzanych, tak jakby były one rzeczywiście zarządzane. Te struktury można przekazać za pomocą wartości lub odwołania, jak pokazano w poniższym przykładzie.

Niezarządzane i zarządzane moduły

Poniższy kod składa się z niezarządzanego i zarządzanego modułu. Moduł niezarządzany to biblioteka DLL, która definiuje strukturę o nazwie Location i funkcję o nazwie GetDistance, która akceptuje dwa wystąpienia struktury Location. Drugi moduł to zarządzana aplikacja wiersza polecenia, która importuje funkcję GetDistance, ale definiuje ją pod względem zarządzanego odpowiednika struktury Location, MLocation. W praktyce ta sama nazwa prawdopodobnie będzie używana dla obu wersji struktury; jednak w tym miejscu jest używana inna nazwa, aby zademonstrować, że prototyp DllImport jest zdefiniowany pod względem wersji zarządzanej.

Należy pamiętać, że żadna część biblioteki DLL nie jest widoczna w kodzie zarządzanym przy użyciu tradycyjnej dyrektywy #include. W rzeczywistości biblioteka DLL jest dostępna tylko w czasie wykonywania, więc problemy z funkcjami zaimportowanymi z biblioteką DllImport nie zostaną wykryte w czasie kompilacji.

Przykład: niezarządzany moduł DLL

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

Przykład: zarządzany moduł aplikacji wiersza polecenia

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

Zobacz też

Używanie jawnej funkcji PInvoke w języku C++ (atrybut DllImport)