ttroy50 / cmake-examples

Useful CMake Examples
http://ttroy50.github.io/cmake-examples
MIT License
12.4k stars 2.5k forks source link

Building D-shared-library on Windows with Visual Studio #76

Open end2endzone opened 2 years ago

end2endzone commented 2 years ago

Building example 01-basic/D-shared-library on Visual Studio fails with the following error:

Build started...
1>------ Build started: Project: ZERO_CHECK, Configuration: Debug x64 ------
1>Checking Build System
2>------ Build started: Project: hello_library, Configuration: Debug x64 ------
2>Building Custom Rule D:/dev/cpp/cmake-examples/01-basic/D-shared-library/CMakeLists.txt
2>Hello.cpp
2>hello_library.vcxproj -> D:\Projets\Programmation\Cpp\cmake-examples\01-basic\D-shared-library\build\Debug\hello_library.dll
3>------ Build started: Project: hello_binary, Configuration: Debug x64 ------
3>Building Custom Rule D:/dev/cpp/cmake-examples/01-basic/D-shared-library/CMakeLists.txt
3>main.cpp
3>LINK : fatal error LNK1104: cannot open file 'Debug\hello_library.lib'
3>Done building project "hello_binary.vcxproj" -- FAILED.
4>------ Skipped Build: Project: ALL_BUILD, Configuration: Debug x64 ------
4>Project not selected to build for this solution configuration 
========== Build: 2 succeeded, 1 failed, 0 up-to-date, 1 skipped ==========

This is because the shared library hello_library does not exports any symbols. If a shared library does not export any symbols, Visual Studio will not create a *.lib file which is required by hello_binary. On Windows, when building a shared library, you need to have __declspec(dllexport) before every functions or classes that you want to export. When another project is using the compiled library, you need to have __declspec(dllimport).

CMake can be solve this problem in two different ways:

1. Use CMake's CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS

CMake 3.4 added the variable CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS. When set to true, it creates a pre-link event in Visual Studio to call cmake.exe which generates an exports.def file that Visual Studio can use to exports most symbols.

To set this variable, you can add set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) to your CMakeLists.txt file or you can set the variable on the command line with -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE.

This solution is not perfect. This flag does not translate to something in Visual Studio solution/project files. It allows you to export most of your code without changes. However, it fails to export global and static variables. You can read more about this in this blog post.

This option was also partially discussed in #52.

2. Generate an export header file

On Windows, this is the usual/expected way for creating a shared library.

If you add the following to CMakeLists.txt, you can generate an export file header:

include (GenerateExportHeader) 
generate_export_header(hello_library)

This will generate the file called ${PROJECT_BINARY_DIR}/hello_library_export.h which will declare the macro HELLO_LIBRARY_EXPORT that resolves to __declspec(dllexport) or __declspec(dllimport) as appropriate. With CMake 3.22, the content will look like this: hello_library_export.h.txt.

All files that export symbols must include this generated header. For example, the file include/shared/Hello.h must be modified as the following:

#include "hello_library_export.h"
class HELLO_LIBRARY_EXPORT Hello

Note the new include directive and the use of the HELLO_LIBRARY_EXPORT macro before the class declaration.

To find this include file, the target hello_library must be modified in CMakeLists.txt with the following:

target_include_directories(hello_library
    PUBLIC 
        ${PROJECT_SOURCE_DIR}/include
        ${PROJECT_BINARY_DIR}  # for hello_library_export.h
)

This is the expected way for creating a shared library. I do not have enough experience on Linux to know if gcc (or other compilers) will be able to understand/ignore __declspec(dllexport) and __declspec(dllimport).


I am really happy to have found this tutorial. This is one of the most well made "step by step" guide I have seen. CMake lacks good and simple documentation and this repository really help. I am a huge fan of learning by examples. I also created a CMake guide myself.

Would you consider adding option 2 to the source code ? To clarify that this code is WINDOWS-ONLY, we could wrap the new code inside statements such as IF (WIN32) in CMakeLists.txt and #ifdef __WIN32 in c++ header files.

I realize this would add more complexity to the example but I would argue that this guide should cover both Linux and Windows.