Wednesday, July 01, 2009

It's now easier than ever to evaluate Padarn, our ASP.NET Web Server for Windows CE.  We now have available an Evaluation Kit that comes with:

  • A 600MHz fanless Windows CE 6.0 device
  • 13 hands-on labs with full source code
  • A full sample Padarn web site with source code
  • Fully-functional Padarn evaluation binaries

The kit is being offered at an introductory price of $300.00, and if that isn't enough to convince you, we'll even credit that against any purchase of Padarn you make.

For more information, see the Padarn web site.

7/1/2009 4:06:15 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Monday, June 29, 2009

The latest version of our Padarn Mobile Server supports VirtualPathProviders.  This means that now it is easy to create REST Web Services that run on your Windows CE or Windows Mobile device.  Download the latest hands-on lab "HOL P303 - Using VirtualPathProviders in Padarn to provide REST Web Services" from the web site here.

6/29/2009 1:31:39 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Friday, June 26, 2009

As you may have guessed from some of my recent blog entries, we're making a push to get a lot of our shared source code out to the Codeplex servers.  Our serial library is the latest one to make the move.  It's now available at http://serial.codeplex.com.  As with all of these libraries, if you have the desire to contribute, fix, extend, or whatever just let us know and we'll add you as a developer.

6/26/2009 10:24:33 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Thursday, June 25, 2009

In an effort to clean up our servers, to make code easier to find, and to hopefully make it easier for the community at-large to contribute, we've moved our popular Desktop Communication library over to the Codeplex servers.

Visit the project at rapi.codeplex.com.

6/25/2009 7:38:36 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 

Over the past few months we've been working hard to consolidate the products we at OpenNETCF offer.  What this means is that products that didn't sell well or that had high support loads compared to sales got dropped.  Our Telephony library is one of those that we decided to discontinue.  But that's good news for all!  Instead of just letting it wither and die in the depths of our own source control server, we figure it might as well be thrown out to the community to see if it will flourish.

SO with that, we give you the OpenNETCF.Telephony Library, hosted over at Codeplex as tapi.codeplex.com.  Enjoy.

6/25/2009 7:06:39 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 

In my last blog entry, I showed a workaround for getting mouse events for the CF ListView.  Since I can't leave well enough alone, I decided I'd actually try implementing the HitTest method (now that we have a valid x,y click coordinate).  Here's the result:

namespace OpenNETCF.Windows.Forms
{
  [Flags]
  public enum ListViewHitTestLocations
  {
    None = 0x01,
    Image = 0x02,
    Label = 0x04,
    StateImage = 0x08,

    AboveClientArea = 0x08,
    BelowClientArea = 0x10,
    RightOfClientArea = 0x20,
    LeftOfClientArea = 0x40,
  }

  public class ListViewHitTestInfo
  {
    public ListViewItem Item { get; set; }
    public ListViewHitTestLocations Location { get; set; }
    public ListViewItem.ListViewSubItem SubItem { get; set; }
  }
}

namespace OpenNETCF.Core
{
  using OpenNETCF.Windows.Forms;
  using System.Runtime.InteropServices;
  using System.Diagnostics;

  public static partial class Extensions
  {
    private const int LVM_FIRST = 0x1000;
    private const int LVM_GETCOUNTPERPAGE = LVM_FIRST + 40;
    private const int LVM_SUBITEMHITTEST = LVM_FIRST + 57;

    private struct LVHITTESTINFO
    {
      public int x;
      public int y;
      public ListViewHitTestLocations flags;
      public int iItem;
      public int iSubItem;
    }

    public static int GetVisibleRowCount(this ListView lv)
    {
      return Win32Window.SendMessage(lv.Handle, LVM_GETCOUNTPERPAGE, 0, 0).ToInt32();
    }

    public static ListViewHitTestInfo HitTest(this ListView lv, int x, int y)
    {
      LVHITTESTINFO info = new LVHITTESTINFO();
      info.x = x;
      info.y = y;
      GCHandle pInfo = GCHandle.Alloc(info, GCHandleType.Pinned);
      try
      {
        Win32Window.SendMessage(lv.Handle, LVM_SUBITEMHITTEST, 0, pInfo.AddrOfPinnedObject());
        LVHITTESTINFO result = (LVHITTESTINFO)pInfo.Target;
        ListViewHitTestInfo lvhti = new ListViewHitTestInfo();

        lvhti.Location = result.flags;
        switch (lvhti.Location)
        {
          case ListViewHitTestLocations.Image:
          case ListViewHitTestLocations.Label:
          case ListViewHitTestLocations.StateImage:
            lvhti.Item = lv.Items[result.iItem];
            lvhti.SubItem = lvhti.Item.SubItems[result.iSubItem];
            break;
        }
        return lvhti;
      }
      finally
      {
        pInfo.Free();
      }
    }

    public static ListViewHitTestInfo HitTest(this ListView lv, Point point)
    {
      return lv.HitTest(point.X, point.Y);
    }
  }
}

And usage looks like this:

public partial class Foo : Form
{
  public Foo()
  {
    InitializeComponent();
    listView.Items.Add(new ListViewItem(new string[] { "Item A", "Sub A1", "Sub A2", "Sub A3" }));
    listView.Items.Add(new ListViewItem(new string[] { "Item B", "Sub B1", "Sub B2", "Sub B3" }));
    listView.Items.Add(new ListViewItem(new string[] { "Item C", "Sub C1", "Sub C2", "Sub C3" }));
    listView.Items.Add(new ListViewItem(new string[] { "Item D", "Sub D1", "Sub D2", "Sub D3" }));
    listView.Items.Add(new ListViewItem(new string[] { "Item E", "Sub E1", "Sub E2", "Sub E3" }));

    ClickFilter filter = new ClickFilter(listView);
    Application2.AddMessageFilter(filter);
    filter.MouseDown += new MouseEventHandler(filter_MouseDown);

  }

  void filter_MouseDown(object sender, MouseEventArgs e)
  {
    ListView lv = sender as ListView;
    ListViewHitTestInfo hti = lv.HitTest(e.X, e.Y);

    if (hti.Item != null)
    {
      Debug.WriteLine(string.Format("Item: {0}, SubItem: {1}", hti.Item.Text, hti.SubItem.Text));
    }
  }
}

All of this will end up in SDF v. Next, but why do we have to implement fundamental things like this that should already be there?  It was excusable to be missing in v 1.0.  Maybe even in v 2.0, but in 3.5?  Really?

 

6/25/2009 12:38:57 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 

We're going to be adding a full set of custom-drawn and owner-drawn controls in the next version of the SDF.  These are not "new" controls, but wrappers around the existing native control, but we're adding the custom and owner draw hooks that the OS already provides (and that, honestly, the CF team should have given us by version 3.5). 

In testing them out, I wanted to dogfood the custom-drawn ListView in an application, but I needed to know when a user clicked on an item in the ListView.  Simple.  Or so I thought.  It turns out that the style bits that get set to make a ListView really usable means that the ListView's *parent* gets it's click actions, and to turn those into Click events in the managed control is a *lot* of work, and would likely have some performance penalty.

So that got me to wondering how the existing CF ListView deals with it.  Well after a very quick test it seems that the CF team made the same decision - they didn't support the Click event either

Well my application requires that I know the difference between when the selected index changes (that's about the only useful event you do get) due a click and and when it changes dues to a keyboard action like an up or down arrow. Being me, I refuse to change the way I want my application will work just becasue of some stupid limitation of a framework.  The OS knows when I click on that damned thing - after all, it changes the selected item - so it *will* tell my application when it happens.

The key here is pretty simple.  We know that the click event does come in, and it comes in as a Windows message.  Our application message pump routes it to the parent of the ListView - so we have two point at which we can try to get it.  Getting it from the parent would entail subclassing the parent Form, and that just does seem like fun, nor is it really extensible if I ever have another app where I want to get ListView item clicks.

So that leaves looking at the application message pump.  Unfortunately the CF team has again not seen fit in any version to provide us the ability to add an IMessageFilter, but this was something we overcame long ago in the SDF.  If you use the Application2.Run method, you can then add IMessageFilters.  So what I did here is created an IMessageFilter that looks for messages whose hWnd matches the ListView I'm interested in, and then looks for the WM_LBUTTONDOWN message:

internal class ClickFilter : IMessageFilter
{
  private Control m_control;

  public event EventHandler Click;
  public event MouseEventHandler MouseDown;

  public ClickFilter(Control control)
  {
    m_control = control;
  }

  private int LoWord(IntPtr param)
  {
    return (ushort)(param.ToInt32() & ushort.MaxValue);
  }

  private int HiWord(IntPtr param)
  {
    return (ushort)(param.ToInt32() >> 16);
  }

  public bool PreFilterMessage(ref Microsoft.WindowsCE.Forms.Message m)
  {
    if (m_control.IsDisposed) return false;

    if ((m.HWnd == m_control.Handle) && (m.Msg == (int)Microsoft.Controls.WM.WM_LMOUSEDOWN))
    {
      MouseEventArgs args = new MouseEventArgs(MouseButtons.Left, 1, LoWord(m.LParam), HiWord(m.LParam), 0);

      ThreadPool.QueueUserWorkItem(ButtonInvoker, args);
    }

    return false;
  }

  void ButtonInvoker(object o)
  {
    // let the system select the clicked listview item
    Thread.Sleep(10);
    Application2.DoEvents();

    if (m_control.IsDisposed) return;

    if (m_control.InvokeRequired)
    {
      m_control.Invoke(new WaitCallback(ButtonInvoker), new object[] { o });
      return;
    }

    // not truly a click, since it's not a down/up pair. Fix this later if we feel like it
    if(Click != null) Click(m_control, null);
    if (MouseDown != null) MouseDown(m_control, o as MouseEventArgs);
  }
}

Take note of the slight kludge in there though with the ThreadPool.  The reason for this is that if you just raise the event immediately on getting the mouse down event, the ListViewItem selection won't have happened yet, so if your handler looks at the SelectedItem, it would get the item that was selected before the click, not the one that the was clicked. 

Sure, maybe that's what your app wants, but in my case I wanted to know which item is actually clicked.  The ListView doesn't support a HitTest method (again, why don't we have this yet?), so knowing the x,y coordinates of the click is still a long way from giving us the ListViewItem.  This was a kludge to easily get me the info I wanted.

So using this filter in my application was as easy as adding this to the Form that contains the ListView:

InitializeComponent();

ClickFilter filter = new ClickFilter(listView);
filter.Click += new EventHandler(filter_Click);
Application2.AddMessageFilter(filter);

No, it shouldn't be this hard.  I wish the CF team would spend more time fixing fundamental stuff like this instead of tilting at the Silverlight windmill, but it is what it is, and as developers we still have to ship solutions.

6/25/2009 11:27:26 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [4]  | 
 Wednesday, June 24, 2009

It took a long time to get here, but we've finally release what I'm calling the version 1.0 (pervious version were 0.9.x) release of the OpenNETCF.IoC Framework.  In case you've not been tracking this project, it is a public-domain-licensed (you can't get any more free and unencumbered than that) framework that provides both inversion of control and dependency injection for .NET Compact Framework applications (it can be used on the desktop as well).  It's roughly modelled after Microsoft's SCSF and CAB frameworks, but it's scaled down and optimized for running on mobile and embedded devices, plus I "fixed" stuff that I think the SCSF got wrong (like having a static, globally available RootWorkItem and the ability to insert IMessageFilters into the application's message pump).

This framework is in use in a couple of commercial applications already, so it's been pretty heavily tested and vetted.  I still want to add a few more features as well and go back through it looking for performance optimizations, but it certainly has enough features to be used in applications today.

This release also ships with a full-blown, real-world sample application, not just the typical "Northwind" type of application.  The sample is called WiFiSurvey and it can be used to survey WiFi AP coverage of a site and to monitor associated AP changes as well as network addressability of a device.

WiFiSurvey has a Configuration service, a SQL CE 3.5-backed Data Access Layer, an Infrastructure module and a an application shell all of which are fully decoupled from one another and that are all loaded dynamically using an XML definition file.  The shell makes use of both a DeckWorkspace and a TabWorkspace, showing you not just how to use them, but also how to create your own workspaces if need be.

The WiFiSurvey application has a single source base for all target platforms and has been tested on the following platforms:

  • ARM-based CE 6.0 with a 320x240 (landscape) display.
  • Pocket PC 2003 240x320 (portrait)
  • WinMo 5.0 240x320 (portrait and landscape)

The IoC framework has additionally been tested on x86-based CE 5.0 and CE 6.0 devices.

As a side note, the WiFiSurvey sample application is also a good example of using the OpenNETCF Smart Device Framework for getting wireless information.

6/24/2009 2:54:24 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 
 Wednesday, June 17, 2009

So it seems that something has changed in CE 6.0 in the way that user inactivity is detected by the OS.  In CE 5.0 and before, if we wanted to keep the backlight on we could periodically call SystemIdleTimerReset and all would be well.  In CE 6.0, this no longer works.  Now we have to set a named event that GWE is waiting on.  Here's what it looks like (this code uses the SDF for the named EventWaitHandle - the CF doesn't provide one).

private EventWaitHandle m_activityEvent;

[DllImport("coredll", SetLastError=true)]
private static extern void SystemIdleTimerReset();

private void ResetBacklightTimer()
{
  if (Environment.OSVersion.Version.Major <= 5)
  {
    SystemIdleTimerReset();
  }
  else
  {
    if (m_activityEvent == null)
    {
      using (var key = Registry.LocalMachine.OpenSubKey("System\\GWE"))
      {
        object value = key.GetValue("ActivityEvent");
        key.Close();
        if (value == null) return;

        string activityEventName = (string)value;
        m_activityEvent = new EventWaitHandle(false, EventResetMode.AutoReset, activityEventName);
      }
    }

    m_activityEvent.Set();
  }
}

6/17/2009 5:11:25 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 
 Wednesday, June 10, 2009

I've released yet another version of the OpenNETCF.IoC Framework.  This release has a few minor fixes over what I shipped last week, but more important is that it includes the start for a more complete example.  The new sample shows how to dynamically load modules based on a configuration XML document and how to use the DeckWorkspace.

If you're using the framework, please let me know.  I see that it's been downloaded and we're getting some questions and bug reports, so I know that people are at least testing it out, but I've gotten no feedback as to whether anyone finds it useful of not.

6/10/2009 4:51:53 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 
 Friday, June 05, 2009

The new Demo showing XAML for Windows CE is really, really impressive.  This is non-WinMo, meaning CE no longer is a second-class citizen  in the device market.  If only I knew how to do design work....

6/5/2009 6:48:26 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
I've pushed up a new set of features and fixes for the OpenNETCF.IoC Framework as well as rolled a new release. See the Project Site for full details on what changed, but the general feature additions are that the framework now supports loading modules from configuration XML (you can now really decouple your app modules) and I added support for a DeckWorkspace and all the trappings around starting your app from a workspace.
6/5/2009 2:21:56 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Tuesday, May 19, 2009

Detecting if your application has been idle (i.e. no user mouse or keyboard actions) for a certain period of time is straightforward using an IMessageFilter implementation.  The filter would look like this:

using System;

using System.Collections.Generic;
using System.Text;
using OpenNETCF.Win32;
using System.Windows.Forms;
using OpenNETCF.Windows.Forms;

namespace MessageFilterSample
{
  public class InactivityFilter : IMessageFilter
  {
    public event MethodInvoker InactivityElapsed;

    private Timer m_inactivityTimer;

    public InactivityFilter(int timeoutMilliseconds)
    {
      m_inactivityTimer = new Timer();
      m_inactivityTimer.Interval = timeoutMilliseconds;

      m_inactivityTimer.Tick += new EventHandler(m_inactivityTimer_Tick);
      Reset();
    }

    void m_inactivityTimer_Tick(object sender, EventArgs e)
    {
      m_inactivityTimer.Enabled = false;
      Elapsed = true;

      if (InactivityElapsed != null) InactivityElapsed();
    }

    public bool PreFilterMessage(ref Microsoft.WindowsCE.Forms.Message m)
    {
      switch ((WM)m.Msg)
      {
        case WM.KEYUP:
        case WM.LBUTTONUP:
        case WM.MOUSEMOVE:
          // reset the timer
          m_inactivityTimer.Enabled = false;
          m_inactivityTimer.Enabled = true;
          break;
      }
      return false;
    }

    public int Timeout { get; set; }
    public bool Elapsed { get; private set; }

    public void Reset()
    {
      Elapsed = false;
      m_inactivityTimer.Enabled = true;
    }
  }
}

To use it, you must use one of the Run overloads in Application2 to start your application, then you can add your filter like this:

InactivityFilter m_filter = new InactivityFilter(5000);
m_filter.InactivityElapsed += new MethodInvoker(m_filter_InactivityElapsed);
Application2.AddMessageFilter(m_filter);
...
void m_filter_InactivityElapsed()
{
   MessageBox.Show("Inactivity timer fired");
}

Download the sample here:
 

MessageFilterSample.zip (3.92 KB)
5/19/2009 5:35:42 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [3]  | 
 Wednesday, May 13, 2009

We've been doing a ton of internal work lately on our Padarn ASP.NET Web Server for Windows CE - both on features as well as just the general stuff you have to do to have a product.  Part of this work was to create a series of hands-on labs to demonstrate how to use Padarn effectively.  The PDFs (a dozen in all so far), along with the latest Developer's Guide are available for public viewing on the Padarn Downloads page.

5/13/2009 7:47:53 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Tuesday, May 12, 2009

Admittedly this isn't really mobile related, but the guys over at CI Advantage are giving away T-Shirts with every download of the eval version of Deploy Now (which we do use and love here).  If you have desktop installations of anything, including mobile software or tools (we use it for testing the SDF installer scenarios, among other things) - it's a major time saver.

5/12/2009 10:01:04 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Monday, April 20, 2009

I blogged about this feature of the SDF two years ago (almost to the day in fact), but it certainly bears repeating since it's so useful.  If you need to know wehn a disk (SD, CF, USB, etc) has been attached or removed from a CE device (including Windows Mobile) you can use the SDF's DeviceStatusMonitor class (which, generally speaking, wraps the RequestDeviceNotifications API).  It's really, really simple to use:

private void WatchForDrives()
{
  DeviceStatusMonitor monitor = new DeviceStatusMonitor(DeviceClass.FileSystem, false);
  monitor.StartStatusMonitoring();
  monitor.DeviceNotification += delegate(object sender, DeviceNotificationArgs e)
  {
    string message = string.Format("Disk '{0}' has been {1}.", e.DeviceName, e.DeviceAttached ? "inserted" : "removed");
    MessageBox.Show(message, "Disk Status");
  };
}

4/20/2009 4:08:38 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 
 Friday, April 17, 2009

In one of the mailing lists I'm on today, someone noted that on their Windows Mobile device the performance of GDI is much better in portrait mode than it is in landscape mode and was wondering why.  The reason is actually pretty simple.  Think of the image on the screen as just a contiguous stream of bytes (which is probably is).  The physical display needs to get that data and "paint" it.  Displays are engineered to essentially take in the data in a linear fashion, left to right, top to bottom.  So it's a pretty simple operation to just send out the framebuffer data to the display.

Now consider what happens if you want the display rotated 90 degrees. Remember, the display requires the data top to bottom, left to right, as it will show on the screen.  To get it into that state you have 2 options.  You can either A) rotate the data as you take it out of the frame buffer and send it to the display or B) rotate all calls to draw into the framebuffer.  In either case you have to do a matrix transform, which is expensive and gets worse the larger your display gets (so a VGA device will be more affected than a QVGA device).

Of course some devices have hardware acceleration that can greatly improve things by having matrix functions right in the silicon, but that still requires that the OEM actually modifies the driver to use that function (you'd be surprised how many don't).

So how bad is the performance penalty?  Well, since I like to actually quantify stuff I decided to resurrect an old GDI test app I had and rework it for this.  The results of my testing, as well as tests sent in from other people (if you want to add a test to the table, send me your results):

Device Processor OS Portrait Landscape Penalty
Axim x51 PXA270 WinMo 5 188 ops/s 77 ops/s 59%
Asus P750 PXA270 520MHz WinMo 6 Pro 241 ops/s 55 ops/s 77%

As you can see, there's a significant price to running rotated.

The test application (source and ARMv4I binary) is available here

GDIPerf.zip (10.46 KB)

 

4/17/2009 2:32:33 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 
 Wednesday, April 15, 2009

If you've ever needed to automatically launch an application under Windows CE then you probably know that one of the most common ways is to put an entry into the device registry under HKLM\Init.  This works great for native applications, but for CF applications success is hit or miss.  The reason is that CF applications require certain system APIs to be up and running before they can run.  For a native app that has this requirement, the solution is simple: you call either IsApiReady or WaitForAPIReady (depending on the OS version) and then continue when you're satisfied.  For CF apps it won't work.  The APIs need to be ready long before Main is entered and any of your managed code is running.

Of course the "right" solution is that the CF team should have put that check into the loader so we could launch this way, but they didn't so we have to work around it.

So how do we get around this?  Well we leverage the initialization process itself.  When an app is launched from HKLM\Init, it is responsible for calling SignalStarted once it is running.  This allows any other items launching from HKLM\Init to set up dependencies, for example if item 60 depends on item 50, item 60 won't launch until item 50 has called SignalStarted.  What we can do is create a native application that acts as a launch gate.  This gate app will call the appropriate wait function, and only after the APIs we need are available does it call SignalStarted.  We can then launch any CF application using HKLM\Init by simply having the gate application launch first, and then having the CF app depend on the gate.

So in the registry, it would look like this:

[HKEY_LOCAL_MACHINE\Init]
    "Launch90"="gateapp.exe"
    "Depend90"=hex:1e,00 ; depend on GWES, which is at 30
    "Launch91"="MyCFApp.exe"
    "Depend91"=hex:5a,00 ; depend on gateapp

And here's the source code for a basic gate app:

extern "C" DWORD WaitForAPIReady(DWORD, DWORD);
extern "C" BOOL IsAPIReady(DWORD hAPI);

int _tmain(int argc, _TCHAR* argv[])
{
    // quick sanity check - HKLM\Init will send in our order number
    if(argc == 0) return 0;

    BOOL success = FALSE;

    // wait for window manager - that should be enough for us
    #if _WIN32_WCE > 0x500
        success = (WaitForAPIReady(SH_WMGR, 5000) == WAIT_OBJECT_0);
    #else
        int i = 0;
        while((! IsAPIReady(SH_WMGR)) && (i++ < 50))
        {
             Sleep(100);
        }

        success = (i < 50);
    #endif

    if(success)
    {
        int launchCode = _ttoi(argv[1]);
        SignalStarted(launchCode);
    }
    else
    {
        RETAILMSG(TRUE, (_T("CFInitGate timed out - SH_WMGR was not ready after 5 seconds\r\n")));
    }

    return 0;
}

4/15/2009 1:46:38 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 
 Tuesday, April 14, 2009

If you just got the latest release of the IE8 Beta, your C++ Smart Device wizard will now fail.  When you try to create a project you'll get a few screen flashes and then an unhelpful "project creation failed" message in teh status bar of Studio.  The fix is outlined here (and worked on my XP box).

4/14/2009 10:03:58 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 
 Wednesday, April 01, 2009

I'm in the middle of adding a Passive View Model-View-Presenter(MVP) framework to the OpenNETCF IoC Framework.  I just checked in a working version (it's in the source downloads, not as a release).  If you're interested and want to have a say in how it ends up, go ahead and download it and give me your feedback.  There's a simple usage example in source control.

4/1/2009 4:10:37 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [3]  | 
 Tuesday, March 31, 2009

Another nice addition to the latest drop of the SDF is the ability to get the serial number and manufacturer ID of storage volumes that support it (like SD cards). We did this by simply extending the existing OpenNETCF.IO.DriveInfo class to add a couple new properties.  Here's a quick example of how it works:

foreach(var info in DriveInfo.GetDrives())
{
  Debug.WriteLine("Info for " + info.RootDirectory);
  Debug.WriteLine("\tSize: " + info.TotalSize.ToString());
  Debug.WriteLine("\tFree: " + info.AvailableFreeSpace.ToString());
  Debug.WriteLine("\tManufacturer: " + info.ManufacturerID ?? "[Not available]");
  Debug.WriteLine("\tSerial #: " + info.SerialNumber ?? "[Not available]");
  Debug.WriteLine(string.Empty);
}

3/31/2009 12:03:33 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 

The latest drop of the SDF adds a nice little feature for playing tones with the device.  It works a lot like the old Beep API on the desktop where you provide a frequency and a duration and it plays the tone.

Here's a quick example of how it works:

Tone[] scale = new Tone[]
{
  // up fast, using MIDI
  new Tone { Duration = 10, MIDINote = 63},
  new Tone { Duration = 10, MIDINote = 65},
  new Tone { Duration = 10, MIDINote = 67},
  new Tone { Duration = 10, MIDINote = 68},
  new Tone { Duration = 10, MIDINote = 70},
  new Tone { Duration = 10, MIDINote = 72},
  new Tone { Duration = 10, MIDINote = 74},
  new Tone { Duration = 10, MIDINote = 75},

  // down slow, using freq (same notes as above)
  new Tone { Duration = 100, Frequency = 622 },
  new Tone { Duration = 100, Frequency = 587 },
  new Tone { Duration = 100, Frequency = 523 },
  new Tone { Duration = 100, Frequency = 466 },
  new Tone { Duration = 100, Frequency = 415 },
  new Tone { Duration = 100, Frequency = 391 },
  new Tone { Duration = 100, Frequency = 349 },
  new Tone { Duration = 100, Frequency = 311 },
};

SoundPlayer.PlayTone(scale);

You can see that this just plays an ascending scale of notes quickly, then the same scale descending, but slower.  Note that the PlayTone method takes in an array of Tones, and a Tone can be initialized with either a Frequency or MIDI note value.

3/31/2009 11:57:42 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 

Smart Device Framework 2.3.0.39 has just been published.  This is primarily a drop to fix several issues with the WirelessZeroConfigNetworkInterface class (it now properly supports WPA and WPA2 access points) but there are a couple cool new features.

3/31/2009 11:23:49 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Monday, March 30, 2009

I've pushed up a set of fixes for the OpenNETCF.IoC Framework. See the Project Site for details on what changed.

3/30/2009 5:33:24 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Friday, March 20, 2009

OpenNETCF used to have a product that provided RAS capabilities.  Well Microsoft deprecated RAS in WindowsMobile - starting with WinMo 5.0 as near as I can figure - in favor of Connection Manager.  That depracation actually broke RAS in some scenarios.  FOr example, if you create a RAS connection and then try to use a managed TcpClient, it will try to create it's own connection through the Connection Manager instead of using the connection you created through RAS.

What this did was skyrocket our support incidents for something that we sold very, very few licenses for.  In the interest of keeping our sanity, yet allowing people using plain of Windows CE to still use RAS, we made the decision to just make it a shared source project.  It's now out on CodePlex.  If you need support for it, we still offer consulting services, but everything is there to get you going.

3/20/2009 12:54:05 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Monday, March 16, 2009
I've finally got the CodePlex project set up for the OpenNETCF.IoC framework, so that is now the "official" place to get it.  One thing to note is that my original intent was to publish under a Public Domain license, but evidently it's not one of the options in CodePlex (I'll email them and see what I can do about that).  In the meantime I set it to what looks to be the most permissable license they had - the MIT license.

3/16/2009 5:15:30 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Wednesday, March 11, 2009

One gripe I've had for a long time is that to do smart device app development you have to have the Pro version of Studio, which will set you back about a grand.  It's been a while since I was in high school or college, but I can't imagine it's too easy to find that kind of change couch fishing (and if you did would you actually use it for a dev tool?).  Well Microsoft has at least made it easier for students with the new DreamSpark program (I'm not so keen on the name, but the idea is great).  You can now get the tools for free.

Now if only we can get them to make it easier for the hobbiest to get into the game....

3/11/2009 11:57:52 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 

Articles in this series
Part I: Inversion of Control and the Compact Framework
Part II: The OpenNETCF.IoC Framework: Items and Services
Part III: The OpenNETCF.IoC Framework: Events (this article)
Part IV: The OpenNETCF.IoC Framework: Performance (TBD)

Downloads
Code and Sample available through CodePlex.


In the Part II of this series we looked at how the OpenNETCF.IoC framework provides dependency injection for the lists of Items and Services and if you look at the sample application that ships with the framework you’ll see that the application creates and displays 3 separate Froms and a Service without a single call to the ‘new’ operator anywhere in the solution and, more importantly, without having to pass object references around yourself.

While I find that both fun and useful, the real thing I love about the OpenNETCF.IoC framework (and the SCSF that it’s modeled after) is the implementation of inversion of control through event publication and subscription.

A New Paradigm for Events

In the traditional managed development, an object exposes an event, something like this:

public event EventHandler OnMyEvent;

And when a subscriber wants to get notified of that event, they add a handler delegate like this:

publisherInstance.OnMyEvent += HandleOnMyEvent;

void HandleOnMyEvent(object sender EventArgs args)
{
  // do something useful here
}

That’s all well and good, but in my mind there are two problems with it. First it’s just plain ugly.  I have code in two places, first to attach the event, and second to handle it.  I like to keep related code close together, and this coupling of attaching the handler and the delegate implementation makes that hard (unless you use anonymous delegates). 

The second, and far worse, problem is that it requires that you have the instance of the event publisher to wire this up.  In some cases this is fine, but in others it seems rather pointless.  Let’s go back to one of our early examples of People and Cars and extend it ever so slightly.  We’ll add an event to the Car class :

public delegate void WreckHandler(ICar car);

class Car : ICar
{
  public event WreckHandler OnWreck;
}

And let’s say we have a specialized person – a PoliceOfficer – and he’d like to know any time there is a car wreck.

class PoliceOfficer : Person
{
  public void HandleCarWreck(ICar car) {…}
}

How would we wire this up?  Should we pass every Car instance in town to the officer so he can wire up the event?

class PoliceOfficer : Person
{
  public void HandleCarWreck(ICar car) {…}

  public void WatchCar(ICar car)
  {
    car.OnWreck += HandlerCarWreck;
  }
}

When would we call this?  Every time a Car instance is created?  How would the new Car instance know about the PoliceOfficer?  What if we have multiple Officers?  You can see that this gets real ugly, real fast.  Wouldn’t it be nice if any PoliceOfficer could just listen for the OnWreck event globally and any time any Car instance raised it, he would get notified?  Well that’s what the OpenNETCF.IoC framework’s eventing structure is all about. You publish and subscribe to events based on a unique string name for the event of interest.

So for the event publisher, the Car in this case, we use the same event definition but we add a simple attribute:

class Car : ICar
{
  [EventPublication(“CarWreck”)]
  public event WreckHandler OnWreck;
}

The important piece here is the text string that is sent in to the EventPublication attribute.  It can be any string at all, but it’s that string that event subscribers will use.

Over on the subscriber end it, hooking up the event looks like this:

class PoliceOfficer : Person
{
  [EventSubscription(“CarWreck”, ThreadOption.Caller)]
  public void HandleCarWreck(ICar car) {…}
}

Notice that I’m using the same text string in both.

Now there are a few important notes on using events in the OpenNETCF.IoC framework.  First all of the objects (publishers and subscribers) have to be actually in the framework (in either the Items or Services collection).  In fact with the version of the Framework that ships as I write this, the objects actually have to be created by the framework.  This means that if you create the object manually and then use the Add method to get it into the collection (as opposed to calling AddNew or using an InjectionConstructor or ServiceDependency), then the events won’t get wired up.  I intend to fix this in a future version, but if you pull down the code today, be aware of that limitation.

The second note is in relation to the subscriber.  You’ll see that the EventSubscription attribute takes a ThreadOption as a second parameter.  The idea behind this parameter is that it should dictate the thread on which the handler runs – either in the context of the caller or in the context of the UI.  Well that attribute, for now, is just a placeholder.  It’s not actually used anywhere, so don’t be surprised if your worker thread raises an event and your subscriber tries to update the UI and gets an exception whining about the use of Control.Invoke.  Again, this is something I intend to complete but when I looked at the options of releasing the framework either earlier with a few missing features or later and feature complete, I thought that just getting it out would be a lot more useful.


Next up: Looking at the performance implications of all of this IoC and DI stuff

3/11/2009 11:23:45 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 
 Tuesday, March 10, 2009

So I was reading the Windows Mobile Team Blog and to my surprise I saw "How to capture a screen shot with .NET CF" as a topic that actually made it to the top of their "upcoming articles" list.  Seriously?  I know the SDF provides that.  So I did some basic Googling and sure enough, unless you know what you're looking for, it's tough to find (but it's been out for over a year).  For the record, the code is way too simple for a full article.  Even with the P/Invokes directly it's pretty basic, but with the SDF, it looks like this (pulled from a sample web page for our Padarn web server):

// create a bitmap and graphics objects for the capture
Drawing.Bitmap destinationBmp = new Drawing.Bitmap(Forms.Screen.PrimaryScreen.Bounds.Width, Forms.Screen.PrimaryScreen.Bounds.Height);
Drawing.Graphics g = Drawing.Graphics.FromImage(destinationBmp);
GraphicsEx gx = GraphicsEx.FromGraphics(g);

// capture the current screen
gx.CopyFromScreen(0, 0, 0, 0, Forms.Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);

// save the file
destinationBmp.Save(filename, System.Drawing.Imaging.ImageFormat.Png);

// clean house
gx.Dispose();
g.Dispose();
destinationBmp.Dispose();

Simple enough, right?

3/10/2009 10:59:06 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 

Articles in this series
Part I: Inversion of Control and the Compact Framework
Part II: The OpenNETCF.IoC Framework: Items and Services (this article)
Part III: The OpenNETCF.IoC Framework: Events
Part IV: The OpenNETCF.IoC Framework: Performance (TBD)

Downloads
Code and Sample available through CodePlex.


The OpenNETCF.IoC Framework

 

Since I don’t expect that everyone has used the SCSF and since the OpenNETCF IoC framework is a very small subset, let’s walk through what it is and how it works.  First, the OpenNETCF.IoC framework is based on the concept of “Components.”  A Component is simply an instance of a class and our framework has two flavors of Components: an Item and a Service.  Generally speaking, the difference between an Item and a Service is that an Item is a uniquely-named instance of a Type (so you can have any number of them provided each instance has a unique name) whereas there can only be one Instance of a service per registered type (it acts somewhat like a Singleton).

Both Items and Services are contained as collections in a root container called the RootWorkItem (a name that comes from the CAB framework).  Let’s take a look more in depth at each of these components and how you might use them together.  For this example we’ll get a little more concrete that the Cars and People example from before, and instead look at classes more actual applications might use.

Items

As I mentioned, Items are simply uniquely named instances of objects.  They can be of any Type – the Items collection doesn’t need to contain only a set of a specific type.  The Items are contained, conveniently enough, in the RootWorkItem.Items collection.  So exactly what advantages to we gain by putting our items in this collection?  The primary benefit is that the RootWorkItem becomes an instance manager and, as we’ll see in a little bit, it can also be a factory.

Let’s assume that our application contains the following Forms: MenuForm, EntryForm and SettingsForm and to make things simple, we’ll assume that they are named with their type name.  If we have these forms in the IoC framework, then any time we need to reference one of them we can simply do something like this:

desiredForm = RootWorkItem.Items[“EntryForm”];

and ta-da, we get the instance of the Form.  We don’t need to pass around a reference to it or store it in some other application global location.  So that’s pretty useful right there.  But the RootWorkItem really is just an application global, right, so it must provide some other benefit.  Well it’s also a factory.  If we want to create the entry form we can use a basic create-and-insert mechanism like this:

EntryForm form = new EntryForm();
RootWorkItem.Items.Add(form, “EntryForm”);

But that really isn’t all that interesting.  Where it gets interesting is that you can let the framework do the construction for you by simply giving it a Type and name (the name is actually optional, as the framework will assign it a GUID if you don’t provide one):

RootWorkItem.AddNew<EntryForm>(“EntryForm”);

That’s handy.  No need to call the object contructor to get an instance and then stuff it into the collection.  Less code is always a good thing.  But what else can it do (right now I’m feeling a bit like Ron Popeil)?

Well that’s not all!  Where it gets fun is in its ability to do injection.  This requires a little more complex of a scenario.  Let’s assume that the MenuForm requires an instance of both an EntryForm and a SettingsForm.  Something like this:

class MenuForm
{
  public MenuForm(EntryForm entryForm, MenuForm menuForm) {…}
}
 

Well the OpenNETCF.IoC framework can actually do these injections for you – all you have to do it provide it a little direction with attributes.  If we simply decorate the constructor with the InjectionConstructor attribute, the framework will search the Items collection for existing items of the proper type to inject.  So the MenuForm looks like this:

class MenuForm
{
  [InjectionConstructor]
  public MenuForm(EntryForm entryForm, MenuForm menuForm) {…}
}

And construction now looks like this:

RootWorkItem.AddNew<SettingsForm>(“SettingsForm”);
RootWorkItem.AddNew<EntryForm>(“EntryForm”);
RootWorkItem.AddNew<MenuForm>(“MenuForm”);

And the magic of the OpenNETCF.IoC framework will inject the first two instances into the third.  An interesting note here is that the Injection Constructor does not have to be public.  The OpenNETCF.IoC framework looks for internal and private constructors as well so you can actually create objects that can only be generated via injection if you wish.

Of course the framework also supports Injection Methods as well, in the event that an InjectionConstructor doesn’t meet your needs:

class MenuForm
{
  public MenuForm() {…}

  [InjectionMethod]
  void InjectEntryForm(EntryForm entryForm) {…}

  [InjectionMethod]
  void InjectSettingsForm(SettingsForm settingsForm) {…}
}

But wait, there’s more!  In the examples so far the RootWorkItem.Items collection must contain the SettingsForm and EntryForm before the MenuForm is created.  Well what if we are lazy and don’t even want to do that?  Well the OpenNETCF.IoC framework can handle that too.  Just add the CreateNew attribute like this:

class MenuForm
{
  [InjectionConstructor]
  public MenuForm([CreateNew]EntryForm entryForm, [CreateNew]MenuForm menuForm) {…}
}

And the framework will create a new instance of the Type if it can’t find it in the Items collection. Construction of all three objects and injecting them now looks like this:

 

RootWorkItem.AddNew<MenuForm>(“MenuForm”);

Extremely simple and clean.

Services

The RootWorkItem also contains a collection of Services.  A Service is very similar to an Item except for the fact that there can only be one service of any given registered type (we’ll covered what “registered” means in a moment).  The collection provides a very similar set of methods and attributes as items.  Again, let’s consider a more concrete example.  Assume your application has a class that handles reading and setting configurations.  There really would only be one instance of this class and in a lot of classic cases people would use the global-wrapped-in-a-new-name called a Singleton.

In the OpenNETCF.IoC framework this would be a service.  Since there can only be one pre registered type, there’s no need to name a Service – the registration type becomes the identifier.  So as a simple construct/add/retrieve a Service operation would look  like this:

Configuration config = new Configuration();
RootWorkItem.Services.Add<Configuration>(config);

Configuration retrievedConfig = RootWorkItem.Services.Get<Configuration>();

As with the Items collection, there is a lot more power and convenience in the framework.  First, we can have the framework do construction for us:

RootWorkItem.Services.AddNew<Configuration>();

Like the Items collection, InjectionConstructor or InjectionMethod attributes can be used to control which constructor for the service class gets called.

The OpenNETCF.IoC framework also offers lazy loading of services, so the service instance isn’t actually created until it is first accessed (instead of when it’s added).

RootWorkItem.Services.AddOnDemand<Configuration>();

We saw earlier that the OpenNETCF.IoC framework would walk the Items collection looking for instances to use during injection.  Well what if an object depends on a Service rather than another Item?  The framework also provides a mechanism for that as well using the ServiceDependency attribute.  So to inject a Service into a consumer class using Constructor Injection it would look like this:

class ServiceConsumer
{
  [InjectionConstructor]
  public ServiceConsumer([ServiceDependency]Configuration config) {…}
}

And of course there is a way to do setter injection instead of constructor injection.  Here it injects into a property instead of using a method like the Items collection:

class ServiceConsumer
{
  public ServiceConsumer() {…}

  [ServiceDependency]
  public Configuration Config { set; get; }
}

And if you want the framework to construct the service if it doesn’t already exist, you simply set the EnsureExists member of the ServiceDependency attribute like this:

[ServiceDependency(EnsureExists=true)]
public Configuration Config { set; get; }

The only other aspect of a Service that an Item does not have is a “registration type”.  This allows you to register a service instance as a type other than its actual base type.  For example you may have a Configuration class that you want to register as a service, but you want to register it as an IConfiguration (this would allow consumers to extract the service by the interface type without ever knowing about or having a reference to the concrete implementation).


Up next: The OpenNETCF.IoC Framework: Event Publication and Subscription

3/10/2009 9:17:43 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [4]  | 
 Monday, March 09, 2009

Articles in this series
Part I: Inversion of Control and the Compact Framework (this article)
Part II: The OpenNETCF.IoC Framework: Items and Services
Part III: The OpenNETCF.IoC Framework: Events
Part IV: The OpenNETCF.IoC Framework: Performance (TBD)

Downloads
Code and Sample available through CodePlex.


Introduction

 

Periodically the software industry goes through a shift in underlying programming practices.  In the 80s the shift was from procedural code to object oriented.  In the 90s we saw the rise of things like “extreme” and “agile” programming.  Recently I’ve seen a shift toward using dependency injection (DI) and inversion of control (IoC).  They’ve been around long enough now that I think they’ll stick, and since any programmer should try to maintain some level of understanding of the latest technologies, I decided to dive into them.

What came out of my investigation were the following:

1.       I was already doing a lot of the stuff, I just didn’t know it

2.       That there really isn’t a reasonable framework in existence for the Compact Framework

3.       Dependency Injection and Inversion of Control, when followed well, can greatly improve extensibility, maintainability and testability of code.

4.       My own IoC framework

Definitions

Before we can talk about all of the benefits of using a framework for Dependency Injection and Inversion of Control, we really need to define them. 

Dependency Injection

Though the name sounds complex, Dependency injection really is what the name implies and is really simple, but explaining it in abstract terms tends to get convoluted.  For example, it could be defined as “with dependency injection you inject dependent objects into the object which depends on them.” Doesn’t really roll of the tongue, does it?  I read that I tend to see “blah, blah, object, blah, depend, blah” and want to move on to something else altogether.  But really, it is simple.  So let’s use an actual example and a picture.

Let’s say we have a couple classes,  Car and Person, and a person can own and drive a car. So it’s something like this:

class Car
{
  public Car() {…}
}

class Person
{
  private Car m_car;

  public Person() {…}

  public void Drive () {…}
}

Now the question here is how does the Drive method “get” or “know” what car to Drive?  One way would be to have the Person instance create the car along these lines:

 

 

public Person()
{
  m_car = new Car();
}


 

So the Person class knows what its dependency is.  It knows at compile time that it needs a Car and creates it.  That’s all well and good, and sure, it work, but it’s certainly not extensible.  To make it a little more extensible, we could use an interface for the Car instead:

 

class Person
{
  private ICar m_car;

  public Person()
  {
    if(this.IsAMoparGuy)

      m_car = new ShinyDodgeChallenger();
    else
      m_car = new RustBucket();
  }
}

 

This is a bit more extensible – the Person has some opportunity to decide what type of car it wants, but it still puts that decision in the Person class and the Person class still has to know about the concrete types of Car. So how do we make it even more extensible so that Person needs to only know about the interface?  Simple – we pass in the object.  That can be done, generally speaking, in two ways.  Either in the constructor:

 

class Person
{
  private ICar m_car;

  public Person(ICar car)
  {
    m_car = car;
  }
}

Or using a method to set it:

 

class Person
{
  private ICar m_car;

  public Person() {}


  public SetCar(ICar car)
  {
    m_car = car;
  }
}

It’s very likely that you’ve used patterns like this in the past, and in fact these are dependency injection.  The first is called “constructor injection” and the second is called “method injection.”  I’ve also seen references to “property injection” which you can probably guess sets the dependency using a Property instead of a method, but a Property really is nothing more than syntactic sugar around a pair of methods.  If you really want to consider it separate, then we can lump the Property and Method injections into something I’ll call “setter injection.”

Inversion of Control

Inversion of Control is a lot like Dependency Injection in that the name puts me off immediately because it sounds like an attempt to use overly large words to describe a likely simple thing, and indeed it is.  In “old school” programming you might have an object (like a Person) that controls another object (like a Car) and when you want to know if some state has changed, you simply query it.

So the Person instance might do something like this

if(m_car.HasCrashed) Call911();

Well Inversion of Control simply turns that around.  Instead of us asking the object for state, the child object (the Car in this example) will inform us of a change.  Sound familiar?  .NET events and delegates are a classic, and really often used, case of Inversion of Control.

So you might hook it up like this

m_car.OnCrash += new CrashHandler(
    delegate { Call911(); }
  );

It’s nothing more complex than that.

IoC and the Compact Framework

When I started out looking into IoC and DI, I was a little put off.  I dislike following programming fads or chasing after some technology simply because it’s all the rage and there are conferences touring the country talking about them.  But I was working on a desktop project and a friend said that I should check out Microsoft’s Smart Client Software Factory (or SCSF) as he thought it would be a good framework to achieve the goals we were after.

Well it turns out that SCSF is built on Microsoft’s Composite UI Application Block (also called CAB) and together they really are just a library that provides a very robust IoC framework.  I’m currenly working on porting that desktop project to use the SCSF and it’s turning out to be very, very useful.

When I needed an IoC framework for the CF, I found that Microsoft’s Patterns and Practices team ported the CAB/SCSF framework a few years back.  I also quickly found out that the people who did the port appeared to do a literal port of the code.  So while it compiles and runs, it certainly does not take into consideration the limited memory and processor power of a typical Windows CE device.  A very common complain about the Mobile Client Software Factory is that its performance sucks.  And I can attest to that.  It does suck.

So what to do?  There are a couple (and really only two that I could find) IoC frameworks that claim to be CF compatible.  I looked at them briefly, but I pretty quickly abandoned them because I really don’t feel like learning the object model for a whole new framework.  I’m using the SCSF and I’m comfortable with it, and I don’t like having to constantly have to think about object models depending on what my code targets.  I also like to keep my code as reusable as possible, and changing frameworks certainly wouldn’t help on that front.

So what I decided to do was to build my own framework based, mostly, on the object model of the SCSF.  The goal was to implement only the bare minimum of what I needed and to build it specifically considering that it would be used on CE devices. What I ended up with is a simple, lightweight dependency injection framework that I called OpenNETCF.IoC. 


Up Next: The OpenNETCF.IoC Framework: Items and Services

3/9/2009 2:48:36 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [7]  | 
 Tuesday, February 24, 2009
Every time we release a new version of the SDF, even minor releases, we often sneak in a few added features. For example the latest release (2.3.0.21) added a static OpenNETCF.Net.Dns class with a couple of methods:QueryARecord and ReplaceARecord (the source is itself is actually CE and desktop compliant if you're interested). These correspond to the APIs DnsQuery_W and DnsReplaceRecordSetW and can be quite useful when your device is on a public network getting assigned a dynamic IP address. (For the record, the MSDN documentation on the APIs is abysmal - and even the headers don't provide much clarity for usage).

Here's a quick example of usage:

static void Main(string[] args)
{
  System.Net.IPAddress dnsServer = System.Net.IPAddress.Parse(DNS_ADDRESS);

  try
  {
    string nameToResolve = PC_NAME;

    Console.WriteLine(string.Format("Getting an A record for '{0}'...", nameToResolve));
    ARecord[] records = Dns.QueryARecord(dnsServer, nameToResolve, QueryOptions.BypassCache);
    Console.WriteLine(string.Format("\r\n Name: {0}", records[0].Name));
    Console.WriteLine(string.Format(" Address: {0}", records[0].Address.ToString()));
    Console.WriteLine(string.Format(" TTL: {0}", records[0].TTL));

    string replacementAddress = REPLACE_ADDRESS;
    Console.WriteLine(string.Format("\r\nReplacing address {0} with {1}...", records[0].Address.ToString(), replacementAddress));
    records[0].Address = System.Net.IPAddress.Parse(replacementAddress);
    Dns.ReplaceARecord(dnsServer, records[0], DnsUpdateSecurity.On, UID, DOMAIN, PWD);

    Console.WriteLine(string.Format("\r\n Getting an A record for '{0}' again...", nameToResolve));
    records = Dns.QueryARecord(dnsServer, nameToResolve, QueryOptions.BypassCache);
    Console.WriteLine(string.Format("\r\n Name: {0}", records[0].Name));
    Console.WriteLine(string.Format(" Address: {0}", records[0].Address.ToString()));
    Console.WriteLine(string.Format(" TTL: {0}", records[0].TTL));
  }
  catch (DnsException ex)
  {
    Console.WriteLine(string.Format("\r\n!! FAILED with error code {0} : '{1}'", ex.ErrorCode, ex.Message));
  }

}


2/24/2009 10:25:32 AM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  | 
 Monday, January 12, 2009
No idea why the CF omitted the GetCultures method.  The info it returns is clearly supported in the OS, and it's not like it's a lot of work (plus if you're doing globalization, it really is helpful.

Here's a quick implementation I put together this morning that we'll likely add to the SDF:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.InteropServices;

namespace OpenNETCF.Globalization
{
  public class CultureInfoHelper
  {
    private delegate int EnumLocalesHandler(string lpLocaleString);
    private static EnumLocalesHandler m_localesDelegate;
    private static List<CultureInfo> m_cultures;

    private static int EnumLocalesProc(string locale)
    {
      try
      {
        m_cultures.Add(CultureInfo.GetCultureInfo(
        int.Parse(locale, NumberStyles.HexNumber)));
      }
      catch
      {
        // failed for this locale - ignore and continue
      }

      return 1;
    }

    public static CultureInfo[] GetCultures()
    {
      if (m_localesDelegate == null)
      {
        m_cultures = new List<CultureInfo>();
        m_localesDelegate = new EnumLocalesHandler(EnumLocalesProc);
        IntPtr fnPtr = Marshal.GetFunctionPointerForDelegate(
              m_localesDelegate);
        int success = EnumSystemLocales(fnPtr, LCID_INSTALLED);
      }

      return m_cultures.ToArray();
    }

    private const int LCID_INSTALLED = 0x01;
    private const int LCID_SUPPORTED = 0x02;

    [DllImport("coredll", SetLastError = true)]
    private static extern int EnumSystemLocales(
    IntPtr lpLocaleEnumProc, uint dwFlags);
  }
}


1/12/2009 1:22:58 PM (Eastern Standard Time, UTC-05:00)  #    Comments [4]  | 
 Tuesday, December 23, 2008
We still need to put together a list of "what's new" in SDF 2.3 - it's a bit of a challenge, and we're a little busy here - but in the meantime I'll try to blog about some of the features that come to mind.

One simple new item if the ability to change radio power states (WiFi, Bluetooth and Phone) on WinMo devices.  Here's a quick example of how it might be done:

using OpenNETCF.WindowsMobile;

namespace SmartDeviceProject1
{
   public static class Utility
   {
     public static void SetWifiRadioPower(bool turnOn)
     {
       foreach (IRadio radio in Radios.GetRadios())
       {
         if (radio is WiFiRadio)
         {
           radio.RadioState = turnOn ? RadioState.On : RadioState.Off;
         }
       }
     }
   }
}


12/23/2008 1:52:59 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  | 
 Wednesday, December 03, 2008
It seems that just about every project I work on requires that I write classes that expose events.  Of course this isn't surprising or new.  What has always bothered me was the fact that this tends to generate lots of custom delegates - especially if you have multiple developers working on a project.  Often, the event is simply passing a single data item, and so we end up with ugliness like this, especially when we want the delegate to be strongly typed:

delegate void StringDelegate(object o, string item);
delegate void IntDelegate(object o, int item);
delegate void BoolDelegate(object o, bool item);
delegate void StringListDelegate(object o, List<string> item);

...

public event StringDelegate StringEvent;
public event IntDelegate IntEvent;
public event BoolDelegate BoolEvent;
public event StringListDelegate StringListEvent;


It's ugly, it's a pain in the ass to maintain, and it's just terribly redundant.

Lately I've started using a much simpler pattern heavily leveraging generics.  First, the only declaration outside of the event is a single type:

public class GenericEventArg<T> : EventArgs
{
   public GenericEventArg(T value)
   {
     Value = value;
   }

   public T Value { get; set; }
}

Using this, I can create a strongly-typed event to pass any form of argument like this:

public event EventHandler<GenericEventArg<MyClass>> MyClassEvent;
public event EventHandler<GenericEventArg<List<string>>> StringListEvent;
public event EventHandler<GenericEventArg<int>> IntEvent;

No custom delegate required since we use the EventHandler<> delegate for them all.

12/3/2008 12:28:08 PM (Eastern Standard Time, UTC-05:00)  #    Comments [3]  | 
Calling Control.Invoke tends to be a problem, especially for developers who are new to the .NET world.  I think the primary struggle is how to call Invoke without writing a custom delegate, creating a member variable and/or some helper methods.  Here are a couple patterns that I use pretty regularly:

Using an anonymous delegate

if (this.InvokeRequired)
{
   this.Invoke(new EventHandler( delegate (object o, EventArgs a)
   {
     // do your work here
   }));
}
else
{
  
// do your work here
}

Reusing the existing EventHandler

void MyEventHandler(object sender, EventArgs e)
{
   if (this.InvokeRequired)
   {
     this.Invoke(
       new EventHandler(MyEventHandler), new object[] { sender, e }
     );
     return;
   }

   // do your work here
}


12/3/2008 12:12:37 PM (Eastern Standard Time, UTC-05:00)  #    Comments [2]  | 
 Wednesday, November 26, 2008
A major player in our very, very late delivery of version 2.3 of the Smart Device Framework turned out to be problems with toolbox integration in Visual Studio 2008.  The process of automating the install itself isn't well documented, so it took a while to determine that we weren't actually doing anything wrong, but instead that we were uncovering bugs and limitations in Visual Studio itself.

There are two problems every CF developer needs to be aware of (and I really hope Microsoft will publish a KB article on these to help get the word out):
  1. Controls built against CF 2.0 will not show up in the toolbox for CF 3.5 projects.  Period.  There is absolutely no way to make them show up.  So if you want your control to be available in the toolbox for CF 2.0 and CF 3.5 projects, you must build and deploy two versions of your control.  This appears to be "by design," though I'd argue a seriously flawed design.
  2. To compound the problems caused by #1, there is a serious bug as well.  If your control uses the Microsoft.WindowsCE.Forms assembly, then you cannot add it to the CF 3.5 toolbox.  The toolbox accepts CF 2.0 controls using it, but CF 3.5 controls will always throw an exception in Studio.  Even worse, if you have several controls in a single assembly and just one of them uses Microsoft.WindowsCE.Forms, then none of the controls in the assembly can be added to the Toolbox.

So what does this mean to SDF users?  Well nothing good.  We first tried to work around "feature" #1 by building and deploying two SDF assemblies for our controls.  It's not very maintainable, and it's painful, but we spent a good week getting our new automated build scripts to generate the necessary assemblies, CAB files and deployment manifests.  After all that, we found bug #2, so even though we had the 3.5 assembly, it was unusable.

This means that, unless Microsoft releases changes to Visual Studio or we remove several controls from the SDF, you will not have Toolbox support for SDF controls with Compact Framework 3.5 projects in Studio 2008.  We regret that fact and apologize to our users, but there's simply nothing we can do about it at this point.  What we may do (I'm not saying this *is* what will happen, only what we're considering at this point) is split the OpenNETCF.Windows.Forms.dll assembly up to pull CF 3.5 Toolbox supportable controls out, but that's going to break references and make deployment and nasty business.

So if you're an SDF user and you'd like to use the SDF Controls in your CF 3.5 toolbox we encourage you to not open a bug with us, but instead open a support case with Microsoft.  Remember, if you open a case with them and it turns out to be a bug, the support incident is free.  What it does, however, is give them some insight into the number of customers that the bugs actually affect, and hopefully will give them some incentive to release a fix for these problems before 2010.

Note: One semi-kludgy way to get the Controls to show up is to start the project as a 2.0 project and drop them on the Form, then upgrade the project to 3.5.  It seems that the designer itself can handle the objects, it's just that the Toolbox can't.

11/26/2008 11:39:22 AM (Eastern Standard Time, UTC-05:00)  #    Comments [2]  | 
 Thursday, November 06, 2008
So a few months ago we released our CAB Installer SDK, and we decided to try out value-based pricing as a social experiment.  Our thinking was that developers make their living - and typically a more comfortable living than flipping burgers - and that the would a) understand the value of code and time saved and b) be willing to compensate us for the SDK based on their perceived value of the product.

We here we are a full four months later, and how is this experiment going?  Well here's a graph that says it all:




What this says is that we've sold 51 "value units" to 37 customers, meaning that over 80% of you who bought it only paid $5.  Now assuming you're a low-paid, entry-level guy making only $40k a year that means you felt it's worth just over 15 minutes of your time (and keep in mind this thing comes with full source code).

What this tells me is that one of these must be the case:

1) The SDK sucks and has no value
2) People don't understand "value-based" pricing
3) Developers are cheap bastards who will pay as little as possible for something

Well #1 is probably not true, as we've used it on a few projects and it works well.  I think we descibed value-based pricing pretty clearly, and it's not a tough concept.  So all I can conclude is, well, #3 must be true.  Now we can't really hold it against you, after all we did allow you to buy it for $5 and some people simply have low moral standards.  I'm just surprised it's so many of you.

Will we change the pricing model?  Well I have two thoughts on that.

1) the current pricing includes zero support, so it's no "work" for us to just leave it as-is
2) moving it to fully open source might increase the number of people using it

I'm inclined to go with #1, simply because moving it to open source requires a bit of work on our part.  In short, we'll keep it out there as an apparent $5 product because I'm too busy to do anything else with it, but the likelihood of it getting any additional features is pretty slim.  It was an experiment that yielded data, and as such I'd say that it was valuable.  It certainly shows that it's a pricing model that can't be used to support a business.

11/6/2008 10:13:06 AM (Eastern Standard Time, UTC-05:00)  #    Comments [6]  | 
 Monday, October 20, 2008
As with most developers, I have a large pool of code snippets from work, tests and investigations I've done in the past.  Sometimes these grow into products, other times they just sit on a hard drive waiting for a time for me to actually use them.

Over the coming weeks I plan to blow the dust off of some of these and see what looks worthwhile.  Simple stuff might get rolled into the SDF (I rolled in a keyboard hook a couple weeks ago) and some stuff will get pushed out as open source libraries because we simply don't need any more products generating any more support load on us at the moment.

Hopefully we'll come up with a coherent place to put all of these in the near future, but for now I'll just post them here on my blog.

The first is the series is called POOMHelper.  I put this together back when WinMo 5.0 first came out while doing work that required that I detect when a POOM item was modified or deleted.  It's a fairly straightforward piece of code that hooks into the eventing mechanism of POOM, plus it also allows you to set some properties of POOM items (mostly Contacts IIRC) that didn't exist in the POOM object model at the time (they may have since been added).

Here's what the public object model looks like:

POOMHelper.PNG

The code is released under our MIT X11 license, so do with it what you wish.  If you need support or help, we offer consulting services.

Download the POOMHelper source here
Download the Interop.PocketOutlook.dll assemly(64 KB) here.
10/20/2008 10:44:21 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 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 [1]  | 
 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]  | 
 Wednesday, September 03, 2008
Here's another quick peek at an upcoming class in the next release of the SDF:

Object Model

keyhook2.PNG

Usage

    private KeyboardHook m_keyHook;
    
    public Form1()
    {
      m_keyHook = new KeyboardHook();
      m_keyHook.KeyDetected += OnKeyDetected;
      m_keyHook.Enabled = true;
    }

    void OnKeyDetected(OpenNETCF.Win32.WM keyMessage, KeyData keyData)
    {
      // Do Stuff
    } 


Sample App in the SDF

keyhook1.PNG

9/3/2008 1:40:08 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [3]  | 
 Thursday, August 28, 2008
This afternoon I learned of a few APIs that I was completely unaware of (that would have made my life a lot easier on some earlier projects).  3 hours later, We have this fully implemented:

radios.PNG

Usage looks like this:

Radios radios = Radios.GetRadios();

Debug.WriteLine("\nBefore\r\n--------");
foreach (IRadio radio in radios)
{
  Debug.WriteLine(string.Format("Name: {0}, Type: {1}, State: {2}", radio.DeviceName, radio.RadioType.ToString(), radio.RadioState.ToString()));

  // toggle all radio states
  radio.RadioState = (radio.RadioState == RadioState.On) ? RadioState.Off : RadioState.On;
}

// give the radios enough time to change state - some (like BT) seem to be slow
Thread.Sleep(1000);

radios.Refresh();

// display again
Debug.WriteLine("\r\nAfter\r\n--------");
foreach (IRadio radio in radios)
{
  Debug.WriteLine(string.Format("Name: {0}, Type: {1}, State: {2}", radio.DeviceName, radio.RadioType.ToString(), radio.RadioState.ToString()));
}
Debug.WriteLine("\r\n\n");
Thread.Sleep(100);


The only down side is that they are WinMo 5.0 and later only, so sorry CE devs.

I'm going to add it to the SDF source tree so it will be in the next release (soon, I promise).

8/28/2008 5:25:21 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 
 Thursday, August 14, 2008
If you're using the sample code from Alex Feinman's MSDN article on hosting ActiveX controls, then you might be interested to know that we've found and fixed a bug in it.  The original code doesn't properly clean up and destroy the native control instances, so the native destructor is never called and you leak objects.  For many things like Media Player, where you create one control and use it for the life of your app it's not much of a problem, but if you're creating and disposing a lot of controls in your app, it is a problem.

The fixed file is available here [AxHost.zip (7.89 KB)]
8/14/2008 10:40:38 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 
 Wednesday, August 13, 2008
Today I saw two separate posts on pretty much the same question.  How can you determine if the foreground window changes in a WinMo application?  Moreover, how can you determine if the new foreground window is your own, or in some other process?  My initial thoughts were to do some work in the Form's Deactivate event, but that would lead to having to plumb it into every Form, and then you'd still need special case handlers for MessageBoxes and Dialogs and it would be an unmaintainable pain in the ass. I decided to put some time aside this afternoon and see if I could come up with a better solution, and what I came up with is outlined in a new article entitled 'Determining Form and Process Changes in Windows CE'.

8/13/2008 6:14:58 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Tuesday, August 12, 2008
We've been getting a lot of inquiries lately about our plans for releasing the SDF built for Visual Studio 2008 and against CF 3.5, so I'll lay out our current status and short-term plan.

I realize we're a bit late in releasing a version for Studio '08.  Now you might say to yourself "how hard can it be?  Just open the solution in Studio '08, let it upgrade, recompile and release."  Sure, it could be that simple if we wer content with just tossing it out there, but we're not.  With the move to Studio '08 we decided to take advantage of some of the new tools we have.  First we migrated the entire SDF source tree from Vault to TFS.  Vault worked just fine, but we wanted to take advantage of TFS and integrate both continuous integration, automated testing and test-driven development into the product. 

That mean rearchitecting the solution and projecy layouts and then writing tests.  Lots of tests.  Of course writing tests leads to finding bugs, which then leads to fixing bugs.  We started by looking at reported bugs but also looking at some use cases and testing classes we know get the most use.  We have no delusion that we're going to have even close to full code coverage (or even 50%) by our next release, but we want to get off on the right foot and at least have some coverage for the next release.

Of course we've also added some new features like the OpenNETCF.Net.Mail namespace and all of this takes time.  As of right now we have less than 40 hours of test writing left to hit our release milestone.  Once we hit that, we then have to build the Help and installation package and release.  My hope is to have something ready in early September, but that's not a guarantee.  We know you want the release - we do - we just want to make sure it's right.

An ancillary question that also comes up is "when will we be releasing a version compiled for CF 3.5?"  As of right now we have no plans to release a CF 3.5-targeted version of the SDF.  Yes, you read that right.  We have no plan for a CF 3.5 release.  "Why is that?" you might ask, after all CF 3.5 is the latest and greatest, right?  Sure, it is, and we think that when possible you should use it.  However the SDF has historically been used by developers using older versions of the CF and is already rolled out in a *lot* of CF 2.0 projects.  If we moved to 3.5, none of those 2.0 project would be able to use the SDF without recompiling themselves.  If we moved to 3.5, then we'd also be tempted to use 3.5 features, which would then even make the source incompatible with 2.0 and a recompile wouldn't even be an option. I, for one, don't really want to leave all of those CF 2.0 developer's high and dry. 

Since CF 3.5 assemblies are unusable in CF 2.0 projects, but CF 2.0 assemblies can be used without a problem in CF 3.5 applications it makes the decision pretty simple.  If we stay with CF 2.0 as a target, then far more people can use the library.  If you absolutely must have it built targeting CF 3.5, you can always recompile the source yourself.

8/12/2008 12:03:11 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 
 Thursday, August 07, 2008
    Microsoft has released SQL Server Compact 3.5 SP1, providing support for the ADO.NET Entity Framework and 64-bit desktops.

8/7/2008 9:40:05 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Friday, August 01, 2008
It seems that every time I work on a project I get near the end and have to deal with actual deployment of the application and things go south.  Let's face it, Microsoft's wceload application sucks - and that's being generous. It's limited, it's got no object model, and it's behavior has changed over time without any of those changes being documented.

In a recent project I was trying to silently install an application to a directory that would change depending on the target hardware becasue different devices have their storage media named differently.  I wanted to do this without changing or having multiple CAB files, since the application was no different. Achieving this with wceload, I am convinced, is utterly impossible so I put on my reverse-engineering hat, downloaded the CAB spec (cabfmt.doc), and went to work.  Now, a few month later, and with the help of Alex Feinman, we've created a new product called the Windows CE CAB Installer SDK.  In addition to a new product, we went with a new pricing model as well.

The SDK comes with full source,  unit and integration tests designed for running under mstest, samples for generating compressed and uncompressed CAB (the SDK supports both), a template for creating custom installer DLLs and both VB and C# examples of using the SDK.

The main workhorse of the SDK is the WinCEInstallerFIle class, which looks like this:



An example of a custom installer looks like this (just to give you a flavor of how it works):

using System;

using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using OpenNETCF.Compression.CAB;

namespace System.Runtime.CompilerServices
{
public class ExtensionAttribute : Attribute
{
}
}

namespace ONCFInstall
{
public delegate void FileProgressHandler(int progressPercent);

public static class Extensions
{
public static string Find(this List list, string findString)
{
foreach (string file in list)
{
if (string.Compare(file, findString, true) == 0)
{
return file;
}
}
return null;
}
}

public class CustomCABInstaller : WinCEInstallerFile
{
private int m_fileCount = 0;
private CommandLineArgs m_args;

public event FileProgressHandler FileProgress;

public CustomCABInstaller(string cabFileName, CommandLineArgs args)
: base(cabFileName)
{
m_args = args;
SkipFileNames = m_args.SkipFiles ?? new List();
PathStringReplacements = m_args.PathStringReplacements ?? new Dictionary();
SkipOSVersionCheck = m_args.SkipOSVersionCheck;
}

/// /// List of file names to skip during installation /// public List SkipFileNames { get; set; }

/// /// List of path replacement strings /// public Dictionary PathStringReplacements { get; set; }

/// /// If true, the installer will not check to ensure the target meets the installer's version requirements
///
public bool SkipOSVersionCheck { get; set; } public override void OnInstallBegin() { m_fileCount = 0; } public override void OnTargetOSVersionCheck() { // check to see if we should skip the OS version check if (!SkipOSVersionCheck) { base.OnTargetOSVersionCheck(); } } public override void OnInstallFile(ref FileInstallInfo fileInfo, out bool skipped) { // check to see if it's a name we should skip if (SkipFileNames.Find(fileInfo.FileName) != null) { Utility.Output(string.Format("Skipping file '{0}'", fileInfo.FileName)); skipped = true; return; } // do any path replacements foreach (KeyValuePair val in PathStringReplacements)
{
fileInfo.DestinationFolder = fileInfo.DestinationFolder.Replace(val.Key, val.Value);
}

Utility.Output(string.Format("Installing '{0}' to '{1}'", fileInfo.FileName, fileInfo.DestinationFolder));

base.OnInstallFile(ref fileInfo, out skipped);

if (FileProgress != null)
{
FileProgress((++m_fileCount * 100) / FileCount);
}
}
}
}

8/1/2008 3:42:52 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [3]  | 
Today OpenNETCF announces a new pricing model that is going to be somewhat of a social experiment.  Our latest product - the Windows CE CAB Installer SDK - is being released under a "value-based" pricing (VBP) model.  In this model we're letting the customer determine how much they pay for a product based on how much value they feel it provides them.

The general idea is that we believe that most developers are honest, understand the value of time and have varying perceptions on the value of a software package.  For example a college student writing some quick utility for her personal use might find that the product saved a little time, but that she doesn't have a whole lot of expendable cash.  To her, $10 may be a reasonable representation of the value that our product brought to the solution.  On the other hand a developer working on an enterprise solution might find that the product saved his team several days of internal development and testing.  He knows the cost of his developers' time and the opportunity cost of letting them work on other product features instead of implementing what our product does.  For him $1,000 is a reasonable value.

In both cases we agree.  Software certainly doesn't always hold the same value to everyone.  We've all purchased software that we use a lot, and we feel that it was a great deal for what we paid for it.  We've all also bought software that maybe got used once or twice and that we know wasn't worth what we paid for it. Since the only person that can reasonably determine the value of the software is the customer themself, we've decided to let them pay based on their perceived value of the software.  Instead of purchasing the product, or some quantity of the product, customers will instead purchase a quantity of $5 "value units."  The number of units they purchase is completely up to them.

So now the college student can pay $10 for the exact same software that some company might pay $1,000 or more for.  If you're unsure if the software will meet your needs you can pay a small amount to give it a try - after all there is value in the effort alone.  If you determine that it does solve a problem for you and indeed does have value, you can always come back and purchase more value units.  What about bug fixes and upgrades?  New features and fixes are added value, so simply come back and purchase the number of value units that you feel represent the feature or fix.

Sure, I suspect that there will be a few people who take advantage of the model.  People who know that it saved them days of work but decide to pay in only $5.  The hope, however, is that those people will be the exception.  Software is never complete - there are always more features to add and bugs to fix.  What drives our ability to add those features and make those fixes is cash flow. We feel that if most people pay what they truly feel is the value of the product, then it will provide us enough revenue to continue working on it, and if the model works for this product, it may well spread and be applied to some of our other products as well.

8/1/2008 3:00:38 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Wednesday, June 04, 2008

We've been working for a couple months now at migrating some of our projects from our older SourceGear Vault source code repository to Microsoft's Team Foundation Server.  I've been perfectly happy with Vault - it's a great, inexpensive code repository - but we wanted to start doing test driven design and adding continuous integration into the mix.  One would think that since that's what TFS is all about that this would be pretty simple, right?  Hardly.

TFS may work out of the box for desktop developers, but when it comes to doing device work you quickly end up in a tar pit of problems, which is only compounded when you're new to TFS to begin with.  In my opinion a root of these problems is that the Visual Studio IDE is not using msbuild.exe and mstest.exe for doing device project builds and tests.  This is evidenced by the fact that you can create a device project with unit tests and they all run just happily from the IDE, but if you open a command windows and use msbuild with your solution it fails miserably.

I consider this a major failure on the part of the Visual Studio for Devices (VSD) team.  You see TFS doesn't launch Studio to build your solution, it uses msbuild.  So to just get the solution to compile you have to learn how TFS works and make modifications.  Oh, and once it's compiling that certainly doesn't mean that unit tests will actually run.  We've hit several snags along the way on that too.

Fortunately for us, I have a good friend and long-time colleague, Tim Bassett, who is big into CI and TDD, has a lot of experience with TFS and is considering hanging out his own shingle. He just has no experience doing device development (well not really any since the days of eVB when we worked together).  So we formed a kind of symbiotic relationship with him helping us getting our server working and running and us helping him develop some products that will benefit any other hapless suckers who think that maybe automating builds and testing for device development might be useful.  If you're in that boat, take a look at what he's got. Ask him questions.  Help him help us improve the experience of using TFS for device work. And if you ever have the ear of anyone on the VSD or CF team, tell them they should be dogfooding this stuff so they can feel our pain and that yes, unit tests for native code *is* necessary.

 

6/4/2008 11:36:47 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Tuesday, June 03, 2008

For some time now we've offered "support and maintenance" as an option when purchasing our flagship Smart Device Framework product.  If I recall correctly, the price is something like $240 for a year.  In the entire time we've offered it I can probably count the number of sales of that option on one hand - it's way less than 1% of the people who bought the extensions.

I had a call from a prospective customer asking about the feature and it clued me in to what most people's thought process is.  He asked "why should I buy maintenance for $240 when I can just buy each new version for $50.  It's less expensive to just re-buy."

Ah, on the surface that may seem true, and like a good deal.  But here's the value in buying maintenance:

Right now we're in the process of moving the SDF to Studio 2008.  In the process we're also moving to using Team Foundation Server as a back end and integrating unit testing and continuious integration.  In this process we're shaking out quite a few bugs (take a look at our online bug database to see what we're up to).  People with maintenance agreements can request the source for these fixes at any time.  They also get source drops for all service packs (no one else does).  Those without maintenance must wait until the next release or service pack to take advantage of these fixes.

There is one exception to this rule.  We feel that if you've gone through the task of finding it and reporting it, we owe you so if you are the first to find and report a bug, we'll provide you the fix immediately upon fixing it. 

6/3/2008 12:48:33 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 
 Thursday, May 29, 2008

We're just about to release a new version of Padarn, out ASP.NET Web Server for Windows CE.  This version brings SSL support as well as Basic and Digest authentication.  Another part of this release was trying to keep the footprint reasonably small.  Here's a screen shot of all of the assemblies needed for the entire engine implementation:

padarnfootprint.PNG

So yes, we have an ASP.NET web server with SSL and authentication support and it's just 305KB - and half of that is for SSL alone.

 

5/29/2008 12:26:56 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Wednesday, May 28, 2008

Since the Compact Framework doesn't have support for App.Config file, we created our own implementation that follows the full framework model.  It requires that the config file be named “MyApp.exe.config” (which is how it works on the desktop) with a subset of the desktop functionality and it's worked well for some time.  Until recently that is.

Yesterday I set out to create some unit tests for some of our OpenNETCF.Rss namespace objects.  Well the FeedEngine object requires information from an app.config file on construction, so I figured it would be simple - I'd just add an app.config file to the test project and mark it as a DeploymentItem.  After a little investigation I found that the calling assembly for a device unit test is SmartDeviceTestHost.exe, which by default runs out of \Program Files\SmartDeviceTest on the target device.  That means that to use your own app config file from a unit test it would need to be named SmartDeviceTestHost.exe.config. 

Interestingly (or frustratingly, depending on when you asked me yesterday), this test host deploys *its own* version of a config file with the same name with some info on what framework it’s running against.  The test framework just heavy-handedly overwrites any existing file rather than merging its contents into the existing one, and it overwrites *after* it deploys all of the test pieces, so you can’t just merge its contents into your own app config and use it.

As a workaround I actually modified the OpenNETCF implementation for app config files.  I didn't really want to, but the only other solution I could think of was to write code that would open the MS-deployed version and do a manual merge in the unit  test code before the test is run, and that seemed like a much uglier route. The OpenNETCF Configuration implementation now looks for a file named MyApp.exe.config.unittest before looking for MyApp.exe.config and uses the "unittest"-suffixed version if it’s there.  I then modified my TestBase class (from which all of my unit tests derive) to add this:

 

 

        [TestInitialize]

        public virtual void TestInitialize()

        {

            CopyTestConfigFile();

        }

 

        private void CopyTestConfigFile()

        {

            // copy the config file to the test host folder

            string src = Path.Combine(TestContext.TestDeploymentDir, "SmartDeviceTestHost.exe.config");

            string dest = Path.Combine(TestHostFolder, "SmartDeviceTestHost.exe.config.unittest");

            if ((File.Exists(src)) && (!File.Exists(dest)))

            {

                File.Copy(src, dest);

            }

        }

 

        public string TestHostFolder

        {

            get

            {

                return  Path.GetDirectoryName(

                        Path.GetDirectoryName(

                        Path.GetDirectoryName(

                        Assembly.GetCallingAssembly().GetName().CodeBase)));

            }

        }

 

Now I simply add SmartDeviceTestHost.exe.config to the unit test project, mark it as a deployment item and voila - it works as expected.  Just how it should have yesterday morning when I set out to write a couple simple tests.

And for the record - the current "solution" for debugging device unit tests (which involves putting in a Debugger.Break() call in the unit test and then doing an "attach to process" from another instance of Studio) is an unweildy pain in the ass.  It takes no less than a minute just te get a unit test running and in a state that you can step through code.  That might not sound like a lot, but try this: put a breakpoint in your code and when the debugger hit is, wait a full minute before you step or look at the Locals window.  Now do this every time you want to debug.

 

5/28/2008 12:46:42 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 
 Tuesday, May 27, 2008

I don't use LINQ terribly often because it seems that most of the work I do is pre-CF 3.5.  It only creeps into my code in things like unit tests or test projects. 

This weekend I spent a little time updating my AudioTagR project and adding some tools and utilities to it.  One of the challenges I faced was let's assume I have two root directories that contain music files (they really can be any file, but this is a real-world example, so we'll use the exact scenario I had).  In each of these roots are folders for Artists.  In each of those are one or more folders that represent albums by that artist.

Now let's assume one of those root folders (Root A) is my "root" music collection and it contains a few thousand artists.  The other root folder (Root B) is a collection of music off my old hard drives, CDs, etc.  I know that there are duplicates between these folders - artists that exist in both as well as albums that exist in both.  I want to go through Root B and move only those albums to Root A if they don't already exist.  That means that I want to move all albums that aren't already in Root A, creating the containing Artist folder if it's there.  If the folder is there, I want to skip it and I'll manually check the list of songs later.

The traditional way to do this would be to write a recursive method that would look something like this pseudocode:

void Parse(sourcefolder, destfolder)
{
   foreach(directory in sourcefolder.directories)
   {
       if(! exists(destfolder[folder]))
       {
          Copy(sourcefolder, destfolder);
       }
       else
       {
           Parse(directory, destfolder + directory);
       }
   }
}

Well since this code really is nothing but a series of operations on a collection, LINQ immediately comes to mind as a good fit.

The first challenge I ran into is that my algorithm requires a list of directories contained in a parent directory.  Unfortunately Directory.GetDirectories returns a list with the fully qualified path and I just needed the name of the folder itself.  So I wrote a couple of extension methods that get purely a list of the directory names, not the paths.  I also added one for checking to see if a directory was empty (to be used later for cleanliness):


public static class IO
{
  public static bool IsEmptyDirectory(this string path)
  {
    return (Directory.GetDirectories(path).Count() + Directory.GetFiles(path).Count()) == 0;
  }

  public static string FolderNameWithoutPath(this string path)
  {
    return Path.GetFileName(path);
  }

  public static string[] GetFolderNamesWithoutPath(this string path)
  {
    return (from folder in Directory.GetDirectories(path)
        select folder.FolderNameWithoutPath()).ToArray();
  }
}

Once I had those, the resulting merge method was a simple iteration across a couple LINQ queries.

private void MergeFolders(string sourceFolder, string destinationFolder)
{
  // copy those in A but not in B
  foreach(string folder in
      sourceFolder.GetFolderNamesWithoutPath().Except(
      destinationFolder.GetFolderNamesWithoutPath(), StringComparer.InvariantCultureIgnoreCase))
  {
    Directory.Move(Path.Combine(sourceFolder, folder), Path.Combine(destinationFolder, folder));
  }

  // now go through those in both and repeat
  foreach (string folder in
      sourceFolder.GetFolderNamesWithoutPath().Intersect(
      destinationFolder.GetFolderNamesWithoutPath(), StringComparer.InvariantCultureIgnoreCase))
  {
    string src = Path.Combine(sourceFolder, folder);
    MergeFolders(src, Path.Combine(destinationFolder, folder));

    if (src.IsEmptyDirectory()) Directory.Delete(src);
  }
}

 

5/27/2008 10:33:04 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Monday, May 19, 2008
We've published a new article on the OpenNETCF Community Site titled "Native vs. Managed Code: GDI Performance"
 
 
In it, I look at the performance differences between native and managed code making GDI calls.
 
In case you missed them, our other recently published articles include:
 
- Performance Implications of Crossing the P/Invoke Boundary
- An Introduction to WCF for Device Developers
- Getting a Millisecond-Resolution DateTime under Windows CE
- Using GDI+ on Windows Mobile
- Sharing Windows Mobile Ink with the Desktop
- OpenNETCF Mobile Ink Library for Windows Mobile 6
- Improving Data Access Performance with Data Caching
- Developing Connected Smart Device Applications with sqlClient
- Debugging Without ActiveSync
- Image Manipulation in Windows Mobile 5.0
- Don't Fear the Garbage Collector
 
All of our articles are available online at:
http://community.OpenNETCF.com/articles
 
5/19/2008 12:27:16 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Friday, May 09, 2008
OpenNETCF is in the midst of some major infrastructure updates to get automated unit testing of device assemblies working with Team Foundation Server.  Let's just say that it's nowhere near a simple task.

As part of the work, we needed to automate access to the Device Emulator using the Microsoft-supplied Automation Interfaces.  Their C++ and WScript samples for usage leave a bit to be desired, so we wrapped them in an object-oriented model to provide at least a skeletal framework for launching, resetting, saving state etc.  The classes haven't been heavily tested and the test harness for the classes is very basic, but it's a good starting point, and the download (below) is what we're using in our continuous integration process, so it's definitely functional.



We're releasing this source under the MIT X-11 license.  If you make updates and want to submit them back to us, we'll be happy to integrate them, but it's not required.

Download the source and test harness here

5/9/2008 3:04:11 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 
 Monday, May 05, 2008

Finally!  Someone has created a code profiler for the .NET Compact Framework.  And what's even more exciting is that it's free.  I've not used it, so I can't vouch for how well it works, or how easy it is to use, but the fact it exists is quite promising.

5/5/2008 8:14:17 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 
 Friday, April 18, 2008

Ever since learning how to use function pointers in C, I've always been a fan of using them to help make code a bit more usable, especially when you've got a state machine.  Today, as I'm working on a Wizard UI for a desktop application I came across a typical scenario for using a function pointer.  Depending on the stage of the Wizard you're in, a button will have to do separate things.

That got me to thinking that most managed developers simply don't understand the power and utility of delegates, but instead simply consider them a necessity when using Control.Invoke or creating custom events. Sure, in my case I could have a switch statement in the click handler and do logic there, or I could unhook the click handler from one method and hook it to another, but those all seem ugly and a pain in the ass to me.  A simple function pointer change is all you need.  So I decided I'd throw together a really simple example of how you would use a delegate to change the behavior of a Button click.

Let's assume that we have a button that we want to click, and when it's clicked it will do one of 4 things, depending on the state of our application.  We'll just use a messagebox here to give you the idea - what it does is up to you- it's a function after all.

public void FunctionA()
{
  MessageBox.Show("FunctionA");
}

public void FunctionB()
{
  MessageBox.Show("FunctionB");
}

public void FunctionC()
{
  MessageBox.Show("FunctionC");
}

public void FunctionD()
{
  MessageBox.Show("FunctionD");
}

To simulate the different "states" I simply added a ListBox (called functionList) to the Form and manually added the function names to it in the Form's constructor.  Sure, I could have used Reflection to be clever and populate the list, but I'm tryiong to keep it simple and show delegates.

functionList.Items.Add("FunctionA");
functionList.Items.Add("FunctionB");
functionList.Items.Add("FunctionC");
functionList.Items.Add("FunctionD");

Alright, so now we know that depending on which item is selected, we want to call one of our four functions.  Since they all have the same interface (and they have to to use a delegate) we simply define a delegate that matches them.  This delegate can be privately scoped inside your class.

delegate void FunctionDelegate();

And then we create an instance variable to hold the current function pointer we want to use:

private FunctionDelegate m_functionPointer = null;

We add an event handler for the SelectedIndexChanged event of the ListBox (in the Form constructor)

functionList.SelectedIndexChanged += new EventHandler(functionList_SelectedIndexChanged);

And implement the event handler.  It simply looks at the newly selected index in the list and changes the value stored in m_functionPointer appropriately.

void functionList_SelectedIndexChanged(object sender, EventArgs e)
{
  // determine which function pointer to store based on selection
  switch (functionList.SelectedIndex)
  {
    case 0:
      m_functionPointer = FunctionA;
      break;
    case 1:
      m_functionPointer = FunctionB;
      break;
    case 2:
      m_functionPointer = FunctionC;
      break;
    case 3:
      m_functionPointer = FunctionD;
      break;
    default:
      m_functionPointer = null;
      break;
  }
}

Next we wire up an event handler for our button (again the the Form constructor):

callButton.Click += new EventHandler(callButton_Click);

And finally the magic and simplicity of the state-dependent call

void callButton_Click(object sender, EventArgs e)
{
  // call our function (as long as it's not null)
  if (m_functionPointer != null)
  {
    m_functionPointer();
  }
}

That's all there is to it.  Run the application, select a function and click the button.

Get the full source here.

4/18/2008 6:07:46 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [3]  | 
 Saturday, April 12, 2008

In a recent project we decided to migrate the application's data engine from SQL CE 3.1 to SQL CE 3.5.  The challenge with this is that the file formats themselves are not compatible and trying to connect to a 3.1 database usign the 3.5 objects will cause an Exception.  3.5 provides a method to upgrade 3.1 database files, but to use it you really need to know that the file is in 3.1 format to begin with.

Of course you could always put in logic to connect and catch the exception and then upgrade, but let's face it - using exceptions for flow control is a bad practice and any ime it can be avoided it should be. 

Surprisingly, the SQL CE team didn't give us any mechanism otherwise to determine a database file's version, so I put together the following (which should work on the desktop as well as the device):

internal enum SSCEVersion
{
    Unknown,
    v3_1,
    v3_5
}

internal static SSCEVersion GetDatabaseVersion(string path)
{
    uint signature = 0;

    using (FileStream stream = new FileStream(path, FileMode.Open))
    {
        using (BinaryReader reader = new BinaryReader(stream))
        {
            stream.Seek(16, SeekOrigin.Begin);

            signature = reader.ReadUInt32();
        }
    }

    switch (signature)
    {
        case 0x00357b9d: // 3.5
            return SSCEVersion.v3_5;
        case 0x002dd714: // 3.1
            return SSCEVersion.v3_1;
        default:
            return SSCEVersion.Unknown;
    }
}

4/12/2008 3:11:20 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Thursday, March 06, 2008
If you use Visual Studio 2008, do yourself a huge favor and install the PowerCommands.

3/6/2008 10:32:55 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Friday, February 15, 2008

So, you want network statistics for your Windows CE or Windows Mobile device?  I think the new Smart Device Framework 2.2's NetworkInformation namespace might just give you what you're after.  It's got almost everything the desktop gives, plus we even provide access to the ARP and routing tables (something you *don't* get on the desktop).  Here's a complete sample on getting the supported statistics:

IPGlobalProperties globalProps = IPGlobalProperties.GetIPGlobalProperties();
Debug.WriteLine("\r\nGlobal IP Properties\r\n====================");
Debug.WriteLine(string.Format(" Host Name: {0}", globalProps.HostName));
Debug.WriteLine(string.Format(" Domain Name: {0}", globalProps.DomainName));
Debug.WriteLine(string.Format(" DHCP Scope Name: {0}", globalProps.DhcpScopeName));

IPGlobalStatistics globalStatsv4 = globalProps.GetIPv4GlobalStatistics();
Debug.WriteLine("\r\nGlobal IPv4 Statistics\r\n====================");
Debug.WriteLine(string.Format(" Packets Received: {0}", globalStatsv4.ReceivedPackets));
Debug.WriteLine(string.Format(" Packets Delivered: {0}", globalStatsv4.ReceivedPacketsDelivered));
Debug.WriteLine(string.Format(" Packets Discarded: {0}", globalStatsv4.ReceivedPacketsDiscarded));
Debug.WriteLine(string.Format(" Packets Forwarded: {0}", globalStatsv4.ReceivedPacketsForwarded));
Debug.WriteLine(string.Format(" Packets Received w/ Address Errors: {0}", globalStatsv4.ReceivedPacketsWithAddressErrors));
Debug.WriteLine(string.Format(" Packets Received w/ Header Errors: {0}", globalStatsv4.ReceivedPacketsWithHeadersErrors));
Debug.WriteLine(string.Format(" Packets Received w/ Protocol Errors: {0}", globalStatsv4.ReceivedPacketsWithUnknownProtocol));
Debug.WriteLine(string.Format(" Default TTL: {0}", globalStatsv4.DefaultTtl));
Debug.WriteLine(string.Format(" Forwarding {0} Enabled", globalStatsv4.ForwardingEnabled ? "IS" : "IS NOT"));
Debug.WriteLine(string.Format(" Interfaces: {0}", globalStatsv4.NumberOfInterfaces));
Debug.WriteLine(string.Format(" IP Addresses: {0}", globalStatsv4.NumberOfIPAddresses));
Debug.WriteLine(string.Format(" Routes: {0}", globalStatsv4.NumberOfRoutes));
Debug.WriteLine(string.Format(" Output Packet Requests: {0}", globalStatsv4.OutputPacketRequests));
Debug.WriteLine(string.Format(" Output Routing Discards: {0}", globalStatsv4.OutputPacketRoutingDiscards));
Debug.WriteLine(string.Format(" Output Packet Discards: {0}", globalStatsv4.OutputPacketsDiscarded));
Debug.WriteLine(string.Format(" Output Packets w/o Route: {0}", globalStatsv4.OutputPacketsWithNoRoute));
Debug.WriteLine(string.Format(" Fragmented Packets: {0}", globalStatsv4.PacketsFragmented));
Debug.WriteLine(string.Format(" Fragment Failures: {0}", globalStatsv4.PacketFragmentFailures));
Debug.WriteLine(string.Format(" Reassemblies Required: {0}", globalStatsv4.PacketReassembliesRequired));
Debug.WriteLine(string.Format(" Reassemblies Succeeded: {0}", globalStatsv4.PacketsReassembled));
Debug.WriteLine(string.Format(" Reassembly Failures: {0}", globalStatsv4.PacketReassemblyFailures));
Debug.WriteLine(string.Format(" Reassembly Timeouts: {0}", globalStatsv4.PacketReassemblyTimeout));

#region --- IPv6 stats ---
try
{
    Debug.WriteLine("\r\nGlobal IPv6 Statistics\r\n====================");
    IPGlobalStatistics globalStatsv6 = globalProps.GetIPv6GlobalStatistics();
    Debug.WriteLine(string.Format(" Packets Received: {0}", globalStatsv6.ReceivedPackets));
    Debug.WriteLine(string.Format(" Packets Delivered: {0}", globalStatsv6.ReceivedPacketsDelivered));
    Debug.WriteLine(string.Format(" Packets Discarded: {0}", globalStatsv6.ReceivedPacketsDiscarded));
    Debug.WriteLine(string.Format(" Packets Forwarded: {0}", globalStatsv6.ReceivedPacketsForwarded));
    Debug.WriteLine(string.Format(" Packets Received w/ Address Errors: {0}", globalStatsv6.ReceivedPacketsWithAddressErrors));
    Debug.WriteLine(string.Format(" Packets Received w/ Header Errors: {0}", globalStatsv6.ReceivedPacketsWithHeadersErrors));
    Debug.WriteLine(string.Format(" Packets Received w/ Protocol Errors: {0}", globalStatsv6.ReceivedPacketsWithUnknownProtocol));
    Debug.WriteLine(string.Format(" Default TTL: {0}", globalStatsv6.DefaultTtl));
    Debug.WriteLine(string.Format(" Forwarding {0} Enabled", globalStatsv6.ForwardingEnabled ? "IS" : "IS NOT"));
    Debug.WriteLine(string.Format(" Interfaces: {0}", globalStatsv6.NumberOfInterfaces));
    Debug.WriteLine(string.Format(" IP Addresses: {0}", globalStatsv6.NumberOfIPAddresses));
    Debug.WriteLine(string.Format(" Routes: {0}", globalStatsv6.NumberOfRoutes));
    Debug.WriteLine(string.Format(" Output Packet Requests: {0}", globalStatsv6.OutputPacketRequests));
    Debug.WriteLine(string.Format(" Output Routing Discards: {0}", globalStatsv6.OutputPacketRoutingDiscards));
    Debug.WriteLine(string.Format(" Output Packet Discards: {0}", globalStatsv6.OutputPacketsDiscarded));
    Debug.WriteLine(string.Format(" Output Packets w/o Route: {0}", globalStatsv6.OutputPacketsWithNoRoute));
    Debug.WriteLine(string.Format(" Fragmented Packets: {0}", globalStatsv6.PacketsFragmented));
    Debug.WriteLine(string.Format(" Fragment Failures: {0}", globalStatsv6.PacketFragmentFailures));
    Debug.WriteLine(string.Format(" Reassemblies Required: {0}", globalStatsv6.PacketReassembliesRequired));
    Debug.WriteLine(string.Format(" Reassemblies Succeeded: {0}", globalStatsv6.PacketsReassembled));
    Debug.WriteLine(string.Format(" Reassembly Failures: {0}", globalStatsv6.PacketReassemblyFailures));
    Debug.WriteLine(string.Format(" Reassembly Timeouts: {0}", globalStatsv6.PacketReassemblyTimeout));
    Debug.WriteLine(string.Format(" Packets Received: {0}", globalStatsv6.ReceivedPackets));
}
catch (PlatformNotSupportedException)
{
    Debug.WriteLine(" IPv6 Not supported");
}
#endregion

#region --- Icmpv4 stats ---
IcmpV4Statistics icmpstats = globalProps.GetIcmpV4Statistics();
Debug.WriteLine("\r\nICMPv4 Statistics\r\n====================");
Debug.WriteLine(string.Format(" Address Mask Requests Received: {0}", icmpstats.AddressMaskRequestsReceived));
Debug.WriteLine(string.Format(" Address Mask Requests Sent: {0}", icmpstats.AddressMaskRequestsSent));
Debug.WriteLine(string.Format(" Address Mask Replies Received: {0}", icmpstats.AddressMaskRepliesReceived));
Debug.WriteLine(string.Format(" Address Mask Replies Sent: {0}", icmpstats.AddressMaskRepliesSent));
Debug.WriteLine(string.Format(" Dest Unreachable Messages Received: {0}", icmpstats.DestinationUnreachableMessagesReceived));
Debug.WriteLine(string.Format(" Dest Unreachable Messages Sent: {0}", icmpstats.DestinationUnreachableMessagesSent));
Debug.WriteLine(string.Format(" Echo Replies Received: {0}", icmpstats.EchoRepliesReceived));
Debug.WriteLine(string.Format(" Echo Replies Sent: {0}", icmpstats.EchoRepliesSent));
Debug.WriteLine(string.Format(" Echo Requests Received: {0}", icmpstats.EchoRequestsReceived));
Debug.WriteLine(string.Format(" Echo Requests Sent: {0}", icmpstats.EchoRequestsSent));
Debug.WriteLine(string.Format(" Errors Received: {0}", icmpstats.ErrorsReceived));
Debug.WriteLine(string.Format(" Errors Sent: {0}", icmpstats.ErrorsSent));
Debug.WriteLine(string.Format(" Messages Received: {0}", icmpstats.MessagesReceived));
Debug.WriteLine(string.Format(" Messages Sent: {0}", icmpstats.MessagesSent));
Debug.WriteLine(string.Format(" Parameter Problems Received: {0}", icmpstats.ParameterProblemsReceived));
Debug.WriteLine(string.Format(" Parameter Problems Sent: {0}", icmpstats.ParameterProblemsSent));
Debug.WriteLine(string.Format(" Redirects Received: {0}", icmpstats.RedirectsReceived));
Debug.WriteLine(string.Format(" Redirects Sent: {0}", icmpstats.RedirectsSent));
Debug.WriteLine(string.Format(" Source Quenches Received: {0}", icmpstats.SourceQuenchesReceived));
Debug.WriteLine(string.Format(" Source Quenches Sent: {0}", icmpstats.SourceQuenchesSent));
Debug.WriteLine(string.Format(" Time Exceeded Messages Received: {0}", icmpstats.TimeExceededMessagesReceived));
Debug.WriteLine(string.Format(" Time Exceeded Messages Sent: {0}", icmpstats.TimeExceededMessagesSent));
Debug.WriteLine(string.Format(" Timestamp Replies Received: {0}", icmpstats.TimestampRepliesReceived));
Debug.WriteLine(string.Format(" Timestamp Replies Sent: {0}", icmpstats.TimestampRepliesSent));
Debug.WriteLine(string.Format(" Timestamp Requests Received: {0}", icmpstats.TimestampRequestsReceived));
Debug.WriteLine(string.Format(" Timestamp Requests Sent: {0}", icmpstats.TimestampRequestsSent));
#endregion

#region --- TCPIPv4 stats ---
TcpStatistics tcpstats = globalProps.GetTcpIPv4Statistics();
Debug.WriteLine("\r\nTCP/IP v4 Statistics\r\n====================");
Debug.WriteLine(string.Format(" Connections Accepted: {0}", tcpstats.ConnectionsAccepted));
Debug.WriteLine(string.Format(" Connections Initiated: {0}", tcpstats.ConnectionsInitiated));
Debug.WriteLine(string.Format(" Cumulative Connections: {0}", tcpstats.CumulativeConnections));
Debug.WriteLine(string.Format(" Current Connections: {0}", tcpstats.CurrentConnections));
Debug.WriteLine(string.Format(" Errors Received: {0}", tcpstats.ErrorsReceived));
Debug.WriteLine(string.Format(" Failed Connection Attempts: {0}", tcpstats.FailedConnectionAttempts));
Debug.WriteLine(string.Format(" Maximum Connections: {0}", tcpstats.MaximumConnections));
Debug.WriteLine(string.Format(" Max Transmission Timeout: {0}", tcpstats.MaximumTransmissionTimeout));
Debug.WriteLine(string.Format(" Min Transmission Timeout: {0}", tcpstats.MinimumTransmissionTimeout));
Debug.WriteLine(string.Format(" Reset Connections: {0}", tcpstats.ResetConnections));
Debug.WriteLine(string.Format(" Resets Sent: {0}", tcpstats.ResetsSent));
Debug.WriteLine(string.Format(" Segments Received: {0}", tcpstats.SegmentsReceived));
Debug.WriteLine(string.Format(" Segments Resent: {0}", tcpstats.SegmentsResent));
Debug.WriteLine(string.Format(" Connections Sent: {0}", tcpstats.SegmentsSent));
#endregion

#region --- TCPIPv6 stats ---
Debug.WriteLine("\r\nTCP/IP v6 Statistics\r\n====================");
try
{
    tcpstats = globalProps.GetTcpIPv6Statistics();
    Debug.WriteLine(string.Format(" Connections Accepted: {0}", tcpstats.ConnectionsAccepted));
    Debug.WriteLine(string.Format(" Connections Initiated: {0}", tcpstats.ConnectionsInitiated));
    Debug.WriteLine(string.Format(" Cumulative Connections: {0}", tcpstats.CumulativeConnections));
    Debug.WriteLine(string.Format(" Current Connections: {0}", tcpstats.CurrentConnections));
    Debug.WriteLine(string.Format(" Errors Received: {0}", tcpstats.ErrorsReceived));
    Debug.WriteLine(string.Format(" Failed Connection Attempts: {0}", tcpstats.FailedConnectionAttempts));
    Debug.WriteLine(string.Format(" Maximum Connections: {0}", tcpstats.MaximumConnections));
    Debug.WriteLine(string.Format(" Max Transmission Timeout: {0}", tcpstats.MaximumTransmissionTimeout));
    Debug.WriteLine(string.Format(" Min Transmission Timeout: {0}", tcpstats.MinimumTransmissionTimeout));
    Debug.WriteLine(string.Format(" Reset Connections: {0}", tcpstats.ResetConnections));
    Debug.WriteLine(string.Format(" Resets Sent: {0}", tcpstats.ResetsSent));
    Debug.WriteLine(string.Format(" Segments Received: {0}", tcpstats.SegmentsReceived));
    Debug.WriteLine(string.Format(" Segments Resent: {0}", tcpstats.SegmentsResent));
    Debug.WriteLine(string.Format(" Connections Sent: {0}", tcpstats.SegmentsSent));
}
catch (PlatformNotSupportedException)
{
    Debug.WriteLine(" IPv6 Not supported");
}
#endregion

#region --- UDPv4 stats ---
UdpStatistics udpstats = globalProps.GetUdpIPv4Statistics();
Debug.WriteLine("\r\nUDP v4 Statistics\r\n====================");
Debug.WriteLine(string.Format(" UDP Listeners: {0}", udpstats.UdpListeners));
Debug.WriteLine(string.Format(" Datagrams Received: {0}", udpstats.DatagramsReceived));
Debug.WriteLine(string.Format(" Datagrams Sent: {0}", udpstats.DatagramsSent));
Debug.WriteLine(string.Format(" Datagrams Discarded: {0}", udpstats.IncomingDatagramsDiscarded));
Debug.WriteLine(string.Format(" Datagrams with Errors: {0}", udpstats.IncomingDatagramsWithErrors));
#endregion

#region --- UDPv6 stats ---
Debug.WriteLine("\r\nUDP v6 Statistics\r\n====================");
try
{
    udpstats = globalProps.GetUdpIPv6Statistics();
    Debug.WriteLine(string.Format(" UDP Listeners: {0}", udpstats.UdpListeners));
    Debug.WriteLine(string.Format(" Datagrams Received: {0}", udpstats.DatagramsReceived));
    Debug.WriteLine(string.Format(" Datagrams Sent: {0}", udpstats.DatagramsSent));
    Debug.WriteLine(string.Format(" Datagrams Discarded: {0}", udpstats.IncomingDatagramsDiscarded));
    Debug.WriteLine(string.Format(" Datagrams with Errors: {0}", udpstats.IncomingDatagramsWithErrors));
}
catch (PlatformNotSupportedException)
{
    Debug.WriteLine(" IPv6 Not supported");
}
#endregion

ArpTable table = ArpTable.GetArpTable();
if (table.Count > 0)
{
    Debug.WriteLine("\r\n*ARP Table\r\n====================");
    Debug.WriteLine(string.Format("{0}{1}{2}{3}",
   
"[Interface]".PadRight(16),
    "[Type]".PadLeft(10),
    "[IP Address]".PadLeft(17),
    "[MAC Address]".PadLeft(22)));
    foreach (ArpEntry entry in table)
    {
        Debug.WriteLine(string.Format("{0}{1}{2}{3}"
            entry.NetworkInterface.Name.PadRight(16), 
            entry.ArpEntryType.ToString().PadLeft(10), 
            entry.IPAddress.ToString().PadLeft(17), 
            entry.PhysicalAddress.ToString().PadLeft(22)));
    }
}
else
{
    Debug.WriteLine(" No ARP table entries");
}

IPRoutingTable routingTable = IPRoutingTable.GetRoutingTable();
if(routingTable.Count > 0)
{
    Debug.WriteLine("\r\n*IP Routing Table\r\n====================");
    Debug.WriteLine(string.Format("{0}{1}{2}{3}{4}",
        "[Interface]".PadRight(16),
        "[Type]".PadRight(12),
        "[Destination]".PadLeft(17),
        "[NetMask]".PadLeft(17),
        "[Next Hop]".PadLeft(17)));
    foreach (IPForwardEntry entry in routingTable)
    {
        string intfname = entry.NetworkInterface == null ? intfname = "{Loopback}" : entry.NetworkInterface.Name;
        Debug.WriteLine(string.Format("{0}{1}{2}{3}{4}",
        intfname.PadRight(16),
        entry.RouteType.ToString().PadRight(12),
        entry.Destination.ToString().PadLeft(17),
        entry.SubnetMask.ToString().PadLeft(17),
        entry.NextHop.ToString().PadLeft(17))); 
    }
}
else
{
    Debug.WriteLine(" No IP Routing table entries");
}

TcpConnectionInformation[] connections = globalProps.GetActiveTcpConnections();
if (connections.Length > 0)
{
    Debug.WriteLine("\r\nActive TCP Connections\r\n====================");
    Debug.WriteLine(string.Format("{0}:{1}<---->{2}{3}[{4}]",
        "[local IP]".PadLeft(16),
        "[port]".PadRight(6),
        "[remote IP]".PadLeft(16),
        "[port]".PadRight(6),
        "[state]"));
    foreach (TcpConnectionInformation info in connections)
    {
        Debug.WriteLine(string.Format("{0}:{1}<---->{2}:{3}[{4}]",
        info.LocalEndPoint.Address.ToString().PadLeft(16),
        info.LocalEndPoint.Port.ToString().PadRight(6),
        info.RemoteEndPoint.Address.ToString().PadLeft(16),
        info.RemoteEndPoint.Port.ToString().PadRight(6),
        info.State.ToString()));
    }
}
else
{
    Debug.WriteLine(" No active TCP connections");
}

2/15/2008 11:57:23 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 

Smart Device Framework 2.2 refactored a lot of our network interface classes.  Yes, it's likely to casue some angst with people that used the older stuff, but it really is for the best.  I never liked the older mechanism that exposed things like SSID for network interfaces that weren't even wireless.  The new model is much friendlier and object-oriented.  Here's a quick (and very thorough) example of how to use the new classes.  As you can see, there's *nothing* to be known about the interface that isn't exposed.

NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
WirelessZeroConfigNetworkInterface wzcInterface = null;

foreach (NetworkInterface ni in interfaces)
{
    Debug.WriteLine(string.Format("===========\r\nInterface {0}\r\n===========", ni.Id));
    Debug.WriteLine(string.Format(" Type: {0}", ni.GetType().Name));
    Debug.WriteLine(string.Format(