Как правильно установить ссылку на исполняемый файл в Windows?

Мне нужно использовать некоторые символы из основного исполняемого файла в плагине.

Связывание с исполняемым файлом вызывает следующие ошибки компоновщика:

i686-w64-mingw32-g++ example.cpp -shared -I.. -std=c++11 -o test.dll ../../test.exe -static-libgcc -static-libstdc++ -fvisibility=hidden
[..]/test.exe:cygming-crtbegin.c:(.text+0x500): multiple definition of `__gcc_register_frame'
/usr/lib/gcc/i686-w64-mingw32/5.1.0/crtbegin.o:cygming-crtbegin.c:(.text+0x0): first defined here
[..]/test.exe:cygming-crtbegin.c:(.text+0x560): multiple definition of `__gcc_deregister_frame'
/usr/lib/gcc/i686-w64-mingw32/5.1.0/crtbegin.o:cygming-crtbegin.c:(.text+0x60): first defined here
[..]/test.exe: In function `ZlsRSoRK5Color':
[..]src/tools.h:212: multiple definition of `operator<<(std::ostream&, Color const&)'
/tmp/ccC97Hkz.o:example.cpp:(.text$_ZlsRSoRK5Color[__ZlsRSoRK5Color]+0x0): first defined here
../../test.exe: In function `ZN7MessageILb0EElsIcEERS0_OT_':
[..]/src/tools.h:241: multiple definition of `Message<false>& Message<false>::operator<< <char>(char&&)'
/tmp/ccC97Hkz.o:example.cpp:(.text$_ZN7MessageILb0EElsIcEERS0_OT_[__ZN7MessageILb0EElsIcEERS0_OT_]+0x0): first defined here
[..]/test.exe:crtexe.c:(.idata+0x3f0): multiple definition of `_imp__GeoIP_country_code'
[..]/test.exe:crtexe.c:(.idata+0x3f0): first defined here
[..]/test.exe:crtexe.c:(.idata+0x3f4): multiple definition of `_imp__GeoIP_country_name'
[..]/test.exe:crtexe.c:(.idata+0x3f4): first defined here
/usr/lib/gcc/i686-w64-mingw32/5.1.0/crtbegin.o:cygming-crtbegin.c:(.text+0x22): undefined reference to `_Jv_RegisterClasses'
collect2: error: ld returned 1 exit status

Теперь, если я создаю основной исполняемый файл с -shared -Wl,--export-all-symbols, то связывание с test.exe работает, но загрузчик Windows (или, по крайней мере, винный) жалуется, что test.exe является dll.

Поэтому мне нужно повторно связать test.exe еще раз без -shared, чтобы я мог запустить test.exe.

i.e.:

# produce the import executable
i686-w64-mingw32-g++ tools.o main.o [...] -o ../test.exe -shared -Wl,--export-all-symbols [...] -static-libgcc -static-libstdc++

# produce the real executable
i686-w64-mingw32-g++ tools.o main.o [...] -o ../test.exe -Wl,--export-all-symbols [...] -static-libgcc -static-libstdc++

Это супер хакерство, но в конце концов у меня есть рабочий плагин ...

Чтобы подойти к моему вопросу:

Есть ли лучший способ добиться этого (без передачи указателей на функции)?

Я знаю, что MSVC может выводить библиотеки импорта для исполняемых файлов, есть ли аналогичный способ для MinGW?

Я попытался добавить -Wl,--out-implib,test.a к флагам компоновщика, чтобы получить библиотеку импорта для исполняемого файла, но похоже, что --out-implib игнорируется при компоновке исполняемого файла.


person Thomas    schedule 26.05.2015    source источник


Ответы (2)


Это тот случай, когда вы, вероятно, действительно захотите квалифицировать символы обратного вызова в .exe атрибутом __declspec(dllexport). При кросс-компиляции на моем Linux Mint Debian мне подходит следующий минимальный пример:

$ cat foo.c
#include <stdio.h>

int __declspec(dllexport) foo( int bar ){ return bar << 2; }
int main(){ printf( "%d\n", foo( 4 ) ); return 0; }

$ mingw32-gcc -o ~/src/exp/foo.exe -Wl,--out-implib=libfoo.dll.a foo.c

Это создает и рабочий исполняемый файл, и , и библиотеку импорта для сопоставления его экспортированных символов для использования при связывании подключаемых модулей с помощью всего лишь один вызов компоновщика в предыдущих командах (как видно при запуске исполняемого файла под вином и перечислении библиотеки импорта с помощью встроенного инструмента linux nm):

$ ~/src/exp/foo.exe
16

$ nm -A libfoo.dll.a
libfoo.dll.a:d000002.o:00000000 I _foo_exe_iname
libfoo.dll.a:d000002.o:00000000 i .idata$4
libfoo.dll.a:d000002.o:00000000 i .idata$5
libfoo.dll.a:d000002.o:00000000 i .idata$7
libfoo.dll.a:d000000.o:         U _foo_exe_iname
libfoo.dll.a:d000000.o:00000000 I __head_foo_exe
libfoo.dll.a:d000000.o:00000000 i .idata$2
libfoo.dll.a:d000000.o:00000000 i .idata$4
libfoo.dll.a:d000000.o:00000000 i .idata$5
libfoo.dll.a:d000001.o:00000001 a @feat.00
libfoo.dll.a:d000001.o:00000000 T _foo
libfoo.dll.a:d000001.o:         U __head_foo_exe
libfoo.dll.a:d000001.o:00000000 i .idata$4
libfoo.dll.a:d000001.o:00000000 i .idata$5
libfoo.dll.a:d000001.o:00000000 i .idata$6
libfoo.dll.a:d000001.o:00000000 i .idata$7
libfoo.dll.a:d000001.o:00000000 I __imp__foo
libfoo.dll.a:d000001.o:00000000 t .text

Точно так же исполняемый файл отлично работает в WinXP (работает в VirtualBox в поле LMDE, с ~ / src / exp, отображаемым как диск E: в виртуальной машине WinXP и вызываемым из оболочки MSYS):

$ /e/foo.exe
16

FWIW, я могу воспроизвести вашу неудачу при создании исполняемого файла при добавлении атрибута -shared к вызову компоновщика; как вы заметили, это предназначено для создания библиотек DLL (которые отличаются от исполняемых файлов форматом только тем, что в заголовок встроено другое магическое число; в остальном они в основном одинаковы).

В итоге:

  • Не указывайте -shared при связывании исполняемого файла.

  • Подбирайте символы для экспорта из исполняемого файла с атрибутом __declspec(dllexport).

  • Укажите атрибут -Wl,--out-implib=lib<exename>.dll.a при связывании исполняемого файла.

person Keith Marshall    schedule 27.05.2015
comment
Спасибо за Ваш ответ. Полезно знать, что компоновщик генерирует библиотеку импорта только для явно экспортированных символов при компоновке исполняемого файла. Однако мне бы очень хотелось избежать засорения множества символов __declspec(dllexport) / API defines только из-за Windows. Но это действительно лучшее решение, чем мое. Возможно, людям GNU следует рассмотреть возможность добавления опции для создания библиотеки импорта для исполняемых файлов без явного указания __declspec(dllexport) - для них не должно быть много работы, поскольку код для dll уже существует. - person Thomas; 27.05.2015
comment
@Thomas: Я согласен, что избыток атрибутов __declspec(dllexport) просто уродлив. OTOH, вы действительно не хотите экспортировать все символы из исполняемого файла, поэтому я считаю, что текущее поведение GNU ld является правильным. Возможно, более элегантным было бы объявить макрос, скажем, #define EXE_CALLBACK __declspec(dllexport), и использовать его для уточнения ваших экспортов; имеет то преимущество, что подчеркивает особое значение этих символов в вашем коде. (Возможно, даже подумайте о том, чтобы определить его условно, как __declspec(dllexport) в исполняемом файле и __declspec(dllimport) в ваших подключаемых модулях). - person Keith Marshall; 28.05.2015
comment
@Thomas: FWIW, mingw32-gcc -o foo.exe -Wl,--export-all-symbols -Wl,--out-implib=libfoo.dll.a foo.c у меня отлично работает. Он действительно создает библиотеку импорта libfoo.dll.a; проблема, которую я вижу в этом, заключается в том, что эта библиотека импорта включает символы, которые, IMO, ей не принадлежат. - person Keith Marshall; 28.05.2015
comment
Вы правы, компоновщик действительно выводит библиотеку импорта. Я, должно быть, забыл -Wl,--export-all-symbols, когда проверял это. Довольно обидно. В любом случае, я нашел решение, чтобы избежать __declspec(dllexport), см. Мой ответ ниже. - person Thomas; 28.05.2015

Как и Кейт Маршалл в комментариях, -Wl,--out-implib действительно работает в сочетании с:

  • -Wl,--export-all-symbols

  • объявив символы с помощью __declspec(dllexport)

  • или предоставив файл .def

Я выбрал третий вариант и написал сценарий bash для генерации сценариев файла / версии def на лету, чтобы избежать экспорта большого количества ненужных символов.

Скрипт можно найти здесь.

Используйте это как:

export SYMBOLS_TO_EXPORT="*tools* *network* _Z8compressPvRjPKvjib ..." # use mangled names and skip leading underscores on i686
export HOSTPREFIX=i686-w64-mingw32 # unneeded on Windows
i686-w64-mingw32-g++ $(OBJS) `./gen_export_file $(OBJS)` -Wl,--out-implib=foo.exe.a -o foo.exe
person Thomas    schedule 26.05.2015
comment
Да, создание файла .def вместо использования атрибута __declspec(dllexport) также будет работать. Однако это не был бы мой предпочтительный выбор, потому что он а) увеличивает нагрузку на обслуживание дополнительного .def файла и б) жертвует самодокументированным преимуществом наличия хорошо выбранного имя макроса, расширяющееся до __declspec(dllexport), чтобы четко идентифицировать ваши обратные вызовы прямо там, где они определены в вашем исходном коде. - person Keith Marshall; 31.05.2015
comment
@KeithMarshall: Верно, ваше решение действительно предпочтительнее. - person Thomas; 03.06.2015