0Pricing
C# Academy · บทเรียน

LibraryImport และ P/Invoke ที่สร้างจากซอร์ส

ใช้ [LibraryImport] (C# 11+) สำหรับการจัดเรียงข้อมูลที่สร้างจากซอร์สและรองรับ AOT ซึ่งมีประสิทธิภาพเหนือกว่า DllImport

LibraryImport และ P/Invoke ที่สร้างจากซอร์ส เป็นบทเรียน C# Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C# Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน

เหตุใดจึงใช้ LibraryImport

[LibraryImport] ถูกนำมาใช้ใน .NET 7 (C# 11) ในฐานะตัวแทนที่สร้างจากซอร์สและรองรับ AOT แทนที่ [DllImport] การใช้งาน DllImport แบบดั้งเดิมอาศัยการมาร์แชลลิงขณะทำงานผ่านรีเฟลกชัน ซึ่งเป็นปัญหาสำหรับ AOT แบบเนทีฟ ส่วน LibraryImport จะสร้างโค้ดการมาร์แชลลิงทั้งหมดในเวลาคอมไพล์

การประกาศเมธอด LibraryImport

กำกับเมธอด static partial ด้วย [LibraryImport] ตัวสร้างซอร์สจะเติมการทำงานให้ เมธอดต้องอยู่ในคลาสที่กำกับด้วย 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);
}

การมาร์แชลลิงสตริงใน LibraryImport

ต่างจาก DllImport ตรงที่ LibraryImport กำหนดให้คุณระบุการมาร์แชลลิงสตริงอย่างชัดเจน คุณสามารถใช้ชนิดแจกแจง StringMarshalling หรือแอตทริบิวต์ MarshalAs ได้ ทำให้มองเห็นและควบคุมต้นทุนของการมาร์แชลลิงได้

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

การมาร์แชลลิงโครงสร้าง

สำหรับโครงสร้าง ให้เพิ่ม [NativeMarshalling] เพื่อกำหนดวิธีจับคู่ชนิดข้อมูลที่จัดการโดย .NET กับตัวแทนแบบเนทีฟของชนิดนั้น ตัวสร้างซอร์สจะใช้ชนิดมาร์แชลเลอร์เพื่อสร้างโค้ดที่ปลอดภัยและลดการจัดสรรหน่วยความจำให้น้อยที่สุด

[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 จะถูกแปลความหมายขณะทำงาน จึงเริ่มต้นช้า อาศัยรีเฟลกชัน และไม่เข้ากันได้กับการตัดโค้ดของ AOT ส่วน LibraryImport จะสร้างโค้ด C# ที่ปรับให้เหมาะสมแล้วในเวลาสร้าง: ไม่ใช้รีเฟลกชันขณะทำงาน ปลอดภัยต่อการตัดโค้ด และเร็วขึ้นอย่างวัดผลได้จากการทดสอบประสิทธิภาพ

// 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> และการมาร์แชลลิงหน่วยความจำ

ข้อดีประการหนึ่งของ 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 ทั้งหมดไว้ แล้วเปิดเผย API ระดับสูงที่ปลอดภัยไว้ด้านบน ให้การประกาศ partial เป็นแบบ internal/private และเปิดเผยต่อสาธารณะเฉพาะตัวห่อหุ้มที่ปลอดภัยเท่านั้น

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
  • การมาร์แชลลิงถูกสร้างขึ้นในเวลาคอมไพล์ จึงไม่ต้องใช้รีเฟลกชันขณะทำงาน
  • ต้องใช้เมธอด static partial และคลาส partial
  • ต้องระบุการมาร์แชลลิงสตริงอย่างชัดเจนผ่าน StringMarshalling หรือ MarshalAs
  • รองรับ Span<T> โดยตรงโดยไม่มีต้นทุนจากการตรึง
  • ตรวจสอบโค้ดที่สร้างขึ้นได้ด้วย EmitCompilerGeneratedFiles

คำถามที่พบบ่อย

บทเรียน “LibraryImport และ P/Invoke ที่สร้างจากซอร์ส” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “LibraryImport และ P/Invoke ที่สร้างจากซอร์ส” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C# Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “LibraryImport และ P/Invoke ที่สร้างจากซอร์ส”

ใช้ [LibraryImport] (C# 11+) สำหรับการจัดเรียงข้อมูลที่สร้างจากซอร์สและรองรับ AOT ซึ่งมีประสิทธิภาพเหนือกว่า DllImport คุณปฏิบัติ C# Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C# Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน C# Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “LibraryImport และ P/Invoke ที่สร้างจากซอร์ส” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน C# Academy นี้ได้ไหม

ได้ บทเรียน C# Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. พื้นฐาน P/Invoke
  2. LibraryImport และ P/Invoke ที่สร้างจากซอร์ส
  3. โค้ดไม่ปลอดภัย พอยน์เตอร์ และบัฟเฟอร์ขนาดคงที่
  4. การทำงานร่วมกันระหว่าง COM และตัวห่อที่เรียกใช้ได้ขณะรันไทม์
← กลับไปที่ C# Academy