Click here to Skip to main content
Rate this: bad
good
Please Sign up or sign in to vote.
Hello Everybody,
 
I have an interesting problem at hand.
 
I have a need to create a WPF Application which will host several UserControls. (The application does just that - serves as a container with docking abilities for UserControls)
 
The application will also host the data model for the UserControls as another hot Swappable component. (I plan to use internal queues for each Component so that Components can be swapped.)
 
These user controls will have to be pluggable (This is Easy), and reloadable (This seems to be difficult/ impossible) at run time.(AppDomains seem to be the answer here, But I don't know the limitations other than AppDomain limitations on MarshalByValue and marshalByRefObject Are there any GUI limitations? ) "Reloadable" at runtime as in Hot Swap the UI component while the app is UP.
 
I also want the Context menu on a UserControl to have some items from Host and some Items from the UserControl itself.
 
I also have logic to control Focus (Change Border Color etc. on Focus Events) between the host and UserControls seamlessly (Tabbing across Host and UserControls).
 
As an aside, I would also want the UserControl to be ActiveX enabled for integration with ActiveX containers like Excel.
 
This will give me flexibility in terms of running the UserControl by itself (Unit test), In a container (seamless Navigation and integration with container and other relevant UserControls), and as ActiveX control to integrate with ActiveX containers.
 
I would like to know if it is possible, and if there are frameworks(MAF?) that supports this. Any pointers to examples or documentation will help.
Thank you in advance,
 
Regards, Ven.
Posted 13-Mar-11 7:17am
Comments
SAKryukov at 13-Mar-11 14:50pm
   
Very good, reasonable and interesting Question, my 5+++. Such questions are rare; one per hundreds with 99.9% of the rest being completely idiotic.
Respect,
--SA
Rate this: bad
good
Please Sign up or sign in to vote.

Solution 1

This is not a problem. You can even host and plug-in whole WPF applications. I think you don't need this, so I'll explain a skeleton of plug-in design.
 
Let's assume you have host application which never changes (more exactly, its plug-in support sub-system is not changed or is changed incrementally, see below) and several plug-ins with possibility of changing plug-ins and developing new plug-ins. Pluggable at the very beginning of run-time is easy, reloadable is possible but way more difficult; the problem here is not loading, but unloading, because unloading of assembly is impossible; you can only unload Application Domain.
 
1) First, you should avoid matching of any type of member by name. This is quite possible. You need to develop a plug-in interface. Define interface type and keep in mind you should never change is from the moment you start developing plug-in implementations. More exactly, you can make interfaces upgradeable. For this purpose, you can add additional functionality in the derived interfaces. In other words, you need to keep interface changed incremental (never remove methods). The plug-in interface(s) will be your contract with plug-ins.
 
2) Make the definition(s) of your plug-in interfaces(s) accessible to both he host application and plug-in. It looks trivial, because you do it using a separate assembly referenced by both host and all plug-ins. You can do better! You can keep the interfaces and all helper types to be used by plug-ins in your application. It will help you to keep consistent versioning (no different assemblies — no different versions). Not everyone knows that you can reference your whole application (*.exe) in another assembly as any DLL. Essentially, .NET does not know difference between EXE and DLL (in contrast to native Windows).
 
3) Now, you need to recognize a plug-in in the plug-in assembly loaded from file. You can do it traversing all top-level types in assembly and checking is any of them support one of your plug-in interfaces. Trivial? And what if more than one class supports the plug-in interface? What if there are too many classes? You can do better! Create a custom attribute of assembly which helps to claim some type supporting a plug-in interface. Every plug-in should have something like this:
[assembly: ExposePlugin(
    typeof(TextProcessor),
    typeof(MyDataImporter),
    typeof(MyDataExporter))]
(Just in case this sample is not clear: the type ExposePluginInterface does not exist, I suggest you develop it.)
In this way, your host plug-in support does not traverse all assembly types but only those listed in the isntance of ExposePluginInterface.
 
4) A plug-in concept in interesting: each plug-in implements plug-in interface and passes implementation to the host; and host passes required host resources to plug-in. You implement the plug-in using those resources. (This is a note to item (1).) Now you can implement plug-ins and load them in the host. It leaves for the problem: how can you unload it?
 
5) Unloading of plug-ins is much more difficult. You should understand that Application Domains have totally isolated data. You cannot simply add control from one AppDomain to another. Application Domains can only communication through IPC. However, you can use highly simplified form of IPC, see System.AppDomain.SetData, System.AppDomain.GetData. There is a problem passing any data containing reference data, as the reference is only valid in the source AppDomain, so you can only assembly a deep clone on the other end. So, you hardly can pass UI controls between this boundary.
Here is one idea: you should not try to share control data between AppDomains. You can develop meta-data which only contains the instructions on how to assembly the controls on the host site. Make you plug-in interfaces more semantic. Of course, you can always share code between two ApplicationDomains and pass data (but only add data) through the method parameters.
 
[EDIT]
6) See this on how to run the code in the context of the newly created AppDomain:
AppDomain refuses to load an assembly[^]
The idea is using System.AppDomain.DoCallBack or (less likely as it required the plugin-in with the entry point (EXE file) System.AppDomain.ExecuteAssembly.
[END EDIT]
 
You don't have to develop whole plug-in architecture is this way. Alternatively, you can take the other way around. In place of plug-in you can develop separate applications, each representing semantics of what you now consider a plug-in. The plug-ins will be your UI presentation libraries. Imagine you create several Presentation Foundations on your own, but simple and specialized. In particular, you should know how to run a WPF application in a library. If you're interested, ask another Question, but the answer might be a whole separate article. I actually successfully used both ways; which one to take depends on your ultimate goals.
 
Good luck,
—SA

 
P.S.: Please see another Answer for follow-up discussion.
  Permalink  
v5
Comments
Espen Harlinn at 13-Mar-11 15:42pm
   
I like your answer, my 5!
SAKryukov at 13-Mar-11 15:57pm
   
Thank you very much; my purpose was to bring your attention that good Questions still appear...
By the way, did you see my (the only one) Question on [Completely Useless Challenge]? (I put forward the idea in discussion with John.)
--SA
SAKryukov at 13-Mar-11 21:28pm
   
Thank you, where?
--SA
Espen Harlinn at 14-Mar-11 3:52am
   
Glitch, lost in space, or maybe > dev/null, anyway - I've fixed it :)
SAKryukov at 14-Mar-11 3:53am
   
Thank you. It's very nice of you to come back.
VENKATHEGDE at 13-Mar-11 19:08pm
   
Thank You SAKryukov for your answer. But I feel somewhat dry...
 
It is understood as to how a Plugin Architecture can be created. There is a great article by Daniel moth on MAF:
http://www.danielmoth.com/Blog/maf-screencasts.aspx
and the book by Jeffery Ritcher gives details about AppDomains.
 
I can create a base interface and find types from DLLs. In addition to that, I also learned that the DLL locking problem can be resolved, by using Load() APIs that take a bytestream (Two bytestreams if You want to provide PDB info) which eliminates the DLL locking issue.
 
By not maintaining references to any of the classes, and maintaining my own queues, I can register and unregister plugins for processing requests, making them eligible for unloading when I call AppDomain.unload(...), whenever I have to replace a Plugin on the fly.
 
The implementation would work with Data Classes, The issues start showing up when we start doing the same with GUI
 
Window Focus traversal and interception, Combined Context menu [App + Plugin] are the issues I am expecting, and need answers for them.
 
I looked at WPF add-in architecture as well. But this has limitations (http://msdn.microsoft.com/en-us/library/bb909794.aspx#WPFAddInModelLimitations[^]). I would like to know if somebody was able to overcome them by using any workaround.
 
How do you access and listen to each WPF Dispatcher thread, if they are separate apps loaded vi ExecuteAssembly()?
 
GUI issues can become frustrating as focus issues alone can take a toll on you if the components cannot have communication (Lost Focus / Gained Focus like issues => If, for example, Every focused component has a RED Border around it, It would be inconsistent If TWO components have Red Borders within the same application.]
 
--
regards,
Ven.
SAKryukov at 13-Mar-11 21:19pm
   
I don't use any frameworks and find it beneficial to do it by myself; my plug-in architectures do work for me. From your post it is not clear what's your problem.
 
Let's start from what I can understand.
 
Dispatcher thread. You should not use ExecuteAssembly! It makes no sense. (Well, it might but would be a completely different approach close to hosting applications.) You should load plug-in assembly. You should use a single process. You should also understand that application domain is transparent to thread. Same thing is for focus. You really has to use only one application object.
Also, you could use metadata-driven approach but I'm not sure how well did you catch the idea.
 
To me, your goal and general idea is the problem: you did not really share much. I look something like application server. I'm also not sure how much you need the unloading of plug-in and hence AppDomain. I'm also not sure how much you understand it yourself -- probably you're somewhere in inception stage. If you could share the ideas on big picture, it would make some matter for discussion and probably some help.
 
--SA
VENKATHEGDE at 14-Mar-11 0:05am
   
Thanks for your reply, Again.
 
I will try to explain my problem:
 
The plugins are the widgets of the application (Different kinds, each for which the App will create a contract).
 
The Unloading/Reloading of the plugin will be used to fix defects on the fly. I will also be able to incorporate new features without bringing down the host application.
 
Regarding "AppDomain is transparent to threads", In a marshalbyRefObject scenario, The app thread stops at the AppDomain boundary, and another thread in the other appDomain calls the method. The calling thread is blocked until the Remote Object returns control. In a GUI environment, the Dispatcher thread is One per process. AppDomains do not share threads. Is my UserControl hooked to its own dispacther Or the Host Application's dispatcher ?
 
If I were to use the meta data approach, Are you suggesting that the host build the UI based on the meta data?
 
My issues and doubts circulate around GUI issues. I have stated two specific scenarios as integration issues (Posed as two questions) between the WPF container and the Plugin as I see it.
 
Say for example, My App is a WPF app, and my Plugin will reside in a Dockable panel. first Question is: How do I add the Plugin into my WPF container ? The UserControl does not inherit from MarshalByRefObject and hence cannot have a remoting object. The Serializable won't work either. So,
 
Question 1: How do I add a UserControl that has GUI (and data model) to the Host WPF app?
 
After adding the UserConrol, I want to have seamless focus between Host and Plugin. Say I have 3 plugins (A, B, C) in one Dockable window and two plugins (D, E) in another dockable window.
 
Question 2: How do I seamlessly tab from A, B, C, D to E across Dockable Windows? (I need to have host and Plugin Co-ordination)
 
Thank you very much for your valuable time.
And Thank you for all the help and expertise that you are providing.
 
--
regards,
ven.
SAKryukov at 14-Mar-11 1:00am
   
You see, I think you limit yourself by associating your plug-in with control or group of controls or UI elements. I believe you understand the difference technically, I only say you think in just one dimension. I can see it by your phrase: "How do I add the Plugin into my WPF container?". You do not add Plugin to container, you just load plug-in. You know, this is relatively popular approach. You have some container for controls. You can use it to host other controls; and those "other" are supplied by a plug-in which is a different assembly. You have one-to-one correspondence between a sub-tree in the parent-child relationship in the UI and the host-plugin relationship. This is very easy-to-picture mapping, but very limiting. I would advice you to take a wider look. In particular, you can view a plug-in as a piece of functionality. Metadata-driven approach is of this kind, too. Yes, as you say it can be the build of UI based on meta-data. Look, this is not that meta-data which is used to bind assemblies together, this is your own meta-data. This is a big topic though. I'm the author of meta-data driven technology and know this is a big but productive approach.
 
About AppDomain not sharing threads. The whole notion of "sharing" threads is wrong. Threads live on the space of stacks and processor states, Application Domains live in the space of data domains. These two notions are orthogonal. Your give me the example which looks artificial to me. (But this is because you imply some use of thread and object marshalling I don't know.) A thread is not confined to a single app.domain. For pretty good explanation, see, for example: http://en.csharp-online.net/Building_Multithreaded_Applications—The_Process/AppDomain/Context/Thread_Relationship.
 
Question 1. Your plugin-should not provide controls (data). It should provide method with data supplied by host and passed as parameters to the interface method. You know, I need some room for that...
 
Please think about all that and wait for my separate Answer...
 
--SA
SAKryukov at 14-Mar-11 1:41am
   
I put another answer and probably removed the confusion about threads as well.
 
I want to pay your attention: you did not explain the goal of your work. You only provided more explanation of your vision of your architecture. This is not enough. Everyone can be preoccupied with some ideas, so the architectural vision is not free from your personal view. If you want my opinion you would better explain your _ultimate_ goals in a way more or less free from your personal product of your inception. Do you understand what do I mean?
 
--SA
Sandeep Mewara at 14-Mar-11 2:44am
   
Strange there was no 5 to this answer till now! :doh:
 
My 5+++ to it.
SAKryukov at 14-Mar-11 2:47am
   
Thank you, Sandeep. I would say my follow-up Answer is more interesting.
OP understands matter quite well I think, you know how rare this is. It's a part of creative process.
--SA
Sandeep Mewara at 14-Mar-11 2:52am
   
Indeed rare it is. 5ed the Question too.
Nuri Ismail at 7-Apr-11 5:59am
   
Excellent! 5-ed!
SAKryukov at 7-Apr-11 6:12am
   
Thank you, Nuri. This is just a result of couple of past works. One of the is really serious: a novel Computer Algebra System. What is discussed is only a presentation level, nothing to do with the main idea of the CAS.
--SA
Nuri Ismail at 7-Apr-11 6:24am
   
Wow! I just noticed your follow-up answer, a very interesting one! :)
Albin Abel at 16-Apr-11 15:44pm
   
It is a great explanations
SAKryukov at 16-Apr-11 15:58pm
   
Thank you, Albin.
--SA
Rate this: bad
good
Please Sign up or sign in to vote.

Solution 2

Answering follow-up Questions:

Question 1

Consider the following sample:
//plugin-code of the implemented plug-in interface:

Control[] IControlPlugin.AddControls(DockPanel parent) {
    Button leftRect = new Button();
    leftRect.Content = "_Left";
    DockPanel.SetDock(leftRect, Dock.Left);
    parent.Children.Add(leftRect);
    Button rightRect = new Button();
    rightRect.Content = "_Right";
    DockPanel.SetDock(rightRect, Dock.Right);
    parent.Children.Add(rightRect);
    return new Control[] { leftRect, rightRect, };
} //AddControls

//host code:
Controls[] pluginControls = MyPlugin.AddControls(MyHostDockPanel);
Plug-in implements operations on some UIElement set and adds some new controls. What happens if this method is called from a different AppDomain? It will work, because no data in the plug-in AppDomain are involved. All data are taken from the host AppDomain and all returned data is used only in the host AppDomain. The fact that the code is from the different (plug-in) domain does not matter, the data is passes only on stack, which belongs to the calling thread, and all heap involved belongs to the host AppDomain. This usage does not try to violate domain boundary. Note that "this" is never used, it would be a problem. By the way, instantiation of the classes by plug-in interface also can be done in the host AppDomain. In this way, you can even use "this" and instance method. The idea is to use the plug-in only as a source of classes and methods. Instances of the classes (and all other data) should always be in the host AppDomain side.
 
This example also illustrates that threads do not feel domain boundaries. When you try to do Dispatcher.Invoke or Dispatcher.BeginInvoke, this is different story. This will not work across Application Domain. Why? Because Invoke is not a call, not in any sense of this word. This is pure data exchange. A delegate instance and all parameters used for call (including instance of the calling class) are put into the queue to be used in the UI thread. But this is all data, only sensible in one AppDomain, not in any other. Anyway, you can only use IPC if you want run thread with data in one AppDomain and want to handle UI running in other AppDomain, but this is not a boundary between threads in different Application Domain (such boundary does not exist, and even the notion of such boundary makes no sense). This is still the save very boundary between data.

Question 2

The TAB navigation and all other aspects will work seamlessly by definition by the reasons explained in the answer to Q1. Once controls are created by plug-in but belong to data in the host AppDomain, they will work exactly in the same way as they were created in the same assembly and the same AppDomain.
 
—SA
  Permalink  
Comments
Sandeep Mewara at 14-Mar-11 2:51am
   
5ed... and bookmarked!
 
:)
SAKryukov at 14-Mar-11 2:55am
   
Thank you. Want to use some of the ideas? :-)
--SA
Sandeep Mewara at 14-Mar-11 3:14am
   
Sure yes. In a month, would be learning a lot on WPF... so...yeah... looking forward to learn from you too. :)
SAKryukov at 14-Mar-11 3:31am
   
From me? Well, give it a try :-)
--SA
Sandeep Mewara at 14-Mar-11 3:32am
   
Yeah. You too. :)
VENKATHEGDE at 14-Mar-11 7:38am
   
Thank you for clarifying the usage of Threading Models and AppDomains - Very Enlightening...
 
In the code Snippet above:

//plugin-code of the implemented plug-in interface:
Control[] IControlPlugin.AddControls(DockPanel parent) {

DockPanel has to be serializable or should inherit from MarshalByRefObject if the method call crosses AppDomain Boundaries and neither of it can be true.
 
When Making the call from the host application,
IControlPlugin.AddControls(DockPanel parent)
It will use Remoting and try to Marshal DockPanel (As it is an Argument in the method) and will fail.
 

Kindly confirm if my understanding about DockPanel not being marshalable / proxied is correct.
 
Thanks again, for helping me out.
 
--
Regards,
ven.
VENKATHEGDE at 14-Mar-11 7:56am
   
My problem is that none of the WPF controls / Containers (fancy ones) are "marshalable"....
SAKryukov at 14-Mar-11 12:46pm
   
Understand. If you keep all you data on the host side and in host domain, as I explained above, it will work in exactly the same way.
 
I highly recommend using Data Contract.
 
Look, all I write about can be prototyped within just one day. I hope you're not going to fall into developing without prototyping all key techniques..?
 
--SA
SAKryukov at 14-Mar-11 17:40pm
   
This is another recommendation: never try to marshal/persist or remotely control any UI elements. Read wikipedia: "Loose coupling". Create a model of data in the set of pure data classes and create mapping between controls and data model. Persist (etc.) only the object graph using the data model.
See MVVM, MVC, MVP architectural patterns for some incite.
--SA

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

  Print Answers RSS
0 OriginalGriff 5,170
1 DamithSL 4,357
2 Maciej Los 3,750
3 Kornfeld Eliyahu Peter 3,470
4 Sergey Alexandrovich Kryukov 2,851


Advertise | Privacy | Mobile
Web02 | 2.8.141216.1 | Last Updated 13 Apr 2011
Copyright © CodeProject, 1999-2014
All Rights Reserved. Terms of Service
Layout: fixed | fluid

CodeProject, 503-250 Ferrand Drive Toronto Ontario, M3C 3G8 Canada +1 416-849-8900 x 100