Bagikan melalui


Cara: Struktur Marshal Menggunakan PInvoke

Dokumen ini menjelaskan bagaimana fungsi asli yang menerima struct gaya C dapat dipanggil dari fungsi terkelola dengan menggunakan P/Invoke. Meskipun kami menyarankan agar Anda menggunakan fitur Interop C++ alih-alih P/Invoke karena P/Invoke menyediakan sedikit pelaporan kesalahan waktu kompilasi, tidak aman untuk jenis, dan dapat melelahkan untuk diimplementasikan, jika API yang tidak dikelola dikemas sebagai DLL dan kode sumber tidak tersedia, P/Invoke adalah satu-satunya opsi. Jika tidak, lihat dokumen berikut ini:

Secara default, struktur asli dan terkelola ditata secara berbeda dalam memori, sehingga berhasil melewati struktur di seluruh batas terkelola/tidak terkelola memerlukan langkah tambahan untuk mempertahankan integritas data.

Dokumen ini menjelaskan langkah-langkah yang diperlukan untuk menentukan struktur asli yang setara dengan terkelola dan bagaimana struktur yang dihasilkan dapat diteruskan ke fungsi yang tidak dikelola. Dokumen ini mengasumsikan bahwa struktur sederhana — yang tidak berisi string atau pointer — digunakan. Untuk informasi tentang interoperabilitas yang tidak dapat diputar, lihat Menggunakan Interop C++ (PInvoke Implisit). P/Invoke tidak boleh memiliki tipe yang tidak dapat di-blittable sebagai nilai pengembalian. Jenis blittable memiliki representasi yang sama dalam kode terkelola dan tidak terkelola. Untuk informasi selengkapnya, lihat Jenis Blittable dan Non-Blittable.

Menghaluskan struktur sederhana dan dapat dirampungkan di seluruh batas terkelola/tidak terkelola terlebih dahulu mengharuskan versi terkelola dari setiap struktur asli didefinisikan. Struktur ini dapat memiliki nama hukum apa pun; tidak ada hubungan antara versi asli dan terkelola dari dua struktur selain tata letak datanya. Oleh karena itu, sangat penting bahwa versi terkelola berisi bidang yang berukuran sama dan dalam urutan yang sama dengan versi asli. (Tidak ada mekanisme untuk memastikan bahwa versi struktur terkelola dan asli setara, sehingga ketidaksesuaian tidak akan terlihat sampai run time. Ini adalah tanggung jawab programmer untuk memastikan bahwa kedua struktur memiliki tata letak data yang sama.)

Karena anggota struktur terkelola terkadang diatur ulang untuk tujuan performa, perlu menggunakan StructLayoutAttribute atribut untuk menunjukkan bahwa struktur ditata secara berurutan. Sebaiknya atur pengaturan pengemasan struktur secara eksplisit agar sama dengan yang digunakan oleh struktur asli. (Meskipun secara default, Visual C++ menggunakan pengemasan struktur 8 byte untuk kedua kode terkelola.)

  1. Selanjutnya, gunakan DllImportAttribute untuk mendeklarasikan titik masuk yang sesuai dengan fungsi tidak terkelola yang menerima struktur, tetapi gunakan versi terkelola struktur dalam tanda tangan fungsi, yang merupakan titik moot jika Anda menggunakan nama yang sama untuk kedua versi struktur.

  2. Sekarang kode terkelola dapat meneruskan versi terkelola struktur ke fungsi yang tidak dikelola seolah-olah mereka benar-benar fungsi terkelola. Struktur ini dapat diteruskan baik berdasarkan nilai atau referensi, seperti yang ditunjukkan dalam contoh berikut.

Modul tidak terkelola dan terkelola

Kode berikut terdiri dari modul yang tidak dikelola dan terkelola. Modul yang tidak dikelola adalah DLL yang menentukan struktur yang disebut Lokasi dan fungsi yang disebut GetDistance yang menerima dua instans struktur Lokasi. Modul kedua adalah aplikasi baris perintah terkelola yang mengimpor fungsi GetDistance, tetapi mendefinisikannya dalam hal struktur Lokasi yang setara dengan terkelola, MLocation. Dalam praktiknya nama yang sama mungkin akan digunakan untuk kedua versi struktur; namun, nama yang berbeda digunakan di sini untuk menunjukkan bahwa prototipe DllImport didefinisikan dalam hal versi terkelola.

Perhatikan bahwa tidak ada bagian DLL yang diekspos ke kode terkelola menggunakan direktif #include tradisional. Bahkan, DLL diakses pada waktu proses saja, sehingga masalah dengan fungsi yang diimpor dengan DllImport tidak akan terdeteksi pada waktu kompilasi.

Contoh: Modul DLL tidak terkelola

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

Contoh: Modul aplikasi baris perintah terkelola

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

Baca juga

Menggunakan PInvoke Eksplisit di C++ (Atribut DllImport)