How to use Debug.Write and Trace.Write across app domain boundaries

If you’ve ever written an application that uses multiple application domains you may have noticed that calls to Debug.Write or Trace.Write in new application domains get lost, especially if you’ve add the TraceListener in code (e.g. “Debug.Listeners.Add(new ConsoleTraceListener());”).

Fortunately this is reasonably easy to solve with just two classes, one to handle the Debug.Write events in the child domain and another to handle the communication of messages between domains. I’m going to use a TraceListener implementation that I originally wrote to help with the writing of Debug messages to a TextBox in a WPF application, I call in the DelegateTraceListener, and it looks like this:

    private class DelegateTraceListener : TraceListener
    {
        private Action<string> _write;

        public DelegateTraceListener(Action<string> write)
        {
            _write = write;
        }

        public override void Write(string message)
        {
            _write(message);
        }

        public override void WriteLine(string message)
        {
            Write(message + Environment.NewLine);
        }
    }

Which can be easily used in my aforementioned WPF scenario like so:

    Debug.Listeners.Add(
        new DelegateTraceListener(
            message => textBox1.AppendText(message)));

The other class required to allow messages to cross between domains CrossDomainTraceHelper, which does a couple of things. Firstly it creates two instances of itself, one in the domain you specify and one in the current domain, secondly it uses the DelegateTraceListener to receive messages from the child domain, thirdly it pipes the messages from the child domain instance to the local domain instance, lastly it writes the messages to the Debug object.

    public class CrossDomainTraceHelper : MarshalByRefObject
    {
        private CrossDomainTraceHelper _parentDomain;

        private CrossDomainTraceHelper()
        {
        }

        public static void StartListening(AppDomain domain)
        {
            var listenerType = typeof(CrossDomainTraceHelper);

            // Create a remote instance
            var remoteHelper = 
                (CrossDomainTraceHelper)domain.CreateInstanceAndUnwrap(
                    listenerType.Assembly.FullName, 
                    listenerType.FullName);

            // Create a local instance
            var localHelper = new CrossDomainTraceHelper();

            // Register the local helper in the remote domain
            remoteHelper.Register(localHelper);
        }

        private void Register(CrossDomainTraceHelper parentDomain)
        {
            // Store the parent domain to pass messages to later
            _parentDomain = parentDomain;

            // Create and register the delegate trace listener
            var listener = new DelegateTraceListener(Write);

            Debug.Listeners.Add(listener);
        }

        private void Write(string message)
        {
            // Send the message to the parent domain
            _parentDomain.RemoteWrite(message);
        }

        private void RemoteWrite(string message)
        {
            Debug.Write(message);
        }
    }

Now you can start listening for Debug messages in the remote domain with a single line:

    CrossDomainTraceHelper.StartListening(domain);

Phil

6 Responses to “ “How to use Debug.Write and Trace.Write across app domain boundaries”

  1. Lothar Grieb says:

    Compact clear code. well done.

  2. Wouter says:

    Great work,
    This article was my inspiration to create a DelegateLoggerFactoryAdapter for Common.Logging.

    • Phil says:

      Hey, I’m glad to hear it 🙂

      I’d be interested to see what you’ve done, if you’d like to share it 🙂

  3. Xeyone says:

    The constructor should be public instead of private. This clever class works great!

    05.private CrossDomainTraceHelper()
    06.{
    07.}

  4. Martin Müller says:

    Great article,
    just a little addendum:
    You should override InitializeLifetimeService() to return null, otherwise your CrossDomainTraceHelper Proxy might die after 2 minutes of inactivity.

    Regards,
    Martin

  5. Superb, ωhat a weblog it is! This blog presents useful information to uѕ, keep it up.