Replaceable assemblies for plugin - c#

we are trying to hot swap (update) assemblies, the normal workflow is that we make some changes, build the assembly, do some changes and build again and in an ideal world the host app would get the new version of the assembly (with the updated types).
Here's our small plugin loader class:
public class PluginLoader<T>
{
private CompositionContainer _compositionContainer;
private RegistrationBuilder _registrationBuilder;
private DirectoryCatalog _catalog;
[ImportMany(AllowRecomposition = true)]
public IList<T> Plugins { get; set; }
public PluginLoader(string pluginsDirectory)
{
Plugins = new List<T>();
SetShadowCopy();
_registrationBuilder = new RegistrationBuilder();
_registrationBuilder
.ForTypesDerivedFrom(typeof(T))
.SetCreationPolicy(CreationPolicy.NonShared)
.Export<T>();
_catalog = new DirectoryCatalog(pluginsDirectory, _registrationBuilder);
_compositionContainer = new CompositionContainer(_catalog, CompositionOptions.DisableSilentRejection);
_compositionContainer.ComposeParts(this);
}
public void Reload()
{
_catalog.Refresh();
_compositionContainer.ComposeParts(this);
}
private static void SetShadowCopy()
{
AppDomain.CurrentDomain.SetShadowCopyFiles();
AppDomain.CurrentDomain.SetCachePath(Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "ShadowCopyCache"));
AppDomain.CurrentDomain.SetShadowCopyPath(Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Plugins"));
}
}
We have code to recognize a new plugin dropping into the plugins folder using FileSystemWatcher, and we call Reload when that happens, but the new versions of assemblies aren't actually loaded.
Any pointers?
Notes:
No new or deleted types are recognized, it's as if it doesn't recognize the new assembly at all.
We checked and there are no composition and other errors either, so we are a bit lost :D
It is important to note that if we build the same non recognized assembly with a different compiler(Roslyn), then it is recognized (which points to nothing being badly setup, just that the assembly needs to be somehow different )

The methods called in SetShadowCopy are deprecated. You cannot enable ShadowCopy on an existing AppDomain. For an example on how to enable ShadowCopy on a new AppDomain, Have a look at this answer.
DirectoryCatalog.Refresh does update already loaded assemblies. It only checks for file deletions and additions. Have a look at this answer for a crude work-around. Note thought that I'm not sure if such an approach is thread-safe or production-ready since I have only tested simple scenarios. Another approach would be to create your own DirectoryCatalog that can handle updates as well. The MEF source code is available (as it is for the rest of the framework). The tricky part is thread-safety since the DirectoryCatalog implementation is using Microsoft's internal classes for locking.

Related

How to dynamically load and unload (reload) a .dll assembly

I'm developing a module for an external application, which is a dll that is loaded.
However in order to develop, you have to restart the application to see the results of your code.
We have built a piece of code that loads a dll dynamically from a startassembly:
startassembly
var dllfile = findHighestAssembly(); // this works but omitted for clarity
Assembly asm = Assembly.LoadFrom(dllFile);
Type type = asm.GetType("Test.Program");
MethodInfo methodInfo = type.GetMethod("Run");
object[] parametersArray = new object[] { };
var result = methodInfo.Invoke(methodInfo, parametersArray);
Effectively we have a solution with a startassembly which will be static and a test assembly which will be invoked dynamically, which allows us to swap the assembly during runtime.
The problem
This piece of code will load a new dll every time and search for the highest version at the end of the assembly name. e.g. test02.dll will be loaded instead of test01.dll, because the application locks both startassemly.dll as well as test01.dll. Now we have to edit the properties > assembly name all the time.
I want to build a new dll while the main application still runs. However for now I get the message
The process cannot access the file test.dll because it is being used
by another process
I have read that you can unload a .dll using AppDomains however the problem is that I don't know how to properly unload an AppDomain and where to do this.
The goal is to have to reload the new test.dll everytime the window is re-opened (by a button click from the main application).
You cannot unload a single assembly, but you can unload an Appdomain. This means you need to create an app domain and load the assembly in the App domain.
Exmaple:
var appDomain = AppDomain.CreateDomain("MyAppDomain", null, new AppDomainSetup
{
ApplicationName = "MyAppDomain",
ShadowCopyFiles = "true",
PrivateBinPath = "MyAppDomainBin",
});
ShadowCopyFiles property will cause the .NET runtime to copy dlls in "MyAppDomainBin" folder to a cache location so as not to lock the files in that path. Instead the cached files are locked. For more information refer to article about Shadow Copying Assemblies
Now let's say you have an class you want to use in the assembly you want to unload. In your main app domain you call CreateInstanceAndUnwrap to get an instance of the object
_appDomain.CreateInstanceAndUnwrap("MyAssemblyName", "MyNameSpace.MyClass");
However, and this is very important, "Unwrap" part of CreateInstanceAndUnwrap will cause the assembly to be loaded in your main app domain if your class does not inherit from MarshalByRefObject. So basically you achieved nothing by creating an app domain.
To solve this problem, create a 3rd Assembly containing an Interface that is implemented by your class.
For example:
public interface IMyInterface
{
void DoSomething();
}
Then add reference to the assembly containing the interface in both your main application and your dynamically loaded assembly project. And have your class implement the interface, and inherit from MarshalByRefObject. Example:
public class MyClass : MarshalByRefObject, IMyInterface
{
public void DoSomething()
{
Console.WriteLine("Doing something.");
}
}
And to get a reference to your object:
var myObj = (IMyInterface)_appDomain.CreateInstanceAndUnwrap("MyAssemblyName", "MyNameSpace.MyClass");
Now you can call methods on your object, and .NET Runtime will use Remoting to forward the call to the other domain. It will use Serialization to serialize the parameters and return values to and from both domains. So make sure your classes used in parameters and return values are marked with [Serializable] Attribute. Or they can inherit from MarshalByRefObject in which case the you are passing a reference cross domains.
To have your application monitor changes to the folder, you can setup a FileSystemWatcher to monitor changes to the folder "MyAppDomainBin"
var watcher = new FileSystemWatcher(Path.GetFullPath(Path.Combine(".", "MyAppDomainBin")))
{
NotifyFilter = NotifyFilters.LastWrite,
};
watcher.EnableRaisingEvents = true;
watcher.Changed += Folder_Changed;
And in the Folder_Changed handler unload the appdomain and reload it again
private static async void Watcher_Changed(object sender, FileSystemEventArgs e)
{
Console.WriteLine("Folder changed");
AppDomain.Unload(_appDomain);
_appDomain = AppDomain.CreateDomain("MyAppDomain", null, new AppDomainSetup
{
ApplicationName = "MyAppDomain",
ShadowCopyFiles = "true",
PrivateBinPath = "MyAppDomainBin",
});
}
Then when you replace your DLL, in "MyAppDomainBin" folder, your application domain will be unloaded, and a new one will be created. Your old object references will be invalid (since they reference objects in an unloaded app domain), and you will need to create new ones.
A final note: AppDomains and .NET Remoting are not supported in .NET Core or future versions of .NET (.NET 5+). In those version, separation is achieved by creating separate processes instead of app domains. And using some sort of messaging library to communicate between processes.
Not the way forward in .NET Core 3 and .NET 5+
Some of the answers here assume working with .NET Framework. In .NET Core 3 and .NET 5+, the correct way to load assemblies (with ability to unload them) in the same process is with AssemblyLoadContext. Using AppDomain as a way to isolate assemblies is strictly for .NET Framework.
.NET Core 3 and 5+, give you two possible ways to load dynamic assemblies (and potentially unload):
Load another process and load your dynamic assemblies there. Then use an IPC messaging system of your choosing to send messages between the processes.
Use AssemblyLoadContext to load them in the same process. Note that the scope does NOT provide any kind of security isolation or boundaries within the process. In other words, code loaded in a separate context is still able to invoke other code in other contexts within the same process. If you want to isolate the code because you expect to be loading assemblies that you can't fully trust, then you need to load it in a completely separate process and rely on IPC.
An article explaining AssemblyLoadContext is here.
Plugin unloadability discussed here.
Many people who want to dynamically load DLLs are interested in the Plugin pattern. The MSDN actually covers this particular implementation here:
https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support
2021-9-12 UPDATE
Off-the-Shelf Library for Plugins
I use the following library for plugin loading. It has worked extremely well for me:
https://github.com/natemcmaster/DotNetCorePlugins
what you're trying to do in the code you posted is unload the default app domain which your program will run in if another isn't specified. What you're probably wanting is to load a new app domain, load the assembly into that new app domain, and then unloaded the new app domain when the user destroys the page.
https://learn.microsoft.com/en-us/dotnet/api/system.appdomain?view=netframework-4.7
the reference page above should give you a working example of all of this.
Here is an example for loading and unloading an AppDomain.
In my example I have 2 Dll's: DynDll.dll and DynDll1.dll.
Both Dll's have the same class DynDll.Class and a method Run (MarshalByRefObject is required):
public class Class : MarshalByRefObject
{
public int Run()
{
return 1; //DynDll1 return 2
}
}
Now you can create a dynamic AppDomain and load a Assembly:
AppDomain loDynamicDomain = null;
try
{
//FullPath to the Assembly
string lsAssemblyPath = string.Empty;
if (this.mbLoad1)
lsAssemblyPath = Path.Combine(Application.StartupPath, "DynDll1.dll");
else
lsAssemblyPath = Path.Combine(Application.StartupPath, "DynDll.dll");
this.mbLoad1 = !this.mbLoad1;
//Create a new Domain
loDynamicDomain = AppDomain.CreateDomain("DynamicDomain");
//Load an Assembly and create an instance DynDll.Class
//CreateInstanceFromAndUnwrap needs the FullPath to your Assembly
object loDynClass = loDynamicDomain.CreateInstanceFromAndUnwrap(lsAssemblyPath, "DynDll.Class");
//Methode Info Run
MethodInfo loMethodInfo = loDynClass.GetType().GetMethod("Run");
//Call Run from the instance
int lnNumber = (int)loMethodInfo.Invoke(loDynClass, new object[] { });
Console.WriteLine(lnNumber.ToString());
}
finally
{
if (loDynamicDomain != null)
AppDomain.Unload(loDynamicDomain);
}
Here is an idea, instead of loading the DDL directly (as is), let the application rename it, then load the renamed ddl (e.g. test01_active.dll). Then, just check for the original file (test01.dll) before loading the assembly and if exists, just delete the current one(test01_active.dll) and then rename the updated version then reload it, and so on.
Here is a code shows the idea :
const string assemblyDirectoryPath = "C:\\bin";
const string assemblyFileNameSuffix = "_active";
var assemblyCurrentFileName = "test01_active.dll";
var assemblyOriginalFileName = "test01.dll";
var originalFilePath = Path.Combine(assemblyDirectoryPath, assemblyOriginalFileName);
var currentFilePath = Path.Combine(assemblyDirectoryPath, assemblyCurrentFileName);
if(File.Exists(originalFilePath))
{
File.Delete(currentFilePath);
File.Move(originalFilePath, currentFilePath);
}
Assembly asm = Assembly.LoadFrom(currentFilePath);
Type type = asm.GetType("Test.Program");
MethodInfo methodInfo = type.GetMethod("Run");
object[] parametersArray = new object[] { };
var result = methodInfo.Invoke(methodInfo, parametersArray);

TypeLoadException on Assembly.GetType Explicit Interface implementation

So here is my issue. I have a complex archetecture of interfaces and abstract classes that I am trying to load up via Assembly.LoadFrom("x.dll"). When certain types that have an interface implementation where the implementation is explicit in a base class are trying to be loaded, I am getting a TypeLoadException saying:
Method 'MyMethod' in type 'MyPart2DerivedType' from assembly 'MyPart2Assembly, version...' does not have an implementation. I am trying to understand why this is as I have gone through several articles and have even attempted to delete the obj files and dlls manually. Here are the references to what I have done so far:
Solution to TypeLoadException
TypeLoadException says 'no implementation', but it is implemented
Visual Studio Forumns: TypeLoadException
Private accessors and explicit interface implementation
So here is my example code:
//This is in project 1
public interface IFooPart1
{
void DoStuff();
}
//This is in project 2
public interface IFooPart2
{
void DoOtherStuff();
}
//This is in project 3
public interface IFooPart3: IFooPart1, IFooPart2
{
void DoEvenMoreStuff();
}
//This is in project 4
public abstract class MyBaseType: IFooPart1, IFooPart2
{
void IFooPart1.DoStuff()
{
DoStuffInternal();
}
void IFooPart2.DoOtherStuff()
{
DoOtherStuffInternal();
}
}
//This is in project 5
public class MyDerivedType: MyBaseType, IFooPart3
{
public void DoEvenMoreStuff()
{
//Logic here...
}
}
//Only has references to projects 1, 2, & 3 (only interfaces)
public class Program
{
void Main(params string[] args)
{
//Get the path to the actual dll
string assemblyDll = args[0];
//Gets the class name to load (full name, eg: MyNameSpace.MyDerivedType)
string classNameToLoad = args[1];
//This part works...
var fooAssembly = Assembly.LoadFrom(assemblyDll);
//Here we throw a TypeLoadException stating
// Method 'DoStuff' in type 'MyDerivedType' from assembly 'Project 5...' does
// not have an implementation.
Type myDerivedTypeExpected = Assembly.GetType(classNameToLoad);
}
}
Note: If I move the explicit implementation to MyDerivedType instead of MyBaseType it works... but I don't get why I would have to do that. Seems like I should be able to. This code is only an example, the actual code has a factory that returns the loaded class but only via the interface type. (eg: var myDerivedType = GetInstance();)
Okay for everyone that is interested in my stupid fix. Here was my problem:
Project6 (which was the console app) has PROJECT references to the other projects, not references to the dlls in the location that they are supposed to build to. The other projects actually were being built to a specific repository area. So, the console application was using it's own version of the dll's when it was trying to automatically load the dependancies. This evidently made some other type way down there that was being dynamically loaded to not be loaded because it was not in the same folder as the dlls that were there...
So in short, Assembly.LoadFrom might cause you to load an assembly twice, but .NET treats it like a different assembly!!! This may introduce some real odd errors when trying to dynamically load types!!!
Please learn from my frustration/mistake. Fiends don't let freinds DI alone (code review is key to catching this stupid stuff).
I've had a similar problem caused by one of my projects having a reference to an older version of a nuget package dependency. The older version didn't have an implementation for one of the methods.

MEF Runtime Plugin Update Issue

Issue My MEF code is not appropriately updating assemblies during runtime, from a folder associated to a DirectoryCatalog. The plugins load at run-time succesffully, but when i update the dll and call Refresh on the DirectoryCatalog, the assemblies are not getting updated.
Background I am building a dll that has an MEF container, and uses a DirectoryCatalog to find a local plugin folder. I call this dll currently from a simple WinForm, that is setup to with a seperate project to use ShadowCopy so i can overwrite the dlls in my plugin folder. Instead of using a FileWatcher to update this folder, I am exposing a public method that calls refresh on the DirectoryCatalog, so i can update the assemblies at will instead of automatically.
Code
base class instantiates the MEF catalogs and container, and saves them as class variables for referential access later
public class FieldProcessor
{
private CompositionContainer _container;
private DirectoryCatalog dirCatalog;
public FieldProcessor()
{
var catalog = new AggregateCatalog();
//Adds all the parts found in the same assembly as the TestPlugin class
catalog.Catalogs.Add(new AssemblyCatalog(typeof(TestPlugin).Assembly));
dirCatalog = new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory + "Plugin\\");
catalog.Catalogs.Add(dirCatalog);
//Create the CompositionContainer with the parts in the catalog
_container = new CompositionContainer(catalog);
}
public void refreshCatalog()
{
dirCatalog.Refresh();
}
} ...
here's the plugin i'm trying to overwrite. My test of updating, is that the stings returned are output to a text box, I change the Strings the plugin is returning, rebuild, and copy it in to the plugin folder. But it does not update for the running app, until i close and restart the app.
[Export(typeof(IPlugin))]
[ExportMetadata("PluginName", "TestPlugin2")]
public class TestPlugin2 : IPlugin
{
public IEnumerable<IField> GetFields(ContextObject contextObject, params string[] parameters)
{
List<IField> retList = new List<IField>();
//Do Work Return Wrok Results
retList.Add(new Field("plugin.TestPlugin2", "TestPluginReturnValue2"));
return retList;
}
}
Edit Import Statement
[ImportMany(AllowRecomposition=true)]
IEnumerable<Lazy<IPlugin, IPluginData>> plugins;
Research I have done fairly extensive research and everywhere in articles and code samples the answer appears to be, to add a DirectoryCatalog to a container and save a reference of that catalog, then call Refresh on that reference, after a new plugin has bee added, and it will update the assemblies...which i am doing, but it's not showing updated output, from the new plugin dll.
Request Has anyone seen this issue, or know what may be causing my problems with the assemblies not updating during runtime? Any additional information or insight would be appreciated.
Resolution Thanks to Panos and Stumpy for their links which led me to the solution my issue. For future knowledge seekers, my main issue was that the Refresh method does not update assemblies, when the new assembly has the exact same assembly name as the overwritten dll. For my POC i just tested rebuilding with a Date appended to the assembly name and everything else the same, and it worked like a charm. their links in the comments below, were very useful, and are recommended if you have the same issue.
did you set AllowRecomposition parameter to your Import attribut?
AllowRecomposition
Gets or sets a value that indicates whether the property or field will be recomposed when exports with a matching contract have changed in the container.
http://msdn.microsoft.com/en-us/library/system.componentmodel.composition.importattribute(v=vs.95).aspx
edit:
DirectoryCatalog doesn't update assemblies, only added or removed:
http://msdn.microsoft.com/en-us/library/system.componentmodel.composition.hosting.directorycatalog.refresh.aspx
for a work around:
https://stackoverflow.com/a/14842417/2215320

Facing error during catalog refresh, the new dll is not used

I am trying to create a POC with mef where i have the requirement to load dll dynamically in an all ready running project , for this i have created one console application project and a class Library
project .
the code for console application project is as follows-
namespace MefProjectExtension
{
class Program
{
DirectoryCatalog catalog = new DirectoryCatalog(#"D:\MefDll", "*.dll");
[Import("Method1", AllowDefault = true, AllowRecomposition = true)]
public Func<string> method1;
static void Main(string[] args)
{
AppDomainSetup asp = new AppDomainSetup();
asp.ShadowCopyFiles = "true";
AppDomain sp = AppDomain.CreateDomain("sp",null,asp);
string exeassembly = Assembly.GetEntryAssembly().ToString();
BaseClass p = (BaseClass)sp.CreateInstanceAndUnwrap(exeassembly, "MefProjectExtension.BaseClass");
p.run();
}
}
public class BaseClass : MarshalByRefObject
{
[Import("Method1",AllowDefault=true,AllowRecomposition=true)]
public Func<string> method1;
DirectoryCatalog catalog = new DirectoryCatalog(#"D:\MefDll", "*.dll");
public void run()
{
FileSystemWatcher sw = new FileSystemWatcher(#"D:\MefDll", "*.dll");
sw.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.Size;
sw.Changed += onchanged;
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(this);
Console.WriteLine(this.method1());
sw.EnableRaisingEvents = true;
Console.Read();
}
void onchanged(object sender, FileSystemEventArgs e)
{
catalog.Refresh();
Console.WriteLine(this.method1());
}
}
}
the library project which satisfy import looks as follow-
namespace MefLibrary
{
public interface IMethods
{
string Method1();
}
public class CallMethods : IMethods
{
[Export("Method1")]
public string Method1()
{
return "Third6Hello";
}
}
}
once i compile the library project(MefLibrary) and put the dll in D:\MefDll location and run the console application for first time i will see the output as
Third6hello on screen
but now if i change the implementation of method1 and make it return "third7hello" build MEF Library project and replace at D:\MefDll while my console app is running the onchanged handler even after calling catalog refresh prints
Third6hello on screen rather than third7hello
Whether anyone knows what is the reason for this , if yes please help.
DirectoryCatalog.Refresh will only add new or remove existing assemblies. It will not update an assembly. A crude workaround is:
Move the updated assembly to a temp folder.
Call DirectoryCatalog.Refresh. This will remove the part(s) contained in the assembly.
Move the assembly back to the watched folder
Call DirectoryCatalog.Refresh. This will add the updated part(s) contained in the assembly.
Note:
For this to work your "plugin" assemblies have to be strong named and with different version numbers (AssemblyVersionAttribute). This is needed because when parts are removed using the DirectoryCatalog.Refresh the actual assembly will not be unloaded. Assemblies can only be unloaded when the whole application domain is unloaded. So if DirectoryCatalog.Refresh finds a new assembly it will create an AssemblyCatalog using the assembly filepath. AssemblyCatalog will then call Assembly.Load to load the assembly. But this method will not load an assembly that has the same AssemblyName.FullName with an already loaded assembly.
Make sure that the steps I mention will not trigger another FileSystemWatcher.Changed event. For example you could use this approach.
Your program will need to have write access on the watched folder. This can be a problem if you deploy in the %ProgramFiles% folder.
If you need thread-safety you can consider creating your CompositionContainer with the CompositionOption.IsThreadSafe flag.
As I mentioned this is a workaround. Another approach would be to download MEF's source code and use DirectoryCatalog.cs as a guideline for your own directory catalog implementation.
Once a dll is loaded in an app domain it can't be unloaded from that domain. Only the whole domain can be unloaded. As such it is not easy to implement what you are after. It is possible to constantly scan for the changes and load new copies and repoint the calls (you will be accumulating more and more useless assemblies in your domain this way), but I don't believe this is something that MEF implements out of the box. In other words the behaviour you are observing is by design.
The implementation of this can be also tricky and bug prone because of state. Imagine you set a filed in a class instance of the old DLL and assign it to a variable. Then the new dll comes through. What happens to the old instance and its fields? Apparently they will stay the same and now you have different version of your plug-in in use in memory. What a mess!
And in case you are interested here is the reason why there isn't an Assembly.Unload method. And possible (conceptual) workaround.

.NET substitute dependent assemblies without recompiling?

I have a question about how the .NET framework (2.0) resolves dependent assemblies.
We're currently involved in a bit of a rewrite of a large ASP.NET application and various satellite executables. There are also some nagging problems with our foundation classes that we developed a new API to solve. So far this is a normal, albeit wide-reaching, update.
Our heirarchy is:
ASP.NET (aspx)
business logic (DLLs)
foundation classes (DLLs)
So ASP.NET doesn't throw a fit, some of the DLLs (specifically the foundation classes) have a redirection layer that contains the old namespaces/functions and forwards them to the new API. When we replaced the DLLs, ASP.NET picked them up fine (probably because it triggered a recompile).
Precompiled applications don't though, even though the same namespaces and classes are in both sets of DLLs. Even when the file is renamed, it complains about the assemblyname attribute being different (which it has to be by necessity). I know you can redirect to differnet versions of the same assembly, but is there any way to direct to a completely different assembly?
The alternatives are to recompile the applications (don't really want to because the applications themselves haven't changed) or recompile the old foundation DLL with stubs refering to the new foundation DLL (the new dummy DLL is file system clutter).
You want to move the types to a new assembly? You can do that with [TypeForwardedTo(..)].
If you originally have (AssemblyA):
namespace SomeNamespace {
public class SomeType {}
}
You can instead move that type into AssemblyB, and have a virtually empty AssemblyA which references AssemblyB and simply contains:
[assembly: TypeForwardedTo(typeof(SomeNamespace.SomeType))]
Then anything trying to load SomeNamespace.SomeType from AssemblyA actually gets the type from AssemblyB.
Most of the runtime respects this attribute... everything except WCF extensions. Which bit me ;-p Oh, and it isn't a fan of nested types...
//File: RKAPPLET.EXE
namespace RKAPPLET
{
using RKMFC;
public static class Program
{
public static void Main ()
{
RKMFC.API.DoSomething();
}
}
}
//File: RKMFC.DLL
namespace RKMFC
{
public static class API
{
public static void DoSomething ()
{
System.Windows.Forms.MessageBox.Show("MFC!")
}
}
}
//File: RKNET.DLL
namespace RKNET
{
public static class API
{
public static void DoSomethingElse ()
{
System.Windows.Forms.MessageBox.Show("NET!")
}
}
}
namespace RKMFC
{
public static class API
{
public static void DoSomething ()
{
RKNET.API.DoSomethingElse()
}
}
}
I want RKAPPLET.EXE, compiled with RKMFC.DLL, to find RKNET.DLL (which has a copy of everything in RKMFC.DLL and then some) without recompiling either RKAPPLET.EXE (to point to it) or RKMFC.DLL (to redirect types).
Did you try adding <assemblyBinding> setting to config file ?
http://msdn.microsoft.com/en-us/library/twy1dw1e.aspx

Categories

Resources