Bagikan melalui


Pemanggilan Platform (P/Invoke)

P/Invoke adalah teknologi yang memungkinkan Anda mengakses struktur, panggilan balik, dan fungsi di pustaka yang tidak dikelola dari kode terkelola Anda. Sebagian besar API P/Invoke terkandung dalam dua namespace layanan: System dan System.Runtime.InteropServices. Menggunakan kedua namespace layanan ini memberi Anda alat untuk menjelaskan bagaimana Anda ingin berkomunikasi dengan komponen asli.

Mari kita mulai dari contoh yang paling umum, dan yang memanggil fungsi yang tidak dikelola dalam kode terkelola Anda. Mari kita perlihatkan kotak pesan dari aplikasi baris perintah:

using System;
using System.Runtime.InteropServices;

public partial class Program
{
    // Import user32.dll (containing the function we need) and define
    // the method corresponding to the native function.
    [LibraryImport("user32.dll", StringMarshalling = StringMarshalling.Utf16, SetLastError = true)]
    private static partial int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);

    public static void Main(string[] args)
    {
        // Invoke the function as a regular managed method.
        MessageBox(IntPtr.Zero, "Command-line message box", "Attention!", 0);
    }
}

Contoh sebelumnya sederhana, tetapi memamerkan apa yang diperlukan untuk memanggil fungsi yang tidak dikelola dari kode terkelola. Mari kita melangkah melalui contoh:

  • Baris #2 menunjukkan direktif using untuk System.Runtime.InteropServices namespace layanan yang menyimpan semua item yang diperlukan.
  • Baris #8 memperkenalkan atribut LibraryImportAttribute. Atribut ini memberi tahu runtime bahwa ia harus memuat biner yang tidak dikelola. String yang diteruskan adalah biner yang tidak dikelola yang berisi fungsi target. Selain itu, ini menentukan pengodean yang akan digunakan untuk marshalling string. Terakhir, ini menentukan bahwa fungsi ini memanggil SetLastError dan bahwa runtime harus menangkap kode kesalahan tersebut sehingga pengguna dapat mengambilnya melalui Marshal.GetLastPInvokeError().
  • Baris #9 adalah crux dari pekerjaan P/Invoke. Ini mendefinisikan metode terkelola yang memiliki tanda tangan yang sama persis dengan yang tidak dikelola. Deklarasi menggunakan LibraryImport atribut dan partial kata kunci untuk memberi tahu ekstensi kompilator untuk menghasilkan kode untuk dipanggil ke pustaka yang tidak dikelola.
    • Dalam kode yang dihasilkan dan sebelum .NET 7, DllImport digunakan. Deklarasi ini menggunakan extern kata kunci untuk menunjukkan kepada runtime ini adalah metode eksternal, dan bahwa ketika Anda memanggilnya, runtime harus menemukannya dalam biner yang tidak dikelola yang ditentukan dalam DllImport atribut .

Contoh lainnya adalah memanggil metode seperti yang Anda lakukan pada metode terkelola lainnya.

Sampelnya mirip untuk macOS. Nama pustaka dalam atribut LibraryImport perlu berubah karena macOS memiliki skema penamaan pustaka dinamis yang berbeda. Sampel berikut menggunakan fungsi getpid(2) untuk mendapatkan ID proses aplikasi dan mencetaknya ke konsol:

using System;
using System.Runtime.InteropServices;

namespace PInvokeSamples
{
    public static partial class Program
    {
        // Import the libSystem shared library and define the method
        // corresponding to the native function.
        [LibraryImport("libSystem.dylib")]
        private static partial int getpid();

        public static void Main(string[] args)
        {
            // Invoke the function and get the process ID.
            int pid = getpid();
            Console.WriteLine(pid);
        }
    }
}

Ini juga mirip di Linux. Nama fungsinya sama, karena getpid(2) merupakan panggilan sistem POSIX standar.

using System;
using System.Runtime.InteropServices;

namespace PInvokeSamples
{
    public static partial class Program
    {
        // Import the libc shared library and define the method
        // corresponding to the native function.
        [LibraryImport("libc.so.6")]
        private static partial int getpid();

        public static void Main(string[] args)
        {
            // Invoke the function and get the process ID.
            int pid = getpid();
            Console.WriteLine(pid);
        }
    }
}

Memanggil kode terkelola dari kode yang tidak dikelola

Runtime memungkinkan komunikasi mengalir di kedua arah, memungkinkan Anda memanggil kembali ke kode terkelola dari fungsi asli dengan menggunakan penunjuk fungsi. Hal terdekat dengan penunjuk fungsi dalam kode terkelola adalah delegasi, jadi inilah yang digunakan untuk memungkinkan panggilan balik dari kode asli ke dalam kode terkelola.

Cara menggunakan fitur ini mirip dengan proses yang dikelola secara asli yang dijelaskan sebelumnya. Untuk panggilan balik tertentu, Anda menentukan delegasi yang cocok dengan tanda tangan dan meneruskan ke metode eksternal. Runtime akan mengurus segala sesuatu yang lain.

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    public static partial class Program
    {
        // Define a delegate that corresponds to the unmanaged function.
        private delegate bool EnumWC(IntPtr hwnd, IntPtr lParam);

        // Import user32.dll (containing the function we need) and define
        // the method corresponding to the native function.
        [LibraryImport("user32.dll")]
        private static partial int EnumWindows(EnumWC lpEnumFunc, IntPtr lParam);

        // Define the implementation of the delegate; here, we simply output the window handle.
        private static bool OutputWindow(IntPtr hwnd, IntPtr lParam)
        {
            Console.WriteLine(hwnd.ToInt64());
            return true;
        }

        public static void Main(string[] args)
        {
            // Invoke the method; note the delegate as a first parameter.
            EnumWindows(OutputWindow, IntPtr.Zero);
        }
    }
}

Sebelum menelusuri contoh, ada baiknya untuk meninjau tanda tangan fungsi tidak terkelola yang perlu Anda kerjakan. Fungsi yang akan dipanggil untuk menghitung semua jendela memiliki tanda tangan berikut: BOOL EnumWindows (WNDENUMPROC lpEnumFunc, LPARAM lParam);

Parameter pertama adalah panggilan balik. Panggilan balik tersebut memiliki tanda tangan berikut: BOOL CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lParam);

Sekarang, mari kita telusuri contohnya:

  • Baris #9 dalam contoh menentukan delegasi yang cocok dengan tanda tangan panggilan balik dari kode yang tidak dikelola. Perhatikan bagaimana jenis LPARAM dan HWND diwakili menggunakan IntPtr dalam kode terkelola.
  • Baris #13 dan #14 memperkenalkan fungsi EnumWindows dari pustaka user32.dll.
  • Baris #17 - 20 mengimplementasikan delegasi. Untuk contoh sederhana ini, kami hanya ingin menghasilkan handel ke konsol.
  • Akhirnya, dalam baris #24, metode eksternal dipanggil dan diteruskan dalam delegasi.

Contoh Linux dan macOS ditunjukkan di bawah ini. Untuk ini, kami menggunakan fungsi ftw yang dapat ditemukan di libc, pustaka C. Fungsi ini digunakan untuk melintasi hierarki direktori dan dibutuhkan penunjuk ke fungsi sebagai salah satu parameternya. Fungsi tersebut memiliki tanda tangan berikut: int (*fn) (const char *fpath, const struct stat *sb, int typeflag).

using System;
using System.Runtime.InteropServices;

namespace PInvokeSamples
{
    public static partial class Program
    {
        // Define a delegate that has the same signature as the native function.
        private delegate int DirClbk(string fName, ref Stat stat, int typeFlag);

        // Import the libc and define the method to represent the native function.
        [LibraryImport("libc.so.6", StringMarshalling = StringMarshalling.Utf16)]
        private static partial int ftw(string dirpath, DirClbk cl, int descriptors);

        // Implement the above DirClbk delegate;
        // this one just prints out the filename that is passed to it.
        private static int DisplayEntry(string fName, ref Stat stat, int typeFlag)
        {
            Console.WriteLine(fName);
            return 0;
        }

        public static void Main(string[] args)
        {
            // Call the native function.
            // Note the second parameter which represents the delegate (callback).
            ftw(".", DisplayEntry, 10);
        }
    }

    // The native callback takes a pointer to a struct. This type
    // represents that struct in managed code.
    [StructLayout(LayoutKind.Sequential)]
    public struct Stat
    {
        public uint DeviceID;
        public uint InodeNumber;
        public uint Mode;
        public uint HardLinks;
        public uint UserID;
        public uint GroupID;
        public uint SpecialDeviceID;
        public ulong Size;
        public ulong BlockSize;
        public uint Blocks;
        public long TimeLastAccess;
        public long TimeLastModification;
        public long TimeLastStatusChange;
    }
}

Contoh macOS menggunakan fungsi yang sama, dan satu-satunya perbedaan adalah argumen ke atribut LibraryImport, karena macOS disimpan libc di tempat yang berbeda.

using System;
using System.Runtime.InteropServices;

namespace PInvokeSamples
{
    public static partial class Program
    {
        // Define a delegate that has the same signature as the native function.
        private delegate int DirClbk(string fName, ref Stat stat, int typeFlag);

        // Import the libc and define the method to represent the native function.
        [LibraryImport("libSystem.dylib", StringMarshalling = StringMarshalling.Utf16)]
        private static partial int ftw(string dirpath, DirClbk cl, int descriptors);

        // Implement the above DirClbk delegate;
        // this one just prints out the filename that is passed to it.
        private static int DisplayEntry(string fName, ref Stat stat, int typeFlag)
        {
            Console.WriteLine(fName);
            return 0;
        }

        public static void Main(string[] args)
        {
            // Call the native function.
            // Note the second parameter which represents the delegate (callback).
            ftw(".", DisplayEntry, 10);
        }
    }

    // The native callback takes a pointer to a struct. This type
    // represents that struct in managed code.
    [StructLayout(LayoutKind.Sequential)]
    public struct Stat
    {
        public uint DeviceID;
        public uint InodeNumber;
        public uint Mode;
        public uint HardLinks;
        public uint UserID;
        public uint GroupID;
        public uint SpecialDeviceID;
        public ulong Size;
        public ulong BlockSize;
        public uint Blocks;
        public long TimeLastAccess;
        public long TimeLastModification;
        public long TimeLastStatusChange;
    }
}

Kedua contoh sebelumnya bergantung pada parameter, dan dalam kedua kasus, parameter diberikan sebagai jenis terkelola. Runtime melakukan "hal yang benar" dan memprosesnya menjadi setara di sisi lain. Pelajari tentang bagaimana jenis dinamai kode asli di halaman kami di Jenis penyusunan.

Sumber daya lainnya