Nota del autor

Si la entrada que estás leyendo carece de imágenes, no se ve el vídeo que teóricamente lleva incrustado o el código fuente mostrado aparece sin formato, podéis conocer los motivos aquí. Poco a poco iré restableciendo la normalidad en el blog.
Este blog es un archivo de los artículos situados previamente en Lobosoft.es y ha dejado de ser actualizado. Las nuevas entradas pueden encontrarse en www.lobosoft.es. Un saludo,
Lobosoft.

lunes, 21 de abril de 2008

Uso dinámico de código no manejado desde .NET

El último día estuvimos viendo cómo usar las funciones de librerías creadas en C desde C#, realizando una importación de la misma en el momento de la compilación. Para ello, era necesario adornar con el atributo DllImport la función que deseáramos usar, y el CLR nos permitiría acceder a la misma. Sin embargo, podría darse el caso de necesitar enlazar con la librería en tiempo de ejecución, por ejemplo, porque fuese un elemento de terceros, o que proporcionase el propio usuario en un momento dado para usarla dentro de nuestra aplicación .NET, por ejemplo, dándonos un codificador o una funcionalidad específica. En ese caso, el proceso se complica un poco (pero no mucho), como veremos a continuación.


Lo primero que tenemos que hacer es pensar cómo estructura la memoria el CLR, y de qué manera se produce la ejecución de una aplicación .NET. Todo el código .NET es código manejado, y como tal se ejecuta sobre un framework, en una capa superior a la del sistema operativo, en una zona de memoria que podríamos denominar "segura". El código no manejado tiene acceso a recursos del propio sistema operativo, lo que lo hace más inseguro, pero a la vez más rápido y eficaz en algunas tareas. Cuando el CLR ejecuta una de nuestras aplicaciones .NET, éstas no tienen acceso a los recursos del sistema de una forma directa, sino a través del propio Framework, lo que evita accesos indebidos a recursos del sistema, así como un mayor control en caso de error. Sin embargo, esto también dificulta el acceso a las funciones que deseamos usar desde las .DLLs (hay que tener en cuenta que las DLLs de .NET no son iguales a las que obtenemos con un compilador de C o C++ "al uso"). Por ello, si queremos usar de forma dinámica de éstas, deberemos recurrir a algún pequeño truco. El primero de ellos, y el más evidente, sería usar código en C++ para acceder a la función de la DLL que deseásemos, y lanzarlo, actuando en cierto modo como un "proxy" entre el código manejado y el no manejado. Sin embargo, esta solución, si bien no es mala, implica un problema y es que para cada función requeriríamos una función proxy en C++. Otra opción más óptima es la de usar algo de lenguaje ensamblador para codificar la llamada a la función, y utilizar librerías de Win32 para ejecutar la llamada.


capas.PNG


Una posible implementación del código en ensamblador sería la siguiente:





; -------------------------------------------------------------
;
; InvokeFuncAsm - Invokes a function through a function pointer passed as
; the first argument. All other parameters are forwarded on, plus the return
; value of the function invoked is returned.
;
; Copyright (c) Richard Birkby, ThunderMain ltd, November 2001
;
; -------------------------------------------------------------

.386
.model flat

option prologue:none
option epilogue:none
option dotname

.code
align DWORD
DllMain proc stdcall public, instance:DWORD, reason:DWORD, reserved:DWORD
mov eax, 1 ; success
ret 12
DllMain endp

align DWORD
InvokeFunc proc stdcall public, funcptr:DWORD

pop ecx ; save return address
pop edx ; Get function pointer
push ecx ; Restore return address
jmp edx ; Transfer control to the function pointer
InvokeFunc endp

end


El código es de Richard Birkby, ya que tengo el ensamblador suficientemente oxidado como para no lanzarme a programar algo así, aunque no sea excesivamente complejo. Básicamente, el código recibe el un puntero a la función a invocar así como los parámetros que recibirá. La carga de la DLL que queremos usar se hará a través de la función LoadLibrary de Kernel32, y su dirección dentro del espacio de ejecución de la aplicación la podemos obtener con GetProcAddress, también de Kernel32. Se ejecuta la función mediante el código en ensamblador, que a su término retorna el valor con una instrucción jmp, que devuelve además el control a la aplicación que la llamó. Al finalizar, liberamos los recursos con FreeLibrary. El código completo puede verse a continuación.



[csharp]
///


/// Usa la librería mediante una importación dinámica, en tiempo de ejecución
///

class UsoDinamico
{
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string csFileName);

[DllImport("kernel32.dll")]
static extern IntPtr GetProcAddress(IntPtr IntPtr_Module, string
csProcName);

[DllImport("kernel32.dll")]
static extern bool FreeLibrary(IntPtr IntPtr_Module);

[DllImport("Invoke", CharSet = CharSet.Unicode)]
static extern int InvokeFunc(IntPtr funcptr, int operando1,
int operando2);

///
/// Calcula la operación Suma sobre todos los enteros introducidos en los parámetros
/// de la aplicación de consola
///

///
public static void Calcular(string[] args)
{
IntPtr DllACargar = LoadLibrary("c_math_lib.dll");
IntPtr PunteroAFuncion = GetProcAddress(DllACargar, "Sum");
int resultado, operando;

resultado = 0;

for (int i = 0; i < args.Length; i++)
{
int.TryParse(args[i], out operando);
resultado = InvokeFunc(PunteroAFuncion, resultado, operando);
}

Console.WriteLine("El resultado de la operación es " + resultado);

FreeLibrary(DllACargar);
}
}
[/csharp]

Como vemos, es bastante sencillo usar una librería dinámicamente, tanto con una importación implícita, en tiempo de compilación, como explícita durante la ejecución del programa. Nos quedará ver cómo saber qué funciones ofrece, por ejemplo, una DLL, sin necesidad de conocer sus nombres desde un principio, pero esto será tema para otra entrada.

No hay comentarios:

Publicar un comentario