My first question is: what do you mean by 'process'? Is this really inter-process (in the OS sense) communication, or are you talking about interacting subsystems within the same application? The reason I ask is because plugins are usually implemented in the second way – in C#, usually by loading assemblies at runtime with Assembly.Load or Assembly.LoadFrom – and if you're actually using separate processes (Process.Start) I would need to know more about what you're doing to be sure that's the right approach.
If you're using genuine processes, you're limited to what the OS will give you; that's usually named pipes or sockets. I typically use TCP sockets because I can use classes I already wrote for networked communication, it makes it easy to split the system across a network if that's useful, and because that's what I have experience with. Any cross-process communication though will require you to serialise and deserialise transfer objects, you won't be able to share memory, etc.
I suspect you don't mean that, particularly with your references to static variables; I think you mean communicating between subsystems. In this case you can craft a solution that fits your exact needs. A good starting point is a message based architecture: modules can post messages on the queue of other modules (possibly just themselves and a controlling hub module, possibly any other module in the system; I don't know enough to judge on that), and modules process their message queues and do things. For example, a module that allows user entry could validate the data, process it with information that that module has available, and then post a message to the hub saying that data in table XYZ should be updated.
This has the advantage that you can stream data control messages serially, so you won't get race condition data corruption, but you don't have to make each module wait on all the others (which kills UI responsiveness). You can also have the hub tell other modules that data is updated so they should refresh their views (a bit like the Observer pattern but on a subsystem scale).
I tried the pipe approach once, and it works fine if you have truly separate processes. As does shared memory, but that was in the days of C++. What I end up doing for what seems to be your need (plugins that don't actually run in separate processes, but maybe I'm wrong), I just provide a reference to an API that manages containers (bags, collections, whatever you want to call them.) I learned quickly that it's nice to have different containers, and I also learned that I want to abstract the "variable" (usually it's a native type, but it could be a complex type as well) so that I can trigger events when the value changes. It also is a good mechanism for doing some client-side validation: the value can't be null, it has to be within a certain range, etc., all of these constraints and validations can be tied in through the setter event.
So, in short, my solution requires an abstraction of memory management (the container) and an abstraction of a variable (aka memory location) as a full class citizen, the Var.
And quite frankly, if I have to work across processes, the concept still holds - the event getter/setter becomes a pipe push or pull, depending on what your needs are (but usually a push.)
More complicated things can be done as well by triggering an event when a set of information changes. For example, you only want to connect to a database when both username and password have been supplied (and maybe other information, like the database instance name, schema, and so forth.) If you go fully this route, it can literally change the way you code - it's pretty cool.
Marc
Global variables are generally considered dangerous because all parts of the program can modify them at will so it becomes hard to be assured that their state is internally consistent and/or consistent with the state of the rest of the program at any given time.
There are two design considerations that come to mind immediately, that might help you create a workable, reliable and long-lived architecture:
1. If these 'global' values are initialise-once-but-thereafter-don't-change, you could make an object, with read-only (get-only) properties, that is implemented as a singleton, instantiated once its values have been obtained and initialised via constructor parameters.
This use of globals is safe because once initialised they never change ... and CAN NEVER change. That latter assurance is what makes it safe to let these values be global. They provide a (truly) static context for the rest of your program.
2. If, on the other hand, the variables you're considering are expected to be modified by the program, the do NOT constitute a static context and should not be global.
In this case, you may want to pass the from one subsystem to the next by value: this allows each subsystem to maintain a private copy of the values of these variables that were valid at they time the module received them.
If modules need to change the values of these variables you will need to consider how the rest of the program must behave when any one module modifies the value of one of these variables - probably with some kind of value-changed notification event.
Repeating the previous two sub-points:
a. pass-by-value: modules are insulated from changes to the variables in/by the rest of the program
b. pass-by-reference + change notification: can get tangled & hard to debug; needs a lot of thought; is little different from the use of globals (since all subsystems have a reference to the actual value).
Based on your reply to BobJanova, it sounds like what you are actually looking for is for a mechanism to support a MEF based architecture to pass messages between your MEF objects. This is a fairly common requirement in disconnected systems, and is one that is often brought up/thought about in the WPF/Silverlight world where the MVVM pattern emphasises the disconnected nature of the different components of the system.
Effectively, in this type of system, two techniques have become fairly common. The first is to use an interface and tie it up to a service implementation - this would be loaded in by MEF, and the classes that are interested in it can subscribe to events on it to receive notifications, or they can act on methods on the service to trigger the notifications.
The second, and much commoner technique in the MVVM world, is to implement something known as a Mediator. Basically, this is a pattern whereby a plugin registers it's interest in one or more well defined messages (along with an action to be triggered on receipt of the notification). A component triggers the Mediator to notify the colleagues (plugins) of a particular message, and the Mediator looks up those instances that are interested in the notification and it triggers an action on the plugin.
Whichever technique you end up choosing, careful management is needed to ensure that the lifetime of "interested" components is managed carefully, otherwise you end up with an application that leaks memory.
iopiopiopopiGlobal variables are generally considered dangerous because all parts of the program can modify them at will so it becomes hard to be assured that their state is internally consistent and/or consistent with the state of the rest of the program at any given time.
There are two design considerations that come to mind immediately, that might help you create a workable, reliable and long-lived architecture:
1. If these 'global' values are initialise-once-but-thereafter-don't-change, you could make an object, with read-only (get-only) properties, that is implemented as a singleton, instantiated once its values have been obtained and initialised via constructor parameters.
This use of globals is safe because once initialised they never change ... and CAN NEVER change. That latter assurance is what makes it safe to let these values be global. They provide a (truly) static context for the rest of your program.
2. If, on the other hand, the variables you're considering are expected to be modified by the program, the do NOT constitute a static context and should not be global.
In this case, you may want to pass the from one subsystem to the next by value: this allows each subsystem to maintain a private copy of the values of these variables that were valid at they time the module received them.
If modules need to change the values of these variables you will need to consider how the rest of the program must behave when any one module modifies the value of one of these variables - probably with some kind of value-changed notification event.
Repeating the previous two sub-points:
a. pass-by-value: modules are insulated from changes to the variables in/by the rest of the program
b. pass-by-reference + change notification: can get tangled & hard to debug; needs a lot of thought; is little different from the use of globals (since all subsystems have a reference to the actual value).
This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)