C# Language
Interoperabilität
Suche…
Bemerkungen
Mit Win32-API mit C # arbeiten
Windows bietet viele Funktionen in Form der Win32-API. Mithilfe dieser API können Sie eine direkte Operation in Windows ausführen, wodurch die Leistung Ihrer Anwendung erhöht wird. Quelle Klicken Sie hier
Windows bietet eine breite Palette von APIs. Um Informationen zu verschiedenen APIs zu erhalten, können Sie Websites wie Pinvoke überprüfen.
Importieren Sie die Funktion aus einer nicht verwalteten C ++ - DLL
Im Folgenden finden Sie ein Beispiel zum Importieren einer Funktion, die in einer nicht verwalteten C ++ - DLL definiert ist. Im C ++ - Quellcode für "myDLL.dll" ist die Funktion add
definiert:
extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
{
return a + b;
}
Dann kann es wie folgt in ein C # -Programm aufgenommen werden:
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));
}
}
Erläuterungen dazu, warum extern "C"
und __stdcall
erforderlich sind, finden Sie unter Aufrufen von Konventionen und C ++ - Namensveränderung .
Die dynamische Bibliothek finden
Wenn die externe Methode zum ersten Mal aufgerufen wird, sucht das C # -Programm nach der entsprechenden DLL und lädt diese. Weitere Informationen dazu, wo gesucht wird, um die DLL zu finden, und wie Sie die Suchpositionen beeinflussen können, finden Sie in dieser Stackoverflow-Frage .
Einfacher Code, um Klasse für com verfügbar zu machen
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 ++ - Namensverstümmelung
C ++ - Compiler codieren zusätzliche Informationen in den Namen exportierter Funktionen, z. B. Argumenttypen, um Überladungen mit verschiedenen Argumenten zu ermöglichen. Dieser Vorgang wird als Namensveränderung bezeichnet . Dies führt zu Problemen mit Funktionen in C # (und Interop mit anderen Sprachen im Allgemeinen), wie der Name des Import int add(int a, int b)
Funktion ist nicht mehr add
, kann es sein ?add@@YAHHH@Z
, _add@8
oder etwas anderes, abhängig vom Compiler und der aufrufenden Konvention.
Es gibt verschiedene Wege, um das Problem der Namensveränderung zu lösen:
Exportieren von Funktionen mit
extern "C"
zum Umschalten auf externe C-Verknüpfung, die C-Namensverstümmelung verwendet:extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
Der Funktionsname wird immer noch
_add@8
(_add@8
), aberStdCall
+extern "C"
Namensveränderungen wird vom C # -Compiler erkannt.Exportierte Funktionsnamen in der
myDLL.def
:EXPORTS add
int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
Der Funktionsname wird in diesem Fall rein
add
.Verstümmelten Namen importieren Sie benötigen einen DLL-Viewer, um den beschädigten Namen zu sehen. Dann können Sie ihn explizit angeben:
__declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll", EntryPoint = "?add@@YGHHH@Z")]
Konventionen aufrufen
Es gibt verschiedene Konventionen für den Aufruf von Funktionen, die angeben, wer (Aufrufer oder Aufpasser) Argumente aus dem Stack abbricht, wie und in welcher Reihenfolge Argumente übergeben werden. C ++ verwendet Cdecl
die Cdecl
Aufrufkonvention, C # erwartet jedoch StdCall
, das normalerweise von der Windows-API verwendet wird. Sie müssen das eine oder das andere ändern:
Ändern Sie die Aufrufkonvention in
StdCall
in C ++:extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
Oder ändern Sie die Aufrufkonvention in
Cdecl
inCdecl
:extern "C" __declspec(dllexport) int /*__cdecl*/ add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
Wenn Sie eine Funktion mit der Cdecl
Aufrufkonvention und einem überflüssigen Namen verwenden möchten, Cdecl
Ihr Code folgendermaßen aus:
__declspec(dllexport) int add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?add@@YAHHH@Z")]
thiscall ( __thiscall ) wird hauptsächlich in Funktionen verwendet, die Mitglieder einer Klasse sind.
Wenn eine Funktion verwendet Thiscall (__thiscall), ein Zeiger auf die Klasse wird als der erste Parameter weitergegeben.
Dynamisches Laden und Entladen von nicht verwalteten DLLs
Wenn Sie das Attribut DllImport
, müssen Sie die korrekte DLL und den richtigen Methodennamen zur Kompilierzeit kennen . Wenn Sie flexibler sein und zur Laufzeit entscheiden möchten, welche DLLs und Methoden geladen werden sollen, können Sie die Windows-API-Methoden LoadLibrary()
, GetProcAddress()
und FreeLibrary()
. Dies kann hilfreich sein, wenn die zu verwendende Bibliothek von den Laufzeitbedingungen abhängt.
Der Zeiger zurückgegeben durch GetProcAddress()
kann in einen Delegierten gegossen werden unter Verwendung von Marshal.GetDelegateForFunctionPointer()
.
Das folgende Codebeispiel demonstriert dies mit der myDLL.dll
aus den vorherigen Beispielen:
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);
}
}
Umgang mit Win32-Fehlern
Wenn Sie Interop-Methoden verwenden, können Sie die GetLastError- API verwenden, um zusätzliche Informationen zu Ihren API-Aufrufen abzurufen .
DllImport-Attribut SetLastError-Attribut
SetLastError = true
Gibt an, dass der Aufgerufene SetLastError (Win32-API-Funktion) aufruft.
SetLastError = false
Gibt an, dass der Angerufene SetLastError (Win32-API-Funktion) nicht aufruft. Daher erhalten Sie keine Fehlerinformationen.
Wenn SetLastError nicht festgelegt ist, wird es auf false gesetzt (Standardwert).
Sie können den Fehlercode mithilfe der Marshal.GetLastWin32Error-Methode erhalten:
Beispiel:
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr OpenMutex(uint access, bool handle, string lpName);
Wenn Sie versuchen, einen nicht vorhandenen Mutex zu öffnen, gibt GetLastError ERROR_FILE_NOT_FOUND zurück .
var lastErrorCode = Marshal.GetLastWin32Error();
if (lastErrorCode == (uint)ERROR_FILE_NOT_FOUND)
{
//Deal with error
}
Systemfehlercodes finden Sie hier:
https://msdn.microsoft.com/de-de/library/windows/desktop/ms681382(v=vs.85).aspx
GetLastError-API
Es gibt eine native GetLastError- API, die Sie ebenfalls verwenden können:
[DllImport("coredll.dll", SetLastError=true)]
static extern Int32 GetLastError();
- Wenn Sie die Win32-API über verwalteten Code aufrufen, müssen Sie immer Marshal.GetLastWin32Error verwenden .
Hier ist der Grund:
Zwischen Ihrem Win32-Aufruf, der den Fehler festlegt (Aufrufe von SetLastError), kann die CLR andere Win32-Aufrufe aufrufen, die ebenfalls SetLastError aufrufen könnten . Dieses Verhalten kann Ihren Fehlerwert überschreiben. Wenn Sie in diesem Szenario GetLastError aufrufen, können Sie einen ungültigen Fehler erhalten.
Durch Festlegen von SetLastError = true wird sichergestellt, dass die CLR den Fehlercode abruft, bevor andere Win32-Aufrufe ausgeführt werden.
Gepinntes Objekt
GC (Garbage Collector) ist für die Reinigung unseres Mülls verantwortlich.
Während GC unseren Müll säubert, entfernt er die nicht verwendeten Objekte aus dem verwalteten Heap, wodurch eine Heap-Fragmentierung verursacht wird. Wenn der GC mit dem Entfernen fertig ist, führt er eine Heap-Komprimierung (Defragmentierung) durch, bei der Objekte auf dem Heap verschoben werden.
Da GC nicht deterministisch ist, wenn Objektreferenz / Zeiger auf nativen Code verwaltet geben, können GC jederzeit eintreten, wenn es nur nach Inerop Anruf auftritt, gibt es eine sehr gute Möglichkeit, dass Objekt (die sich auf einheimische bestanden) auf dem verwalteten Heap verschoben werden - als Ergebnis erhalten wir auf der verwalteten Seite eine ungültige Referenz.
In diesem Szenario sollten Sie das Objekt stecken , bevor sie an nativen Code übergeben.
Gepinntes Objekt
Ein fixiertes Objekt ist ein Objekt, das von GC nicht verschoben werden darf.
Gc Fixierter Griff
Sie können ein Pin-Objekt mit der Gc.Alloc- Methode erstellen
GCHandle handle = GCHandle.Alloc(yourObject, GCHandleType.Pinned);
- Wenn Sie ein gepinntes GCHandle- Objekt an ein verwaltetes Objekt beziehen , wird ein bestimmtes Objekt als ein Objekt markiert, das vom GC nicht verschoben werden kann, bis der Griff freigegeben wird
Beispiel:
[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()
}
}
Vorsichtsmaßnahmen
- Beim Feststecken (besonders bei großen Objekten) versuchen Sie, das festgesteckte GcHandle so schnell wie möglich freizugeben , da dies die Heap-Defragmentierung unterbricht.
- Wenn Sie vergessen, GcHandle zu befreien, wird nichts passieren . Tun Sie es in einem sicheren Code-Abschnitt (wie finaly)
Strukturen lesen mit Marschall
Die Marshal-Klasse enthält eine Funktion namens PtrToStructure . Diese Funktion gibt uns die Möglichkeit, Strukturen über einen nicht verwalteten Zeiger zu lesen.
Die PtrToStructure- Funktion hat viele Überladungen, aber alle haben die gleiche Absicht.
Generische PtrToStructure :
public static T PtrToStructure<T>(IntPtr ptr);
T - Strukturtyp.
ptr - Ein Zeiger auf einen nicht verwalteten Speicherblock.
Beispiel:
NATIVE_STRUCT result = Marshal.PtrToStructure<NATIVE_STRUCT>(ptr);
- Wenn Sie sich beim Lesen nativer Strukturen mit verwalteten Objekten beschäftigen, vergessen Sie nicht, Ihr Objekt zu fixieren :)
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;
}