2010-04-23 13 views
6

Je souhaite avoir une application qui fonctionne comme hôte pour de nombreuses autres petites applications. Chacune de ces applications devrait fonctionner comme une sorte de plugin pour cette application principale. Je les appelle des plugins non pas dans le sens où ils ajoutent quelque chose à l'application principale, mais parce qu'ils ne peuvent fonctionner qu'avec cette application hôte car ils dépendent de certains de ses services.Question sur la façon de mettre en œuvre une application hôte C# avec une architecture de type plug-in

Mon idée était de faire fonctionner chacun de ces plugins dans un domaine d'application différent. Le problème semble être que mon application hôte devrait avoir un ensemble de services que mes plugins voudront utiliser et d'après ce que je comprends, faire en sorte que les données entrent et sortent des différents domaines d'applications n'est pas une bonne chose. D'une part, je voudrais qu'ils se comportent comme des applications autonomes (bien que, comme je l'ai dit, ils doivent utiliser plusieurs fois les services de l'application hôte), mais d'autre part, je voudrais que si l'un d'entre eux se bloque, mon application principale n'en souffrirait pas. Quelle est la meilleure approche (.NET) pour ce genre de situation?

Faites-les tous courir sur le même AppDomain mais chacun dans un thread différent? Utiliser différents AppDomains? Un pour chaque "plugin"? Comment les ferais-je communiquer avec l'application hôte? Un autre moyen de le faire?

Bien que la vitesse ne soit pas un problème ici, je ne voudrais pas que les appels de fonction soient beaucoup plus lents qu'ils ne le sont lorsque nous travaillons avec une simple application .NET.

Merci

EDIT: Peut-être que je vraiment besoin d'utiliser différents domaines d'application. D'après ce que j'ai lu, le chargement d'assemblys dans différents AppDomains est le seul moyen de pouvoir les décharger ultérieurement du processus.

Répondre

5

J'ai implémenté quelque chose le long de ces lignes en utilisant le MAF (Managed Addin Framework) dans l'espace de noms System.Addin. Avec MAF vous empaquetez vos addins en tant que DLL séparées, que votre application hôte peut découvrir et lancer dans son domaine d'application, dans un domaine distinct pour tous les addins, ou chaque addin dans son propre domaine. Avec les clichés instantanés et les domaines séparés, vous pouvez même mettre à jour un addin sans arrêter votre hostapp.

Votre application hôte et les modules d'extension communiquent via des contrats que vous dérivez d'interfaces MAF. Vous pouvez envoyer des objets entre l'hôte et les addins. Les cotnracts fournissent une interface boîte noire entre les addins et l'hôte, vous permettant de modifier l'implémentation d'un addin à l'insu de l'hôte.

Les modules complémentaires peuvent même communiquer entre eux si l'hôte les signale les uns aux autres. Dans mon cas, un addin de journalisation est partagé par les autres. Cela me permet de laisser tomber dans différents enregistreurs sans toucher les autres addins ou l'hôte.

Pour mon application, l'addin utilise des classes de superviseur simples qui lancent des classes de travailleurs sur leurs propres threads qui effectuent tout le traitement. Les travailleurs saisissent leurs propres exceptions, qu'ils retournent à leur superviseur par le biais de méthodes de rappel. Les superviseurs peuvent redémarrer les travailleurs ou prendre d'autres mesures. L'hôte contrôle les superviseurs via un contrat de commande, qui leur demande de démarrer et d'arrêter les employés et de renvoyer les données.

Mon application hôte est un service Windows.Les threads de travail ont jeté des exceptions pour toutes les raisons habituelles (y compris les bugs!), Mais l'application hôte n'a jamais planté dans aucune de nos installations. Comme les services de débogage ne sont pas pratiques, les addins me permettent de créer des applications de test qui utilisent les mêmes contrats, avec une assurance supplémentaire que je teste ce que je déploie.

Les extensions peuvent également exposer des éléments d'interface utilisateur. Ceci est très utile pour moi car j'ai besoin de déployer une application de contrôleur avec le service hôte, car les services n'ont pas d'interface utilisateur. Chaque plugin inclut sa propre interface de contrôleur. L'application du contrôleur elle-même est très simple - elle charge les addins et affiche leurs éléments d'interface utilisateur. Cela me permet d'expédier un addin mis à jour avec une interface mise à jour et ne pas avoir à expédier un nouveau contrôleur.

Même si le contrôleur et le service hôte utilisent les mêmes extensions, ils ne se superposent pas; en fait, ils ne savent même pas qu'une autre application utilise les mêmes addins. Le contrôleur et l'hôte communiquent entre eux via une base de données partagée, mais vous pouvez également utiliser un autre mécanisme inter-app comme MSMQ. Dans la prochaine version, l'hôte sera un service WCF avec des extensions sur le backend et des services web pour le contrôle.

C'est un peu long, mais je voulais vous donner une idée de la polyvalence de MAF. Ce n'est pas aussi complexe que ça pourrait l'être, et vous pouvez créer des applications solides avec ce logiciel.

1

Ceci est une question intéressante. Ma première idée était de simplement implémenter les interfaces de votre application hôte dans vos applications plugin pour leur permettre de communiquer via Reflection, mais cela ne permettrait que la communication et n'apporterait pas une véritable architecture "sandbox-like".

Ma deuxième pensée était de concevoir une plate-forme orientée service. L'application hôte serait une sorte de "plugin broadcaster" qui publierait vos plugins dans un ServiceHost sur un thread différent. Comme cela doit être vraiment réactif et "non brainer configuré", l'application hôte peut communiquer avec le plugin via le canal des canaux nommés (NetNamedPipesBinding for WCF), ce qui signifie qu'elle communique uniquement avec les conduits locaux et n'a besoin d'aucune configuration réseau. . Je pense que cela pourrait être une bonne solution à votre problème.

Cordialement.

2

Cela dépend de la confiance que vous souhaitez accorder aux extensions. Je travaille sur une application similaire et j'ai choisi de faire principalement confiance au code d'extension, car cela simplifie grandement les choses. J'appelle le code à partir d'un fil commun (dans mon cas, les extensions ne «tournent» pas vraiment dans une boucle continue, mais exécutent plutôt certaines tâches que l'application principale veut faire) et attrapent des exceptions dans ce thread, de sorte que pour fournir des avertissements utiles que les extensions chargées se comportent mal.

Actuellement, il n'y a rien qui empêche ces extensions de lancer leurs propres threads qui pourraient lancer et bloquer l'application entière, mais ceci où j'ai dû faire le compromis entre la sécurité et la complexité. Mon application n'est pas critique (pas comme un serveur web ou un serveur de base de données), donc je considère comme un risque acceptable qu'une extension boguée puisse faire tomber mon application.Je fournis des protections pour couvrir plus poliment les cas d'échec les plus communs et laisser aux développeurs de plugins (qui seront pour la plupart des internes pour le moment) de nettoyer leurs bugs.


En ce qui concerne Déchargement, oui, vous ne pouvez décharger le code et les métadonnées pour un ensemble si vous le placez dans un AppDomain. Cela dit, à moins que vous ne vouliez charger et décharger fréquemment pendant la durée de vie de votre programme, les frais généraux associés à la conservation du code en mémoire ne sont pas nécessairement un problème. Toutes les instances ou ressources utilisant des types de l'assemblage seront toujours nettoyées par le GC lorsque vous arrêterez de l'utiliser, donc le fait qu'il soit encore en mémoire n'implique pas de fuite de mémoire. Si votre cas d'utilisation principal est une série de plugins que vous localisez une fois au démarrage et que vous fournissez ensuite une option pour instancier pendant que votre application est en cours d'exécution, je suggère d'étudier l'empreinte mémoire réelle associée à leur chargement au démarrage et les garder chargés. Si vous utilisez AppDomains, il y aura également une surcharge supplémentaire (par exemple, la mémoire pour les objets proxy et le code chargé/JITed pour prendre en charge le marshaling AppDomain). Il y aura également un surcoût du processeur associé au marshaling et à la sérialisation du standard.

En bref, je ne l'utilise AppDomains si une des options suivantes étaient vraies:

  • Je veux obtenir une véritable isolation aux fins de la sécurité de code (je dois exécuter du code non sécurisé dans un endroit isolé chemin)

  • Mon application est critique et je dois absolument m'assurer que si un plugin tombe en panne, il ne peut pas faire tomber mon application de base.

  • Je dois charger et décharger plusieurs fois le même plugin, afin de prendre en charge les changements dynamiques de la DLL. C'est principalement si mon application ne peut pas arrêter de fonctionner, mais je veux hot-patch plugins pendant qu'il est encore en cours d'exécution.

Je ne préférerais pas AppDomains dans le seul but de réduire l'empreinte mémoire possible en autorisant le déchargement.

+0

Ceci est pour une application de passe-temps, rien de spécial. Je fais entièrement confiance au code courant. Le problème est que j'ai besoin que chacun de mes plugins fonctionne à tout moment (ie je ne peux pas simplement faire en sorte que mon application hôte appelle mes plugins, ils doivent être indépendants et s'exécuter eux-mêmes.) –

+1

chacun leur propre Thread pour fonctionner et appeler n'importe quelle méthode 'Main' que vous avez définie. Vous pouvez choisir d'enrouler l'appel sur 'Main' à partir du thread dans un try/catch si vous souhaitez fournir un repli amical dans le cas où un plugin lève une exception. –

+0

Oui, mais voir Modifier ci-dessus. De cette façon, je ne pouvais plus décharger les assemblys chargés sans redémarrer l'application. Je dois les avoir dans différents domaines d'application pour pouvoir les décharger quand je le veux. –