Ссылка на файл .dll без .lib

Мне нужно переписать некоторый код Delphi на C++, и нам нужно связать динамическую библиотеку TMLComm2004.dll. Оказывается, у нас нет файла .lib, поэтому мы решили сгенерировать его, используя следующие командные строки:

dumpbin /EXPORTS C:\Users\fayard\Desktop\TMLComm2004.dll > C:\Users\fayard\Desktop\TMLComm2004.txt

Получаем следующий файл

Microsoft (R) COFF/PE Dumper Version 14.00.24215.1
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file C:\Users\fayard\Desktop\TMLComm2004.dll

File Type: DLL

  Section contains the following exports for TMLcomm.dll

    00000000 characteristics
    401F6AD5 time date stamp Tue Feb  3 10:33:09 2004
        0.00 version
          1 ordinal base
          27 number of functions
          27 number of names

    ordinal hint RVA      name

          1    0 00001122 _MSK_COFFDownloadFlash@16
          2    1 0000114F _MSK_COFFDownloadRAM@20
          3    2 0000106E _MSK_CheckSum@12
          4    3 00001172 _MSK_CloseComm@0
          5    4 00001190 _MSK_GetActiveBoard@4
          6    5 0000109B _MSK_GetBoardInfo@8
          7    6 0000104B _MSK_GetBytesCountInQueue@0
          8    7 0000119A _MSK_GetChar@4
          9    8 000010E6 _MSK_GetCharNoWait@4
         10    9 000011A4 _MSK_GetCommBaudRate@4
         11    A 000010DC _MSK_GetHostBoard@4
         12    B 0000108C _MSK_OpenComm@16
         13    C 000011DB _MSK_ReceiveData@16
         14    D 000011CC _MSK_ReceiveMessage@4
         15    E 0000105A _MSK_RegisterReceiveUnsolicitedMsgHandler@4
         16    F 000011AE _MSK_ResetMSKBoard@0
         17   10 00001037 _MSK_SendChar@4
         18   11 000010EB _MSK_SendData@16
         19   12 0000126C _MSK_SendMessage@4
         20   13 0000128F _MSK_SetActiveBoard@4
         21   14 00001136 _MSK_SetB0BlockAsData@4
         22   15 0000100A _MSK_SetBoardBaudRate@4
         23   16 00001019 _MSK_SetCharMode@4
         24   17 00001168 _MSK_SetCommBaudRate@4
         25   18 00001050 _MSK_SetDebugWindow@8
         26   19 00001276 _MSK_SetHostBoard@4
         27   1A 00001046 _MSK_UpdateCommTimeouts@0

  Summary

        C000 .data
        3000 .idata
        6000 .rdata
        4000 .reloc
       21000 .rsrc
       3E000 .text
        2000 shdata

Затем мы отредактировали файл TMLComm2014.txt и изменили его на файл .def с удаленным @xx. Файл выглядит так:

EXPORTS
_MSK_COFFDownloadFlash
...

Затем мы создаем файл .lib с:

lib /def:C:\Users\fayard\Desktop\TMLComm2004.def /out:C:\Users\fayard\Desktop\TMLComm2004.lib

Собираем следующую программу:

extern "C" {
  bool MSK_OpenComm(unsigned char a, unsigned short b, bool c, unsigned char d);
}

...

Когда мы компилируем и связываем с .lib, мы получаем ошибку:

LNK2019 unresolved external symbol _MSK_OpenComm referenced in function...

Что не так с тем, что я делаю?

Обновить

Мне удалось скомпилировать с:

extern "C" {
  bool __stdcall MSK_OpenComm(unsigned char a, unsigned short b, bool c, unsigned char d);
}

но теперь проблема во время ссылки. Я озадачен, как из командной строки, у меня есть:

==================================================
dumpbin /headers TMLComm.lib

  Version      : 0
  Machine      : 14C (x86)
  TimeDateStamp: 5964C365 Tue Jul 11 14:24:05 2017
  SizeOfData   : 00000022
  DLL name     : TMLComm2004.dll
  Symbol name  : __MSK_OpenComm@16
  Type         : code
  Name type    : no prefix
  Hint         : 11
  Name         : _MSK_OpenComm@16

==================================================
dumpbin /exports TMLComm.dll

         12    B 0000108C _MSK_OpenComm@16

==================================================

Теперь компиляция проходит нормально, но проблема во время компоновки:

link tmlcomm.obj Motor.obj main.obj TMLComm.lib /out:main.exe


Microsoft (R) Incremental Linker Version 14.00.24215.1
Copyright (C) Microsoft Corporation.  All rights reserved.

tmlcomm.obj : error LNK2019: unresolved external symbol _MSK_OpenComm@16 referenced in function "bool __cdecl msk_opencomm(unsigned char,unsigned short,bool,unsigned char)" (?msk_opencomm@@YA_NEG_NE@Z)
Teclis.exe : fatal error LNK1120: 1 unresolved externals

person InsideLoop    schedule 11.07.2017    source источник
comment
IMPLIB должен импортировать эти функции из DLL. Но это похоже на VC++ (или совместимую) DLL. Это означает, что вам придется заняться оформлением: см. здесь.   -  person Rudy Velthuis    schedule 11.07.2017
comment
Неразрешенный внешний символ... Вы упустили интересную часть: какие символы считаются неопределенными? Может ли помочь ссылка на MSVCRT.dll?   -  person Rudy Velthuis    schedule 11.07.2017
comment
@Rudy: _MSK_OpenComm сообщается как неопределенный   -  person InsideLoop    schedule 11.07.2017
comment
Вероятно потому, что он экспортируется как _MSK_OpenComm@16, а не как _MSK_OpenComm. .def рассматриваются только тогда, когда код C++ компилируется и компонуется, а не когда он импортируется.   -  person Rudy Velthuis    schedule 11.07.2017
comment
@Rudy: Это может быть причиной. Но я понятия не имею, как вызвать функцию с @ в ее имени. Это запрещено C/C++.   -  person InsideLoop    schedule 11.07.2017
comment
Взгляните на ссылку, которую я разместил в своем комментарии.   -  person Rudy Velthuis    schedule 11.07.2017
comment
Вы всегда можете динамически импортировать функции, используя LoadLibraryи GetProcAddress, то есть без библиотеки импорта.   -  person Rudy Velthuis    schedule 11.07.2017
comment
@Руди. Мне удалось создать файл .def, который переводит искаженные имена в имена C. Теперь он компилируется, но когда я запускаю программу, я получаю: Точка входа в процедуру MSK_SetBoardBaudRate не может быть расположена в библиотеке динамической компоновки C:\...\Main.exe . Почему он рассматривает .exe как динамическую библиотеку?   -  person InsideLoop    schedule 11.07.2017
comment
трудно сказать отсюда, извините. Но вроде ищет MSK_SetBoardBaudRate, а DLL экспортирует _MSK_SetBoardBaudRate@4. То, что он сообщает о неправильном исполняемом файле, не так важно, факт в том, что у вас все еще есть та же проблема (но теперь во время выполнения): он не может найти правильные экспортированные имена. Вместо этого попробуйте динамическое связывание. Там вы можете указать правильное имя в виде строки и присвоить его любому подходящему указателю на функцию. Проблем с отделкой больше нет.   -  person Rudy Velthuis    schedule 11.07.2017
comment
FWIW, как код Delphi связан с DLL?   -  person Rudy Velthuis    schedule 11.07.2017
comment
@Rudy: так было объявлено: function MSK_OpenComm; external k_TMLCommName name '_MSK_OpenComm@16';   -  person InsideLoop    schedule 11.07.2017
comment
В Delphi это действительно очень просто. В C++Builder это не так просто. В вашем файле .def есть такие записи, как MSK_SetBoardBaudRate=_MSK_SetBoardBaudRate@4? Это действительно должно работать.   -  person Rudy Velthuis    schedule 11.07.2017
comment
@Rudy: я пробовал это, но это не сработало. Но использование __stdcall в файлах C, кажется, ищет правильную функцию. Теперь он жалуется, что не нашел его (проверьте обновленный исходный вопрос).   -  person InsideLoop    schedule 11.07.2017


Ответы (1)


Взгляните на документацию Microsoft по оформленным именам. Для C подчеркивание в начале и @<number> в конце обозначают соглашение о вызовах stdcall, которое отличается от стандартного cdecl.

Итак, вы должны просто изменить свои объявления для всех функций, где символ заканчивается на @<number>:

extern "C" {
  __stdcall bool MSK_OpenComm(unsigned char a, unsigned short b, bool c, unsigned char d);
}

Я не уверен, что удаление части @<number> из файла .def является правильным решением, я сомневаюсь в этом, но просто попробуйте.

Из моего эксперимента с инструментом MinGWs для создания файла .def (gendef) имя в .def должно не включать начальное подчеркивание, но оно должно включать часть @<number> в конце конец. Итак, для этой примерной функции .def должна иметь следующую строку:

MSK_OpenComm@16

Альтернативой ручному созданию этих необходимых файлов являются инструменты, поставляемые с MinGW. Вы можете просто сделать следующее:

gendef foobar.dll                     # generates the .def
dlltool -d foobar.def -l foobar.lib   # generates the import library

Судя по вашим комментариям, вам возможно придется вручную отредактировать .def файл, который gendef дает вам, чтобы он содержал такие строки:

MSK_OpenComm@16=_MSK_OpenComm@16

Кажется, что gendef можно спутать, потому что dll экспортирует декорированные имена, что не ожидается.


При использовании библиотеки импорта у нее будут "статические заглушки", названные так же, как фактические экспортированные символы библиотеки DLL, но с добавлением __imp__ в начале. Чтобы учесть это, вам нужен еще один атрибут в ваших объявлениях:

extern "C" {
  __declspec(dllimport) __stdcall bool MSK_OpenComm(unsigned char a, unsigned short b, bool c, unsigned char d);
}
person Community    schedule 11.07.2017
comment
Это здорово, если вы пишете DLL, но не в том случае, если вы просто используете существующую DLL, написанную на VC++ из другого компилятора. DLL могла бы быть написана с файлом .def, дающим красивые и чистые неукрашенные имена, но это не так. Это не сильно поможет коду C++Builder, использующему DLL. - person Rudy Velthuis; 11.07.2017
comment
Конечно, будет. __stdcall в используемом вами объявлении сообщает компилятору, какой символ искать и как его вызывать. -- но я проглядел что-то другое, наверное... (и отредактировал) - person ; 11.07.2017
comment
@RudyVelthuis, если это правда, компилятор непригоден для платформы Windows. Я мог бы представить, что фактические атрибуты могут отличаться от того, что у меня есть в моем ответе (который работает для MSVC, а также для gcc и clang в Windows), но должен быть способ объявить функции как stdcall и использовать их, даже если DLL была скомпилирована другим компилятором. Win32 ABI требует этого. - person ; 11.07.2017
comment
@Felix: Спасибо за помощь. Я пытался следовать вашему пути, но я застрял на более поздней позиции. Описание находится в обновленном исходном вопросе. - person InsideLoop; 11.07.2017
comment
@Феликс: нет, это не так. Win32 ABI имеет неукрашенные имена, даже если они __stdcall. Многие третьи стороны также генерируют неукрашенные имена с использованием файла .def (и это будет работать как в VC++, так и в C++Builder). Только те, которые генерируют имена с подчеркиванием и at-num, создают некоторые проблемы (или, по крайней мере, больше работы) для пользователей C++Builder. - person Rudy Velthuis; 11.07.2017
comment
@RudyVelthuis Я не вижу, где вопрос относится к C++Builder, но в любом случае, хотя вы можете именовать любые символы с помощью файлов .def и импортировать библиотеки, это просто бесполезно, если ваши инструменты не поддерживают то, что стандартно для Платформа. - person ; 11.07.2017
comment
@Felix: я удалил подчеркивание. Теперь ссылка проходит нормально, но программа не запускается. Пишет: Не удалось найти точку входа в процедуру MSK_OpenComm@16 в библиотеке динамической компоновки C:\...\Main.exe - person InsideLoop; 11.07.2017
comment
@Felix: меня тоже смущает это сообщение - person InsideLoop; 11.07.2017
comment
@Felix: я тоже пробовал с __declspec. Та же проблема. - person InsideLoop; 11.07.2017
comment
@Felix: Это был тип. Я связывал файл .lib. Проблема теперь, когда я запускаю программу. Как было сказано выше, кажется, что Main.exe является динамической библиотекой, что странно. - person InsideLoop; 11.07.2017
comment
@Феликс: это не работает. Я попытался сгенерировать .def и .lib из 32-битной Minw (я использую 32-битную библиотеку), и с __declspec(dllimport) он просто говорит, что не может найти символ __imp__... при компоновке. - person InsideLoop; 11.07.2017
comment
Проверка @InsideLoop nm TMLComm.lib должна показывать символ 00000000 I __imp__MSK_OpenComm@16. Вы использовали командные строки для gendef и dlltool, которые я показал в своем ответе? - person ; 11.07.2017
comment
@Felix: Да, я использовал команды, которые вы использовали. При запуске nm TMLComm2004.lib я получаю 00000000 I __imp___MSK_OpenComm@16@16, с 3 _ после чертенка и 2 @16. Просто чтобы вы знали, программа затем связана с Visual Studio 2015. - person InsideLoop; 11.07.2017
comment
@Felix: Нет, .def содержит _MSK_OpenComm@16@16 - person InsideLoop; 11.07.2017
comment
@InsideLoop добавил еще одно редактирование, мой ответ теперь в конечном счете лоскутный - похоже, у вас здесь необычная ситуация;) - person ; 11.07.2017
comment
@Felix: я попробовал ваше предложение, но не повезло - person InsideLoop; 11.07.2017
comment
@InsideLoop Вы проверили полученный .lib с помощью nm? Он должен иметь символ I __imp__MSK_OpenComm@16. В .def строка после знака равенства является символом, экспортируемым DLL. см. документацию здесь, вместо имени также можно использовать порядковый номер. - person ; 11.07.2017
comment
@Felix: Да, у него есть этот символ. - person InsideLoop; 11.07.2017
comment
@Felix: Вас не озадачивает тот факт, что мой Main.exe, кажется, воспринимается как динамическая библиотека? - person InsideLoop; 11.07.2017
comment
@InsideLoop нет, если вы не используете __declspec(dllimport). Вы должны использовать его, ссылки на окна работают на двух уровнях... - person ; 11.07.2017
comment
@Felix: сейчас я использую __declspec(dllimport). Но это не имеет значения. - person InsideLoop; 11.07.2017
comment
Это невозможно, это не имеет значения. Вы не можете связать DLL без библиотеки импорта, а __declspec(dllimport) в объявлении функции сообщает компоновщику, какой символ искать (тот, что с префиксом __imp__). Если вы можете успешно скомпоновать, но у вас возникли проблемы во время выполнения, и вы действительно использовали __declspec(dllimport), что-то все еще было не так в файле .def, из которого была сгенерирована библиотека импорта. См. мой предыдущий комментарий со ссылкой на документацию. - person ; 11.07.2017