Passing data across appdomains with MarshalByRefObject - c#

I'm having a little trouble passing some data between two .NET appdomains and I'm hoping someone on here can help me.
Basically what I have is a main application (Main) which loads assembly A and B into it's main domain, then when I run a plugin(C) Main calls a create domain method on B which creates a new domain and loads C and a instance of B into it, so that C can only access B and not the others.
B contains a pointer to the IDispatch of Main but only it seems to get it after it is loaded into the new domain with C. What I am trying to do is send a copy of the pointer from the new domain instance of B and send it to A which is still running in the default domain.
Just for the record I control A,B and C but not Main
Sorry if this is a bit hard to understand I tried my best to explain it.
Code:
In A:
public class Tunnel : MarshalByRefObject
{
public void SetPointer(int dispID)
{
IntPtr pointer = new IntPtr(dispID);
}
}
In B:
//Call by Main after loading plug in but after A.dll is loaded.
public void CreateDomain()
{
AppDomain maindomain= AppDomain.CurrentDomain;
tunnel = (Tunnel)maindomain.CreateInstanceAndUnwrap(typeof(Tunnel).FullName,
typeof(Tunnel).FullName);
AppDomain domain = base.CreateDomain(friendlyName, securityInfo, appDomainInfo);
//Load assembly C (plug in) in domain.
// C uses B so it loads a new instance of B into the domain also at the same time.
// If I do this here it creates new instance of A but I need to use the one in
// the main domain.
//tunnel = (Tunnel)domain.CreateInstanceAndUnwrap(typeof(Tunnel).FullName,
typeof(Tunnel).FullName);
tunnel.SetPointer(//Send data from B loaded in new domain.)
}
So at the end it looks something like this:
Default Domain:
Main.dll
A.dll
B.dll
Plug in Domain:
B.dll
C.dll

In your code above you are calling
AppDomain.CurrentDomain.CreateInstanceAndUnwrap(...)
This is simply a round-about way of creating an object in the current domain, same as if you just called the constructor. You need to call that method on a remote domain, ie.
AppDomain domain = AppDomain.Create(...)
Tunnel tunnel = (Tunnel)domain.CreateInstanceAndUnwrap(...)
If you then call tunnel.SetPointer(...) that will run on the remote object.

So I guess that:
class B : MarshallByRef
with that you need only create the object in the domain from class A:
AppDomain domain = ...
B inst = (B) domain.CreateInstanceAndUnwrap(typeof(B).Assembly.FullName, typeof(B).FullName);
inst.DoPlugin("C");
The DoPlugin() method will dynamically load type "C" and call whatever methods are appropriate.

You may have to copy across search paths and evidence when creating "Child" domains (especially for running under unit test libraries):
var dom = AppDomain.CreateDomain("NewDomain",
AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.BaseDirectory,
AppDomain.CurrentDomain.RelativeSearchPath, false);
try
{
var tunnel = (MyMarshallByRef)dom.CreateInstanceAndUnwrap(
typeof(MyMarshallByRef).Assembly.FullName,
typeof(MyMarshallByRef).FullName);
tunnel.DoStuff("data");
}
finally
{
AppDomain.Unload(dom);
}
Also note that any arguments the the DoStuff method, and any types it returns must be marked [Serializable]

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?

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.

CreateInstanceAndUnwrap and Domain

I have a property whose instance I want to be in other domain.
public ModuleLoader Loader
{
get
{
if(_loader == null)
_loader = (ModuleLoader)myDomain.CreateInstanceAndUnwrap(
this.GetType().Assembly.FullName,
"ModuleLoader",
false,
System.Reflection.BindingFlags.CreateInstance,
null,
null,
null,
null);
System.Diagnostics.Debug.WriteLine("Is proxy={0}",
RemotingServices.IsTransparentProxy(_loader));
//writes false
_loader.Session = this;
return _loader;
}
}
This works fine. But I assume all the method calls on _loader instance will invoke in other domain (myDomain). But when I run following code, it still writes main app domain.
public void LoadModule(string moduleAssembly)
{
System.Diagnostics.Debug.WriteLine("Is proxy={0}",
RemotingServices.IsTransparentProxy(this));
System.Diagnostics.Debug.WriteLine(
AppDomain.CurrentDomain.FriendlyName);
System.Diagnostics.Debug.WriteLine("-----------");
}
Is it because of Unwrap()? Where I am doing wrong?
I understand AppDomain creates seperate memory. What I need is my main application runs, it loads modules in different AppDomain. Since main app also wants to watch some activity of modules and interfare with objects running in seperate domain, what is the best way to achieve it.
If you want to actually run the code in the other assembly, you need to make your ModuleLoader class inherit from MarshalByRefObject. If you do that, CreateInstanceAndUnwrap() will actually return a proxy and the call will be executed in the other appdomain.
If you don't do that, and instead mark the class as Serializable (as an exception message suggests), CreateInstanceAndUnwrap() will create the object in the other appdomain, serialize it, transfer the serialized form to the original appdomain, deserialize it there and call the method on the deserialized instance.

C# Reflection: Replace a referenced assembly

I am currently writing a framework for MutationTesting. The code is almost complete, but there is a tiny bit which (after spending half a day on it) I cannot figure out:
via reflection I would like to execute a method "TestMethod" which is inside the class "TestClass". The project in which "TestClass" is, references an essembly, lets call it "Proband.dll".
The "TestMethod" creates an object of some type inside the Proband.dll and executes a method on that object.
In order to clarify a little:
TestClass - is a class that contains unit tests.
TestMethod - is a single unit test.
Proband.dll - contains the method/class to test.
Before executing the TestMethod, I already successfully disassembled, mutated and reassembled the Proband.dll. So now I have a new "Proband.dll" which shall be taken by the TestClass instead!
The problem is that the TestClass is already in the middle of execution. I was thinking whether it is possible to create an AppDomain in which the new Proband.dll will be loaded and inside the fresh AppDomain, the TestMethod will be executed.
I have already created this AppDomain and successfully loaded the new Proband.dll into it, however, I do not know how to execute the TestMethod inside this new AppDomain. Also I do not know whether this is going to "replace" the old Proband.dll for the test method.
Here is my TestClass:
[TestClass()]
public class TestClass
{
[TestInitialize()]
public void MyTestInitialize()
{
// code to perform the mutation.
// From here, the "TestMethod" should be called with
// the new "Proband.dll".
}
[TestMethod()]
public void TestMethod()
{
// test code calling into Proband.dll
}
}
Does anyone have an idea of how this could be achieved? Or any clue or keyword?
Thanks,
Christian
This is probably overkill, but check out my answer:
Static Fields in AppDomain
You'll want to create the TestClass in the AppDomain that you've loaded the Proband.dll in, and use a MarshalByRef to keep the class in the AppDomain (so you can unload it later).
Here is more about what I did (not exact, as i'm changing it to match more what you need).
// Create Domain
_RemoteDomain = AppDomain.CreateDomain(_RemoteDomainName,
AppDomain.CurrentDomain.Evidence,
AppDomain.CurrentDomain.BaseDirectory,
AppDomain.CurrentDomain.BaseDirectory,
true);
// Load an Assembly Loader in the domain (mine was Builder)
// This loads the Builder which is in the current domain into the remote domain
_Builder = (Builder)_RemoteDomain.CreateInstanceAndUnwrap(
Assembly.GetExecutingAssembly().FullName, "<namespace>.Builder");
_Builder.Execute(pathToTestDll)
public class Builder : MarshalByRefObject
{
public void Execute(string pathToTestDll)
{
// I used this so the DLL could be deleted while
// the domain was still using the version that
// exists at this moment.
Assembly newAssembly = Assembly.Load(
System.IO.File.ReadAllBytes(pathToTestDll));
Type testClass = newAssembly.GetType("<namespace>.TestClass",
false, true);
if (testClass != null)
{
// Here is where you use reflection and/or an interface
// to execute your method(s).
}
}
}
This should provide you the solution you need.

application domains and threads

A quote from MSDN: http://msdn.microsoft.com/en-us/library/6kac2kdh.aspx
One or more managed threads
(represented by
System.Threading.Thread) can run in
one or any number of application
domains within the same managed
process. Although each application
domain is started with a single
thread, code in that application
domain can create additional
application domains and additional
threads. The result is that a managed
thread can move freely between
application domains inside the same
managed process; you might have only
one thread moving among several
application domains.
I tried to write code with 2 application domains that share one thread. But i gave up. I have really no idea how this is possible. Could you give me a code sample for this?
This can be done by simply creating an object which is MarshalByRef in a separate AppDomain and then calling a method on that object.
Take for example the following class definition.
public interface IFoo
{
void SomeMethod();
}
public class Foo : MarshalByRefObject, IFoo
{
public Foo()
{
}
public void SomeMethod()
{
Console.WriteLine("In Other AppDomain");
}
}
You can then use this definition to call into a separate AppDomain from the current one. At the point the call writes to the Console you will have 1 thread in 2 AppDomains (at 2 different points in the call stack). Here is the sample code for that.
public static void CallIntoOtherAppDomain()
{
var domain = AppDomain.CreateDomain("Other Domain");
var obj = domain.CreateInstanceAndUnwrap(typeof(Foo).Assembly.FullName, typeof(Foo).FullName);
var foo = (IFoo)obj;
foo.SomeMethod();
}
Call a method on an object of the other app domain.

Categories

Resources