I am implementing a module system for my C# IRC Bot. The modules are .dll assemblies which are stored in a subdirectory, "modules", and they are used to add functionality to the bot, such as adding extra commands on IRC. These modules are designed to be loaded and unloaded at runtime so I can update the bot or fix bugs, without having to restart the entire application.
Currently the module system creates a new AppDomain
for each module to be loaded, and a proxy to be created using CreateInstanceFromAndUnwrap
inside a class called ModuleHelper
.
AppDomain domain = AppDomain.CreateDomain(name, null, new AppDomainSetup
{
ApplicationName = AppDomain.CurrentDomain.SetupInformation.ApplicationName,
ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
DisallowApplicationBaseProbing = true,
PrivateBinPathProbe = ModuleDirectory,
PrivateBinPath = ModuleDirectory,
ShadowCopyDirectories = ModuleDirectory,
CachePath = Path.Combine(ModuleDirectory, "cache"),
ShadowCopyFiles = bool.TrueString
});
ModuleProxy proxy = null;
try
{
proxy = (ModuleProxy)domain.CreateInstanceFromAndUnwrap(location, AssemblyName.GetAssemblyName(location).Name + ".Module");
proxy.OnLoad();
}
catch
{
AppDomain.Unload(domain);
throw;
}
This proxy inherits from MarshalByRefObject
.
public abstract class ModuleProxy : MarshalByRefObject
{
internal protected virtual void OnLoad()
{
}
internal protected virtual void OnUnload()
{
}
}
OnLoad
and OnUnload
are called when the module is loaded or unloaded.
Modules also inherit from MarshalByRefObject
in the external assembly, such as this class in a module, ConfigurationReader.dll
.
public class Module : ModuleProxy
{
private Configuration _configuration = new Configuration();
protected override void OnLoad()
{
string fileName = Path.Combine(ModuleHelper.ModuleDirectory, AssemblyName.GetAssemblyName(Assembly.GetExecutingAssembly().Location).Name + ".conf");
_configuration.ReadAndLoadConfiguration(fileName);
IrcBot bot = new IrcBot(_configuration);
if (_configuration.Perform != null)
{
bot.EventManager.OnRegister += PerformOnRegister;
}
if (!string.IsNullOrWhiteSpace(_configuration.IdentifyMatchPeer + _configuration.IdentifyMatchText + _configuration.IdentifyPassword))
{
bot.EventManager.OnNotice += IdentifyOnNotice;
}
IrcBot.Bots.Add(_configuration.Id, bot);
IrcBot.Bots[_configuration.Id].Start();
}
...
...
...
The problem is, when I modify something that belongs in the main appdomain (specifically, adding a new bot to the IrcBot.Bots collection, IrcBot.Bots.Add(_configuration.Id, bot);
) the IrcBot.Bots
count is increased inside the secondary appdomain only, and not the main appdomain as I want it to be.
After doing a bit of Console.WriteLining, I have found that calling IrcBot.Bots.Count
after the Add
call in the secondary appdomain returns 1, and calling it again straight after the OnLoad
call in the main appdomain returns 0. This has a disastrous effect, and causes the other modules that are loaded afterwards to malfunction. How can I update the bot count (among other things) in the main AppDomain after changing it in the secondary AppDomain?
As Juliet said, AppDomains are indeed isolated so that "static" variables are not visible from other AppDomains. A solution might be using cross-AppDomain singletons, as explained at http://jonathan.dickinsons.co.za/blog/2010/11/cross-domain-singleton-in-c/ and http://www.dolittle.com/blogs/einar/archive/2007/05/18/cross-appdomain-singleton.aspx.