Plattformaufruf (P/Invoke)Platform Invoke (P/Invoke)

P/Invoke ist eine Technologie, die Ihnen den Zugriff auf Strukturen, Rückrufe und Funktionen in nicht verwalteten Bibliotheken von verwaltetem Code aus ermöglicht.P/Invoke is a technology that allows you to access structs, callbacks, and functions in unmanaged libraries from your managed code. Der Großteil der P/Invoke-API ist in zwei Namespaces enthalten: System und System.Runtime.InteropServices.Most of the P/Invoke API is contained in two namespaces: System and System.Runtime.InteropServices. Über diese beiden Namespaces können Sie auf die Tools zugreifen, die beschreiben, wie Sie mit der nativen Komponente kommunizieren möchten.Using these two namespaces give you the tools to describe how you want to communicate with the native component.

Beginnen wir mit dem gängigsten Beispiel, dem Aufruf nicht verwalteter Funktionen im Ihrem verwalteten Code.Let’s start from the most common example, and that is calling unmanaged functions in your managed code. Wir zeigen ein Meldungsfeld aus einer Befehlszeilenanwendung:Let’s show a message box from a command-line application:

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

Das obige Beispiel ist recht einfach, zeigt jedoch, was zum Aufrufen nicht verwalteter Funktionen von verwaltetem Code aus erforderlich ist.The previous example is simple, but it does show off what's needed to invoke unmanaged functions from managed code. Gehen wir das Beispiel schrittweise durch:Let’s step through the example:

  • Zeile 1 zeigt die using-Anweisung für System.Runtime.InteropServices. Dies ist der Namespace, der alle benötigten Elemente enthält.Line #1 shows the using statement for the System.Runtime.InteropServices namespace that holds all the items needed.
  • In Zeile 7 wird das DllImport-Attribut eingeführt.Line #7 introduces the DllImport attribute. Dieses Attribut ist äußerst wichtig, da es der Runtime mitteilt, dass die nicht verwaltete DLL geladen werden soll.This attribute is crucial, as it tells the runtime that it should load the unmanaged DLL. Die übergebene Zeichenfolge ist die DLL, in der sich unsere Zielfunktion befindet.The string passed in is the DLL our target function is in. Zusätzlich gibt sie an, welcher Zeichensatz für das Marshallen der Zeichenfolgen verwendet werden soll.Additionally, it specifies which character set to use for marshalling the strings. Schließlich gibt sie an, dass diese Funktion SetLastError aufruft und dass die Laufzeit diesen Fehlercode abfangen muss, damit der Benutzer ihn über Marshal.GetLastWin32Error() abrufen kann.Finally, it specifies that this function calls SetLastError and that the runtime should capture that error code so the user can retrieve it via Marshal.GetLastWin32Error().
  • Zeile 8 ist der Kern der P/Invoke-Funktion.Line #8 is the crux of the P/Invoke work. Sie definiert eine verwaltete Methode, die genau dieselbe Signatur aufweist wie die nicht verwaltete Methode.It defines a managed method that has the exact same signature as the unmanaged one. Wie Sie sehen, enthält die Deklaration ein neues Schlüsselwort, extern. Dieses teilt der Runtime mit, dass es sich um eine externe Methode handelt und dass die Runtime die Methode bei Aufruf in der im DllImport-Attribut angegebenen DLL findet.The declaration has a new keyword that you can notice, extern, which tells the runtime this is an external method, and that when you invoke it, the runtime should find it in the DLL specified in DllImport attribute.

Der Rest des Beispiels besteht nur aus dem Aufruf der Methode, der wie bei jeder anderen verwalteten Methode erfolgt.The rest of the example is just invoking the method as you would any other managed method.

Das Beispiel ist für macOS ähnlich.The sample is similar for macOS. Der Name der Bibliothek im DllImport-Attribut muss geändert werden, da macOS ein anderes Schema für die Benennung dynamischer Bibliotheken verwendet.The name of the library in the DllImport attribute needs to change since macOS has a different scheme of naming dynamic libraries. Das folgende Beispiel verwendet die getpid(2)-Funktion, um die Prozess-ID der Anwendung abzurufen und in der Konsole auszugeben.The following sample uses the getpid(2) function to get the process ID of the application and print it out to the console:

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

Bei Linux ist es ähnlich.It is also similar on Linux. Der Funktionsname ist identisch, da es sich bei getpid(2) um einen standardmäßigen POSIX-Systemaufruf handelt.The function name is the same, since getpid(2) is a standard POSIX system call.

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

Aufrufen von verwaltetem Code von nicht verwaltetem Code ausInvoking managed code from unmanaged code

Die Runtime ermöglicht den Kommunikationsfluss in beide Richtungen, sodass Sie verwalteten Code mithilfe von Funktionszeigern aus nativen Funktionen zurückrufen können.The runtime allows communication to flow in both directions, enabling you to call back into managed code from native functions by using function pointers. Einem Funktionszeiger in verwaltetem Code kommt ein Delegat am nächsten. Dieser wird verwendet, um Rückrufe von nativem Code an verwalteten Code zu ermöglichen.The closest thing to a function pointer in managed code is a delegate, so this is what is used to allow callbacks from native code into managed code.

Dieses Feature wird ähnlich dem oben beschriebenen Verfahren von verwaltetem zu nativem Code verwendet.The way to use this feature is similar to the managed to native process previously described. Für einen bestimmten Rückruf definieren Sie einen Delegaten, der der Signatur entspricht, und übergeben Sie ihn an die externe Methode.For a given callback, you define a delegate that matches the signature and pass that into the external method. Die Runtime übernimmt alles Weitere.The runtime will take care of everything else.

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

Bevor wir unser Beispiel durchgehen, sollten wir die Signaturen der nicht verwalteten Funktionen betrachten, mit denen wir arbeiten müssen.Before walking through the example, it's good to review the signatures of the unmanaged functions you need to work with. Die Funktion, die wir zum Auflisten aller Fenster aufrufen möchten, weist folgende Signatur auf: BOOL EnumWindows (WNDENUMPROC lpEnumFunc, LPARAM lParam);The function to be called to enumerate all of the windows has the following signature: BOOL EnumWindows (WNDENUMPROC lpEnumFunc, LPARAM lParam);

Der erste Parameter ist ein Rückruf.The first parameter is a callback. Dieser Rückruf besitzt die folgende Signatur: BOOL CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lParam);The said callback has the following signature: BOOL CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lParam);

Schauen wir uns jetzt das Beispiel an:Now, let’s walk through the example:

  • Zeile 9 im Beispiel definiert einen Delegaten, der der Signatur des Rückrufs von nicht verwaltetem Code aus entspricht.Line #9 in the example defines a delegate that matches the signature of the callback from unmanaged code. Beachten Sie, wie die Typen LPARAM und HWND über IntPtr im verwalteten Code dargestellt werden.Notice how the LPARAM and HWND types are represented using IntPtr in the managed code.
  • In den Zeilen 13 und 14 wird die EnumWindows-Funktion über die user32.dll-Bibliothek eingeführt.Lines #13 and #14 introduce the EnumWindows function from the user32.dll library.
  • In den Zeilen 17 bis 20 wird der Delegat implementiert.Lines #17 - 20 implement the delegate. In diesem einfachen Beispiel möchten wir nur das Handle an die Konsole ausgeben.For this simple example, we just want to output the handle to the console.
  • Abschließend wird in Zeile 24 die externe Methode aufgerufen und an den Delegaten übergeben.Finally, in line #24, the external method is called and passed in the delegate.

Die Beispiele für Linux und macOS werden im Folgenden angezeigt.The Linux and macOS examples are shown below. Hierfür verwenden wir die ftw-Funktion, die in libc zu finden ist, der C-Bibliothek.For them, we use the ftw function that can be found in libc, the C library. Anhand dieser Funktion werden die Verzeichnishierarchien durchlaufen. Sie verwendet einen Zeiger auf eine Funktion als einen ihrer Parameter.This function is used to traverse directory hierarchies and it takes a pointer to a function as one of its parameters. Diese Funktion besitzt die folgende Signatur: int (*fn) (const char *fpath, const struct stat *sb, int typeflag).The said function has the following signature: 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, StatClass 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, StatClass 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. The below class
    // represents that struct in managed code. You can find more information
    // about this in the section on marshalling below.
    [StructLayout(LayoutKind.Sequential)]
    public class StatClass
    {
        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;
    }
}

Im macOS-Beispiel wird die gleiche Funktion verwendet. Der einzige Unterschied ist das Argument für das DllImport-Attribut, da macOS libc an einer anderen Stelle speichert.macOS example uses the same function, and the only difference is the argument to the DllImport attribute, as macOS keeps libc in a different place.

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, StatClass 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, StatClass 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. The below class
    // represents that struct in managed code.
    [StructLayout(LayoutKind.Sequential)]
    public class StatClass
    {
        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 oben aufgeführten Beispiele hängen von Parametern ab, und in beiden Fällen werden die Parameter als verwaltete Typen angegeben.Both of the previous examples depend on parameters, and in both cases, the parameters are given as managed types. Die Runtime reagiert demgemäß und verarbeitet diese in ihren Entsprechungen auf der anderen Seite.Runtime does the "right thing" and processes these into its equivalents on the other side. Weitere Informationen dazu, wie Typen in nativen Code auf unserer Seite gemarshallt werden, finden Sie unter Marshalling von Typen.Learn about how types are marshaled to native code in our page on Type marshaling.

Weitere RessourcenMore resources