C# AppDomain can't load DLL - c#

I'm attempting to build a hot-swapable plugin system where the user can dynamically load and unload dll's. It's necessary that the core application restart as little as possible, so I'm moving as much of the functionality to external libraries instead. From what research I've figured out, I need to create a second AppDomain and load the DLL into that, then just pass along parameters and such to that to run it. Currently, I believe I have most of the program working, but I'm encountering an error when calling the AppDomain.Unwrap() function on the object from CreateInstance. The error is as follows:
System.InvalidCastException: Unable to cast transparent proxy to type 'Program1.Loader'.
Here is the loading code:
try {
unload(dom,out loader,true);
dom=null;
AppDomainSetup dms = new AppDomainSetup();
dms.ConfigurationFile=Environment.CurrentDirectory+Path.DirectorySeparatorChar+"Program1.exe.config";
dms.ApplicationBase=Environment.CurrentDirectory+Path.DirectorySeparatorChar+"Plugins";
Evidence ev = AppDomain.CurrentDomain.Evidence;
dom=AppDomain.CreateDomain("PluginManager",ev,dms);
AssemblyName an = AssemblyName.GetAssemblyName(Environment.CurrentDirectory+"\\Plugins\\PluginManager.dll");
ObjectHandle obj = dom.CreateInstance(an.FullName,"PluginManager.PluginManager");
loader = (Loader)obj.Unwrap();
loader.LoadAssembly(#"PluginManager.dll");
if(!suppressOutput)
Console.WriteLine("Reload successful.");
}
catch(Exception e) {
unload(dom,out loader,true);
loader=null;
Console.WriteLine("PluginManager failed loading. Enter \"reload\" to try again.\n");
Console.Write(e+"\n\n");
}
This line is where the error is thrown:
loader = (Loader)obj.Unwrap();
The external DLL has almost no code in it, since this is a proof of concept. It is as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PluginManager {
public class PluginManager:MarshalByRefObject {
public void run(string comm) {
Console.WriteLine(comm);
}
}
}
Edit: Here's the code for the loader class.
class Loader:MarshalByRefObject {
private Assembly _assembly;
public override object InitializeLifetimeService() {
return null;
}
public void LoadAssembly(string path) {
_assembly=Assembly.Load(AssemblyName.GetAssemblyName(path));
}
public object ExecuteStaticMethod(string typeName,string methodName,params object[] parameters) {
Type type = _assembly.GetType(typeName);
MethodInfo method = type.GetMethod(methodName,BindingFlags.Static|BindingFlags.Public);
return method.Invoke(null,parameters);
}
}

It looks like you're trying to create an object of the type PluginManager in the AppDomain and then cast its proxy to the type Loader (which is missing from your code sample).
The problem is in these lines:
ObjectHandle obj = dom.CreateInstance(an.FullName,"PluginManager.PluginManager");
loader = (Loader)obj.Unwrap();
It would work if you either create an instance of Loader instead of PluginManager - or - cast to PluginManager instead of Loader. My guess is the former:
ObjectHandle obj = dom.CreateInstance(an.FullName,"LoaderNamespace.Loader");
(Replace LoaderNamespace with the real one.)

Related

Why usage of dynamic throws exception in runtime?

I have an external dll I'm loading into my appdomain during runtime.
I'm creating an instance of a class from that assembly into a local dynamic variable.
As far as I understood the usage of dynamic in C#, I can simply call a method of it, which will be resolved at run time...
Using this approach, the following code gets me a runtime "'object' does not contain a definition for 'Get'" exception.
I'll try to illustrate the structure as I can't expose the actual code.
External dll name: a.b.c
namespace Ext
{
public static class FactoryCreator
{
public static ProxyFactory CreateFactory()
{
return new ProxyFactory();
}
}
public interface FactoryIfc
{
Proxy Get();
}
internal class ProxyFactory: FactoryIfc
{
private Proxy proxy;
public Proxy Get()
{
if (this.proxy == null)
this.proxy = <a method to create a proxy>
return this.proxy;
}
}
}
I'm using the following code
var assembly = "a.b.c, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<key>,processorArchitecture=MSIL";
var instName = "Ext.FactoryCreator";
dynamic factoryCreator = AppDomain.CurrentDomain.Load(assembly).GetType(instName).GetMethod("CreateFactory").Invoke(null, new object[0]);
dynamic proxy = factoryCreator.Get();
I understand that for FactoryCreator dynamic variable, I need to get the Type and invoke the static method of it, but.. as I said, it is throwing an exception "'object' does not contains a definition for 'Get'" - at the factory.Get() statement - while I would expect dynamic factory to be resolve automatically to the object and service the Get() call.
Observing the situation under a debug session, I can clearly see the Get method using factory.GetType().GetMethods() in the quickwatch window.
Can you explain what is happening?
Must I use factory.GetType().GetMethod("Get") followed by an Invoke? I thought the power of dynamic should work this out automatically in runtime...

How to call a method from an external Assembly

I have sample c# project:
namespace SampleExe
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
And I have sample c# dll:
namespace SampleDll
{
public class Program
{
public static void TestMethod(string samplestr)
{
Console.WriteLine("TestMethod Void Runned! Your string is: "+samplestr);
}
}
}
How can i call TestMethod() from compilled SampleDll.DLL (i want to load external dll)
Here's a working example of using Reflection to load a library at runtime and execute a static method. Note that it assumes quite a lot: you must know the library name, the class name, the method name, and all of its arguments ahead of time. It's often much easier to just reference a library directly.
A great way to use Reflection successfully is together with inheritance/interfaces. Library A contains the base class or interface, and Library B contains a derived class. Library A can use reflection to load Library B , then find all class types in Library B that are derived from the base class or interface (using Type.IsAssignableFrom). In this way, Library A will have strongly typed properties and methods to work with coming from the base, instead of having to know string names of classes, methods, and properties in Library B a priori.
Code for main EXE doing the reflection:
using System;
using System.IO;
using System.Linq;
using System.Reflection;
namespace SomeNamespace
{
public class Program
{
static void Main()
{
string pathToSampleDLL = "<if you know the path ahead of time, use it>";
// if SampleDLL.dll is in same directory as this EXE (a common occurrence):
string workingDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
pathToSampleDLL = Path.Combine(workingDirectory, "SampleDLL.dll");
// load the DLL at runtime
Assembly sampleDLL = Assembly.LoadFrom(pathToSampleDLL);
// since you know the type name, you can use LINQ to return your type:
Type sampleType = sampleDLL.GetTypes().FirstOrDefault(t => t.Name == "Program");
// you are looking for a static method on this type, and you know its name, so use GetMethods:
MethodInfo staticMethod = sampleType.GetMethod("TestMethod", BindingFlags.Public | BindingFlags.Static);
// invoke the method. Since you know its arguments and return value ahead of time, just hard code it:
// you can use null for the object since this is a static method. It takes only one argument, a sample string
staticMethod.Invoke(null, new object[] { "sampleStr" });
}
}
}
Code for sample library (compiled to "SampleDLL.dll"):
using System;
namespace SampleDll
{
public class Program
{
public static void TestMethod(string sampleStr)
{
Console.WriteLine("TestMethod Void Runned! Your string is: " + sampleStr);
}
}
}
You have multiple options for this. You can create a dll and add the dll as a reference to the project. You can add the project as a reference also. You can create a NuGet package of dll also and use that.
Then simply call SampleDll.Program.TestMethod
To do this, you need to use reflection.
var assembly = Assembly.Load(File.ReadAllBytes("SampleDLL.dll"));
foreach(Type type in assembly.GetExportedTypes())
{
var c = Activator.CreateInstance(type);
type.InvokeMember("TestMethod", BindingFlags.InvokeMethod, null, c, new object[] { #"Hi!" });
}

Invoke Method, pass object as type

I am working with the following class:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
And I have a string containing following:
public class PersonActions
{
public static void Greet(Person p)
{
string test = p.Name;
}
}
In my client application developped in WPF (.NET 4.7) I am compiling this string at runtime and invoke the Greet method like this:
//Person x = new Person();
//x.Name = "Albert";
//x.Age = 76;
var assembly = Assembly.LoadFile(pathToAsseblyContainingPersonClass);
Type t = assembly.GetType("Person");
var x = Activator.CreateInstance(t);
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add(pathToAsseblyContainingPersonClass);
//code being the code from abrom above (PersonActions)
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
Assembly importassembly = results.CompiledAssembly;
Type assemblytype = importassembly.GetType("PersonActions");
ConstructorInfo constructor = assemblytype.GetConstructor(Type.EmptyTypes);
object classObject = constructor.Invoke(new object[] { });// not used for anything
MethodInfo main = assemblytype.GetMethod("Greet");
main.Invoke(classObject, new object[] { x });
Unfotunately this always crashes because somehow it cannot find the method with the same parameter type even if the types come from the same assembly.
The error thrown is a "System.IO.FileNotFoundException" although this makes not much sense. It's not a file that can't be found it's the method overload.
Somehow it is just looking for:
public static void Greet(object p)
Using just 'object' as parameter type works, but is not a possibility in my case.
Is there a way to recieve the object in the type that it is? Or maby to tell the Invocation method that the types match?
EDIT:
Guess I made both an error in my code above and my tests:
Declareing the Person as mentioned before (now commented above) works properly:
Person x = new Person();
x.Name = "Albert";
x.Age = 76;
Using Activator.Createinstance (now correct above) to create the Person x dynamically form the assebly does not work. It seems like var x = Activator.CreateInstance(t);
causes x still to be an "object" and not a "Person".
EDIT 2:
Here a minimal working example of the problem:
Having a solution containing one WPF application. MainWindow.cs containing:
using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Example
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
string code = #"public class PersonActions
{
public static void Greet(Person p)
{
}
}";
//Change to an absolute path if there is an exception
string pathToAsseblyContainingPersonClass = System.IO.Path.GetFullPath(#"..\..\..\Person\bin\Debug\Person.dll");
var assembly = Assembly.LoadFile(pathToAsseblyContainingPersonClass);
Type t = assembly.GetType("Person");
var x = Activator.CreateInstance(t);
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add(pathToAsseblyContainingPersonClass);
//code being the code from abrom above (PersonActions)
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
Assembly importassembly = results.CompiledAssembly;
Type assemblytype = importassembly.GetType("PersonActions");
ConstructorInfo constructor = assemblytype.GetConstructor(Type.EmptyTypes);
object classObject = constructor.Invoke(new object[] { });// not used for anything
MethodInfo main = assemblytype.GetMethod("Greet");
main.Invoke(classObject, new object[] { x });
}
}
}
And containing one class Library Project calles "Person" containing: (note that there is no namespace)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
EDIT 3: What I ended up with
Thanks to #Adam Benson I could identify the whole problem. The overall problem is, that the current appdomain does not allow to directly load load assemblies from other appdomains. Like Adam pointed out there are three solutions for that (in the linked Microsoft article). The third and definitely also the easiest solution to implement is using the AssemblyResolve event. Although this is a good solution it pains my heart and bones to let my application run into exceptions to resolve this problem.
Like Adam also pointed out is that you get another exception if you put the dll directly into the folder where the exe is located. This is only partly true since the evil twin error only appears if you compare the Person from the original Debug folder assembly and the Person loaded from the appdomain assembly (basically if you have the dll in both directories)
Loading the assembly only from the folder where there exe is located resolves both the FileNotFound and the evil twin error:
old: System.IO.Path.GetFullPath(#"..\..\..\Person\bin\Debug\Person.dll");
new:System.IO.Path.GetFullPath(#"Person.dll");
So what I ended up doing was copying the necessary assembly into the current working directory first:
File.Copy(pathToAsseblyContainingPersonClass, currentDir + #"\\Person.dll" , true);
results.CompiledAssembly throws FileNotFoundException because the assembly is not being generated due to an error occurring during the generation process. You can see the actual compilation error by checking Errors property of CompilerResults.
In this case, the error is that code provided to CompileAssemblyFromSource does not know what Person class is.
You can fix this by adding a reference to the assembly containing Person class:
parameters.ReferencedAssemblies.Add("some_dll");
Edit: I missed the comment saying that parameters contain the reference to assembly containing Person class. That probably means that there is a different error in the results.Error collection. Check it and I will update the answer (I cannot comment yet due to not having 50 rep).
This works (at least it doesn't exception):
object classObject = constructor.Invoke(new object[] { });// not used for anything
//////////////////////////////////////////
AppDomain.CurrentDomain.AssemblyResolve +=
(object sender, ResolveEventArgs resolve_args) =>
{
if (resolve_args.Name == assembly.FullName)
return assembly;
return null;
};
//////////////////////////////////////////
MethodInfo main = assemblytype.GetMethod("Greet");
Based on https://support.microsoft.com/en-gb/help/837908/how-to-load-an-assembly-at-runtime-that-is-located-in-a-folder-that-is method 3 (use the AssemblyResolve event).
I must confess to being mystified as to why it doesn't just work since you have added a ref to the assembly.
I should add that copying the extra dll that defines Person into your exe directory will not work as you then run into the "evil twin" issue where a type created in one assembly cannot be used by another instance of that assembly. (The error you get is the mind-bending "System.ArgumentException: 'Object of type 'Person' cannot be converted to type 'Person'."!!)
Edit: Just discovered that LoadFrom avoids loading the same assembly twice. See Difference between LoadFile and LoadFrom with .NET Assemblies?

Dynamically loaded Assembly not loading in new AppDomain

This is not a duplicate - I have reviewed this related StackOverflow question with no luck: How to Load an Assembly to AppDomain with all references recursively?
I have two console applications. AssemblyLoaderTest.exe and testapp.exe
I am trying to use AssemblyLoaderTest.exe to dynamically load testapp.exe and call a method from a class within testapp.exe
So far the code works - the method "TestWrite()" in testapp.exe is executed correctly (and outputsuccess.txt is written), however, testapp.exe is loaded in the same AppDomain, which is proven because "CallMethodFromDllInNewAppDomain" always returns false. I am trying to load testapp.exe in a new AppDomain.
My question: how can I modify the below code so that testapp.exe is loaded in a new AppDomain, and as a result, "CallMethodFromDllInNewAppDomain" returns true? Thank you!
Code below. Both can be simply copied into new Console applications in VS and executed/compiled.
Console application #1:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Policy;
namespace AssemblyLoaderTest
{
class Program
{
static void Main(string[] args)
{
List<object> parameters = new List<object>();
parameters.Add("Test from console app");
bool loadedInNewAppDomain = DynamicAssemblyLoader.CallMethodFromDllInNewAppDomain(#"c:\temp\testapp.exe", "testapp.TestClass", "TestWrite", parameters);
}
}
public static class DynamicAssemblyLoader
{
public static string ExeLoc = "";
public static bool CallMethodFromDllInNewAppDomain(string exePath, string fullyQualifiedClassName, string methodName, List<object> parameters)
{
ExeLoc = exePath;
List<Assembly> assembliesLoadedBefore = AppDomain.CurrentDomain.GetAssemblies().ToList<Assembly>();
int assemblyCountBefore = assembliesLoadedBefore.Count;
AppDomainSetup domaininfo = new AppDomainSetup();
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain domain = AppDomain.CreateDomain("testDomain", adevidence, domaininfo);
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
domain.CreateInstanceFromAndUnwrap(exePath, fullyQualifiedClassName);
List<Assembly> assemblies = domain.GetAssemblies().ToList<Assembly>();
string mainExeName = System.IO.Path.GetFileNameWithoutExtension(exePath);
Assembly assembly = assemblies.FirstOrDefault(c => c.FullName.StartsWith(mainExeName));
Type type2 = assembly.GetType(fullyQualifiedClassName);
List<Type> parameterTypes = new List<Type>();
foreach (var parameter in parameters)
{
parameterTypes.Add(parameter.GetType());
}
var methodInfo = type2.GetMethod(methodName, parameterTypes.ToArray());
var testClass = Activator.CreateInstance(type2);
object returnValue = methodInfo.Invoke(testClass, parameters.ToArray());
List<Assembly> assembliesLoadedAfter = AppDomain.CurrentDomain.GetAssemblies().ToList<Assembly>();
int assemblyCountAfter = assembliesLoadedAfter.Count;
if (assemblyCountAfter > assemblyCountBefore)
{
// Code always comes here
return false;
}
else
{
// This would prove the assembly was loaded in a NEW domain. Never gets here.
return true;
}
}
public static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
// This is required I've found
return System.Reflection.Assembly.LoadFrom(ExeLoc);
}
}
}
Console application #2:
using System;
namespace testapp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello from console");
}
}
[Serializable]
public class TestClass : MarshalByRefObject
{
public void TestWrite(string message)
{
System.IO.File.WriteAllText(#"outputsuccess.txt", message);
}
}
}
Use this class. Here are some notes:
This class explicitly sets the current directory of the process and the app base path of the isolated app domain. This isn't entirely necessary, but it will make your life a whole lot easier.
If you don't set the app base path to the directory containing the secondary assembly, then the runtime will attempt to resolve any dependencies of the secondary assembly against the same app base path as the primary assembly, which probably doesn't have the secondary assembly's dependencies. You could use the AssemblyResolve event to manually resolve the dependencies correctly, but settings the app base path is a much simpler and less error-prone way to do this.
If you don't set Environment.CurrentDirectory, then file operations such as File.WriteAllText("myfile.txt", "blah") will resolve paths against the current directory, which is probably not what the secondary assembly's author intended. (ASIDE: Always resolve paths manually for this reason.)
I believe simple reflection operations like GetMethod won't work on a MarshalByRefObject proxy such as returned by CreateInstanceFromAndUnwrap. So you need to do a little more to invoke.
If you are the owner of both the primary and secondary assemblies, you could create an interface for the invocation -- put the interface in a shared assembly, define the cross-domain call in the interface, implement the interface in the target class, do a domain.CreateInstanceFromAndUnwrap on the target type and cast the result as the interface, which you can then use to call across the domain boundary.
The solution below provides an alternative means that is less invasive -- you don't have to own the secondary assembly for this technique to work. The idea is that the primary domain creates a well-known intermediary object (InvokerHelper) in the secondary domain, and that intermediary performs the necessary reflection from inside the secondary domain.
Here's a complete implementation:
// Provides a means of invoking an assembly in an isolated appdomain
public static class IsolatedInvoker
{
// main Invoke method
public static void Invoke(string assemblyFile, string typeName, string methodName, object[] parameters)
{
// resolve path
assemblyFile = Path.Combine(Environment.CurrentDirectory, assemblyFile);
Debug.Assert(assemblyFile != null);
// get base path
var appBasePath = Path.GetDirectoryName(assemblyFile);
Debug.Assert(appBasePath != null);
// change current directory
var oldDirectory = Environment.CurrentDirectory;
Environment.CurrentDirectory = appBasePath;
try
{
// create new app domain
var domain = AppDomain.CreateDomain(Guid.NewGuid().ToString(), null, appBasePath, null, false);
try
{
// create instance
var invoker = (InvokerHelper) domain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, typeof(InvokerHelper).FullName);
// invoke method
var result = invoker.InvokeHelper(assemblyFile, typeName, methodName, parameters);
// process result
Debug.WriteLine(result);
}
finally
{
// unload app domain
AppDomain.Unload(domain);
}
}
finally
{
// revert current directory
Environment.CurrentDirectory = oldDirectory;
}
}
// This helper class is instantiated in an isolated app domain
private class InvokerHelper : MarshalByRefObject
{
// This helper function is executed in an isolated app domain
public object InvokeHelper(string assemblyFile, string typeName, string methodName, object[] parameters)
{
// create an instance of the target object
var handle = Activator.CreateInstanceFrom(assemblyFile, typeName);
// get the instance of the target object
var instance = handle.Unwrap();
// get the type of the target object
var type = instance.GetType();
// invoke the method
var result = type.InvokeMember(methodName, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, instance, parameters);
// success
return result;
}
}
}

Why am I getting this exception when emitting classes that reference each other via value-type generics?

This code snippet is a simplified extract of my class-generation code, which creates two classes that reference each other as arguments in a generic type:
namespace Sandbox
{
using System;
using System.Reflection;
using System.Reflection.Emit;
internal class Program
{
private static void Main(string[] args)
{
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Test"), AssemblyBuilderAccess.Run);
var module = assembly.DefineDynamicModule("Test");
var typeOne = module.DefineType("TypeOne", TypeAttributes.Public);
var typeTwo = module.DefineType("TypeTwo", TypeAttributes.Public);
typeOne.DefineField("Two", typeof(TestGeneric<>).MakeGenericType(typeTwo), FieldAttributes.Public);
typeTwo.DefineField("One", typeof(TestGeneric<>).MakeGenericType(typeOne), FieldAttributes.Public);
typeOne.CreateType();
typeTwo.CreateType();
Console.WriteLine("Done");
Console.ReadLine();
}
}
public struct TestGeneric<T>
{
}
}
Which should produce MSIL equivalent to the following:
public class TypeOne
{
public Program.TestGeneric<TypeTwo> Two;
}
public class TypeTwo
{
public Program.TestGeneric<TypeOne> One;
}
But instead throws this exception on the line typeOne.CreateType():
System.TypeLoadException was unhandled
Message=Could not load type 'TypeTwo' from assembly 'Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
Source=mscorlib
TypeName=TypeTwo
StackTrace:
at System.Reflection.Emit.TypeBuilder.TermCreateClass(RuntimeModule module, Int32 tk, ObjectHandleOnStack type)
at System.Reflection.Emit.TypeBuilder.CreateTypeNoLock()
at System.Reflection.Emit.TypeBuilder.CreateType()
at Sandbox.Program.Main(String[] args) in C:\Users\aca1\Code\Sandbox\Program.cs:line 20
Interesting things to note:
The circular reference isn't required to cause the exception; if I don't define field One on TypeTwo, creating TypeOne before TypeTwo still fails, but creating TypeTwo before TypeOne succeeds. Therefore, the exception is specifically caused by using a type that has not yet been created as an argument in a generic field type; however, because I need to use a circular reference, I cannot avoid this situation by creating the types in a specific order.
Yes, I do need to use a circular reference.
Removing the wrapper TestGeneric<> type and declaring the fields as TypeOne & TypeTwo directly does not produce this error; thus I can use dynamic types that have been defined but not created.
Changing TestGeneric<> from a struct to a class does not produce this error; so this pattern does work with most generics, just not generic value types.
I can't change the declaration of TestGeneric<> in my case as it is declared in another assembly - specifically, System.Data.Linq.EntityRef<> declared in System.Data.Linq.dll.
My circular reference is caused by representing two tables with foreign key references to each other; hence the need for that specific generic type and this specific pattern.
Changing the circular reference to a self-reference edit succeeds. This failed originally because I had TestGeneric<> as a nested type in Program, so it inherited the internal visibility. I've fixed this now in the code sample above, and it does in fact work.
Compiling the generated code manually (as C# code) also works, so it's not an obscure compiler issue.
Any ideas on a) why this occuring, b) how I can fix this and/or c) how I can work around it?
Thanks.
I do not know exactly why this is occurring. I have a good guess.
As you have observed, creating a generic class is treated differently than creating a generic struct. When you create the type 'TypeOne' the emitter needs to create the generic type 'TestGeneric' and for some reason the proper Type is needed rather than the TypeBuilder. Perhaps this occurs when trying to determine the size of the new generic struct? I'm not sure. Maybe the TypeBuilder can't figure out its size so the created 'TypeTwo' Type is needed.
When TypeTwo cannot be found (because it only exists as a TypeBuilder) the AppDomain's TypeResolve event will be triggered. This gives you a chance to fix the problem. While handling the TypeResolve event you can create the type 'TypeTwo' and solve the problem.
Here is a rough implementation:
namespace Sandbox
{
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
internal class Program
{
private static void Main(string[] args)
{
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Test"), AssemblyBuilderAccess.Run);
var module = assembly.DefineDynamicModule("Test");
var typeOne = module.DefineType("TypeOne", TypeAttributes.Public);
var typeTwo = module.DefineType("TypeTwo", TypeAttributes.Public);
typeOne.DefineField("Two", typeof(TestGeneric<>).MakeGenericType(typeTwo), FieldAttributes.Public);
typeTwo.DefineField("One", typeof(TestGeneric<>).MakeGenericType(typeOne), FieldAttributes.Public);
TypeConflictResolver resolver = new TypeConflictResolver();
resolver.AddTypeBuilder(typeTwo);
resolver.Bind(AppDomain.CurrentDomain);
typeOne.CreateType();
typeTwo.CreateType();
resolver.Release();
Console.WriteLine("Done");
Console.ReadLine();
}
}
public struct TestGeneric<T>
{
}
internal class TypeConflictResolver
{
private AppDomain _domain;
private Dictionary<string, TypeBuilder> _builders = new Dictionary<string, TypeBuilder>();
public void Bind(AppDomain domain)
{
domain.TypeResolve += Domain_TypeResolve;
}
public void Release()
{
if (_domain != null)
{
_domain.TypeResolve -= Domain_TypeResolve;
_domain = null;
}
}
public void AddTypeBuilder(TypeBuilder builder)
{
_builders.Add(builder.Name, builder);
}
Assembly Domain_TypeResolve(object sender, ResolveEventArgs args)
{
if (_builders.ContainsKey(args.Name))
{
return _builders[args.Name].CreateType().Assembly;
}
else
{
return null;
}
}
}
}

Categories

Resources