9

I have 6 static library projects :-

- Math
- ECS             : depends on Math
- Utility         : depends on ECS
- Physics         : depends on Utility         
- Graphics        : depends on Utility     
- BaseGame        : depends on Physics and Graphics         
- Some game (.exe): depends on BaseGame      
(The "depends" here is transitive e.g. BaseGame also depends on ECS.)    

I succeeded in using 6 projects via "static libraries" technique.

Today, I heard that dynamic library can reduce compilation time (Let's not discuss whether this is true),
so I read these below links and successfully create a small demo.

Here is some code in my test demo :-

#ifdef SomeName1_EXPORTS
#define SomeMacro1 __declspec(dllexport) 
#else
#define SomeMacro1 __declspec(dllimport) 
#endif
SomeMacro1 void someFunction(int someParam);

Now, it is exciting time to apply it to my real projects.

At the first step, I want to export all functions and classes of my 6 libraries.
I assume that I have to add SomeMacro1 (different for each project) to every function in all 6 projects (~100K lines), right?

That is a huge refactoring.
Are there any easier way? Do I miss something very important?

Other notes :-

  • I want to switch my library projects back to static library easily (in case something go wrong).
  • I prefer a cross platform solution. (e.g. no pervasive refactoring need if I want to run in Linux later)
  • I prefer a solution that when I cut-and-paste a source file from one project to another, the cost of refactoring (in code) does not increase from normal.
    (SomeMacro1 is currently specific to a project)

Similar question : How to convert a static library project into a dll project in VS2005

Bounty Reason

Thank Andriy Tylychko's answer that provides useful cautions and suggests that refactoring would be inevitably complicated, but I still believe there are some easy ways to refactor my projects.

Now, I wish to change my library projects to dynamic libraries. (faster compilation)
Then when I ship my product, I will convert them back to static library. (better performance)

Edit: Bounty awards to Robert Andrzejuk because of his link in comment. (https://learn.microsoft.com/en-us/cpp/cpp/using-dllimport-and-dllexport-in-cpp-classes?view=vs-2019)
It may sound simple, but I have never known that I can __declspec(dllexport) at class level.
Although that is not my dream, it makes a lot of things easier.

cppBeginner
  • 1,114
  • 9
  • 27
  • In order to convert static libraries to dynamic libraries you will need to define an appropriate dll interface (not just "export all functions"), there is no way around it. Also it is not clear why would you want to do it on the first place. – user7860670 May 29 '19 at 08:02
  • @VTT I never heard about it. May you provide more detail, please? (e.g. what is "dll interface"?) Thank. – cppBeginner May 29 '19 at 08:03
  • As far as I have found, it is either `__declspec` or manually writing `.def` file with exported function names. `__declspec` still seems to be easier. https://learn.microsoft.com/en-us/cpp/build/exporting-from-a-dll-using-def-files?view=vs-2019 – R2RT Jun 02 '19 at 21:55

4 Answers4

5

Strictly speaking, converting to DLLs will reduce mainly linking time, but you can notice (subtle in most cases) performance degradation because now "whole program optimisation" has much less room for optimisations, e.g. inlining functions across different binaries.

Dynamically linked libraries and statically linked libraries are quite different beasts, DLLs require much more attention. You need to carefully design their public API and define it accordingly (usually by macros like ones you presented). E.g. it's not recommended to use standard library types (and many others) in DLL public API that depend on particular type of C-runtime (like debug and release) - mainly it's about allocating memory in one binary and releasing in another that should be avoided. But this is not always a problem.

It doesn't make much sense to switch back and forth between static and dynamic libs.

Static libraries are usually much simpler and don't care about public API as everything is automatically accessible from outside. E.g. Math libraries are usually static as almost all functionality should be exported anyway and they (usually) don't have any complicated internal "business" logic.

Bigger libraries with "business" logic that you'd like to hide (e.g. to have more freedom to modify it) and with well defined public API bring long-term benefits. E.g. you can update only a small DLL instead of huge monolitic EXE if the API wasn't changed.

As it's hard to carefully plan ahead, the need to convert static lib to DLL (once) happens quite often as the project matures. In this case you don't need to export every class and function but to carefully select what exactly should be exported and ideally refactor existing API to better suit new reality. Luckily it seems you don't have any circular dependencies as they could add a lot of headache.

Refactoring inevitably becomes more complicated across DLLs, but this shouldn't be a big issue. If your lib looks like it should be a DLL - make it a DLL, advantages will be bigger than a disadvantage of a not so easy copy/pasting.

From your example, with very limited knowledge so it's pure guessing, it looks like only Physics and maybe Graphics should be DLLs, probably BaseGame too.

Andriy Tylychko
  • 15,967
  • 6
  • 64
  • 112
  • 1
    (1) "it's about allocating memory in one binary and releasing in another that should be avoided" Why? (2) +1 for Slower execution. I never know about it. Thank! (3) Yes, I eventually won't expose all functions. But I want to taste the dirty result first! (4) Yes, I don't have circular dependency at project level. Thank about distinguishing of {Math} VS {Physics,Graphics,BaseGame} – cppBeginner May 31 '19 at 15:46
  • 1
    (1) e.g. to be able to mix different runtimes, like one DLL built in debug configuration while the rest of the project - in release. Not sure it's important in your case. Probably not. – Andriy Tylychko May 31 '19 at 19:21
  • 2
    @cppBeginner Sometimes the global variables in memory allocators are statically linked and **duplicated**. It means that you can't interoperate. – curiousguy Jun 06 '19 at 05:21
  • @curiousguy I can't use `Physics` as static lib & `Graphics` as dynamic lib, and assume that `Math`'s global variables - which both lib refer to - are the same instance, correct? Now, I can see the danger. Your comments are always useful. Thanks. :) – cppBeginner Jun 06 '19 at 06:20
3

To reduce the refactoring required by marking all the free functions for export/import, an easier way is when the functions are inside a class, and just the class has to be marked for export/import, then all the functions are exported also.

https://learn.microsoft.com/en-us/cpp/cpp/using-dllimport-and-dllexport-in-cpp-classes?view=vs-2019


In Visual Studio to configure a project to differ depending on the target build type I suggest to use "Configurations". By default prepared are:

  • Debug
  • Release

It is possible to create new configurations.

For this example let's consider that

  • Debug version should use a dynamic dll,
  • Release should use a static dll

Too make a project be either static lib or dll (let's call that a "mixed-lib") then in the Properties -> General -> Configuration Type, it is possible to select:

  • Dynamic dll (.dll)
  • Static library (.lib)
  • ...

So for the "Debug" project select a "Dynamic Library".

And for "Release" project select a "Static library".

(Please beware - by changing the project type, not all the settings are appropriatly modifed!! Everything has to be double-checked.)

Depending on the selected configuration, there can be different settings. For example on the preprocessor tab, there are different definitions.

So if Debug is selected, a definition "_DEBUG" is available. In Release "NDEBUG" is available. These definitions can be used inside the code:

#ifdef _DEBUG

#ifdef SomeName1_EXPORTS
#define SomeMacro1 __declspec(dllexport) 
#else
#define SomeMacro1 __declspec(dllimport) 
#endif

#else

#define SomeMacro1

#endif

SomeMacro1 void someFunction(int someParam);

These Confiugrations have to be appropriately configured in both the mixed-dll and the application.

Robert Andrzejuk
  • 5,076
  • 2
  • 22
  • 31
2

You don't have to add the macro to function definitions, just all declarations made in header files which are used by other compilation units. For functions defined in header files, the function doesn't need to be exported as the original source is already available in the header.

That's the easiest and most platform independent way. As mentioned, a .def file can also work on Visual Studio, but you have to list every function name twice, so it's not terribly DRY.

Honestly, 100K lines of code isn't that bad. You may be able to get away with a regex to do most of the work for you.

I see there is a way to use dumpbin to generate a .def file for you. This comment mentions a way to generate the .def file for all function definitions in your .o files.

You only have to add the macro to function declarations, not just definitions. You will also have to decide how you export classes.

As mentioned, as long as you have separate compilation units per project and are not just including all the source into one compilation unit (*.o files are created per compilation unit), the dll will not improve compile-time performance at all.

solinent
  • 1,605
  • 1
  • 17
  • 19
1

Now, I wish to change my library projects to dynamic libraries. (faster compilation) Then when I ship my product, I will convert them back to static library. (better performance)

Dynamic link libraries are different from static libs and don't really provide a benefit for "faster compilation" outside of a potential gain in link time since you're basically going to presumably link smaller modules (n# dlls + 1 exe) as opposed to 1 big exe. If you're planning to statically link the .lib into your exe anyway, then it largely defeats the purpose of converting things into a dll since it's a dll you're not planning to use anyway.

If you wanted to break the project apart so that you could replace individual modules in the project then that's an argument to go to dlls. For example, you want to modify Graphics without having to relink the entire executable, you can wrap "Graphics" into Graphics.dll, make the .exe depend on Graphics.dll and then when you make a change to Graphics, you can just build and replace Graphics.dll instead of the entire executable locally to improve the time it takes to change/test something. This can be a large gain to productivity if the exe takes forever to link.

For Windows specifically:

Given your links, I'm making the following assumptions:

  • You're developing for windows
  • You're using some version of Visual Studio and the Microsoft C compiler.

If you're using the GL or LTCG flag over the exe, then there's a small argument to not build dlls since gl/ltcg apply over a module and having all of your static libs link together will give you better gains with GL/LTCG though take longer to link (not recommended for debug type builds).

If you wanted to wrap libraries into a dll, you can look into building a dll using C++/WinRT which has support in modern versions of visual studio or if for whatever reason you can't start the Windows Runtime, you can look into COM dlls.

Jun
  • 764
  • 5
  • 8
  • "don't really provide a benefit for 'faster compilation' outside of a potential gain in link time" <== It would be very useful if you add some references (aka I don't believe it easily). Thanks. – cppBeginner Jun 07 '19 at 14:16
  • Presumably you would be building less code in the dll because it's a subsystem of your exe and then removing that compile time from your exe (which now loads the dll). If your dll was building all of the code in the exe, then there's no speed up. – Jun Jun 12 '19 at 06:23
  • 1
    also read this: https://stackoverflow.com/questions/140061/when-to-use-dynamic-vs-static-libraries?noredirect=1&lq=1 – Jun Jun 12 '19 at 07:04