Встрял с DLL export

Тема в разделе "WASM.WIN32", создана пользователем SadKo, 30 июл 2022.

  1. SadKo

    SadKo Владимир Садовников

    Публикаций:
    8
    Регистрация:
    4 июн 2007
    Сообщения:
    1.600
    Адрес:
    г. Санкт-Петербург
    Встрял с компилятором MinGW и экспортом в DLL...

    У нас есть два стула файла

    file1.cpp:
    Код (Text):
    1.  
    2. extern "C"
    3. {
    4.   __declspec(dllexport)
    5.   int f() { return 1; }
    6.  
    7.   __declspec(dllexport)
    8.   int g() { return 2; }
    9.  
    10.   int k() { return 3; }
    11. }
    12.  
    file2.cpp:
    Код (Text):
    1.  
    2. extern "C"
    3. {
    4.   __declspec(dllexport)
    5.   int l() { return 1; }
    6.  
    7.   __declspec(dllexport)
    8.   int m() { return 2; }
    9.  
    10.   int n() { return 3; }
    11. }
    12.  
    Компилируем их таким образом:
    Код (Text):
    1.  
    2. g++ -o file1.o -c file1.cpp -fPIC
    3. g++ -o file2.o -c file2.cpp -fPIC
    4.  
    И линкуем:
    Код (Text):
    1.  
    2. g++ -o test1.dll file1.o file2.o -shared -fPIC
    3. dumpbin /exports test1.dll
    4.  
    Работает ожидаемо, экспортируются символы, помеченные __declspec(dllexport):
    [​IMG]

    Но если попытаться файлы предварительно слить в более крупный объектный файл:
    Код (Text):
    1.  
    2. ld -r -o file3.o file1.o file2.o
    3. g++ -o test2.dll file3.o -shared -fPIC
    4. dumpbin /exports test2.dll
    5.  
    То получаем экспорт всех возможных символов в DLL:
    [​IMG]

    MinGW 8.1.0, тестировал различные сборки, но результат не меняется.
    Есть какие-то идеи, как можно пофиксить, не используя DEF-файл (который как-то ещё сгенерировать придётся)?

    UPD:
    Похоже, нашёл, в какую сторону копать: файлы "file1.o" и "file2.o" содержат секции ".drectve", которые содержат нужные директивы от линкера, а файл "file3.o" уже их не содержит.
    Вот пример содержимого секции:
    [​IMG]
    Теперь вопрос как объединить эти секции при линковке...
     
    Последнее редактирование: 30 июл 2022
  2. aa_dav

    aa_dav Active Member

    Публикаций:
    0
    Регистрация:
    24 дек 2008
    Сообщения:
    256
    В линуксе аналоги 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 для аналогичного поведения. Но при нестандартных извратах видимо слетает этот налёт майкрософт-прогиба.
     
    Последнее редактирование: 30 июл 2022
  3. SadKo

    SadKo Владимир Садовников

    Публикаций:
    8
    Регистрация:
    4 июн 2007
    Сообщения:
    1.600
    Адрес:
    г. Санкт-Петербург
    aa_dav
    Я не про линуксы, а именно про винду.
    Вообще, три дня бился, наконец решил вопрос.
    Раскрываю всю соль. Дело действительно в секции .drectve.
    Когда мы, пытаясь объединить, прогоняем файлы через ld, он эту секцию просто дискардит.
    Поэтому когда объединённый объектный файл скармливается на итоговую линковку, то работает правило по умолчанию --export-all-symbols.

    Из доки по mingw:
    Теперь как этот механизм работает. А именно через секцию .drectve:
    Когда мы объединяем объектные файлы, нужно, чтобы секции .drectve сливались вместе в итоговом файле. Но в итоговом файле секции .drectve просто нет. Я искал, есть ли у ld какая-нибудь магическая опция, которая позволяет включить объединение этих секций, но не нашёл. Но, в итоге, спасло следующее решение.

    Решение
    В качестве решения, как выяснилось, подходит генерация дефолтного скрипта линковки для ld, в котором эта секция дискардится:
    Код (Text):
    1.  
    2.   /DISCARD/ :
    3.   {
    4.     *(.drectve)
    5.     *(.debug$S)
    6.     *(.debug$T)
    7.     *(.debug$F)
    8.   }
    9.  
    Если подправить скрипт и сделать вот так:
    Код (Text):
    1.  
    2.   .drectve  :
    3.   {
    4.     *(.drectve)
    5.   }
    6.   /DISCARD/ :
    7.   {
    8.     *(.debug$S)
    9.     *(.debug$T)
    10.     *(.debug$F)
    11.   }
    12.  
    То, ура-уа, всё работает, если этот скрипт подпихнуть компоновщику через опцию -T.
     
  4. aa_dav

    aa_dav Active Member

    Публикаций:
    0
    Регистрация:
    24 дек 2008
    Сообщения:
    256
    SadKo,
    Да я как бы тоже не про линуксы, а про то как GCC работает. Т.к. он изначально на линуксах, то MinGW невольно наследует этот базис. Подход несколько иной нежели в MSVC, но ради совместимости с исходниками MSVC явно были поправки на ветер с этими секциями.
    Но раз сработало, то и хорошо.


    Я еще немного погуглил сейчас - а как на линуксах запрещают экспорт из .so состоящего из нескольких модулей функции которые нужны только этим модулям (т.е. extern, но для внутреннего потребления) и действительно подход отличается от майкрософтовского опять же - делается это опять таки обратным макаром через атрибут __attribute__ ((visibility ("hidden"))) который наоборот вырезает символ из экспорта финальной .so. В свою очередь для совместимости с MSVC атрибут dllexport вменяет __attribute__ ((visibility ("default"))) и таким образом всё как бы работает в ожидаемом стиле.
     
  5. SadKo

    SadKo Владимир Садовников

    Публикаций:
    8
    Регистрация:
    4 июн 2007
    Сообщения:
    1.600
    Адрес:
    г. Санкт-Петербург
    aa_dav
    В линуксах можно компилятору передать опцию -fvisibility=hidden, и тогда все символы по умолчанию будут невидимы.
    А потом уже включать отдельные символы через __attribute__ ((visibility ("default"))).
    Деляется это с целью предотвращения конфликтов между символами из разных *.so, но имеющих одинаковое название, потому что тут имеет место замещение одного символа другим, что может приводить к странному поведению, багам и прочим ошибкам.
    Короче, мне не хватало соответствующей опции в ld.
    Я к тому, что если собираешь исходники из нескольких репозиториев вместе, отследить, что там экспортится и кем, очень проблематично, поэтому хотелось иметь какое-то универсальное решение.
    К счастью, оно нашлось.
     
  6. Rel

    Rel Well-Known Member

    Публикаций:
    2
    Регистрация:
    11 дек 2008
    Сообщения:
    4.847
    А зачем это может быть нужно? Будет ли это нормально работать с link time оптимизациями (-flto)?
     
  7. SadKo

    SadKo Владимир Садовников

    Публикаций:
    8
    Регистрация:
    4 июн 2007
    Сообщения:
    1.600
    Адрес:
    г. Санкт-Петербург
    Будет нормально работать.
    Сливается для того, чтобы при финальной линковке не словить ошибку с нехваткой длины командной строки.
    Плюс модульность. Собрал несколько модулей в объектники, потом слинковал всё вместе в *.so либо исполняемый файл.