Работа с файлами манифеста Visual Studios C ++

Я написал код, который использует библиотеку с открытым исходным кодом для выполнения тяжелой работы. Эта работа была проделана в Linux с использованием модульных тестов и cmake, которые помогли перенести его на Windows. Требуется, чтобы он работал на обеих платформах.

Мне нравится Linux, и мне нравится cmake, и мне нравится, что я могу автоматически генерировать файлы визуальных студий. Как и сейчас, в Windows все будет скомпилировано, свяжется и сгенерирует тестовые исполняемые файлы.

Однако, чтобы добраться до этого момента, мне пришлось несколько дней бороться с Windows, изучая все о файлах манифеста и распространяемых пакетах.

Насколько я понимаю:

В VS 2005 Microsoft создала библиотеки DLL Side By Side. Причина этого в том, что раньше несколько приложений устанавливали разные версии одной и той же dll, что приводило к сбою ранее установленных и работающих приложений (например, «Dll Hell»). Библиотеки Side by Side исправляют это, поскольку теперь к каждому исполняемому файлу / dll добавлен «файл манифеста», в котором указывается, какая версия должна быть выполнена.

Это все хорошо. Приложения больше не должны вылетать из-за загадочного сбоя. Тем не мение...

Microsoft, кажется, выпускает новый набор системных dll с каждым выпуском Visual Studios. Кроме того, как я упоминал ранее, я разработчик, пытающийся установить ссылку на стороннюю библиотеку. Часто эти вещи распространяются как «предварительно скомпилированные dll». Теперь, что происходит, когда предварительно скомпилированная dll, скомпилированная с одной версией визуальных студий, связана с приложением, использующим другую версию визуальных студий?

Судя по тому, что я читал в Интернете, случаются неприятности. К счастью, я никогда не заходил так далеко - я продолжал сталкиваться с проблемой «MSVCR80.dll не найден» при запуске исполняемого файла и, таким образом, начал свой набег на всю эту проблему манифеста.

В конце концов я пришел к выводу, что единственный способ заставить это работать (помимо статической компоновки всего) - это то, что все сторонние библиотеки должны быть скомпилированы с использованием одной и той же версии Visual Studios, то есть не использовать предварительно скомпилированные библиотеки DLL - загрузить исходный код, создайте новую dll и используйте ее вместо нее.

Это правда? Я что-то пропустил?

Более того, если это так, то я не могу не думать, что Microsoft сделала это специально по гнусным причинам.

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

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

Наконец, подведем итоги: можно ли использовать предварительно скомпилированные двоичные файлы с этой новой схемой манифеста? Если да, то в чем была моя ошибка? Если это не так, действительно ли Microsoft считает, что это упрощает разработку приложений?

Обновление - более краткий вопрос: Как Linux избегает использования файлов манифеста?


person Voltaire    schedule 26.02.2009    source источник
comment
Я не понимаю, почему этот вопрос был отклонен ... кажется, он продуман ... и в то время как Microsoft немного застенчива от разочарования, уравновешена и готова рассуждать.   -  person Beska    schedule 26.02.2009
comment
Я предполагаю, что это много текста для чтения! Если бы вопрос был более кратким, ему, возможно, повезло больше.   -  person Gavin Miller    schedule 26.02.2009
comment
Когда в заголовке вопроса задается вопрос, пытается ли x быть злым? не удивляйтесь, когда люди проголосуют против. :)   -  person bk1e    schedule 26.02.2009
comment
Полагаю, это правда. Если поразмыслить над этим больше, это будет довольно круговой путь быть злым. Никогда не относитесь к злому умыслу .... Более краткий вопрос: как Linux избегает нужды в файлах манифеста.   -  person Voltaire    schedule 27.02.2009
comment
Windows также никогда не нуждалась в манифестах до VS2003. Они - уловка, чтобы обойти ад dll, в основном, вводя другую проблему - распространение dll :)   -  person gbjbaanb    schedule 03.03.2009


Ответы (4)


Все компоненты в вашем приложении должны совместно использовать одну и ту же среду выполнения. Когда это не так, вы сталкиваетесь со странными проблемами, такими как утверждение операторов удаления.

Это то же самое на всех платформах. Это не то, что изобрела Microsoft.

Вы можете обойти эту проблему «только одной среды выполнения», зная, где среды выполнения могут «укусить». Обычно это происходит в тех случаях, когда вы выделяете память в одном модуле и освобождаете ее в другом.

a.dll
    dllexport void* createBla() { return malloc( 100 ); }

b.dll
    void consumeBla() { void* p = createBla(); free( p ); }

Когда a.dll и b.dll связаны с разными временами, происходит сбой, потому что функции времени выполнения реализуют свою собственную кучу.

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

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

Для справки:

  • не выделять / освобождать память / объекты через границы модуля
  • не используйте сложные объекты в вашем интерфейсе dll. (например, std :: string, ...)
  • не используйте сложные механизмы C ++ через границы dll. (typeinfo, исключения C ++, ...)
  • ...

Но это не проблема манифестов.

Манифест содержит информацию о версии среды выполнения, используемой модулем, и внедряется компоновщиком в двоичный файл (exe / dll). Когда приложение загружено и его зависимости должны быть разрешены, загрузчик просматривает информацию манифеста, встроенную в exe-файл, и использует соответствующую версию DLL среды выполнения из папки WinSxS. Вы не можете просто скопировать среду выполнения или другие модули в папку WinSxS. Вам необходимо установить среду выполнения, предлагаемую Microsoft. Microsoft поставляет пакеты MSI, которые можно запустить при установке программного обеспечения на тестовом компьютере или компьютере конечного пользователя.

Поэтому установите среду выполнения перед использованием приложения, и вы не получите ошибку «отсутствует зависимость».


(Обновлено до вопроса «Как Linux избегает использования файлов манифеста»)

Что такое файл манифеста?

Файлы манифеста были введены для размещения информации разрешения неоднозначности рядом с существующей исполняемой / динамической библиотекой или непосредственно встроены в этот файл.

Это делается путем указания конкретной версии dll, которые должны быть загружены при запуске приложения / загрузке зависимостей.

(Есть еще несколько вещей, которые вы можете делать с файлами манифеста, например, здесь могут быть помещены некоторые метаданные)

Почему это делается?

Версия не является частью имени dll по историческим причинам. Таким образом, comctl32.dll назван так во всех его версиях. (Таким образом, comctl32 под Win2k отличается от такового в XP или Vista). Чтобы указать, какая версия вам действительно нужна (и с которой вы проводили тестирование), вы помещаете информацию о версии в файл «appname.exe.manifest» (или вставляете этот файл / информацию).

Почему это было сделано именно так?

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

Этой концепцией злоупотребляли многие программисты, когда устанавливали все свои dll в этот каталог; иногда перезапись более новых версий разделяемых библиотек более старыми. Иногда библиотеки незаметно меняли свое поведение, так что зависимые приложения падали.

Это привело к подходу «Распределить все dll в каталог приложения».

Почему это было плохо?

Когда появлялись ошибки, приходилось обновлять все dll, разбросанные по нескольким директориям. (gdiplus.dll) В других случаях это было даже невозможно (компоненты Windows)

Явный подход

Такой подход решает все вышеперечисленные проблемы. Вы можете установить dll в центральное место, где программист не может вмешиваться. Здесь dll могут быть обновлены (путем обновления dll в папке WinSxS), а загрузчик загружает «правильную» dll. (сопоставление версий выполняется dll-загрузчиком).

Почему в Linux нет этого механизма?

У меня есть несколько догадок. (Это действительно просто предположение ...)

  • Большинство вещей имеют открытый исходный код, поэтому перекомпиляция для исправления ошибок не является проблемой для целевой аудитории.
  • Поскольку существует только одна «среда выполнения» (среда выполнения gcc), проблема с разделением среды выполнения / границами библиотеки возникает не так часто.
  • Многие компоненты используют C на уровне интерфейса, где этих проблем просто не возникает, если все сделано правильно.
  • Версия библиотек в большинстве случаев встроена в имя ее файла.
  • Большинство приложений статически привязаны к своим библиотекам, поэтому не может возникнуть никаких проблем с dll.
  • Среда выполнения GCC поддерживалась стабильной ABI, чтобы эти проблемы не могли возникнуть.
person Christopher    schedule 26.02.2009
comment
Что вы имеете в виду, что они должны использовать одну и ту же среду выполнения? Я полагаю, что в Linux все общие объекты должны ссылаться на одну и ту же стандартную библиотеку c lib, и я понятия не имею, что бы произошло, если бы они этого не сделали. Однако файлы-манифесты - это целая мелочь, которая кажется в некоторой степени ненужной. Я желаю комментировать дольше - person Voltaire; 27.02.2009
comment
«разные среды выполнения» обычно означают отладку или выпуск. Распределители памяти разные, поэтому, если выделить в a.dll, передать память в b.dll приведет к сбою вашего приложения, если они разные. Решение - всегда выделять и освобождать память в одной и той же dll. Тогда у вас нет проблем. - person gbjbaanb; 03.03.2009
comment
(Почему в Linux нет этого механизма?) Linux использует ELF, в котором хранится информация о версии. Если вы используете версию библиотеки 2.2, ваша программа работает с 2.3, но просто выходит с 2.1 или 3.0. Все используют совместимую среду выполнения C (glibc / eglibc) только с обратно совместимыми изменениями с 2002 года. Статическое связывание крайне редко. Поставщики библиотек осторожно относятся к изменениям, и ад DLL решают разработчики дистрибутива, а не системные администраторы. Изменение C ++ ABI было проблемой, но все кончено. Когда появится GTK 3.0, он будет установлен вместе с GTK 2.0, поэтому приложения для обоих будут работать нормально. - person Dietrich Epp; 27.01.2012

Если сторонняя DLL будет выделять память и вам нужно ее освободить, вам понадобятся те же библиотеки времени выполнения. Если в DLL есть функции выделения и освобождения, все в порядке.

Если сторонняя DLL использует std контейнеры, такие как vector и т. Д., Могут возникнуть проблемы, поскольку расположение объектов может быть совершенно другим.

Можно заставить что-то работать, но есть некоторые ограничения. Я столкнулся с обеими перечисленными выше проблемами.

person crashmstr    schedule 26.02.2009
comment
Почему использование контейнеров std вызывает разную компоновку памяти? - person baye; 10.03.2009
comment
Если интерфейс кода использует stl, тогда да! Контейнеры std VC6 кодируются иначе, чем std VS2008, которые оба отличаются от STLPort. Скомпилированный код dll имеет одно определение, но ваш код, который включает их заголовки, затем компилирует этот код по-другому. Поверьте, я это видел. - person crashmstr; 10.03.2009

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

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

person Peter Jeffery    schedule 26.02.2009

В конце концов я пришел к выводу, что единственный способ заставить это работать (помимо статической компоновки всего) - это то, что все сторонние библиотеки должны быть скомпилированы с использованием одной и той же версии Visual Studios, то есть не использовать предварительно скомпилированные библиотеки DLL - загрузить исходный код, создайте новую dll и используйте ее вместо нее.

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

...я так понимаю. :)

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

person dash-tom-bang    schedule 26.02.2009
comment
Почему статическое связывание безопасно? Предположим, что сторонняя DLL статически связана с vc8, а ваша DLL статически связана с vc9, а затем вы пытаетесь удалить объект в своей DLL, который был создан в библиотеке 3rd patry, что произойдет? - person baye; 10.03.2009
comment
@lzprgmr: Это не так, и - ка-бламмо! - person Spike0xff; 02.09.2011
comment
Статическая компоновка в этом случае безопасна, потому что вы знаете, каковы зависимости всех библиотек, которые вы связываете. Другими словами, вы не стали бы пытаться связать вместе две библиотеки, которые зависят от разных сред выполнения. - person dash-tom-bang; 05.09.2011