IronPython как движок для макросов в .NET приложениях
Для начала, следует определится — что мы будем иметь в виду под словом «макрос» — это скрипт, который без перекомпиляции проекта позволял бы получить доступ к определенному API. Т.е. вытаскивать значения с формы, модифицировать их — и все это в режиме run-time, без модификации приложения.
Первым вариантом, который приходит на ум будет создание собственного интерпретатора для простенького скрипт-языка. Вторым — будет динамическая компиляция какого-нибудь .NET языка (того же C#) — с динамической же подгрузкой сборок и выполнением через Reflection. И третий — использование интерпретируемых .NET языков (DLR) — IronPython или IronRuby.
Создавать свой язык + интерпретор к нему с возможностью .NET interoperability — задача нетривиальная, оставим ее для энтузиастов.
Динамическая компиляция — слишком громоздко и тащит за собой использование Reflection. Однако, этот метод не лишен преимуществ — написанный макрос компилируется единожды и в дальшейшем может использоватся многократно — в виде полноценной .NET сборки. Итак — финалист — метод номер три — использование существующих DLR языков. В качестве такого языка выбираем IronPython (примите это как факт :). Текущая версия IPy — 2.0, взять можно на codeplex.com/IronPython
Перейдем непосредствено к кодированию.
Для начала, рассмотрим интерфейс тестового приложения «Notepad».
private void Main_Load(object sender, EventArgs e)
{
MacrosToolStripMenuItem itm = null;
string[] files = Directory.GetFiles(@".\Macroses");
foreach (string file in files)
{
itm = new MacrosToolStripMenuItem(Path.GetFileNameWithoutExtension(file)) { MacrosFileName = file };
itm.Click += new EventHandler(macroToolStripMenuItem_Click);
макросыToolStripMenuItem.DropDownItems.Add(itm);
}
}
internal class MacrosToolStripMenuItem : ToolStripMenuItem
{
public MacrosToolStripMenuItem(string FileName) : base(FileName) { }
public string MacrosFileName { get; set; }
}
MacrosToolStripMenuItem — класс-наследник от ToolStripMenuItem отличающийся только свойством MacrosFileNameДля начала, создадим макрос, который просмотрит текст в textBox`е и найдет все e-mail адреса вида «Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в вашем браузере должен быть включен Javascript.». В папке Macroses создаем файл SaveEmail.py, запускаем приложение — и смотрим, что в меню Макросы появился пункт SaveEmail.
Теперь собственно ключевой момент — выполнение IPy скрипта и доступ его к интерфейсу. Добавляем к проекту ссылку на сборку IronPython.dll. И создаем класс MacroRunner — выполняющий скрипт.
public class MacroRunner
{
public static Form CurrentForm;
public string FileName { get; set; }
public MacroRunner() { }
public void Execute()
{
// собственно среда выполнения Python-скрипта
IronPython.Hosting.PythonEngine pyEngine = new IronPython.Hosting.PythonEngine();
// важный момент - к среде выполнения подключаем текушую выполняемую сборку, т.к.
// в ней собственно и объявлена форма, к которой необходимо получит доступ
pyEngine.LoadAssembly(System.Reflection.Assembly.GetExecutingAssembly());
try
{
pyEngine.ExecuteFile(FileName);
}
catch (Exception exc)
{
MessageBox.Show(exc.Message);
}
}
}
Ключевой момент — подключение к выполняющей среде IPy текущей сборки — для доступа к форме. Когда сборка подключена, в IPy скрипте появится возможность использовать классы пространства имен Notepad. Так же, через LoadAssebmly можно добавить и другие необходимые сборки — типа System.Windows.Forms — чтобы работать с формами.
Класс готов, теперь модифицируем обработчик клика на пунктах подменю Макросы
protected void macroToolStripMenuItem_Click(object sender, EventArgs e)
{
MacrosToolStripMenuItem item = sender as MacrosToolStripMenuItem;
MacroRunner runner = new MacroRunner() { FileName = item.MacrosFileName };
MacroRunner.CurrentForm = this;
runner.Execute();
}
Здесь следует отметить следующий момент — чтобы передать в IPy-скрипт форму, из которой собственно вызывается макрос — используется статическое поле CurrentForm. В скрипте форма будет доступна как Notepad.MacroRunner.CurrentForm. В идеале, скрипт, разумеется, не должен иметь полного доступа к интерфейсу формы — а должен пользоватся только предоставленным API — и ограничиваться только им. Но сейчас этим заморачиваться не будем — и просто сделаем textBox открытым (Modifier = Public). Ну и кроме текстового поля, разрешим скрипту доступ к пункту меню Сервис (Modifier = Public).Работа с формой закончена, собираем проект и открываем файл SaveEmail.py — теперь работаем только с макросами.
Итак, первый макрос — SaveEmail.py:
from Notepad import *
import re
text = MacroRunner.CurrentForm.textBox.Text
links = re.findall("\w*@\w*\.\w{2,4}", text)
file = open("emails.txt", "w")
file.write("\n".join(links))
file.close()
Т.к. сборка подключена к среде выполнения — то доступно пространство имен Notepad — в котором объявлены классы приложения. Как раз сейчас потребуется статический метод класса MacroRunner — чтобы получить доступ к активной форме (еще раз оговорюсь — что правильнее было бы предоставить не прямой доступ, а через класс-посредник — которые ограничит доступ определенным API). Ну а дальше все просто — получаем текст, регулярным выражением вытаскиваем email — и сохраняем их в файл в текущем каталоге.Можно запустить приложение, ввести произвольный текст, содежащий email — и убедиться, что после того, как макрос отработал — в папке с выполняемой программой появился файл emails.txt.
Теперь еще один пример, что может сделать макрос — чуть интереснее предыдущего. Итак, создаем в папке Macroses файл UIModifier.py. Как можно догадаться по названию — макрос будет изменять элементы интерфейса приложения. Конкретно — добавит новый пункт в меню Сервис. Для того, чтобы можно было работать с элементами управления WinForms необходимо в среде выполнения IPy подключить сборку System.Windows.Forms. Это можно сделать в момент запуска скрипта из приложения — добавить еще один вызов LoadAssembly. Но мы решили — никаких перекомпиляций, пусть IronPython обходится своими силами. Ну что ж, силы есть :). Чтобы подключить сборку используется метод AddReference класса clr.
from Notepad import *
main = MacroRunner.CurrentForm
import clr
clr.AddReference("System.Windows.Forms")
from System.Windows.Forms import *
def pyHello(s,e):
MessageBox.Show("Hello from IPy!")
item = ToolStripMenuItem()
item.Name = "pybtn"
item.Text = "Python created!"
item.Click += pyHello
main.сервисToolStripMenuItem.DropDownItems.Add(item)
Все просто — получаем текущую форму, подключаем сборку System.Windows.Forms и импортируем из пространства имен System.Windows.Forms все — пригодится.
pyHello — простенький обработчик события — при щелчке на созданном пункте меню — будет выводится сообщение.
Запускаем приложение, выполняем макрос. Смотрим пункт меню Сервис:
При щелчке на пункт меню «Python сreated!» появится стандартный MessageBox — собственно, чего и добивались.
Спасибо всем за внимание :)