Thursday, October 02, 2008

One of the things we tend to take for granted and assume to be true is that identical code will produce identical behaviors across different platforms.  It only seems sensible that if we write some C# code that compiles and runs for the full framework as well as the compact framework that the resulting behavior, provided the code isn't obviously platform dependent, should behave the same.  Right?

Apparently not.

While porting some networking code today from the device to the desktop, I was having failures in code that I was certain worked.  It turns out that sockets don't behave the same.  Since a Socket class is an abstraction of something that is pretty damned standard, the fact that we have this disparity surprises and alarms me.  It smells an awful lot like a bug.

Want to try it yourself?  I put together some pretty basic repro code:

class Program

{

private Socket m_serverSocket;

private ManualResetEvent m_requestDoneEvent = new ManualResetEvent(false);

public static void Main()

{

Program p = new Program();

p.Run();

}

public void Run()

{

IPEndPoint localEndpoint = new IPEndPoint(IPAddress.Any, 90);

m_serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

m_serverSocket.Bind(localEndpoint);

Thread serverThread = new Thread(ServerThreadProc);

serverThread.IsBackground = true;

serverThread.Priority = ThreadPriority.AboveNormal;

serverThread.Start();

Thread.Sleep(100);

Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

EndPoint ep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 90);

clientSocket.Connect(ep);

m_requestDoneEvent.WaitOne(10000, false);

}

private void ServerThreadProc()

{

m_serverSocket.Listen(10);

m_requestDoneEvent.Reset();

m_serverSocket.BeginAccept(AcceptRequest, m_serverSocket);

// wait for the async accept to complete

m_requestDoneEvent.WaitOne(10000, false);

}

private void AcceptRequest(IAsyncResult result)

{

Debug.WriteLine("\r\n\n -------------------------------------------");

Debug.WriteLine("| The current Platform is " + Environment.OSVersion.Platform.ToString());

Debug.WriteLine("| The server socket reports Connected == " + m_serverSocket.Connected.ToString());

Debug.WriteLine(" -------------------------------------------\r\n\n");

m_requestDoneEvent.Set();

}

}


And the outputs:

On the desktop:

 -------------------------------------------
|  The current Platform is Win32NT
|  The server socket reports Connected == False
 -------------------------------------------

On the device:

 -------------------------------------------
|  The current Platform is WinCE
|  The server socket reports Connected == True
 -------------------------------------------

WTF?!


	
10/2/2008 6:40:17 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [6]  | 
Our push toward Continuous Integration, Test Driven Development and overall code quality here at OpenNETCF is a continuous learning experience.  One of the issues for unit/integration tests we've run into is that many SDF classes require an application message pump in order to "operate" properly.  A classic case it the FileSystemWatcher (FSW).  The FSW handles all of its eventing using the underlying OS architecture, which in turn uses windows messages (wouldn't have been my choice, but hey, I didn't architect it).

Well for windows messages to get dispatched, you need a message pump.  In a normal application, this is set up for you when you call Application.Run but in a unit test there isn't such a call, and MSTEST certainly doesn't spin up a pump for you.  We also don't really want to create a Form just for our test (Application.Run requires a Form instance be passed in) and Application.Run blocks until the Form is closed so how wopuld we run our test and the pump anyway?

The solution I came up with is to use the OpenNETCF.Windows.Forms.Application2.Run method, which doesn't require a Form, and to spawn it in a background thread at the start of the test, then at the end of the test call Application2.Exit, something like this:


      ThreadPool.QueueUserWorkItem(delegate(object o)
        {
          OpenNETCF.Windows.Forms.Application2.Run();
        });

      // run test here

      OpenNETCF.Windows.Forms.Application2.Exit();

Of course just having a Pump doesn't necessarily mean that your messages will be dispatched when you want, and keep in mind that Windows messages are very low priority, so they might not get posted or dispatched immediately, so that adds more complexty to the test.  After you perform an action that causes a message (like creating a file) you want to call Application2.DoEvents to force messages to be dispatched, and you probably want to do it a few times after having your thread yield to make sure that the scheduler actually gets around to posting the things to the queue in the first place.

In the end it all works, it's just not near as clean as one would hope for in a unit test.  Here's an example of an FSW test from our production system:


AutoResetEvent m_testEvent = new AutoResetEvent(false);

[TestMethod()]
public void FileSystemWatcherCreatedWatchDirEventTestPositive()
{
  ThreadPool.QueueUserWorkItem(delegate(object o)
    {
      OpenNETCF.Windows.Forms.Application2.Run();
    });

  FileSystemWatcher fsw = null;
 
  string filename = "\\Temp\\Test.txt";
  if (File.Exists(filename)) File.Delete(filename);

  try
  {
    fsw = new FileSystemWatcher("\\Temp", "*.*");
    fsw.Created += new FileSystemEventHandler(fsw_Created);
    fsw.EnableRaisingEvents = true;

    m_testEvent.Reset();

    File.CreateText(filename).Close();
    // give time for the message to get queued, and flush the queue a few times
    for (int i = 0; i < 10; i++)
    {
      Thread.Sleep(20);
      OpenNETCF.Windows.Forms.Application2.DoEvents();
    }

    // check for an event
    Assert.IsTrue(m_testEvent.WaitOne(1000, false), "Create Event did not fire");
  }
  finally
  {
    // clean up
    OpenNETCF.Windows.Forms.Application2.Exit();
    File.Delete("\\Test.txt");
    if( fsw != null) fsw.Dispose();
  }
}

void fsw_Created(object sender, FileSystemEventArgs e)
{
  m_testEvent.Set();
}
10/2/2008 12:04:26 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 
 Friday, September 26, 2008
So yesterday I decided that I'd start a longer-term project of creating a decent WiFi viewer application for PPC/WinMo.  Basically something to use as a good sample for the Smart Device Framework, as well as to help test out several of the features (those classes are nearly impossible to automate testing for).  I've decided to use it as a showcase for everything I can think of as long as it isn't superfluous - so using OpenNETCF UI controls, etc.

One of the first snags I hit is with the test device I just happen to be using - an Axim x51.  I ran the app and it finds the wireless NIC, no problem.  I then added some code to handle when the adapter comes or goes (like when it's powered on and off).  Again, fine.  I then set out to do the list of nearby APs, so I set up an open AP and connected to it with the built-in utility.

Now all of a sudden my app doesn't see the wireless NIC.  WTF?  Gotta be a bug in the SDF, right?  So I trace it down and it turns out that there doesn't seem to be a bug.  a call to IPHLPAPI.DLL's GetInterfaceInfo() method simply says there's only one adapter - the USB connection.  Strange.  Maybe it's some state problem.  I power the radio off and back on (again using the built-in tool) while running my app.  The adapter shows up again (hooray) but only briefly, then it disappears again.  WTF?  So after several of these cycles, I find that as soon as the adapter connects to an AP, it no longer shows up as an adapter using the IPHLPAPI function. 

It appears that somehow they've decided that either when they connect or when they bind that the interface needs to be hidden - maybe to prevent other apps from interfering with theirs.  Yet another genius decision by an OEM.  Why would anyone ever want to do something that's not built in?  Off to eBay to find another device on which this will (hopefully) work I guess.

9/26/2008 12:54:03 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 
 Friday, September 19, 2008
Smart Device Framework 2.3 is really close to release - we've frozen the code base and have been working on the installer (a large chunk of work since we're getting it fully automated).  The hope is that with all of this CI infrastructure in place, we'll be able to turn SDF releases far more frequently (right now it's taken over a month to go from the decision to release to get where we are - and the release still hasn't shipped). 

In order to turn out releases more often, we need ideas for features to add - after all we need a reason to release.  This is where you come in.  Navigate over to the SDF Product Page and you'll see a new "Feedback" widget over on the side (courtesy of UserVoice).Click on it and create or vote for feature ideas.

We've also added the widget to the Padarn page, so if you've a Padarn user, let us know what you'd like to see there too.


Smart Device Framework Suggestions

Padarn Suggestions

9/19/2008 12:54:42 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 
 Thursday, September 18, 2008
As part of our push to implement continuous integration, we need to have TFS generate CAB files for us.  Unfortunately the only tool we have for this is the archaic piece of crap CABWIZ and it's not a very friendly tool for automating.  What we did was create a custom build task that calls cabwiz for you and will output the error file back to the build log on failure. Now all we have to do is add this to our build script:

    <CabWiz
      InfPath="$(CABFolder)\SDF.INF"
        ErrorLog="Error.log"
        OutputPath="$(CABFolder)" />


The custom build task looks like this:

namespace OpenNETCF.Build.Utilities
{
  using System;
  using System.IO;
  using System.Text;
  using Microsoft.Build.Framework;
  using Microsoft.Build.Utilities;
  using Microsoft.Win32;
  using System.Diagnostics;

  /// <summary>
  /// Uses CabWiz to create a Smart Device CAB installer.
  /// </summary>
  /// <example>
  /// <code><![CDATA[
  /// <CabWiz
  ///   InfPath="MyApp.inf"
  ///   ErrorLog="Error.log"
  ///   WorkingFolder="..\Build"
  ///   Cpu="x86"
  ///   Compress="true"
  ///   NoUninstall="false"
  ///   OutputPath="..\Release"
  ///   Platform="wm"
  ///   PreXml="Pre.xml"
  ///   PostXml="Post.xml"
  /// />
  /// ]]></code>
  /// </example>
  public sealed class CabWiz : ToolTask
  {
    [Required]
    public string InfPath { get; set; }

    public bool Compress { get; set; }
    public string Cpu { get; set; }
    public string ErrorLog { get; set; }
    public bool NoUninstall { get; set; }
    public string OutputPath { get; set; }
    public string Platform { get; set; }
    public string PostXml { get; set; }
    public string PreXml { get; set; }

    private static string CabWizSubPath
    {
      get { return @"SmartDevices\SDK\SDKTools"; }
    }

    protected override string ToolName
    {
      get { return "cabwiz.exe"; }
    }

    public override bool Execute()
    {
      bool ret = base.Execute();

      if (!ret)
      {
        string path = Path.GetDirectoryName(InfPath);
        path = Path.Combine(path, ErrorLog);

        if (File.Exists(path))
        {
          Log.LogMessageFromText(string.Format("!!! BEGIN CABWIZ ERROR FILE DUMP !!!", path), MessageImportance.High);
          Log.LogMessagesFromFile(path, MessageImportance.High);
          Log.LogMessageFromText(string.Format("!!! END CABWIZ ERROR FILE DUMP !!!", path), MessageImportance.High);
        }
        else
        {
          Log.LogMessageFromText(string.Format("CABWIZ: Couldn't find error log at '{0}'", path), MessageImportance.High);
        }
      }
      return ret;
    }

    protected override string GenerateFullPathToTool()
    {
      if (String.IsNullOrEmpty(ToolPath))
      {
        string path = string.Empty;
        using (RegistryKey regKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\VisualStudio\9.0\Setup\VS", false))
        {
          if (regKey != null)
          {
            path = (string)regKey.GetValue("ProductDir");
          }
        }
        ToolPath = Path.Combine(path, CabWizSubPath);
      }

      return Path.Combine(ToolPath, ToolName);
    }

    protected override string GenerateCommandLineCommands()
    {
      GenerateFullPathToTool();
      StringBuilder cabWizArgs = new StringBuilder();
      cabWizArgs.AppendFormat("\"{0}\" ", InfPath);

      if (!string.IsNullOrEmpty(OutputPath))
      {
        cabWizArgs.AppendFormat("/dest \"{0}\" ", OutputPath);
      }

      if (!string.IsNullOrEmpty(ErrorLog))
      {
        cabWizArgs.AppendFormat("/err \"{0}\" ", ErrorLog);
      }

      if (!string.IsNullOrEmpty(PreXml))
      {
        cabWizArgs.AppendFormat("/prexml \"{0}\" ", PreXml);
      }

      if (!string.IsNullOrEmpty(PostXml))
      {
        cabWizArgs.AppendFormat("/postxml \"{0}\" ", PostXml);
      }

      if (!string.IsNullOrEmpty(Cpu))
      {
        cabWizArgs.AppendFormat("/cpu {0} ", Cpu);
      }

      if (!string.IsNullOrEmpty(Platform))
      {
        cabWizArgs.AppendFormat("/platform {0} ", Platform);
      }

      if (Compress)
      {
        cabWizArgs.Append("/compress ");
      }

      if (NoUninstall)
      {
        cabWizArgs.Append("/nouninstall");
      }

      Log.LogMessage(MessageImportance.High, cabWizArgs.ToString());
      return cabWizArgs.ToString();
    }
  }
}


9/18/2008 10:22:34 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 
 Wednesday, September 10, 2008
Every now and then (much less now that .NET languages have been around are are pretty mature) I see people who are moving into .NET programming and they ask "which is better, C# or VB.NET."  Generally speaking there is no "better" but there are some things available in one language but not the other.  Typically I've always thought that C# had just a little more - it has the ability to support unsafe code, which I like and use occasionally.  I could never come up with something VB had that C# didn't.  Until today.

A friend asked me how he could use the Contains() method of a string inside a case statement, and it reminded me of an old VB 6 construct that I'd used, so I tried it to be sure VB.NET still supported it, and sure enough, it works fine:

        Dim myvar As String = "My Test String"

        Select Case True
            Case myvar.Contains("not there")
                Debug.WriteLine("Contains 'not there'")
            Case myvar.Contains("Test")
                Debug.WriteLine("Contains 'Test'")
            Case myvar.Contains("Other")
                Debug.WriteLine("Contains 'Other'")
        End Select

However the construct won't work in C#.  It won't even compile because C# expects case labels to be constants.

        string myvar = "My Test String";

        switch (true)
        {
          case myvar.Contains("not there"):
            Debug.WriteLine("Contains 'not there'");
            break;
          case myvar.Contains("Test"):
            Debug.WriteLine("Contains 'Test'");
            break;
          case myvar.Contains("Other"):
            Debug.WriteLine("Contains 'Other'");
            break;
        }

So there you go VB lovers - score on point for your side.  I'm not saying that I'm going to start writing all my code in VB now (not that I have anything against VB, I mean I did co-author a book on it, I'm just really rusty) but here's some fodder for what some consider a religious debate.

9/10/2008 11:55:08 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  |