Building a shared library using gcc on Linux and MinGW on Windows

24,006

On Windows, you need to create an import library for the DLL. An import library looks like a static library, in that it defines all of the needed symbols, but it doesn't have the actual function implementations, it just has stubs. The import library will resolve the "undefined reference" errors while avoiding static linking.

To create an import library with MinGW, follow the instructions here. The key is that when building the DLL, you must pass the option -Wl,--out-implib,libexample_dll.a to the linker to generate the import library libexample_dll.a.

Then, when you compile your main executable, you use the -lexample_dll option (along with -L.) to link against the import library. So with your code, I think this should work:

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

Also, note that on Windows, the calling convention for exported functions in DLL is almost always __stdcall, not the default __cdecl, so if you want your DLLs to be usable by other software, I'd recommend making them __cdecl. But that's not strictly requires, as long as both the code in the DLL and the header files agree on what the calling convention is.

Share:
24,006
wyer33
Author by

wyer33

Updated on July 12, 2020

Comments

  • wyer33
    wyer33 almost 4 years

    I'm having trouble with generating a build setup that allows shared libraries to be built in both Linux and Windows using gcc and MinGW, respectively. In Linux, a shared library doesn't have to resolve all dependencies at compile time; whereas, this appears to the case in Windows. Here is the problem setup:


    $ 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
    

    Now, in Linux, this compiles and runs just fine:

    $ 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!
    

    In Windows, we need to change so to dll, which is minor and fine:

    $ 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
    

    However, when we try to build, we get the following 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
    

    Now, we can fix the error by simply including the objects from foo.o into 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!
    

    However, I don't like this approach since libbar.dll now contains symbols for both foo and bar. In Linux, it only contains symbols for bar. This separation is important for situations where a library depends on some standard numerical library like BLAS. I'd like to be able to deploy the shared library and have it depend on the optimized version of the numerical library on the user's machine and not my own.

    In any case, what's the proper procedure to create a shared library where not all of the symbols are present at compile time?

    In case it matters, I compiled these examples with gcc 4.6.3 on Linux and mingw-get-inst-20120426.exe with gcc 4.7.2 on Windows.

  • wyer33
    wyer33 almost 11 years
    This worked great. As a minor comment, I used: "gcc -shared bar.o -L . -lfoo -o libbar.dll -Wl,--out-implib,libbar.a" in order to use the import library.
  • bit2shift
    bit2shift over 6 years
    You got the calling convention part all wrong. The Win32 API convention is __stdcall, but this isn't the most used convention, __cdecl is. You should use an empty calling convention (aka implicit __cdecl) paired with extern "C" to get the best results: no mangling and no need for .def files (which should be considered obsolete these days unless you're doing nasty stuff).
  • bit2shift
    bit2shift over 6 years
    __stdcall is mainly used explicitly in user code when defining callbacks for message handling in the Win32 window subsystem, using the CALLBACK macro.
  • bit2shift
    bit2shift over 6 years
    With MinGW, the need for import libs has been removed. You can link an executable directly to a DLL that was compiled with MinGW.