Sdílet prostřednictvím


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

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 direktivu usingSystem.Runtime.InteropServices pro obor názvů, který obsahuje všechny potřebné položky.
  • Řádek č. 8 představuje LibraryImportAttribute atribut. Tento atribut říká modulu runtime, že by měl načíst nespravovaný binární soubor. Řetězec předaný je nespravovaný binární soubor, který obsahuje cílovou funkci. Kromě toho určuje kódování, které 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.GetLastPInvokeError().
  • Řádek 9 je crux práce volání nespravovaného kódu. Definuje spravovanou metodu, která má stejný podpis jako nespravovaná metoda. Deklarace pomocí atributu LibraryImport a klíčového partial slova sdělí rozšíření kompilátoru, aby vygeneroval kód pro volání do nespravované knihovny.
    • Vygenerovaný kód a před rozhraním .NET 7 se DllImport použije. Tato deklarace používá extern klíčové slovo k označení modulu runtime, jedná se o externí metodu a že při vyvolání by modul runtime měl najít v nespravovaném binárním souboru zadaném v atributu DllImport .

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

Ukázka je podobná pro macOS. Název knihovny v atributu LibraryImport 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 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);
        }
    }
}

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

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

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

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

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

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