Buscar..


Observaciones

Trabajando con la API de Win32 usando C #

Windows expone muchas funcionalidades en forma de API de Win32. Usando estas API, puede realizar operaciones directas en Windows, lo que aumenta el rendimiento de su aplicación. Fuente Haga clic aquí

Windows expone una amplia gama de API. Para obtener información sobre varias API, puede consultar sitios como Pinvoke .

Función de importación desde DLL de C ++ no administrado

Este es un ejemplo de cómo importar una función que está definida en una DLL de C ++ no administrada. En el código fuente de C ++ para "myDLL.dll", la función add está definida:

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

Luego se puede incluir en un programa de C # de la siguiente manera:

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

Consulte Convenciones de llamadas y el nombre de C ++ para obtener explicaciones sobre por qué son necesarias extern "C" y __stdcall .

Encontrando la librería dinámica.

Cuando se invoca por primera vez el método externo, el programa C # buscará y cargará la DLL adecuada. Para obtener más información sobre dónde se busca el DLL, y cómo puede influir en las ubicaciones de búsqueda, consulte esta pregunta de stackoverflow .

Código simple para exponer la clase para 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;
        }
    }
}

Nombre en C ++

Los compiladores de C ++ codifican información adicional en los nombres de las funciones exportadas, como los tipos de argumentos, para hacer posible las sobrecargas con diferentes argumentos. Este proceso se llama mutilación de nombres . Esto causa problemas con la importación de funciones en C # (y la interoperabilidad con otros lenguajes en general), ya que el nombre de int add(int a, int b) ya no se add , puede ser ?add@@YAHHH@Z , _add@8 o cualquier otra cosa, dependiendo del compilador y la convención de llamada.

Hay varias formas de resolver el problema de la manipulación de nombres:

  • Exportación de funciones usando extern "C" para cambiar a enlace externo C que usa la denominación de nombres C:

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

    El nombre de la función seguirá siendo _add@8 ( _add@8 ), pero el compilador de C # reconoce el nombre de la función extern "C" StdCall + extern "C" .

  • Especificando nombres de funciones exportadas en el archivo de definición de módulo myDLL.def :

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

    El nombre de la función será puro add en este caso.

  • Importando nombre destrozado. Necesitará algún visor de DLL para ver el nombre mutilado, luego puede especificarlo explícitamente:

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

Convenciones de llamadas

Existen varias convenciones sobre las funciones de llamada, que especifican quién (el llamante o el que recibe la llamada) saca los argumentos de la pila, cómo se pasan los argumentos y en qué orden. C ++ usa la convención de llamadas Cdecl de forma predeterminada, pero C # espera StdCall , que generalmente es utilizado por la API de Windows. Necesitas cambiar uno u otro:

  • Cambie la convención de llamada a StdCall en C ++:

    extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll")]
    
  • O bien, cambie la convención de llamada a Cdecl en C #:

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

Si desea usar una función con la convención de llamadas Cdecl y un nombre mutilado, su código se verá así:

__declspec(dllexport) int add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl,
           EntryPoint = "?add@@YAHHH@Z")]
  • thiscall ( __thiscall ) se usa principalmente en funciones que son miembros de una clase.

  • Cuando una función usa thiscall ( __thiscall ), un puntero a la clase se pasa como primer parámetro.

Carga y descarga dinámica de archivos DLL no administrados

Cuando use el atributo DllImport , debe saber el dll correcto y el nombre del método en el momento de la compilación . Si desea ser más flexible y decidir en tiempo de ejecución qué dll y qué métodos cargar, puede usar los métodos de la API de Windows LoadLibrary() , GetProcAddress() y FreeLibrary() . Esto puede ser útil si la biblioteca a usar depende de las condiciones de tiempo de ejecución.

El puntero devuelto por GetProcAddress() puede convertir en un delegado utilizando Marshal.GetDelegateForFunctionPointer() .

El siguiente ejemplo de código lo demuestra con el myDLL.dll de los ejemplos anteriores:

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

Tratar con los errores de Win32

Al usar métodos de interoperabilidad, puede usar la API GetLastError para obtener información adicional sobre sus llamadas a la API.

Atributo DllImport Atributo SetLastError

SetLastError = true

Indica que el destinatario llamará a SetLastError (función de API de Win32).

SetLastError = false

Indica que la persona que llama no llamará a SetLastError (función de la API de Win32), por lo tanto, no obtendrá información de error.

  • Cuando SetLastError no está establecido, se establece en falso (valor predeterminado).

  • Puede obtener el código de error utilizando el método Marshal.GetLastWin32Error:

Ejemplo:

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

Si intenta abrir la exclusión mutua que no existe, GetLastError devolverá ERROR_FILE_NOT_FOUND .

var lastErrorCode = Marshal.GetLastWin32Error();

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

Los códigos de error del sistema se pueden encontrar aquí:

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

API GetLastError

Existe una API nativa de GetLastError que también puede utilizar:

[DllImport("coredll.dll", SetLastError=true)]
static extern Int32 GetLastError();
  • Al llamar a la API de Win32 desde el código administrado, siempre debe usar Marshal.GetLastWin32Error .

Este es el por qué:

Entre su llamada a Win32 que establece el error (llamadas a SetLastError), el CLR puede llamar a otras llamadas de Win32 que también podrían llamar a SetLastError , este comportamiento puede anular su valor de error. En este escenario, si llama a GetLastError , puede obtener un error no válido.

Al establecer SetLastError = true , se asegura de que el CLR recupera el código de error antes de ejecutar otras llamadas de Win32.

Objeto fijado

GC (recolector de basura) es responsable de limpiar nuestra basura.

Mientras GC limpia nuestra basura, elimina los objetos no utilizados del montón administrado que causa la fragmentación del montón. Cuando GC termina con la eliminación, realiza una compresión del montón (desfragmentación) que implica mover objetos en el montón.

Dado que GC no es determinista, al pasar la referencia / puntero del objeto administrado al código nativo, GC puede activarse en cualquier momento, si ocurre justo después de la llamada Inerop, existe una gran posibilidad de que el objeto (referencia que se pasa al nativo) ser movido en el montón administrado - como resultado, obtenemos una referencia no válida en el lado administrado.

En este escenario, debe fijar el objeto antes de pasarlo al código nativo.

Objeto fijado

El objeto fijado es un objeto que no está permitido mover por GC.

Mango de Gc Pinned

Puedes crear un objeto pin usando el método Gc.Alloc

GCHandle handle = GCHandle.Alloc(yourObject, GCHandleType.Pinned); 
  • La obtención de un GCHandle anclado a un objeto administrado marca un objeto específico como uno que GC no puede mover hasta que se libera el controlador.

Ejemplo:

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

Precauciones

  • Al fijar (especialmente los objetos grandes) el objeto, intente liberar el GcHandle anclado lo más rápido posible, ya que interrumpe la desfragmentación del montón.
  • Si olvidas liberar GcHandle nada lo hará. Hágalo en una sección de código de seguridad (como finaly)

Estructuras de lectura con mariscal

La clase Marshal contiene una función llamada PtrToStructure , esta función nos permite leer estructuras mediante un puntero no administrado.

La función PtrToStructure tiene muchas sobrecargas, pero todas tienen la misma intención.

PtrToStructure genérico:

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

T - tipo de estructura.

ptr : un puntero a un bloque de memoria no administrado.

Ejemplo:

NATIVE_STRUCT result = Marshal.PtrToStructure<NATIVE_STRUCT>(ptr);       
  • Si trata con objetos gestionados mientras lee estructuras nativas, no olvide fijar su objeto :)
 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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow