Насколько необходимо принудительно загружать сопутствующие сборки в CAD?
Автор: Кулик Алексей aka kpblc | Дата: 23 Декабрь 2024 · 1 комментарий
Так, мне тут много раз сказали, что "не надо грузить все сборки, особенно если они находятся в одном каталоге". Попробую разобраться.
Ситуация - единственный аддон, единственная команда, вызывающая некое окошко (в котором галочку поставили - кнопка "ОК" становится доступной для нажатия). С CAD никакого взаимодействия по факту нет и не предполагается. Окно - на WPF, пробую подключить команды, ViewModel, ну и что еще получится. Да, пока не забыл! В окне обязательно должен быть какой-то символ из FontAwesome.
Понятно, что для случая "все внутри одной сборки" проблем быть не должно. Но проверить не помешает. И начну проверку, пожалуй, с ACAD2021 - тупо потому, что виртуалка с ним уже запущена и работает.
Результаты - в репозитории https://github.com/kpblc2000/CadLoadAssemblies, ветка master
Теперь, особо не заморачиваясь, прокидываю Nuget-пакет для FontAwesome5 и пишу простенькое окно (для упрощения не прописываю и не прокидываю темы, они только замусорят текст, а его и так будет овердофига):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | <Window x:Class="LoadAssemblies.ACAD.Views.Windows.TestWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:LoadAssemblies.ACAD.Views.Windows" xmlns:fa="http://schemas.fontawesome.com/icons/" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" WindowStyle="ToolWindow" WindowStartupLocation="CenterOwner"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <fa:ImageAwesome Grid.Column="0" Grid.Row="0" Icon="Solid_Database" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="15" /> <CheckBox Content="Можно нажать ОК" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" /> <UniformGrid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Rows="1" VerticalAlignment="Bottom" HorizontalAlignment="Right"> <Button Content="ОК" Margin="15"></Button> <Button Content="Отмена" Margin="15"></Button> </UniformGrid> </Grid> </Window> |
И пока забываю про функционал окна, пишу команду для вызова окна:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.Runtime; using LoadAssemblies.ACAD.Views.Windows; namespace LoadAssemblies.ACAD.CadCommands { public static class TestCmd { [CommandMethod("test-aio")] public static void TestAllInOneCommand() { Document doc = Application.DocumentManager.MdiActiveDocument; if (doc == null) { return; } TestWindow win = new TestWindow(); Application.ShowModalWindow(win); } } } |
Чтобы не было лишних вопросов, на данный момент структура проекта примерно такова:
По поводу загрузки пока что тоже не парюсь, буду грузить вручную. Ок, netload, вручную выбираю приложение, в ком.строке test-aio, и… Ййес!
Что уж там сам ACAD будет говорить про ошибки – лучше и не вспоминать Но основная фраза
Теперь то же самое – под наник 23.1. Другая виртуалка, опять же VS, создание уже NET6-проекта в том же решении, минимальная настройка… Чтоб не включать мозг, тупо копирую код по максимуму.
Запуск – и кто там чего говорил про необязательность загрузки?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | public void Initialize() { AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve; } public void Terminate() { } private Assembly CurrentDomainOnAssemblyResolve(object Sender, ResolveEventArgs Args) { string name = GetAssemblyName(Args); string path = Path.Combine(Path.GetDirectoryName((typeof(ExtensionInitialize).Assembly.Location)), name + ".dll"); if (File.Exists(path)) { Assembly assembly = Assembly.LoadFrom(path); if (assembly.FullName == Args.Name) { return assembly; } } return null; } private string GetAssemblyName(ResolveEventArgs args) { if (args.Name.IndexOf(",") > -1) { return args.Name.Substring(0, args.Name.IndexOf(",")); } else { return args.Name; } } } |
Доколдовываю csproj для наника (да, я пользуюсь nuget-пакетами для ncad, которые нанодевы так и не могут выкатить):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0-windows</TargetFramework> <ImplicitUsings>disable</ImplicitUsings> <Nullable>enable</Nullable> <UseWPF>true</UseWPF> <UseWindowsForms>true</UseWindowsForms> <Platform>x64</Platform> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> </PropertyGroup> <ItemGroup> <PackageReference Include="FontAwesome5" Version="2.1.11" /> <PackageReference Include="nanoCadCore23.1.NET" Version="1.0.0"> <IncludeAssets>compile</IncludeAssets> <ExcludeAssert></ExcludeAssert> </PackageReference> </ItemGroup> </Project> |
Запускаю под NCAD23.1 (хоссспидяаааа, когда ж они от сплеша откажутся!). Окно слегка поменял, чтоб плюс-минус вменяемо выглядело:
Теперь то же самое, под ACAD (блин, ну ведь знал же, что две виртуалки практически гарантировано положат физическую машину, и все равно обе запустил! Мдаааа…) Тем не менее:
Прежде всего – создаю отдельную сборку с окнами. Чтоб не сходить с ума с пробросом всяких разных ссылок, делаю на NET6, библиотека UserControl. Возврат в ветку master, и начинаю новую ветку feature/SeparateViews (от master).
Из LoadAsemnblies.NCAD убираю Nuget-пакет FontAwesome. И выношу окно в только что созданную сборку. Соответственно вношу изменения в команду:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | using HostMgd.ApplicationServices; using LoadAssemblies.Views.Windows; using Teigha.Runtime; namespace LoadAssemblies.NCAD.CadCommands { public static class TestCmd { [CommandMethod("test-aio")] public static void TestAllInOneCommand() { Document doc = Application.DocumentManager.MdiActiveDocument; if (doc == null) { return; } TestWindow win = new TestWindow(); Application.ShowModalWindow(win); } } } |
При этом ASsemblyResove в инициализаторе не прописываю от слова совсем. Сборка, компиляция, запуск, вызов… Ошибка в момент инициализации окна.
Проверяю, что на самом деле болтается в выходном каталоге:
Так, получается, надо прописывать копирование dll в выходной каталог для LoadAssemblies.NCAD:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0-windows</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="nanoCadCore23.1.NET" Version="1.0.0" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\LoadAssemblies.Views\LoadAssemblies.Views.csproj" /> </ItemGroup> <Target Name="CopyDLLs" AfterTargets="PostBuildEvent"> <ItemGroup> <FontAwesomeDll Include="$(SolutionDir)packages\**\net6.0-windows7.0\*.dll" /> </ItemGroup> <Copy SourceFiles="@(FontAwesomeDll)" DestinationFolder="$(TargetDir)" SkipUnchangedFiles="true" /> </Target> </Project> |
Теперь в выходном каталоге вроде бы все хорошо:
Копирование прописано, конечно, криво – но на бОльшее пока мозгов не хватает. В частности, я не знаю, что и как надо будет прописывать, если какая-то dll будет иметь собственные зависимости, не предоставляемые как Nuget-пакеты.
Ну что ж, новый запуск, вызов test-aio. Хм, неожиданно – но окно появляется и корректно работает. Запомню…
“Пробрасывать” ссылку на LoadASsemblies.Views бесполезно: Framework не даст такой возможности. И вот тут я понял, что неправильно (по идее) назвал проект LoadAssemblies.Views: его надо было называть LoadAssemblies.Views.NET и подсовывать к NCAD-овской библиотеке, а LoadAssemblies.Views – уже под ACAD. Хотя и лениво, но лучше переделать, чтоб потом с малопонятными проблемами поменьше разбираться.
Ну ок, теперь добавляю сборку LoadAssemblies.Views (NET Framework, библиотека пользовательских элементов) и копирую туда код окна. Да, не самое идеальное решение (мягко говоря), но у меня и задача – выяснить необходимость принудительной загрузки связанных dll, а не добиваться примерного кода Живите теперь с этим
AssemblyResolve не прописан, инициализатора вообще нет. Вместо сборки и загрузки просто собираю проект. И библиотек для FontAwesome в выходном каталоге нет. Поиски привели примерно к следующему варианту:
В событиях “после сборки” прописываю
1 | xcopy /s $(SolutionDir)packages\FontAwesome5.2.1.11\lib\net472\*.dll $(TargetDir) |
Опять же – криво и косо, более универсальный и гибкий вариант стопудов есть. Но этот код по крайней мере позволяет получить все сборки в одном каталоге.
Памятуя о факте, что AssemblyResolve в ACAD все же нужен, дублирую код в инициализации приложения. Сборка, запуск, загрузка, вызов команды…
Как ни удивительно, но окно вызывается и показывается. С какого перепугу у меня в свое время вся эта технология отказывалась работать – тайна (((
Получается, что для NET-приложений достаточно просто добиться наличия связанных dll в выходном каталоге основного приложения, а для Framework - наличия dll + AssemblyResolve? Антиресно пляшут девки по четыре штуки в ряд... Но для собственного спокойствия прокидывать принудительную загрузку по запросу не помешает - что в одном случае, что в другом.
Если есть альтернативные варианты - с удовольствием пообщаемся. Хоть в комментах, хоть где.
форк твоего проекта
https://github.com/doctorRaz/CadLoadAssemblies_drz
LoadAssemblies.NCAD.NF и LoadAssemblies.NCAD грузятся работают и подгружают зависимые библиотеки