Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > .NET Frameworks (точка нет фреймворки) > Общие вопросы .NET
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 11.01.2010, 14:39   #1
ds.Dante
Старожил
 
Аватар для ds.Dante
 
Регистрация: 06.08.2009
Сообщений: 2,992
По умолчанию Программа с динамически подключаемыми библиотеками (плагинами)

Недавно пришлось освоить такую технологию, теперь хочу поделиться с форумчанами, кому надо.

Я попробовал несколько вариантов - интерфейс плагина в отдельной DLL, в отдельном .cs, или встроенный в .exe; остановился на последнем.

1) Создаём проект основной программы (Windows Forms или Console Application), в нём добавляем файл с интерфейсом классов плагинов (Project -> Add New Item -> Interface). Интерфейс - это класс, в котором объявлены прототипы методов. Если класс наследуется от интерфейса, он обязан переопределить все его методы, см. DSPluginInterface.cs.
2) В тот же solution добавляем Class Library Project, в References добавляем ссылку на проект основной программы, содержащей интерфейс. Наследуем класс плагина от интерфейса. См. DSPluginTxtIO.cs.
3) Пишем основную программу, которая ищет *.dll, а в них - объекты, унаследованные от интерфейса (см. Program.cs).

Конкретную реализацию смотрите в проектах, далее опишу только их настройку.

Тут такая хитрость: во время компиляции проект плагина жёстко зависит от основной программы. В то же время, когда мы жмём F5, мы хотим, чтобы перед запуском все плагины автоматически скомпилировались и скопировались в папку основной программы. Обычно это достигается установкой зависимостей (Project Dependencies), но в нашем случае это приведёт к циклическим зависимостям. На самом деле эти зависимости возникают в разное время, но Visual Studio этого не знает, и выдаёт ошибку. Поэтому мы прибегнем к полуавтоматическому копированию файла *.dll в папку Debug или Release. Это достигается настройкой проекта плагина (обратите внимание - не основной программы): Project Properties -> Build Events -> Post-build... нужно написать так:
copy "$(TargetPath)" "$(SolutionDir)DSPluginsApplication \$(OutDir)"
при этом, конечно, заменив DSPluginsApplication на имя проекта вашей основной программы. Обратите внимание: эта строка не зависит от типа компиляции (Debug или Release), это необходимо, так как у разных типов компиляции нельзя задать разные настройки. При нажатии F5 происходит следующее:
1) компилируется основная программа
2) с использованием первого скомпилированного проекта компилируется плагин (зависимость A->B)
3) плагин копируется к основной программе
4) программа запускается с плагином у себя (зависимость B->A)


Стороннему разработчику, чтобы написать плагин, нужно скопировать программу (exe) себе в проект, и указать этот файл в References. При этом программист может написать в своём проекте, скажем, имя интерфейса, нажать Go To Definition, и он увидит исходный код интерфейса - это что-то вроде дизассемблирования.

Далее - исходники. В моём примере плагин читает из input.txt, и пишет в output.txt. Программа ищет нужные классы во всех DLL, подключает, предлагает выбрать, и запускает функции Read() и Write() (см. интерфейс). В конце поста прикреплён весь проект.

Интерфейс плагинов, DSPluginInterface.cs:
Код:
// Plugin Interface (used both by program and by plugin projects)

using System;

namespace DSPlugins
{
	public interface DSPluginInterface
	{
		string Name { get; }

		object Read();
		void Write (object Data);
	}
}

Сам плагин, DSPluginTxtIO.cs:
Код:
// Plugin (referenced to main program for interface)

using System;
using System.IO;
// "using DSPlugins" is not needed, because we use "namespace DSPlugins { }" further


namespace DSPlugins
{
	public class DSPluginIOTxt: DSPluginInterface	// Derive plugin interface
	{
		public string Name	// Plugin name
		{
			get { return "File I/O"; }
		}

		public object Read()	// Read form intput.txt
		{
			StreamReader Reader = new StreamReader ("input.txt");
			string Data = Reader.ReadToEnd ();
			Reader.Close();
			return Data;
		}

		public void Write (object Data)	// Write to output.txt
		{
			StreamWriter Writer = new StreamWriter ("output.txt");
			Writer.Write (Data);
			Writer.Close();
		}
	}
}

Последний раз редактировалось ds.Dante; 11.01.2010 в 14:49.
ds.Dante вне форума Ответить с цитированием
Старый 11.01.2010, 14:41   #2
ds.Dante
Старожил
 
Аватар для ds.Dante
 
Регистрация: 06.08.2009
Сообщений: 2,992
По умолчанию

Основная программа, Program.cs:

Код:
// Main program that load and use plugins

using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using DSPlugins;

class Program
{
	static void Main()
	{
		try
		{
			List <DSPluginInterface> Plugins = new List<DSPluginInterface>();	// Container for instances of all our plugin classes
			foreach (string DLLPath in Directory.GetFiles (AppDomain.CurrentDomain.BaseDirectory, "*.dll"))	// Find all *.dll (also in subfolders)
			{
				try
				{
					Assembly assembly = Assembly.LoadFile (DLLPath);
					foreach (Type type in assembly.GetTypes())
						if (type.GetInterface ("DSPluginInterface") != null)	// Check if the class is derived from our interface
							Plugins.Add ((DSPluginInterface) Activator.CreateInstance (type));	// Add instance (not class as is)
				}
				catch (Exception) { }	// Don't mind wrong DLL's and so on
			}

			if (Plugins.Count == 0)
				throw new Exception ("Plugins not found");


			for (int i=0; i<Plugins.Count; i++)	// Show plugins list
				Console.WriteLine ("{0} - {1}", i+1, Plugins[i].Name);
			Console.WriteLine ("Select number of plugin or press escape to exit.");

			while (true)
			{
				ConsoleKey KeyPressed = Console.ReadKey().Key;
				Console.CursorLeft = 0;

				if (KeyPressed == ConsoleKey.Escape)
					break;

				int num = KeyPressed - ConsoleKey.D1;	// Number of chosen plugin
				if (num<0 || num>=Plugins.Count)
					continue;
				object Data = Plugins[num].Read();		// Use the plugin
				Plugins[num].Write (Data);
				Console.WriteLine ("Data: " + Data);
			}
		}
		catch (Exception exc)
		{
			Console.Write (exc.Message);
			Console.ReadKey();
		}
	}
}
Весь проект:
Вложения
Тип файла: rar DSPlugins.rar (11.9 Кб, 20 просмотров)
ds.Dante вне форума Ответить с цитированием
Старый 12.01.2010, 22:13   #3
BOZKURT
Пользователь
 
Регистрация: 14.10.2009
Сообщений: 70
По умолчанию

Спасибо. Мне тоже скоро надо этим заняться (опыта нету), буду изучать.
BOZKURT вне форума Ответить с цитированием
Старый 18.01.2010, 00:40   #4
Никки
Форумчанин Подтвердите свой е-майл
 
Аватар для Никки
 
Регистрация: 20.11.2007
Сообщений: 500
По умолчанию

Как раз хотел задать вопрос по этой теме, а вы уже на всё ответили!

Но вот что ещё интересует: можно же интерфейс для плагинов скомпилировать в отдельную библиотеку и подключать к проекту плагинов её, а не сам исполняемый файл??
Никки вне форума Ответить с цитированием
Старый 18.01.2010, 10:45   #5
ds.Dante
Старожил
 
Аватар для ds.Dante
 
Регистрация: 06.08.2009
Сообщений: 2,992
По умолчанию

Можно. Для этого нужно создать проект Class Library, состоящий из одного интерфейса. Полученную DLL подключать к основному проекту и к плагинам.

Плюс такого подхода - никаких циклических зависимостей, проекты зависят только от этой DLL.

Минус - лишний файл в программе. Кто-нибудь скопирует прогу, а у него - бах! - ошибка: не найден файл DSPluginInterface.dll.
ds.Dante вне форума Ответить с цитированием
Старый 13.04.2010, 18:02   #6
BOBAH13
Android Developer
Старожил Подтвердите свой е-майл
 
Аватар для BOBAH13
 
Регистрация: 19.02.2007
Сообщений: 3,708
По умолчанию

Благодарю за пример. Самому скоро придется реализовывать, а пока время тратить на разборку не хотелось. Спасибо еще раз.

Как дополнение, или уточнение, это Windows Forms или WPF ?
Меня собственно интересует WPF, т.е. если интерфейс (программа) UserControl в .dll файле, то основное приложение (.exe) может грузить и как я понимаю, брать этот UserControl и обращаться с ним как с таким же UserControl если бы он был в самой программе (.exe) ? Такой заковыристый вопрос, просто если знаете то подтвердите мою теорию, если нет, то ладно, придет время сам разберусь.
BOBAH13 вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Работа с библиотеками типов(TLB) в С++ MadBoxer Общие вопросы C/C++ 1 14.05.2009 16:59
работа с библиотеками kuzmich Общие вопросы Delphi 2 25.02.2009 19:39
Mozilla Firefox 3.0.4 Глюки с плагинами? iankov Софт 0 09.01.2009 17:44
проблемма с подключаемыми файлами ratibor32 Общие вопросы C/C++ 4 18.01.2008 11:36