There are a bunch of ways to do plugin architectures which can make it
somewhat confusing, and it really depends what your specific needs are.
Here's a sort of general method to do it that I've used a few times:
First, I create a plugin library DLL. This basically contains interfaces
I've defined that the application and plugins will implement. Both the main
application and the plugin DLLs reference this library.
Some examples of interfaces are: IApplication (this is implemented by the
application itself and contains methods that the plugin can call to get
information from the application) and IPlugin (which is, obviously,
implemented by a plugin).
There is then some sort of Initialize() method in the IPlugin interface that
takes an IApplication parameter.
The application then loads all the DLLs, probably through Assembly.LoadFrom
or some similar method and searches for DLLs that implement the IPlugin
interface (using reflection). The application then creates instances of each
of these IPlugin implementors and follows that by calling Initialize() and
passing the IApplication implementor.
Now you have an application and plugins that can speak to each other. You
simply need to define the methods in IApplication and IPlugin that are
necessary to do whatever communication you need between the two.
There are a number of variations to this. You might want to use some sort of
configuration file to tell you what DLLs to load instead of searching for
IPlugin implementations. But the basic strategy above makes for a pretty
minimal implementation that's easy to work with.