
- •Добавление к com-серверу поддержки событий
- •Создание модели взаимодействия приложения хоста с плагинами
- •Описание com-интерфейсов, связанных с поддержкой плагинов
- •Создание com-интерфейсов, связанных с поддержкой плагинов
- •Создание com-классов, связанных с поддержкой плагинов
- •Создание класса, отвечающего за взаимодействие с плагинами
- •Код класса Plugin.Cs
- •Внесение косметических изменений в серверную часть
- •Строим своё меню с плагинами и идентификаторами
- •Вносим изменения в главную форму сервера
- •Создание первого плагина
- •Импорт типов с сервера
- •Создание класса для хранения внутреннего представления точек
- •Создание форм редактирования данных о точках
- •Реализация форм редактирования данных о точках
- •Реализация класса первого плагина
- •Создание третьего плагина
- •Делаем наш плагин com-видимым
- •Создание главной формы плагина
- •Реализация класса третьего плагина
- •Создание инсталлятора для плагина
- •Создание второго плагина
- •Создание главной формы плагина
- •Создание библиотеки типов
- •Реализация формы второго плагина
- •Реализация класса второго плагина
- •Добавление метода GetComClassName
- •Data Execution Prevention и его отключение
- •Тестирование совместной работы клиента и плагинов
- •Исходный код
Создание инсталлятора для плагина
Добавим в решение новый проект.
Это будет приложение Windows Forms.
Далее, установим зависимости и порядок построения.
Теперь регистратор будет строится после библиотеки плагина. Назначим его запускаемым проектом.
Немного схитрим и добавим ссылку на библиотеку плагина, дабы она после построения копировалась в директорию к исполняемому файлу регистратора.
Для того, чтобы зарегистрировать нашу библиотеку в реестре, нам потребуется, чтобы регистратор после запуска требовал права администратора. Ведь на Win7 и Vista для этого обычно требуются права администратора – все подряд в реестр писать не могут. В .NET можно сделать так, чтобы запускаемое приложение перед запуском требовало повышения прав. Методику я взял отсюда.
Добавим в проект регистратора файл manifest.xml.
Вставляем туда следующий текст:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0" name="ElevationTest" type="win32"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
Укажем в свойствах xml-ки, что мы хотим, чтоб файл копировался при построении в выходную директорию.
Теперь откроем свойства проекта регистратора (Alt+Enter), вкладка «События построения». Добавим события после построения следующий код:
"C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\mt.exe" -manifest "$(ProjectDir)manifest.xml" –outputresource:"$(TargetDir)$(TargetFileName)";#1
del "$(TargetDir)manifest.xml"
Этим самым мы назначим манифест нашему приложению и удалим его после этого. Теперь после построения исполняемый файл будет всегда требовать повышения перед запуском.
Добавим несложную форму RegistrationForm.cs в проект регистратора:
Привожу код файла RegistrationForm.Designer.cs:
namespace Plugin3Registrator
{
partial class RegistrationForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.register_button = new System.Windows.Forms.Button();
this.unregister_button = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// register_button
//
this.register_button.Location = new System.Drawing.Point(12, 12);
this.register_button.Name = "register_button";
this.register_button.Size = new System.Drawing.Size(336, 23);
this.register_button.TabIndex = 0;
this.register_button.Text = "Register";
this.register_button.UseVisualStyleBackColor = true;
this.register_button.Click += new System.EventHandler(this.register_button_Click);
//
// unregister_button
//
this.unregister_button.Location = new System.Drawing.Point(12, 41);
this.unregister_button.Name = "unregister_button";
this.unregister_button.Size = new System.Drawing.Size(336, 23);
this.unregister_button.TabIndex = 1;
this.unregister_button.Text = "Unregister";
this.unregister_button.UseVisualStyleBackColor = true;
this.unregister_button.Click += new System.EventHandler(this.unregister_button_Click);
//
// RegistrationForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(360, 79);
this.Controls.Add(this.unregister_button);
this.Controls.Add(this.register_button);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "RegistrationForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Plugin registrator";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button register_button;
private System.Windows.Forms.Button unregister_button;
}
}
Копипастите его себе и форма принимает идентичный моему вид. Перейдём к коду формы.
Принцип регистрации плагина будет примерно таким же, каким была регистрация сервера, только значительно проще, ибо плагин будет предоставлять InProc COM server.
Наш плагин является точно таким же сервером. Приложение-хост будет запрашивать с него объект, который реализован на нём. Ведь класс Plugin2.ZigzagPlugin – такой же COM-класс, как и ZigzagControl. Таким образом, когда мы говорим, что наш плагин использует экземпляр ZigzagControl, то сервером является приложение-хост, а клиентом – плагин. Наоборот, когда мы говорим про то, что плагин экспонирует для приложения-хоста класс Plugin2.ZigzagPlugin, то сервером является уже плагин, а клиентом – приложение-хост. Такая вот теория относительности, дамы и господа.
Подключим неймспейсов в файл.
using System.Runtime.InteropServices;
using System.Reflection;
using System.Diagnostics;
Перейдём к классу формы. Добавим туда пути к утилите RegAsm.exe.
const string v4RegAsmPath = @"C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe";
const string v2RegAsmPath = @"C:\Windows\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe";
Потом конструктор:
public RegistrationForm()
{
InitializeComponent();
}
Теперь метод, который вызывает RegAsm с параметрами, переданными в него в качестве аргумента.
/// <summary>
/// Вызывает RegAsm.exe с заданными параметрами.
/// </summary>
/// <param name="param">Параметры запуска RegAsm.exe</param>
/// <returns>Возвращает exit code программы.</returns>
static int CallRegAsm(string param)
{
int result = 0;
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = v4RegAsmPath; //Для регистрации в более ранних версиях Framework используйте переменную v2RegAsmPath
info.Arguments = param;
info.RedirectStandardOutput = true; //В случае, если надо будет отлаживать, вывод regasm.exe можно будет получить в regasm.StandardOutput.ReadToEnd()
info.UseShellExecute = false;
Process regasm = new Process();
regasm.StartInfo = info;
regasm.Start();
regasm.WaitForExit();
result = regasm.ExitCode;
//MessageBox.Show(regasm.StandardOutput.ReadToEnd(), "RegAsm.exe output");
return result;
}
И наконец обработчики нажатия на кнопки.
private void register_button_Click(object sender, EventArgs e)
{
try
{
string path = Environment.CurrentDirectory + "\\Plugin3.dll";
Assembly pluginAssembly = Assembly.LoadFile(path);
//Запустим RegAsm.exe для регистрации интерфейсов сборки и создания библиотеки типов. Для этого сгенерируем параметры запуска.
string assemblyDirectoryPath = pluginAssembly.Location.Remove(pluginAssembly.Location.LastIndexOf('\\') + 1);
string assemblyName = pluginAssembly.Location.Substring(pluginAssembly.Location.LastIndexOf('\\') + 1);
assemblyName = assemblyName.Remove(assemblyName.LastIndexOf('.')); // Получим имя сборки.
// Зададим параметры RegAsm-а, чтобы тут использовал codebase и создавал .tlb-файл с именем сборки
string regasmParams = "\"" + pluginAssembly.Location + "\" /s /codebase /tlb:\"" + assemblyDirectoryPath + assemblyName + ".tlb\"";
int exitCode = CallRegAsm(regasmParams); // Вызовем RegAsm.exe.
if (exitCode != 0)
throw new Exception("RegAsm.exe вернул результат, отличный от нуля. Exit code: " + exitCode.ToString());
MessageBox.Show("Done");
}
catch (Exception exc)
{
string message = "Message: " + exc.Message + "\n\r";
string stack = "Stack trace: " + exc.StackTrace + "\n\r";
MessageBox.Show(message + stack, "На сервере произошло исключение.", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void unregister_button_Click(object sender, EventArgs e)
{
string path = Environment.CurrentDirectory + "\\Plugin3.dll";
Assembly pluginAssembly = Assembly.LoadFile(path);
string regasmParams = pluginAssembly.Location + " /u";
int exitCode = CallRegAsm(regasmParams);
if (exitCode != 0)
throw new Exception("RegAsm.exe вернул результат, отличный от нуля. Exit code: " + exitCode.ToString());
MessageBox.Show("Done");
}
Каждый из таких обработчиков находит путь к сборке плагина и создаёт соответствующие строки аргументов утилиты RegAsm. Вызов этой утилиты делает за нас всю грязную работу по регистрации сборки плагина в реестре.
Поздравляю, плагин готов, можете тестировать его работоспособность. И пора уже переходить к последней, хардкорной части с Borland Builder-ом.