Delen via


Platform-aanroep (P/Invoke)

P/Invoke is een technologie waarmee u vanuit uw beheerde code toegang hebt tot structs, callbacks en functies in niet-beheerde bibliotheken. De meeste P/Invoke-API bevindt zich in twee naamruimten: System en System.Runtime.InteropServices. Als u deze twee naamruimten gebruikt, beschikt u over de hulpprogramma's om te beschrijven hoe u wilt communiceren met het systeemeigen onderdeel.

Laten we beginnen met het meest voorkomende voorbeeld en dat roept onbeheerde functies aan in uw beheerde code. Laten we een berichtvak uit een opdrachtregeltoepassing weergeven:

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

Het vorige voorbeeld is eenvoudig, maar het laat zien wat er nodig is om onbeheerde functies aan te roepen vanuit beheerde code. Laten we het voorbeeld eens doorlopen:

  • Regel 2 toont de using instructie voor de System.Runtime.InteropServices naamruimte die alle benodigde items bevat.
  • In regel 8 wordt het LibraryImportAttribute kenmerk geïntroduceerd. Dit kenmerk geeft aan dat de runtime het niet-beheerde binaire bestand moet laden. De tekenreeks die wordt doorgegeven, is het niet-beheerde binaire bestand dat de doelfunctie bevat. Daarnaast geeft het de codering op die moet worden gebruikt voor het marshallen van de tekenreeksen. Ten slotte geeft het aan dat deze functie SetLastError aanroept en dat de runtime die foutcode moet vastleggen, zodat de gebruiker deze kan ophalen via Marshal.GetLastPInvokeError().
  • Regel 9 is de crux van het werk P/Invoke. Hiermee wordt een beheerde methode gedefinieerd die exact dezelfde handtekening heeft als de niet-beheerde methode. De declaratie maakt gebruik van het LibraryImport kenmerk en het partial trefwoord om een compilerextensie te laten weten code te genereren die moet worden aangeroepen in de niet-beheerde bibliotheek.
    • Binnen de gegenereerde code en vóór .NET 7 wordt de DllImport code gebruikt. Deze declaratie gebruikt het extern trefwoord om aan te geven aan de runtime dat dit een externe methode is en dat wanneer u deze aanroept, de runtime deze moet vinden in het niet-beheerde binaire bestand dat is opgegeven in het DllImport kenmerk.

De rest van het voorbeeld is het aanroepen van de methode, net zoals elke andere beheerde methode.

Het voorbeeld is vergelijkbaar voor macOS. De naam van de bibliotheek in het LibraryImport kenmerk moet worden gewijzigd omdat macOS een ander schema heeft voor het benoemen van dynamische bibliotheken. In het volgende voorbeeld wordt de getpid(2) functie gebruikt om de proces-id van de toepassing op te halen en deze af te drukken naar de console:

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

Het is ook vergelijkbaar in Linux. De functienaam is hetzelfde, omdat getpid(2) dit een standaard POSIX-systeemoproep is.

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

Beheerde code aanroepen vanuit niet-beheerde code

Met de runtime kan communicatie in beide richtingen stromen, zodat u terug kunt bellen naar beheerde code vanuit systeemeigen functies met behulp van functiepointers. Het dichtst bij een functieaanwijzer in beheerde code is een gemachtigde, dus dit is wat wordt gebruikt om callbacks van systeemeigen code toe te staan in beheerde code.

De manier om deze functie te gebruiken, is vergelijkbaar met het systeemeigen proces dat eerder is beschreven. Voor een bepaalde callback definieert u een gemachtigde die overeenkomt met de handtekening en die doorgeeft aan de externe methode. De runtime zorgt voor al het andere.

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

Voordat u het voorbeeld doorloopt, is het handig om de handtekeningen van de niet-beheerde functies te bekijken waarmee u moet werken. De functie die moet worden aangeroepen om alle vensters te inventariseren, heeft de volgende handtekening: BOOL EnumWindows (WNDENUMPROC lpEnumFunc, LPARAM lParam);

De eerste parameter is een callback. De genoemde callback heeft de volgende handtekening: BOOL CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lParam);

Laten we nu het voorbeeld bekijken:

  • Regel 9 in het voorbeeld definieert een gemachtigde die overeenkomt met de handtekening van de callback van niet-beheerde code. U ziet hoe de LPARAM- en HWND-typen worden weergegeven met behulp van IntPtr de beheerde code.
  • Regel 13 en #14 introduceren de EnumWindows functie uit de user32.dll-bibliotheek.
  • Regels 17 - 20 implementeren de gemachtigde. Voor dit eenvoudige voorbeeld willen we alleen de ingang naar de console uitvoeren.
  • Ten slotte wordt in regel 24 de externe methode aangeroepen en doorgegeven aan de gemachtigde.

Hieronder ziet u de voorbeelden van Linux en macOS. Voor hen gebruiken we de ftw functie die te vinden is in libc, de C-bibliotheek. Deze functie wordt gebruikt om adreslijsthiërarchieën te doorlopen en er wordt een aanwijzer naar een functie als een van de parameters gebruikt. De genoemde functie heeft de volgende handtekening: 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;
    }
}

macOS-voorbeeld maakt gebruik van dezelfde functie en het enige verschil is het argument voor het LibraryImport kenmerk, omdat macOS op een andere plaats blijft libc .

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

Beide eerdere voorbeelden zijn afhankelijk van parameters en in beide gevallen worden de parameters gegeven als beheerde typen. Runtime voert het 'juiste' uit en verwerkt deze in de equivalenten aan de andere kant. Meer informatie over hoe typen worden marshalled naar systeemeigen code op onze pagina over Type marshalling.

Meer resources