CreateInstanceAndUnwrap in Another Domain?
Asked Answered
S

2

9

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", " ");
    }
Snodgrass answered 15/7, 2014 at 14:13 Comment(4)
Could be as simple as the CLR not finding the assembly. Don't skip documenting the exact exception you get.Enjambment
I don't seem to be getting any exceptions -- aside from my ideal case above where I just cast it to the desired type. I've also attached handlers to log event callback on AppDomain.Current, as well as the new domain. There aren't any Exceptions or callbacks unless I try to cast it quickly.Snodgrass
This is a very common problem with plugins. You have two definitions for ICrossDomainApplication. They came from different assemblies. Either because you copied an assembly or because you included the source code twice. The CLR treats them as separate and incompatible types. You must make sure that the exact same assembly with the one-and-only ICrossDomainApplication gets loaded in both appdomains.Enjambment
I tried resolving it from the new AppDomain, by using CreateInstanceFromAndUnwrap -- still no luck and still has issues. Do you have any resources/links/articles that might help make this more obvious or gives us an example of how to "fix" it?Snodgrass
S
16

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!

Snodgrass answered 16/7, 2014 at 19:19 Comment(2)
What is the GetPrivateBin method and what does it do?Originative
That finally did the trick for me! Thank you! So many sleepless nightsPickup
V
0

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?

Voncile answered 15/7, 2014 at 14:28 Comment(1)
Please see above (for edits). I don't really get any errors -- I just cannot convert it to the type I need to -- in your example this would be IMarshalByRefTypeInterface. When the "runtime" was just of type object (no casting), I logged runtime.GetType() and it always wants to return MarshalByRefObject...Snodgrass

© 2022 - 2024 — McMap. All rights reserved.