Sdílet prostřednictvím


Postupy: Zařazení struktur pomocí služby PInvoke

Tento dokument vysvětluje, jak nativní funkce, které přijímají struktury ve stylu jazyka C, lze volat ze spravovaných funkcí pomocí volání nespravovaného kódu. Přestože doporučujeme místo volání nespravovaného kódu použít funkce interoperability jazyka C++, protože funkce P/Invoke poskytuje zasílání zpráv o chybách v době kompilace málo, není typově bezpečné a nelze je implementovat, pokud je nespravované rozhraní API zabalené jako knihovna DLL a zdrojový kód není k dispozici, P/Invoke je jedinou možností. V opačném případě se podívejte na následující dokumenty:

Nativní a spravované struktury jsou ve výchozím nastavení rozloženy odlišně v paměti, takže úspěšné předávání struktur napříč spravovanými nebo nespravovanými hranicemi vyžaduje další kroky pro zachování integrity dat.

Tento dokument vysvětluje kroky potřebné k definování spravovaných ekvivalentů nativních struktur a způsobu předání výsledných struktur nespravovaným funkcím. Tento dokument předpokládá, že se použijí jednoduché struktury ( ty, které neobsahují řetězce nebo ukazatele). Informace o nebližovatelné interoperabilitě naleznete v tématu Použití Zprostředkovatele komunikace C++ (Implicit PInvoke). Volání nespravovaného kódu nemůže mít jako návratovou hodnotu neschválitelné typy. Typy Blittable mají stejnou reprezentaci ve spravovaném a nespravovaném kódu. Další informace naleznete v tématu Blittable a Non-Blittable Typy.

Zařazování jednoduchých, blittable struktur napříč spravovanými nebo nespravovanými hranicemi nejprve vyžaduje, aby byly definované spravované verze každé nativní struktury. Tyto struktury mohou mít jakýkoli právní název; mezi nativní a spravovanou verzí obou struktur kromě jejich rozložení dat neexistuje žádný vztah. Proto je důležité, aby spravovaná verze obsahovala pole se stejnou velikostí a ve stejném pořadí jako nativní verze. (Neexistuje žádný mechanismus pro zajištění, že spravované a nativní verze struktury jsou ekvivalentní, takže nekompatibility se nebudou zjevit, dokud neběží čas. Je zodpovědností programátora zajistit, aby obě struktury měly stejné rozložení dat.)

Vzhledem k tomu, že členové spravovaných struktur jsou někdy přeuspořádané pro účely výkonu, je nutné použít StructLayoutAttribute atribut k označení, že struktura je rozložena postupně. Je také vhodné explicitně nastavit nastavení balení struktury tak, aby bylo stejné jako nastavení používané nativní strukturou. (I když visual C++ ve výchozím nastavení používá pro spravovaný kód balení struktury 8 bajtů.)

  1. Dále použijte DllImportAttribute k deklaraci vstupních bodů, které odpovídají všem nespravovaným funkcím, které přijímají strukturu, ale použijte spravovanou verzi struktury v podpisech funkce, což je moot bod, pokud použijete stejný název pro obě verze struktury.

  2. Spravovaný kód teď může předat spravovanou verzi struktury nespravovaným funkcím, jako by byly vlastně spravované funkce. Tyto struktury lze předat buď hodnotou, nebo odkazem, jak je znázorněno v následujícím příkladu.

Nespravované a spravované moduly

Následující kód se skládá z nespravovaného a spravovaného modulu. Nespravovaný modul je knihovna DLL, která definuje strukturu s názvem Location a funkce s názvem GetDistance, která přijímá dvě instance struktury Umístění. Druhý modul je spravovaná aplikace příkazového řádku, která importuje funkci GetDistance, ale definuje ji z hlediska spravovaného ekvivalentu struktury umístění MLocation. V praxi by se stejný název pravděpodobně použil pro obě verze struktury; Zde se však používá jiný název, který demonstruje, že prototyp DllImport je definován z hlediska spravované verze.

Všimněte si, že žádná část knihovny DLL není vystavena spravovanému kódu pomocí tradiční direktivy #include. Knihovna DLL je ve skutečnosti přístupná pouze za běhu, takže problémy s funkcemi importovanými pomocí dllImport nebudou zjištěny v době kompilace.

Příklad: Nespravovaný modul 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;
}

Příklad: Spravovaný modul aplikace příkazového řádku

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

Viz také

Použití explicitního volání PInvoke v jazyce C++ (atribut DllImport)