Assembly cross references lost when manually loading assembly - c#

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.

Related

Passing delegates across assemblies

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?

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.

AssemblyResolve Does not fire

I have an asp.net application. I want to load some assemblies dynamically.
this is my code on application start
protected void Application_Start(Object sender, EventArgs e)
{
LoadPrivateAssemblies();
}
private static void LoadPrivateAssemblies()
{
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainAssemblyResolve;
Assembly.Load("MyDLL");
}
static Assembly CurrentDomainAssemblyResolve(object sender, ResolveEventArgs args)
{
//loads and returns assembly successfully.
}
this code works fine except when a nested c# code calls a class from my dynamic dll Inside an asp.net page (not code-behind)
sample :
<%if(MyDLL.TestObject.Value){%>white some ting<%}%>
what should I do now ?
I think If I know When a new AppDomain is created, it may solve my problem.
I really think you are barking up the wrong tree here.
Assembly.Load(byte[]) does "persist" the assembly in the app domain - otherwise, what else would it be doing?
To illustrate the fact it does, try this:
Create a solution with one console application, and one class library, named OtherAssembly.
In the class library, OtherAssembly, add a single class:
namespace OtherAssembly
{
public class Class1
{
public string HelloWorld()
{
return "Hello World";
}
}
}
In the console application, use this as your program:
public class Program
{
static void Main(string[] args)
{
try
{
using (var fs = new FileStream("OtherAssembly.dll", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
var buffer = new byte[fs.Length];
// load my assembly into a byte array from disk
fs.Read(buffer, 0, (int) fs.Length);
// load the assembly in the byte array into the current app domain
AppDomain.CurrentDomain.Load(buffer);
}
// get my type from the other assembly that we just loaded
var class1 = Type.GetType("OtherAssembly.Class1, OtherAssembly");
// create an instance of the type
var class1Instance = class1.GetConstructor(Type.EmptyTypes).Invoke(null);
// find and invoke the HelloWorld method.
var hellowWorldMethod = class1.GetMethod("HelloWorld");
Console.WriteLine(hellowWorldMethod.Invoke(class1Instance, null));
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
Console.ReadLine();
}
}
}
Don't reference OtherAssembly from your main program assembly, instead, compile the solution and manually drop OtherAssembly.dll in the main program's bin folder.
Run the main program. It outputs "Hello World", which it can only have done if the assembly was loaded and retained in memory. You'll note I've been very careful not to give Visual Studio or C# any hint to load this OtherAssembly. No reference was added, the type is not explicitly referenced in C#.
You need to look again at your problem.
[EDIT: in response to you commenting on the fact this is not an ASP.NET application]
OK - I've moved my main program to an ASP.NET web page and tried accessing the assembly both from code behind and the markup - and it works in both situations. I'm sure you are missing something here - it just doesn't make sense that the behaviour of this method - whose job it is to load an assembly into the current app domain - is different in an ASP.NET scenario.
It seems to me there are at least two things to investigate:
If it really is a new app domain being created on each call, it would only be because of an unhandled exception in your application. The quickest way to diagnose this is to use SysInternals Process Explorer. Fire it up - add .Net -> Total AppDomains to the list of columns and watch the app domain count for your process (IIS worker process or web dev) as you run your app. If this increases on each call, you've got unhandled exceptions tearing down one AppDomain and forcing another to be created.
Are you sure this assembly is really being loaded? When you pass the byte array to Assembly.Load, try also writing it out to disk using FileStream.Write. Open the file in Reflector or some similar tool. Is this the assembly you expected it to be. Is it really being loaded at all, or is it an empty/corrupt array?
I'm not trying to be argumentative, but this really feels like you are looking in the wrong place for the cause of the problem.
I think If I know When a new AppDomain is created, it may solve my problem
You should use AssemblyLoad event. AssemblyResolved occurs when the resolution of assembly is fails
I found that the problem is because Assembly.Load(bytes); does not persist the assembly in appdomain. does any body know how to persist loaded assembly using Assembly.Load(bytes); in appdomain ?
finally I decided to switch to LoadFile method instead of load.
EDIT
Finally I switched from Assemly.Load(byte[]) to Assembly.LoadFile(string).
But it did not correct the problem itself. I have added <%#Assembly name="MyDLL"%> To All of my ASPX files that has markup C# code emebeded.
And This Solved my Problem.
Thanks to your answers. I have voted up answers that helped me, but I can not accept your solutions because no one is not complete enough.

Categories

Resources