سلسلة net. للمحترفين خدمات استدعاء المنصة Platform Invoke في dot Net


رضوى العربي

خدمات استدعاء المنصة Platform Invocation Services

يُشار إلى الشيفرة التي تُنفَّذ داخل بيئة التَّنفيذ المشتركة (CLR) باسم الشيفرة المُدارة (managed code) بخلاف الشيفرة التي تُنفَّذ خارجها، والتي يُشار إليها بطبيعة الحال باسم الشيفرة غير المُدارة (unmanaged code).

تَتوفَّر العديد من الطرائق لتَسهِيل العمليات بين الشيفرات (interoperability) من كِلَا النوعين. تُعدّ خدمات اِستدعاء المنصة Platform Invocation Services إحداها.

استدعاء شيفرة غير مُدارة من أُخرى مُدارة

يُمكِنك استدعاء إحدى الدوال غير المُدارة مثل دوال واجهة ويندوز لبرمجة التطبيقات Windows API -في حالة عدم وجودها بمكتبات الأصناف المُدارة (class libraries)- عن طريق التََّصرِيح عن تابع خارجي ساكن (static extern) يَحمِل نفس بَصمة الدالة المطلوب استدعائها. سيُعدّ هذا التابع بمثابة مُمثِل للدالة غير المُدارة بحيث تستطيع استدعائها باستدعائه. لتحقيق ذلك، لابُدّ أن يُزَخرَف هذا التابع بالسمة DllImportAttribute مع تمرير اسم مكتبة dll التي توجد بها تلك الدالة كمُعامِل للسمة.

عند استدعائك لهذا التابع، ستُحمِّل خدمات استدعاء المنصة Platform Invocation Services مكتبة dll المُخصَّصة، ثم تَستدعِي الدالة غير المُدارة المُناظِرة للتابع.

ستَحتاج إلى تَضمِين فضاء الاسم System.Runtime.InteropServices.

انظر المثال التالي:

using System.Runtime.InteropServices;

class PInvokeExample
{
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern uint MessageBox(IntPtr hWnd, String text, String caption, int options);

    public static void test()
    {
        MessageBox(IntPtr.Zero, "Hello!", "Message", 0);
    }
}

اِستعِن بتوثيق pinvoke.net قبل التَّصرِيح عن تابع خارجي مُمثِل لإحدى دوال واجهة ويندوز لبرمجة التطبيقات Windows API، فغالبًا ستَجِدْ معلومات عن الطريقة المُلائمة للتَّصرِيح عنه مع جميع الأنواع المطلوبة كمُعامِلات أو كنوع للقيمة المُعادة بالإضافة إلى بعض الأمثلة التوضيحية.

ترتيب الأنواع (Marshalling)

في حالة وجود مُعامِلات للدوال غير المُدارة أو قيم مُعادَة منها، فغالبًا ما تَتَمَكَّن خدمات استدعاء المنصة Platform Invocation Services من تحويل أنواع ‎.NET المُدارة -بالتحديد الأنواع البسيطة- إلى أنواع المكتبة المُستخدَمة ضِمْن الاستدعاء والعكس تحويلًا أتوماتيكيًا وبدون تعليمات إضافية. تُعرَف هذه العملية باسم الترتيب (Marshalling).

أما إذا كان المُعامِل من نوع مُركَّب مثل struct أو union، فستَحتَاج إلى التَّصرِيح عن صنف جديد struct أو class بشيفرة الـ c#‎ الخاصة بك. يَعمَل هذا الصنف كمُمثِل للنوع غير المُدار، ولابُدّ أن يُزخرَف بالسمة StructLayoutAttribute لإعلام المُرَتِّب (marshaler) بطريقة رَبْط الحُقول (mapping). قد تحتاج أيضًا إلى مزيد من التَخصِيص.

ترتيب النوع union

يَستعرِض المثال التالي تَّصرِيح مكتبة c++‎ عن صنف union مُكَّون من حقول من نوع القيمة فقط:

typedef union
{
    char c;
    int i;
} CharOrInt

في هذه الحالة، تستطيع اِستخدَام LayoutKind.Explicit كقيمة لمُعامِل السمة StructLayout، كالتالي:

[StructLayout(LayoutKind.Explicit)]
public struct CharOrInt
{
    [FieldOffset(0)]
    public byte c;
    [FieldOffset(0)]
    public int i;
}

أما إذا كان الصنف union مُكَّون من حقول من نَوعي القيمة والمَرجِع، كالتالي:

typedef union
{
    char text[128];
    int i;
} TextOrInt;

في هذه الحالة، لا يُمكِنك مجرد اِستخدَام السمة FieldOffset كالمثال الأسبق. وإنما ستحتاج في الغالب إلى تَخصِيص عملية الترتيب (marshaling).

مع ذلك، يُعدّ المثال بالأعلى بسيطًا نوعا ما، وتَتوفَّر طريقة مُبسَّطة باِستخدَام القيمة LayoutKind.Sequential كقيمة لمُعامِل السمة StructLayout مما يعني طريقة تسلسُليّة لرَبْط الحقول (mapping).

قد تحتاج بعض أنواع حقول الصنف إلى الزَخرَفة باستخدَام السمة MarshalAs لتَخصِيص النوع غير المُدار المُناظِر.

[StructLayout(LayoutKind.Sequential)]
public struct TextOrInt
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
    public byte[] text;
    public int i { get { return BitConverter.ToInt32(text, 0); } }
}

ترتيب النوع struct

يستعرض المثال التالي تَّصرِيح مكتبة c++‎ عن صنف struct:

typedef struct _PERSON
{
    int age;
    char name[32];
} PERSON, *LP_PERSON;

void GetSpouse(PERSON person, LP_PERSON spouse);

بصورة مشابهة للأنواع unions. ستحتاج إلى تَعرِيف نوع جديد مُزخرَف باستخدام السمة StructLayout يُمثِل نَظيره غير المُدار، كالتالي:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct PERSON
{
    public int age;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string name;
}

[DllImport("family.dll", CharSet = CharSet.Auto)]
public static extern bool GetSpouse(PERSON person, ref PERSON spouse);

ترتيب حقل مصفوفة غير مَعْلومة الحجم (إرسال)

بصمة c++‎:

typedef struct
{
    int length;
    int *data;
} VECTOR;

void SetVector(VECTOR &vector);

في هذه الحالة، ينبغي أن يُمرَّر حقل المصفوفة غير مَعْلومة الحجم كقيمة من النوع IntPtr مع الاستدعاء الصريح للتابعين Marshal.AllocHGlobal()‎ و Marshal.FreeHGlobal()‎ من أجل تَخصِيص مساحة بالذاكرة لهذا الحقل وتَّفرِيغها على الترتيب، كالتالي:

[StructLayout(LayoutKind.Sequential)]
public struct VECTOR : IDisposable
{
    int length;
    IntPtr dataBuf;
    public int[] data
    {
        set
        {
            FreeDataBuf();
            if (value != null && value.Length > 0)
            {
                dataBuf = Marshal.AllocHGlobal(value.Length * Marshal.SizeOf(value[0]));
                Marshal.Copy(value, 0, dataBuf, value.Length);
                length = value.Length;
            }
        }
    }
    void FreeDataBuf()
    {
        if (dataBuf != IntPtr.Zero)
        {
            Marshal.FreeHGlobal(dataBuf);
            dataBuf = IntPtr.Zero;
        }
    }
    public void Dispose()
    {
        FreeDataBuf();
    }
}

[DllImport("vectors.dll")]
public static extern void SetVector([In]ref VECTOR vector);

ترتيب حقل مصفوفة غير مَعْلومة الحجم (استقبال)

بصمة C++‎:

typedef struct
{
    char *name;
} USER;
bool GetCurrentUser(USER *user);

عند استقبال شيفرة مُدارة لمصفوفة غير مَعْلومة الحجم من أُخرى غير مُدارة، فإن مساحة الذاكرة التي تَشغَلها المصفوفة تكون بطبيعة الحال مُخصَّصة بواسطة دوال الشيفرة غير المُدارة.

في هذه الحالة، ينبغي للشيفرة المُدارة أن تَستقبِل البيانات إلى مُتغير من النوع IntPrt ثم تَقرَّأ بيانات المُخزِّن المؤقت (buffer) إلى مصفوفة مُدارة.

إذا كانت المصفوفة من النوع string، يُمكن استخدام التابع Marshal.PtrToStringAnsi()‎ المُخَصَّص لهذا الغرض.

[StructLayout(LayoutKind.Sequential)]
public struct USER
{
    IntPtr nameBuffer;
    public string name { 
        get { return Marshal.PtrToStringAnsi(nameBuffer); } 
    }
}

[DllImport("users.dll")]
public static extern bool GetCurrentUser(out USER user);

ترتيب مصفوفة

إذا كانت مصفوفة من نوع بسيط:

[DllImport("Example.dll")]
static extern void SetArray(
    [MarshalAs(UnmanagedType.LPArray, SizeConst = 128)]
    byte[] data);

إذا كانت مصفوفة من النوع string:

[DllImport("Example.dll")]
static extern void SetStrArray(string[] textLines);

ترجمة -وبتصرف- للفصل Platform Invoke من كتاب ‎.NET Framework Notes for Professionals





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن