Passing delegates across assemblies - c#

I created a wrapper for an existing project that currently only officially supports Lua for server-side coding, the server is coded in C# so it wasn't a real issue to use reflection to access the methods provided in Lua.
I'm loading "scripts" into a new AppDomain, which works just fine, however, as soon as I'm passing a delegate (delegates are used as event handlers by the server code, passed through one of the methods), the host domain attempts to load the script's assembly, which defeats the original purpose of separating the scripts from the host domain so they could be unloaded.
if I do provide the host domain with the assembly, everything works fine, until I edit the code and add/remove/modify the delegates, which then breaks every reference to the calling methods since it relies on an older copy of the assembly, as I'm loading it from a byte array so the assembly file could be modified at run time.
How can I pass delegates without having to load the assembly passing them into the host AppDomain so the scripts could remain truly isolated from the hosting AppDomain and be unloaded/loaded at will?
EDIT: Using the classes SeparateAppDomain and MefLoader from the Plugin framework project on https://www.codeproject.com/Articles/831823/Plugin-framework I load scripts like so:
Load() on the hosting domain
MefLoader mefLoader = SeparateAppDomain.CreateInstance<MefLoader>(path, path);
List<IServerScript> scripts = mefLoader.Load<IServerScript>();
foreach (IServerScript script in scripts)
{
ServerScript ss = ((ServerScript)script);
ss.CreateProxy(AppDomain.CurrentDomain);
}
In the ServerScript class (which is loaded on the new AppDomain by the MefLoader class)
private Wrapper w = null;
internal void CreateProxy(AppDomain HostDomain)
{
Type wrappertype = typeof(Wrapper);
w = (Wrapper)HostDomain.CreateInstanceAndUnwrap(wrappertype.Assembly.FullName, wrappertype.FullName, false, BindingFlags.CreateInstance, null, new object[] { }, CultureInfo.InvariantCulture, null);
}
w is the way back to the hosting domain, which handles everything regarding reflection to the server assembly.
The issue is reproduced like so:
In ServerScript
public void Test(Delegate d)
{
if (w != null) w.Test(d);
}
In any class inheriting ServerScript that would be loaded by MefLoader
Test(new Action(() => { });
In Wrapper
public void Test(Delegate d)
{
}
The hosting domain does not attempt to load the script assembly until the call w.Test(d) is made in ServerScript.
EDIT 2: After further tests, the cause isn't the separate appdomains, but the separate assemblies, unless I pass a delegate that's defined in the wrapper assembly I end up with the issue described above, this is more than likely why the wrapper attempts to load the script assembly, is there any way I could pass delegates (would often be an Action<> with a varying amount of parameters depending on the use case) from the script assembly to the wrapper assembly without loading the script assembly into the wrapper's domain?

Related

Does AssemblyLoadContext isolate static variables?

Announcement tells us:
Assembly unloadability is a new capability of AssemblyLoadContext. This new feature is largely transparent from an API perspective, exposed with just a few new APIs. It enables a loader context to be unloaded, releasing all memory for instantiated types, static fields and for the assembly itself. An application should be able to load and unload assemblies via this mechanism forever without experiencing a memory leak.
Also, this design notes has mentioning of "statics".
I have tried this straightforward test:
static void Main()
{
Proxy.X = 15;
var alc = new AssemblyLoadContext("MyTest", true);
var asm = alc.LoadFromAssemblyName(typeof(Program).Assembly.GetName());
var proxy = (Proxy)asm.CreateInstance(typeof(Proxy).FullName);
Console.WriteLine(proxy.Increment());
}
class Proxy
{
public static int X;
public int Increment() => ++X;
}
It outputs "16", which means that isolation doesn't work.
My goal is to unit-test class static members which can throw exceptions. Usual tests can affect each other's behavior by triggering type initializers, so I need to isolate them in the cheapest possible way. Test should run on .NET Core 3.0.
Is it right way to do it, and can AssemblyLoadContext help with it?
Yes, it does isolate static variables.
If we look at the newest design notes, we see this addition:
LoadFromAssemblyName
This method can be used to load an assembly into
a load context different from the load context of the currently
executing assembly. The assembly will be loaded into the load context
on which the method is called. If the context can't resolve the
assembly in its Load method the assembly loading will defer to the
Default load context. In such case it's possible the loaded assembly
is from the Default context even though the method was called on a
non-default context.
Calling this method directly on the AssemblyLoadContext.Default will
only load the assembly from the Default context. Depending on the
caller the Default may or may not be different from the load context
of the currently executing assembly.
This method does not "forcefully" load the assembly into the specified
context. It basically initiates a bind to the specified assembly name
on the specified context. That bind operation will go through the full
binding resolution logic which is free to resolve the assembly from
any context (in reality the most likely outcome is either the
specified context or the default context). This process is described
above.
To make sure a specified assembly is loaded into the specified load
context call AssemblyLoadContext.LoadFromAssemblyPath and specify the
path to the assembly file.
It's little bit frustrating, because now I need to determine the exact location of the assembly to load (there's no easy way to "clone" already loaded assemblies).
This code works (outputs "1"):
static void Main()
{
Proxy.X = 15;
var alc = new AssemblyLoadContext("MyTest", true);
var asm = alc.LoadFromAssemblyPath(typeof(Program).Assembly.Location);
var proxy = asm.CreateInstance(typeof(Proxy).FullName);
Console.WriteLine(proxy.GetType().GetMethod("Increment").Invoke(null, null));
}
class Proxy
{
public static int X;
public static int Increment() => ++X;
}
(Notice, now we can't cast to Proxy class, because it is different from the run-time class of proxy variable, even being the same class...)

Assembly cross references lost when manually loading assembly

I just came across an issue with assembly references I haven't seen before. In my usual production code, my loader application loads the main application assembly (and other references) by passing the raw bytes to Assembly.Load and then calling the entry point.
Today I needed to have the main application dynamically load another reference (henceforth called the 'dll'), which contains a class that inherits from a base class in the main program. This works fine when running the main application directly. When running it through the loader, the main application loads the dll just fine, but it doesn't seem to know that the currently loaded main application is the same as the one the dll references. So casting to the base class obviously won't work. And I'm stuck.
I'm assuming the main application assembly is losing it's identity somehow.
Here's some code that illustrates the issue:
// main application
namespace Program1
{
public class BaseClass { }
class Program
{
static void Main( string[] args )
{
string s = "Library1.Class1, Library1";
var t = Type.GetType( s, true );
Debug.Assert( t.IsSubclassOf( typeof( BaseClass ) ) );
}
}
}
// dll
namespace Library1
{
public class Class1 : Program1.BaseClass { }
}
// loader
class Program
{
static void Main( string[] args )
{
var bytes = File.ReadAllBytes( "Program1.exe" );
var asm = Assembly.Load( bytes );
var e = asm.EntryPoint;
e.Invoke( null, new object[] { null } );
}
}
When running Program1 directly it works, when running it through the loader the assert fails.
So, could anyone explain what's going on here - and if there is a possible way around it?
If (let's call dllA and dllB your libraries), dllA has a reference to dllB and you load dllA and it doesn't crashes it means .net has autoresolved and loaded dllB.
Then when you load again dllB it's another assembly, and then the types don't match, I ran myself a lot of times with that situation with a dll-loading system, at the end is better to add the referenced libraries (in this case dllB) in the .exe folder and let the system load it automatically when you load dllA.
Another option if you need the reference to the library is to attach to the AssemblyResolve event before loading DllA, then when you load it the event will fire requiring you to load DllB and thus you can store the reference to the library.

Call function from DLL

It is well known that you can have functions in a dll, reference the DLL and then call the functions from your main executable.
I like to know if the reverse way is also possible?
So I like to call a function in the main executable from the dll, without having the actual function that should be called inside the dll.
Reason: I am working on a pluginsystem.
Yes, executables can be added as reference in your project and you can use them same way you call functions from referenced dlls
You're sort of comparing apples and oranges: referencing a dll by the build system is completely different from a plugin system where everything happens at runtime. Typically a plugin system where you would want to call some functions from the plugin host (your exe) would be like this (simplified):
//in a common project
//functions from the host that will be callable by the plugin
public interface PluginHost
{
void Foo();
}
//the plugin
public interface Plugin
{
void DoSomething( PluginHost host );
}
//in the exe
class ThePluginHost : PluginHost
{
//implement Foo
}
//in the plugin
class ThePlugin : Plugin
{
//implement DoSomething,
//has access to exe methods through PluginHost
}
//now al that's left is loading the plugin dll dynamically,
//and creating a Plugin object from it.
//Can be done using Prism/MEF etc, that's too broad of a scope for this answer
PluginHost host = new ThePluginHost();
Plugin plugin = CreatePluginInstance( "/path/to/dll" );
plugin.DoSomething( host );

Loading an assembly into an AppDomain outsite of applicationBase C#

So lately I've been working on a project where the application (or executable,whatever you wish to call it) needs to be able to load and unload assemblies not found within the executable's folder at all. (might even be another drive)
For the sake of an example , I want to be able to have my application at D:\AAA\theAppFolder , and the assemblies of the DLL files at C:\BBB\Assemblies
Looking thoroughly , I found out AppDomain allow the ability to unload themselves and any attached assemblies , so I figured I'd give it a shot, however there seems to be an issue after a few hours worth of attempts : AppDomains cannot look anywhere outside the application base.
According to AppDomain's documentary (and my own experience) you cannot set the PrivateBinPath outside of ApplicationBase , and if I set the ApplicationBase outside of the drive the application is at (via AppDomainSetup) , I get System.IO.FileNotFoundException complaining it can't find the application itself.
Because of that I can't even reach the phase where I can use the AssemblyResolve ResolveEventHandler to attempt to get the assembly using MarhsalByRefObject inheriting classes...
Here's a few snippets of code related to what I am currently attempting
internal class RemoteDomain : MarshalByRefObject
{
public override object InitializeLifetimeService() //there's apparently an error for marshalbyref objects where they get removed after a while without this
{
return null;
}
public Assembly GetAssembly(byte[] assembly)
{
try
{
return Assembly.Load(assembly);
}
catch (Exception e)
{
Console.WriteLine(e);
}
return null;
}
public Assembly GetAssembly(string filepath)
{
try
{
return Assembly.LoadFrom(filepath);
}
catch (Exception e)
{
Console.WriteLine(e);
}
return null;
}
}
public static Assembly LoadAssembly(string modName, BinBuffer bb)
{
string assembly = pathDirTemp+"/"+modName+".dll";
File.WriteAllBytes(assembly, bb.ReadBytes(bb.BytesLeft()));
RemoteDomain loader = (RemoteDomain)modsDomain.CreateInstanceAndUnwrap(typeof(RemoteDomain).Assembly.FullName, typeof(RemoteDomain).FullName);
return loader.GetAssembly(assembly);
}
To be as specific as I can : Is there any way to get an unloadable AppDomain to load an assembly that is not within the application's base folder?
Each AppDomain has it's own base directory and is not constrained at all by the main application base dir (unless it is the main AppDomain of the application). So you can achieve what you want using AppDomains.
The reason your approach doesn't work is that you are passing Assembly objects between AppDomains. When you call any of the GetAssembly methods, the assembly will be loaded in the child AppDomain but when the method returns, the main AppDomain will try to load the Assembly as well. Of course, the assembly will not be resolved because it is not in the main AppDomains's base dir, private paths or the GAC.
So in general you should never pass Type or Assembly objects between AppDomains.
A simple way to load assemblies without leaking them to the main AppDomain can be found in this answer.
Of course to make your application work with assemblies loaded in child AppDomains you will have to make MarshalByRefObject derived classes that will be your access point between AppDomains.
Maybe you need to use global variable so if you use global variable to fix the problem you can declare readonly global variable for example:
public static string a = "Moosaie";
Convert it to
public static readonly a = "Moosaie";
Anyway you can not use global dynamic value variable for CLR assembly.

Hosting CLR Runtime in C++

I am working on an extension for a project that will allow hosting the CLR inside the core application. With this I plan to allow this extension to manage managed extensions that it loads/unloads inside itself. That being said, I need to use separate AppDomains to ensure unloading is possible.
Currently, I am able to get the domains setup and get the plugin file loaded but at that point I'm stuck. I'm not sure how to call functions inside the domains loaded assembly at will and so on.
Here is my loading setup so far, minus error checking and such:
ICorRuntimeHost* lpRuntimeHost = NULL;
CorBindToRuntimeEx( L"v4.0.30319", L"wks", 0, CLSID_CorRuntimeHost, IID_PPV_ARGS( &lpRuntimeHost ) );
lpRuntimeHost->Start();
// File data read from disk.
// Dll file just CreateFile/ReadFile and insert into pluginFileData.
CComSafeArray<BYTE> pluginFileData;
IUnknown* lpUnknown = NULL;
lpRuntimeHost->CreateDomain( wstrPlugin.c_str(), NULL, &lpUnknown );
CComPtr<_AppDomain> appDomain = NULL;
lpUnknown->QueryInterface( &appDomain.p );
CComPtr<_Assembly> appAssembly = NULL;
hResult = appDomain->Load_3( pluginFileData, &appAssembly );
I have a class library that all plugins must reference and use in order to be considered a plugin. Which so far is nothing more than a base class to inherit:
namespace FrameworkAPI
{
public class IFrameworkPlugin
{
public override bool Initialize(IntPtr interfaceObj)
{
return false;
}
}
}
And then an extension would reference that class library and use that as its base:
namespace ClassLibrary1
{
public class Main : IFrameworkPlugin
{
public override bool Initialize(IntPtr interfaceObj)
{
// Return true to stay loaded.
return true;
}
}
}
What I am stuck at is how to do a few things:
How can I obtain the main class but as the base to invoke methods in the base that allow the main class to still handle?
How can I ensure that the main class inherits the base so I can ensure its a valid plugin file?
How I can freely invoke methods from the C++ side to fire events in the C# plugin.
For the firing events, the C++ plugin will call more things in the C# plugins once they are loaded, such as rendering events, command handling, etc.
Most of the examples I find online are specific to requiring the entire C# side to be static which I don't want. Also most do not use separate AppDomains and rather all execute in the default. I don't want this since it limits being able to unload a specific plugin.
If any other info is missing and needed feel free to let me know.
I resolved this issue by using a COM exposed interface for the C# side of things.
I have placed the FrameworkAPI inside a separate DLL and exposed it's main interface to COM then reference it in the plugins that will use it.
With it compiled with COM enabled, I can import the .tlb generated file to use the interface in C++ easily.

Categories

Resources