Разница между общими объектами (.so), статическими библиотеками (.a) и DLL (.so)?

273

Я принимал участие в некоторых дебатах по поводу библиотек в Linux и хотел бы подтвердить некоторые вещи.

Насколько я понимаю (пожалуйста, исправьте меня, если я ошибаюсь, и я отредактирую свой пост позже), что есть два способа использования библиотек при создании приложения:

  1. Статические библиотеки (файлы .a): во время компоновки копия всей библиотеки помещается в конечное приложение, чтобы функции внутри библиотеки всегда были доступны вызывающему приложению
  2. Общие объекты (файлы .so). Во время соединения объект просто проверяется на соответствие его API через соответствующий файл заголовка (.h). Библиотека фактически не используется до времени выполнения, где это необходимо.

Очевидное преимущество статических библиотек состоит в том, что они позволяют полностью автономно использовать все приложение, тогда как преимущество динамических библиотек заключается в том, что файл «.so» можно заменить (т. Е. В случае необходимости его обновления из-за безопасности ошибка) без необходимости перекомпиляции базового приложения.

Я слышал, что некоторые люди делают различие между общими объектами и динамически связанными библиотеками (DLL), хотя они оба являются файлами ".so". Есть ли какое-либо различие между общими объектами и библиотеками DLL, когда речь идет о разработке C / C ++ для Linux или любой другой POSIX-совместимой ОС (например, MINIX, UNIX, QNX и т. Д.)? Мне говорят, что одно ключевое отличие (пока) заключается в том, что общие объекты просто используются во время выполнения, в то время как библиотеки DLL сначала должны открываться с помощью вызова dlopen () в приложении.

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

Спасибо всем заранее за вашу помощь.

Обновить


В контексте, в котором эти термины были предоставлены мне, это были фактически ошибочные термины, используемые командой разработчиков Windows, которые должны были изучать Linux. Я пытался их исправить, но (неправильные) языковые нормы застряли.

  1. Общий объект: библиотека, которая автоматически связывается с программой при запуске и существует как отдельный файл. Библиотека включается в список ссылок во время компиляции (т. LDOPTS+=-lmylibЕ. Для файла библиотеки с именем mylib.so). Библиотека должна присутствовать во время компиляции и при запуске приложения.
  2. Статическая библиотека: библиотека, которая объединяется с самой программой во время сборки для одного (большего) приложения, содержащего код приложения и код библиотеки, который автоматически связывается с программой при сборке программы, и конечный двоичный файл, содержащий оба основная программа и сама библиотека существуют в виде отдельного двоичного файла. Библиотека включается в список ссылок во время компиляции (то есть: LDOPTS+=-lmylibдля файла библиотеки с именем mylib.a). Библиотека должна присутствовать во время компиляции.
  3. DLL: по сути то же самое, что и общий объект, но вместо того, чтобы быть включенным в список ссылок во время компиляции, библиотека загружается с помощью команд dlopen()/, dlsym()так что библиотека не должна присутствовать во время сборки для компиляции программы. Кроме того, библиотека не обязательно должна присутствовать (обязательно) при запуске приложения или во время компиляции , так как это необходимо только в момент выполнения вызовов dlopen/ dlsym.
  4. Общий архив: по сути, такой же, как статическая библиотека, но компилируется с флагами "export-shared" и "-fPIC". Библиотека включается в список ссылок во время компиляции (т. LDOPTS+=-lmylibSЕ. Для файла библиотеки с именем mylibS.a). Различие между ними заключается в том, что этот дополнительный флаг необходим, если общий объект или DLL хотят статически связать общий архив с собственным кодом и иметь возможность сделать функции в общем объекте доступными для других программ, а не просто использовать их. внутренний в DLL. Это полезно в том случае, если кто-то предоставляет вам статическую библиотеку, и вы хотите переупаковать ее в виде SO. Библиотека должна присутствовать во время компиляции.

Дополнительное обновление

Различие между " DLL" и " shared library" было просто (ленивым, неточным) разговором в компании, в которой я работал в то время (разработчики Windows были вынуждены перейти на разработку Linux, и термин застрял), придерживаясь описаний, упомянутых выше.

Кроме того, конечный « S» литерал после имени библиотеки в случае «общих архивов» был просто соглашением, используемым в этой компании, а не в отрасли в целом.

облако
источник
14
Для .aфайлов «а» фактически означает «archove», и это просто архив объектных файлов. Современные компоновщики должны быть достаточно хорошими, чтобы не включать библиотеку while, только файлы объектов в архиве, которые необходимы, и могут даже просто использовать фрагменты кода / данных в объектных файлах, на которые есть ссылки.
Какой-то программист чувак
4
DLL это просто терминология Windows. Он не используется в универе.
R .. GitHub ОСТАНОВИТЬ ЛЬДА
2
Читайте tldp.org/HOWTO/Program-Library-HOWTO
Старынкевич,
2
@DevNull "Arch I Ve", конечно. :)
Какой-то программист чувак

Ответы:

94

Я всегда думал, что библиотеки DLL и общие объекты - это просто разные термины для одного и того же - Windows называет их библиотеками DLL, в то время как в системах UNIX они являются общими объектами с общим термином - динамически связанной библиотекой - охватывающим оба (даже функцию open .so в UNIX вызывается dlopen()после «динамической библиотеки»).

Они действительно связаны только при запуске приложения, однако ваше представление о проверке по заголовочному файлу неверно. Заголовочный файл определяет прототипы, которые необходимы для компиляции кода, который использует библиотеку, но во время компоновки компоновщик просматривает саму библиотеку, чтобы убедиться, что необходимые ей функции действительно присутствуют. Компоновщик должен найти тела функций где-то во время ссылки, иначе возникнет ошибка. Он также делает это во время выполнения, потому что, как вы правильно заметили, сама библиотека могла измениться после компиляции программы. Вот почему стабильность ABI так важна в библиотеках платформ, поскольку изменение ABI - это то, что нарушает существующие программы, скомпилированные с более старыми версиями.

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

Мэтью Уолтон
источник
1
Почему некоторые проекты, которые я вижу в Linux, должны использовать вызов dlopen () для доступа к функциям в файле ".so", а некоторые вообще не обязаны это делать? Спасибо, кстати!
Облако
9
Те, кто этого не делают, получают функции, передаваемые им загрузчиком процессов, то есть эльфийским загрузчиком linux. dlopen существует, если приложение хочет открыть и использовать .so или .dll, которых не было при компиляции, или просто добавить дополнительную функциональность, такую ​​как плагины.
Rapadura
Но не будет ли приложение вообще не компилироваться, если .so не присутствует во время сборки? Можно ли заставить компоновщик просто создать окончательную программу без .so вообще? Спасибо.
Облако
1
Я полагаю, что это зависит от того, как вы используете функции из .so, но здесь мои знания об этом останавливаются: / Хорошие вопросы.
Rapadura
1
Что касается dlopen () и его семейства функций, то, насколько я понимаю, это используется для программного открытия / закрытия библиотеки DLL, чтобы ее не приходилось загружать в память на протяжении всего запуска приложения. В противном случае вы должны указать компоновщику в его аргументах командной строки (он же ваш make-файл), что вы хотите, чтобы библиотека была загружена. Он будет загружен во время выполнения и останется загруженным в память до выхода из приложения. На уровне ОС может происходить больше вещей, но это примерно то, что происходит с вашим приложением.
Тейлор Прайс
198

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

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

Динамически подключаемую библиотеку на окнах (.dll) , как разделяемую библиотеку (.so) на Linux , но есть некоторые различия между этими двумя реализациями, которые связаны с ОС (Windows , против Linux):

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

SO библиотека Linux не требует специального экспорта заявления , чтобы указать экспортируемые символы, поскольку все символы доступны для процесса запросного.

aleroot
источник
1
+1 хорошее простое объяснение. Если функция объявлена ​​как «Внутренняя» в DLL, значит ли это, что ее нельзя вызвать из-за пределов библиотеки?
Майк
23
Это не обязательно верно, что все символы доступны в SO библиотеке. Скрытые символы возможны и рекомендуются, потому что у пользователей библиотеки нет веских причин видеть все ваши символы.
Zan Lynx
3
К вашему сведению: g ++ имеет __attribute__синтаксис для выборочного «экспорта» символов:#define DLLEXPORT __attribute__ ((visibility("default"))) #define DLLLOCAL __attribute__ ((visibility("hidden")))
Брайан Хаак
33

Я могу подробнее рассказать о библиотеках DLL в Windows, чтобы помочь разъяснить эти загадки моим друзьям здесь, в * NIX-стране ...

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

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

DLL-библиотеки Windows создаются путем компиляции и компоновки, как если бы вы работали с EXE (исполняемым приложением), но DLL должна быть автономной, как и SO, предназначенный для использования приложением либо посредством динамической загрузки, либо с помощью привязки по времени соединения (ссылка на SO встроена в метаданные двоичного файла приложения, и загрузчик программы ОС автоматически загрузит ссылочные SO). DLL могут ссылаться на другие DLL так же, как SO могут ссылаться на другие SO.

В Windows библиотеки DLL сделают доступными только определенные точки входа. Это называется «экспорт». Разработчик может либо использовать специальное ключевое слово компилятора, чтобы сделать символ видимым извне (для других компоновщиков и динамического загрузчика), либо экспорт может быть указан в файле определения модуля, который используется во время компоновки, когда сама библиотека DLL создается Современная практика заключается в украшении определения функции ключевым словом для экспорта имени символа. Также возможно создать заголовочные файлы с ключевыми словами, которые объявят этот символ как импортируемый из DLL вне текущей единицы компиляции. Найдите ключевые слова __declspec (dllexport) и __declspec (dllimport) для получения дополнительной информации.

Одной из интересных особенностей библиотек DLL является то, что они могут объявлять стандартную функцию-обработчик «при загрузке / выгрузке». Всякий раз, когда DLL загружается или выгружается, DLL может выполнить некоторую инициализацию или очистку, в зависимости от обстоятельств. Это хорошо сочетается с наличием библиотеки DLL в качестве объектно-ориентированного менеджера ресурсов, такого как драйвер устройства или интерфейс общего объекта.

Когда разработчик хочет использовать уже созданную DLL, он должен либо ссылаться на «экспортную библиотеку» (* .LIB), созданную разработчиком DLL при создании DLL, либо он должен явно загрузить DLL во время выполнения и запросить адрес точки входа по имени через механизмы LoadLibrary () и GetProcAddress (). В большинстве случаев связывание с файлом LIB (который просто содержит метаданные компоновщика для экспортированных точек входа DLL) - это способ использования DLL. Динамическая загрузка обычно зарезервирована для реализации «полиморфизма» или «конфигурируемости во время выполнения» в поведении программы (доступ к надстройкам или определенным позже функциям, также называемым «плагинами»).

Способ работы Windows может иногда вызывать некоторую путаницу; система использует расширение .LIB для ссылки как на обычные статические библиотеки (архивы, такие как файлы POSIX * .a), так и на библиотеки «экспорта-заглушки», необходимые для привязки приложения к DLL во время соединения. Таким образом, следует всегда проверять, имеет ли файл * .LIB файл с таким же именем * .DLL; в противном случае велика вероятность, что файл * .LIB является статическим архивом библиотеки, а не экспортирует метаданные привязки для DLL.

JoGusto
источник
4

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

Вызов dlopen предназначен не только для общих объектов, если приложение желает сделать это во время выполнения от своего имени, в противном случае общие объекты загружаются автоматически при запуске приложения. DLLS и .so - это одно и то же. dlopen существует, чтобы добавить еще более тонкие возможности динамической загрузки для процессов. Вам не нужно использовать dlopen для открытия / использования библиотек DLL, что также происходит при запуске приложения.

Rapadura
источник
Что может быть одним из примеров использования dlopen () для большего контроля загрузки? Если SO / DLL автоматически загружается при запуске, закрывает ли, например, dlopen () и повторно открывает его с другими разрешениями или ограничениями? Спасибо.
Облако
1
Я считаю, что dlopen предназначен для плагинов или аналогичных функций. Разрешения / ограничения должны быть такими же, как и для автоматической загрузки, и в любом случае dlopen будет рекурсивно загружать зависимые библиотеки.
Rapadura
DLL и .soявляются не совсем то же самое. Смотрите этот ответ
Василий Старынкевич