Wywołanie platformy (P/Invoke)
P/Invoke to technologia umożliwiająca dostęp do struktur, wywołań zwrotnych i funkcji w bibliotekach niezarządzanych z poziomu kodu zarządzanego. Większość interfejsu API P/Invoke znajduje się w dwóch przestrzeniach nazw: System
i System.Runtime.InteropServices
. Dzięki tym dwóm przestrzeniom nazw możesz opisać, jak chcesz komunikować się ze składnikiem natywnym.
Zacznijmy od najbardziej typowego przykładu, który wywołuje funkcje niezarządzane w kodzie zarządzanym. Pokażmy pole komunikatu z poziomu aplikacji wiersza polecenia:
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);
}
}
Poprzedni przykład jest prosty, ale pokazuje, co jest potrzebne do wywoływania funkcji niezarządzanych z kodu zarządzanego. Przyjrzyjmy się przykładowi:
- Wiersz 2 zawiera instrukcję using dla
System.Runtime.InteropServices
przestrzeni nazw, która zawiera wszystkie potrzebne elementy. - Wiersz 8 wprowadza
DllImport
atrybut . Ten atrybut informuje środowisko uruchomieniowe, że powinno załadować niezarządzaną bibliotekę DLL. Przekazany ciąg jest biblioteką DLL, w których znajduje się nasza funkcja docelowa. Ponadto określa, który zestaw znaków ma być używany do marshalingu ciągów. Na koniec określa, że ta funkcja wywołuje metodę SetLastError i że środowisko uruchomieniowe powinno przechwycić ten kod błędu, aby użytkownik mógł go pobrać za pomocą polecenia Marshal.GetLastWin32Error(). - Wiersz 9 jest cruxem pracy P/Invoke. Definiuje metodę zarządzaną, która ma dokładnie taki sam podpis jak niezarządzany. Deklaracja zawiera nowe słowo kluczowe, które można zauważyć,
extern
, który informuje środowisko uruchomieniowe, że jest to metoda zewnętrzna i że po wywołaniu go środowisko uruchomieniowe powinno znaleźć go w dll określonym wDllImport
atrybucie.
Pozostała część przykładu polega na wywołaniu metody tak, jak w przypadku każdej innej metody zarządzanej.
Przykład jest podobny dla systemu macOS. Nazwa biblioteki w atrybucie DllImport
musi ulec zmianie, ponieważ system macOS ma inny schemat nazewnictwa bibliotek dynamicznych. W poniższym przykładzie użyto getpid(2)
funkcji , aby uzyskać identyfikator procesu aplikacji i wyświetlić ją w konsoli:
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);
}
}
}
Jest również podobny w systemie Linux. Nazwa funkcji jest taka sama, ponieważ getpid(2)
jest standardowym wywołaniem systemu 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);
}
}
}
Wywoływanie kodu zarządzanego z niezarządzanego kodu
Środowisko uruchomieniowe umożliwia komunikację w obu kierunkach, umożliwiając wywołanie z powrotem do kodu zarządzanego z funkcji natywnych przy użyciu wskaźników funkcji. Najbliżej wskaźnika funkcji w kodzie zarządzanym jest delegat, dlatego służy do zezwalania na wywołania zwrotne z kodu natywnego do kodu zarządzanego.
Sposób korzystania z tej funkcji jest podobny do opisanego wcześniej procesu natywnego zarządzanego. Dla danego wywołania zwrotnego należy zdefiniować delegata, który pasuje do podpisu i przekazać go do metody zewnętrznej. Środowisko uruchomieniowe zajmie się wszystkimi innymi elementami.
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);
}
}
}
Przed przejściem przez przykład warto przejrzeć podpisy funkcji niezarządzanych, z których należy pracować. Funkcja, która ma zostać wywołana w celu wyliczenia wszystkich okien, ma następujący podpis: BOOL EnumWindows (WNDENUMPROC lpEnumFunc, LPARAM lParam);
Pierwszy parametr to wywołanie zwrotne. Powiedział callback ma następujący podpis: BOOL CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lParam);
Teraz przyjrzyjmy się przykładowi:
- Wiersz 9 w przykładzie definiuje delegata pasujący do podpisu wywołania zwrotnego z niezarządzanego kodu. Zwróć uwagę, że typy LPARAM i HWND są reprezentowane przy użyciu
IntPtr
w kodzie zarządzanym. - Wiersze #13 i #14 wprowadzają
EnumWindows
funkcję z biblioteki user32.dll. - Wiersze nr 17–20 implementują delegata. W tym prostym przykładzie chcemy po prostu wyświetlić uchwyt do konsoli.
- Na koniec, w wierszu 24, metoda zewnętrzna jest wywoływana i przekazywana do delegata.
Poniżej przedstawiono przykłady systemów Linux i macOS. W tym przypadku używamy ftw
funkcji , która znajduje się w libc
bibliotece języka C. Ta funkcja służy do przechodzenia przez hierarchie katalogów i przyjmuje wskaźnik do funkcji jako jeden z jego parametrów. Ta funkcja ma następujący 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;
}
}
Przykład systemu macOS używa tej samej funkcji, a jedyną różnicą jest argument atrybutu DllImport
, ponieważ system macOS utrzymuje libc
się w innym miejscu.
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 poprzednie przykłady zależą od parametrów, a w obu przypadkach parametry są podane jako typy zarządzane. Środowisko uruchomieniowe wykonuje "właściwą rzecz" i przetwarza je w odpowiedniki po drugiej stronie. Dowiedz się, jak typy są ułożone do kodu natywnego na naszej stronie na stronie Typ marshalling.
Więcej zasobów
- Pisanie wieloplatformowych wywołań P/Invoke
- Generowanie źródłowego marshalingu P/Invoke
- Generator źródła języka C#/Win32 P/Invoke automatycznie generuje definicje dla interfejsów API systemu Windows.
- P/Invoke w języku C++/interfejsie wiersza polecenia
- Dokumentacja mono dotycząca protokołu P/Invoke
Opinia
https://aka.ms/ContentUserFeedback.
Dostępne już wkrótce: W 2024 r. będziemy stopniowo wycofywać zgłoszenia z serwisu GitHub jako mechanizm przesyłania opinii na temat zawartości i zastępować go nowym systemem opinii. Aby uzyskać więcej informacji, sprawdź:Prześlij i wyświetl opinię dla