SDK Multiplataforma en C logo

SDK Multiplataforma en C

Crear nueva librería

❮ Anterior
Siguiente ❯

Lo único que debes saber absolutamente es donde está ubicada la librería. Albert Einstein


El uso de librerías nos va a permitir compartir código común entre varios proyectos. Sirva como ejemplo el SDK de NAppGUI, que se ha organizado en varias librerías de enlace estático o dinámico y que pueden ser reutilizadas por diferentes aplicaciones.


1. Librerías estáticas

Vamos a rescatar dos aplicaciones incluidas en los ejemplos de NAppGUI: Die (Figura 1) y Dice (Figura 2). En ambas se debe poder dibujar la silueta de un dado.

Captura de la aplicación Die, que permite dibujar un dado paramétricamente.
Figura 1: Aplicación Die.
Captura de la aplicación Dice, que dibuja seis dados de forma aleatoria.
Figura 2: Aplicación Dice.

No es muy complicado intuir que podríamos reutilizar la rutina de dibujo paramétrico en ambos proyectos. Una forma de hacerlo sería copiando dicha rutina desde Die a Dice, pero esto no es lo más aconsejable ya que tendríamos dos versiones del mismo código que mantener. Otra opción, la más sensata, es mover la función de dibujo a una librería y vincularla en ambas aplicaciones.

Descarga el ejemplo completo desde este enlace. La estructura del proyecto es muy similar a lo visto en el capítulo anterior, empezando por el CMakeLists.txt principal:

CMakeLists.txt.
1
2
3
4
5
6
7
8
9
cmake_minimum_required(VERSION 3.0)
project(Dice)
find_package(nappgui REQUIRED)
include("${NAPPGUI_ROOT_PATH}/prj/NAppProject.cmake")
include("${NAPPGUI_ROOT_PATH}/prj/NAppCompilers.cmake")
nap_config_compiler()
nap_project_library(casino casino)
nap_project_desktop_app(Die die)
nap_project_desktop_app(Dice dice)
  • Línea 1: Establece la versión mínima de CMake.
  • Línea 2: Nombre del proyecto.
  • Línea 3: Localiza la instalación del NAppGUI-SDK.
  • Línea 4: Incluye el módulo NAppProject.cmake.
  • Línea 5: Incluye el módulo NAppCompilers.cmake.
  • Línea 6: Configura el compilador.
  • Línea 7: Crea un target librería en el directorio casino.
  • Línea 8: Crea un target aplicación en el directorio die.
  • Línea 9: Crea un target aplicación en el directorio dice.

Observa que el comando nap_project_library() precede a las aplicaciones. Esto se debe a que CMake necesita procesar las dependencias antes que los proyectos que las utilizan.

 
nap_project_library(libName path)
  • libName: Nombre de la librería.
  • path: Ruta relativa donde se ubica el proyecto.

Al igual que con los proyectos de aplicaciones, la primera vez que se ejecuta nap_project_library(), se crean una serie de archivos predeterminados. Posteriormente se podrán editar, eliminar o añadir más como acabamos de ver en el caso de las aplicaciones.

En die/CMakeLists.txt y dice/CMakeLists.txt vemos la vinculación con casino:

die/CMakeLists.txt
 
nap_desktop_app(Die "casino" NRC_EMBEDDED)
dice/CMakeLists.txt
 
nap_desktop_app(Dice "casino" NRC_NONE)

Por el momento, no te preocupes de las constantes NRC_EMBEDDED y NRC_NONE. En Procesamiento de recursos las veremos con detenimiento. Puedes generar y compilar el proyecto de la forma habitual:

 
// Windows
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=C:/nappgui
cmake --build build --config Debug

// macOS
cmake -G Xcode -S . -B build -DCMAKE_INSTALL_PREFIX=/usr/local/nappgui
cmake --build build --config Debug

// Linux
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr/local/nappgui
cmake --build build

En build/Debug/bin tendrás los ejecutables. Tanto Die como Dice han añadido una dependencia con casino (Figura 3) por medio del parámetro dependList del comando nap_desktop_app(). De esta forma CMake sabe que debe enlazar, además de NAppGUI-SDK (NAPPGUI_LIBRARIES), la librería casino que es donde se encuentra código común de ambos proyectos (Figura 4).

Esquema que muestra las dependencias de Die y Dice.
Figura 3: Árbol de dependencias de las aplicaciones, centrado en la librería casino.
Captura de Visual Studio que muestra un nuevo proyecto con la librería casino.
Figura 4: Solución NAppDice con los tres proyectos.

¿Que significa realmente que Die y Dice tengan una dependencia con casino? Que a partir de ahora ninguna de ellas se podrá compilar si hay algún error en el código de casino, ya que es un módulo fundamental para ambas. Dentro del proyecto de compilación (Visual Studio, Xcode, Makefile, Ninja, etc) han ocurrido varias cosas:

  • Las dos aplicaciones saben donde está ubicada casino, por lo que pueden hacer #include "casino.h" sin preocuparse de su ubicación.
  • El código binario de las funciones de casino se incluirá en cada ejecutable en el proceso de enlazado. CMake ya se encargó de vincular la librería con los ejecutables.
  • Cualquier cambio realizado en casino obligará a recompilar las aplicaciones debido al punto anterior. De nuevo, el proyecto de compilación sabrá como hacerlo de la forma más eficiente posible. Tan solo deberemos volver a lanzar cmake --build build para actualizar todos los binarios.

2. Librerías dinámicas

Las librerías dinámicas son, en esencia, lo mismo que las estáticas. Lo único que cambia es la forma con la que se vinculan al ejecutable (Figura 5). En el enlace estático, el código de la librería se añade al propio ejecutable, por lo que el tamaño de este último crecerá. En el enlace dinámico el código de la librería se distribuye en su propio archivo (.dll, .so, .dylib) y se carga justamente antes que el programa ejecutable.

Gráfico que compara el enlace estático y dinámico de librerías.
Figura 5: Enlace estático o dinámico de casino.

Para crear la versión dinámica de casino, abre casino/CMakeLists.txt y cambia el parámetro buildShared de nap_library() de NO a YES.

/casino/CMakeLists.txt
 
nap_library(casino "" YES NRC_NONE)
target_include_directories(casino PUBLIC "${NAPPGUI_INCLUDE_PATH}")

Tras re-generar y re-compilar la solución, observarás que en build/Debug/bin aparece un nuevo casino.dll. Esta dll será compartida por Die.exe y Dice.exe, algo que no ocurría al compilar la versión estática.

/build/bin/Debug
 
12/18/23  04:38 PM    <DIR>          .
12/18/23  03:59 PM    <DIR>          ..
12/18/23  04:38 PM            53,248 casino.dll
12/18/23  04:38 PM            92,672 Dice.exe
12/18/23  04:38 PM           102,400 Die.exe

2.1. Ventajas de las DLLs

Como hemos podido intuir en el ejemplo anterior, utilizando DLLs reduciremos el tamaño de los ejecutables, agrupando el código binario común (Figura 6), (Figura 7). Esto es precisamente lo que hacen los sistemas operativos. Por ejemplo, Die.exe necesitará acceder, en última instancia, a las funciones del API de Windows. Si todas las aplicaciones tuviesen que enlazar de forma estática los binarios de Windows, su tamaño crecería desmesuradamente y se desperdiciaría mucho espacio dentro del sistema de archivos.

Ejemplos de programación de NAppGUI en su versión de enlace estático.
Figura 6: Los ejemplos de programación ocupan 6.52 Mb en su versión estática.
Ejemplos de programación de NAppGUI en su versión de enlace estático.
Figura 7: Los ejemplos de programación ocupan 4.08 Mb en su versión dinámica.

Otra gran ventaja de las DLLs es el ahorro de memoria en tiempo de ejecución. Por ejemplo, si cargamos Die.exe, se cargará casino.dll al mismo tiempo. Pero si después cargamos Dice.exe, ambas compartirán la copia de casino.dll existente en memoria. Sin embargo, con enlace estático, existirían dos copias de casino.lib en la memoria RAM: Una integrada en Die.exe y otra en Dice.exe.

2.2. Desventajas de las DLLs

El principal inconveniente del uso de DLLs es la incompatibilidad que puede presentarse entre las diferentes versiones de una librería. Supongamos que lanzamos una primera versión de los tres productos:

 
casino.dll        102,127 (v1)
Die.exe            84,100 (v1)
Dice.exe           73,430 (v1)

Unos meses después, lanzamos una nueva versión de la aplicación Dice.exe que implica cambios en casino.dll. En ese caso, la distribución de nuestra suite quedaría así:

 
casino.dll        106,386  (v2)*
Die.exe            84,100  (v1)?
Dice.exe           78,491  (v2)*

Si no hemos sido muy cuidadosos, es muy probable que Die.exe ya no funcione al no ser compatible con la nueva versión de la DLL. Este problema trae de cabeza a muchos desarrolladores y ha sido bautizado como DLL Hell. Dado que en este ejemplo trabajamos sobre un entorno "controlado" podríamos solucionarlo sin demasiados problemas, creando una nueva versión de todas la aplicaciones funcionando bajo casino.dll(v2).

 
casino.dll        106,386  (v2)
Die.exe            84,258  (v2)
Dice.exe           78,491  (v2)

Esto no siempre será posible. Supongamos ahora que nuestra compañía desarrolla tan solo casino.dll y son terceras empresas las que trabajan en los productos finales. Ahora cada producto tendrá sus ciclos de producción y distribución (entorno no controlado) por lo que, para evitar problemas, cada compañía incluirá una copia de la versión concreta de la DLL con la que funciona su producto. Esto podría dar lugar al siguiente escenario:

 
/Apps/Die
casino.dll        114,295  (v5)
Die.exe            86.100  (v8)

/Apps/Dice
casino.dll        106,386  (v2)
Dice.exe           72,105  (v1)

Viendo esto intuimos de que las bondades del uso de DLLs ya no lo son tanto, sobre todo en lo relativo a la optimización del espacio y tiempos de carga. El caso es que se puede agravar aún más. Normalmente, las librerías se escriben para que sean lo más genéricas posible y puedan dar servicio a muchas aplicaciones. En muchas ocasiones, una aplicación concreta utiliza sólo unas pocas funciones cada librería con las que enlaza. Utilizando librerías estáticas, se puede reducir considerablemente el tamaño del ejecutable (Figura 8), ya que el enlazador sabe perfectamente que funciones concretas utiliza la aplicación y añade el código estrictamente necesario. Sin embargo, utilizando DLLs, debemos distribuir la librería completa por muy pocas funciones que utilice el ejecutable (Figura 9). En este caso, se está desperdiciando espacio y aumentando innecesariamente los tiempos de carga de la aplicación.

Esquema que muestra en enlace estático de código.
Figura 8: Con librerías estáticas se optimiza el espacio y tiempos de carga de esta aplicación.
Esquema que muestra en enlace dinámico de código.
Figura 9: Con librerías dinámicas esta aplicación ocupa más de lo que debería y aumentan sus tiempos de carga.

2.3. Comprobar vínculos con DLLs

Cuando se lanza un ejecutable, por ejemplo Die.exe, se cargan en memoria todas las librerías dinámicas vinculadas con él (en el caso de que no existan previamente). Si hay algún problema durante dicha carga, el ejecutable no podrá arrancar y el sistema operativo mostrará algún tipo error.

Vínculos en Windows

Windows mostrará un aviso de error (Figura 10) cuando no pueda cargar una DLL asociada a un ejecutable.

Mensaje de error emitido por Windows cuando no puede cargar una DLL.
Figura 10: Error en la carga de la DLL casino.

Si queremos ver que DLLs están vinculadas con un ejecutable, utilizaremos el comando dumpbin.

 
dumpbin /dependents Die.exe

Dump of file Die.exe

File Type: EXECUTABLE IMAGE

  Image has the following dependencies:

    casino.dll
    KERNEL32.dll
    USER32.dll
    GDI32.dll
    SHELL32.dll
    COMDLG32.dll
    gdiplus.dll
    SHLWAPI.dll
    COMCTL32.dll
    UxTheme.dll
    WS2_32.dll

Vemos, al principio, la dependencia con casino.dll. Las demás son librerías de Windows relacionadas con el kernel y la interfaz de usuario. En el caso de que hagamos un enlace estático de casino:

 
dumpbin /dependents Die.exe

Dump of file Die.exe

File Type: EXECUTABLE IMAGE

  Image has the following dependencies:

    KERNEL32.dll
    USER32.dll
    GDI32.dll
    SHELL32.dll
    COMDLG32.dll
    gdiplus.dll
    SHLWAPI.dll
    COMCTL32.dll
    UxTheme.dll
    WS2_32.dll

Ya no aparece casino.dll, al haber sido enlazada de forma estática dentro de Die.exe.

Vínculos en Linux

En Linux ocurre algo similar, obtendremos un error si no es posible cargar una librería dinámica (*.so).

 
:~/$ ./Die
./Die: error while loading shared libraries: libcasino.so: cannot open shared object file: No such file or directory

Para comprobar que librerías están vinculadas con un ejecutable utilizamos el comando ldd.

 
~/$ ldd ./Die
linux-vdso.so.1 (0x00007fff58036000)
libcasino.so => libcasino.so (0x00007f6848bf4000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f6848bba000)
libgtk-3.so.0 => /lib/x86_64-linux-gnu/libgtk-3.so.0 (0x00007f6848409000)
libgdk-3.so.0 => /lib/x86_64-linux-gnu/libgdk-3.so.0 (0x00007f6848304000)
libpangocairo-1.0.so.0 => /lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 (0x00007f68482f2000)
libpango-1.0.so.0 => /lib/x86_64-linux-gnu/libpango-1.0.so.0 (0x00007f68482a3000)
libcairo.so.2 => /lib/x86_64-linux-gnu/libcairo.so.2 (0x00007f684817e000)
libgdk_pixbuf-2.0.so.0 => /lib/x86_64-linux-gnu/libgdk_pixbuf-2.0.so.0 (0x00007f6848156000)
libgio-2.0.so.0 => /lib/x86_64-linux-gnu/libgio-2.0.so.0 (0x00007f6847f75000)
libgobject-2.0.so.0 => /lib/x86_64-linux-gnu/libgobject-2.0.so.0 (0x00007f6847f15000)
libglib-2.0.so.0 => /lib/x86_64-linux-gnu/libglib-2.0.so.0 (0x00007f6847dec000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f6847c9d000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6847aa9000)
...

Donde vemos que Die depende de libcasino.so. Las demás son dependencias del kernel de Linux, de la librería estándar de C y de GTK.

Vínculos en macOS: Utilizamos el comando otool.

 
% otool -L ./Die.app/Contents/MacOS/Die
@rpath/libcasino.dylib
/System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa
/System/Library/Frameworks/UniformTypeIdentifiers.framework/Versions/A/UniformTypeIdentifiers
/usr/lib/libc++.1.dylib
/usr/lib/libSystem.B.dylib
/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
/System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics
/System/Library/Frameworks/CoreText.framework/Versions/A/CoreText
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation
/usr/lib/libobjc.A.dylib

2.4. Carga de DLLs en tiempo de ejecución

Hasta ahora, la importación de los símbolos de las DLLs se resuelven en tiempo de compilación o, mejor dicho, en tiempo de enlace. Esto significa que:

  • Los ejecutables pueden acceder directamente a las variables globales y funciones definidas en la DLL. Volviendo al código de Dice.exe, tenemos:
  •  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    #include "ddraw.h"
    ...
    static void i_OnRedraw(App *app, Event *e)
    {
        const EvDraw *params = event_params(e, EvDraw);
        color_t green = color_rgb(102, 153, 26);
        real32_t w = params->width / 3;
        real32_t h = params->height / 2;
        real32_t p = kDEF_PADDING;
        real32_t c = kDEF_CORNER;
        real32_t r = kDEF_RADIUS;
        draw_clear(params->ctx, green);
        die_draw(params->ctx, 0.f, 0.f, w, h, p, c, r, app->face[0]);
        die_draw(params->ctx, w, 0.f, w, h, p, c, r, app->face[1]);
        die_draw(params->ctx, 2 * w, 0.f, w, h, p, c, r, app->face[2]);
        die_draw(params->ctx, 0.f, h, w, h, p, c, r, app->face[3]);
        die_draw(params->ctx, w, h, w, h, p, c, r, app->face[4]);
        die_draw(params->ctx, 2 * w, h, w, h, p, c, r, app->face[5]);
    }
    
    • Se ha realizado un #include "ddraw.h", cabecera pública de casino.
    • Se han utilizado los símbolos die_draw(), kDEF_PADDING, kDEF_CORNER, kDEF_RADIUS, definidas en ddraw.h.
  • La librería dinámica casino.dll se cargará de forma automática justamente antes de Dice.exe.
  • El uso de versión estática o dinámica de casino no implica cambios en el código de Dice. Tan solo tendríamos que cambiar el casino/CMakeLists.txt y recompilar la solución.
  • casino/CMakeLists.txt
     
    
    # Static library
    nap_library(casino "" NO NRC_NONE)
    target_include_directories(casino PUBLIC "${NAPPGUI_INCLUDE_PATH}")
    
    # Dynamic library
    nap_library(casino "" YES NRC_NONE)
    target_include_directories(casino PUBLIC "${NAPPGUI_INCLUDE_PATH}")
    

No obstante, existe la posibilidad que de sea el programador el encargado de cargar, descargar y acceder a los símbolos de las DLLs en cualquier momento. Esto se conoce como enlace en tiempo de ejecución o enlace sin importación de símbolos. En (Listado 1) tenemos una nueva versión de Dice:

Listado 1: Carga de símbolos en tiempo de ejecución.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef void(*FPtr_ddraw)(DCtx*, const real32_t, const real32_t, const real32_t, const real32_t, const real32_t, const real32_t, const real32_t, const uint32_t);

static void i_OnRedraw(App *app, Event *e)
{
    const EvDraw *params = event_params(e, EvDraw);
    DLib *casino = dlib_open(NULL, "casino");
    FPtr_ddraw func_draw = dlib_proc(casino, "die_draw", FPtr_ddraw);
    color_t green = color_rgb(102, 153, 26);
    real32_t w = params->width / 3;
    real32_t h = params->height / 2;
    real32_t p = *dlib_var(casino, "kDEF_PADDING", real32_t);
    real32_t c = *dlib_var(casino, "kDEF_CORNER", real32_t);
    real32_t r = *dlib_var(casino, "kDEF_RADIUS", real32_t);
    draw_clear(params->ctx, green);
    func_draw(params->ctx, 0.f, 0.f, w, h, p, c, r, app->face[0]);
    func_draw(params->ctx, w, 0.f, w, h, p, c, r, app->face[1]);
    func_draw(params->ctx, 2 * w, 0.f, w, h, p, c, r, app->face[2]);
    func_draw(params->ctx, 0.f, h, w, h, p, c, r, app->face[3]);
    func_draw(params->ctx, w, h, w, h, p, c, r, app->face[4]);
    func_draw(params->ctx, 2 * w, h, w, h, p, c, r, app->face[5]);
    dlib_close(&casino);
}
  • La línea 6 carga la librería casino.
  • La línea 7 accede a la función die_draw definida en casino.
  • Las líneas 11-13 acceden a variables públicas de casino.
  • Las líneas 15-20 utilizan die_draw a través del puntero func_draw.
  • La línea 21 descarga la librería casino de memoria.

Como vemos, esta carga en tiempo de ejecución sí que implica cambios en el código fuente, pero también trae consigo ciertas ventajas:

  • La librería se carga en el momento que la necesitamos, no al inicio del programa. Por esto es muy importante que casino no aparezca como dependencia de Dice.
  •  
    
    nap_desktop_app(Dice "" NRC_NONE)
    
  • Podemos tener diferentes versiones de casino y elegir cual utilizar en tiempo de ejecución. Este es el mecanismo de funcionamiento de los plug-ins utilizados por muchas aplicaciones. Por ejemplo, el programa Rhinoceros 3D enriquece su funcionalidad gracias a nuevos comandos implementados por terceros y añadidos en cualquier momento mediante un sistema de plugins (.DLLs) (Figura 11).
  • Captura del listado de plug-ins del programa Rhinoceros 3D.
    Figura 11: Sistema de plug-ins de Rhinoceros 3D, implementado mediante DLLs.

2.5. Ubicación de DLLs

Cuando el sistema operativo debe cargar una librería dinámica, sigue cierto orden de búsqueda. En sistemas Windows busca en este orden:

  • El mismo directorio que el ejecutable.
  • El directorio de trabajo actual.
  • El directorio %SystemRoot%\System32.
  • El directorio %SystemRoot%.
  • Los directorios especificados en la variable de entorno PATH.

Por otro lado, en Linux y macOS:

  • Los directorios especificados en la variable de entorno LD_LIBRARY_PATH (Linux) o DYLD_LIBRARY_PATH (macOS).
  • Los directorios especificados en el ejecutable rpath.
  • Los directorios del sistema /lib, /usr/lib, etc.

Aquí tenemos una gran diferencia entre Windows y Unix, ya que en estos últimos es posible añadir dentro del ejecutable directorios de búsqueda de dependencias. Esta variable se conoce como RPATH y no está disponible en Windows. Para consultar el valor del RPATH:

 
// In Linux
~/$ readelf -d ./Die | grep RUNPATH
 0x000000000000001d (RUNPATH)            Library runpath: [${ORIGIN}]

 // In macOS
otool -l ./Die.app/Contents/MacOS/Die
...
Load command 25
          cmd LC_RPATH
      cmdsize 40
         path @executable_path/../../.. (offset 12)
...
Los ejecutables generados por el comando nap_desktop_app() establecen automáticamente el RPATH para encontrar las dependencias dinámicas en el mismo directorio que los ejecutables en Linux o los bundles en macOS.

3. Símbolos y visibilidad

En el proceso de enlace tras la compilación de la librería, se denomina símbolo a aquellos elementos que pueden generar código máquina u ocupar espacio en el binario final. Estos son métodos, funciones y variables globales. No se consideran símbolos:

  • Las definiciones de tipos como enum, struct o union. Estos ayudan al programador a organizar el código y al compilador a validarlo, pero no generan código binario alguno. No existen desde el punto de vista del enlazador.
  • Las variables locales. Estas se crean y se destruyen automáticamente en el Segmento Stack durante la ejecución del programa. No existen en tiempo de enlace.

Por otro lado, todas las funciones y variables globales declaradas como static dentro de un módulo *.c serán considerados símbolos privados no visibles en tiempo de enlace y donde el compilador es libre de realizar las optimizaciones oportunas. Con esto en mente, el código dentro de NAppGUI se organiza de la siguiente forma:

  • *.c: Fichero de implementación. Definición de símbolos (funciones y variables globales).
  • *.h: Fichero de cabecera pública. Declaración de funciones y variables globales (extern), disponibles para el usuario de la librería.
  • *.hxx: Declaración de tipos públicos: struct, union y enum.
  • *.inl: Declaración de funciones y variables privadas. Solo los módulos internos de la librería tendrán acceso a estos símbolos.
  • *.ixx: Declaración de tipos privados. Aquellos compartidos entre los módulos de la librería, pero no con el exterior.
Si una función solo es necesaria dentro de un módulo *.c, no se incluye en un *.inl. Se marcará como static dentro del mismo *.c. De esta forma no estará visible para el enlazador y se permitirá al compilador realizar optimizaciones.
De igual forma, tipos que solo se utilicen dentro de un módulo concreto, se declararán al inicio del *.c y no en el *.ixx.
En pro de la mantenibilidad y escalibidad del código, se mantendrán las declaraciones de tipos y funciones lo más privado posible.

3.1. Exportación en DLLs

Cuando generamos una librería de enlace dinámico, además de incluir los símbolos públicos en una o varias cabeceras *.h deberemos marcarlos explícitamente como exportables. La macro de exportación se declara en el archivo *.def de cada librería. Por ejemplo en casino.def, se define la macro _casino_api.

Listado 2: casino.def
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* casino library import/export */

/* clang-format off */

#if defined(NAPPGUI_SHARED)
    #if defined(NAPPGUI_BUILD_CASINO_LIB)
        #define NAPPGUI_CASINO_EXPORT_DLL
    #else
        #define NAPPGUI_CASINO_IMPORT_DLL
    #endif
#endif

#if defined(__GNUC__)
    #if defined(NAPPGUI_CASINO_EXPORT_DLL)
        #define _casino_api __attribute__((visibility("default")))
    #else
        #define _casino_api
    #endif
#elif defined(_MSC_VER)
    #if defined(NAPPGUI_CASINO_IMPORT_DLL)
        #define _casino_api __declspec(dllimport)
    #elif defined(NAPPGUI_CASINO_EXPORT_DLL)
        #define _casino_api __declspec(dllexport)
    #else
        #define _casino_api
    #endif
#else
    #error Unknown compiler
#endif

/* clang-format on */

Esta macro deberá anteponerse a todas las funciones y variables públicas declaradas en los *.h de la librería. Los proyectos basados nap_desktop_app() definirán las macros NAPPGUI_XXXXX_EXPORT_DLL cuando se compile la DLL y NAPPGUI_XXXXX_IMPORT_DLL cuando se utilice la DLL en otros targets. De esta forma, la exportación e importación de símbolos se realizará correctamente en todas las plataformas.

3.2. Comprobación en DLLs

Podemos ver, a partir del binario de una librería dinámica, que símbolos públicos exporta. En Windows utilizaremos dumpbin /exports dllname, en Linux nm -D soname y en macOS nm -gU dylibname.

Símbolos públicos de core.dll (Windows).
 
C:\>dumpbin /exports core.dll
2    1 00001000 array_all
3    2 00001010 array_bsearch
4    3 00001090 array_bsearch_ptr
5    4 00001120 array_clear
6    5 000011C0 array_clear_ptr
7    6 00001260 array_copy
8    7 00001340 array_copy_ptr
9    8 00001420 array_create
10    9 00001430 array_delete
11    A 00001530 array_delete_ptr
12    B 00001640 array_destopt
13    C 00001650 array_destopt_ptr
14    D 00001660 array_destroy
15    E 000016F0 array_destroy_ptr
16    F 00001790 array_esize
17   10 000017A0 array_find_ptr
18   11 000017D0 array_get
...
Símbolos públicos de libcore.so (Linux).
 
$ nm -D ./libcore.so
0000000000011f85 T array_all
000000000001305c T array_bsearch
000000000001316d T array_bsearch_ptr
0000000000011832 T array_clear
00000000000118a1 T array_clear_ptr
0000000000011009 T array_copy
000000000001115d T array_copy_ptr
0000000000010fdd T array_create
0000000000012649 T array_delete
000000000001276b T array_delete_ptr
0000000000011668 T array_destopt
0000000000011746 T array_destopt_ptr
00000000000115c3 T array_destroy
00000000000116ad T array_destroy_ptr
0000000000011b87 T array_esize
0000000000012dd3 T array_find_ptr
0000000000011e8c T array_get
Símbolos públicos de libcore.dylib (macOS).
 
% nm -gU ./libcore.dylib
00000000000029f0 T _array_all
0000000000003c90 T _array_bsearch
0000000000003d60 T _array_bsearch_ptr
00000000000024c0 T _array_clear
00000000000025d0 T _array_clear_ptr
0000000000001c20 T _array_copy
0000000000001dd0 T _array_copy_ptr
0000000000001b50 T _array_create
00000000000030f0 T _array_delete
0000000000003350 T _array_delete_ptr
00000000000022f0 T _array_destopt
0000000000002470 T _array_destopt_ptr
0000000000002120 T _array_destroy
0000000000002340 T _array_destroy_ptr
00000000000028b0 T _array_esize
0000000000003980 T _array_find_ptr
00000000000028f0 T _array_get
❮ Anterior
Siguiente ❯