Construyendo una biblioteca compartida usando gcc en Linux y MinGW en Windows

6 minutos de lectura

Tengo problemas para generar una configuración de compilación que permita compilar bibliotecas compartidas tanto en Linux como en Windows usando gcc y MinGW, respectivamente. En Linux, una biblioteca compartida no tiene que resolver todas las dependencias en tiempo de compilación; mientras que, esto parece ser el caso en Windows. Aquí está la configuración del problema:


$ cat foo.h 
#ifndef FOO_H
#define FOO_H
void printme();
#endif

$ cat foo.c
#include "foo.h"
#include <stdio.h>
void printme() {
    printf("Hello World!\n");
}

$ cat bar.h
#ifndef BAR_H
#define BAR_H
void printme2();
#endif

$ cat bar.c
#include "bar.h"
#include "foo.h"
void printme2() {
    printme();
    printme();
}

$ cat main.c
#include "bar.h"
int main(){
    printme2();
}

$ cat Makefile 
.c.o:
        gcc -fPIC -c $<

all: foo.o bar.o main.o
        gcc -shared foo.o -o libfoo.so
        gcc -shared bar.o -o libbar.so
        gcc main.o  -Wl,-rpath=. -L . -lbar -lfoo -o main

Ahora, en Linux, esto compila y funciona bien:

$ make
gcc -fPIC -c foo.c
gcc -fPIC -c bar.c
gcc -fPIC -c main.c
gcc -shared foo.o -o libfoo.so
gcc -shared bar.o -o libbar.so
gcc main.o  -Wl,-rpath=. -L . -lbar -lfoo -o main

$ ./main 
Hello World!
Hello World!

En Windows, necesitamos cambiarlo a dll, que es menor y está bien:

$ cat Makefile 
.c.o:
        gcc -fPIC -c $<

all: foo.o bar.o main.o
        gcc -shared foo.o -o libfoo.dll
        gcc -shared bar.o -o libbar.dll
        gcc main.o  -Wl,-rpath=. -L . -lbar -lfoo -o main

Sin embargo, cuando intentamos compilar, obtenemos el siguiente error:

$ make
gcc -fPIC -c foo.c
foo.c:1:0: warning: -fPIC ignored for target (all code is position independent) [enabled by default]
gcc -fPIC -c bar.c
bar.c:1:0: warning: -fPIC ignored for target (all code is position independent) [enabled by default]
gcc -fPIC -c main.c
main.c:1:0: warning: -fPIC ignored for target (all code is position independent) [enabled by default]
gcc -shared foo.o -o libfoo.dll
gcc -shared bar.o -o libbar.dll
bar.o:bar.c:(.text+0x7): undefined reference to `printme'
bar.o:bar.c:(.text+0xc): undefined reference to `printme'
collect2.exe: error: ld returned 1 exit status
make: *** [all] Error 1

Ahora, podemos corregir el error simplemente incluyendo los objetos de foo.o en libbar.dll:

$ cat Makefile 
.c.o:
        gcc -fPIC -c $<

all: foo.o bar.o main.o
        gcc -shared foo.o -o libfoo.dll
        gcc -shared bar.o foo.o -o libbar.dll
        gcc main.o  -Wl,-rpath=. -L . -lbar -lfoo -o main

$ make
gcc -fPIC -c foo.c
foo.c:1:0: warning: -fPIC ignored for target (all code is position independent) [enabled by default]
gcc -fPIC -c bar.c
bar.c:1:0: warning: -fPIC ignored for target (all code is position independent) [enabled by default]
gcc -fPIC -c main.c
main.c:1:0: warning: -fPIC ignored for target (all code is position independent) [enabled by default]
gcc -shared foo.o -o libfoo.dll
gcc -shared bar.o foo.o -o libbar.dll
gcc main.o -Wl,-rpath=. -L . -lbar -lfoo -o main 

$ ./main 
Hello World!
Hello World!

Sin embargo, no me gusta este enfoque ya que libbar.dll ahora contiene símbolos tanto para foo como para bar. En Linux, solo contiene símbolos para barra. Esta separación es importante para situaciones en las que una biblioteca depende de alguna biblioteca numérica estándar como BLAS. Me gustaría poder implementar la biblioteca compartida y hacer que dependa de la versión optimizada de la biblioteca numérica en la máquina del usuario y no en la mía.

En cualquier caso, ¿cuál es el procedimiento adecuado para crear una biblioteca compartida en la que no todos los símbolos estén presentes en el momento de la compilación?

En caso de que importe, compilé estos ejemplos con gcc 4.6.3 en Linux y mingw-get-inst-20120426.exe con gcc 4.7.2 en Windows.

  • Te falta lo requerido __declspec(dllimport) y __declspec(dllexport) en ambos foo.h y bar.h. Algo como: #if defined __ELF__ #define API __attribute((visibility("default"))) #elif defined EXPORT #define API __declspec(dllexport) #else #define API __declspec(dllimport) #endif Luego #define EXPORT en foo.c y bar.c.

    – bit2shift

    13 de septiembre de 2017 a las 2:58

  • Algo como esta pero sin extern "C"que es una construcción de C++.

    – bit2shift

    13 de septiembre de 2017 a las 3:01

En Windows, debe crear un importar biblioteca para la DLL. Una biblioteca de importación se parece a una biblioteca estática, ya que define todos los símbolos necesarios, pero no tiene las implementaciones de funciones reales, solo tiene stubs. La biblioteca de importación resolverá los errores de “referencia indefinida” y evitará los enlaces estáticos.

Para crear una biblioteca de importación con MinGW, siga las instrucciones aquí. La clave es que al construir la DLL, debes pasar la opción -Wl,--out-implib,libexample_dll.a al enlazador para generar la biblioteca de importación libexample_dll.a.

Luego, cuando compilas tu ejecutable principal, usas el -lexample_dll opción (junto con -L.) para enlazar con la biblioteca de importación. Así que con tu código, creo que esto debería funcionar:

all: foo.o bar.o main.o
    gcc -shared foo.o -o libfoo.dll -Wl,--out-implib,libfoo.a
    gcc -shared bar.o foo.o -o libbar.dll -Wl,--out-implib,libbar.a
    gcc main.o  -Wl,-rpath=. -L. -lbar -lfoo -o main

Además, tenga en cuenta que en Windows, la convención de llamadas para funciones exportadas en DLL casi siempre es __stdcallno el predeterminado __cdeclpor lo que si desea que otro software pueda utilizar sus archivos DLL, le recomiendo que los haga __cdecl. Pero eso no es estrictamente necesario, siempre que tanto el código de la DLL como los archivos de encabezado coincidan en cuál es la convención de llamada.

  • Esto funcionó muy bien. Como comentario menor, usé: “gcc -shared bar.o -L . -lfoo -o libbar.dll -Wl,–out-implib,libbar.a” para usar la biblioteca de importación.

    – wyer33

    11/07/2013 a las 20:10


  • Te equivocaste en la parte de la convención de llamadas. La convención de la API de Win32 es __stdcall, pero esta no es la convención más utilizada, __cdecl es. Debe usar una convención de llamada vacía (también conocida como implícita __cdecl) Emparejado con extern "C" para obtener los mejores resultados: sin manipulación y sin necesidad de archivos .def (que deberían considerarse obsoletos en estos días a menos que esté haciendo cosas desagradables).

    – bit2shift

    13 de septiembre de 2017 a las 2:43

  • __stdcall se utiliza principalmente de forma explícita en el código de usuario cuando se definen devoluciones de llamada para el manejo de mensajes en el subsistema de ventana Win32, utilizando el CALLBACK macro.

    – bit2shift

    13 de septiembre de 2017 a las 2:47


  • Con MinGW, se eliminó la necesidad de importar bibliotecas. Puede vincular un ejecutable directamente a una DLL compilada con MinGW.

    – bit2shift

    13 de septiembre de 2017 a las 2:52

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad