[ English | Español | Pyccκuú ]

ImpLib SDK

Fecha de publicación:
Última actualización: 2021-06-25
Autor:

ImpLib SDK sirve para crear librerías de importación MS-COFF personalizadas con funciones avanzadas, a saber:

¿Cómo funciona?

El primer paso es generar un listado de los símbolos (es decir, funciones) exportados por la DLL. La sintaxis es muy simple y similar al formato de archivo DEF de Microsoft. Por ejemplo, hagamos una librería de importación con algunas funciones de Entrada/Salida exportadas por KERNEL32:

include 'implib.inc'

   ; KERNEL32.DLL
   implib kernel32.dll, CreateFileA
   implib kernel32.dll, CreateFileW
   implib kernel32.dll, ReadFile
   implib kernel32.dll, SetFilePointer
   implib kernel32.dll, CloseHandle

endlib

Todo lo que esté precedido por un ';' es un comentario. Antes de comenzar a describir los símbolos, se debe colocar una referencia a implib.inc. Se requiere indicar la ruta relativa o absoluta, a menos que implib.inc esté en el mismo directorio donde se encuentra el archivo actual. Después de completar la lista, no olvide colocar el comando endlib. Cada definición de símbolo comienza con el comando implib, seguido de una serie de argumentos, siendo los más importantes el nombre de DLL y el nombre simbólico de la función. Puede especificar el valor ordinal en lugar del nombre simbólico, con el prefijo 'ord.':

   implib kernel32.dll, ord.5
Si desea que el linker indexe un símbolo con un nombre público diferente, agregue otro argumento con el nombre que desee:
   implib kernel32.dll, CreateFileA, CreateFile

De esta manera, cuando se hace referencia a CreateFile en el código objeto, el linker lo resolverá como KERNEL32!CreateFileA. Por lo tanto, no es necesario especificar el sufijo 'A'. Esta función es útil para traducir nombres simbólicos. Por ejemplo, las herramientas de MS asumen que la función STDCALL CreateFileA con 7 argumentos debe llamarse _CreateFileA@28. Teniendo esto en cuenta, podemos reescribir nuestro primer ejemplo para que sea compatible con las convenciones de nombres de MS:

include 'sdk\implib.inc'

   ; KERNEL32.DLL
   implib kernel32.dll, CreateFileA,    _CreateFileA@28
   implib kernel32.dll, CreateFileW,    _CreateFileW@28
   implib kernel32.dll, ReadFile,       _ReadFile@20
   implib kernel32.dll, SetFilePointer, _SetFilePointer@16
   implib kernel32.dll, CloseHandle,    _CloseHandle@4

endlib

Como puede ver, los nombres STDCALL tienen prefijo '_' y sufijo '@#', donde # el número de argumentos x4. En realidad, # es el número de bytes que ocupan todos los parámetros de la función cuando son almacenados en la pila. Usualmente este valor equivale al número de argumentos x4.

Guardemos este listado con el nombre kernel32.def. Puede elegir cualquier otro nombre. Estamos listos para compilarlo desde la línea de comando:

   bin\fasm kernel32.def kernel32.lib
   flat assembler  version 1.67.23  (533081 kilobytes memory)
   3 passes, 4247 bytes.

¡Obtuvimos un archivo llamado kernel32.lib! Por cierto, FASM es realmente un ensamblador, pero su preprocesador es tan avanzado que podemos usarlo para ejecutar toda la lógica de ImpLib :-)

Existen más argumentos opcionales para implib. Puede abrir implib.inc en un editor de texto plano y leer el encabezado con la descripción detallada de todos los argumentos.

DLL2DEF

Las DLL grandes pueden contener muchísimas funciones. Listarlas manualmente en un archivo de texto sería una tarea demasiado tediosa. Se incluye una herramienta DLL2DEF en el subdirectorio \bin, la cual sirve para automatizar este proceso. Por ejemplo:

   dll2def c:\windows\system32\kernel32.dll

¡Obtuvimos un archivo llamado kernel32.def con el listado de todas las funciones públicas de KERNEL32.DLL! La referencia de implib.inc contiene la ruta completa (solo si implib.inc se encuentra en el mismo directorio que dll2def.exe). Por lo tanto, puede mover el archivo a cualquier lugar que desee sin tener que actualizar la ubicación de implib.inc. Cada definición de símbolo comienza con un par de comentarios, a saber:

   ; KERNEL32.AddVectoredExceptionHandler ORD:9
   ; -> NTDLL.RtlAddVectoredExceptionHandler
   implib kernel32.dll, AddVectoredExceptionHandler

Los comentarios incluyen el nombre simbólico completo (si está disponible), el valor ordinal y la referencia de otras DLL, si corresponde. Conocer el ordinal es útil si existe una buena razón para utilizar la importación por ordinal. La referencia de otras DLL significa que el símbolo realmente corresponde a otra DLL. En el ejemplo anterior, el símbolo AddVectoredExceptionHandler de KERNEL32.DLL realmente corresponde a una función de NTDLL. Puede actualizar la definición del símbolo para importar directamente desde la DLL real, pero generalmente no se recomienda hacer esto por razones de compatibilidad. Si no desea ver estos comentarios en el listado, simplemente incluya el argumento /COMPACT en el llamado a DLL2DEF:

   sdk\dll2def /COMPACT c:\windows\system32\kernel32.dll

Modificaciones

Actualmente existen 2 modificaciones o mods:

Reemplace el comando implib con vbimplib o pbimplib en el listado ".def" para usar las modificaciones de Visual Basic o PureBasic respectivamente. Al llamar a DLL2DEF, agregue el parámetro /VB o /PB en la línea de comandos para que se use el mod correspondiente. Puede consultar los tutoriales a continuación para ver ejemplos más completos.

Tutoriales

Luego de leer la introducción anterior, los siguientes minitutoriales presentan ejemplos de uso real:

MSVCRT.lib para MASM32 y NASM

La librería de tiempo de ejecución (runtime) de Microsoft Visual C, conocida como MSVCRT.DLL, exporta más de 700 funciones, automatizando muchas tareas de programación comunes, como formateo de Entrada/Salida por consola, administración de memoria, manipulación de cadenas de texto, ordenamiento, etc. Las mismas funcionalidades pueden ser implementadas por medio de la API de Windows. Sin embargo, dado que la librería de C casi siempre está preinstalado en todas las versiones de Windows (excepto en Windows 95), tiene sentido utilizar esta DLL en lugar de crear versiones propias de printf(), malloc(), etc. Entonces, el objetivo de nuestro primer tutorial es crear un par de programas de ejemplo en MASM32 y NASM usando MSVCRT.DLL.

Comencemos con MASM32. Ya existe un archivo de cabecera para MSVCRT, llamado msvcrt.inc, en la última versión de MASM32, pero no se incluye la librería de importación msvcrt.lib necesaria para el linker. Si tiene Visual Studio instalado, puede obtener la versión dinámica de msvcrt.lib desde allí, pero hay un pequeño conflicto de nombres con las funciones div() y fabs(). No se puede usar div o fabs como nombre de una función en MASM32 o NASM, ya que se genera un conflicto con los mnemónicos (conjunto de instrucciones x86) del mismo nombre. El mismo error se presenta cuando se usan nombres con solo caracteres numéricos, o cualquiera de las palabras reservadas por el compilador, como proc, label, etc. Las librerías de importación en Visual Studio a veces introducen dependencias adicionales de OLDNAMES.lib, UUID.lib y otras. Parece que no se permite redistribuir la librería de importación de Visual Studio original. Por eso MASM32 no incluye msvcrt.lib.

Por lo tanto, crearemos nuestro propio MSVCRT.lib sin derechos de autor de terceros, sin generar conflicto con las palabras reservadas de MASM o NASM.

El primer paso es crear el listado con todos los nombres públicos que se encuentran en MSVCRT.DLL:

   dll2def /COMPACT \windows\system32\msvcrt.dll

A continuación, necesitamos resolver el conflicto de nombres. Podemos abrir msvcrt.def en el Bloc de notas y buscar la siguiente línea:

   implib msvcrt.dll, div

Dado que div es el mnemónico de división de enteros sin signo, necesitamos cambiar el nombre de esta función. Hutch sugirió agregar el prefijo '_crt_' (refiérase al foro oficial de masm32 para mayor detalle). Entonces, este está el reemplazo:

   implib msvcrt.dll, div, _crt_div

Hagamos lo mismo con la función de fabs. Se recomienda agregar '_' como prefijo en el alias de salto (thunk) para todos los nombres de funciones que no comiencen con este caracter. Esto permite llamar a estas funciones a través de la macro invoke en MASM32. Por ejemplo:

   ; MSVCRT.printf ORD:742
   implib msvcrt.dll, printf

Se recomienda actualizar la definición de la siguiente manera:

   ; MSVCRT.printf ORD:742
   implib msvcrt.dll, printf, _printf

La misma definición incluyendo el ordinal sería así:

   ; MSVCRT.printf ORD:742
   implib msvcrt.dll, ord.742, _printf, __imp__printf

Dado que el nombre real de la función se pierde cuando se usa su ordinal, se recomienda especificar el alias de salto (_printf) y el nombre público de importación (__imp__printf) explícitamente en este caso. ImpLib no necesita estos nombres, pero hacer el llamado a printf() será complicado sin los nombres ;-)

Compilemos la librería. Dado que ImpLib se ejecuta de modo interpretado por el preprocesador de FASM y el listado contiene más de 700 funciones, puede tardar un tiempo en completar la tarea (hasta un par de minutos en una máquina rápida):

   \sdk\fasm msvcrt.def msvcrt.lib
   flat assembler  version 1.67.23  (518320 kilobytes memory)
   3 passes, 35.4 seconds, 460585 bytes.

Ahora tenemos nuestro propio msvcrt.lib :-) O una serie de errores de sintaxis, símbolos duplicados u otros mensajes poco alentadores. Hay que corregir los errores y compilar de nuevo. Una versión de msvcrt.def lista para usar y un msvcrt.lib precompilado están disponibles en \src\test.

Para llamar a funciones C en MASM32 usando invoke, necesita especificar los prototipos. Hay un ejemplo de un archivo de cabecera con prototipos C en \src\test\msvcrt.inc. Como alternativa, puede usar PROTO en lugar de externdef, si desea llamar a la librería de C por medio de saltos (thunks). Por ejemplo:

   printf PROTO C :DWORD,:VARARG

El llamado a printf() usando invoke con el prototipo anterior generará call <jmp msvcrt!printf> en lugar de call msvcrt!printf. En otras palabras, se utilizará un salto adicional. El uso de estos saltos no favorece el rendimiento. Por eso, en la mayoría de los casos se recomienda crear los prototipos de funciones externas con externdef, aunque la sintaxis sea menos elegante:

   cdecl_dword_vararg typedef PROTO C :DWORD,:VARARG
   externdef _imp__printf:PTR cdecl_dword_vararg
   printf equ <_imp__printf>

Esta vez, invoke printf() generará una llamada directa, sin saltos.

A continuación se presenta un ejemplo completo para MASM32 que realiza una llamada a MSVCRT!printf:

   .386
   .model flat,stdcall

   ; MSVCRT API
   include msvcrt.inc
   includelib msvcrt.lib

   .CODE
   format db "Hola mundo!",0

   start:
      invoke printf,OFFSET format
      ret
   END start

Compilemos, enlacemos y ejecutemos el ejemplo desde la línea de comando:

   \masm32\bin\ml /c /coff test_masm32.asm
   \masm32\bin\link /SUBSYSTEM:CONSOLE test_masm32.obj
   test_masm32
   Hola mundo!

El mismo ejemplo, pero para NASM:

   EXTERN __imp__printf
   %define printf [__imp__printf]

   section .text
   format db "Hola mundo!",0

   GLOBAL _start
   _start:
      push format
      call printf
      add esp,4 ; fix the stack
      ret

Compilemos, enlacemos y ejecutemos:

   \nasm\nasmw -fwin32 test_nasm.asm
   \nasm\polink /ENTRY:start /SUBSYSTEM:CONSOLE test_nasm.obj msvcrt.lib
   test_nasm
   Hola mundo!

Con esto se concluye el tutorial de MSVCRT.LIB.

OpenAL PureBasic Userlib

La intención es generar un módulo Userlib para PureBasic que interactúe con una DLL externa. La DLL de ejemplo es OPENAL32.DLL del paquete de distribución de OpenAL v1.0 / v1.1. Esta DLL usa la convención de llamado CDECL para toda la API. Por lo tanto, no podemos usar la herramienta de importación de DLL de PureBasic. Podríamos usar PureBasic v4 o posterior con la sintaxis de ImportC y el archivo OpenAL32.lib que se incluye en el SDK de OpenAL, o simplemente usar OpenLibrary seguido de CallCFunctionFast cada vez que necesitemos invocar una función OpenAL. Posiblemente existan soluciones alternativas más complejas, como usar una DLL contenedora, etc. En este tutorial crearemos una librería Userlib para PureBasic, que nos dará acceso directo a OpenAL, con una sobrecarga mínima. Las llamadas serán más rápidas en comparación con CallCFunctionFast, contaremos con validación de prototipos, ayuda en línea y cualquier otra ventaja que ofrece Userlib. También funcionará en PureBasic v3.

En primer lugar, necesitamos crear el listado de símbolos de importación:

   dll2def /PB /COMPACT \windows\system32\OpenAL32.dll

Esto creará un archivo llamado openal32.def, con la lista de todos los símbolos públicos que se encuentran en OpenAL32.dll. Por ejemplo:

   pbimplib OpenAL32.dll, STDCALL, 0, alBuffer3f

La sintaxis completa del comando pbimplib es la siguiente:

   pbimplib dllname, convention, numarguments, real_name, PureBasics_name, public_name

El parámetro dllname contiene el nombre de archivo de la DLL. El parámetro convention identifica la convención de llamado para la función especificada. Actualmente se admiten los siguientes valores:

numarguments indica la cantidad de valores de 32 bits que se requieren para almacenar los argumentos de la función en la pila. Generalmente coincide con la cantidad de argumentos, excepto cuando se trata de parámetros de 64 bits (double, __int64, etc.) Este valor no se usa realmente en STDCALL. real_name es el nombre de la función, tal y como aparece en la tabla de exportación de la DLL. Los 2 últimos parámetros son opcionales. PureBasics_name permite personalizar el nombre de la función en PureBasic. Por defecto, cuando no se especifica, se utiliza real_name. public_name está reservado para casos muy específicos, generalmente relacionados con la programación en ensamblador. Este parámetro permite especificar el nombre del símbolo para llamar a una función directamente, sin utilizar saltos (thunks). Generalmente no es necesario preocuparse por esto. Revisemos el listado generado una vez más:

   pbimplib OpenAL32.dll, STDCALL, 0, alBuffer3f

alBuffer3f() no es una función STDCALL y no recibe 0 argumentos. DLL2DEF no puede adivinar la cantidad de argumentos y la convención de llamado porque no puede leer la "Guía del programador de OpenAL" :-) El uso de "STDCALL, 0" evita la reparación automática de la pila después de llamar a la función dada. El prototipo correcto debería ser:

   pbimplib OpenAL32.dll, CDECL, 5, alBuffer3f

Puede actualizar las demás definiciones o simplemente usar el listado ya corregido en src\PureBasic\openal32.def. Compilemos:

   fasm openal32.def pbopenal.lib

¿Sin errores? - Bien. El siguiente paso es crear una descripción de librería con el mismo nombre que la librería de importación recién creada, pero con extensión .desc. En el ejemplo actual este archivo debería llamarse pbopenal.desc. Consulte en PureBasic\Library SDK\Readme.txt si no conoce la herramienta LibraryMaker.

   ; Lenguaje de la librería: ASM o C. En este caso debe ser C.
   C

   ; Número de DLL de Windows requeridas para esta librería: ninguna en este caso.
   0

   ; El tipo de librería (puede ser OBJ o LIB). Usaremos LIB, ya que la librería es pbopenal.lib.
   LIB

El resto del archivo depende del contenido real de la librería. Como ejemplo podemos usar src\PureBasic\pbopenal.desc. Finalmente, usemos LibraryMaker.exe para crear nuestra Userlib:

   LibraryMaker.exe pbopenal.desc /COMPRESSED

Simplemente mueva esta Userlib a PureBasic\PureLibraries\UserLibraries e intente compilar una aplicación de prueba simple. Puede encontrar un ejemplo de PureBasic OpenAL en uFMOD.

Eso es todo. Espero que este minitutorial haya sido útil.
 

El proyecto ImpLib SDK inició hace más de 15 años. Más de 8 mil descargas demuestran que esta herramienta resultó ser más popular de lo que podría haber imaginado. Aun me dedico a la programación de bajo nivel, principalmente para sistemas embebidos. Tengo un formulario de contacto disponible en mi sitio web actual: CelerSMS.

 
© 2006-2021Vladimir Kameñar.
Todos los derechos reservados.