Пользователь
0,0
рейтинг
6 сентября 2010 в 14:31

Разработка → Расширяем кругозор. SharpDevelop AddIns

.NET*
С SharpDevelop я знаком, наверное, уже около года. На моем мягко говоря не топовом ноутбуке он чувствует себя превосходно и при этом умудряется решать большинство поставленных перед ним задач. Но как и любое другое средство разработки, не всесилен. Время от времени приходится обращаться к Visual Studio и другим инструментам. Иногда выручают самописные Project Templates и File Templates. Иногда — подключение консольных утилит через меню Tools. Но хотелось бы чего-то большего.

SharpDevelop — это, как известно, Open Source. Так что ничего не мешает взять его код и переписать, как вздумается. Но оставим это на крайний случай. У SharpDevelop есть замечательная возможность писать для него плагины или AddIns, как называют их авторы. Поэтому сегодня остановимся на плагинах и разберем, как они работают и как их писать. Для примера напишем простой плагин для поддержки Microsoft Moles Isolation Framework.

До этого я сталкивался с немногими реализациями плагинов. Пару раз создавал свою версию. Но это в подметки не годится тому, что я увидел в SharpDevelop. В нем практически все реализовано в виде плагинов. Окна редакторов, боковые панели, диалоги, поддержка различных языков, элементы панели инструментов, отдельные элементы панели меню и контекстного меню. Если убрать все плагины, то все, что останется — это ядро для поддержки плагинов.

Немного истории


Но так было не всегда. Первые пробные версии SharpDevelop появились в 2000 году — сразу после выхода первой .NET Framework. Главный архитектор, Mike Kruger, до этого никогда не работал на Windows платформе. Только с Linux. Когда он увидел C#, этот язык показался ему лучше, чем Java. Поэтому он решил написать для него IDE, так как на то время ничего подобного для .NET еще не было.

Первая версия была обычным окном с .NET RichTextBox. Она умела загружать файлы с кодом и компилировать их при помощи csc.exe. Далее был создан собственный редактор текста и инструменты для управления файлами проекта. Через некоторое время SharpDevelop был достаточно стабилен, чтобы продолжить разработку на нем самом.

В начале 2001-го появилась первая реализация AddIn. Она была довольно простой — можно было лишь добавлять элементы в специальный пункт главного меню. А в начале 2002-го система AddIn приобрела свой окончательный вид, в котором она дожила до наших дней.

AddIn Tree


Разработчики поставили перед собой серьезную задачу — нужно было реализовать такую систему плагинов, чтобы можно было расширять одни плагины при помощи других. Так появилась идея AddIn Tree. AddIn Tree позволяет описывать расширения при помощи XML и подключать их практически куда угодно, указав путь (Path).

Вся новая функциональность должна объявлять путь, по которому она помещена. Под Path подразумевается путь в дереве, от корня до определенного узла. Например — "/SharpDevelop/Workbench/MainMenu". Путь — это просто строка, которая позволяет находить нужные расширения.

Все расширения должны содержать файл *.addin (XML), который их описывает. Также может быть одна или несколько .NET сборок, в которых заключена логика расширения. Во время выполнения содержимое addin-файлов объединяетя в одно дерево. Но связанные сборки могут загружаться не сразу.

Например, по пути "/SharpDevelop/Workbench/MainMenu" располагаются все пункты главного меню. Дерево используется при отрисовке этого меню, а код загружается только когда выбран один из его пунктов. С панелями схожая ситуация — пока панель закрыта или свернута, ее код не загружен в память. Это позволяет значительно уменьшить время запуска SharpDevelop.

Рассмотрим, как используется Path при описании расширения.

<AddIn>
  <!-- some stuff -->
  
  <Path name="/SharpDevelop/Workbench/MainMenu">
    <!-- node with id="View" -->
    <!-- node with id="Edit" -->
  </Path>
</AddIn>

Наши узлы, которые мы добавили, станут пунктами View и Edit в этом меню. По понятным причинам узлы, расположенные по одному пути, должны иметь уникальный id.

После этого каждый узел сам становится элементом пути. Например — "/SharpDevelop/Workbench/MainMenu/Edit". Следовательно по этому пути можно добавлять новые узлы:

<Path name="/SharpDevelop/Workbench/MainMenu/Edit">
  <!-- node with id="Copy" -->
  <!-- node with id="Paste" -->
</Path>

Пункт меню Edit может быть реализован в одном плагине, а пункты Copy и Paste — в другом. Таким образом, используя пути в дереве, можно не только добавлять свои расширения, но и «расширять» существующие.

Doozer


Подведем небольшой итог. XML-файл с корневым элементом AddIn позволяет описывать наше расширение. В частности то, какое место в программе оно «расширяет». Сам код хранится в отдельной .NET сборке. Поэтому теперь нам нужно описать в XML-файле то, как этот код запускать.

Для этого создатели SharpDevelop придумали специальный уровень абстракции — Doozer (раньше назывался Codon). Doozer помогаeт создавать всевозможные объекты, используя параметры — атрибуты XML. Например, MenuItemDoozer умеет создавать элемент меню, а PadDoozer — панели (такие как Class View или Toolbox). Теперь можно обновить наше описание расширения, добавив в него Doozer.

<AddIn>
  <!-- some stuff -->
  
  <Path name="/SharpDevelop/Workbench/MainMenu">
    <MenuItem id="Edit"
         label="Edit"
         class="Namespace.EditCommandImplementation" />
  </Path>
</AddIn>

Label — это имя пункта меню, которое будет отображено на экране. Class — это имя класса, который реализует интерфейс IMenuCommand (наследуется от ICommand). При нажатии на пункт меню создается объект данного класса и вызывается его метод Run.

В распоряжении разработчика уже есть множество готовых Doozers. Полный список можно найти в документации (все ссылки в конце статьи). Все они реализуют интерфейс IDoozer, а их имена заканчиваются на Doozer. В XML-файле суффикс Doozer опускается.

Не все Doozers работают с командами. Например, ClassDoozer просто создает объект класса, используя конструктор по умолчанию, и вставляет его в дерево. А CustomPropertyDoozer создает одно из свойств, в которых хранятся пользовательские настройки.

Переходим к практике


Теперь, когда вы получили базовое представление о том, как устроены AddIns, можно попробовать написать свой плагин. Напомню, что для примера мы будем создавать плагин для использования Microsoft Moles. Первое, с чего нужно начать — это посмотреть на окно SharpDevelop и подумать, куда в нем поместить ваш плагин, как он будет запускаться и использоваться.

Не будем оригинальничать и сделаем по аналогии с Visual Studio. Если помните, для создания Moles там нужно выбрать нужную сборку в папке References, в панели Projects. В контекстном меню этих сборок есть пункт «Add Moles Assembly».

С запуском плагина мы определились. Теперь нужно узнать, какой путь (Path) следует использовать. Для этого не придется зарываться в книги-форумы-мануалы. В SharpDevelop есть превосходный инструмент — AddIn Scout (главное меню — Tools — AddIn Scout), который показывает AddIn Tree в виде дерева папок.

Немного поплутав по этому дереву, находим то, что нам нужно:



"/SharpDevelop/Pads/ProjectBrowser/ContextMenu/ReferenceNode" — это как раз тот путь, который нам нужен. По нему уже добавлены существующие пункты контекстного меню: Refresh, Remove и Properties.

Ссылка FileName указывает на путь .addin-файла, в котором определено выбранное расширение. В данном случае это ICSharpCode.SharpDevelop.addin — главный файл, в котором определены основные расширения. Он довольно большой, 2171 строка. Если кликнуть по ссылке, то этот файл откроется во встроенном редакторе XML.

Редактировать этот файл не рекомендуется. Лучше найдем, как в нем описаны элементы контекстного меню.

<Path name="/SharpDevelop/Pads/ProjectBrowser/ContextMenu/ReferenceNode">
  <MenuItem id="RefreshReference"
       icon="Icons.16x16.BrowserRefresh"
       label="${res:AddIns.HtmlHelp2.Refresh}"
       class="ICSharpCode.SharpDevelop.Project.Commands.RefreshReference"/>
  <MenuItem id="Remove"
       label="${res:Global.RemoveButtonText}"
       icon="Icons.16x16.DeleteIcon"
       class="ICSharpCode.SharpDevelop.Project.Commands.DeleteProjectBrowserNode"/>
  <MenuItem id="RemoveSeparator" type="Separator" />
  <MenuItem id="Properties"
       icon="Icons.16x16.PropertiesIcon"
       label="${res:XML.MainMenu.FormatMenu.ShowProperties}"
       class="ICSharpCode.SharpDevelop.Project.Commands.ShowPropertiesForNode"/>
</Path>

Наконец, можно переходить к самому интересному — написанию плагина. Для начала нужно скачать последнюю версию SharpDevelop в бинарном виде и установить ее. Также нам понадобятся его исходники. Все это можно найти здесь (стабильная версия) или здесь (build server). Я брал с build server.

Исходники сперва нужно скомпилировать. Для этого лучше использовать файл debugbuild.bat, который идет в комплекте. «А зачем нам две версии SharpDevelop?» — спросите вы. Дело в том, что при запуске debug-версии кроме основного окна запускается еще и консоль, в которой можно наблюдать лог. В частности там можно наблюдать, когда и какие сборки загружаются.

Теперь можно открыть основной SharpDevelop и создать новый проект «SharpDevelop addin», который находится в разделе «C#/SharpDevelop». Назовем его MolesAddIn. Шаблон содержит два файла: AddInWritingHelp.txt и MolesAddIn.addin. Первый содержит несколько советов по написанию плагинов и парочку ссылок. Его можно смело удалять. Второй будет содержать описание нашего плагина.

<AddIn name="MolesAddIn"
      author="OpenMinded"
      copyright="GNU Lesser General Public License Version 2.1"
      url=""
      description="Adds support for Microsoft Moles to the projects browser">
    
    <Runtime>
        <Import assembly="MolesAddIn.dll"/>
    </Runtime>
    
    <Manifest>
        <Identity name="OpenMinded.MolesAddIn" version="@MolesAddIn.dll"/>
        <Dependency addin="SharpDevelop" version="4.0"/>
    </Manifest>
    
    <Path name="/SharpDevelop/Pads/ProjectBrowser/ContextMenu/ReferenceNode">
        <MenuItem id="AddMolesAssembly"
                 label="Add Moles Assembly"
                 class="OpenMinded.MolesAddIn.AddMolesCommand"/>
    </Path>

</AddIn>

В тег Runtime следует поместить все сборки, которые понадобятся для работы вашего расширения. В данном случае — это сборка нашего проекта. Identity — это то имя расширения, которое будет отображено в AddIn Manager (главное меню — Tools — AddIn Manager). У каждого расширения должна быть одна identity, а имя должно быть уникально. В качестве версии можно либо явно указать версию, например 1.0.0, либо использовать версию сборки, как это сделано выше.

Dependency — это расширения, которые нужны для работы вашего AddIn. Их может быть несколько. SharpDevelop — это главное расширение, которое содержит много всего, в том числе Project Browser, который нам нужен. Поэтому данная зависимость является обязательной для всех AddIns.

Path — путь, по которому мы добавили пункт меню. Путей в одном файле может быть несколько, как в ICSharpCode.SharpDevelop.addin. Также каждый пункт меню может содержать вложенные пункты меню. Атрибут class содержит имя класса, который отвечает за обработку нажатия на пункт меню. Для MenuItem данный класс должен наследовать интерфейс IMenuCommand либо абстрактный класс AbstractMenuCommand.

Добавим к нашему проекту две ссылки: ICSharpCode.Core.dll и ICSharpCode.SharpDevelop.dll. Их можно найти в /path/to/sharpdevelop-source/bin после того, как вы скомпилировали исходники. Для ссылок нужно установить свойство Copy To Local в False, так как наше расширение будет запускать сам SharpDevelop. Теперь можно добивлять класс AddMolesCommand.

using System;
using System.Windows.Forms;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop.Project;

namespace OpenMinded.MolesAddIn
{
  public class AddMolesCommand : AbstractMenuCommand
  {
    public override void Run()
    {
      // получаем доступ к выделенному элементу папки References
      ReferenceNode node = Owner as ReferenceNode;
      if (node != null)
      {
        // получаем доступ к объекту, который представляет саму ссылку
        ReferenceProjectItem item = node.ReferenceProjectItem;
        if (item != null) {
          string fileName = item.FileName;
          
          MessageBox.Show(fileName);
        }
      }
    }
  }
}

Пока что команда не делает ничего полезного. Мы лишь получили доступ к сборке, по которой кликнули правой кнопкой мыши и узнали ее имя файла.

Для запуска расширения нужно немного подредактировать свойства проекта MolesAddIn. Первое, что нужно сделать — это изменить путь, по которому будет скопировано готовое расширение, чтобы SharpDevelop его автоматически запускал. Для этого есть специальная папка AddIns. Создадим в ней вложенную папку с именем нашего расширения.

Таким образом Output Path нужно установить в "/path/to/sharpdevelop-source/AddIns/MolesAddIn".



Чтобы запускать debug-версию SharpDevelop при нажатии F5, нужно в свойствах проекта изменить способ запуска.



Теперь все готово. Ctrl+F5 — и у нас уже запускается второй SharpDevelop. Откроем в нем проект MolesAddIn и кликнем правой кнопкой по любой из его ссылок.



Пункт меню «Add Moles Assembly» можно наблюдать сразу после «Properties». К этому моменту сборка MolesAddIn.dll еще не загружена, в этом можно убедиться, просмотрев лог в консоли. После клика по выбранному пункту меню откроется MessageBox, а в логе появится строчка «Loading AddIn MolesAddIn.dll».

В AddIn Manager (Tools — AddIn Manager) можно найти запись о нашем расширении: имя, версия и описание. Также его можно наблюдать в AddIn Scout.

А что же дальше?


Вот так и выглядит разработка расширений для SharpDevelop. После того, как AddIn становится достаточно стабильным, его можно подготавливать для установки в вашу рабочую версию IDE. Делается это просто — все сборки, которые нужны для его работы (в нашем случае одна — MolesAddIn.dll) вместе с файлом *.addin пакуются в архив zip. Далее этот архив переименовывается в *.sdaddin. Все, установочный пакет готов. С помощью AddIn Manager его можно установить SharpDevelop.

Разумеется, данное описание системы AddIns далеко не полное. Оно лишь дает общее представление и помогает взглянуть на написание плагинов со стороны разработчика.

Более полное описание можно найти в книге Dissecting a C# Application: Inside SharpDevelop. В цифровом виде ее можно скачать бесплатно. С тех пор, как она вышла не произошло серьезных изменений в архитектуре, в основном переименования. Например Path в книге называется Extension, а Doozer — Codon. Более актуальную информацию можно найти в дистрибутиве SharpDevelop, в папке doc/technotes.

P. S. Почему-то тег source у меня отказывается работать.
Алексей Сигов @OpenMinded
карма
135,5
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (16)

  • +2
    Если кому-то интересно, в следующий раз могу рассказать про добавление нового редактора на примере Workflow Designer, которого пока нет в SharpDevelop.
    • 0
      Очень интересно.
      Хотелось бы так же узнать каким образом создавать собственные текстовые редакторы для своих языков.
  • 0
    А там с MonoDevelop по плагинам совместимость есть? SharpDevelop, вроде как, изначально его форком был.
    • 0
      Извиняюсь, наоборот. MonoDevelop — форк SharpDevelop'а.
      • 0
        Общие корни просматриваются. Принципы те же, структура файла .addin та же. Хотя дерево другое и названия классов отличаются.
        • 0
          В общем, прямой совместимости нет. Но процесс портирования скорее всего пройдет легко.
  • +2
    SharpDevelop — рулёзная вещь, особенно на менее мощных машинах. Но замечаю за собой что в даже самом простеньком проекте ощущается нехватка чего-то… угадайте чего!
    • +5
      решарпера)
    • +4
      ReSharperа?
    • +4
      Resharper форева! :)
    • 0
      <cарказм>
      Украденного ReSharper'а?
      </cарказм>
      • 0
        Кому украденного, кому купленного, кому бесплатно полученного…
    • +2
      Нормального дебагера? :)
      • +1
        О, это жестоко!
  • 0
    Спасибо большое автору за описание подготовки плагина, сам изучал внутренности SharpDevelop'а, кое что взял за и идею для своего проекта на работе. Кстати, вроде бы и Codon и Doozer присутствуют одновременно в исходниках. вроде бы Codon — это узел дерева аддинов, а Doozer — это строитель элементов этого дерева.
    • 0
      Все верно. Раньше был Codon и CodonBuilder. Были ClassCodon, MenuItemCodon и т. д. Сейчас это ClassDoozer и MenuItemDoozer.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.