В «Форсайт. Аналитическая платформа» существует возможность загрузки и использования внешних сборок. Выполняемые сборки загружаются в домен приложения и остаются там до момента завершения всего приложения. Если в методах внешней сборки осуществляется динамическая загрузка других сборок, то эти сборки также попадают в домен текущего приложения и остаются там до его завершения. Из-за этого может возникнуть ситуация, описанная ниже.
Примечание. В .NET Framework для динамической загрузки сборок используются различные методы Load* класса System.Reflection.Assembly. Также динамическая загрузка производится при работе некоторых системных сборок, в частности при сериализации с помощью System.Xml.Serialization.XmlSerializer.
Допустим, разработанная внешняя сборка загружена в .NET-сборку репозитория, обозначим ее как сборка A. После каких-либо доработок внешняя сборка была загружена в другую .NET-сборку репозитория - сборку B. При этом наименования всех типов, реализованных во внешней сборке, и наименование самой сборки остались неизменными. Какой-либо метод внешней сборки осуществляет динамическую загрузку и работу с различными типами другой внешней сборки - сборки Test. При использовании сборок А и B в репозитории произойдет следующее:
При обращении к сборке А она будет загружена в домен приложения. При выполнении метода, который осуществляет динамическую загрузку, в домен приложения будет загружена сборка Test. После завершения использования сборок они останутся в домене приложения.
При обращении к сборке B она также будет загружена в домен текущего приложения. При выполнении метода, который осуществляет динамическую загрузку, будет произведена проверка, не имеется ли в домене уже загруженной сборки Test и при этом будет найдена сборка, загруженная во время работы со сборкой А. Сборки А и B имеют одну идентичность, но для среды выполнения имеют совершенно различные типы.
При работе в сборке B с типами сборки Test будет возникать ошибка о несовместимости типов, так как найденная сборка является производной от сборки А и не подходит.
Данная проблема решаема с помощью своевременной выгрузки сборок из домена приложения, но ввиду особенностей реализации работы со сборками в «Форсайт. Аналитическая платформа», проблема остается открытой. Её решение запланировано на более поздние версии «Форсайт. Аналитическая платформа».
Если динамическая загрузка осуществляется в коде внешней сборки, то рекомендуется отказаться от нее и использовать ссылки на сборку в настройках проекта.
Если во внешней сборке используется сериализатор в XML (XmlSerializer), то используйте путь обхода, описанный ниже.
Для решение проблемы с сериализатором, который во время своей работы динамически загружает в память сборку с сериализуемыми типами, существует следующей путь обхода: необходимо объединить исходную сборку со сборкой сериализации XML, предварительно изменив код для инициализации сериализатора (измененный код смотрите в примере ниже). Это позволит использовать сериализатор, хранящийся в теле сборки, и соответственно инициализирующийся для того экземпляра сборки, с которым будет производиться работа.
Сборка сериализации XML содержит оболочку для инструмента создания XML-сериализатора. Для создания сборки сериализации используется утилита SGen, поставляемая вместе с Microsoft Visual Studio. Команда для создания сборки сериализации: SGen <ИмяСборки>.DLL. После выполнения данной команды в папке, где находится библиотека сборки, будет создана еще одна библиотека - <ИмяСборки>.XmlSerializers.dll. Для объединения двух сборок используйте программу ILMerge (Программа доступна для скачивания на сайте http://www.microsoft.com/). Команда для объединения сборок: ILMerge /t:library /out: <КонечнаяСборка>.dll <ИмяСборки>.dll <ИмяСборки>.XmlSerializers.dll. Полученная объединенная сборка, а также различные ее модификации, могут быть загружены в разные .NET-сборки репозитория и использованы параллельно.
Рассмотрим процесс объединения сборок на следующем примере:
Имеется сборка, разрабатываемая на языке C# в среде Microsoft Visual Studio. Тело сборки включает в себя следующий код:
using System;
using System.Xml;
using System.Xml.Serialization;
namespace TestAssembly
{
[Serializable()]
public class Test
{
//...
//Сериализация объекта в XML
public void ToXML(string path)
{
//...
XmlSerializer s = new XmlSerializer(this.GetType());
//...
}
//...
// Десериализация объекта из XML
public Test FromXml(string path)
{
//...
XmlSerializer s = new XmlSerializer(this.GetType());
//...
}
//...
}
}
Процедура ToXML используется для сериализации в XML текущего экземпляра объекта класса Test. Функция FromXml осуществляет десериализацию объекта из файла, результатом ее работы является объект класса Test. Для возможности использования указанной сборки и нескольких ее модификаций в «Форсайт. Аналитическая платформа» изменим код для инициализации сериалайзера следующим образом:
XmlSerializer s = (XmlSerializer)Assembly.GetExecutingAssembly().CreateInstance("Microsoft.Xml.Serialization.GeneratedAssembly.TestSerializer");
На данном этапе тип "Microsoft.Xml.Serialization.GeneratedAssembly.TestSerializer" еще не существует, он будет доступен после получения сборки сериализации. Скомпилируйте сборку. Для генерирования сборки сериализации перейдите в папку проекта \bin\Debug\ и выполните в командной строке следующую команду:
SGen TestAssembly.dll
Примечание. Путь к утилите SGen должен быть добавлен в системную переменную Path в операционной системе.
При выполнении команды в этой же папке будет создана библиотека TestAssembly.XmlSerializers.dll. Если просмотреть содержимое этой библиотеки, то мы увидим в её составе пространство имен Microsoft.Xml.Serialization.GeneratedAssembly, в котором имеется класс TestSerializer. Именно этот класс и будет использоваться для инициализации сериалайзера.
Для объединения сборок установите программу ILMerge и выполните следующую команду:
ILMerge /t:library /out:TestXAssembly.dll TestAssembly.dll TestAssembly.XmlSerializers.dll
После выполнения команды будет создана объединенная сборка TestXAssembly.dll. Именно эту сборку можно загрузить в разные .NET-сборки репозитория.
Указанные команды для генерации сборки сериализации и объединения сборок могут быть заменены внесением доработок в файл проекта разрабатываемой сборки. В конце файла проекта предусмотрено место для формирования двух разделов <Target Name="BeforeBuild"> и <Target Name="AfterBuild">. В нашем случае создадим раздел <Target Name="AfterBuild"> и укажем в нем следующий код:
<Target Name="AfterBuild" DependsOnTargets="AssignTargetPaths;Compile;ResolveKeySource" Inputs="$(MSBuildAllProjects);@(IntermediateAssembly)" Outputs="$(OutputPath)$(_SGenDllName)">
<!-- Удаление файла сериализации, если он уже был создан -->
<Delete Files="$(TargetDir)$(TargetName).XmlSerializers.dll" ContinueOnError="true" />
<!-- Создание нового файла сериализации -->
<SGen BuildAssemblyName="$(TargetFileName)" BuildAssemblyPath="$(OutputPath)" References="@(ReferencePath)" ShouldGenerateSerializer="true" UseProxyTypes="false" KeyContainer="$(KeyContainerName)" KeyFile="$(KeyOriginatorFile)" DelaySign="$(DelaySign)" ToolPath="$(TargetFrameworkSDKToolsDirectory)" Platform="$(Platform)">
<Output TaskParameter="SerializationAssembly" ItemName="SerializationAssembly" />
</SGen>
<!-- Создание временной папки, куда будет помещена объединенная сборка -->
<MakeDir Directories="$(TargetDir)merged"/>
<Exec Command="ILMerge /t:library /out:$(TargetDir)merged\$(TargetFileName) $(TargetDir)$(TargetFileName) $(TargetDir)$(TargetName).XmlSerializers.dll"/>
<!-- Удаление всех файлов, созданных при компиляции -->
<Exec Command="del $(TargetDir)$(TargetName)*.* /q"/>
<!-- Перемещение объединенной сборки в основную папку проекта, где хранятся результаты компиляции -->
<Exec Command="move $(TargetDir)merged\*.* $(TargetDir)"/>
<!-- Удаление временной папки -->
<RemoveDir Directories="$(TargetDir)merged"/>
</Target>
Данные изменения позволяют произвести все действия со сборками во время компиляции текущего проекта.
См. также: