Как стать автором
Обновить

Комментарии 19

К сожалению, часто бывает, что сторонний продукт bar линкуется с version.so, которая ссылается на version.<latest>.<latest>.so.

Теперь, вы не можете просто взять и поставить новый version, потому что ссылка version.so перетрется при установке на последнюю версию и bar перестанет работать.

Особенно весело будет, если вы хотите использовать сторонние библиотеки foo и bar, каждая из которых линкуется с version.so, но обе подразумевают разные версии этой самой version.so. Вот тут-то и начинаются проблемы.
+1. Этим вообще грешат многие: как сами разработчики либ, так и создатели различных ОС с ядром Линукс и другие.

С Вашей подачи забью камень в огород гугла:
$ objdump -x chrome|grep NEEDED|grep nss3
  NEEDED               libnss3.so
$ ./chrome
./chrome: /usr/lib64/seamonkey/libnss3.so: version 'NSS_3.14.3' not found (required by ./chrome)
$ objdump -x /usr/lib64/seamonkey/libnss3.so |grep SONAME
  SONAME               libnss3.so

У меня slackware64-14.0. Я же просто скачал хром с сайта гугла (google-chrome-stable_current_amd64.deb), т.к. хромиум мне ой как не хотелось компилировать. Как видите, меня ждал сюрприз. Решить его я могу только с помощью LD_LIBRARY_PATH, устанавливая первыми пути с нужными библиотеками, т.к. R(UN)PATH тоже не был предусмотрен (в противном случае я бы просто прокинул нужные ссылки в заданное место):
$ objdump -x chrome|egrep 'R(\|UN)PATH'
$ 


Ссылки вида libX.so → libX.x.y.z.so – это вообще анахронизм, т.к. нужны только компоновщику во время связывания, чтобы явно не писать имя файла библиотеки, а просто указать -lx. Новый gcc & ld умеет прямо компоновать с нужным файлом: -l:x.so.3.2.1. Разве что на Маке я не нашёл такой возможности. (Дурацкий префикс 'lib' тоже не нужен).

Это – пол беды. Задавать SONAME тоже никто не хочет (посмотрите выше на nss3 в слакваре): т.е. даже если целевой файл будет скомпонован с нужной версией либы, в его зависимостях будет просто libx.so (см. выше вывод для хрома).

Ещё хочу упомянуть про pkgconfig. Для nss3 на моей системе файлик выглядит так:
prefix=/usr
exec_prefix=${prefix}
libdir=/usr/lib64
includedir=${prefix}/include/nss

Name: NSS
Description: Network Security Services
Version: 3.13.5
Requires: nspr >= 4.9.1 sqlite3
Libs: -L${libdir} -lnss3 -lsmime3 -lssl3 -lsoftokn3  -lnssutil3
Cflags: -I${includedir}

а называется он nss.pc. Если кто-то воспользуется услугами pkgconfig для nss3, то с какой версией ssl3, smime3, softokn3, nssutil3 будут скомпонованы библиотеки?
Вы написали гневную тираду на совершенно другую тему (впрочем близкую). Описанные вами проблемы — это просто-напросто отсуствие у Linux'а полноценного SDK.

Какая-нибудь MSVCRT.DLL или USER.DLL в Windows устроены ровно так же, как libnss3.so в Linux. И если вы соберёте вашу программу так, что она будет вызвывать из этих библиотек функции, которых нет в Windows XP (или в Windows 7), то чёрта с два вы такую программу на Windows XP (или Windows 7) запустите.

Но в Windows этому уделяется особое внимание, людям предоставлена возможность собрать с помощью MSVC 2013 программы, которые будут запускаться на Windows XP. Ни о чём таком в Linux никто даже не думает, увы. Ну за исключением разработчиков LSB, но если вы посмотрите на оный LSB, то обнаружите, что там кучи библиотек, нужных для разработки программ типа Chrome нету, а о сколько-нибудь полноценной поддержке C++11 даже и мечтать не приходится.
Вы не зря тут упомянули про C++. Но если уж упомянули, то стоит сказать и как с ним-то быть. А то можно подумать, что все на чистом C работают. У людей, работающих с C++ использование version script'а не прокатит из-за шаблонов (они описаны в заголовочных файлах и если liba использовала при сборке version-1.0.h, а libb — version-1.1.h, то это значит что у них внутри сгенерировались одинаковые символы для каких-ниюудь std::list<version::Window>::size()).

Для решения этой проблемы следует использовать inline namespace. Если приходится разрабатывать проект без поддержки C++11, то к вашим услугам (на MacOS и Linux) strong using появившийся в GCC 3.4 (не 4.4, а именно 3.4) — собственно он явился прототипом для inline namespace.
Вы задали очень хороший вопрос. Я сейчас пробежался по статье и, действительно, как-то упустил из виду Си++.

Я думаю, что можно. Попробую аргументировать свою позицию.

Есть несколько вариантов:
1) version экспортирует только шаблоны, т.е. никаких сгенерированных классов наружу не торчит;
2) version экспортирует шаблоны, а также некоторые классы, сгенерированные с помощью этих шаблонов.

Сначала разберу второй случай. Проблема здесь заключается в том, что когда liba включит version-1.0.h
// ...
typedef version::list<version::window> windows_list;
// ...

к себе в исходный текст, то дальнейшее развитие событий зависит от того, как оформлен шаблон version::list. Если он – хедер-онли, то всё плохо:
1) во-первых, в самой version.dll будет присутствовать сгенерировнный класс version::list<version::window>;
2) когда будет компилироваться сама либа а, то в той единице трансляции, в которую был включен version.h, также будет сгенерирован класс version::list<version::window>. Он будет скомпилирован, в лучшем случае бинарно совместим с классом в version.dll, и все ссылки на него будут не undefined (что заставило бы загрузчик искать эти символы во время загрузки), а определены и указывать на сущности внутри а (в терминах nm – T либо t).

Даже если класс version::list<version::window> и был версионирован в version.dll, он окажется в непонятном состоянии в liba. (Тут, кстати, стоит отметить, что именно по этой причине некоторые люди используют AddRef/Release семантику для экспортируемых классов, а также, не экспортируют stl-классы: нагенерреные классы внутри самой либе и её пользователе отличаются бинарно даже в случае Debug/Release )) ).

Чтобы решить эту проблему для C++03 и выше, шаблон надо разделить на объявление и определение: version_list.h, version_list_impl.h. Внутри самой либы в файле реализации version_windows_list.cpp будет примерно так:
#include "version_windows_list.hpp"

// подключаем тело шаблона
#include "version_list_impl.h"

// явно инстанцируем шаблон
template class version::list<version::window>;
случайно отправил комментарий.

Теперь, при сборке самой version.dll будет использован version.script для символов vesion::list<version::window> внутри самой библиотеки. liba и другие при компиляции не получат тело шаблона version::list, что заставит компоновщик требовать его при связывании. Таким образом, все получат в зависимостях версионированный шаблонный класс. Эта техника разделения шаблона описана тут: http://www.parashift.com/c++-faq/templates-defn-vs-decl.html.

Начиная с Си++11 можно заставить компилятор не инстанцировать шаблон даже для хедер-онли шаблонов с помощью extern. Например, для std::unique_ptr:
// MyTypePtr.hpp
#include <memory>

namespace mylib
{

class MyType;

} // namespace mylib

extern template class std::unique_ptr<mylib::MyType>;

namespace mylib
{

using MyTypePtr = std::unique_ptr<mylib::MyType>;

} // namespace mylib


// MyTypePtr.cpp

#include "MyTypePtr.hpp"

#include "MyType.hpp"

template class std::unique_ptr<mylib::MyType>;


Такая техника позволит экспортировать даже классы, сгенеренные из хедер-онли шаблонов, и применить для этих классов version.script. Стоит отметить, что extern уже давно использовался как расширение Си++ в msvc: http://support.microsoft.com/kb/168958/en-us.

К сожалению, с помощью extern не получится экспортировать контейнер перемещаемых объектов: http://stackoverflow.com/questions/22381685/extern-template-class-stdcontainer-of-movable-objects.

Теперь вернёмся к первому случаю, когда экспортируются только шаблоны. Как видим, эти шаблоны должны быть грамотно оформлены )). С точки зрения version, это проблема других, как они используют эти шаблоны. Если высовывают их наружу – это первый случай, только с позиции уже другой либы. Если используют для внутренних нужд – то с помощью того же version.script они должны быть помечены как local.
Теперь, при сборке самой version.dll будет использован version.script для символов vesion::list<version::window> внутри самой библиотеки. liba и другие при компиляции не получат тело шаблона version::list, что заставит компоновщик требовать его при связывании.
Флаг вам в руки и барабан на шею. Поробуйте проделать это с Boost'ом или каким-нибудь Eigen'ом, потом возвращайтесь расскажете про результаты.

С точки зрения version, это проблема других, как они используют эти шаблоны.
Почему тогда не считать что проблемы версионированных символов, SONAME и прочего — это тоже «проблема других»? Наши тестовые программы работают — и ладно. Пусть всё через dlopen грузят, им жалко, что ли?
Даже если класс version::list<version::window> и был версионирован в version.dll, он окажется в непонятном состоянии в liba.
Вот собственно тут вы и ошибаетесь. Если version::list<version::window> был версионирован в version.dll с использованием inline namespace<code> (или <code>string using), то в liba он тоже будет версионирован.
Насколько я знаю, в Windows проблема разных библиотек но одного символа — вообще не стоит.

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

Далее, чтобы избежать версионирования имен dll — как раз таки можно использовать SxS, он для этого и предназначен.
Для этого пишется manifest, который либо складывается в секцию .rsrc у dll/exe, либо же кладется рядом с приложением/dll в виде файла ${имя модуля включая суффикс exe/dll}.manifest
В этом манифесте прописываются зависимости.

Т.е. все намного проще и удобнее, чем в других ОС, на самом деле.
Разумеется проблема не стоит. Вы даже выделить объект в одной библиотеке, а потом удалить в другой не можете, какие уж тут шаблоны.

Но это как бы… лечение насморка с помощью гильотины.
Вы даже выделить объект в одной библиотеке, а потом удалить в другой не можете, какие уж тут шаблоны.


Выделять память нужно стандартным HeapAlloc(а потом делать placement new), и/или же предоставлять функции для удаления объекта из библиотеки. Т.е. эта проблема надуманная, т.к. создавать в одной библиотеке объект а потом удалять его как попало — это дурной тон и ССЗБ.

Во-вторых, специально для C++, который может быть сам с собой не совместим даже в случае разных минорных версий компилятора — в MS придумали COM, в котором и ABI, и менеджмент памяти стандартизированный, и т.д.
Выделение памяти в одной библиотеке, а удаление в другой возникает совершенно естественным путём при использовании шаблонов. Которое не менее естественно возникает при использовании стандартной библиотеки C++. Какой-нибудь простейший возврат std::string'а из функции уже приводит к этому эффекту.

Все ваши рассказы про HeapAlloc, COM и прочее — это как раз и есть описание той самой гильотины. Это уже не нормальный C++-интерфейс библиотеки (как он описан в стандартах языка C++), а некоторое жалкое подобие поверх Microsoft'овских технологий.

Да, это решает проблему разных версий библиотек — но какой ценой?
Это проблемы C++ — нет ABI, нет стандартизированного name mangling, и т.п.

Т.к. мы, простые смертные, не влияем на принятие стандарта C++, то мы используем к примеру COM. Моя позиция по этому вопросу такая — COM конечно не самое удобное в мире изобретение, особенно если смотреть из C++, но оно, в отличие от стандартного C++ — изобретение работающее, а это решающий фактор, уж извините.
А вот с этого момента: поподробнее. С чего вы решили, что если у вас нет стандартного C++, то его и ни у кого нет?

В MacOS и в Linux'е, к примеру, он есть. c++filt входит в состав системы, стандартный ABI вполне себе существует и всё, в общем, вполне себе работает. Есть, конечно, проблемы свяанные с тем, что программисты соответствующие инструменты неправильно используют, ну так от этого и COM не защищает.

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

С этим согласен полностью.

К manifest«ам это не относится. Пока писал статью, я понял, что в Windows изначально был механизм (который Вы описали и который является two-level namespace по сути), чтобы не допустить dependency hell: мы бы имели в папке system32, к примеру, два файла comctl32-5.0.2.dll и comctl32-6.3.1.dll. Почему инженеры из Microsoft не сделали этого изначально? Я не знаю.

Лично мне механизм с манифестами кажется избыточным.
Манифесты на самом деле досточно удобны. Мне кажется, лучше пусть будет куча разных dll в специальном кеше и одна псевдо-dll в условном system32 или %programfiles%/%App%, чем та же куча в разных местах и с разными именами.
Кроме версий dll, они могут версионировать COM-компоненты.
Могут указывать где искать компоненты/dll, и т.п.

Ну и вообще, формат расширяемый, т.к. xml, и поэтому через них можно много чем управлять. Например, уровнями integrity(aka MAC, aka UAC).

Не отрицаю, что с первого взгляда они кажутся избыточными и переусложненными. Но, манифесты действительно решают проблемы, которые на других системах не решены никак.
Если манифеста нет как определять какая мне версия нужна, самая новая по тому что в ней больше всего реализовано, или самая старая раз я настолько динозавр что даже манифеста нет? OS Винда как настоящий партизан отдает мне то что считает совместимым с моим экзешником да ещё и окружение подменяет так что я даже не всегда адекватно могу узнать что мне подсунули! Как тот же юак распознавать если тебя под 98SE написали? А если как сисадмин считаю что в системеме должна быть одна и только одна версия библиотеки по тому что в ней мой патч/у старых версий проблемы с безопасностью/мало памяти? А если я как сисадмин хочу чтобы библиотека физически находилась не в том месте где это решил разработчик? А если у меня наоборот куча старого и не очень софта ещё без манифестов, но уже использующие несовместимые версии либы? Лично меня линуксовое решение куда больше устраивает.
НЛО прилетело и опубликовало эту надпись здесь
А по основной теме добавлю, считаю что ставить в систему какую-либо библиотеку вместе со своим софтом имеет право только разраб библиотеки, предварительно приняв ряд мер по обеспечению совместимости, в край мантейнер дистра. Используешь в проекте что-то чего нет у 99% пользователей — клади свои версии либы себе под нос, желательно при установке в лог маякни, что мол в системе несовместимая со мной версия, глядишь админ что-то да придумает чтоб и пол оси не разносить и в памяти 10 версий того-же QT-а с вебкиттом не висело. В том же линуксе никто не мешает определить в зависмостях паккета ту либо что должна быть в системе, а все чего дистром не предусмотрено складывать к себе /опт/шаре. Если же вы сами пишите действительно разделяемую либу, которой для функционирования продакшн версии вашего же пепелаца нужно сразу несколько версий… ну пфффф.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории