Of System.Diagnostics.Debug, ActiveSync & Sockets

Today started simple enough. I needed more debug info from my Smartphone app than just using the debugger could give me in a reasonable fashion. My usual solution is log4net for getting a debug spew, but trying to stay as simple as possible on the .NETCF 1.0, I decided to hit up System.Diagnostics instead. More to the point, System.Diagnostics.Debug.WriteLine() and its brethren.

Except, nothing showed up in the output window. Checking whether there was a config setting, then making sure that .NETCF 1.0 supported it, I was stumped. Finally I find out that the default TraceListener is only supported by VS.NET 2k5 if you use .NETCF 2.0. Now I don't know if you've looked at deploying 2.0 apps in a world where even the most modern phones still ship with 1.0, but I certainly couldn't find a clean and simple solution. Especially not one that didn't require being tethered and downloading 40MB from MS, just so they can run a couple hundred kilobytes of App. And it's a pity, because I run into something every day to which the answer is "Use .NETCF 2.0". I want to, really, I do, but deployment convenience trumps developer convenience.

Next up was writing a custom TraceListener and wanting to be as unobtrusive to my Apps operation, I built it on UDP, i.e. a UdpTraceListener. Console based prototype code was up in about 20 minutes for the UdpTraceListener and the console app for listening to the spew. Then followed a couple of hours of testing and stepping through debug and not being able to get anything to be sent from my Emulator cradled via ActiveSync.

I even thought it might be because the phone Emulator couldn't talk to the hosting computer and moved the console app to Mono on my server. That part worked flawlessly--copy the binary call mono UdpTraceConsole.exe and it was up and running. But it also received no data.

Then finally I find a chat log with the .NETCF team that reveals that UDP is not supported over ActiveSync. Nice.

Another 20 minutes of coding and i had a TcpTraceListener up in running in my console test harness. Woot. Time to put test on the phone, so i can finally get back to real coding....

Right. That would have been nice. The same game as before. Tried all sorts of permutations and received no data. Sometimes the debugger would throw a Socket Exception, usually it wouldn't. It made no sense. I knew that TCP worked because I use HttpWebRequest extensively. So I tried putting my console listener on port 80. Nothing. Finally, I decided to open up a port on my firewall and forward it back to my dev machine so that I could try to contact my console listener via a network address from the outside world. And that finally worked. Only theory I have right now is that the DHCP setup via ActiveSync is screwy and it just can't see any of my local net. I'll investigate that later, for now I'm just happy to have a working debug spew.

And here is the exceedingly simple TcpTraceListener. Note: I use '\0' to denote the end of any message so my debug messages can include linefeeds.

using System;
using System.Diagnostics;
using System.Net.Sockets;
using System.Net;
using System.Text;

namespace Droog.Diagnostics
{
  public class TcpTraceListener : TraceListener
  {
    IPEndPoint endPoint;
    Socket console;

    public TcpTraceListener(IPEndPoint consoleEndPoint)
    {
      this.endPoint = consoleEndPoint;
    }

    private Socket ConsoleSocket
    {
      get
      {
        if (console == null || !console.Connected)
        {
          console = new Socket(
            AddressFamily.InterNetwork,
            SocketType.Stream,
            ProtocolType.Tcp);
          console.Connect(endPoint);
        }
        return console;
      }
    }

    public override void Write(string message)
    {
      try
      {
        ConsoleSocket.Send(Encoding.ASCII.GetBytes(message));
      }
      catch
      {
        // this is for a debug spew. I'd rather swallow the exceptions
        // than have my debug code mess with my App.
      }
    }

    public override void WriteLine(string message)
    {
      Write(message + "\\r\\n\\0");
    }

    public override void Close()
    {
      if (console != null)
      {
        if (console.Connected)
        {
          console.Close();
        }
        console = null;
      }
      base.Close();
    }
  }
}