Потребовалось вдруг собрать DLL к довольно старому 32-битному приложению. И это оказалось для меня нетривиальной задачей.
У меня стоит QtCreator с компилятором MinGW64. В нем была собрана и отлажена необходимая DLL в 64-битном формате, на чистом C++, без всяких "потрохов" от Qt.
Судя по википедии, разработчики MinGW в свое время разругались с разработчиками версии под 64-битную архитектуру, поэтому ответвление MinGW64 стало самостоятельным продуктом, а не влилось в пакет MinGW. MinGW64 более полно поддерживает 64-битный режим, как и 32-битный. Поэтому рекомендуют выбирать именно его.
Компилятор MinGW64 должен собирать как 64-битные приложения, так и 32-х битные. Т.е. для сборки 32-разрядной версии DLL ожидалось, что достаточно будет сделать 32-разрядный "Kit" в "Manage Kits" QtCreator и собрать его так-же, как 64 разрядную версию. Для этого в Kit пришлось:
Но после сборки оказалось DLL собралась все-равно 64 битная, несмотря на все старания и попытки перебора настроек.
Если в Kit выбрать версию "Qt: None", то блокируется возможность использовать штатную систему сборки qMake. (На сколько это я сейчас помню… , но это не точно) Поэтому пришла идея использовать сборщик CMake. К тому же, современный QtCreator уже содержит CMake в своем составе.
Оказалось, что у меня так-же установлен еще один CMake, где-то глобально вне QtCreator. Отсюда возникли непонятки какой собственной CMake будет запускаться, если делать сборку через командную строку. К счастью, видимо CMake от Qt прописан я путях PATH раньше, чем второй вариант, поэтому у меня вызывался штатный CMake от QtCreator.
Сборка через CMake 64-разрядной версии DLL прошла успешно, и предстояло "всего лишь" попросить компилятор сделать 32 разрядную сборку ключами "-m32".
set_target_properties(NT_Spectrm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
Но…, это не сработало. Возможно мой компилятор более не поддерживает эти ключи, или по каким-то своим причинам все-равно выбирает 64-bit версию. Ведь CMake всё конфигурит сам, и не понятно, что он там выбирает под капотом. По строкам в файле CMakeCache.txt было видно, что он выбирает все те-же утилиты из пакета QtCreator.
Возникла идея поставить другой компилятор, заведомо поддерживающий 32-разрядный режим, и собрать DLL все всякого QtCreator. Для выбора компилятора в CMake необходимо указать файл CMAKE_TOOLCHAIN_FILE.
Этот файл должен описывать каким инструментарием будет пользоваться Cmake, вместо того, что он там выбрал сам при конфигурации.
# the name of the target operating system set(CMAKE_SYSTEM_NAME Windows) # which compilers to use for C and C++ set(CMAKE_C_COMPILER i586-mingw32msvc-gcc) set(CMAKE_CXX_COMPILER i586-mingw32msvc-g++) # where is the target environment located set(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc /home/alex/mingw-install) # adjust the default behavior of the FIND_XXX() commands: # search programs in the host environment set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # search headers and libraries in the target environment set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
Люди как-то так и делают, Habr: Как мы переводили проект на CMake:
cmake -G "Ninja" \ --toolchain=path_to/toolchainXX_file.cmake .. cmake --build .
По отзывам в Internet, есть такой пакет MSYS2 который призван упростить сборку проектов разными инструментами (Build Toolchain). Toolchain это не просто утилиты компилятор и линкер, Toolchain включает:
Вместе с установкой MSYS2 ставится пакетный менеджер Pacman (видимо аналог PIP в Python). С его помощью уже можно поставить любой Toolchain для сборки из поддерживаемых.
Соответственно, я снова поставил компилятор MinWG64 через Pacman, и попытался подключить этот компилятор через CMAKE_TOOLCHAIN_FILE для своей сборки через CMake. Но опять ничего не вышло. Не вспомню конкретно в чем заключалась проблема, но четко помню ощущения, что "руки походу ветвятся у меня не из того места". (Скорее всего Qt-шный CMake делал что-то свое, или какие-то запчасти не находились)
И тут, попалась статья которая все расставила по местам: Установка и использование MSYS2 и Mingw-w64 под Windows. После установки MSYS2 в системе появляется несколько изолированных терминалов со своими изолированными настройками.
В моем случае, мне необходимо было собрать DLL под 32-бита с помощью MinGW64. Соответственно, я запускаю терминал MSYS2 MinGW 32bit:
pacman -S --needed mingw-w64-i686-toolchain
pacman -S mingw-w64-i686-cmake
cd path_to/build_32 cmake -G "MinGW Makefiles" .. cmake --build .
УРА! Запускается правильный CMake (не Qt-шный) и сам генерит все необходимое для сборки под выбранную платформу - MSYS2 MinGW 32bit. DLL готова!
Интересно, что если запустить терминал MSYS2 MinGW 64bit, то вызов g++ или cmake в нем показывают, что такие пакеты не установлены. Соответственно, для сборки 64-разрядной версии надо поставить в этом терминале -mingw-w64-x86_64-toolchain и mingw-w64-x86_64-cmake.
В терминале сборки так-же можно узнать дополнительную информацию о полученной DLL, используем утилиты file и ldd:
$> file libNT_Spectrm.dll libNT_Spectrm.dll: PE32 executable (DLL) (console) Intel 80386, for MS Windows, 18 sections $> ldd libNT_Spectrm.dll ntdll.dll => /c/Windows/SYSTEM32/ntdll.dll (0x7fffae5f0000) KERNEL32.DLL => /c/Windows/System32/KERNEL32.DLL (0x7fffadce0000) KERNELBASE.dll => /c/Windows/System32/KERNELBASE.dll (0x7fffabff0000) msvcrt.dll => /c/Windows/System32/msvcrt.dll (0x7fffad890000) libNT_Spectrm.dll => /c/path.../libNT_Spectrm.dll (0x63990000)
Для примера, вот используемый cmake (возможно здесь есть что-то лишнее):
cmake_minimum_required(VERSION 3.14) project(NT_Spectrm LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set (CMAKE_CXX_FLAGS "-static-libstdc++ -Wall -m32 -s") set(CMAKE_BUILD_TYPE Release) message("Compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}") add_library(NT_Spectrm SHARED ../../SM_Dll/app_common.cpp ../../SM_Dll/app_lin_pos.cpp ../../SM_Dll/app_rot_gratings.cpp ../../SM_Dll/app_rot_items.cpp ../../SM_Dll/app_sel_desel.cpp ../../SM_Dll/app_targ_pos.cpp ../../SM_Dll/app_wave_len.cpp ../../SM_Dll/board_protocol.cpp ../../SM_Dll/coro_calibr.cpp ../../SM_Dll/coro_init_parse.cpp ../../SM_Dll/coro_moving.cpp ../../SM_Dll/ctrl_thread.cpp ../../SM_Dll/hidapi/nt_hidapi.cpp ../../SM_Dll/logger.cpp ../../SM_Dll/sm_dll.cpp ../../SM_Dll/storage.cpp ../../SM_Dll/wl_math.cpp ../../SM_Dll/sm_dll.h ) # Path to headers target_include_directories(NT_Spectrm PRIVATE ${CMAKE_SOURCE_DIR}/../../SM_Dll/ ${CMAKE_SOURCE_DIR}/../../SM_Dll/hidapi/ ) find_library(HIDAPI_LIBRARY NAMES hidapi.lib PATHS ${CMAKE_SOURCE_DIR}/../../HID_Api/x86/) message("HidAPi Lib: ${HIDAPI_LIBRARY}") target_link_libraries(NT_Spectrm PRIVATE ${HIDAPI_LIBRARY}) target_compile_definitions(NT_Spectrm PRIVATE SM_DLL_LIBRARY)
Ключ -s удаляет из библиотеки отладочные символы (strip symbols).
В моем случае, при подключении DLL к вызывающему приложению так-же потребовалось скопировать из C:\msys64\mingw32 библиотеки libwinpthread-1.dll и libgcc_s_dw2-1.dll. Старое приложение написано на Delphi и при вызове LoadLibrary() отладчик показывал, что моя библиотека и используемые в ней DLL загружаются, а потом сразу же выгружаются. В итоге LoadLibrary() возвращает 0. Используя TDump.exe необходимо посмотреть какие дополнительные DLL используются в моей DLL и скопировать их к вызывающему приложению.
Возможно пригодится: