.netserializationremotingappdomainmarshalbyrefobject

Mixing MarshalByRefObject and Serializable


Various sources explain that

When an object derives form MarshalByRefObject, an object reference will be passed from one application domain to another rather than the object itself. When an object is marked with [Serializable], the object will be automatically serialized, transported from one application domain to another and then deserialized to produce an exact copy of the object in the second application domain. Note then that while MarshalByRefObject passes a reference, [Serializable] causes the object to be copied. [source]

I'm designing my first app that uses AppDomains and I'm wondering what happens when you place references to MarshalByRefObjects inside serializable objects that do not implement MarshalByRefObject, because so far I can't find any documentation on the subject.

For example, what happens if I try to return a List<MBR> where MBR : MarshalByRefObject across an AppDomain boundary? Do I get a copy of the List<MBR> where each MBR is a TransparentProxy to the original object? And is there any documentation about the technical details of mixing the two mechanisms?


Solution

  • I just did a quick test with List<MBR> and it seems to work as I had hoped:

    public class MBR : MarshalByRefObject
    {
        List<MBR> _list;
        public MBR() { _list = new List<MBR> { this }; }
        public IList<MBR> Test() { return _list; }
        public int X { get; set; }
    }
    
    // Later...
    var mbr = AppDomainStarter.Start<MBR>(@"C:\Program Files", "test", null, true);
    var list = mbr.Test();
    list[0].X = 42;
    list.Clear();
    Debug.WriteLine(string.Format("X={0}, Count={1}", mbr.X, mbr.Test().Count));
    

    The output is X=42, Count=1, and the debugger shows that the List<MBR> contains a __TransparentProxy. So clearly, the MarshalByRefObject is successfully marshaled by reference inside another object that was marshaled by value.

    I would still like to see documentation or technical details if anyone can find some.

    For anyone who is curious, I wrote this handy-dandy sandbox AppDomainStarter:

    /// <summary><see cref="AppDomainStarter.Start"/> starts an AppDomain.</summary>
    public static class AppDomainStarter
    {
        /// <summary>Creates a type in a new sandbox-friendly AppDomain.</summary>
        /// <typeparam name="T">A trusted type derived MarshalByRefObject to create 
        /// in the new AppDomain. The constructor of this type must catch any 
        /// untrusted exceptions so that no untrusted exception can escape the new 
        /// AppDomain.</typeparam>
        /// <param name="baseFolder">Value to use for AppDomainSetup.ApplicationBase.
        /// The AppDomain will be able to use any assemblies in this folder.</param>
        /// <param name="appDomainName">A friendly name for the AppDomain. MSDN
        /// does not state whether or not the name must be unique.</param>
        /// <param name="constructorArgs">Arguments to send to the constructor of T,
        /// or null to call the default constructor. Do not send arguments of 
        /// untrusted types this way.</param>
        /// <param name="partialTrust">Whether the new AppDomain should run in 
        /// partial-trust mode.</param>
        /// <returns>A remote proxy to an instance of type T. You can call methods 
        /// of T and the calls will be marshalled across the AppDomain boundary.</returns>
        public static T Start<T>(string baseFolder, string appDomainName, 
            object[] constructorArgs, bool partialTrust)
            where T : MarshalByRefObject
        {
            // With help from http://msdn.microsoft.com/en-us/magazine/cc163701.aspx
            AppDomainSetup setup = new AppDomainSetup();
            setup.ApplicationBase = baseFolder;
    
            AppDomain newDomain;
            if (partialTrust) {
                var permSet = new PermissionSet(PermissionState.None);
                permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
                permSet.AddPermission(new UIPermission(PermissionState.Unrestricted));
                newDomain = AppDomain.CreateDomain(appDomainName, null, setup, permSet);
            } else {
                newDomain = AppDomain.CreateDomain(appDomainName, null, setup);
            }
            return (T)Activator.CreateInstanceFrom(newDomain, 
                typeof(T).Assembly.ManifestModule.FullyQualifiedName, 
                typeof(T).FullName, false,
                0, null, constructorArgs, null, null).Unwrap();
        }
    }