Как я должен дополнительно ссылаться на проект библиотеки классов в другом проекте .NET?

Итак, у меня есть проект .NET, в котором используются плагины. Плагины реализованы в виде проектов библиотеки классов (DLL), каждый из которых создается в своей собственной папке. Основной проект не требует запуска подключаемых библиотек DLL, но если они доступны, они используются для различных дополнительных функций. Классы из плагинов загружаются Type.GetType().

Однако для моих собственных целей при тестировании программного обеспечения я хотел бы иметь возможность разрабатывать плагины и основное приложение одновременно. Я создал «главный» файл решения, который ссылается на все проекты, поэтому я могу устанавливать точки останова и переходить границы сборки. Однако проблема в том, что плагины создаются в своих собственных каталогах, поэтому мне нужно каким-то образом получить DLL-файлы плагинов где-то, где Type.GetType() их можно найти.

Изменить: чтобы уточнить, мой код уже перечисляет файлы DLL в том же каталоге, что и .exe, и находит классы, соответствующие заданному интерфейсу (используя Assembly.LoadFile(), Assembly.GetExportedTypes() и Type.IsAssignableFrom()). Затем пользователь выбирает плагины для включения, и я сохраняю эти варианты в базе данных с помощью Type.AssemblyQualifiedName. Наконец, когда мне нужно использовать функции, предоставляемые плагином, я использую Type.GetType() для загрузки класса плагина. Все это работает именно так, как задумано. Просто когда я создаю приложение, я получаю копию .exe без каких-либо плагинов. Я ищу способ структурировать файлы проекта и решения, чтобы я мог разделить основное приложение и проекты плагинов, но при этом иметь возможность отлаживать их вместе в Visual Studio. Имеет ли это смысл?

Пока у меня такие идеи:

  • Добавить ссылки на проекты подключаемых модулей из основного проекта.
    Это имеет то преимущество, что оно всегда будет работать должным образом в моей среде отладки, но вызовет ли это проблемы при развертывании моего приложения без подключаемых модулей ? Я проверил с помощью Dependency Walker, и похоже, что нет прямой зависимости от DLL, созданной ссылкой на проект. Есть ли какие-то скрытые проблемы, о которых нужно знать?

  • Измените все проекты подключаемых модулей для сборки в один и тот же целевой каталог.
    Казалось бы, это работает достаточно хорошо, но это также запутывает мое дерево сборки и затрудняет разработку только одного проект без извлечения других проектов (сейчас все они являются родственными папками в Subversion).

  • Добавить сценарий пост-сборки для копирования плагинов в другой каталог.
    На самом деле это то, что я делаю в данный момент, и это работает достаточно хорошо, но кажется очень хакерским и хрупким. Я хотел бы перейти на другой подход.

  • Найти для Type.GetType() способ поиска библиотек DLL в других каталогах
    Это похоже на то, как я использовал бы LD_LIBRARY_PATH в системе Unix. Очевидно, я хотел бы включить это только для сборок отладки, так как в режиме выпуска это может вызвать множество тонких проблем в системе пользователя. Но возможно ли это? Если да, то как?

Интересно, что в этом руководстве по этому вопросу говорится:

Первое, что нужно сделать, это сослаться на библиотеку классов, которую мы только что создали, и установить выходные данные сборки в тот же каталог.

Мне это кажется неоптимальным. Есть ли способ лучше?


person Daniel Pryden    schedule 05.08.2009    source источник


Ответы (4)


Настройте свои проекты так, чтобы исполняемый файл был скомпилирован в 1 место, а ваши «плагины» скомпилированы в одно и то же место. Вы можете использовать Junctions, чтобы сделать «bin» путем к вашему конечному местоположению.

Также в вашем проекте плагина установите исполняемый файл в качестве хоста отладки.

Если вы еще этого не сделали, убедитесь, что ваш интерфейс не перекомпилируется при каждой компиляции вашего исполняемого файла (если он имеет строгое имя), потому что это заставит вас перекомпилировать каждый из ваших плагинов каждый раз, когда вы перекомпилируете свой исполняемый файл. хост exe.

person Paul Farry    schedule 05.08.2009
comment
Да, это почти мой второй вариант выше. Не оптимально, потому что это означает перемещение по моему исходному дереву, но это похоже на рабочий вариант. Кроме того, +1 за указание на то, что плагины также зависят от версии интерфейса. - person Daniel Pryden; 06.08.2009

Для модели плагина я бы посмотрел на методы Activator.CreateInstance и Activator.CreateInstanceFrom или даже взглянул бы на Assembly.LoadFrom. Эти методы позволяют загружать типы из сборок в каталоги, отличные от «традиционных» выходных каталогов (bin, debug, release).

Принуждение Type.GetType к поиску ваших сборок «плагинов» действительно зависит от того, как вы изменяете политику для загрузчика для поиска сборок. Например, вы можете установить свойство AppDomain.PrivateBinPath, чтобы изменить подкаталоги, в которых загрузчик будет искать ссылочные сборки.

По поводу первого пункта "Добавление ссылок на проекты плагинов". Что ж, это может привести к сложной ситуации, препятствующей расширению в будущем. Что, если я хочу создать новый подключаемый модуль? Ой, теперь у вас нет ссылки на него, и я думаю, что тогда мой плагин не будет работать? Вот почему должен быть другой подход. Один из них мы бы «прощупали» на наличие сборок плагинов.

Примечание
В модели подключаемых модулей все подключаемые модули должны работать с общим интерфейсом (или абстрактным классом), и это действительно эта реализация, которую вы ищете во всех новых подключаемых модулях, которые вы нагрузка. Например:

private void LoadAllPlugins(string path)
{
    foreach(string file in Directory.GetFiles(path, "*.dll"))
    {
        Assembly a = Assembly.LoadFrom(file);
        foreach(Type t in a.GetTypes())
        {
            if (t.GetInterface("IMyPluginInterface", true))
            {
               // we now have a potential plugin type that implements required functionality
            }
        }
    }
}

Наконец, .NET 3.5 поддерживает модель подключаемых модулей (System.AddIn), а также ознакомьтесь с версией Managed Extensibility Framework от Microsoft, которая позволяет разработчикам создавать простые компонуемые подключаемые модули (http://www.codeplex.com/MEF)

person Mike J    schedule 05.08.2009
comment
Спасибо за ответ. На самом деле мой код для загрузки плагинов очень похож на ваш пример кода. (Я использую typeof(IMyInterface).IsAssignableFrom(t) вместо t.GetInterface("IMyInterface"), но идея та же.) Мой вопрос не в том, как загружать сборки плагинов, а в том, как структурировать мои файлы проекта и решения, чтобы упростить работу с ними (в частности, отладку между звонки туда и обратно между плагином и кодом приложения). Имеет ли это смысл? - person Daniel Pryden; 06.08.2009
comment
Ах. Это имеет смысл, я могу предложить то, что я пытался сделать с моделью плагина: просто собрать каждый плагин отдельно (либо с помощью скрипта, либо с помощью какого-либо события после сборки) и установить их вывод на тот же вывод, что и основное приложение (или согласованный в выходном каталоге). При отладке из основного проекта Visual Studio обладает достаточным интеллектом, чтобы находить отладочную информацию для каждого подключаемого модуля всякий раз, когда вы используете тип. - person Mike J; 06.08.2009
comment
Файлы отладочной информации (.PDB) должны быть как минимум доступны из основного проекта, иначе отладка плагинов работать не будет. В большинстве случаев, если отладочная информация не приходит, можно попробовать скопировать их в выходной каталог основного приложения. - person Mike J; 06.08.2009

Просто включите проекты плагинов в свое решение, но только с зависимостью от плагинов к проектам «приложений». Обеспечьте это, сохранив дубликат решения, но только с проектом (проектами) приложения и обеспечив чистоту сборки. Вы также можете рассмотреть этап пост-сборки, который пытается собрать каждый проект плагина отдельно без других.

Еще одна проверка — хотите ли вы обеспечить обратную совместимость или, по крайней мере, знать, когда произошел очевидный сбой. Это проще всего, если у вас есть тестовый проект, который ссылается на подключаемые модули по ссылке dll, а не по ссылке на проект (небольшое раздражение в тестовом проекте). Затем, изменив путь к ссылкам в настройках проекта, вы можете указать на старые версии плагинов. Выполнить это должным образом как часть тестов по сценарию также достаточно просто.

Я бы поддержал предложение использовать систему MEF, если ваши плагины эффективно известны во время компиляции, но не конкретная версия dll, которую вы планируете загрузить.

person ShuggyCoUk    schedule 05.08.2009

Некоторое время назад я создал систему плагинов, используя отражение. Механизм подобен тому, что используется в drupal, который я нахожу довольно гибким.

У меня есть две статьи, объясняющие это: Система плагинов для C#. Часть I. Концепции и Система плагинов с C#. Часть II. El código explicado, но у меня пока нет времени их переводить (поэтому они на испанском). Тем не менее код прокомментирован на английском языке (если я правильно помню).

Я попытаюсь объяснить свой подход здесь.

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

Тогда у вас есть два вида аспектов для плагинов.

Сначала подключаемый модуль может предложить услугу другим классам, реализовав общедоступный (известный) интерфейс и пометив себя как услугу с помощью «ServiceAttribute». Затем система плагинов зарегистрирует сервис и сделает его общедоступным для всех, кто заинтересован в его использовании. Например:

ICrypt crypto = Plugins.Service["Crypt"] as ICrypt;
if (crypto != null)
  crypto.Crypt(data, key, etc)

С другой стороны, у вас есть крючки. Хуки — это события плагина. У вас есть два атрибута HookAttribute и HookableAttribute.

Когда вы объявляете метод как Hookable, вы говорите, что ожидаете, что он будет перехвачен другими плагинами. Например

[Hookable]
public event TextChanged OnTextChanged;
//.....
if (OnTextChanged != null)
  OnTextChanged(ref myText);

и в другом плагине

[Hooks("OnTextChanged")]
public bool MyTextChanged(ref myText)
{
  myText = "Hello from the plugin";
  return true;
}

Подпись ловушки (делегат) может быть неизвестна до тех пор, пока подпись совпадает. Опять же, система плагинов обрабатывает совпадения и объединяет их.

Он также включает в себя некоторые атрибуты для выражения зависимостей между плагинами и автоматическим обнаружением и загрузкой и тому подобными вещами. Сам проект находится под лицензией LGPL, так что вы можете найти в нем какие-то идеи или даже лучше... внести свой вклад :P

Кстати, я использую подпапку Plugins, куда помещаю плагины. У меня нет проблем с их отладкой из VS, если проект плагина является частью решения, над которым я работаю. Мне не нужно ссылаться на них или что-то еще. Если плагин является частью решения, я могу его отладить.

person Jorge Córdoba    schedule 05.08.2009
comment
Это выглядит очень интересно. Сейчас немного поздно использовать это для моего текущего проекта, но я запомню это для будущего использования. Для всех, кому интересно, перевод Google довольно понятен: http://translate.google.com/translate?u=http://www.stackframe.net/es/content/01-2009/sistema-de-plugins-con-c-parte-i-conceptos&sl=es&tl=en&hl=en&ie=UTF-8. (Хотя мне нравится, как utilizado una librería переводится как «возьми подержанный книжный магазин»... - person Daniel Pryden; 06.08.2009