Как работает библиотека импорта? Детали?

88

Я знаю, что фанатам это может показаться довольно простым. Но я хочу прояснить это.

Когда я хочу использовать Win32 DLL, обычно я просто вызываю API, такие как LoadLibrary () и GetProcAdderss (). Но в последнее время я работаю с DirectX9, и мне нужно добавить файлы d3d9.lib , d3dx9.lib и т. Д.

Я достаточно слышал, что LIB предназначен для статического связывания, а DLL - для динамического связывания.

Итак, я понимаю, что LIB содержит реализацию методов и статически связан во время компоновки как часть окончательного EXE-файла. Хотя DLL динамически загружается во время выполнения и не является частью окончательного EXE-файла.

Но иногда вместе с файлами DLL поставляются некоторые LIB -файлы, поэтому:

  • Для чего нужны эти файлы LIB?
  • Как они достигают того, для чего предназначены?
  • Есть ли какие-нибудь инструменты, которые позволят мне проверить внутреннюю структуру этих LIB-файлов?

Обновление 1

После проверки википедии я вспомнил, что эти файлы LIB называются библиотекой импорта . Но мне интересно, как это работает с моим основным приложением и динамически загружаемыми библиотеками DLL.

Обновление 2

Как и сказал RBerteig, в файлах LIB есть некоторый код-заглушка, рожденный вместе с библиотеками DLL. Итак, последовательность вызовов должна быть такой:

Мое основное приложение -> заглушка в LIB -> настоящая целевая DLL

Итак, какая информация должна содержаться в этих LIB? Я мог думать о следующем:

  • Файл LIB должен содержать полный путь к соответствующей DLL; Таким образом, DLL может быть загружена во время выполнения.
  • Относительный адрес (или смещение файла?) Точки входа каждого метода экспорта DLL должен быть закодирован в заглушке; Так можно было сделать правильные переходы / вызовы методов.

Я прав в этом? Есть что-то еще?

Кстати: есть ли какой-нибудь инструмент, который может проверить библиотеку импорта? Если я это увижу, сомнений больше не будет.

smwikipedia
источник
4
Я вижу, что никто не ответил на последнюю часть вашего вопроса, касающуюся инструментов, которые могут проверять библиотеку импорта. В Visual C ++ это можно сделать как минимум двумя способами: lib /list xxx.libи link /dump /linkermember xxx.lib. См. Этот вопрос о переполнении стека .
Алан
Кроме того , dumpbin -headers xxx.libобеспечивает некоторую более подробную информацию, по сравнению с libи linkкоммунальных услуг.
m_katsifarakis

Ответы:

102

Связывание с файлом DLL может происходить неявно во время компоновки компиляции или явно во время выполнения. В любом случае DLL загружается в область памяти процессов, и все ее экспортированные точки входа становятся доступными для приложения.

Если используется явно во время выполнения, вы используете LoadLibrary()и, GetProcAddress()чтобы вручную загрузить DLL и получить указатели на функции, которые вам нужно вызвать.

Если компоновка выполняется неявно при построении программы, то заглушки для каждого экспорта DLL, используемого программой, связываются с программой из библиотеки импорта, и эти заглушки обновляются по мере загрузки EXE и DLL при запуске процесса. (Да, я здесь немного упростил ...)

Эти заглушки должны откуда-то поступать, а в цепочке инструментов Microsoft они берутся из специальной формы файла .LIB, называемой библиотекой импорта . Требуемый .LIB обычно создается одновременно с DLL и содержит заглушку для каждой функции, экспортируемой из DLL.

Как ни странно, статическая версия той же библиотеки также будет поставляться в виде файла .LIB. Нет тривиального способа отличить их друг от друга, за исключением того, что LIB, которые являются библиотеками импорта для DLL, обычно будут меньше (часто намного меньше), чем соответствующие статические LIB.

Кстати, если вы используете инструментальную цепочку GCC, вам не нужны библиотеки импорта, соответствующие вашим DLL. Версия компоновщика Gnu, перенесенная на Windows, понимает библиотеки DLL напрямую и может синтезировать практически любые необходимые заглушки на лету.

Обновить

Если вы просто не можете устоять перед знанием того, где на самом деле все гайки и болты и что на самом деле происходит, в MSDN всегда есть что-то, что может вам помочь. Статья Мэтта Пьетрека « Углубленный взгляд на формат переносимых исполняемых файлов Win32» - это очень полный обзор формата EXE-файла и того, как он загружается и запускается. Его даже обновили, чтобы охватить .NET и многое другое, с тех пор, как он впервые появился в MSDN Magazine ca. 2002 г.

Кроме того, может быть полезно знать, как точно узнать, какие библиотеки DLL используются программой. Для этого используется Dependency Walker, также известный как Dependency Walker. Его версия включена в Visual Studio, но последняя версия доступна у ее автора по адресу http://www.dependencywalker.com/ . Он может идентифицировать все библиотеки DLL, которые были указаны во время компоновки (как ранняя, так и отложенная загрузка), а также может запускать программу и следить за любыми дополнительными библиотеками DLL, которые она загружает во время выполнения.

Обновление 2

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

Итак, у нас есть три способа сделать библиотечные функции доступными для использования в программе. Тогда очевидным последующим вопросом будет: «Как мне выбрать, какой путь?»

Статическая компоновка - это способ компоновки самой программы. Все ваши объектные файлы перечислены и собраны компоновщиком в EXE-файл. Попутно компоновщик берет на себя мелкие хлопоты, такие как исправление ссылок на глобальные символы, чтобы ваши модули могли вызывать функции друг друга. Библиотеки также могут быть статически связаны. Объектные файлы, составляющие библиотеку, собираются библиотекарем в файл .LIB, в котором компоновщик ищет модули, содержащие необходимые символы. Одним из эффектов статической компоновки является то, что с ней связываются только те модули из библиотеки, которые используются программой; другие модули игнорируются. Например, традиционная математическая библиотека C включает в себя множество функций тригонометрии. Но если вы свяжетесь с ним и используетеcos(), вы не получите копию кода для sin()или, tan()если вы также не вызвали эти функции. Для больших библиотек с богатым набором функций такое выборочное включение модулей важно. На многих платформах, таких как встроенные системы, общий размер кода, доступного для использования в библиотеке, может быть большим по сравнению с пространством, доступным для хранения исполняемого файла на устройстве. Без выборочного включения было бы труднее управлять деталями построения программ для этих платформ.

Однако наличие копии одной и той же библиотеки в каждой запущенной программе создает нагрузку на систему, которая обычно запускает множество процессов. При правильном типе системы виртуальной памяти страницы памяти с идентичным содержимым должны существовать в системе только один раз, но могут использоваться многими процессами. Это создает преимущество для увеличения вероятности того, что страницы, содержащие код, будут идентичны какой-либо странице в максимально возможном количестве других запущенных процессов. Но если программы статически связаны с библиотекой времени выполнения, то каждая из них имеет различное сочетание функций, каждая из которых выложена в карте памяти процессов в разных местах, и не так много кодовых страниц с общим доступом, если только это не программа, которая сама по себе запускать больше, чем просто процесс. Так что идея DLL получила еще одно важное преимущество.

DLL для библиотеки содержит все ее функции, готовые к использованию любой клиентской программой. Если много программ загружают эту DLL, все они могут использовать ее кодовые страницы. Все выигрывают. (Ну, пока вы не обновите DLL новой версией, но это не часть нашей истории. Google DLL Ад за эту сторону истории.)

Итак, первый большой выбор при планировании нового проекта - между динамической и статической связью. Благодаря статической привязке у вас меньше файлов для установки, и вы защищены от третьих лиц, обновляющих используемую DLL. Однако ваша программа больше, и она не так хороша в экосистеме Windows. При динамическом связывании у вас есть больше файлов для установки, у вас могут возникнуть проблемы с третьим лицом, обновляющим используемую вами DLL, но вы обычно более дружелюбны по отношению к другим процессам в системе.

Большим преимуществом DLL является то, что ее можно загружать и использовать без перекомпиляции или даже повторного связывания основной программы. Это может позволить стороннему поставщику библиотеки (например, Microsoft и среде выполнения C) исправить ошибку в своей библиотеке и распространить ее. Как только конечный пользователь устанавливает обновленную DLL, он немедленно получает исправление ошибки во всех программах, использующих эту DLL. (Если это не сломает вещи. См. DLL Hell.)

Другое преимущество заключается в различии неявной и явной загрузки. Если вы приложите дополнительные усилия для явной загрузки, то DLL может даже не существовать, когда программа была написана и опубликована. Это позволяет использовать механизмы расширения, которые могут, например, обнаруживать и загружать плагины.

RBerteig
источник
3
Удаление моего сообщения и поддержка этого, потому что вы объясняете вещи лучше, чем я;) Хороший ответ.
ereOn
2
@RBerteig: Спасибо за отличный ответ. Небольшое исправление, согласно здесь ( msdn.microsoft.com/en-us/library/9yd93633.aspx ), существует 2 типа динамического связывания с DLL: неявное связывание во время загрузки и явное связывание во время выполнения . Нет связывания во время компиляции . Теперь мне интересно, в чем разница между традиционным статическим связыванием (связывание с файлом * .lib, который содержит полную реализацию) и динамическим связыванием во время загрузки с DLL (через библиотеку импорта)?
smwikipedia
1
Продолжить: каковы плюсы и минусы статического связывания и динамического связывания во время загрузки ? Кажется, что оба этих подхода загружают все необходимые файлы в адресное пространство в начале процесса. Зачем нам их 2? Спасибо.
smwikipedia
1
вы можете использовать такой инструмент, как «objdump», чтобы заглянуть внутрь файла .lib и выяснить, является ли он библиотекой импорта или настоящей статической библиотекой. в Linux при кросс-компиляции с целью Windows можно запустить 'ar' или 'nm' для файлов .a (версия mingw файлов .lib) и обратите внимание, что библиотеки импорта имеют общие имена файлов .o и не содержат кода (просто инструкция jmp), в то время как статические библиотеки содержат множество функций и кода внутри.
Дон
1
Небольшое исправление: вы также можете неявно связывать во время выполнения. Поддержка компоновщика для DLL с отложенной загрузкой подробно объясняет это. Это полезно, если вы хотите динамически изменить путь поиска DLL или корректно обработать ошибку разрешения импорта (например, для поддержки новых функций ОС, но по-прежнему работать в более старых версиях).
Inspectable 06
5

Эти файлы библиотеки импорта .LIB используются в следующем свойстве проекта Linker->Input->Additional Dependenciesпри создании набора DLL, которым во время компоновки требуется дополнительная информация, которая предоставляется файлами библиотеки импорта .LIB. В приведенном ниже примере, чтобы не получать ошибки компоновщика, мне нужно ссылаться на библиотеки DLL A, B, C и D через их файлы lib. (обратите внимание, чтобы компоновщик нашел эти файлы, вам может потребоваться указать путь их развертывания, Linker->General->Additional Library Directoriesиначе вы получите ошибку сборки о невозможности найти какой-либо из предоставленных файлов lib.)

Компоновщик-> Ввод-> Дополнительные зависимости

Если ваше решение строит все динамические библиотеки, вы, возможно, смогли избежать этой явной спецификации зависимостей, полагаясь вместо этого на ссылочные флаги, отображаемые в Common Properties->Framework and Referencesдиалоговом окне. Похоже, что эти флаги автоматически выполняют связывание от вашего имени с использованием файлов * .lib. Рамки и ссылки

Однако это, как говорится, Общие свойства, которые не зависят от конфигурации или платформы. Если вам нужно поддерживать сценарий смешанной сборки, как в нашем приложении, у нас была конфигурация сборки для рендеринга статической сборки и специальная конфигурация, которая создавала ограниченную сборку подмножества сборок, которые были развернуты как динамические библиотеки. Я использовал Use Library Dependency Inputs и Link Library Dependenciesфлаги установлены истину в различных случаях , чтобы получить вещи , чтобы построить , а потом реализовать , чтобы упростить вещи , но при введении кода для статической сборку я представил тонну компоновщик предупреждения и сборка была невероятно медленной для статических сборок. В итоге я ввел кучу подобных предупреждений ...

warning LNK4006: "bool __cdecl XXX::YYY() already defined in CoreLibrary.lib(JSource.obj); second definition ignored  D.lib(JSource.obj)

И я закончил использовать ручную спецификацию, Additional Dependenciesчтобы удовлетворить компоновщик для динамических сборок, в то же время поддерживая статические сборщики, не используя общее свойство, которое их замедляло. Когда я развертываю сборку динамического подмножества, я развертываю только файлы dll, поскольку эти файлы lib используются только во время компоновки, а не во время выполнения.

Jxramos
источник
3

Есть три вида библиотек: статические, общие и динамически загружаемые.

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

Динамически загружаемые библиотеки очень похожи на разделяемые библиотеки, за исключением того, что они загружаются, когда и если в этом возникает необходимость из написанного вами кода.

Золтан Сеч
источник
@ Спасибо zacsek. Но я не уверен в вашем утверждении об общей библиотеке.
smwikipedia
@smwikipedia: В Linux они есть, я ими пользуюсь, так что они определенно существуют. Читайте также: en.wikipedia.org/wiki/Library_(computing)
Золтан Сеч,
3
Это тонкая разница. Общие и динамические библиотеки - это файлы DLL. Разница в том, когда они загружены. Общие библиотеки загружаются ОС вместе с EXE. Динамические библиотеки загружаются путем вызова кода LoadLibrary()и связанных API.
RBerteig
Я читал из [1], что DLL - это реализация Microsoft концепции Shared Library. [1]: en.wikipedia.org/wiki/Dynamic-link_library#Import_libraries
smwikipedia
Я не согласен с тем, что это тонкая разница, с точки зрения программирования она имеет огромное значение, динамически загружена общая библиотека или нет (если она динамически загружена, вам нужно добавить шаблонный код для доступа к функциям).
Zoltán Szcs