Ricerca…


Osservazioni

Lavorare con API Win32 usando C #

Windows espone molte funzionalità sotto forma di API Win32. Usando queste API è possibile eseguire operazioni dirette in Windows, il che aumenta le prestazioni della tua applicazione. Source Clicca qui

Windows espone un'ampia gamma di API. Per ottenere informazioni su varie API puoi controllare siti come pinvoke .

Funzione di importazione da DLL C ++ non gestita

Ecco un esempio di come importare una funzione definita in una DLL C ++ non gestita. Nel codice sorgente C ++ per "myDLL.dll", la funzione add è definita:

extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
{
    return a + b;
}

Quindi può essere incluso in un programma C # come segue:

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

Vedi Calling convenzioni e manomissione dei nomi in C ++ per spiegazioni sul perché extern "C" e __stdcall siano necessari.

Trovare la libreria dinamica

Quando viene invocato per la prima volta il metodo extern, il programma C # cercherà e caricherà la DLL appropriata. Per ulteriori informazioni su dove viene eseguita la ricerca per trovare la DLL e su come è possibile influenzare le posizioni di ricerca, consultare questa domanda StackOverflow .

Codice semplice per esporre la classe per 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;
        }
    }
}

Mancanza di nomi in C ++

I compilatori C ++ codificano informazioni aggiuntive nei nomi delle funzioni esportate, come i tipi di argomenti, per rendere possibili sovraccarichi con argomenti diversi. Questo processo è chiamato mangling del nome . Ciò causa problemi con l'importazione di funzioni in C # (e l'interoperabilità con altre lingue in generale), poiché il nome della funzione int add(int a, int b) non viene più add , può essere ?add@@YAHHH@Z , _add@8 o qualsiasi altra cosa, a seconda del compilatore e della convenzione di chiamata.

Esistono diversi modi per risolvere il problema della manomissione dei nomi:

  • Esportare funzioni usando extern "C" per passare al collegamento esterno C che utilizza il nome C mangling:

    extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll")]
    

    Il nome della funzione sarà ancora _add@8 ( _add@8 ), ma StdCall + extern "C" nome mangling è riconosciuto dal compilatore C #.

  • Specifica dei nomi delle funzioni esportate nel file di definizione del modulo myDLL.def :

    EXPORTS
      add
    
    int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll")]
    

    In questo caso, il nome della funzione sarà pura add .

  • Importazione del nome mutilato. Avrai bisogno di un visualizzatore DLL per vedere il nome storpiato, quindi puoi specificarlo esplicitamente:

    __declspec(dllexport) int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll", EntryPoint = "?add@@YGHHH@Z")]
    

Chiamare convenzioni

Esistono diverse convenzioni delle funzioni di chiamata, che specificano chi (chiamante o chiamato) apre gli argomenti dallo stack, come vengono passati gli argomenti e in quale ordine. C ++ utilizza la convenzione di chiamata Cdecl per impostazione predefinita, ma C # si aspetta che StdCall venga utilizzato di solito dalle API di Windows. Devi cambiare uno o l'altro:

  • Modifica convenzione di chiamata a StdCall in C ++:

    extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll")]
    
  • In alternativa, modifica la convenzione di Cdecl in Cdecl in C #:

    extern "C" __declspec(dllexport) int /*__cdecl*/ add(int a, int b)
    
    [DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
    

Se si desidera utilizzare una funzione con convenzione di chiamata Cdecl e un nome storpiato, il codice sarà simile a questo:

__declspec(dllexport) int add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl,
           EntryPoint = "?add@@YAHHH@Z")]
  • thiscall ( __thiscall ) è usato principalmente in funzioni che sono membri di una classe.

  • Quando una funzione utilizza thiscall ( __thiscall ), un puntatore alla classe viene passato come primo parametro.

Caricamento e scaricamento dinamico di DLL non gestite

Quando si utilizza l'attributo DllImport è necessario conoscere la dll corretta e il nome del metodo in fase di compilazione . Se si desidera essere più flessibili e decidere in fase di esecuzione quali DLL e metodi da caricare, è possibile utilizzare i metodi dell'API di Windows LoadLibrary() , GetProcAddress() e FreeLibrary() . Questo può essere utile se la libreria da utilizzare dipende dalle condizioni di runtime.

Il puntatore restituito da GetProcAddress() può essere convertito in un delegato utilizzando Marshal.GetDelegateForFunctionPointer() .

Il seguente esempio di codice lo dimostra con myDLL.dll degli esempi precedenti:

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

Gestione degli errori Win32

Quando si utilizzano i metodi di interoperabilità, è possibile utilizzare l'API GetLastError per ottenere ulteriori informazioni sulle chiamate API dell'utente.

Attributo DllImport Attributo SetLastError

SetLastError = true

Indica che il callee chiamerà SetLastError (funzione API Win32).

SetLastError = false

Indica che il chiamato non chiamerà SetLastError (funzione API Win32), quindi non si otterranno informazioni di errore.

  • Quando SetLastError non è impostato, è impostato su false (valore predefinito).

  • È possibile ottenere il codice di errore utilizzando Marshal.GetLastWin32Error Method:

Esempio:

[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr OpenMutex(uint access, bool handle, string lpName);

Se si sta tentando di aprire il mutex che non esiste, GetLastError restituirà ERROR_FILE_NOT_FOUND .

var lastErrorCode = Marshal.GetLastWin32Error();

if (lastErrorCode == (uint)ERROR_FILE_NOT_FOUND)
{
    //Deal with error         
}

I codici di errore del sistema possono essere trovati qui:

https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx

API GetLastError

Esiste un'API GetLastError nativa che puoi usare anche:

[DllImport("coredll.dll", SetLastError=true)]
static extern Int32 GetLastError();
  • Quando si chiama l'API Win32 dal codice gestito, è necessario utilizzare sempre Marshal.GetLastWin32Error .

Ecco perché:

Tra la chiamata Win32 che imposta l'errore (chiama SetLastError), il CLR può chiamare anche altre chiamate Win32 che potrebbero chiamare SetLastError , questo comportamento può ignorare il valore dell'errore. In questo scenario, se si chiama GetLastError è possibile ottenere un errore non valido.

Impostazione SetLastError = true , assicura che il CLR recuperi il codice di errore prima di eseguire altre chiamate Win32.

Oggetto appuntato

GC (Garbage Collector) è responsabile della pulizia della nostra spazzatura.

Mentre GC pulisce la nostra spazzatura, rimuove gli oggetti non utilizzati dall'heap gestito che causano la frammentazione dell'heap. Quando GC ha terminato la rimozione, esegue una compressione heap (defragmintation) che implica lo spostamento di oggetti nell'heap.

Dato che GC non è deterministico, quando passa il riferimento / puntatore dell'oggetto gestito al codice nativo, GC può dare il via in qualsiasi momento, se si verifica subito dopo la chiamata Inerop, c'è una buona possibilità che l'oggetto (il riferimento passato a nativo) possa essere spostati sull'heap gestito - di conseguenza, otteniamo un riferimento non valido sul lato gestito.

In questo scenario, è necessario bloccare l'oggetto prima di passarlo al codice nativo.

Oggetto appuntato

L'oggetto appuntato è un oggetto che non è autorizzato a spostare da GC.

Maniglia appuntata Gc

Puoi creare un oggetto pin usando il metodo Gc.Alloc

GCHandle handle = GCHandle.Alloc(yourObject, GCHandleType.Pinned); 
  • Ottenere un GCHandle aggiunto all'oggetto gestito contrassegna un oggetto specifico come uno che non può essere spostato da GC , fino a liberare l'handle

Esempio:

[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()
    }
}

Precauzioni

  • Quando si immobilizza (specialmente oggetti di grandi dimensioni), provare a rilasciare il GcHandle bloccato il più velocemente possibile, poiché interrompe la deframmentazione dell'heap.
  • Se dimentichi di liberare GcHandle, niente lo farà. Fallo in una sezione di codice sicura (come ad esempio finaly)

Leggere strutture con Maresciallo

La classe Marshal contiene una funzione denominata PtrToStructure , questa funzione ci dà la possibilità di leggere le strutture con un puntatore non gestito.

La funzione PtrToStructure ha molti sovraccarichi, ma tutti hanno la stessa intenzione.

PtrToStructure generico:

public static T PtrToStructure<T>(IntPtr ptr);

T - tipo di struttura.

ptr - Un puntatore a un blocco di memoria non gestito.

Esempio:

NATIVE_STRUCT result = Marshal.PtrToStructure<NATIVE_STRUCT>(ptr);       
  • Se hai a che fare con oggetti gestiti durante la lettura di strutture native, non dimenticare di appuntare il tuo oggetto :)
 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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow