7
votes

Question sur la mise en œuvre d'une application hôte C # avec une architecture de plugin

Je veux avoir une application qui fonctionne comme hôte à de nombreuses autres petites applications. Chacune de ces applications devrait fonctionner comme une sorte de plugin à cette application principale. Je les appelle des plugins non dans le sens où ils ajoutent quelque chose à l'application principale, mais parce qu'ils ne peuvent travailler qu'avec cette application hôte, car ils dépendent de certains de ses services.

Mon idée était d'avoir chacun de ces plugins exécutés dans un domaine d'application différent. Le problème semble être que ma demande d'hôte devrait avoir un ensemble de services que mes plug-ins tiens à utiliser et à partir de ce que ma compréhension est de faire des flux de données dans les différents domaines d'applications, n'est pas si génial d'une chose.

D'une part J'aimerais qu'ils se comportent comme des applications autonomes (bien que, comme je l'ai dit, ils doivent utiliser beaucoup de fois les services d'application hôte), mais d'autre part j'aimerais que le cas échéant d'entre eux se bloque, ma principale application n'en souffrirait pas.

Quelle est la meilleure approche (.NET) de ce type de situation? Faites-les tous courir sur le même appdomain mais chacun dans un fil différent? Utiliser différents appdomains? Un pour chaque "plugin"? Comment pourriez-je les communiquer avec l'application hôte? Toute autre façon de faire cela?

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

merci

edit: peut-être que j'ai vraiment besoin d'utiliser différentes AppDomains. D'après ce que j'ai lu, le chargement des assemblages dans différentes AppDomains est le seul moyen de pouvoir les décharger ultérieurement du processus.


0 commentaires

3 Réponses :


1
votes

Ceci est une question intervenante.

Ma première idée était de simplement implémenter des interfaces à partir de votre application hôte dans vos applications de plugin afin de leur permettre de communiquer via la réflexion, mais cela ne permettrait que la communication et n'apporterait pas une véritable architecture "sandbox".

Ma deuxième pensée était de concevoir une plate-forme axée sur les services. L'application hôte serait une sorte de "diffuseur de plugin" qui publierait vos plugins dans un service de service sur un fil différent. Comme cela doit être vraiment réactif et "pas d'évidence configuré", l'application hôte pourrait communiquer avec le plug-in via des tuyaux nommés canaux (NetNamedPipesBinding pour WCF), ce que signifie communiquer uniquement avec des tuyaux localhost et ne nécessite aucune configuration réseau ni connaissance du tout. . Je pense que cela pourrait être une bonne solution à votre problème.

Cordialement.


0 commentaires

2
votes

Cela dépend de la confiance que vous souhaitez autoriser les extensions. Je travaille sur une application similaire et j'ai choisi de faire face à la plupart du temps au code de poste, car cela simplifie considérablement les choses. J'appelle dans le code d'un fil commun (dans mon cas, les extensions ne sont pas vraiment "exécutées" dans une boucle continue, mais exécutent plutôt certaines tâches que l'application principale veut faire) et attraper des exceptions dans ce fil, de sorte que Fournir des avertissements utiles que les extensions chargées sont malbulées.

Il n'y a actuellement rien de garder ces extensions du lancement de leurs propres threads susceptibles de lancer et de planter toute l'application, mais que je devais faire le compromis entre la sécurité et la complexité. Mon application n'est pas critique de mission (pas comme un serveur Web ou un serveur de base de données), donc je considère que c'est un risque acceptable qu'une extension de buggy puisse réduire mon application. Je fournis des sauvegardes à couvrir plus poliment les cas d'échec les plus courants et laissez-le sur les développeurs du plugin (qui seront surtout des personnes internes pour le moment) de nettoyer leurs bugs.


En ce qui concerne le déchargement, oui, vous ne pouvez décharger que le code et les métadonnées pour un assemblage si vous le placez dans une appdomaine. Cela dit, à moins que vous ne souhaitiez pas charger et décharger fréquemment la vie de votre programme, la surcharge associée à la conservation du code en mémoire n'est pas nécessairement un problème. Toutes les instances ou ressources réelles utilisant des types de l'assembly seront toujours nettoyées par le GC lorsque vous arrêtez «l'utiliser», donc le fait qu'il est toujours en mémoire n'implique pas de fuite de mémoire.

Si votre cas d'utilisation principale est une série de plugins que vous localisez une fois au démarrage, puis fournissez une option pour instancier lorsque votre application est en cours d'exécution, je suggère d'enquêter sur l'empreinte de mémoire réelle associée à la chargement de tous au démarrage et les garder chargés. Si vous utilisez AppDomains, il y aura également des frais généraux supplémentaires (par exemple, la mémoire pour les objets proxy et le code chargé / jitée pour prendre en charge le maréchalage de l'appdomain). Il y aura également des frais de calcul de la CPU associés à la sérialisation du maréchalation et de l'accompagnement.

En bref, je n'utiliserais qu'appdomains si l'une des suivantes était vraie:

  • Je veux obtenir un véritable isolement aux fins de la sécurité du code (c'est-à-dire que je dois exécuter du code non approuvé de manière isolée)

  • Mon application est essentielle de la mission et j'ai absolument besoin de vous assurer que si un plugin échoue, il ne peut pas abaisser mon application de base.

  • J'ai besoin de charger et de décharger le même plugin à plusieurs reprises afin de prendre en charge les modifications dynamiques de la DLL. Ceci est principalement si mon application ne peut pas arrêter de courir, mais je veux des plug-ins chauds pendant qu'il est toujours en marche.

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


5 commentaires

Ceci est pour une application de passe-temps, rien de spécial. Je fais entièrement confiance en code d'exécution. Le problème est que j'ai besoin de chacun de mes plugins pour être en cours d'exécution de tous les temps (c'est-à-dire. Je ne peux pas simplement faire appel à mon hôte appelle mes plugins, ils doivent être indépendants et courir eux-mêmes.)


Dans ce cas, je voudrais juste donner à chacun leur propre thread pour exécuter et appeler toute la méthode de «principale» que vous avez définie. Vous pouvez choisir d'envelopper l'appel à «Main» du fil dans un try-catch si vous souhaitez fournir une baisse amicale au cas où un plugin jette une exception.


Oui, mais voir éditer ci-dessus. De cette façon, je ne pouvais pas télécharger plus tard des assemblages chargés sans redémarrer l'application. Je dois les avoir dans différents domaines d'applications afin que je puisse les décharger chaque fois que je veux.


Je voudrais charger et décharger des plugins au moins lorsque je les essais pour la première fois. Je ne voudrais pas avoir à tuer mon application principale à chaque fois que je souhaite recompiler le plugin x ou Y. :(


Vous pouvez réellement charger les plugins côte à côte (I.E. Importer une copie totalement nouvelle de l'Assemblée), qui accumule bien sûr la mémoire pendant les tests, mais une option. Fondamentalement, vous publiez des références à l'ancien plugin et utilisez les types de la nouvelle charge du même assemblage. Il vous suffit de faire preuve de prudence ici pour libérer toutes les anciennes références afin d'éviter des exceptions étranges telles que ("objet" (de type "type") n'est pas de type "type")



5
votes

J'ai mis en place quelque chose dans ces lignes à l'aide du cadre Addin géré (MAF) dans l'espace de noms System.addin. Avec MAF, vous emballez vos addins sous forme de DLL séparées, que votre application hôte peut découvrir et lancer dans son domaine d'application, dans un domaine séparé pour toutes les addines ou chaque addin dans son propre domaine. Avec une copie d'ombre et des domaines distincts, vous pouvez même mettre à jour une addition sans fermer votre Hostapp.

Votre application hôte et les Addins communiquent via des contrats que vous découlez d'interfaces MAF. Vous pouvez envoyer des objets entre l'hôte et les addins. Les cotnrats fournissent une interface blanche entre les addins et l'hôte, vous permettant de modifier la mise en œuvre d'un addin à l'insu de l'hôte.

Addins peut même communiquer entre eux si l'hôte leur dit l'un de l'autre. Dans mon cas, une addition logicielle est partagée par les autres. Cela me permet de déposer dans différents magasins sans toucher les autres addins ou l'hôte.

Pour mon application, l'Addin utilise des classes de superviseur simples qui, dans les classes de travailleurs de lancement sur leurs propres threads qui font tout le traitement. Les travailleurs attrapent leurs propres exceptions, qu'ils retournent à leur superviseur à travers des 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 commandement, qui leur demande de démarrer et d'arrêter les travailleurs et de renvoyer les données.

Mon hôte App est un service Windows. Les threads de travailleurs ont lancé des exceptions pour toutes les raisons habituelles (y compris les bugs!), Mais l'application hôte ne s'est jamais écrasée dans aucune de nos installations. Étant donné que les services de débogage sont gênants, Addins permettez-moi 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.

Addins peut également exposer des éléments de l'interface utilisateur. Cela me convient très utile car je dois déployer une application de contrôleur avec le service hôte, car les services n'ont pas d'UIS. Chaque plugin inclut sa propre interface de contrôleur. L'application de 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 une addition mise à jour avec une interface mise à jour et de ne pas avoir à expédier un nouveau contrôleur.

Même si le contrôleur et le service hôte utilisent les mêmes addins, ils ne marchent pas les uns sur les autres; En fait, ils ne savent même pas qu'une autre application utilise les mêmes addins. Le contrôleur et l'hôte se parlent dans une base de données partagée, mais vous pouvez également utiliser un autre mécanisme inter-appli comme MSMQ. Dans la version suivante, l'hôte sera un service WCF avec Addins sur le backend et les services Web pour le contrôle.

Ceci est un peu long, mais je voulais vous donner une idée de la façon dont le MAF polyvalent est. Ce n'est pas aussi complexe que possible d'abord, et vous pouvez construire des applications solides de rock avec elle.


0 commentaires