0Pricing
C# Academy · درس

LibraryImport وP/Invoke المولّد من المصدر

استخدموا [LibraryImport] ‏(C# 11+) لتنظيم مولّد من المصدر ومتوافق مع AOT، ويتفوّق أداءً على DllImport.

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

لماذا LibraryImport؟

تم تقديم [LibraryImport] في .NET 7 ‏(C# 11) ليكون بديلًا متوافقًا مع AOT ويعتمد على توليد الشيفرة للمصدر بدلًا من [DllImport]. يعتمد DllImport التقليدي على marshaling وقت التشغيل عبر reflection، ما يسبب مشكلات مع Native AOT. أما LibraryImport فيولّد شيفرة marshaling كاملة في وقت الترجمة.

التصريح عن دالة LibraryImport

ضع السمة [LibraryImport] على دالة static partial. سيملأ مولّد المصدر التنفيذ تلقائيًا. ويجب أيضًا تعريف الفئة الحاوية على أنها partial.

using System.Runtime.InteropServices;

internal static partial class NativeMethods
{
    // Source generator creates the P/Invoke body at compile time
    [LibraryImport("mylib", EntryPoint = "add_integers")]
    internal static partial int AddIntegers(int a, int b);

    // String marshaling must be explicit in LibraryImport
    [LibraryImport("mylib", StringMarshalling = StringMarshalling.Utf8)]
    internal static partial int ProcessString(string text);
}

Marshaling السلاسل النصية في LibraryImport

على خلاف DllImport، يتطلب LibraryImport تحديد marshaling السلاسل النصية بشكل صريح. يمكنك استخدام تعداد StringMarshalling أو سمات MarshalAs، مما يجعل تكلفة marshaling واضحة وقابلة للتحكم.

// Option 1: StringMarshalling enum (all strings in method)
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16)]
static partial bool CreateDirectory(string lpPathName, nint lpSecurityAttributes);

// Option 2: Per-parameter MarshalAs
[LibraryImport("libc")]
static partial int Open(
    [MarshalAs(UnmanagedType.LPUTF8Str)] string path,
    int flags);

// Option 3: MarshalUsing for custom marshalers
[LibraryImport("mylib")]
static partial void Process(
    [MarshalUsing(typeof(Utf8StringMarshaller))] string name);

Marshaling البنى

بالنسبة إلى البنى، أضف [NativeMarshalling] لتحديد كيفية ربط النوع المُدار بتمثيله الأصلي. يستخدم مولّد المصدر نوع marshaller لإنتاج شيفرة آمنة تقلل التخصيصات.

[NativeMarshalling(typeof(PointMarshaller))]
public struct Point
{
    public int X;
    public int Y;
}

[CustomMarshaller(typeof(Point), MarshalMode.Default, typeof(PointMarshaller))]
public static class PointMarshaller
{
    public static Point ConvertToManaged(NativePoint native)
        => new Point { X = native.X, Y = native.Y };
    public static NativePoint ConvertToUnmanaged(Point managed)
        => new NativePoint { X = managed.X, Y = managed.Y };

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

مقارنة DllImport وLibraryImport

يُفسَّر DllImport في وقت التشغيل، ما يؤدي إلى بدء تشغيل بطيء واعتماد على reflection وعدم التوافق مع trimming في AOT. أما LibraryImport فيولّد شيفرة C# محسّنة وقت البناء، دون reflection وقت التشغيل، وبأمان مع trimming، وبسرعة أعلى يمكن قياسها في اختبارات الأداء.

// Old — DllImport (still works, but avoid for new AOT code)
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool MoveFile(string src, string dst);

// New — LibraryImport (AOT-safe, source-generated)
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16,
                SetLastError = true)]
static partial bool MoveFile(string src, string dst);
// The compiler generates the actual P/Invoke wrapper body

SetLastError ومعالجة الأخطاء

عيّن SetLastError = true في [LibraryImport] لالتقاط رمز خطأ نظام التشغيل. استخدم Marshal.GetLastPInvokeError()، وهو الخيار المفضل، أو Marshal.GetLastWin32Error() لاسترداده بعد الاستدعاء.

[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16,
                SetLastError = true)]
static partial bool CreateDirectory(string path, nint secAttr);

bool ok = CreateDirectory(@"C:\Temp\NewDir", 0);
if (!ok)
{
    int err = Marshal.GetLastPInvokeError();
    // err is ERROR_ALREADY_EXISTS (183) if folder exists
    throw new Win32Exception(err);
}

Span<T> وMarshaling الذاكرة

من مزايا P/Invoke المولّد من المصدر دعمه الكامل لـ Span<T>. إن تمرير ReadOnlySpan<byte> يتجنب تثبيت الذاكرة والتخصيص الذي كانت تتطلبه المصفوفات مع DllImport.

[LibraryImport("mylib")]
static partial int ProcessBuffer(
    ReadOnlySpan<byte> data,
    int length);

// Usage — no fixed or GCHandle needed
byte[] buffer = Encoding.UTF8.GetBytes("hello");
int result = ProcessBuffer(buffer, buffer.Length);

// For output buffers use Span<byte>
[LibraryImport("mylib")]
static partial int FillBuffer(Span<byte> output, int maxLen);

تفعيل توليد المصدر

يُفعَّل توليد المصدر تلقائيًا عند الإشارة إلى مساحة الأسماء System.Runtime.InteropServices في مشروع يستهدف .NET 7 أو إصدارًا أحدث. أما الأهداف الأقدم فتتطلب حزمة المحلل Microsoft.Interop.SourceGeneration.

<!-- In your .csproj — no extra package needed on .NET 7+ -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <!-- Enable Roslyn analyzers and source generators -->
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <!-- AllowUnsafeBlocks may be needed for some marshalers -->
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
</Project>

فحص الشيفرة المُولَّدة

أضف <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> إلى ملف csproj لكتابة الملفات المُولَّدة في obj/. يتيح لك ذلك فحص ما ينتجه مولّد المصدر بدقة، وهو تمرين تعليمي ممتاز.

// The generator emits something like this for a LibraryImport method:
// (simplified view of generated stub)

internal static partial int AddIntegers(int a, int b)
{
    // Direct call — no reflection, no boxing, fully inlineable
    return __PInvoke(a, b);

    [System.Runtime.InteropServices.DllImportAttribute(
        "mylib",
        EntryPoint = "add_integers",
        ExactSpelling = true)]
    static extern int __PInvoke(int a, int b);
}

مثال عملي: تغليف مكتبة C

من الأنماط الشائعة تعريف فئة تغليف ثابتة تتضمن جميع تصريحات LibraryImport، ثم توفير واجهة برمجة تطبيقات آمنة وعالية المستوى فوقها. اجعل التصريحات partial داخلية أو خاصة، ولا تُظهر للعامة سوى الأغلفة الآمنة.

internal static partial class LibSodiumNative
{
    private const string LibName = "libsodium";

    [LibraryImport(LibName, EntryPoint = "crypto_secretbox_keybytes")]
    internal static partial nuint KeyBytes();

    [LibraryImport(LibName, EntryPoint = "crypto_secretbox_easy")]
    internal static partial int SecretBoxEasy(
        Span<byte> ciphertext,
        ReadOnlySpan<byte> message,
        ulong mlen,
        ReadOnlySpan<byte> nonce,
        ReadOnlySpan<byte> key);
}

// Public safe wrapper hides the native signature
public static byte[] Encrypt(byte[] message, byte[] key, byte[] nonce)
{
    var cipher = new byte[message.Length + 16];
    LibSodiumNative.SecretBoxEasy(cipher, message, (ulong)message.Length, nonce, key);
    return cipher;
}

اختبار سريع

ما الميزة الأساسية لـ [LibraryImport] مقارنةً بـ [DllImport]؟

مراجعة: LibraryImport وP/Invoke المولّد من المصدر

أهم النقاط:

  • يستبدل [LibraryImport] السمة [DllImport] لتوفير P/Invoke آمن مع AOT
  • يُولَّد marshaling وقت الترجمة، دون reflection وقت التشغيل
  • يتطلب دالة static partial وفئة partial
  • يجب تحديد marshaling السلاسل النصية صراحةً عبر StringMarshalling أو MarshalAs
  • دعم كامل لـ Span<T> دون تكلفة تثبيت الذاكرة
  • يمكن فحص الشيفرة المُولَّدة باستخدام EmitCompilerGeneratedFiles

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

هل درس «LibraryImport وP/Invoke المولّد من المصدر» مجاني؟

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

ماذا ستتعلم في «LibraryImport وP/Invoke المولّد من المصدر»؟

استخدموا [LibraryImport] ‏(C# 11+) لتنظيم مولّد من المصدر ومتوافق مع AOT، ويتفوّق أداءً على DllImport. تتمرن على C# Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

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

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

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

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

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

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

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

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