Platformhívás (P/Invoke)
A P/Invoke egy olyan technológia, amellyel a felügyelt kódból nem felügyelt kódtárakban lévő szerkezetekhez, visszahívásokhoz és függvényekhez férhet hozzá. A P/Invoke API nagy része két névtérben található: System
és System.Runtime.InteropServices
. A két névtér használatával leírhatja, hogyan szeretne kommunikálni a natív összetevővel.
Kezdjük a leggyakoribb példával, amely nem felügyelt függvényeket hív meg a felügyelt kódban. Jelenítsünk meg egy üzenetmezőt egy parancssori alkalmazásból:
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);
}
}
Az előző példa egyszerű, de megmutatja, hogy mi szükséges a nem felügyelt függvények felügyelt kódból való meghívásához. Vizsgáljuk meg a példát:
- A 2. sor az
using
összes szükséges elemet tartalmazó névtér irányelvétSystem.Runtime.InteropServices
jeleníti meg. - A 8. sor bemutatja az LibraryImportAttribute attribútumot. Ez az attribútum azt jelzi a futtatókörnyezetnek, hogy be kell töltenie a nem felügyelt bináris fájlt. A megadott sztring a célfüggvényt tartalmazó nem felügyelt bináris. Emellett meghatározza a sztringek rendezéséhez használandó kódolást is. Végül megadja, hogy ez a függvény meghívja a SetLastErrort, és a futtatókörnyezetnek rögzítenie kell a hibakódot, hogy a felhasználó lekérhesse azt.Marshal.GetLastPInvokeError()
- A 9. sor a P/Invoke munka keresztje. Olyan felügyelt metódust definiál, amely pontosan ugyanazzal az aláírással rendelkezik, mint a nem felügyelt. A deklaráció az
LibraryImport
attribútumot és apartial
kulcsszót használja arra, hogy egy fordítóbővítményt adjon meg a kód generálásához, hogy meghívja a nem felügyelt kódtárat.- A rendszer a létrehozott kódon belül és a .NET 7 előtt használja a
DllImport
kódot. Ez a deklaráció aextern
kulcsszót használja arra, hogy jelezze a futtatókörnyezetnek, hogy ez egy külső módszer, és amikor meghívja, a futtatókörnyezetnek az attribútumban megadott nem felügyelt bináris fájlbanDllImport
kell megtalálnia.
- A rendszer a létrehozott kódon belül és a .NET 7 előtt használja a
A példa további része a metódus meghívása, ahogyan bármely más felügyelt metódus esetében is.
A minta hasonló a macOS-hez. Az attribútumban lévő LibraryImport
kódtár nevének változnia kell, mivel a macOS-nek más a dinamikus kódtárak elnevezési sémája. Az alábbi minta az getpid(2)
alkalmazás folyamatazonosítójának lekéréséhez és a konzolon való kinyomtatásához használja a függvényt:
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);
}
}
}
Linuxon is hasonló. A függvény neve ugyanaz, mivel getpid(2)
egy standard POSIX rendszerhívás.
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);
}
}
}
Felügyelt kód meghívása nem felügyelt kódból
A futtatókörnyezet lehetővé teszi, hogy a kommunikáció mindkét irányban haladjon, így függvénymutatók használatával visszahívhatja a felügyelt kódokat natív függvényekből. A felügyelt kódban a függvénymutatóhoz legközelebb egy delegált áll, így a rendszer ezt használja a natív kódból a felügyelt kódba történő visszahívások engedélyezéséhez.
A funkció használatának módja hasonló a korábban ismertetett natív folyamathoz. Egy adott visszahíváshoz meg kell adnia egy olyan meghatalmazottat, aki megfelel az aláírásnak, és átadja azt a külső metódusnak. A futtatókörnyezet gondoskodik minden másról.
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);
}
}
}
Mielőtt végigmennénk a példán, érdemes áttekinteni a nem felügyelt függvények aláírását, amellyel dolgoznia kell. Az összes ablak számbavételéhez meghívandó függvény az alábbi aláírást tartalmazza: BOOL EnumWindows (WNDENUMPROC lpEnumFunc, LPARAM lParam);
Az első paraméter egy visszahívás. Az említett visszahívás a következő aláírással rendelkezik: BOOL CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lParam);
Most tekintsük át a példát:
- A példában a 9. sor egy olyan meghatalmazottat határoz meg, amely megfelel a nem felügyelt kódból való visszahívás aláírásának. Figyelje meg, hogy az LPARAM és a HWND típusok hogyan jelennek meg a felügyelt kódban
IntPtr
. - A 13. és a 14. sor bemutatja a függvényt
EnumWindows
a user32.dll könyvtárból. - A 17– 20. sor implementálja a meghatalmazottat. Ebben az egyszerű példában csak a fogópontot szeretnénk kiírni a konzolra.
- Végül a 24. sorban a külső metódus meghívása és átadása a meghatalmazottban történik.
A Linux és a macOS példák alább láthatók. Számukra a ftw
C könyvtárban libc
található függvényt használjuk. Ez a függvény könyvtárhierarchiák átjárására szolgál, és egy függvényre mutató mutatót vesz fel az egyik paramétereként. Az említett függvény a következő aláírást tartalmazza: 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;
}
}
a macOS-példa ugyanazt a függvényt használja, és az egyetlen különbség az attribútum argumentuma, mivel a LibraryImport
macOS egy másik helyen marad 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;
}
}
Az előző példák mindegyike paraméterektől függ, és mindkét esetben a paraméterek felügyelt típusokként vannak megadva. Runtime nem a "helyes dolog", és feldolgozza ezeket a megfelelők a másik oldalon. A típusok natív kódba rendezéséről a Típusrendezésről szóló oldalunkon tájékozódhat.
További erőforrások
- Platformfüggetlen P/Invokes írása
- Forrás által létrehozott P/Invoke rendezés
- A C#/Win32 P/Invoke forrásgenerátor automatikusan létrehozza a Windows API-k definícióit.
- P/Invoke in C++/CLI
- Mono-dokumentáció a P/Invoke szolgáltatásról