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 class Program
{
    // Import user32.dll (containing the function we need) and define
    // the method corresponding to the native function.
    [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern 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 memperlihatkan pernyataan penggunaan untuk System.Runtime.InteropServices namespace layanan yang menyimpan semua item yang diperlukan.
  • Baris #8 memperkenalkan atribut DllImport. Atribut ini memberi tahu runtime bahwa ia harus memuat DLL yang tidak dikelola. String yang diteruskan adalah DLL tempat fungsi target kita berada. Selain itu, string ini menentukan tataan karakter mana yang digunakan untuk penyusunan string. Terakhir, ini menentukan bahwa fungsi ini memanggil SetLastError dan bahwa runtime harus menangkap kode kesalahan tersebut sehingga pengguna dapat mengambilnya melalui Marshal.GetLastWin32Error().
  • Baris #9 adalah crux dari pekerjaan P/Invoke. Ini mendefinisikan metode terkelola yang memiliki tanda tangan yang sama persis dengan yang tidak dikelola. Deklarasi memiliki kata kunci baru yang dapat Anda perhatikan, extern, yang memberi tahu runtime ini adalah metode eksternal, dan bahwa ketika Anda memanggilnya, runtime harus menemukannya di DLL yang ditentukan dalam atribut DllImport.

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

Sampelnya mirip untuk macOS. Nama pustaka dalam atribut DllImport 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 class Program
    {
        // Import the libSystem shared library and define the method
        // corresponding to the native function.
        [DllImport("libSystem.dylib")]
        private static extern 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 class Program
    {
        // Import the libc shared library and define the method
        // corresponding to the native function.
        [DllImport("libc.so.6")]
        private static extern 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 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.
        [DllImport("user32.dll")]
        private static extern 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 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.
        [DllImport("libc.so.6")]
        private static extern 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 DllImport, karena macOS disimpan libc di tempat yang berbeda.

using System;
using System.Runtime.InteropServices;

namespace PInvokeSamples
{
    public static 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.
        [DllImport("libSystem.dylib")]
        private static extern 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