Locke
Locke

Reputation: 1165

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", " ");
    }

Upvotes: 9

Views: 9740

Answers (2)

Locke
Locke

Reputation: 1165

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!

Upvotes: 16

Darek
Darek

Reputation: 4797

This is not an answer, just sample code to share

Hmm, could it be something else? This sample, not exactly 1:1 with what you described, works.

#region

using System;
using System.Reflection;
using System.Threading;

#endregion

internal class Program
{
    #region Methods

    private static void Main(string[] args)
    {
        // Get and display the friendly name of the default AppDomain. 
        string callingDomainName = Thread.GetDomain().FriendlyName;
        Console.WriteLine(callingDomainName);

        // Get and display the full name of the EXE assembly. 
        string exeAssembly = Assembly.GetEntryAssembly().FullName;
        Console.WriteLine(exeAssembly);

        // Construct and initialize settings for a second AppDomain.
        var ads = new AppDomainSetup
                  {
                      ApplicationBase = Environment.CurrentDirectory,
                      DisallowBindingRedirects = false,
                      DisallowCodeDownload = true,
                      ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
                  };

        // Create the second AppDomain.
        AppDomain ad2 = AppDomain.CreateDomain("AD #2", null, ads);

        // Create an instance of MarshalbyRefType in the second AppDomain.  
        // A proxy to the object is returned.
        var mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeAssembly, typeof(MarshalByRefType).FullName);

        // Call a method on the object via the proxy, passing the  
        // default AppDomain's friendly name in as a parameter.
        mbrt.SomeMethod(new MarshalByRefParameter{ModuleName =  callingDomainName});

        // Unload the second AppDomain. This deletes its object and  
        // invalidates the proxy object.
        AppDomain.Unload(ad2);
        try
        {
            // Call the method again. Note that this time it fails  
            // because the second AppDomain was unloaded.
            mbrt.SomeMethod(new MarshalByRefParameter { ModuleName = callingDomainName });
            Console.WriteLine("Successful call.");
        }
        catch (AppDomainUnloadedException)
        {
            Console.WriteLine("Failed call; this is expected.");
        }
    }


    #endregion
}

public interface IMarshalByRefTypeInterface
{
    void SomeMethod(MarshalByRefParameter parameter);
}

public class MarshalByRefType : MarshalByRefObject, IMarshalByRefTypeInterface
{
    //  Call this method via a proxy. 

    #region Public Methods and Operators

    public void SomeMethod(MarshalByRefParameter parameter)
    {
        // Get this AppDomain's settings and display some of them.
        AppDomainSetup ads = AppDomain.CurrentDomain.SetupInformation;
        Console.WriteLine("AppName={0}, AppBase={1}, ConfigFile={2}", ads.ApplicationName, ads.ApplicationBase, ads.ConfigurationFile);

        // Display the name of the calling AppDomain and the name  
        // of the second domain. 
        // NOTE: The application's thread has transitioned between  
        // AppDomains.
        Console.WriteLine("Calling from '{0}' to '{1}'.", parameter.ModuleName, Thread.GetDomain().FriendlyName);
    }

    #endregion
}

[Serializable]
public class MarshalByRefParameter : MarshalByRefObject
{
    public string ModuleName { get; set; }
}

But then, I am just grabbing entry assembly, while you have one which gets dynamically compiled. What error and where do you actually get?

Upvotes: 0

Related Questions