Страницы

Поиск по вопросам

суббота, 6 июля 2019 г.

Компилирование и повторная загрузка сборки в runtime

Товарищи, кратко опишу ситуацию:
Пишу небольшое расширяемое приложение. Чтобы пользователи могли добавлять собственный функционал, в рамках программы создал интерфейсы, от которых они должны наследовать свои типы
При этом я сделал генерацию кода также и внутри самого приложения для большего удобства

Положим, есть такой интерфейс:
namespace MyAppNamespace { public interface INamed { string Name { get; } } }
Далее внутри приложения генерируется следующий код (и кладется в resultCode):
using MyAppNamespace;
public class Wrapper : INamed { public string Name { get { return "Test"; } } }
Компилирую это дело:
// Указываю, что на выходе мне не нужен исполняемый файл, а также что сборку нужно создать по указанному пути CompilerParameters options = new CompilerParameters { GenerateExecutable = false, GenerateInMemory = false, OutputAssembly = $"{SavePath}.dll" }; options.ReferencedAssemblies.Add(new Uri(GetType().Assembly.CodeBase, UriKind.Absolute).LocalPath); // Добавляю ссылку на текущую сборку для наследования интерфейса // Получаю результат компиляции CompilerResults results = new Microsoft.CSharp.CSharpCodeProvider().CompileAssemblyFromSource(options, resultCode); // Опустим проверки // Загружаю сборку из массива байт (так как сам файл потом, возможно, может быть удален) Assembly asm = Assembly.Load(File.ReadAllBytes(results.PathToAssembly)); // Создаю instance типа, который в сборке унаследован от нужного интерфейса INamed named = (INamed)Activator.CreateInstance(asm.DefinedTypes.First(x => x.ImplementedInterfaces.Contains(typeof(INamed))).AsType()); asm = null; GC.Collect(); // Вычищаю сборку из памяти. По крайней мере, я хочу в это верить return named;
После этого я могу спокойно получать доступ к named.Name. Через некоторое время объект "выбрасывается", пока пользователь явно не укажет, что хочет его использовать. В таком случае его нужно будет повторно достать из сборки
Но есть одно жирное но: если я попытаюсь тем же самым образом загрузить сборку и достать из нее тип во второй раз, то визуально процесс пройдет успешно, но при попытке доступа к named.Name вылетит ошибка, что сборка, в которой он определен, не найдена

Я могу поправить логику приложения и сделать считывание единоразовым (пожалуй, это будет даже правильнее), но сейчас для меня важно понимание, почему же так происходит: при первом считывании все работает как часы, а при втором считывании тем же самым способом из того же самого файла процесс проходит успешно, но объект оказывается "битым", так как при попытке доступа к его свойствам я получу ошибку о том, что сборка не может быть загружена


Ответ

Вычищаю сборку из памяти. По крайней мере, я хочу в это верить
Увы, эта вера не имеет оснований. Использованный вами способ загрузки сборки не только не позволяет выгрузить сборку из памяти без выгрузки всего домена приложений, но и при каждом повторном запуске будет грузить сборку с того же пути заново (иными словами, это хороший способ исчерпать память при длительной работе программы).
Создайте Dictionary (где string будет путем к файлу) и кэшируйте все загружаемые сборки в нем. Можно использовать вместо пути CRC/хэш файла, если вам нужно как-то учесть само содержимое файла. Или грузить каждую сборку в новый домен приложений, тогда их можно будет выгрузить (вообще, это обычная практика при создании приложений с расширениями).

Комментариев нет:

Отправить комментарий