Время на прочтение2 мин
Количество просмотров41K
Введение
CMake (от англ. cross platform make) — это кроссплатформенная система автоматизации сборки программного обеспечения из исходного кода.
CMake не занимается непосредственно сборкой, a лишь генерирует файлы управления сборкой из файлов CMakeLists.txt.
Динамические библиотеки. Теория
Создание динамических библиотек со статической линковкой в ОС Windows отличается от ОС GNU/Linux.
На ОС Windows для этого требуется связка .dll (dynamic link library) + .lib (library) файлов.
На ОС GNU/Linux для этого нужен всего лишь один .so (shared object) файл.
Динамические библиотеки. Практика
На практике хочется писать удобный, одинаковый код на обеих ОС.
В каждом проекте (или на несколько проектов одна) присутствовала условная компиляция:
#ifndef __linux__
#if defined( <target_name>_EXPORTS )
#define DLL_<target_name>_EXPORT __declspec(dllexport)
#else // !BUILDING_DLL
#define DLL_<target_name>_EXPORT __declspec(dllimport)
#endif // BUILDING_DLL
#else
#define DLL_<target_name>_EXPORT
#endif // __linux__
Соответственно, для каждого экспортируемого класса из библиотеки необходимо прописать данный макрос:
class DLL_<target_name>_EXPORT <class_name>
В данном случае, на ОС Windows экспортируются все классы/методы, которые помечены данным макросом, а на ОС GNU/Linux, по умолчанию, всё экспортируется, т.к. нет макроса для скрытия классов/методов.
С выходом CMake версии 3.4.0, стало возможным создание библиотек с классами, которые экспортируются по умолчанию. Для этого в каждой цели (target), которые объявлены как SHARED (динамическая библиотека), необходимо включить свойство:
set ( CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
Пример небольшой библиотеки:
# Проверка версии CMake
cmake_minimum_required( VERSION 3.4.0 )
# Если версия установленой программы ниже, то ошибка выполнения
# Название проекта и проверка доступности компиляторя с++
project( shared_lib CXX )
# Установка переменной со списком исходников
set( SOURCE_LIB example.cpp )
# Включение экспорта всех символов для создания динамической библиотеки
set ( CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON )
# Создание динамической библиотеки с именем example
add_library( example SHARED ${SOURCE_LIB} )
И убрать определение и использование макросов из кода:
DLL_<target_name>_EXPORT
Данное свойство автоматически создает module definition (.def) со всеми глобальными символами из .obj файла для динамической библиотеки на ОС Windows.
Далее данный файл (.def) передается компоновщику для создания .lib файла. На выходе на ОС Windows получается связка .lib + .dll
Итоги
Код стал более читабельным, нет лишних строчек. И вероятность появления ошибки во время неправильного написания блока условной компиляции и определения макроса среди разработчиков сведена к нулю. В CMakeLists файле всего одна дополнительная строчка.
CMake 3.4 will have a new feature to simplify porting C and C++ software using shared libraries from Linux/UNIX to Windows. Linux/UNIX developers are often surprised to learn that creating a shared library on Windows known as a DLL (dynamic linked library) requires changes to the source code or an explicit listing of all the symbols that the dll will export. The compilers on Linux/UNIX have the ability to export all symbols in a shared library automatically. On Windows, you must either use compiler directives __declspec(import) and __declspec(export) to declare which symbols are exported/imported from a shared library, or you must create a module definition text file (.def) with a list of all the symbols you want to export and pass that file to the linker.
With C libraries, creating a .def file by hand or automatically is not very difficult since you just have to list the names of all the functions in the library. However, with C++ code, name mangling and the sheer number of functions make crafting a .def file by hand nearly impossible. The standard workaround uses the preprocessor to conditionally insert __declspec(import) and __declspec(export) into the code. However, with a large existing C++ code base, it can be difficult and time consuming to edit all of the source code. CMake now has a feature which allows it to query .obj files that will make up a DLL and create a .def file automatically, in most cases without needing to modify the original source code.
The feature is implemented in CMake via a new target property, WINDOWS_EXPORT_ALL_SYMBOLS. When enabled, this property causes CMake to automatically create a .def file with all symbols found in the input .obj files for a SHARED library on Windows. The .def file will be passed to the linker causing all symbols to be exported from the DLL. For global data symbols, __declspec(dllimport) must still be used when compiling against the code in the DLL. The symbol is exported from the DLL correctly and automatically, but the compiler needs to know that it is being imported from a DLL at compile time. All other function symbols will be automatically exported and imported by callers. This simplifies porting projects to Windows by reducing the need for explicit dllexport markup, even in C++ classes. This property is initialized by the value of the CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS variable when a target is created.
To try this out on an existing project, run
cmake -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE -DBUILD_SHARED_LIBS=TRUE
on a CMake project. This should turn all add_library calls that do not explicitly specify build type into shared builds. If there are no global data variables in the project, all libraries will be built as DLLs with no errors. If you run into undefined symbols, check for global data like static data members of classes. The easiest way to handle the global data is to use the CMake GenerateExportHeader module like this:
add_library(mylibrary ${mylibrary_SOURCES}) # add these lines include(GenerateExportHeader) generate_export_header(mylibrary)
// edit the source like this:
#include <mylibary_export.h> class myclass { static mylibrary_EXPORT int GlobalCounter; …
Note, if you use GenerateExportHeader and still want static builds to work, you will need to add a -Dmylibrary_STATIC during static builds. See the generated mylibary_export.h for more details.
Real World Use Cases
After adding this feature to CMake, I tried it on two relatively large software projects VXL ( (http://vxl.sourceforge.net/) and ITK (www.itk.org). Although, it did take some time, it was much easier than adding dll markup to every source file. In the end the changes required fell into the following repeated issues and fixes:
-
Handling class static data members
-
Add dll import/export macros to the static data.
-
Eliminate the static data –
-
This can be done by making a static getter function with a local static variable.
-
-
-
Static const members
-
These are not exported by the new feature. In some cases to support older compilers, these are put in .cxx files and compiled into the library and are not just inlined in the .h files. If you have this case, you will need to use dllimport and dllexport markup and not just dllimport mark up as the auto export will not work.
-
-
Inherit from std::string or other templated classes with dllimport/dllexport mark up.
class mylibrary_EXPORT myclass public std::string { ..
-
This can cause duplicate symbol errors in the parent templated class to show up in some cases. The fix for this is to remove the markup and let the auto-export do its thing.
-
-
Not having fully linked libraries. On many Unix systems the linker will allow for shared libraries to have unresolved symbols at create time, that are expected to be resolved at the link time of the executable. Windows dlls must resolve all symbols, so your library must explicitly link to everything it depends on. One way to find out where symbols exist in a big system if you get an undefined symbol at link to time is to look for the symbol in the .def files that are created. From a bash (cygwin or git) shell you could run something like this:
find . -name "*.def" | xargs grep symbol
-
Static data members of templated classes. It is best to just avoid these, or only use explicit instantiation or specialization for these. I was not able to get these to work at all.
Thanks
I would like to give a special thanks to Valeri Fine (code author) and Bertrand Bellenot and the rest of the ROOT team at CERN for contributing the .obj file symbols parsing code, and for testing the feature on their project. Enjoy!
Links
Nightly CMake with new feature: http://www.cmake.org/files/dev/cmake-3.3.20150721-g9cd2f-win32-x86.exe
Nightly Docs for feature:
http://www.cmake.org/cmake/help/git-master/prop_tgt/WINDOWS_EXPORT_ALL_SYMBOLS.html
ITK changes for this feature: http://review.source.kitware.com/#/c/20020
VXL changes for this feature: https://github.com/vxl/vxl/pull/4
Valeri Fine presented this approach and introduced the original code at Computing in High Energy Physics Berlin, Lichtenberger Congress Center April 7-11, 2977:
http://www.ifh.de/CHEP97/abstract/g340.htm
http://www.ifh.de/CHEP97/paper/340.ps.
Original address: blog.kitware.com/create-dlls…
The original author: blog.kitware.com/author/bill…
Release time: July 24, 2015
CMake 3.4 will have a new feature to simplify porting C and C++ software from Linux/UNIX to Windows using shared libraries. Linux/UNIX developers are often surprised to find that creating a shared library called a DLL (dynamic link library) on Windows requires modifying the source code or explicitly listing all symbols that the DLL will export. Compilers on Linux/UNIX have the ability to automatically export all symbols from shared libraries. On Windows, you must use the compiler directives __declspec(import) and __declspec(export) to declare which symbols will be exported/imported from the shared library, or you must create a module definition text file (.def) that contains a list of all symbols you want to export, And pass the file to the linker.
With the C library, it is not difficult to create a. Def file manually or automatically, because you only need to list the names of all the functions in the library. However, with C++ code, it is almost impossible to make a. Def file by hand because of the confusion of names and the number of functions. The standard workaround is to use the preprocessor to conditionally insert __declspec(import) and __declspec(export) into the code. However, with a large existing C++ code base, editing all the source code can be difficult and time-consuming. CMake now has a feature that allows it to query the.obj files that make up DLLS and automatically create a.def file, in most cases without modifying the original source code.
This is done in CMake with a new target property WINDOWS_EXPORT_ALL_SYMBOLS. When enabled, this property causes CMake to automatically create a.def file that contains all symbols found in the input.obj file of the SHARED library on Windows. The.def file will be passed to the linker, causing all symbols to be exported from the DLL. For global data symbols, __declSpec (DLlimPort) must still be used when compiling against code in DLLS. The symbol is automatically exported from the DLL correctly, but the compiler needs to know that it was imported from the DLL at compile time. All other function symbols are automatically exported and imported by the caller. This simplifies porting projects to Windows and reduces the need for explicit dllexport tags, even in C++ classes. When creating a target, this property is initialized by the value of the CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS variable.
To try this feature in an existing project, run the following command
cmake -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE -DBUILD_SHARED_LIBS=TRUE
Copy the code
On the CMake project. This will make all add_library calls without an explicitly specified build type a shared build. If there are no global data variables in the project, all libraries are built as DLLs with no errors. If you encounter undefined symbols, examine global data, such as static data members of a class. The easiest way to process global data is to use the CMake GenerateExportHeader module, like this.
add_library(mylibrary ${mylibrary_SOURCES})
# add these lines
include(GenerateExportHeader)
generate_export_header(mylibrary)
Copy the code
Edit the source code like this.
#include <mylibary_export.h>
class myclass
{
static mylibrary_EXPORT intGlobalCounter; ...Copy the code
Note that if you use GenerateExportHeader and still want static builds to work, you need to add -dmyLIBRARY_static to the static build process. See the generated mylibary_export.h for more details.
Practical application cases
After adding this functionality to CMake, I tried it out on two of the larger software projects VXL (vxl.sourceforge.net/) and ITK (www.itk.org). Although it did take some time, it was much easier than adding DLL tags to every source file. In the end, the changes required are among the following recurring problems and fixes.
- Handles class static data members
- Add DLL import/export macros to static data.
- Eliminate static data –
- This can be done by doing a static getter function with a local static variable.
- Static members
- None of this can be exported by the new feature. In some cases, to support older compilers, these are placed in.cxx files and compiled into libraries, rather than just inline in.h files. If you have this situation, you need to use the DLlimPort and DLlexport tags, not just the DLlimPort tag, because automatic export will not work.
- Inherit the DLlimPort/DLlexPort tag from STD :: String or another template class.
- This can lead to repeated symbol errors in the parent template class in some cases. The solution to this problem is to remove the tag and let the automatic export do its job.
class mylibrary_EXPORT myclass public std::string
{
..
Copy the code
- There are no fully linked libraries. On many Unix systems, the linker allows shared libraries to be created with unparsed symbols that should be parsed when linking executable files. Windows DLLS must parse all symbols, so your library must explicitly link to everything it depends on. If you get an undefined symbol while linking, one way is to look for the symbol’s presence in the larger system in the.def file you created. From a bash (Cygwin or Git) shell, you can run such programs.
find . -name "*.def" | xargs grep symbol
Copy the code
- Static data member of a templated class. It is best to avoid these directly, or to use explicit instantiation or specialization only for these. There’s no way I can make this work.
Thank you
I would especially like to thank Valeri Fine and Bertrand Bellenot and the rest of the ROOT team at CERN for contributing.obj file symbol parsing code and testing it on their projects. Please appreciate
link
Night CMake new features: www.cmake.org/files/dev/c…
Evening papers are special topics.
www.cmake.org/cmake/help/…
The function of the ITK changes: review.source.kitware.com/#/c/20020
VXL changes to this feature: github.com/vxl/vxl/pul…
Valeri Fine presented the method and presented the original code at the Lichtenberger Conference, the Center for Computing in High Energy Physics, Berlin, 7-11 April 2977.
www.ifh.de/CHEP97/abst…
www.ifh.de/CHEP97/pape…
Translation via www.DeepL.com/Translator (free version)
Issue
According to my experience, though it may be incorrect, on Windows if no translation unit exports any symbol then all symbols are implicitly exported when building a DLL with GNU C++ compiler. In contrast, if there is any translation unit exports any symbol, then all others symbols in the DLL are NOT implicitly exported.
In other words, if there is a header we #include
declares a symbol with __declspec(dllexport)
, then we have to manually insert __declspec(dllexport)
to every single declarations of other symbols. Otherwise, those symbols will not get exported.
Is there a convenient way in CMake world that we don’t have to add __declspec(dllexport)
ourselves? I’ve tried
set_target_properties(foo PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
but it doesn’t work. Any suggestion? Thanks.
Reedit: (2021-09-03 8:34:40 UTC)
Below is an example to recreate the problem.
example/CMakeLists.txt:
cmake_minimum_required(VERSION 3.21)
project(example VERSION 1.0)
add_executable(myapp main.cpp)
add_subdirectory(foo)
add_subdirectory(bar)
target_link_libraries(myapp PRIVATE foo)
example/main.cpp:
#include "foo.hpp" // for function foo_func
int main(int argc, char *argv[])
{
foo_func();
return 0;
}
example/foo/CMakeLists.txt:
add_library(foo SHARED foo.cpp)
target_include_directories(foo INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(foo PRIVATE bar)
example/foo/foo.hpp:
#ifndef FOO_HPP
#define FOO_HPP
void foo_func(void);
#endif
example/foo/foo.cpp:
#include "bar.hpp" // for function bar_funcA
void foo_func(void)
{
bar_funcA();
}
example/bar/CMakeLists.txt
add_library(bar SHARED bar.cpp)
target_include_directories(bar INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
set_target_properties(bar PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
example/bar/bar.hpp:
#ifndef BAR_HPP
#define BAR_HPP
void bar_funcA(void);
void bar_funcB(void);
#endif
example/bar/bar.cpp:
#include <cstdio> // for function printf
void bar_funcA(void)
{
std::printf("%s\n", __func__);
}
__declspec(dllexport) void bar_funcB(void)
{
std::printf("%s\n", __func__);
}
My environment:
Windows 10
MSYS2
CMake 3.21.2
Result:
$ cmake -G 'Unix Makefiles' -S example/ -B build/
$ cmake --build build/
[ 16%] Building CXX object bar/CMakeFiles/bar.dir/bar.cpp.obj
[ 33%] Linking CXX shared library libbar.dll
[ 33%] Built target bar
Consolidate compiler generated dependencies of target foo
[ 50%] Building CXX object foo/CMakeFiles/foo.dir/foo.cpp.obj
[ 66%] Linking CXX shared library libfoo.dll
C:/msys64/mingw64/x86_64-w64-mingw32/bin/ld.exe: CMakeFiles/foo.dir/objects.a(foo.cpp.obj):foo.cpp:(.text+0x9): undefined reference to `bar_funcA()'
collect2.exe: error: ld returned 1 exit status
make[2]: *** [foo/CMakeFiles/foo.dir/build.make:102: foo/libfoo.dll] Error 1
make[1]: *** [CMakeFiles/Makefile2:144: foo/CMakeFiles/foo.dir/all] Error 2
make: *** [Makefile:91: all] Error 2
According to Dependency Walker, the only symbol that gets exported from libbar.dll is bar_funcB
.
Solution
According to WIN32 (LD), linker option --export-all-symbols
turns off the auto-export functionality and exports all symbols, except some special ones. Therefore, to also export bar_funcA
without modifying C/C++ codes, add this line in src/bar/CMakeLists.txt:
target_link_options(bar PRIVATE "-Wl,--export-all-symbols")
Answered By — Cody
Answer Checked By — Marie Seifert (WPSolving Admin)
Provide feedback
Saved searches
Use saved searches to filter your results more quickly
Sign up
Appearance settings