Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CreateInstanceAndUnwrap in Another Domain?

I'm having issues with CreateInstanceAndUnwrap at the moment for some reason (was working prior).

My process is this:

I dynamically generate some code and it loads DLL's from a subdirectory via MEF. These applications then load different pieces (on demand) from those DLL's. I had to update my code to now include an AppDomainSetup that contains the path of the calling assembly.

I create the new AppDomain correctly -- no issues. When I try to run this code:

object runtime = domain.CreateInstanceAndUnwrap(
                typeof(CrossDomainApplication).Assembly.FullName,
                typeof(CrossDomainApplication).FullName);

I have massive problems -- the runtime (variable above) no longer can cast to CrossDomainApplication or ICrossDomainApplication.

The actual object looks like:

public class CrossDomainApplication : MarshalByRefObject, ICrossDomainApplication

And the interface looks like:

public interface ICrossDomainApplication
{
    void Run(CrossDomainApplicationParameters parameters);
}

And the parameters look like:

[Serializable]
public class CrossDomainApplicationParameters : MarshalByRefObject
{
    public object FactoryType { get; set; }
    public Type ApplicationType { get; set; }
    public string ModuleName { get; set; }
    public object[] Parameters { get; set; }
}

The native type of runtime appears to be MarshalByRefObject -- and it doesn't like converting to anything else.

Any thoughts on what could be wrong?

EDIT: Here's the error I get when I run it as the following:

            ICrossDomainApplication runtime = (ICrossDomainApplication)domain.CreateInstanceAndUnwrap(
                     typeof(CrossDomainApplication).Assembly.FullName,
                     typeof(CrossDomainApplication).FullName);

            //Exception before reaching here
            runtime.Run(parameters);

System.InvalidCastException: Unable to cast transparent proxy to type 'Infrastructure.ICrossDomainApplication'.

Here's what the domain looks like, as I create it:

        AppDomain domain = AppDomain.CreateDomain(
                   Guid.NewGuid().ToString(),
                   null,
                   new AppDomainSetup()
                   {
                       ApplicationBase = GetPath(),
                       ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
                       ApplicationName = AppDomain.CurrentDomain.SetupInformation.ApplicationName,
                       LoaderOptimization = LoaderOptimization.MultiDomainHost
                   });               

and GetPath() looks like this:

    private string GetPath()
    {
        Uri path = new Uri(Assembly.GetCallingAssembly().CodeBase);

        if (path.IsFile)
        {
            path = new Uri(path, Path.GetDirectoryName(path.AbsolutePath));
        }

        return path.LocalPath.Replace("%20", " ");
    }
like image 352
Locke Avatar asked Jul 15 '14 14:07

Locke


1 Answers

In the desire to help some other poor, poor person out, I finally figured it out after trying SO MANY other combinations.

I had to change a few things... the first of which:

            AppDomain domain = AppDomain.CreateDomain(
                    Guid.NewGuid().ToString(),
                    AppDomain.CurrentDomain.Evidence,
                    new AppDomainSetup()
                    {
                        ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
                        ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
                        LoaderOptimization = LoaderOptimization.MultiDomainHost,
                        PrivateBinPath = GetPrivateBin(AppDomain.CurrentDomain.SetupInformation.ApplicationBase)
                    });

PrivateBinPath is the real trick here that enabled everything else to (finally) start working. PrivateBinPath (read the documentation) ABSOLUTELY needs to be a relative path. If you have a subfolder called "assemblies" then the PrivateBinPath should be "assemblies". If you precede it with a \ or an absolute path, it will not work.

By doing this, it caused the AssemblyResolve event to finally fire. I did that with the following (before creating the new, child AppDomain):

            AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;

and the ResolveAssembly method looks like this:

    private Assembly ResolveAssembly(object sender, ResolveEventArgs args)
    {
        var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();

        foreach (var assembly in loadedAssemblies)
        {
            if (assembly.FullName == args.Name)
            {
                return assembly;
            }
        }

        return null;
    }

The method can be written much easier with Linq, but I kept it this way to try and make it somewhat obvious for myself what was being parsed and loaded for debugging purposes.

And, per always, make sure that you disconnect your event (if you're loading an AppDomain at a time):

            AppDomain.CurrentDomain.AssemblyResolve -= ResolveAssembly;

That fixed everything. Thank you to everyone who helped!

like image 120
Locke Avatar answered Nov 09 '22 14:11

Locke