Юнит-тестирование в Qt классов dll без Q_DECL_EXPORT

Есть разрабатываемая dll в Qt. Как организовать проект, чтоб добавить юнит-тесты для тестирования классов без Q_DECL_EXPORT? Что пробовал я: Создавал проект с поддиректориями, где одна поддиректория - проект разрабатываемой dll, остальные поддиректории - проекты юнит-тестов. Окей, добавлял dll в юнит-тесты как "внешняя динамическая библиотека". Стали видны публичные классы (с Q_DECL_EXPORT). Далее добавляю пути к исходникам классов без экспорта: (test.pro SOURSEC += *путь/к/.срр/файлу/без_q_decl_export* HEADERT += *путь/к/.h/файлу/без_q_decl_export*) И все вроде бы должно работать. Но! Класс без экспортирования наследуется от класса С экспортированием. И Qt начинает пробрасывать ошибку redeclared without dllimport attribute: previous dllimport ignored. Что делать? Уважаемые программисты, работа встала!(


Ответы (1 шт):

Автор решения: Станислав Николаев

Пример структуры проекта с статической библиотекой

Цель: организовать проект так, чтобы можно было тестировать классы без Q_DECL_EXPORT, избежав ошибки redeclared without dllimport attribute.

1. Общая структура папок

MyProject/
├── src/                  # Исходники всей логики
│   ├── core/             # Классы, которые будут в статической библиотеке (включая классы без Q_DECL_EXPORT)
│   │   ├── myclass.cpp
│   │   ├── myclass.h
│   │   └── ...
│   └── dll/              # Классы с Q_DECL_EXPORT (публичный API DLL)
│       ├── dllclass.cpp
│       ├── dllclass.h
│       └── ...
├── lib/                  # Статическая библиотека (будет собрана из src/core/)
│   └── MyStaticLib.pro   # .pro-файл для сборки статической библиотеки
├── dll_project/          # Проект динамической библиотеки (DLL)
│   └── dll_project.pro   # .pro-файл: подключает статическую библиотеку и публичный API
├── test_project/         # Проект юнит-тестов
│   └── test_project.pro  # .pro-файл: подключает статическую библиотеку для тестов
└── MyProject.pro         # Основной .pro-файл (объединяет подпроекты)

2. Настройка .pro-файлов

a) lib/MyStaticLib.pro (статическая библиотека)

TEMPLATE = lib
CONFIG += staticlib

HEADERS += ../src/core/myclass.h
SOURCES += ../src/core/myclass.cpp

# Указываем, что библиотека не экспортируется как DLL
DEFINES += QT_NODLL

b) dll_project/dll_project.pro (DLL-проект)

TEMPLATE = lib
CONFIG += dll

HEADERS += ../src/dll/dllclass.h
SOURCES += ../src/dll/dllclass.cpp

# Подключаем статическую библиотеку
LIBS += -L../lib -lMyStaticLib

# Экспортируем публичные классы
DEFINES += Q_DECL_EXPORT

c) test_project/test_project.pro (юнит-тесты)

TEMPLATE = app
CONFIG += console

HEADERS += ../src/core/myclass.h
SOURCES += test_myclass.cpp

# Подключаем статическую библиотеку
LIBS += -L../lib -lMyStaticLib

# Указываем пути к исходникам (если нужно)
INCLUDEPATH += ../src/core

d) MyProject.pro (основной .pro-файл)

TEMPLATE = subdirs

SUBDIRS += \
    lib/MyStaticLib \
    dll_project/dll_project \
    test_project/test_project

3. Логика работы

  1. Статическая библиотека (MyStaticLib) собирает все классы без Q_DECL_EXPORT (находятся в src/core/). Она компилируется в .a (или .lib на Windows) и не экспортируется как DLL.
  2. DLL-проект использует эту статическую библиотеку и добавляет публичные классы с Q_DECL_EXPORTsrc/dll/). Эти классы формируют публичный API DLL.
  3. Проект юнит-тестов также подключается к статической библиотеке, чтобы тестировать внутренние классы (без Q_DECL_EXPORT).

4. Решение проблемы с dllimport

Ошибка redeclared without dllimport attribute возникает, когда класс без Q_DECL_EXPORT наследуется от класса с Q_DECL_EXPORT. Статическая библиотека решает эту проблему, так как:

  • классы из статической библиотеки не экспортируются (нет dllimport/dllexport);
  • DLL-проект и тесты линкуются со статической библиотекой, а не включают её классы напрямую.

5. Порядок сборки

  1. Сначала собирается MyStaticLib (статическая библиотека).
  2. Затем dll_project (использует статическую библиотеку + свои экспортируемые классы).
  3. Наконец, test_project (тестирует классы из статической библиотеки).

6. Дополнительные нюансы

  • Убедитесь, что пути в INCLUDEPATH и LIBS корректны (относительные пути зависят от структуры папок).
  • Если используете CMake, аналогичная логика реализуется через add_library() и target_link_libraries().
  • Для сложных иерархий наследования можно добавить условные директивы (#ifdef/#ifndef) в заголовки классов.

Такая структура позволяет:

  • тестировать внутренние классы без Q_DECL_EXPORT;
  • избежать конфликтов экспорта/импорта;
  • чётко разделить публичный и приватный API.
→ Ссылка