Введение


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 файле всего одна дополнительная строчка.

Комментарии (11)


  1. stalkerg
    29.02.2016 15:50
    +1

    Данное свойство автоматически создает module definition (.def) со всеми глобальными символами из .obj файла для динамической библиотеки на ОС Windows.

    О!!! Спасибо! А то мне сейчас приходится юзать перловый скрипт который создаёт этот .def. Но увы пока приходится использовать 4.3 т.к. в 4.4 отломали MSVC2015.


  1. tbd
    29.02.2016 16:44
    +4

    а ещё есть generate_export_header


    1. 19as
      29.02.2016 17:56

      С данным флагом не сталкивался, на досуге почитаю и применю в некоторых проектах


      1. oYASo
        29.02.2016 20:02
        +1

        Этот макрос из разряда "must use" в связке библиотеки и cmake. Он генерирует в папке билда файл с определением макросов экспорта символов, учитывая сборку в статику (оставляет пустыми) и динамику (__declspec(..)), а также макросы deprecated для маркировки старых функций.
        В простейшем случае достаточно написать:

        generate_export_header(mylib)

        и использовать сгенерированный файл

        #include "mylib_export.h"
        class MYLIB_EXPORT MyClass 
        {
        };

        Более тонкая настройка позволяет указать необходимое имя генерируемого файла, макросов и прочего.


  1. GamePad64
    29.02.2016 18:20

    Замечательное свойство, теперь можно удалять этот мусор из заголовков.

    Дополню: чтобы экспортировать только символы из определённого target'a, можно написать
    set_target_properties(таргет1 таргет2 PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)


  1. iroln
    29.02.2016 19:42
    +1

    И вероятность появления ошибки во время неправильного написания блока условной компиляции и определения макроса среди разработчиков сведена к нулю
    Просто надо использовать cmake-функцию generate_export_header, тогда человеческий фактор исключается.
    ```
    include(GenerateExportHeader)

    generate_export_header(MyLib
    EXPORT_FILE_NAME MyLibExport.h
    EXPORT_MACRO_NAME MyLib_EXPORT
    NO_EXPORT_MACRO_NAME MyLib_HIDDEN
    STATIC_DEFINE MyLib_STATIC
    )
    ```

    Не знаю, что такое случилось с тегом source, но код нормально не вставляется


    1. oYASo
      29.02.2016 20:04
      +2

      Не знаю, что такое случилось с тегом source, но код нормально не вставляется

      Каким бы странным это не казалось, но теги и код должны быть на разных строчках.


    1. 19as
      29.02.2016 20:11

      Как говорится дело вкуса, но есть одно но:
      данный метод требует добавление нескольких строчек кода, но и сам код будет выглядит не естественным образом.

      Предложенный метод позволяет разработчику видеть код, что на Windows и Linux одинаковым способом без макроса EXPORT.

      и в cmake-листе всего одна строка, а не шесть.


      1. iroln
        29.02.2016 20:59

        Со сгенерированным заголовочным файлом экспорта код тоже выглядит одинаково на всех поддерживаемых платформах.

        А если не нужно экспортировать все символы? Способ с generate_export_header позволяет это сделать (NO_EXPORT_MACRO_NAME). Код в любом случае будет выглядеть одинаково и, я не вижу, что есть неестественного в подобном коде:

        #include "MyLibExport.h"
        
        class MyLib_EXPORT MyClass
        {
        };
        
        MyLib_HIDDEN void MyHiddenFunction();

        Причем это совершенно не мешает при статической линковке, так как сгенерированный файл экспорта учитывает, что библиотека может быть как SHARED, так и STATIC.

        Естественно, что при этом и новая переменная WINDOWS_EXPORT_ALL_SYMBOLS имеет полное право на жизнь. :)


  1. slonopotamus
    29.02.2016 20:12

    Во-первых, при чем здесь __linux__?

    Во-вторых, экспорт всех символов подряд несет за собой негативные последствия, почему вы о них умолчали?


    1. 19as
      29.02.2016 20:29

      1. для настройки экспорта классов в ОС Windows.
      2. Показан новый принцип экспорта на ОС Windows, т.к. на ОС Linux экспортируется все по умолчанию.