Can I export functions of a static library when building a dynamic library linking against that static library?

Francis picture Francis · Dec 13, 2017 · Viewed 9k times · Source

On win32, I built a dynamic library called A.dll which linked against a static library called B.lib, and also built a executable called C.exe which only dependent on A.dll.

But now, in C.exe if I want use a function foo which only has definition in B.lib, I have to link C.exe against B.lib again.

The question is can I export foo from B.lib directly into A.dll when building A.dll, and how?

Also I want to know what will it be when dealing with GCC.

Answer

Mike Kinghan picture Mike Kinghan · Dec 16, 2017

A function foo can be exported from a DLL provided that:-

  • You declare foo with the __declspec(dllexport) attribute when you compile the function into an object file, say foo.obj

  • You link foo.obj into the DLL.

It doesn't matter how foo.obj gets linked into the DLL.

Maybe you specify foo.obj explicitly in the linkage of the DLL.

Maybe you've put foo.obj is inside a static library, say foobar.lib, and the linkage of the DLL contains some reference to the function foo. Then the linker will extract foo.obj from the foobar.lib and link it into the DLL, to resolve that reference.

If foo.obj is linked by being extracted from a static library, the linkage is exactly the same as if foo.obj was linked by name. A static library is simply a bag of object files from which the linker can pick the ones it needs carry on the linkage. When the linkage is done, the program or DLL produced has no dependency on a static library. If it needed any object files in the bag, it has got them now. It doesn't need the bag.

Here's a illustration using the GCC mingw-w64 toolchain for Windows of how a DLL-exported function is linked from within a static library:

C:\mingw-w64\x86_64-7.2.0-posix-seh-rt_v5-rev0>echo off
Microsoft Windows [Version 10.0.15063]
(c) 2017 Microsoft Corporation. All rights reserved.

C:\>cd develop\so\scrap
C:\develop\so\scrap>dir
 Volume in drive C has no label.
 Volume Serial Number is 16C7-F955

 Directory of C:\develop\so\scrap

16/12/2017  17:50    <DIR>          .
16/12/2017  17:50    <DIR>          ..
16/12/2017  12:41               116 greeting.cpp
16/12/2017  12:42                99 greeting.h
16/12/2017  12:10               109 hello.cpp
16/12/2017  12:12                90 hello.h
16/12/2017  16:21               197 main.cpp
16/12/2017  12:25               117 niceday.cpp
16/12/2017  12:26                96 niceday.h
               7 File(s)            824 bytes
               2 Dir(s)  101,878,943,744 bytes free

hello.cpp

#include "hello.h"
#include <iostream>

void hello()
{
    std::cout << "Hello world!" << std::endl;
}

hello.h

#ifndef HELLO_H
#define HELLO_H

extern __declspec(dllexport) void hello();

#endif

We'll compile hello.cpp:

C:\develop\so\scrap>g++ -Wall -c -o hello.obj hello.cpp

Similarly:

niceday.cpp

#include "niceday.h"
#include <iostream>

void niceday()
{
    std::cout << "What a nice day!" << std::endl;
}

niceday.h

#ifndef NICEDAY_H
#define NICEDAY_H

extern __declspec(dllexport) void niceday();

#endif

we'll compile niceday.cpp

C:\develop\so\scrap>g++ -Wall -c -o niceday.obj niceday.cpp

Now we'll put these two object files in a static library. libgreet.lib

ar rcs libgreet.lib hello.obj niceday.obj

Another source file and header:

greeting.cpp

#include "greeting.h"
#include "hello.h"
#include "niceday.h"

void greeting()
{
    hello();
    niceday();
}

greeting.h

#ifndef GREETING_H
#define GREETING_H

extern __declspec(dllexport) void greeting();

#endif

Which we'll also compile:

g++ -Wall -c -o greeting.obj greeting.cpp

Now we'll make a DLL, libgreeting.dll, using greeting.obj and the static library libgreet.lib:

C:\develop\so\scrap>g++ -shared -o libgreeting.dll greeting.obj libgreet.lib

Here we have a program source file:

main.cpp

#include "hello.h"
#include "niceday.h"
#include "greeting.h"
#include <iostream>

int main()
{
    hello();
    niceday();
    std::cout << "I said..." << std::endl;
    greeting();
    return 0;
}

which we'll also compile:

C:\develop\so\scrap>g++ -Wall -c -o  main.obj main.cpp

Finally, we'll make a program by linking our main.obj with libgreeting.dll. Only with libgreeting.dll.

C:\develop\so\scrap>g++ -o prog main.obj libgreeting.dll

Run the program:

C:\develop\so\scrap>prog
Hello world!
What a nice day!
I said...
Hello world!
What a nice day!

All three of the DLL-exported functions, hello, niceday and greeting, are called. All that matters, for this to happen, is that all of them were declared __declspec(dllexport) and all of them were linked into libgreeting.dll somehow. As it happens, two of them (hello, niceday) were linked from a static library and the other (greeting) was linked directly from an object file: this doesn't matter.

And if you're interested...

Here's how you do the same thing with the Visual Studio 2017 toolchain:

**********************************************************************
** Visual Studio 2017 Developer Command Prompt v15.4.3
** Copyright (c) 2017 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'

C:\Users\mikek\source>cd C:\develop\so\scrap

C:\develop\so\scrap>dir
 Volume in drive C has no label.
 Volume Serial Number is 16C7-F955

 Directory of C:\develop\so\scrap

16/12/2017  18:31    <DIR>          .
16/12/2017  18:31    <DIR>          ..
16/12/2017  12:41               116 greeting.cpp
16/12/2017  12:42                99 greeting.h
16/12/2017  12:10               109 hello.cpp
16/12/2017  12:12                90 hello.h
16/12/2017  16:21               197 main.cpp
16/12/2017  12:25               117 niceday.cpp
16/12/2017  12:26                96 niceday.h
               7 File(s)            824 bytes
               2 Dir(s)  101,877,473,280 bytes free

Compile hello.cpp:

C:\develop\so\scrap>cl /W4 /EHsc /c /Fohello.obj hello.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.11.25547 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

hello.cpp

Compile niceday.cpp:

C:\develop\so\scrap>cl /W4 /EHsc /c /Foniceday.obj niceday.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.11.25547 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

niceday.cpp

Make the static library:

C:\develop\so\scrap>lib /out:libgreet.lib hello.obj niceday.obj
Microsoft (R) Library Manager Version 14.11.25547.0
Copyright (C) Microsoft Corporation.  All rights reserved.

Compile greeting.cpp:

C:\develop\so\scrap>cl /W4 /EHsc /c /Fogreeting.obj greeting.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.11.25547 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

greeting.cpp

Link libgreeting.dll:

C:\develop\so\scrap>link /dll /out:libgreeting.dll greeting.obj libgreet.lib
Microsoft (R) Incremental Linker Version 14.11.25547.0
Copyright (C) Microsoft Corporation.  All rights reserved.

   Creating library libgreeting.lib and object libgreeting.exp

Here, as you know, the Microsoft linker creates an import library libgreeting.lib (not to be confused with the static library libgreet.lib) which is used for linking libgreeting.dll to a program or another DLL.

Compile main.cpp:

C:\develop\so\scrap>cl /W4 /EHsc /c /Fomain.obj main.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.11.25547 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

main.cpp

Link our program (using the import library libgreeting.lib in lieu of libgreeting.dll):

C:\develop\so\scrap>link /out:prog.exe main.obj libgreeting.lib
Microsoft (R) Incremental Linker Version 14.11.25547.0
Copyright (C) Microsoft Corporation.  All rights reserved.

And run:

C:\develop\so\scrap>prog
Hello world!
What a nice day!
I said...
Hello world!
What a nice day!

Later

If foo firstly was compiled without __declspec(dllexport) and has a declaration like this void foo() when I built B.lib. But when I built C.exe I changed foo's declaration to __declspec(dllexport) void foo(). The question is can I still make above happen?

In principle, if you compile an object file with a certain declaration of foo in its header file and then change that declaration subsequently to #include the header file in the compilation of some other object file, then you are likely to be lying to the compiler in the second compilation and when you link these object files into some program or DLL you can expect bad things to happen.

In this case however, you can get away with it. In the example above, you may first compile, say, hello.cpp with the declaration in hello.h:

extern void hello(void);

Later, when you compile greeting.cpp, which #includes hello.h, you may change the declaration to:

extern __declspec(dllexport) void hello(void);

with the result that hello will be DLL_exported when you link libgreeting.dll.

The declaration with __declspec(dllexport) refines rather than contradicts the the one without. In linking libgreeting.dll, the linker sees a non-DLL-exported reference to hello in hello.obj and a DLL-exported reference in greeting.obj. It DLL-exports the symbol because it has seen at least one DLL-exported reference.

Be in no doubt that this is a hack.