Насколько необходимо принудительно загружать сопутствующие сборки в CAD?

Так, мне тут много раз сказали, что "не надо грузить все сборки, особенно если они находятся в одном каталоге". Попробую разобраться.

Ситуация - единственный аддон, единственная команда, вызывающая некое окошко (в котором галочку поставили - кнопка "ОК" становится доступной для нажатия). С CAD никакого взаимодействия по факту нет и не предполагается. Окно - на WPF, пробую подключить команды, ViewModel, ну и что еще получится. Да, пока не забыл! В окне обязательно должен быть какой-то символ из FontAwesome.

Понятно, что для случая "все внутри одной сборки" проблем быть не должно. Но проверить не помешает. И начну проверку, пожалуй, с ACAD2021 - тупо потому, что виртуалка с ним уже запущена и работает.

Результаты - в репозитории https://github.com/kpblc2000/CadLoadAssemblies, ветка master

Все-в-одном
Создаю решение, внутри него проект LoadAssemblies.ACAD (NET Framework 4.8), на который прокидываю Nuget-пакет для AutoCAD2019. Коммичу в ветку feature\All-in-one\ACAD (не прописывая ни окна, ни команды – ничего!).

Теперь, особо не заморачиваясь, прокидываю 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);
        }
    }
}

Чтобы не было лишних вопросов, на данный момент структура проекта примерно такова:
2024-12-20_19-24-50

По поводу загрузки пока что тоже не парюсь, буду грузить вручную. Ок, netload, вручную выбираю приложение, в ком.строке test-aio, и… Ййес!
2024-12-20_19-26-15

Что уж там сам ACAD будет говорить про ошибки – лучше и не вспоминать ;) Но основная фраза

Could not load file or assembly ‘FontAwesome5.Net, PublicKeyToken=9cfaf01297a008f8′ or one of its dependencies. Не удается найти указанный файл. —> System.IO.FileNotFoundException: Could not load file or assembly ‘FontAwesome5.Net, PublicKeyToken=9cfaf01297a008f8′ or one of its dependencies. Не удается найти указанный файл.
Ага, угу, уже звоночек. Ну и окно, естественно, не показывается и не обрабатывается. Отлично! Значит, принудительная загрузка по крайней мере в NET Framework-проектах нужна. Еще что-то помню, оказывается :P

Теперь то же самое – под наник 23.1. Другая виртуалка, опять же VS, создание уже NET6-проекта в том же решении, минимальная настройка… Чтоб не включать мозг, тупо копирую код по максимуму.

Запуск – и кто там чего говорил про необязательность загрузки?
2024-12-20_20-38-46

Спасет ли AsspeblyResolve?
Скорее всего, спасет. Пока ситуацию не усложним капитальнейшим образом. Чтоб не быть голословным, прописываю загрузку сборок по запросу (в оба проекта, если что):

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 (хоссспидяаааа, когда ж они от сплеша откажутся!). Окно слегка поменял, чтоб плюс-минус вменяемо выглядело:
2024-12-20_21-02-18

Теперь то же самое, под ACAD (блин, ну ведь знал же, что две виртуалки практически гарантировано положат физическую машину, и все равно обе запустил! Мдаааа…) Тем не менее:
2024-12-20_21-16-00

Усложняем ситуацию под NCAD. Будет страшно, но весело ) Надеюсь

Прежде всего – создаю отдельную сборку с окнами. Чтоб не сходить с ума с пробросом всяких разных ссылок, делаю на 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 в инициализаторе не прописываю от слова совсем. Сборка, компиляция, запуск, вызов… Ошибка в момент инициализации окна.

Проверяю, что на самом деле болтается в выходном каталоге:
2024-12-22_14-22-45

Так, получается, надо прописывать копирование 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>

Теперь в выходном каталоге вроде бы все хорошо:
2024-12-22_14-25-14

Копирование прописано, конечно, криво – но на бОльшее пока мозгов не хватает. В частности, я не знаю, что и как надо будет прописывать, если какая-то dll будет иметь собственные зависимости, не предоставляемые как Nuget-пакеты.

Ну что ж, новый запуск, вызов test-aio. Хм, неожиданно – но окно появляется и корректно работает. Запомню…

Веселье уже для ACAD
Теперь то же самое, но под ACAD 2021 (т.е. Framework, никакого страшного и ужасного NET8).

“Пробрасывать” ссылку на 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? Антиресно пляшут девки по четыре штуки в ряд... Но для собственного спокойствия прокидывать принудительную загрузку по запросу не помешает - что в одном случае, что в другом.

Если есть альтернативные варианты - с удовольствием пообщаемся. Хоть в комментах, хоть где.

Размещено в .NET, AutoCAD, nanoCAD · Метки: , , , ,



Комментарии

Есть 1 комментарий к “Насколько необходимо принудительно загружать сопутствующие сборки в CAD?”
  1. drz пишет:

    форк твоего проекта
    https://github.com/doctorRaz/CadLoadAssemblies_drz
    LoadAssemblies.NCAD.NF и LoadAssemblies.NCAD грузятся работают и подгружают зависимые библиотеки

Поделитесь своим мнением


Я не робот.