Как я могу сослаться на 2 разных DLL с одинаковыми именами?

2

Я занимаюсь разработкой программного обеспечения, которое использует библиотеку изображений Matrox (MIL).
Это программное обеспечение использовало версию 9 MIL в прошлом, теперь мы перешли на v10. Из-за обратной совместимости мы должны продолжать поддерживать v9.

Есть некоторые трудности при работе с MIL и его DLL:

  1. MIL 9 и MIL 10 не могут быть установлены одновременно. Это также не имеет никакого смысла.
  2. Библиотеки С# MIL 9 и MIL 10 называются Matrox.MatroxImagingLibrary.dll.
  3. Пространства имен в обеих DLL идентичны.

Хотя это весьма полезно для взаимозаменяемости (несмотря на то, что некоторые функции изменились), это большая проблема для параллельного использования.
Я не мог ссылаться на обе библиотеки DLL в одной сборке из-за идентичного имени файла и пространства имен, поэтому я создал одну сборку для каждой,
imaging-system_mil9 и
imaging-system_mil10.
Это необходимо, но пока совершенно бесполезно. Мне нужен был базовый класс в общей сборке, чтобы я мог использовать наследование, поэтому я создал сборку
imaging-system.
В этой сборке я добавил свои собственные оболочки для команд MIL. Это казалось довольно хорошим решением и работало очень хорошо, когда я изначально разрабатывал и тестировал на своем компьютере для разработки, где установлен MIL 9. Когда я перешел на другой компьютер и разработал и протестировал MIL 10, я обнаружил, что некоторые команды в моей оболочке необходимо адаптировать, потому что они изменились в MIL 10 С# DLL. Все идет нормально.

Сегодня я вернулся на свой компьютер MIL 9 и хотел проверить больше вещей, но моя тестовая программа не запустилась, сказав MissingMethodException. После некоторого поиска я обнаружил, что совершенно забыл найти решение для одной точки: идентичные имена файлов:
Моя тестовая программа ссылается на imaging-system, imaging-system_mil9 и imaging-system_mil10. Последние два ссылаются на файл Matrox.MatroxImagingLibrary.dll, поэтому вывод в папке bin выглядит так:

test.exe
imaging-system.dll
imaging-system_mil9.dll
imaging-system_mil10.dll
Matrox.MatroxImagingLibrary.dll (the one from MIL 9)
Matrox.MatroxImagingLibrary.dll (the one from MIL 10)

Как видите, последние два файла имеют одинаковое имя, поэтому в основном это похоже на лотерею, один из которых перезаписывается другим.

Первой идеей, с которой мне пришлось решить эту проблему, было переименование файлов в
Matrox.MatroxImagingLibrary9.dll и
Matrox.MatroxImagingLibrary10.dll.
Это работает на первом уровне компиляции, когда
imaging-system_mil9.dll и
imaging-system_mil10.dll
компилируются, потому что они напрямую ссылаются на соответствующие файлы. Вывод в одну из папок bin:

imaging-system_mil10.dll
Matrox.MatroxImagingLibrary10.dll

Но происходит сбой на следующем уровне, когда сборка компилируется, которая не ссылается на библиотеки Matrox напрямую. Компилятор просто пропускает переименованные файлы, скорее всего потому, что имя сборки больше не совпадает с именем файла. Папка bin здесь:

test.exe
imaging-system.dll
imaging-system_mil9.dll
imaging-system_mil10.dll
missing: Matrox.MatroxImagingLibrary9.dll, Matrox.MatroxImagingLibrary10.dll

Кроме того, копирование переименованных файлов вручную в выходную папку EXE также не помогает, потому что EXE не "видит" их. Это имеет смысл: представьте, что существует 1000 DLL, и ни одна не названа так, как сборка, которую ищет программа. Как это должно найти это? Он не может загрузить все 1000 DLL... Таким образом, имя файла должно совпадать с именем сборки.

Следующая идея, которая у меня есть, - установить CopyLocal = false для библиотек Matrox и скопировать их отдельно с помощью события после сборки в dll\mil9 соответственно. dll\mil10.
Каждая сборка будет запускать сценарий PowerShell перед сборкой или после сборки, который копирует весь контент из всех подпапок dll всех указанных библиотек DLL.
Каждый EXE файл получит адаптированный файл app.config как описано в app.config Как сохранить библиотеки DLL в другой папке при компиляции в Visual Studio? ,

Проблема: я не делал этого раньше, потому что в этом не было необходимости. Таким образом, в настоящее время я сталкиваюсь с несколькими вопросами:
1) Сможет ли EXE найти правильную DLL-библиотеку Matrox, потому что она видит их обе при поиске во вложенных папках? Библиотеки DLL имеют одинаковое имя, одинаковую культуру и одинаковый publicKeyToken, но разные номера версий, поэтому их можно отличить друг от друга.
2) Как я могу получить список ссылочных путей к DLL во время сборки для подачи в мой скрипт PowerShell, который ищет подпапки dll и копирует файлы? Единственный способ, который приходит мне в голову - это чтение файла csproj.


То, что я до сих пор тестировал на # 1:
Я уже провел несколько тестов с тестовым решением, содержащим консольный EXE и 2 DLL, которые повторяют ситуацию. Я использовал "CopyLocal = false" и "SpecificVersion = true", я пробовал <probing> и codebase> в файле app.config, но он работает только с одной из DLL:

Структура тестовой папки:

test.exe
dll\testDLL9.DLL
dll\testDLL10.DLL
mil9-x64\mil.net\Matrox.MatroxImagingLibrary.dll
mil10-x64\mil.net\Matrox.MatroxImagingLibrary.dll

Тестовый EXE:

private static void Main ()
{
  Mil10 ();  // when stepping into this, dll\testDLL10.dll is loaded
  Mil9 ();   // when stepping into this, dll\testDLL9.dll is loaded
}

private static void Mil10 ()  // when arriving here, dll\testDLL10.dll has been loaded
{
  testDLL10.CDLL10.Work ();   // when stepping into this, mil10-x64\Mil.net\Matrox.MatroxImagingLibrary.dll is loaded
}

private static void Mil9 ()  // when arriving here, dll\testDLL9.dll has been loaded
{
  testDLL9.CDLL9.Work ();   // when stepping into this, MissingMethodException is thrown, which is correct, because the EXE uses the already loaded DLL, which is the wrong one.
}

Теперь, когда Mil9() вызывается первым, он также загружает
mil10-x64\Mil.net\Matrox.MatroxImagingLibrary.dll
когда testDLL9.CDLL9.Work(), что, очевидно, совершенно неверно. Почему это происходит?
Это работает только тогда, когда я testDLL10 ссылку на testDLL10 и закомментирую связанные функции.

app.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
  </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="dll" />

      <dependentAssembly>
        <assemblyIdentity name="Matrox.MatroxImagingLibrary"
                          publicKeyToken="5a83d419d44a9d98"
                          culture="neutral" />
        <codeBase version="9.2.1109.1" href="mil9-x64\Mil.net\Matrox.MatroxImagingLibrary.dll" />
      </dependentAssembly>

      <dependentAssembly>
        <assemblyIdentity name="Matrox.MatroxImagingLibrary"
                          publicKeyToken="5a83d419d44a9d98"
                          culture="neutral" />
        <codeBase version="10.30.595.0" href="mil10-x64\Mil.net\Matrox.MatroxImagingLibrary.dll" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

Финальные заметки:

  • DLL не нужно загружать одновременно, поскольку это просто невозможно, поскольку MIL 9 и MIL 10 не могут быть установлены параллельно, см. Выше.
  • Я прочитал ссылки на библиотеки DLL с тем же именем, но до сих пор я не хочу загружать библиотеки DLL вручную, как предложено в шаге 3 ответа. Я бы предпочел, чтобы CLR загружал правильную DLL для меня.
  • Я прочитал Как ссылаться на разные сборки с одинаковым именем? , Я не могу использовать GAC, потому что мне нужно иметь возможность переключаться между различными версиями программного обеспечения, просто меняя папки. Как можно меньше связи с операционной системой.
  • 0
    Как насчет создания 2 идентичных классов-оболочек, основанных на одном и том же интерфейсе, где v9 имеет некоторые функции, которые генерируют исключение NotImplementedException, а v10 имеет все реализованные функции. Затем вы создаете правильный экземпляр во время выполнения (который загрузит правильную базовую DLL).
  • 0
    @Neil: Это именно то, что я сделал, возможно, я не описал достаточно подробно. Моя проблема просто загружает правильную DLL.
Теги:
dll

1 ответ

0
Лучший ответ

Сможет ли EXE найти правильную библиотеку Matrox [...]?

ДА

Нет, если используется <probing>, потому что это только добавляет определенные папки в список папок, которые проверяются при поиске в ссылочных сборках. Если файл с запрошенным именем найден, этот файл используется. Проверка версии не производится. Если это правильный файл, он работает. Если это неправильный файл, это терпит неудачу. И если файл с запрошенным именем был загружен, он будет повторно использован позже, независимо от того, соответствует ли версия запрошенной версии.
Да, если используется <codebase>, потому что это включает проверку версии.

После многих тестов и дальнейшего чтения в интернете я обнаружил причину проблемы в том, что был загружен неправильный файл:
В MS Doc "Как среда выполнения находит сборки" говорится в главе "Шаг 1: Проверка файлов конфигурации":

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

Поэтому я подумал: "Хорошо, давайте посмотрим на манифест, чтобы увидеть, содержит ли он некоторые полезные данные". Я открыл его и не нашел... ничего. Поэтому я проверил другие файлы в выходной папке, и testAPP.exe.config привлек мое внимание. До этого я думал, что это была простая копия созданного мной app.config, но, к удивлению, помимо моего контента, он содержал еще один очень важный блок, который сразу привлек мое внимание:

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly>
    <assemblyIdentity name="Matrox.MatroxImagingLibrary" publicKeyToken="5a83d419d44a9d98" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-10.30.595.0" newVersion="10.30.595.0" />
  </dependentAssembly>
</assemblyBinding>

По этой причине моя тестовая программа всегда пыталась загрузить библиотеку v10. Мой следующий вопрос был: Как, черт возьми, это попало туда?
Поэтому я искал "С# компилятор добавляет перенаправление версии сборки" и нашел, как: включить и отключить автоматическое перенаправление привязки, которое говорит:

Автоматическое перенаправление привязки включено по умолчанию для настольных приложений Windows, которые нацелены на .NET Framework 4.5.1 и более поздние версии. Перенаправления привязки добавляются в файл выходной конфигурации (app.config), когда приложение компилируется, и переопределяют объединение сборки, которое в противном случае может иметь место. Исходный файл app.config не изменяется.

В VS 2015 и ниже, файл csproj необходимо редактировать вручную:
Установите для <AutoGenerateBindingRedirects> значение false.
Вы также можете удалить всю запись, но установка на false должна гарантировать, что она не будет автоматически повторно добавлена с true позже.
После этого редактирования выходной файл конфигурации был на 100% идентичен моему исходному файлу (включая любые разрывы строк и пустые строки). И наконец, мой тестовый EXE файл загрузил именно те библиотеки DLL, которые ему нужны, и в правильном порядке в нужное время:

'testAPP.vshost.exe' (CLR v4.0.30319: testAPP.vshost.exe): Loaded 'D:\Visual Studio Projects\testTKS_vs2015\testAPP\bin\x64\Debug\dll\testDLL9.dll'. Symbols loaded.
'testAPP.vshost.exe' (CLR v4.0.30319: testAPP.vshost.exe): Loaded 'D:\Visual Studio Projects\testTKS_vs2015\testAPP\bin\x64\Debug\mil9-x64\Mil.net\Matrox.MatroxImagingLibrary.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'testAPP.vshost.exe' (CLR v4.0.30319: testAPP.vshost.exe): Loaded 'D:\Visual Studio Projects\testTKS_vs2015\testAPP\bin\x64\Debug\dll\testDLL10.dll'. Symbols loaded.
'testAPP.vshost.exe' (CLR v4.0.30319: testAPP.vshost.exe): Loaded 'D:\Visual Studio Projects\testTKS_vs2015\testAPP\bin\x64\Debug\mil10-x64\Mil.net\Matrox.MatroxImagingLibrary.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.

ДА УЖ!! :-)

Единственная оставленная проблема - это предупреждение компилятора:

1>------ Build started: Project: testAPP, Configuration: Debug x64 ------
1>  No way to resolve conflict between "Matrox.MatroxImagingLibrary, Version=10.30.595.0, Culture=neutral, PublicKeyToken=5a83d419d44a9d98" and "Matrox.MatroxImagingLibrary, Version=9.2.1109.1, Culture=neutral, PublicKeyToken=5a83d419d44a9d98". Choosing "Matrox.MatroxImagingLibrary, Version=10.30.595.0, Culture=neutral, PublicKeyToken=5a83d419d44a9d98" arbitrarily.
1>  Consider app.config remapping of assembly "Matrox.MatroxImagingLibrary, Culture=neutral, PublicKeyToken=5a83d419d44a9d98" from Version "9.2.1109.1" [] to Version "10.30.595.0" [] to solve conflict and get rid of warning.
1>C:\Program Files (x86)\MSBuild\14.0\bin\Microsoft.Common.CurrentVersion.targets(1820,5): warning MSB3276: Found conflicts between different versions of the same dependent assembly. Please set the "AutoGenerateBindingRedirects" property to true in the project file. For more information, see http://go.microsoft.com/fwlink/?LinkId=294190.
1>  testAPP -> D:\Visual Studio Projects\testTKS_vs2015\testAPP\bin\x64\Debug\testAPP.exe
========== Build: 1 succeeded, 0 failed, 2 up-to-date, 0 skipped ==========

Может быть, я могу как-то отключить это.


Как я могу получить список ссылочных путей к DLL во время сборки для подачи в мой скрипт PowerShell, который ищет подпапки dll и копирует файлы?

Скорее всего, я не могу.

Я собираюсь написать небольшую программу на С#, которая выполнит эту работу: она возьмет имя файла проекта, прочитает его и найдет все ссылки на проекты и ссылки на основе файлов. Затем он будет искать подпапку dll в папках этих ссылочных проектов и файлов и копировать содержимое в локальную подпапку dll.


Финальные заметки:

  • Во время моих тестов я успешно ссылался на мои слабые сборки, используя <codebase>:

Сборка не имеет publicKeyToken, поэтому я просто оставил ее:

  <!-- probing privatePath="dll" /-->
  <dependentAssembly>
    <assemblyIdentity name="testDLL9" culture="neutral" />
    <codeBase version="1.0.0.0" href="dll\testDLL9.dll" />
  </dependentAssembly>
  <dependentAssembly>
    <assemblyIdentity name="testDLL10" culture="neutral" />
    <codeBase version="1.0.0.0" href="dll\testDLL10.dll" />
  </dependentAssembly>
  • 0
    Спасибо чувак! Я все еще боролся с этой проблемой (я автор вашего связанного вопроса), и мне удалось решить ее с помощью предоставленного вами кода!

Ещё вопросы

Сообщество Overcoder
Наверх
Меню