C# Language
interoperabiliteit
Zoeken…
Opmerkingen
Werken met Win32 API met C #
Windows biedt veel functionaliteit in de vorm van Win32 API. Met behulp van deze API kunt u directe bewerking uitvoeren in Windows, wat de prestaties van uw toepassing verhoogt. Klik hier
Windows onthult een breed scala aan API. Voor informatie over verschillende API's kunt u sites zoals pinvoke bekijken .
Importfunctie van onbeheerde C ++ DLL
Hier is een voorbeeld van het importeren van een functie die is gedefinieerd in een onbeheerde C ++ DLL. In de C ++ broncode voor "myDLL.dll" is de functie add
gedefinieerd:
extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
{
return a + b;
}
Vervolgens kan het als volgt in een C # -programma worden opgenomen:
class Program
{
// This line will import the C++ method.
// The name specified in the DllImport attribute must be the DLL name.
// The names of parameters are unimportant, but the types must be correct.
[DllImport("myDLL.dll")]
private static extern int add(int left, int right);
static void Main(string[] args)
{
//The extern method can be called just as any other C# method.
Console.WriteLine(add(1, 2));
}
}
Zie Bellen conventies en C ++ naam mangelen voor uitleg over de reden waarom extern "C"
en __stdcall
nodig.
De dynamische bibliotheek vinden
Wanneer de externe methode voor het eerst wordt aangeroepen, zoekt het C # -programma naar de juiste DLL en laadt deze. Zie deze stackoverflow-vraag voor meer informatie over waar wordt gezocht om de DLL te vinden en hoe u de zoeklocaties kunt beïnvloeden.
Eenvoudige code om klasse bloot te stellen voor com
using System;
using System.Runtime.InteropServices;
namespace ComLibrary
{
[ComVisible(true)]
public interface IMainType
{
int GetInt();
void StartTime();
int StopTime();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class MainType : IMainType
{
private Stopwatch stopWatch;
public int GetInt()
{
return 0;
}
public void StartTime()
{
stopWatch= new Stopwatch();
stopWatch.Start();
}
public int StopTime()
{
return (int)stopWatch.ElapsedMilliseconds;
}
}
}
C ++ naam mangelen
C ++ -compilers coderen extra informatie in de namen van geëxporteerde functies, zoals argumenttypen, om overbelasting met verschillende argumenten mogelijk te maken. Dit proces wordt naammenging genoemd . Dit veroorzaakt problemen met het importeren van functies in C # (en interop met andere talen in het algemeen), omdat de naam van int add(int a, int b)
functie niet langer add
, het kan zijn ?add@@YAHHH@Z
, _add@8
of iets anders, afhankelijk van de compiler en de aanroepconventie.
Er zijn verschillende manieren om het probleem van naammenging op te lossen:
Functies exporteren met behulp van
extern "C"
om over te schakelen naar C externe koppeling die gebruik maakt van C-naammenging:extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
Functienaam wordt nog steeds verminkt (
_add@8
), maarStdCall
+extern "C"
-naammangling wordt herkend door de C # compiler.myDLL.def
opgeven in hetmyDLL.def
:EXPORTS add
int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
De naam van de functie zal zuiver zijn
add
in dit geval.Verminkte naam importeren. Je hebt een DLL-viewer nodig om de verminkte naam te zien, dan kun je deze expliciet specificeren:
__declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll", EntryPoint = "?add@@YGHHH@Z")]
Conventies oproepen
Er zijn verschillende conventies van aanroepfuncties, die aangeven wie (beller of oproep) argumenten uit de stapel haalt, hoe argumenten worden doorgegeven en in welke volgorde. C ++ gebruikt standaard de Cdecl
, maar C # verwacht StdCall
, dat meestal wordt gebruikt door de Windows API. U moet het een of het ander veranderen:
Wijzig
StdCall
inStdCall
in C ++:extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
Of wijzig de
Cdecl
inCdecl
in C #:extern "C" __declspec(dllexport) int /*__cdecl*/ add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
Als u een functie met Cdecl
en een verminkte naam wilt gebruiken, ziet uw code er als volgt uit:
__declspec(dllexport) int add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?add@@YAHHH@Z")]
thiscall ( __thiscall ) wordt voornamelijk gebruikt in functies die lid zijn van een klasse.
Wanneer een functie thiscall ( __thiscall ) gebruikt, wordt een pointer naar de klasse doorgegeven als de eerste parameter.
Dynamisch laden en lossen van onbeheerde DLL's
Wanneer u het kenmerk DllImport
u tijdens het compileren de juiste dll en de naam van de methode kennen. Als u flexibeler wilt zijn en tijdens runtime wilt beslissen welke DLL en methoden u wilt laden, kunt u de Windows API-methoden LoadLibrary()
, GetProcAddress()
en FreeLibrary()
. Dit kan handig zijn als de te gebruiken bibliotheek afhankelijk is van de runtime-omstandigheden.
De aanwijzer die wordt geretourneerd door GetProcAddress()
kan in een gemachtigde worden gegoten met behulp van Marshal.GetDelegateForFunctionPointer()
.
Het volgende codevoorbeeld demonstreert dit met de myDLL.dll
uit de vorige voorbeelden:
class Program
{
// import necessary API as shown in other examples
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LoadLibrary(string lib);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void FreeLibrary(IntPtr module);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr module, string proc);
// declare a delegate with the required signature
private delegate int AddDelegate(int a, int b);
private static void Main()
{
// load the dll
IntPtr module = LoadLibrary("myDLL.dll");
if (module == IntPtr.Zero) // error handling
{
Console.WriteLine($"Could not load library: {Marshal.GetLastWin32Error()}");
return;
}
// get a "pointer" to the method
IntPtr method = GetProcAddress(module, "add");
if (method == IntPtr.Zero) // error handling
{
Console.WriteLine($"Could not load method: {Marshal.GetLastWin32Error()}");
FreeLibrary(module); // unload library
return;
}
// convert "pointer" to delegate
AddDelegate add = (AddDelegate)Marshal.GetDelegateForFunctionPointer(method, typeof(AddDelegate));
// use function
int result = add(750, 300);
// unload library
FreeLibrary(module);
}
}
Omgaan met Win32-fouten
Wanneer u interop-methoden gebruikt, kunt u de GetLastError API gebruiken om aanvullende informatie over uw API-aanroepen te krijgen.
DllImport Attribuut SetLastError Attribuut
SetLastError = true
Geeft aan dat de callee SetLastError aanroept (Win32 API-functie).
SetLastError = false
Geeft aan dat de callee geen SetLastError (Win32 API-functie) aanroept, zodat u geen foutinformatie krijgt.
Als SetLastError niet is ingesteld, is deze ingesteld op false (standaardwaarde).
U kunt de foutcode verkrijgen met Marshal.GetLastWin32Error Method:
Voorbeeld:
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr OpenMutex(uint access, bool handle, string lpName);
Als u probeert mutex te openen die niet bestaat, retourneert GetLastError ERROR_FILE_NOT_FOUND .
var lastErrorCode = Marshal.GetLastWin32Error();
if (lastErrorCode == (uint)ERROR_FILE_NOT_FOUND)
{
//Deal with error
}
Systeemfoutcodes zijn hier te vinden:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
GetLastError API
Er is een native GetLastError API die u ook kunt gebruiken:
[DllImport("coredll.dll", SetLastError=true)]
static extern Int32 GetLastError();
- Wanneer u Win32 API vanuit beheerde code aanroept , moet u altijd de Marshal.GetLastWin32Error gebruiken .
Dit is waarom:
Tussen uw Win32-aanroep die de fout instelt (roept SetLastError aan), kan de CLR andere Win32-aanroepen aanroepen die ook SetLastError kunnen aanroepen, dit gedrag kan uw foutwaarde overschrijven. Als u in dit scenario GetLastError aanroept , kunt u een ongeldige fout krijgen.
SetLastError = true , zorgt ervoor dat de CLR de foutcode ophaalt voordat andere Win32-aanroepen worden uitgevoerd.
Vastgemaakt object
GC (Garbage Collector) is verantwoordelijk voor het schoonmaken van ons afval.
Terwijl GC ons afval opruimt, verwijdert hij de ongebruikte objecten uit de beheerde heap die heapfragmentatie veroorzaken. Wanneer GC klaar is met het verwijderen, voert het een heap-compressie uit (defragmintation) waarbij bewegende objecten op de heap worden verplaatst.
Omdat GC niet deterministisch, bij het passeren managed objectverwijzing / pointer naar native code, kan GC trap op elk moment, als het optreedt op Inerop oproep er een goede kans dat object (waarnaar doorgegeven aan native) zullen verplaatst worden naar de beheerde heap - als gevolg hiervan krijgen we een ongeldige verwijzing aan de beheerde zijde.
In dit scenario, moet u het object pin voordat het naar native code.
Vastgemaakt object
Vastgezet object is een object dat niet door GC mag worden verplaatst.
Gc Vastgemaakt Handvat
U kunt een pin-object maken met de methode Gc.Alloc
GCHandle handle = GCHandle.Alloc(yourObject, GCHandleType.Pinned);
- Het verkrijgen van een vastgezette GCHandle aan beheerd object markeert een specifiek object als een object dat niet kan worden verplaatst door GC , totdat de hendel wordt vrijgegeven
Voorbeeld:
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void EnterCriticalSection(IntPtr ptr);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void LeaveCriticalSection(IntPtr ptr);
public void EnterCriticalSection(CRITICAL_SECTION section)
{
try
{
GCHandle handle = GCHandle.Alloc(section, GCHandleType.Pinned);
EnterCriticalSection(handle.AddrOfPinnedObject());
//Do Some Critical Work
LeaveCriticalSection(handle.AddrOfPinnedObject());
}
finaly
{
handle.Free()
}
}
Voorzorgsmaatregelen
- Probeer bij het vastzetten van (vooral grote) objecten de vastgezette GcHandle zo snel mogelijk vrij te geven, omdat deze de defragmentatie van de heap onderbreekt.
- Als u GcHandle vergeet te bevrijden, gebeurt er niets. Doe het in een veilig codegedeelte (zoals eindelijk)
Structuren lezen met maarschalk
Marshal-klasse bevat een functie met de naam PtrToStructure , deze functie geeft ons de mogelijkheid om structuren te lezen door een onbeheerde aanwijzer.
PtrToStructure- functie heeft veel overbelastingen, maar ze hebben allemaal dezelfde bedoeling.
Generieke PtrToStructure :
public static T PtrToStructure<T>(IntPtr ptr);
T - structuurtype.
ptr - Een pointer naar een onbeheerd geheugenblok.
Voorbeeld:
NATIVE_STRUCT result = Marshal.PtrToStructure<NATIVE_STRUCT>(ptr);
- Als u te maken hebt met beheerde objecten tijdens het lezen van native structuren, vergeet dan niet uw object vast te pinnen :)
T Read<T>(byte[] buffer)
{
T result = default(T);
var gch = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try
{
result = Marshal.PtrToStructure<T>(gch.AddrOfPinnedObject());
}
finally
{
gch.Free();
}
return result;
}