0Pricing
C# Academy · درس

أساسيات P/Invoke

صرّحوا عن الدوال الأصلية واستدعُوها باستخدام DllImport، وافهموا تنظيم الأنواع البدائية والسلاسل والبنى.

أساسيات P/Invoke درس مجاني في C# Academy على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C# Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C# Academy 4 دروس في المجموع.

ما هو P/Invoke؟

تتيح Platform Invocation Services (P/Invoke) لـ C# استدعاء الدوال الموجودة في المكتبات الأصلية المشتركة (.dll في Windows، و.so في Linux، و.dylib في macOS). وهي الآلية القياسية لاستدعاء واجهات Win32 API أو أي مكتبة تستخدم C-ABI من .NET.

أول استدعاء لـ P/Invoke

صرّحوا عن الدالة الأصلية باستخدام [DllImport] وحددوا اسم المكتبة. تتولى CLR العثور على المكتبة وتحميلها، وتحويل المعلمات.

using System.Runtime.InteropServices;

// Calling MessageBoxW from user32.dll (Windows)
internal static partial class NativeMethods
{
    [DllImport("user32.dll",
        EntryPoint = "MessageBoxW",
        CharSet = CharSet.Unicode,
        SetLastError = true)]
    internal static extern int MessageBox(
        IntPtr hwnd,
        string text,
        string caption,
        uint type);
}

// Call it:
NativeMethods.MessageBox(IntPtr.Zero, "Hello!", "P/Invoke", 0);

// Windows-only — wrap in RuntimeInformation.IsOSPlatform check
// for cross-platform code

استدعاء المكتبات الأصلية المخصصة

يعمل P/Invoke مع أي دالة مُصدّرة باستخدام C، وليس مع واجهات نظام التشغيل فقط. أنشئوا مكتبة أصلية، وصدّروا الدوال باستخدام ربط C، ثم استدعوها من C#.

// mymath.h / mymath.c:
// extern "C" double AddNumbers(double a, double b) { return a + b; }
// Compile: gcc -shared -o libmymath.so mymath.c

// C# declaration:
[DllImport("mymath",              // libmymath.so / mymath.dll
    EntryPoint = "AddNumbers",
    CallingConvention = CallingConvention.Cdecl)]
private static extern double AddNumbers(double a, double b);

// Call:
double result = AddNumbers(3.14, 2.72); // 5.86

// .NET resolves the library via:
// 1. Absolute path if provided
// 2. App directory
// 3. OS library path (PATH / LD_LIBRARY_PATH / DYLD_LIBRARY_PATH)

مارشلة الأنواع الأساسية

تجري CLR تلقائيًا مارشلة لمعظم الأنواع البدائية بين التمثيلات المُدارة والأصلية. ويساعدكم فهم عمليات المطابقة على تجنب الأخطاء الدقيقة.

// C Type       → C# Type
// int (32-bit)  → int  or  System.Int32
// long (64-bit) → long or  System.Int64
// float         → float
// double        → double
// bool          → [MarshalAs(UnmanagedType.Bool)] bool
// char*  (ANSI) → string  (CharSet.Ansi)
// wchar_t*      → string  (CharSet.Unicode)
// void*         → IntPtr
// size_t        → UIntPtr

// Example with explicit marshalling:
[DllImport("libc", EntryPoint = "strlen", CharSet = CharSet.Ansi)]
private static extern UIntPtr StrLen(
    [MarshalAs(UnmanagedType.LPStr)] string s);

int len = (int)StrLen("hello"); // 5

تمرير البنى إلى الكود الأصلي

استخدموا [StructLayout(LayoutKind.Sequential)] لضمان ترتيب البنية في الذاكرة بالطريقة التي يتوقعها الكود الأصلي تمامًا.

// C struct:
// struct Point { int x; int y; };
// void DrawPoint(struct Point p);

[StructLayout(LayoutKind.Sequential)]
public struct Point
{
    public int X;
    public int Y;
}

[DllImport("graphics", EntryPoint = "DrawPoint",
    CallingConvention = CallingConvention.Cdecl)]
private static extern void DrawPoint(Point p);

// Pass by value:
DrawPoint(new Point { X = 10, Y = 20 });

// Pass by pointer (ref or out):
[DllImport("graphics", EntryPoint = "GetCenter",
    CallingConvention = CallingConvention.Cdecl)]
private static extern void GetCenter(out Point center);
GetCenter(out var pt);

معالجة الأخطاء: GetLastWin32Error

تُبلغ دوال Windows API عن الأخطاء عبر GetLastError(). اضبطوا SetLastError = true في [DllImport]، ثم استدعوا Marshal.GetLastWin32Error() بعد الاستدعاء.

[DllImport("kernel32.dll",
    EntryPoint = "CreateFileW",
    CharSet = CharSet.Unicode,
    SetLastError = true)]
private static extern IntPtr CreateFile(
    string fileName,
    uint desiredAccess,
    uint shareMode,
    IntPtr securityAttributes,
    uint creationDisposition,
    uint flagsAndAttributes,
    IntPtr templateFile);

const uint GENERIC_READ = 0x80000000;
const uint OPEN_EXISTING  = 3;

var handle = CreateFile("test.txt", GENERIC_READ, 0,
    IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);

if (handle == (IntPtr)(-1))
{
    int error = Marshal.GetLastWin32Error();
    throw new System.ComponentModel.Win32Exception(error);
}

مارشلة السلاسل والمخازن المؤقتة

تتطلب مارشلة السلاسل انتباهًا دقيقًا إلى الترميز والملكية. استخدموا StringBuilder لمخازن الإخراج المؤقتة، واستخدموا سمات MarshalAs لتحديد الترميز.

// Read into a buffer:
[DllImport("kernel32.dll",
    EntryPoint = "GetComputerNameW",
    CharSet = CharSet.Unicode,
    SetLastError = true)]
private static extern bool GetComputerName(
    System.Text.StringBuilder lpBuffer,
    ref uint nSize);

uint size = 256;
var buffer = new System.Text.StringBuilder((int)size);
if (GetComputerName(buffer, ref size))
    Console.WriteLine(buffer.ToString());

// Return a string owned by native code (don't free it):
[DllImport("mylib", CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.LPStr)]
private static extern string GetVersion();

مؤشرات الدوال وعمليات الاستدعاء الراجعة

مرّروا المفوضات المُدارة كمؤشرات دوال بلغة C. تنشئ CLR دالة وسيطة، لكن يجب الاحتفاظ بالمفوض حيًا، أي الاحتفاظ بمرجع إليه، وإلا فستجمعه GC ويتعطل الكود الأصلي.

// C callback signature: typedef int (*Comparer)(const void*, const void*);
// void qsort(void* base, size_t nitems, size_t size, Comparer compar);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int CompareCallback(IntPtr a, IntPtr b);

[DllImport("libc", CallingConvention = CallingConvention.Cdecl)]
private static extern void QSort(
    int[] data, UIntPtr count, UIntPtr size, CompareCallback compare);

// Keep the delegate ALIVE for the duration of the call:
private static readonly CompareCallback _compare =
    (a, b) => Marshal.ReadInt32(a).CompareTo(Marshal.ReadInt32(b));

var data = new[] { 5, 2, 8, 1, 3 };
QSort(data, (UIntPtr)data.Length, (UIntPtr)sizeof(int), _compare);

واجهة NativeLibrary البرمجية

توفر الفئة NativeLibrary تحميلًا صريحًا للمكتبات، وحلًا لمؤشرات الدوال، وتخصيصًا للمسارات عبر الأنظمة الأساسية، وهي البديل الحديث لحل أسماء المكتبات تلقائيًا.

using System.Runtime.InteropServices;

// Explicit load:
var handle = NativeLibrary.Load("/usr/lib/libssl.so.3");

// Resolve a function pointer:
var addPtr = NativeLibrary.GetExport(handle, "AddNumbers");
var addFn = Marshal.GetDelegateForFunctionPointer<Func<double,double,double>>(addPtr);
double result = addFn(1.0, 2.0);

// Unload:
NativeLibrary.Free(handle);

// Custom resolver (called when DllImport can't find a library):
NativeLibrary.SetDllImportResolver(typeof(MyNative).Assembly,
    (libName, assembly, searchPath) =>
    {
        if (libName == "mymath")
            return NativeLibrary.Load("/opt/mymath/libmymath.so");
        return IntPtr.Zero;
    });

من الواقع العملي: استدعاء OpenSSL

مثال عملي: استدعاء دالة SHA-256 للتجزئة في OpenSSL من C# باستخدام P/Invoke.

internal static class OpenSslInterop
{
    [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
    private static extern IntPtr EVP_MD_CTX_new();

    [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
    private static extern void EVP_MD_CTX_free(IntPtr ctx);

    [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
    private static extern IntPtr EVP_sha256();

    [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
    private static extern int EVP_DigestInit_ex(IntPtr ctx, IntPtr type, IntPtr engine);

    // In practice, use System.Security.Cryptography.SHA256 instead:
    // var hash = SHA256.HashData(data);
    // P/Invoke to OpenSSL is only needed when you require
    // non-managed cryptographic operations or specific OpenSSL features
}

تحقق سريع

لماذا يجب الاحتفاظ بالمفوض حيًا عند تمريره كاستدعاء راجع أصلي؟

مراجعة: أساسيات P/Invoke

أهم النقاط:

  • P/Invoke: استدعاء دوال C-ABI الأصلية عبر تصريحات [DllImport]
  • يتولى CLR عملية marshaling للأنواع البدائية تلقائيًا؛ وأضف التعليقات التوضيحية [MarshalAs] للسلاسل النصية والأنواع المخصصة
  • [StructLayout(LayoutKind.Sequential)]: ضمان توافق تخطيط ذاكرة البنية مع التوقعات الأصلية
  • عيّن SetLastError = true ثم استدعِ Marshal.GetLastWin32Error() لمعالجة أخطاء Win32
  • حافظ على مراجع المفوضات حية عند تمريرها كدوال رد نداء أصلية
  • NativeLibrary: تحميل صريح، وحلّ الرموز، ومحلل مخصص للتحكم في المسارات عبر الأنظمة الأساسية

الأسئلة الشائعة

هل درس «أساسيات P/Invoke» مجاني؟

نعم — نص درس «أساسيات P/Invoke» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C# Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C# Academy 4 دروس في المجموع.

ماذا ستتعلم في «أساسيات P/Invoke»؟

صرّحوا عن الدوال الأصلية واستدعُوها باستخدام DllImport، وافهموا تنظيم الأنواع البدائية والسلاسل والبنى. تتمرن على C# Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ C# Academy؟

لا تُشترط خبرة سابقة. C# Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «أساسيات P/Invoke»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس C# Academy هذا؟

نعم. كل درس في C# Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. أساسيات P/Invoke
  2. LibraryImport وP/Invoke المولّد من المصدر
  3. التعليمات البرمجية غير الآمنة والمؤشرات والمخازن المؤقتة الثابتة
  4. التعامل البيني مع COM والتغليفات القابلة للاستدعاء من وقت التشغيل
← العودة إلى C# Academy