كيفية القيام بما يلي: تنظيم وإرسال البنيات بـاستخدام PInvoke

يشرح هذا المقال كيفية إستدعاء الدالات الأصلية التي تقبل سلاسل نمط C من دوال مدارة مع توفير مثيل String باستخدام P / Invoke. نشجع مبرمجي ++Visual C على استخدام ميزات توافق C++ بدلاً من ذلك لأن P/Invoke يوفر تقارير صغيرة لأخطاء وقت التحويل البرمجي , و هى غير آمنة النوع و شاقة التنفيذ. إذا تم حزم API الغير المدار كـ DLL ، و مصدر التعليمات البرمجية غير متوفر، فإن P/Invoke هو الخيار الوحيد. وإلا, فراجع المواضيع التالية:

بشكل افتراضي،يتم تخطيط البنيات الأصلية و المدارة بشكل مختلف في الذاكرة, لذا فإن تمرير البنيات بنجاح عبر الحدود مُدارة/غير مُدارة يتطلب خطوات إضافية للاحتفاظ بتكامل البيانات.

هذا الموضوع يوضح الخطوات المطلوبة لتعريف بدائل مدارة للبنيات الأصلية و كيف يمكن تمرير البنيات الناتجة إلى وظائف غير مدارة. هذا الموضوع يفترض أن البنيات البسيطة — تلك التي لا تحتوي على سلاسل أو مؤشرات — يتم استخدامها. للحصول على معلومات حول إمكانية التشغيل المتداخل غير المشترك (non-blittable)، راجع استخدام PInvoke) C++ Interop الضمني ).

تنظيم بنيات مشترك (blittable) و بسيطة عبر حدود مُدارة/غير مُدارة يتطلب أولاً أن يتم تعريف الإصدارات المدارة لكل هيكل أصلي. هذه البنيات يمكن أن يكون لها أي اسم قانوني; لا توجد أية علاقة بين الإصدار الأصلي و المدار من البنيتين عدا تخطيط البيانات الخاصة بهم. وبذلك يكون حيوياً أن يحتوي الإصدار المدار على الحقول الموجودة بنفس الحجم وفي نفس ترتيب الإصدار الأصلي. (لا توجد أي آلية للتأكد من أن الإصدارات المدارة والأصلية للبنية متكافئة, لذا لن يصبح عدم التوافق ظاهراً حتى وقت التشغيل. إنها مسؤولية المبرمج أن يتأكد من أن البنيتين لهما نفس تخطيط البيانات.)

لأنه يتم في بعض الأحيان إعادة ترتيب أعضاء البنيات المدارة لأغراض الأداء ، لذلك فمن الضروري استخدام السمة
StructLayoutAttribute لتشير إلى أن البنية تم تخطيطها بشكل تسلسلي. إنها لفكرة جيدة أن يتم بشكل صريح تعيين إعداد حَزم البنية ليكون نفس الذي تم استخدامه من قِبَل البنية الأصلية. (على الرغم من أنه افتراضياً، يستخدم Visual C++ حزم بنية 8 بايت لكلٍ من التعليمات البرمجية المُدارة.)

  1. بعد ذلك، استخدم DllImportAttribute للتصريح بنقاط الإدخال التي تتوافق مع أية دوال غير مدارة تقبل البنية، و لكن استخدم الإصدار المدار من البنية في توقيعات الدالة، وهذه نقطة جدلية إذا كنت تستخدم نفس الاسم لكلا الإصدارين من البنية.

  2. الآن يمكن تمرير الإصدار المدار من البنية للدالة الغير المدارة كما لو أنها دالات مدارة بالفعل. يمكن تمرير هذه البنيات بالقيمة أو بالمرجع ، كما هو موضّح في المثال التالي.

مثال

التعليمات البرمجية التالية تتألف من وحدات نمطية مدارة و غير مدارة. الوحدة النمطية غير المدارة هي DLL يقوم بتعريف بنية تسمى موقع (Location) و دالة تسمى GetDistance تقبل مثيلين من البنية موقع. الوحدة النمطية الثانية هي تطبيق سطر أوامر مدار يستورد دالة GetDistance ، ولكن يعرفها في صورة المدارة المكافئة لبنية الموقع، MLocation. في الواقع العملي، من المحتمل استخدام نفس الاسم لكل من إصداري البنية; ومع ذلك، يُستخدم اسم مختلف هنا لشرح أنه يتم تعريف النموذج الأولي DllImport في صورة الإصدار المدار.

يتم برمجيا ترجمة الوحدة النمطية المُدارة بواسطة /clr ولكن /clr:pure يصلح أيضاً .

لاحظ انه لا يوجد جزء من DLL يتعرض للتعليمات البرمجية المدارة باستخدام توجيه # التقليدي. في الحقيقة، الوصول إلى DLL يتم في وقت التشغيل فقط ،لذلك المشاكل مع الدالات المستوردة مع DllImport لن يتم الكشف عنها في وقت التحويل البرمجي.

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

راجع أيضًا:

موارد أخرى

استخدام PInvoke Explicit في ++C (سمة DllImport)