Platformhívás (P/Invoke)

A P/Invoke egy olyan technológia, amellyel a felügyelt kódból nem felügyelt kódtárakban lévő szerkezetekhez, visszahívásokhoz és függvényekhez férhet hozzá. A P/Invoke API nagy része két névtérben található: System és System.Runtime.InteropServices. A két névtér használatával leírhatja, hogyan szeretne kommunikálni a natív összetevővel.

Kezdjük a leggyakoribb példával, amely nem felügyelt függvényeket hív meg a felügyelt kódban. Jelenítsünk meg egy üzenetmezőt egy parancssori alkalmazásból:

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

Az előző példa egyszerű, de megmutatja, hogy mi szükséges a nem felügyelt függvények felügyelt kódból való meghívásához. Vizsgáljuk meg a példát:

  • A 2. sor az összes szükséges elemet tartalmazó névtérhez tartozó felhasználói utasítást System.Runtime.InteropServices jeleníti meg.
  • A 8. sor bemutatja az DllImport attribútumot. Ez az attribútum tájékoztatja a futtatókörnyezetet, hogy betöltse a nem felügyelt DLL-t. A megadott sztring az a DLL, amelyben a célfüggvény található. Emellett meghatározza, hogy melyik karakterkészletet használja a sztringek rendezéséhez. Végül megadja, hogy ez a függvény meghívja a SetLastErrort, és a futtatókörnyezetnek rögzítenie kell a hibakódot, hogy a felhasználó lekérhesse azt.Marshal.GetLastWin32Error()
  • A 9. sor a P/Invoke munka keresztje. Olyan felügyelt metódust definiál, amely pontosan ugyanazzal az aláírással rendelkezik, mint a nem felügyelt. A deklaráció egy új kulcsszóval rendelkezik, amely azt jelzi, externhogy a futtatókörnyezet külső metódus, és amikor meghívja, a futtatókörnyezetnek meg kell találnia az attribútumban megadott DLL-ben DllImport .

A többi példa csak a metódus meghívása, ahogyan bármely más felügyelt metódus esetében is.

A minta hasonló a macOS-hez. Az attribútumban lévő DllImport kódtár nevének változnia kell, mivel a macOS-nek más a dinamikus kódtárak elnevezési sémája. Az alábbi minta az getpid(2) alkalmazás folyamatazonosítójának lekéréséhez és a konzolon való kinyomtatásához használja a függvényt:

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

Linuxon is hasonló. A függvény neve ugyanaz, mivel getpid(2) egy standard POSIX rendszerhívás.

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

Felügyelt kód meghívása nem felügyelt kódból

A futtatókörnyezet lehetővé teszi, hogy a kommunikáció mindkét irányban haladjon, így függvénymutatók használatával visszahívhatja a felügyelt kódokat natív függvényekből. A felügyelt kódban a függvénymutatóhoz legközelebb egy delegált áll, így a rendszer ezt használja a natív kódból a felügyelt kódba történő visszahívások engedélyezéséhez.

A funkció használatának módja hasonló a korábban ismertetett natív folyamathoz. Egy adott visszahíváshoz meg kell adnia egy olyan meghatalmazottat, aki megfelel az aláírásnak, és átadja azt a külső metódusnak. A futtatókörnyezet gondoskodik minden másról.

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

Mielőtt végigmennénk a példán, érdemes áttekinteni a nem felügyelt függvények aláírását, amellyel dolgoznia kell. Az összes ablak számbavételéhez meghívandó függvény az alábbi aláírást tartalmazza: BOOL EnumWindows (WNDENUMPROC lpEnumFunc, LPARAM lParam);

Az első paraméter egy visszahívás. Az említett visszahívás a következő aláírással rendelkezik: BOOL CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lParam);

Most tekintsük át a példát:

  • A példában a 9. sor egy olyan meghatalmazottat határoz meg, amely megfelel a nem felügyelt kódból való visszahívás aláírásának. Figyelje meg, hogy az LPARAM és a HWND típusok hogyan jelennek meg a felügyelt kódban IntPtr .
  • A 13. és a 14. sor bemutatja a függvényt EnumWindows a user32.dll könyvtárból.
  • A 17– 20. sor implementálja a meghatalmazottat. Ebben az egyszerű példában csak a fogópontot szeretnénk kiírni a konzolra.
  • Végül a 24. sorban a külső metódus meghívása és átadása a meghatalmazottban történik.

A Linux és a macOS példák alább láthatók. Számukra a ftw C könyvtárban libctalálható függvényt használjuk. Ez a függvény könyvtárhierarchiák átjárására szolgál, és egy függvényre mutató mutatót vesz fel az egyik paramétereként. Az említett függvény a következő aláírást tartalmazza: 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;
    }
}

a macOS-példa ugyanazt a függvényt használja, és az egyetlen különbség az attribútum argumentuma, mivel a DllImport macOS egy másik helyen marad libc .

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;
    }
}

Az előző példák mindegyike paraméterektől függ, és mindkét esetben a paraméterek felügyelt típusokként vannak megadva. Runtime nem a "helyes dolog", és feldolgozza ezeket a megfelelők a másik oldalon. A típusok natív kódba rendezéséről a Típusrendezésről szóló oldalunkon tájékozódhat.

További erőforrások