Несколько плагинов с общими библиотеками

Автор Тема: Несколько плагинов с общими библиотеками  (Прочитано 15675 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн Дмитрий Загорулькин

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 735
Во-первых не следует полагаться на порядок загрузки. Я не проверял, но мне кажется, что он читает каталоги внутри ApplicationPlugins и обрабатывает их не в алфавитном порядке, а в том порядке в котором они находятся в каталоге (а это совершенно необязательно отсортированные по алфавиту).
Вот да, именно так! На уровне подсознания где-то, есть понимание, что это весьма ненадежно :)
Во-вторых, я бы предпочел в методе Initialize своих приложений загружать необходимые сборки если они еще не загружены, а если их загрузить не удаётся, то сообщал бы пользователю.
А это уже весьма интересная идея! Надо будет проверить, как это будет работать.
В-третьих, я бы вообще не включал их ни в какие BUNDLE'ы - если это библиотечные файлы, то зачем отдавать их загрузку на волю AutoCAD?
Да, согласен. Если Ваша идея с подгрузкой из Initialize хорошо покажет себя в работе, то от загрузки библиотеки через bundle можно отказаться.
Попробую сделать небольшой тестовый проект и обкатать идею.
Спасибо!

Онлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
А это уже весьма интересная идея! Надо будет проверить, как это будет работать.
Главное, чтобы у тебя не было статических переменных, которые инициализируются значениями, использующие классы и методы из этих "библиотек". Насколько я помню инициализация статических переменных происходит до вызова метода Initialize (могу ошибаться).
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Дима_

  • ADN Club
  • ****
  • Сообщений: 473
  • Карма: 66
Я в этом моменте плюю на размер в угоду надежности. В БД если обновляется любой компонент, то автоматом создается архив со всеми сборками и пр. компонентами и при загрузке автокада "загрузчик" увидев новую версию в БД все переписывает на локальном компе (с учетом "уровня" пользователя) - то есть чистит все "свои" сборки и разворачивает по новой весь загруженный архив. И если что, можно в момент откатить до любой предыдущей версии - пока правда такое "счастье" миновало, но все возможно.

Оффлайн Дмитрий Загорулькин

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 735
Главное, чтобы у тебя не было статических переменных, которые инициализируются значениями, использующие классы и методы из этих "библиотек". Насколько я помню инициализация статических переменных происходит до вызова метода Initialize (могу ошибаться).
Нет, не ошибаетесь. Это естественное поведение при загрузке любого .NET класса.
Как показали опыты, в самом Initialize, после кода подгрузки вспомогательных DLL, также нельзя использовать методы из этих DLL. Если раскомментить строки 44-46 (обращения к методам вспомогательных библиотек), то метод Initialize не выполнится и DLL с этим кодом не будет загружена. Не знаю, почему так. Возможно, что это тоже естественное поведение .NET, но я с таким еще не сталкивался.
Код - C# [Выбрать]
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.EditorInput;
  3. using Autodesk.AutoCAD.Runtime;
  4. using System;
  5. using System.IO;
  6. using System.Reflection;
  7.  
  8. namespace Application1
  9. {
  10.     public class AppInitClass : IExtensionApplication
  11.     {
  12.         static bool AssemblyLoad(string path)
  13.         {
  14.             try
  15.             {
  16.                 Assembly.LoadFrom(path);
  17.                 return true;              
  18.             }
  19.             catch (System.Exception ex)
  20.             {                
  21.                 Application.ShowAlertDialog($"Exception: {ex.Message}");
  22.                 return false;
  23.             }            
  24.         }
  25.  
  26.         public void Initialize()
  27.         {
  28.             string appsDirPath = Path.Combine
  29.                 (Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
  30.                 "Autodesk", "ApplicationPlugins", "AppLibrary");
  31.  
  32.             string myLib1Path = Path.Combine(appsDirPath, "MyLibrary1.dll");
  33.             string myLib2Path = Path.Combine(appsDirPath, "MyLibrary2.dll");
  34.             string myLib3Path = Path.Combine(appsDirPath, "MyLibrary3.dll");
  35.  
  36.  
  37.             if (!AssemblyLoad(myLib1Path)
  38.                 || !AssemblyLoad(myLib2Path)
  39.                 || !AssemblyLoad(myLib3Path))
  40.             {
  41.                 return;
  42.             }
  43.  
  44.             //Document adoc = MyLibrary1.Support1.GetActiveDocument(Application.DocumentManager);
  45.             //string appName = MyLibrary2.Support2.GetAppName(Assembly.GetExecutingAssembly());
  46.             //string adocName = MyLibrary3.Support3.GetAdocName(adoc);
  47.  
  48.             Application.ShowAlertDialog("Application #1 loaded!");            
  49.         }
  50.  
  51.         public void Terminate()
  52.         {
  53.  
  54.         }
  55.  
  56.         [CommandMethod("App1Test")]
  57.         public void Run()
  58.         {
  59.             Document adoc = MyLibrary1.Support1.GetActiveDocument(Application.DocumentManager);
  60.             string appName = MyLibrary2.Support2.GetAppName(Assembly.GetExecutingAssembly());
  61.             string adocName = MyLibrary3.Support3.GetAdocName(adoc);
  62.  
  63.             Application.ShowAlertDialog("Application #1 test cmd: OK");
  64.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  65.             ed.WriteMessage("\nApplication #1 test cmd: OK");
  66.         }
  67.     }
  68. }
  69.  
« Последнее редактирование: 11-02-2020, 10:43:23 от Дмитрий Загорулькин »

Оффлайн Дмитрий Загорулькин

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 735
Я в этом моменте плюю на размер в угоду надежности. В БД если обновляется любой компонент, то автоматом создается архив со всеми сборками и пр. компонентами и при загрузке автокада "загрузчик" увидев новую версию в БД все переписывает на локальном компе (с учетом "уровня" пользователя) - то есть чистит все "свои" сборки и разворачивает по новой весь загруженный архив. И если что, можно в момент откатить до любой предыдущей версии - пока правда такое "счастье" миновало, но все возможно.
У меня "загрузчиков" никаких нет. Для приложений я создаю инсталляторы с помощью WIX. Наверное, в проект инсталляции тоже можно добавить программу, которая будет анализировать уже имеющиеся приложения и библиотеки в них, но я пока до этого не дорос.

Онлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Если раскомментить строки 44-46 (обращения к методам вспомогательных библиотек), то метод Initialize не выполнится и DLL с этим кодом не будет загружена. Не знаю, почему так.
Ну для начала их тоже нужно обернуть в try/catch, так как если в Initialize остаётся необработанное исключение, то сборка не работает.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Дмитрий Загорулькин

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 735
Да нет, исключений там не происходит. Да я, на самом деле, уже что только не делал  :) И по отдельности пробовал раскомментить, и обработку ошибок, и логику методов вспомогательных менял. Ничего не помогает.
Я подозреваю, что есть какой-то механизм проверки обращений метода Initialize к сторонним DLL, и если они в момент обращения к методу недоступны, то сам метод не выполняется. Потому что при дебаге даже не срабатывает точка останова на входе в метод, если хотя бы одна из этих строк не закомментирована.

Оффлайн Дмитрий Загорулькин

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 735
Главное, чтобы у тебя не было статических переменных, которые инициализируются значениями, использующие классы и методы из этих "библиотек".
Ваш совет натолкнул меня на идею - вынести загрузку библиотек в статический конструктор класса. В таком варианте работает как надо! Еще раз, спасибо за идею! Теперь надо обкатать ее на рабочих сборках.
Итоговый код автозагружаемого класса
Код - C# [Выбрать]
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.EditorInput;
  3. using Autodesk.AutoCAD.Runtime;
  4. using System;
  5. using System.IO;
  6. using System.Reflection;
  7.  
  8. namespace Application1
  9. {
  10.     public class AppInitClass : IExtensionApplication
  11.     {
  12.         /// <summary>
  13.         /// Статический конструктор класса
  14.         /// </summary>
  15.         static AppInitClass()
  16.         {
  17.             // составляем путь к папке с библиотечными DLL
  18.             // в данном случае %ProgramData%\Autodesk\ApplicationPlugins\AppLibrary
  19.             string appsDirPath = Path.Combine
  20.                 (Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
  21.                 "Autodesk", "ApplicationPlugins", "AppLibrary");            
  22.  
  23.             // грузим библиотечные DLL
  24.             AssemblyLoad(Path.Combine(appsDirPath, "MyLibrary1.dll"));
  25.             AssemblyLoad(Path.Combine(appsDirPath, "MyLibrary2.dll"));
  26.             AssemblyLoad(Path.Combine(appsDirPath, "MyLibrary3.dll"));
  27.         }
  28.  
  29.         /// <summary>
  30.         /// Безопасный метод загрузки DLL
  31.         /// </summary>
  32.         /// <param name="path">Путь к DLL</param>
  33.         /// <returns>true - загружена, false - не загружена</returns>
  34.         static bool AssemblyLoad(string path)
  35.         {
  36.             try
  37.             {
  38.                 Assembly.LoadFrom(path);
  39.                 return true;
  40.             }
  41.             catch (System.Exception ex)
  42.             {
  43.                 Application.ShowAlertDialog($"Exception: {ex.Message}");
  44.                 return false;
  45.             }
  46.         }
  47.  
  48.         /// <summary>
  49.         /// Метод, выполняемый AutoCAD при загрузке сборки
  50.         /// </summary>
  51.         public void Initialize()
  52.         {
  53.             // Вызываем методы из библиотечных DLL, по одному из каждой
  54.             Document adoc = MyLibrary1.Support1.GetActiveDocument(Application.DocumentManager);
  55.             string appName = MyLibrary2.Support2.GetAppName
  56.                 (Assembly.GetExecutingAssembly()) ?? "*no_app_name*";
  57.             string adocName = MyLibrary3.Support3.GetAdocName(adoc) ?? "*no_adoc_name*";
  58.  
  59.             // Выводим сообщение
  60.             Application.ShowAlertDialog($"{appName} loaded into {adocName}!");
  61.         }        
  62.  
  63.         public void Terminate()
  64.         {
  65.  
  66.         }
  67.  
  68.         /// <summary>
  69.         /// Командный метод для проверки загруженности сборки
  70.         /// </summary>
  71.         [CommandMethod("App1Test")]
  72.         public void Run()
  73.         {
  74.             Document adoc = MyLibrary1.Support1.GetActiveDocument(Application.DocumentManager);
  75.             string appName = MyLibrary2.Support2.GetAppName
  76.                 (Assembly.GetExecutingAssembly()) ?? "*no_app_name*";
  77.             string adocName = MyLibrary3.Support3.GetAdocName(adoc) ?? "*no_adoc_name*";
  78.  
  79.             Application.ShowAlertDialog($"{appName} test command in {adocName}: OK");
  80.             Editor ed = adoc?.Editor;
  81.             ed?.WriteMessage("\n{0} test command in {1}: OK", appName, adocName);
  82.         }
  83.     }
  84. }

Тестовый проект - в архиве (VS 2015)
« Последнее редактирование: 11-02-2020, 10:43:05 от Дмитрий Загорулькин »

Оффлайн Вильдар

  • ADN Club
  • ****
  • Сообщений: 405
  • Карма: 77
  • Skype: vildar82
Со сторонними библиотеками есть проблемы - использования разных версий в разных плагинах.
Есть предположение, что лучший способ - строгие имена и регистрация в гаке!
Правда, пока на практике не проверял  ::)
И есть много библиотек без строгих имен.

Оффлайн Дмитрий Загорулькин

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 735
Со сторонними библиотеками есть проблемы - использования разных версий в разных плагинах.
Я уже третий год использую схему, которую описал в предыдущем своём сообщении в этой теме - такой проблемы у меня больше нет.
Нужно придерживаться нескольких правил. Например - ничего не удалять из библиотечной DLL. Можно менять внутренности, но все публичные члены должны быть неизменными. Если что-то перестаёт быть актуальным - помечаю как Obsolete. Библиотечные dll собираю в отдельные MergeModule и добавляю в проект установки каждого плагина. При установке плагина, все вспомогательные DLL, которые он использует, обновляются до новых версий. Но так как из них ничего старого не удаляется, то и старые плагины спокойно используют обновлённые DLL.
Проблема помещения вспомогательных DLL для AutoCAD в GAC была описана ещё Бушманом Андреем, где-то тут есть на форуме, если поискатьобнаружил, что обсуждение этого вопроса - как раз в этой теме ранее было :). Если я правильно помню, суть в том, что DLL из AutoCAD API не подписаны и если в библиотечной DLL есть ссылка на них, то в GAC они не могут быть помещены. Теоретически, конечно, есть вариант писать библиотеки без привязки к AutoCAD API, но, по мне, затрат будет больше, чем выгоды.

Оффлайн Роман Малютин

  • ADN Club
  • Сообщений: 15
  • Карма: 3
  • Инженер
    • lep10.ru
Ещё раз подниму эту прекрасную тему про совместимость  :)

SQLite - суперпопулярная технология хранения данных.
Плагин#1 использует System.Data.SQLite.dll v.1.0.97, загружается первым, разработан третьей стороной.
Плагин#2 использует System.Data.SQLite.dll v.1.0.112, загружается вторым, мой
Результат - AutoCAD падает с фатальной ошибкой.
Если скопировать более свежую System.Data.SQLite.dll из Плагина#2 в Плагин#1 - всё работает.

Пробовал грузить dll как писал Дмитрий Загорулькин, чтобы получить информацию об ошибке - не вышло.
Подскажите хотя бы направление поиска проблемы. Сигнатуры там не менялись, обычный IDbConnection ...

Онлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Если скопировать более свежую System.Data.SQLite.dll из Плагина#2 в Плагин#1 - всё работает.
Собственно говоря это единственный мне известный метод. Две разных версии одной сборки загрузить в один домен нельзя.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Вильдар

  • ADN Club
  • ****
  • Сообщений: 405
  • Карма: 77
  • Skype: vildar82
Можно переименовать дллку и грузить ее принудительно. В некоторых случаях помогает.

Онлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Вильдар,
Судя по тому, что написано здесь: https://system.data.sqlite.org/index.html/doc/trunk/www/downloads.wiki там используются не только managed, но и native dll-файлы. Так что в этом случае точно ничего не получится. Разве что скачать исходники, поменять везде имена и перекомпилировать.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Вильдар

  • ADN Club
  • ****
  • Сообщений: 405
  • Карма: 77
  • Skype: vildar82
там используются не только managed, но и native dll-файлы
ох, с таким еще не сталкивались, слава богу 😁