A common error people post about in the .NET newsgroups is an
InvalidCastException
being thrown when loading types dynamically,
typically for a plug-in mechanism. This article explains the most common reason,
and how to avoid it.
Generally, there are three main types involved in this kind of set-up: the "driver" class (which loads the plug-in assembly), the interface (which plug-ins must implement), and the plug-in class itself. In order to simplify the example, I'm ignoring the normal code which looks for appropriate types within an assembly, and indeed appropriate assemblies within a directory etc. Here is the source code for the three types involved:
Driver.cs:
using System; using System.Reflection; public class Driver { static void Main() { Assembly assembly = Assembly.LoadFrom ("myplugin.dll"); Type t = assembly.GetType ("SamplePlugin"); IPlugin plugin = (IPlugin) Activator.CreateInstance(t); plugin.SayHello(); } } |
IPlugin.cs:
public interface IPlugin { void SayHello(); } |
SamplePlugin.cs:
using System; public class SamplePlugin : IPlugin { public void SayHello() { Console.WriteLine ("Hello from the sample plug-in."); } } |
With those files all in the same directory, you can compile them with:
csc Driver.cs IPlugin.cs csc /target:library /out:myplugin.dll IPlugin.cs SamplePlugin.cs |
That produces two assemblies: Driver.exe
and myplugin.dll
.
This corresponds to having two different projects in Visual Studio .NET, and both
projects containing IPlugin.cs
. Unfortunately, when you run it, you get:
Unhandled Exception: System.InvalidCastException: Specified cast is not valid. at Driver.Main() |
The cast in the driver code is saying, "Check that the reference actually points to an
instance of IPlugin
(or it's null
), and throw an exception if it
doesn't." However, the IPlugin
type that it is trying to check against is
the one that Driver
knows about - the one within the Driver.exe
assembly. Unfortunately, the actual type of the object in question is
SamplePlugin
which implements the IPlugin
type which is in
myplugin.dll
. Even though the names are the same and the contents are the
same, the runtime treats them as completely different types. A type is uniquely identified
by its fully qualified name and the assembly it comes from. The runtime keeps track of
which assembly files it has loaded, but if there were two identical assemblies in two
separate files, those would be loaded as two different assemblies - so the types
within them would be completely separate.
Clearly we need to make sure that the type used for casting is the same one which the
object itself is aware of. Basically, we want to make sure that there's only one
IPlugin
type in memory. There are two simple ways of doing this, although
one of them won't work in Visual Studio .NET:
Change the compilation process to:
csc Driver.cs IPlugin.cs csc /target:library /out:myplugin.dll /r:Driver.exe SamplePlugin.cs |
Here we have the same assemblies as before, but this time instead of IPlugin
being in the plug-in assembly as well as the driver, it's only in the driver, and we tell
the compiler to reference the driver assembly when compiling the plug-in. Running it,
we get the desired output:
Hello from the sample plug-in. |
Unfortunately, this won't work from Visual Studio .NET 2003 - at least, not with the "Add Reference" dialog.
Even though the runtime lets an assembly reference an executable assembly,
VS.NET 2003 doesn't - if you try to add a reference to Driver.exe
, you
get an error message. Fortunately, this restriction has been lifted in VS 2005, making this solution
or more appealing one.
A "newbie trying to help" pointed out to me that you can, however, edit the
project file in VS.NET 2003 to add the reference manually. Here's an example:
<Reference Name = "IPlugin" AssemblyName = "PlugInTest" HintPath = "..\PlugInTest\bin\debug\plugintest.exe" /> |
Change the compilation process to:
csc /target:library /out:interface.dll IPlugin.cs csc /r:interface.dll Driver.cs csc /target:library /out:myplugin.dll /r:interface.dll SamplePlugin.cs |
This is equivalent to there now being three projects in Visual Studio .NET - one for each
type. The driver and sample plug-in projects need to reference the project providing
the plug-in interface. Three assemblies are now generated, interface.dll
,
Driver.exe
and myplugin.dll
. Again, the interface only
exists in a single assembly in memory, so both the driver casting code and the plug-in
class itself will use the same type. The output is as with solution 1 - no exceptions
are thrown.
When writing plug-in architectures, make absolutely sure that each type is only present
in a single assembly. Apart from some extreme cases (for instance where the same file is
loaded multiple times as different assemblies) this will be enough to get rid of any
InvalidCastException
you may be experiencing. If you still have problems,
please either mail me at skeet@pobox.com, or post
in the newsgroup. Given that these situations are frequently complicated, it helps to
have some short and complete example code to demonstrate
the problem.
Back to the main page.