Встрял с компилятором MinGW и экспортом в DLL... У нас есть два стула файла file1.cpp: Код (Text): extern "C" { __declspec(dllexport) int f() { return 1; } __declspec(dllexport) int g() { return 2; } int k() { return 3; } } file2.cpp: Код (Text): extern "C" { __declspec(dllexport) int l() { return 1; } __declspec(dllexport) int m() { return 2; } int n() { return 3; } } Компилируем их таким образом: Код (Text): g++ -o file1.o -c file1.cpp -fPIC g++ -o file2.o -c file2.cpp -fPIC И линкуем: Код (Text): g++ -o test1.dll file1.o file2.o -shared -fPIC dumpbin /exports test1.dll Работает ожидаемо, экспортируются символы, помеченные __declspec(dllexport): Но если попытаться файлы предварительно слить в более крупный объектный файл: Код (Text): ld -r -o file3.o file1.o file2.o g++ -o test2.dll file3.o -shared -fPIC dumpbin /exports test2.dll То получаем экспорт всех возможных символов в DLL: MinGW 8.1.0, тестировал различные сборки, но результат не меняется. Есть какие-то идеи, как можно пофиксить, не используя DEF-файл (который как-то ещё сгенерировать придётся)? UPD: Похоже, нашёл, в какую сторону копать: файлы "file1.o" и "file2.o" содержат секции ".drectve", которые содержат нужные директивы от линкера, а файл "file3.o" уже их не содержит. Вот пример содержимого секции: Теперь вопрос как объединить эти секции при линковке...
В линуксе аналоги DLL называются не просто так shared object (.so) в противовес простным объектникам (object (.o)) - так происходит потому что это те же самые объектники, просто с линкуемые не статически, а динамически. Т.е. принципиальная разница не в формате даже, а в том когда линковка осуществляется - при сборке выполняемого файла или при его запуске. Это важно понимать потому что тут как раз эта штука прорывается - Minimal GNU for Windows хотя и пытается всячески мимикрировать под поведение Visual C++, но всё таки в сердце его сидит линуксовые корни и это тут важно. Так вот - вопрос - какие функции мы экспортируем в обычном .o? Правильно - все функции которые помечены как extern, а модификатор extern в Си вменяется по умолчанию, поэтому правильный ответ: все функции не помеченные как static! Повторяюсь, что в линуксе разница между .o и .so только в ключах линковки, а не в том что надо там какие то отдельные делать для DLL понятия импорта/экспорта которые отдельны от понятия импорта/экспорта самого языка Си, поэтому всё то же самое - если мы ключами компиляции скомпилируем юнит как .so, то экспортироваться будут все функции не помеченные как static. Это просто сущность того как работает Си. В линуксах прямолинейная и незамутнённая грязными хаками MS. Поэтому модификатор __declspec(dllexport) в линуксах вообще не нужен в принципе (без него всё будет работать как надо), но существует как хинт для некоторых платформ где его применение может оптимизировать процесс динамической линковки (почему и как - тема для довольно большой статьи которую я читал когда то). Поэтому в отличие от MSVC где DLL это какой то свой цирк с конями, .def-файлами и всяким таким - тут надо просто сделать всё как надо. А надо просто объявить функции которые не нужны для внешнего связывания (неважно DLL или нет) как static. Просто то что надо экспортировать (в .obj или в .dll - неважно) объявляем как extern, а что не надо - как static. Это универсальный и логичный принцип. P.S. А эта секция .drective в MinGW скорее всего добавлена как слой совместимости с MSVC для аналогичного поведения. Но при нестандартных извратах видимо слетает этот налёт майкрософт-прогиба.
aa_dav Я не про линуксы, а именно про винду. Вообще, три дня бился, наконец решил вопрос. Раскрываю всю соль. Дело действительно в секции .drectve. Когда мы, пытаясь объединить, прогоняем файлы через ld, он эту секцию просто дискардит. Поэтому когда объединённый объектный файл скармливается на итоговую линковку, то работает правило по умолчанию --export-all-symbols. Из доки по mingw: Теперь как этот механизм работает. А именно через секцию .drectve: Когда мы объединяем объектные файлы, нужно, чтобы секции .drectve сливались вместе в итоговом файле. Но в итоговом файле секции .drectve просто нет. Я искал, есть ли у ld какая-нибудь магическая опция, которая позволяет включить объединение этих секций, но не нашёл. Но, в итоге, спасло следующее решение. Решение В качестве решения, как выяснилось, подходит генерация дефолтного скрипта линковки для ld, в котором эта секция дискардится: Код (Text): /DISCARD/ : { *(.drectve) *(.debug$S) *(.debug$T) *(.debug$F) } Если подправить скрипт и сделать вот так: Код (Text): .drectve : { *(.drectve) } /DISCARD/ : { *(.debug$S) *(.debug$T) *(.debug$F) } То, ура-уа, всё работает, если этот скрипт подпихнуть компоновщику через опцию -T.
SadKo, Да я как бы тоже не про линуксы, а про то как GCC работает. Т.к. он изначально на линуксах, то MinGW невольно наследует этот базис. Подход несколько иной нежели в MSVC, но ради совместимости с исходниками MSVC явно были поправки на ветер с этими секциями. Но раз сработало, то и хорошо. Я еще немного погуглил сейчас - а как на линуксах запрещают экспорт из .so состоящего из нескольких модулей функции которые нужны только этим модулям (т.е. extern, но для внутреннего потребления) и действительно подход отличается от майкрософтовского опять же - делается это опять таки обратным макаром через атрибут __attribute__ ((visibility ("hidden"))) который наоборот вырезает символ из экспорта финальной .so. В свою очередь для совместимости с MSVC атрибут dllexport вменяет __attribute__ ((visibility ("default"))) и таким образом всё как бы работает в ожидаемом стиле.
aa_dav В линуксах можно компилятору передать опцию -fvisibility=hidden, и тогда все символы по умолчанию будут невидимы. А потом уже включать отдельные символы через __attribute__ ((visibility ("default"))). Деляется это с целью предотвращения конфликтов между символами из разных *.so, но имеющих одинаковое название, потому что тут имеет место замещение одного символа другим, что может приводить к странному поведению, багам и прочим ошибкам. Короче, мне не хватало соответствующей опции в ld. Я к тому, что если собираешь исходники из нескольких репозиториев вместе, отследить, что там экспортится и кем, очень проблематично, поэтому хотелось иметь какое-то универсальное решение. К счастью, оно нашлось.
Будет нормально работать. Сливается для того, чтобы при финальной линковке не словить ошибку с нехваткой длины командной строки. Плюс модульность. Собрал несколько модулей в объектники, потом слинковал всё вместе в *.so либо исполняемый файл.