Выбор платформозависимой библиотеки в runtime

в 11:16, , рубрики: .net, msbuild, x64, x86, конфигурация, Платформа, метки: , , , , ,
Суть вопроса

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

Так обычно и происходит до тех пор, пока нам не понадобится использовать внешние платформозависимые библиотеки, например неуправляемые. Если такая библиотека существует в вариантах и для x86, и для x64, то это может принести нам определенную головную боль. Будем исходить из того, что ограничивать наше приложение, например, только 32-хразрядным процессом не в наших правилах.

Возможно, нам придется поддерживать вдвое больше конфигураций проекта. В этом случае при отладке придется переключать конфигурации, ведь разработческий веб-сервер Cassini существует только в x86 варианте, а ReSharper может запускать тесты и в 64-хразрядном процессе. Кроме того, придется выпускать два дистрибутива и предоставлять пользователю при скачивании с сайта ох какой нелегкий выбор. Поэтому разумным решением выглядит выбор подходящей для работы библиотеки уже в runtime в зависимости от того, в каком процессе (32-х или 64-хразрядном) код выполняется. При этом сами проекты остаются AnyCPU.

В нашем приложении необходимо подключаться к к Oracle Database, для чего используются библиотеки Oracle Instant Client и Oracle Data Provider for .NET.

Решение

Решение было найдено в виде тега runtime/assemblyBinding конфигурационного файла приложения. В app.config добавляем следующее:

<configuration>
    <!-- Выбор версии библиотеки ODP.NET в зависимости от Runtime архитектуры -->
    <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
        <dependentAssembly>
          <assemblyIdentity name="Oracle.DataAccess"
                            publicKeyToken="89b483f429c47342"
                            culture="neutral"
                            processorArchitecture="x86" />
          <codeBase
             version="4.112.2.0"
             href=".x86Oracle.DataAccess.dll"/>
        </dependentAssembly>
        <dependentAssembly>
          <assemblyIdentity name="Oracle.DataAccess"
                            publicKeyToken="89b483f429c47342"
                            culture="neutral"
                            processorArchitecture="amd64" />
          <codeBase
             version="4.112.2.0"
             href=".x64Oracle.DataAccess.dll"/>
        </dependentAssembly>
      </assemblyBinding>
    </runtime>
</configuration>

У атрибута processorArchitecture четыре возможных значения: x86, amd64, msil, ia64. Пути в codeBase могут отличаться в зависимости от типа проекта (например, для ASP.NET должно быть href=".binx64Oracle.DataAccess.dll").

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

<Import Project="$(MSBuildBinPath)Microsoft.CSharp.targets" />

добавляется включение собственных целей MSBuild:

<Import Project="$(MSBuildProjectDirectory)..CommonItems.targets" />

В файле проекта обычно есть закомментированная цель AfterBuild. Ее необходимо раскомментировать/добавить/отредактировать:

  <Target Name="AfterBuild" DependsOnTargets="CopyDataAccessFiles" >

Файл CommonItems.targets содержит описание этих общих элементов для исполняемых проектов. Здесь определена цель по копированию зависимостей:

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <OracleICFilesx86 
        Include="$(MSBuildProjectDirectory)..externalsOracleICx86*.dll">
      <Visible>False</Visible>
    </OracleICFilesx86>
    <OracleICFilesx64 
        Include="$(MSBuildProjectDirectory)..externalsOracleICx64*.dll">
      <Visible>False</Visible>
    </OracleICFilesx64>
    <OdpNetFilesx86 
        Include="$(MSBuildProjectDirectory)..externalsOdp.Netx86*.dll">
      <Visible>False</Visible>
    </OdpNetFilesx86>
    <OdpNetFilesx64 
        Include="$(MSBuildProjectDirectory)..externalsOdp.Netx64*.dll">
      <Visible>False</Visible>
    </OdpNetFilesx64>
  </ItemGroup>
  <Target Name="CopyDataAccessFiles" >
    <Copy 
        SourceFiles="@(OracleICFilesX86);@(OdpNetFilesx86)"
        DestinationFolder="$(MSBuildProjectDirectory)$(OutputPath)x86" 
        SkipUnchangedFiles="true"
        UseHardLinkIfPossible="true" />
    <Copy 
        SourceFiles="@(OracleICFilesX64);@(OdpNetFilesx64)"
        DestinationFolder="$(MSBuildProjectDirectory)$(OutputPath)x64"
        SkipUnchangedFiles="true"
        UseHardLinkIfPossible="true" />
  </Target>
</Project>

Ограничения

У предложенного способа есть ограничения:

  • Предполагается, что мы не используем типы управляемой платформозависимой библиотеки и, таким образом, не нуждаемся в ссылке на сборку (Oracle.DataAccess.dll) в своем проекте. Т.е. от сборки зависимость есть, но неявная, динамическая.
  • Теперь мы с собой в дистрибутиве «тащим» библиотеки для всех поддерживаемых платформ. Для OracleIC это более 100 МБ на платформу.

Ссылки

<assemblyBinding> Element for <runtime>

Автор: vlio

Источник

Поделиться

* - обязательные к заполнению поля