Volání platformy (volání nespravovaného kódu)

Volání nespravovaných knihoven je technologie, která umožňuje přístup ke strukturám, zpětným voláním a funkcím v nespravovaných knihovnách ze spravovaného kódu. Většina rozhraní API volání nespravovaného kódu je obsažena ve dvou oborech názvů: System a System.Runtime.InteropServices. Pomocí těchto dvou oborů názvů získáte nástroje, které popisují, jak chcete komunikovat s nativní komponentou.

Začněme od nejběžnějšího příkladu, který volá nespravované funkce ve spravovaném kódu. Pojďme zobrazit okno se zprávou z aplikace příkazového řádku:

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

Předchozí příklad je jednoduchý, ale ukazuje, co je potřeba k vyvolání nespravovaných funkcí ze spravovaného kódu. Pojďme si projít příklad:

  • Řádek č. 2 zobrazuje příkaz using pro System.Runtime.InteropServices obor názvů, který obsahuje všechny potřebné položky.
  • Řádek č. 8 představuje DllImport atribut. Tento atribut říká modulu runtime, že by měl načíst nespravovanou knihovnu DLL. Řetězec předaný je knihovna DLL, ve které je naše cílová funkce. Kromě toho určuje, která znaková sada se má použít pro zařazování řetězců. Nakonec určuje, že tato funkce volá SetLastError a že modul runtime by měl zachytit tento kód chyby, aby ho uživatel mohl načíst prostřednictvím Marshal.GetLastWin32Error().
  • Řádek 9 je crux práce volání nespravovaného kódu. Definuje spravovanou metodu, která má stejný podpis jako nespravovaná metoda. Deklarace má nové klíčové slovo, které si můžete všimnout, , což externříká modulu runtime toto je externí metoda, a že při vyvolání by modul runtime měl najít v knihovně DLL zadané v DllImport atributu.

Zbytek příkladu pouze vyvolá metodu stejně jako jakoukoli jinou spravovanou metodu.

Ukázka je podobná pro macOS. Název knihovny v atributu DllImport se musí změnit, protože macOS má jiné schéma pojmenování dynamických knihoven. Následující ukázka pomocí getpid(2) funkce získá ID procesu aplikace a vytiskne ji do konzoly:

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

Je to také podobné v Linuxu. Název funkce je stejný, protože getpid(2) se jedná o standardní systémové volání POSIX .

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

Vyvolání spravovaného kódu z nespravovaného kódu

Modul runtime umožňuje komunikaci směrovat v obou směrech, což umožňuje volání zpět do spravovaného kódu z nativních funkcí pomocí ukazatelů funkcí. Nejbližší věcí k ukazateli funkce ve spravovaném kódu je delegát, takže se používá k povolení zpětného volání z nativního kódu do spravovaného kódu.

Způsob použití této funkce je podobný spravovanému nativnímu procesu, který jsme popsali dříve. U daného zpětného volání definujete delegáta, který odpovídá podpisu, a předáte ho do externí metody. Modul runtime se postará o všechno ostatní.

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

Než si projdete příklad, je dobré zkontrolovat podpisy nespravovaných funkcí, se kterými potřebujete pracovat. Funkce, která se má volat k vytvoření výčtu všech oken, má následující podpis: BOOL EnumWindows (WNDENUMPROC lpEnumFunc, LPARAM lParam);

Prvním parametrem je zpětné volání. Uvedený zpětné volání má následující podpis: BOOL CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lParam);

Teď si projdeme příklad:

  • Řádek č. 9 v příkladu definuje delegáta, který odpovídá podpisu zpětného volání z nespravovaného kódu. Všimněte si, jak jsou typy LPARAM a HWND reprezentovány pomocí IntPtr spravovaného kódu.
  • Řádky č. 13 a #14 představují EnumWindows funkci z knihovny user32.dll.
  • Řádky č. 17 – 20 implementují delegáta. V tomto jednoduchém příkladu chceme vypíšeme popisovač do konzoly.
  • Nakonec na řádku č. 24 je externí metoda volána a předána v delegátu.

Příklady pro Linux a macOS jsou uvedené níže. Pro ně používáme ftw funkci, která se nachází v libcknihovně jazyka C. Tato funkce slouží k procházení hierarchií adresářů a jako jeden z jeho parametrů přebírá ukazatel na funkci. Uvedená funkce má následující podpis: 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;
    }
}

Příklad macOS používá stejnou funkci a jediným rozdílem je argument atributu DllImport , protože macOS zůstává libc na jiném místě.

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

Oba předchozí příklady závisejí na parametrech a v obou případech jsou parametry uvedeny jako spravované typy. Modul runtime provede "správnou věc" a zpracuje je do svých ekvivalentů na druhé straně. Přečtěte si, jak se typy zařaďují do nativního kódu na naší stránce o zařazování typů.

Další materiály