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

พื้นฐาน P/Invoke

ประกาศและเรียกใช้ฟังก์ชันเนทีฟด้วย DllImport ทำความเข้าใจการจัดเรียงข้อมูลสำหรับชนิดพื้นฐาน สตริง และโครงสร้าง

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

P/Invoke คืออะไร

บริการเรียกใช้แพลตฟอร์ม (P/Invoke) ช่วยให้ C# เรียกใช้ฟังก์ชันในไลบรารีร่วมเนทีฟได้ (.dll บน Windows, .so บน Linux และ .dylib บน macOS) นี่คือกลไกมาตรฐานสำหรับเรียกใช้ API ของ Win32 หรือไลบรารี 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 ไม่ใช่เฉพาะ API ของระบบปฏิบัติการเท่านั้น ให้สร้างไลบรารีเนทีฟ ส่งออกฟังก์ชันด้วยการเชื่อมโยงแบบ 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();

พอยน์เตอร์ฟังก์ชันและคอลแบ็ก

ส่ง delegate ที่มีการจัดการเป็นพอยน์เตอร์ฟังก์ชัน C โดย CLR จะสร้าง thunk ให้ แต่คุณต้องทำให้ delegate ยังมีชีวิตอยู่ (เก็บการอ้างอิงไว้) มิฉะนั้น GC จะเก็บรวบรวม delegate นั้น แล้วโค้ดเนทีฟจะขัดข้อง

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

API ของ 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
}

ตรวจสอบความเข้าใจ

เหตุใดคุณจึงต้องทำให้ delegate ยังมีชีวิตอยู่เมื่อส่งเป็นคอลแบ็กเนทีฟ

ทบทวน: พื้นฐาน P/Invoke

ประเด็นสำคัญ:

  • P/Invoke: เรียกใช้ฟังก์ชัน C-ABI แบบเนทีฟผ่านการประกาศ [DllImport]
  • CLR จะมาร์แชลลิงชนิดข้อมูลพื้นฐานโดยอัตโนมัติ ส่วนสตริงและชนิดข้อมูลแบบกำหนดเอง ให้ใช้แอตทริบิวต์ [MarshalAs]
  • [StructLayout(LayoutKind.Sequential)]: ตรวจสอบให้แน่ใจว่าเค้าโครงหน่วยความจำของโครงสร้างตรงกับที่เนทีฟคาดไว้
  • ตั้งค่า SetLastError = true แล้วเรียก Marshal.GetLastWin32Error() เพื่อจัดการข้อผิดพลาดของ Win32
  • รักษาการอ้างอิงของดีลีเกตให้ยังใช้งานได้เมื่อส่งดีลีเกตเป็นคอลแบ็กให้โค้ดเนทีฟ
  • NativeLibrary: โหลด แก้ไขตำแหน่ง และกำหนดตัวแก้ไขแบบกำหนดเองอย่างชัดเจน เพื่อควบคุมเส้นทางข้ามแพลตฟอร์ม

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

บทเรียน “พื้นฐาน P/Invoke” ฟรีหรือไม่

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

คุณจะเรียนรู้อะไรในบทเรียน “พื้นฐาน P/Invoke”

ประกาศและเรียกใช้ฟังก์ชันเนทีฟด้วย DllImport ทำความเข้าใจการจัดเรียงข้อมูลสำหรับชนิดพื้นฐาน สตริง และโครงสร้าง คุณปฏิบัติ C# Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

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

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

บทเรียน “พื้นฐาน P/Invoke” ใช้เวลานานแค่ไหน

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

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

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

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

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