Crear nueva librería
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. En Uso de librerías ya vimos una primera introducción, que ampliaremos en este capítulo.
1. Librerías estáticas
Para escapar de la introducción tan simplista del capítulo anterior, vamos a utilizar 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.
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. Esto es muy sencillo de realizar gracias a find_package()
y a NAppProject.cmake
. Descarga el ejemplo completo desde este enlace. Descomprímelo e inspecciona sus archivos. La estructura del proyecto es muy similar a lo visto en el capítulo anterior, empezando por el CMakeLists.txt
principal:
1 2 3 4 5 6 7 |
cmake_minimum_required(VERSION 3.0) project(NAppDice) find_package(nappgui REQUIRED) include("${NAPPGUI_ROOT_PATH}/prj/NAppProject.cmake") 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: Busca un target librería en el directorio
casino
. - Línea 6: Busca un target aplicación en el directorio
die
. - Línea 7: Busca un target aplicación en el directorio
dice
.
En /die/CMakeLists.txt
y /dice/CMakeLists.txt
vemos la vinculación con casino
:
|
nap_desktop_app(Die "casino" NRC_EMBEDDED) target_link_libraries(Die ${NAPPGUI_LIBRARIES}) |
|
nap_desktop_app(Dice "casino" NRC_NONE) target_link_libraries(Dice ${NAPPGUI_LIBRARIES}) |
Lo único que, hasta ahora, no hemos visto son las constantes NRC_EMBEDDED
y NRC_NONE
. En Procesamiento de recursos las veremos con detenimiento. Por el momento no te preocupes por ellas. Puedes generar y compilar el proyecto de la forma habitual:
|
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=C:/nappgui cmake --build build --config Debug |
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).
¿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.
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
. También deberás enlazar casino
con NAppGUI-SDK, algo que no es necesario realizar en la versión estática.
|
nap_library(casino "" YES NRC_NONE) target_include_directories(casino PUBLIC "${NAPPGUI_INCLUDE_PATH}") target_link_libraries(casino ${NAPPGUI_LIBRARIES}) |
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.
|
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.
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.
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.
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: - Se ha realizado un
#include "ddraw.h"
, cabecera pública decasino
. - Se han utilizado
die_draw()
,kDEF_PADDING
,kDEF_CORNER
,kDEF_RADIUS
. - La librería dinámica
casino.dll
se cargará de forma automática justamente antes deDice.exe
. - El uso de versión estática o dinámica de
casino
no implica cambios en el código deDice
. Tan solo tendríamos que cambiar el/casino/CMakeLists.txt
y recompilar la solución.
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]); } |
|
# 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}") target_link_libraries(casino ${NAPPGUI_LIBRARIES}) |
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 demo/dice2 tenemos una nueva versión de Dice
:
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 encasino
. - Las líneas 11-13 acceden a variables públicas de
casino
. - Las líneas 15-20 utilizan
die_draw
a través del punterofunc_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 de las que podemos sacar partido.
- 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 deDice2
. - 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).
|
nap_desktop_app(Dice2 "" NRC_NONE) |
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) oDYLD_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 comandonap_desktop_app()
establecen automáticamente elRPATH
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
ounion
. 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
yenum
. - *.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á comostatic
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
.
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 |
/* casino library import/export */ #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 |
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
.
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 ... |
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 |
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 |