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 ), aber StdCall + 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 in Cdecl :

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


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow