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 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]  | 
 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]  | 
 Friday, June 05, 2009
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]  | 
 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]  | 
 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]  | 
 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]  | 
 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]  | 
 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]  | 
 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]  | 
 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]  | 
 Saturday, January 26, 2008
If you follow my blog much, then you know performance is a bit of an interest of mine.  Well here's one that pretty much pulls the pants down on the CF loader.  Nothing like a 3 second load time for some classes.  Admittedly it's an edge case and there seems to be some wacky logic in the repro code, but it's still pretty interesting. And kudos to Noah for looking into it, finding the problem and at least explaining why it's happening and how to mitigate it at least to a degree (and to his managers for letting him do it).  Without doubt the CF team is the most transparent team I've encountered at Microsoft.  They readily admit where they've got shorcomings and are happy to explain why we see problems.  One can only hope this practice continues.

1/26/2008 1:11:37 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Tuesday, January 22, 2008
It took me 5 months to actually get it out the door, but I've just published a new white paper on the performance implications of P/Invoking in the COmpact Framework.  Check it out here.

1/22/2008 5:12:07 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Thursday, January 03, 2008
 Monday, December 31, 2007
Before I go any further, let me state for the record that this library does not provide any ability to play MP3 files, so if that's what you were thinking from the name, then don't get too excited.

The OpenNETCF.Media.MP3 library is a library that allows you to read and write ID3 tags to MP3 files.  It's both Compact Framework 2.0 and Full Framework 2.0 compliant.  For a usage sample, see the AudioTagR project.

The general object model looks like this:



It's licensed under our very open MIT X11 license.
Download the full source code (C#) here.

12/31/2007 3:25:40 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  | 
 Thursday, December 06, 2007

So I picked up a cheap PTZ (pan/tilt/zoom) camera off eBay a couple weeks ago for doing some R&D on a project I'm working on.  My hope was I'd be able to hook it up and talk to it from a device and get streaming video into an app.  Well it turns out that it's not so simple.  All cameras appear to have some form of proprietary interface that obviously varies from OEM to OEM.

I hooked up WireShark in hopes that I could reverse engineer the network commands fromthe packets, but it looks like it would take me weeks to get it figured out, and I don't have that kind of time (or desire) so I shot off an email to the OEM.  While I wait for them to reply (if they ever do) I went about reverse-engineering a kludge.

The device has a built in web server that allows you to control the camera, view video, etc.  So I opened up a page and looked at the source.  It contained a frameset, so I started navigating through frame pages and deconstructing the html to see what commands did what.  Unfortunately the video streaming piece appears to be in a compiled Java applet so getting at that turned out to be a dead-end, however it wasn't a total loss.  I did figure out how to send it commands to pan, tilt and capture single frames (well I figured out a lot more, but I limited my implementation to those functions for now).

So armed with what I knew, I slapped together the following class, basically simulating myself as a browser:

using System;
using System.Net;
using System.Net.Sockets;
using System.Drawing;
using System.IO;
using System.Threading;
using OpenNETCF.Peripherals.Camera;

namespace OpenNETCF.Peripherals
{
  public class MegaTecCamera : ICamera
  {
    private const int CMD_UP = 1;
    private const int CMD_DOWN = 2;
    private const int CMD_LEFT = 3;
    private const int CMD_RIGHT = 4;

    private string m_ip;
    private string m_username;
    private string m_password;

    public MegaTecCamera(IPAddress address, string username, string password)
    {
        m_ip = address.ToString();
        m_username = username;
        m_password = password;
    }

    public Image GetImage()
    {
        Image img = null;

        string command = string.Format(http://{0}/pda.cgi?user={1}&password={2}&page=image&cam=1
            m_ip, m_username, m_password);

        byte[] buffer = new byte[10000];
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(command);
        HttpWebResponse response = (HttpWebResponse)request.GetResponse();
        using (Stream stream = response.GetResponseStream())
        {
            try
            {
                img = new Bitmap(stream);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Capture failed: " + ex.Message);
            }

            response.Close();
        }
        return img;
    }

    public void Pan(PanDirection direction)
    {
        if (direction == PanDirection.Left)
            SendHttpCommand(GetDirectionCommand(CMD_LEFT));
        else
            SendHttpCommand(GetDirectionCommand(CMD_RIGHT));
    }

    public void Tilt(TiltDirection direction)
    {
        if (direction == TiltDirection.Up)
            SendHttpCommand(GetDirectionCommand(CMD_UP));
        else
            SendHttpCommand(GetDirectionCommand(CMD_DOWN));
    }

    private string GetDirectionCommand(int direction)
    {
        return string.Format(http://{0}/pda.cgi?user={1}&password={2}&page=execute&cam=1&command={3},
            m_ip, m_username, m_password, direction);
    }

    private void SendHttpCommand(string command)
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(delegate(object o)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(command);
            try
            {
                request.GetResponse().Close();
            }
            catch { }
        }));
    }
  }
}

The next obvious question is "What the hell do I do with this class that is of any use?"

Well that's the fun part!  I integrated it into a sample page on our demo Padarn server, so we now have images captured from an IP camera streamed back to a 200MHz Windows CE device that in turn serves up those images (and control of the camera) using an ASP.NET server.

The next step I'll add is the ability to turn on and off the light in the room via a web page.

 

12/6/2007 6:15:00 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  | 
 Thursday, November 29, 2007
We just published a new article on using WCF from device applications on the Community siteRead it here.

11/29/2007 2:40:14 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
In case you missed it, I published a new article on our community site last week on how to get a DateTime.Now equivalent with the milliseconds field filled in.  Read it here.

11/29/2007 11:20:53 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Tuesday, November 13, 2007
We've proactively pulled (meaning no one threatened us or asked us to do it) our QRCodeCF Library from public availability due to internal concerns about GPL.  Personally I dislike GPL due to its viral nature - it makes using any GPL code in any software that you intend to use for commercial or proprietary systems extremely risky, and we simply don't want to perpetuate that.  We write software to solve problems, and not surprisingly I feel those who write software should be able to get paid for doing so and not worry that their entire business might have to be given away because of some insane licensing issue.

11/13/2007 12:10:29 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Tuesday, October 30, 2007
OpenNETCF is proud to announce that we've released Padarn - an ASP.NET Web Server designed to run under the limited resource environment of Windows CE.  For more information on Padarn, visit our web site.

10/30/2007 12:51:03 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 
 Friday, September 28, 2007
Mark has just published a couple articles with just about everything you could want to know about inking:

Using the OpenNETCF Mobile Ink Library for Windows Mobile 6
Sharing Windows Mobile Ink with the Desktop

9/28/2007 4:37:14 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Friday, September 21, 2007

If you write applications that query a database - any database - then you might want to check out my new article on data caching.

9/21/2007 12:09:11 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 
 Thursday, September 20, 2007

In case you've missed it, the OpenNETCF Community Site publishes articles relevent to mobile and embedded developers.  We just published the latest entitled Developing Connected Smart Device Application with SqlClient. Check it (and the previous articles) out.

9/20/2007 6:19:52 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Tuesday, September 04, 2007

Today we were working on some final testing of a new app and I was irritated by the About Form's behavior of generice CE devices.  When we went to display the form, we'd get the ugly "expanding rectangle" animation of the Form opening, and even worse, when we closed it it would animate the close, then animate the re-opeing of the main form.

Unfortunately the Compact Framework give us absolutely nothing for controlling this behavior (hello?  Windows Mobile isn't the only platform the CF is used on....).  So we have to work around it.

Add the following to your form (or a utility class is what we actually used, as we called this from multiple places):

private const int WS_EX_NOANIMATION = 0x04000000;
private const int GWL_EX_STYLE = -20;

[DllImport("Coredll.dll", SetLastError=true)]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

[DllImport("coredll.dll", SetLastError=true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);

internal static void StopAnimation(Form form)
{
    int style = GetWindowLong(form.Handle, GWL_EX_STYLE);
    style |= WS_EX_NOANIMATION;
    SetWindowLong(form.Handle, GWL_EX_STYLE, style);
}

Then simply call StopAnimation in your Form's constructor immediately after InitializeComponent is called:

public Form1()
{
    InitializeComponent();
    StopAnimation(this);
}

We'll probably add this to the next release of the Smart Device Framework.

9/4/2007 4:41:52 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 

This morning OpenNETCF announced a new web initiative - our Community Web Site. I'll spare you explaining it in detail here, as it's on the front page of the site, but we've got articles and white papers, a public SVN server for shared-source projects and a monthly coding competition. This month we're giving away a copy of Studio 2005 professional and a Windows Mobile 5.0 device of your choice.

To come are Forums and a Wiki.

Let us know what you think.

9/4/2007 10:42:06 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Tuesday, May 15, 2007

We got no direct mention, but there's a real nice live video of the OpenNETCF RoundGauge control in action at 1:16 into this video:

http://www.youtube.com/watch?v=2AavbEAUHAg

In fact that's the exact app I used for testing and developing the RoundGauge way back when.  As you can see, it's running 4 gauges, all updating real time, on a Cirrus 200MHz processor and there is zero hesitation or flicker so yes, fast, smooth graphics *are* possible with the CF.

5/15/2007 1:18:22 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Friday, May 11, 2007

First of all, let's answer the question. Yes, FindWindow can find any Window that exists, regarless of the style bits or state, provided it is given enough information to identify the Window. This often is as simple as the Window's caption (or Text property in C#).

In a recent newsgroup thread, a user argues vehemently that calling the FindWindow API can't find a window that has been hidden (specifically in C# using the Hide() method).  Since my word is not enough to convince him, and since he apparently won't show the code that's failing, I decided to put it to rest with a test application (download the test app here).

5/11/2007 9:37:31 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Monday, May 07, 2007

Fabien Decret of Adeneo now has a CF Blog.  It's in French so I can't tell you much about it, but the item titles look good and if nothing else he's got a lot of activity.

5/7/2007 12:25:07 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 
 Wednesday, April 18, 2007

Today someone asked how they might enumerate all files and folders on a device.  The concept is pretty simple, and implementing it with a recursive algorithm is straightforward.  To make it a bit more useful I'm adding the items to a TreeView and saving the full path to the display file/folder in the TreeNode's Tag property, so when a node is selected, you don't have to reassemble the path.

Code:

public void GetListOfAllFilesAndFolders(TreeView view)
{
    view.BeginUpdate();
    view.Nodes.Clear();
    TreeNode node = new TreeNode("My Device");

    AddFolderToTreeNode("\\", node);
    view.Nodes.Add(node);
    view.EndUpdate();
}

private void AddFolderToTreeNode(string rootPath, TreeNode rootNode)
{
    // add in all directories
    string[] dirList = Directory.GetDirectories(rootPath);

    foreach (string dir in dirList)
    {
        TreeNode node = new TreeNode(Path.GetFileName(dir));
        node.Tag = dir;
        AddFolderToTreeNode((string)node.Tag, node);
        rootNode.Nodes.Add(node);
    }

    // add all files
    string[] fileList = System.IO.Directory.GetFiles(rootPath);

    foreach (string file in fileList)
    {
        TreeNode node = new TreeNode(Path.GetFileName(file));
        node.Tag = file;
        rootNode.Nodes.Add(node);
    }
}

Usage:

GetListOfAllFilesAndFolders(treeView1);

Yes, It's that simple.

NOTE: I tested this on an OLDI 56SAM-400 800MHz x86 CE device running CE 5.0 so your mileage may vary.

4/18/2007 12:41:01 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Friday, April 06, 2007

If you've done much development on non-Windows Mobile CE devices I'm sure your painfully aware of the pain in the ass process of getting Visual Studio 2005 to actually attach to the device.  Lately I've been doing nothing but non-WM development and deviced to automate and solidify the process, plus document it while I was at it.

We've got a (native) tool called CEDbgSetup that you run on your device (x86 or ARMv4I supported) and then you set up Studio once and only once, then you can debug every time using wired Ethernet, 802.11, USB RNDIS (and likely any other transport that uses TCP/IP) and without ActiveSync.

The tool supports auto-launching from \Windows\Startup or by setting up an LaunchXX entry in HKLM\Init in the device registry, or just manual click.  It should work headless too, though I've not specifically tested it.

It works with native or managed apps.

The tool, along with full source can be downloaded here (607k zip).

A white paper on how to set up Visual Studio 2005 can be viewed here (144k PDF).

Feedback is appreciated, but keep in mind that its free and completely unsupported (read: don't ask me for support).

4/6/2007 5:49:54 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [2]  | 
 Thursday, April 05, 2007

Another feature that missed the 2.1 cutoff - we've added a couple things to our existing EventWaitHandle:

A Set() overload:
public bool Set(int data)

A GetData method:
public int GetData()

For simple, fast, and really easy to implement IPC you can just do this:

Process A
EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.AutoReset, "MY_EVENT_NAME");

int myData = 10;
wh.Set(myData);

Process B
EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.AutoReset, "MY_EVENT_NAME");

if (m_eventsHandle.WaitOne())
{
    int myData = m_eventsHandle.GetData();
}

No MessageWindows, no queues - no ugliness at all - and it works with really minimal headless CE systems.

4/5/2007 11:42:43 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 

I know, SDF 2.1 has only been out for a day, but we're always working on the code base.  I just wrapped up a namespace that didn't make the 2.1 cutoff: OpenNETCF.WindowsCE.Services.  It allows you to enumerate, start, stop, refresh, load, unload, set the debug mask and register for autostart any service on the device.

Moving forward we're trying to componentize the SDF, so this one is stand-alone with no additional dependencies (not even the core OpenNETCF.dll library)

Here's a screen shot of my test/sample harness:

 

4/5/2007 11:05:41 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Friday, March 23, 2007

SDF 2.1 Preview - Get LIT

Ever have a business scenario where you want your application to perform some task on a periodic basis but the period will be very long - maybe days, weeks or even months?  You want it to occur if the device is asleep or aware at the time?  Well we've got a new SDF class that's designed for this: the LargeIntervalTimer (or LIT).

Let's look at a simple scenario.  Let's say I'm writing a custom calendar.  I want to set up a recurring meeting.  Well let's say the first meeting will be tomorrow at the same time as it is now (for simplicity) and then every 7 days after that.  We want the device to do something on that period (presumably make a noise, vibrate or whatever. 

The LIT is specially designed for this type of scenario.  The code to do achieve that scenario would look like this:

// we want the first timer event o happen 1 day from now
m_lit.FirstEventTime = DateTime.Now.AddDays(1);

// after the first event, we want it to fire every 7 days
m_lit.Interval = new TimeSpan(7, 0, 0, 0);

// we want it to be recurring
m_lit.OneShot = false;

// wire up a handler
m_lit.Tick += new EventHandler(LIT_Tick);

// start the timer
m_lit.Enabled = true;

That's it.  No P/Invokes.  No ugly Notifications, threads, events or whatever.  We've swept all that ugliness under the rug for you so all you need to do it implement your app.

3/23/2007 4:56:43 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 
 Thursday, March 01, 2007

I've been writing apps in mostly C# for several years now.  Of course I've also been doing drivers and kernel work in C or C++, and that has only improved my ability to write, debug and understand managed code in ways that pure managed developers will never really get.  Hell, I pretty much believe that if your managed code doesn't require allowing unsafe code then it could probably be done better.

Well for a few months now I've been neck-deep in a C++ application.  It was started using MFC, which tends to give me a rash just by saying the letters, but it was a customer decision not mine.  At any rate, I was doing some work with a RAS class where I needed any number of consumers to be able to be notified of any changes in the RAS dial status (dialing, connecting, authenticating, etc.).

Imagine a C# class for RAS that will expose events for when the dial staus changes.  THe first thing you'd need is a delegate, right?

In C# you'd have this (assuming DialStatus is an enum):

delegate void DialStatusChange(DialStatus dialStatus);

Well the c++ I wrote (without even thinking about C#) looked eerily familiar:

typedef void(*DIAL_STATUS_DELEGATE)(DialStatus newStatus);

In C# you'd use the += or -= operators for adding an event handler to a classes event, so we get spoiled.  In VB, you call AddHandler with the event name and then the address of the handler.  Well my C++ had a private vector of function pointers (which the CF maintains internally):

std::vector <DIAL_STATUS_DELEGATE> ConnectionManager::m_statusCallbackList;

And then I added methods for adding and removing handlers to the class:

 void ConnectionManager::StatusAddHandler(DIAL_STATUS_DELEGATE callback)
 {
   m_statusCallbackList.push_back(callback);
 }
 
 void ConnectionManager::StatusRemoveHandler(DIAL_STATUS_DELEGATE callback)
 {
   std::vector<DIAL_STATUS_DELEGATE>::iterator iterator;
 
   for(iterator = m_statusCallbackList.begin() ; iterator != m_statusCallbackList.end() ; iterator++)
   {
     if((*iterator) == callback)
     {
        m_statusCallbackList.erase(iterator);
        break;
     }
   }
 }

And how about usage?  In C#, our event-exposing class would have a defined event:

 event DialStatusChange OnStatusChange;

and then when we want to raise the event (assume it's multicast) from our app, we'd do something like this:

 if (OnStatusChange != null)
 {
     foreach (DialStatusChange dsc in OnStatusChange.GetInvocationList())
     {
         dsc(newStatus);
     }
 }

Well my C++ didn't need the event declaration, but raising the "event" (which is simply calling a function pointer callback) looked like this:

 std::vector<DIAL_STATUS_DELEGATE>::iterator iterator;
 
 recheck2:
 for(iterator = m_statusCallbackList.begin() ; iterator != m_statusCallbackList.end() ; iterator++)
 {
   if(IsBadCodePtr((FARPROC)(*iterator)))
   {
      // invalid callback found (someone hooked us then died without unhooking)
      m_statusCallbackList.erase(iterator);
      goto recheck2;
   }
      (*iterator)(status);
 }

Now this was just off the top of my head, so maybe there are improvements that could be made, but it kind of surprised me how I'd taken the concepts I really learned in C# and translated them back to my C++ code.  So yes, writing C# can make you a better C++ developer.

3/1/2007 12:02:32 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Thursday, February 22, 2007

For all those naysayers who complain that we don't do enough free stuff for the community, here's another contribution:

We're now hosting the latest zlib source for Windows CE and a managed wrapper for it.  Click the bitmap or here for more info.

2/22/2007 11:49:30 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Saturday, January 13, 2007

A guy in the newsgroups who is new to .NET, coming from VB6, is having a hard time drawing on a Form.  The problem with VB6 was it had the Shape controls which provided a crutch for a developer to never actually understand how drawing worked, and now that crutch has been removed.

Drawing is simple - you need a Graphics object to paint on.  The easiest way to get that for your Form is to just override OnPaint - you'll get it as an input parameter.  The other nice effect is that you don't have to do anything other than refresh the Form to get your code to run.

Now normally I don't like to just give out the answer - no one learns much that way - but he seems to genuinely have spent several hours trying to get this, so I figured I'd throw him a bone so he doesn't get frustrated and give up altogether.  So the goal is to draw a "crosshair" on the Form that the user can move around with the D-Pad on the device.  Again, this is a very, very basic example - it took me roughly 15 minutes to do (and that's becasue my VB is very rusty).

Create a device WinForms app.  Select the Form and make sure KeyPreview is True.  Then add this code:

Public Class Form1
   Private crosshairs As New Rectangle(0, 0, 20, 20)
   Private bluePen As New Pen(Color.Blue)
   Private Const StepBy As Int32 = 3

Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
   Dim b As New SolidBrush(Color.Blue)

   ' let the system do it's normal drawing
   MyBase.OnPaint(e)

   'now draw our crosshairs
   DrawCrosshairs(e.Graphics)
End Sub

Private Sub DrawCrosshairs(ByRef g As Graphics)
   g.DrawEllipse(bluePen, crosshairs)
   g.DrawLine( bluePen, _
               crosshairs.Left + crosshairs.Width / 2, _
               
crosshairs.Top - 10, _
               crosshairs.Left + crosshairs.Width / 2, _
               crosshairs.Bottom + 10)
   g.DrawLine( bluePen, _
               crosshairs.Left - 10, _
               crosshairs.Top + crosshairs.Height / 2, _
               crosshairs.Right + 10, _
               crosshairs.Top + crosshairs.Height / 2)
End Sub

Private Sub Form1_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown
   If (e.KeyCode = System.Windows.Forms.Keys.Up) Then
      If crosshairs.Top >= StepBy Then
         crosshairs.Offset(0, -StepBy)
      End If
   End If
   If (e.KeyCode = System.Windows.Forms.Keys.Down) Then
      If crosshairs.Bottom <= Me.Height - StepBy Then
         crosshairs.Offset(0, StepBy)
     End If
   End If
   If (e.KeyCode = System.Windows.Forms.Keys.Left) Then
      If crosshairs.Left >= StepBy Then
         crosshairs.Offset(-StepBy, 0)
      End If
   End If
   If (e.KeyCode = System.Windows.Forms.Keys.Right) Then
      If crosshairs.Right <= Me.Width - StepBy Then
         crosshairs.Offset(StepBy, 0)
      End If
   End If
   'this forces OnPaint to be called
   Me.Refresh()
End Sub
End Class

That's all there is to it.

1/13/2007 6:18:19 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  | 
 Friday, January 12, 2007

This question comes up a lot, and the well-known and documented workaround is to set your Form's Text property to an empty string to hide it from the Running Programs applet so I won't go into that.  And before reading further, if you're after hiding a Form, then go Google for that info - everything I present here are ways that are proven to not work.  It's simply info for those who are tempted to try another route.

We know that the Running Programs applet simply enumerates all top-level windows with text.  We also know that a lot of native apps don't have the problem of child windows showing up, so I decided to try to mimic that behavior with some P/Invoke shenanigans.  The test app simply had 2 forms - Form1 would create a Form2 instance on a button click and then call ShowDialog on the new Form2 instance.  After that (if it got that far), I'd check the applet to see if my code worked.

Method 1: Reparent the Form with SetParent

[DllImport("coredll.dll", SetLastError = true)]
internal static extern IntPtr SetParent(IntPtr hwndChild, IntPtr hwndNewParent);

  • Tried calling this after creating the Form2 and before calling ShowDialog.  ShowDialog subsequently throws an ArgumentException
  • Tried calling it in the ctor of Form2. ShowDialog subsequently throws an ArgumentException
  • Tried passing Form1's Handle to Form 2, then calling SetParent in a Form2.Activate handler. ShowDialog subsequently throws an ArgumentException
  • Tried calling SetParent from a button click in Form2, so after the dialog is loaded and shown.  No exception now, but when SetParent runs, Form1 gains scrollbars, Form2's controls end up in Form1 along with Form1's controls (so a mash up of both) and all controls are non-responsive.

Method 2: Change the Window Style bits

internal const int GWL_STYLE = -16;
internal const int GWL_EXSTYLE = -20;
internal const uint WS_CHILD = 0x40000000;
internal const uint WS_EX_APPWINDOW = 0x00040000;

[DllImport("coredll.dll", SetLastError = true)]
internal static extern uint SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);

[DllImport("coredll.dll", SetLastError = true)]
internal static extern uint GetWindowLong(IntPtr hWnd, int nIndex);

  • Tried setting style to WS_CHILD after creation and before ShowDialog.  Still appears in applet.
  • Tried setting style to WS_CHILD in Form2 ctor.  Still appears in applet.
  • Tried setting style to WS_CHILD in Form2 Activate handler.  Still appears in applet.
  • Tried setting style to WS_CHILD in Form2 button click handler.  Still appears in applet.
  • Tried unsetting extended style to WS_EX_APPWINDOW after creation and before ShowDialog.  Still appears in applet.
  • Tried unsetting extended style to WS_EX_APPWINDOW in Form2 button handler.  Still appears in applet.

So what's the take away lesson?  CF Forms like to be shown in that damned applet, and the simplest mechanism is still to just set the text to an empty string when you want to hide it.

1/12/2007 2:10:24 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  | 

When we use a language, often we tend to overlook some of the more obvious constructs or be frustrated by what we think should work.  For example, assume we have this simple problem - our function receives a value and based on that value we run through a switch, but we have code that will be run for multiple cases.  Explaining what I mean in words is tough - so let's look at what I'm trying to say in code.  Assume we have these enums:

[Flags]
enum Foo
{
  NoFoo = 0,
  FooA = 1,
  FooB = 2,
  FooC = 4
}

enum Bar
{
  A,
  B,
  C
}

We want a function that will take in a Bar, and based on that create a Foo.  If Bar is A, the the resulting Foo is a combination of FooA, FooB and FooC.  If Bar is B, then it's a combination of FooB and FooC.  If Bar is C, then the result is just FooC.  In C, we'd just do this:

Foo FooBar(Bar bar)
{
  Foo foo = Foo.NoFoo;

  switch (bar)
  {
    case Bar.A:
      foo |= Foo.FooA;
    case Bar.B:
      foo |= Foo.FooB;
    case Bar.C:
      foo |= Foo.FooC;
    break;
  }
  return foo;
}

Letting each case fall into the next intentionally. Yes it's a contrived example, but you get the idea.  There are cases when we need to do processing like this (like a project I'm doing right now).

Well C# doesn't like this type of construct - I'm not certain why it's illegal (other than missing breaks are common bugs) - but the compiler will say 'Error: Control cannot fall through from one case label ('case 0:') to another'.  So you might code a 'fix' like this:

Foo FooBar(Bar bar)
{
   Foo foo = Foo.NoFoo;

   switch (bar)
   {
      case Bar.A:
         foo |= Foo.FooA;
      break;
      case Bar.B:
         foo |= Foo.FooA;
         foo |= Foo.FooB;
      break;
      case Bar.C:
         foo |= Foo.FooA;
         foo |= Foo.FooB;
         foo |= Foo.FooC;
      break;
   }
   return foo;
}

Not too bad, but if you have to do more processing than a single line it gets ugly and maintainability goes downhill fast.

The thing to keep in mind in C# is that those case statements are actually labels, so you can use them as such, meaning they are valid goto targets, so this code is perfectly valid:

Foo FooBar3(Bar bar)
{
   Foo foo = Foo.NoFoo;

   switch (bar)
   {
      case Bar.A:
         foo |= Foo.FooA;
         goto case Bar.B;
      case Bar.B:
         foo |= Foo.FooB;
         goto case Bar.C;
      case Bar.C:
         foo |= Foo.FooC;
      break;
   }
   return foo;
}

1/12/2007 12:34:40 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Wednesday, January 03, 2007

For those who missed it, my MSDN Webcast is now available as an on-demand download.

1/3/2007 11:27:46 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Tuesday, December 19, 2006

If you did not attend my session at last year's MEDC or at MobileConnections then you have yet another chance to learn about memory management in the Compact Framework.  Tomorrow I'll be presenting it again in an MSDN Webcast.

12/19/2006 9:31:30 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  | 
 Monday, December 11, 2006

Over the last week of so I've noticed several questions in the newsgroups about how to synchronize a CE device's time with a time server.  Once again, it's not something I'd done, but I knew there were servers out there that provide the time publicly, so it couldn't bee too tough.

First stop was to see if Wikipedia gave a simple explanation.  Useful, but not exactly what we need - we want to know exactly what the server expects, and what exactly it returns.  It does give us the RFCs.

So a little looking at RFCs and we see that RFC 2030 is very applicable, and gives us all the info we need about the protocol.

A little more looking with Google found a public server domain name from NTP.org that actually rotates through public NTP servers, so we don't have to worry about one being up or not.

Armed with nothing but the second two links I wrote a little code.  This could be far more robust, with fractional seconds. mode, stratum, precision info and all that, but I just wanted to get a reasonable time - so to the second is all I was after.

public DateTime GetNTPTime()
{
   // 0x1B == 0b11011 == NTP version 3, client - see RFC 2030
   byte[] ntpPacket = new byte[] { 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

   IPAddress[] addressList = Dns.GetHostEntry("pool.ntp.org").AddressList;

   if (addressList.Length == 0)
   {
      // error
      return DateTime.MinValue;
   }

   IPEndPoint ep = new IPEndPoint(addressList[0], 123);
   UdpClient client = new UdpClient();
   client.Connect(ep);
   client.Send(ntpPacket, ntpPacket.Length);
   byte[] data = client.Receive(ref ep);

   // receive date data is at offset 32
   // Data is 64 bits - first 32 is seconds - we'll toss the fraction of a second
   // it is not in an endian order, so we must rearrange
   byte[] endianSeconds = new byte[4];
   endianSeconds[0] = (byte)(data[32 + 3] & (byte)0x7F); // turn off MSB (some servers set it)
   endianSeconds[1] = data[32 + 2];
   endianSeconds[2] = data[32 + 1];
   endianSeconds[3] = data[32 + 0];
   uint seconds = BitConverter.ToUInt32(endianSeconds, 0);

   return (new DateTime(1900, 1, 1)).AddSeconds(seconds);
}

12/11/2006 10:59:10 AM (Eastern Standard Time, UTC-05:00)  #    Comments [4]  | 
 Wednesday, November 08, 2006

If you attended one of my presentations at the MobileConnections conference, below are direct links to the PPTs and the Sample Applications I showed (if the links aren't active try again in a while as I FTP them up).

Compact Framework 2.0 and the Smart Device Framework 2.0
Presentation PPT
Sample Apps
Get the SDF here

Memory Management in the Compact Framework 2
Presentation PPT
Sample App
Blog Entry on Bitmaps and OOM

Sharing Assets between the CF and the Full Framework
Presentation PPT
ADT CodePlex project

Update (Nov 13, 06): The 'Sharing Assets..." presentation material originally came from Daniel Moth.  Unfortunately several very useful links in the footer of a few slides did not make the transition into the template for this show.  This omission does a disservice to those looking at the material, as they point to far deeper looks at some of the individual topics discussed.  Below are the links that I highly recommend that you visit, as they give much better insight than just the slides.

Slide 3: Visual Studio For Devices - See Resx compatibility for Smart Device projects
Slide 9: CF-Specific classes v2.0 - See Not a Strict subset
Slide 10: Windows Mobile 5.0 - See Microsoft.Windows.Mobile
Slide 11: CF CLR - See JIT
Slide 12: PPC versus Desktop - See Desktop to PPC (Part A)
Slide 16: CF Assemblies on PC - See Retagetable (=256)
Slide 17: Share the code, not the binary - See Share Code (#if FULL_FRAME)
Slide 20: How VS2005 & CF v2.0 improved the Everett story - See Deploy to My Computer

11/8/2006 12:42:08 PM (Eastern Standard Time, UTC-05:00)  #    Comments [2]  | 
 Tuesday, October 31, 2006

Some info that's worth reading:

10/31/2006 12:45:58 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 

NETCFRPM is a great tool, but it's kind of obvious that it was a late addition to the SP1 SDK.  One big problem is the process of deploying.  There are a couple files that must be pushed to the device in order to run, and these are documented only in Steven Pratchner's blog (maybe elsewhere too, but they aren't in the RPM readme deployed with the SDK). Don't get me wrong, I'd rather have it as an afterthought than not at all.

At any rate, without these files you'll launch RPM, but a device connected via ActiveSync won't show up in the Device ComboBox. Once deployed, RPM pushes uses RAPI to do it's work.  What surprises me is that RPM uses RAPI for data transfer, so why doesn't it use RAPI at the start to find the device and push these files to begin with?  The likely answer is that the team just didn't have time to get it done.  Well, since my general philosophy is any time that I do the same operation more than three times, it needs to be automated.  This is especially true when I have to go hunting for files, (or even the list of files that I can't always recall).

So I created a simple bootstrap that does all this for you.  It requires the OpenNETCF.Desktop.Communication library, but that's about it.  It could be extended to correctly detect target processor architecture (currently just uses ARM), and even do the RAPI provisioning (see Steven Blog entry) for WM 5.0 devices that need it, but at the current moment it works with my devices, so it's unlikely that I'll make changes unless I start testing another device.

At any rate, here's the first cut (download the binary here):

namespace OpenNETCF.RPMBootstrap
{
  class Program
  {
    static void Main(string[] args)
    {
      RapiBootstrap();
    }

    private static OpenNETCF.Desktop.Communication.RAPI m_rapi;

    private static string[] m_filnames = new string[] 
   
      @"netcfrtl.dll",
      @"netcflaunch.exe"
    };

    private const string SDK_REG_KEY = @"SOFTWARE\Microsoft\.NETCompactFramework\v2.0.0.0\InstallRoot";
    private const string SOURCE_PATH = @"\WindowsCE\wce400\armv4";
    private const string TARGET_PATH = @"\Windows";
    private const string CE4_ARM_PATH = @"WindowsCE\wce400\armv4";
    private const string RPM_PATH = @"bin\NetCFRPM.exe";

    public static void RapiBootstrap()
    {
      System.Console.WriteLine("OpenNETCF RPMBootstrap Bootstrap");
      System.Console.WriteLine("================================");

      m_rapi = new OpenNETCF.Desktop.Communication.RAPI();

      System.Console.WriteLine("Connecting via ActiveSync...");
      m_rapi.Connect(true, 10000);

      if (!m_rapi.Connected)
      {
        System.Console.WriteLine("Connection FAILED");
        return;
      }
      System.Console.WriteLine("Connected");

      // determine SDK install folder
      Microsoft.Win32.RegistryKey key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(SDK_REG_KEY);
      string sdkPath = (string)key.GetValue(null);
      key.Close();
      System.Console.WriteLine("CF 2.0 SDK is installed at:\n\t" + sdkPath);
      string localFilePath = System.IO.Path.Combine(sdkPath, CE4_ARM_PATH);

      foreach (string fileName in m_filnames)
      {
        string targetFile = System.IO.Path.Combine(TARGET_PATH, fileName);

        if (!m_rapi.DeviceFileExists(targetFile))
        {
            System.Console.WriteLine(string.Format("{0} not on device", fileName));
            System.Console.Write("Copying...");
            string localFile = System.IO.Path.Combine(localFilePath, fileName);
            m_rapi.CopyFileToDevice(localFile, targetFile);
            System.Console.WriteLine("ok");
        }
        else
        {
            System.Console.WriteLine(string.Format("{0} already on device", fileName));
        }
      }
      System.Console.Write("Disconnecting...");
      m_rapi.Disconnect();
      System.Console.WriteLine("ok");

      // find RPM
      string rpmPath = System.IO.Path.Combine(sdkPath, RPM_PATH);

      if (!System.IO.File.Exists(rpmPath))
      {
        System.Console.WriteLine("Local RPM Binary not found at:\r\t" + rpmPath);
        return;
      }

      // launch
      System.Console.WriteLine("Launching RPM...");
      System.Diagnostics.Process p = System.Diagnostics.Process.Start(rpmPath);
    }
  }
}

10/31/2006 12:35:18 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Thursday, October 26, 2006

If you know much about me, you probably know that simple doesn't excite me.  I like problems that are interesting yet tough to solve.  A year or more ago or so I decided that I really needed to know how CAB files work.  If you do much work creating CE devices you're familiar with the frustration of trying to include some third party package in your device image, but all you have is a CAB.  So you have to find all of the files, folders registry entries and the like that the CAB creates - usually through a process of elimination.

Well I knew that a CAB file has to contain that info, and experience had told me that just unpacking the CAB gives you all of the files (though with mangled names).  So I started playing in my free time reverse engineering how they're packed up.  Like many of my side projects, I'd tabled the project a couple months ago due to a lack of time and paying work that needed doing.  That was until this week, when I needed to combine a third-party CAB with a customer's app to give them a single-CAB deployment.  I spent a couple days working on the tool to get it to generate an INF file that was compatible with CABWIZ.  And that brings us to the quiz.  The following is an output from the tool - right from the source 3rd party CAB.  If I run this and the files into CABWIZ I get the came CAB back out that installs on the device as expected.  Any guess on what the CAB is (yes it's so easy it's a rhetorical question)?

; +-------------------------------------------------+
; | INF Generated by OpenNETCF CABConstructor       |
; | visit
http://www.OpenNETCF.com for product info |
; +-------------------------------------------------+

[Version]
Signature="$Windows NT$"
CESignature="$Windows CE$"
Provider="Microsoft"

[CEStrings]
AppName=".NET CF 2.0"
InstallDir="%CE2%"

[DefaultInstall]
AddReg="RegKeys"
CESetupDLL="NETCF_~1.dll"
CopyFiles="Destination3","Destination2"

[CEDevice]
VersionMin="4.0"
VersionMax="4.999"
BuildMax="0xE0000000"
UnsupportedPlatforms="HPC","JUPITER","SMARTPHONE"

[SourceDisksNames]
1=,"SRCFILES",,SourceFiles

[SourceDisksFiles]
"00System.008"=1
"0mscoree.006"=1
"0NETCFv2.000"=1
"cgacutil.005"=1
"CGACUT~1.021"=1
"CUSTOM~1.020"=1
"MICROS~1.018"=1
"MICROS~2.017"=1
"MICROS~4.019"=1
"MSCORE~1.002"=1
"MSCORE~1.022"=1
"mscorlib.007"=1
"NETCF2~1.001"=1
"NETCFA~1.003"=1
"NETCFD~1.004"=1
"NETCF_~1.dll"=1
"NETCF_~1.dll"=1
"SY40C7~1.014"=1
"SY4317~1.016"=1
"SY726F~1.010"=1
"SY9B57~1.013"=1
"SYC6B2~1.011"=1
"SYSTEM~1.015"=1
"SYSTEM~3.009"=1
"SYSTEM~4.012"=1
"_setup.xml"=1

[DestinationDirs]
Destination3=0,"\%CE2%\.NET CF 2.0"
Destination2=0,"\%CE2%"

[Destination3]
"mscorlib.dll","mscorlib.007",,0x40000001
"System.dll","00System.008",,0x40000001
"System.Drawing.dll","SYSTEM~3.009",,0x40000001
"System.Messaging.dll","SY726F~1.010",,0x40000001
"System.Web.Services.dll","SYC6B2~1.011",,0x40000001
"System.Windows.Forms.dll","SYSTEM~4.012",,0x40000001
"System.Windows.Forms.DataGrid.dll","SY9B57~1.013",,0x40000001
"System.Xml.dll","SY40C7~1.014",,0x40000001
"System.Net.IrDA.dll","SYSTEM~1.015",,0x40000001
"System.Data.dll","SY4317~1.016",,0x40000001
"Microsoft.VisualBasic.dll","MICROS~2.017",,0x40000001
"Microsoft.WindowsCE.Forms.dll","MICROS~1.018",,0x40000001
"Microsoft.WindowsMobile.DirectX.dll","MICROS~4.019",,0x40000001
"CustomMarshalers.dll","CUSTOM~1.020",,0x40000001
"cgacutil.exe.-410~-410~ARMV4","CGACUT~1.021",,0x40000001
"mscoree.dll.-410~-410~ARMV4","MSCORE~1.022",,0x40000001

[Destination2]
"netcf2_0license.txt","NETCF2~1.001",,0x40000001
"MSCOREE2_0.dll","MSCORE~1.002",,0x40000001
"netcfagl2_0.dll","NETCFA~1.003",,0x40000001
"netcfd3dm2_0.dll","NETCFD~1.004",,0x40000001
"cgacutil.exe","cgacutil.005",,0xA0000001
"mscoree.dll","0mscoree.006",,0xA0000001

[RegKeys]
HKLM,"GACPath",""%InstallDir%"",0x00000000,"%InstallDir%"

 

10/26/2006 10:33:22 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [4]  | 
 Wednesday, October 25, 2006

So a couple of us at OpenNETCF have been heads down coding in a project for the last couple months.  It's got some interesting aspects and I'll be posting lessons learned over the coming weeks.  One of the biggest problems is that the deployment device for testing is the HP6315, which I'll go on record as saying sucks.  Working around its flakiness is a real pain.  Another problem is the customer's desire to run on the device out-of-the-box to minimize deployment.  That means running with the version of the CF in ROM (CF 1.0 SP2 in this case).

Well the last few bugs in the project's bug database are that the app can't restart once it's been closed.  The first suspect is obviously that the app didn't really shut down all the way, and the CF is preventing new instances from running.

First step is to repro.  It's odd becasue we've been developing for a couple months and neitehr of us have seen this.  We run through the reported repro steps from the customer's functional testing and can't get it to fail.  The customer can repro it every time, and on multiple devices.  It then dawned on me to try a hard-reset, clean device.  Blam!  Sure enough, it failed.

So what's different?  Well in development we use Studio, which pushes down the latest CF (SP3) the first time and then we forget about it.  SP3 fixed something in SP2.  Of course the requirement is to use what's in ROM, so it's time to debug a little.

Next step is to use Remote Process Viewer and confirm that the process is indeed still running before digging into the code.

Well RPC shows that the second instance did in fact launch - now we have 2 instances.  Further, tapping the icon starts more instances.  If you don't understand the implications of this, let me explain a little.  The CF (on PPC devices) enforces singleton app behavior (a bad idea IMHO, but that's a side topic).  Well if we're getting multiple instances, then this "enforcement" is failing  The CF is not only not shutting the first down, but the second is being prevented from fully coming up (maybe the assembly is locked in some way?).

Well, added a little debug code and we find that Application.Run is exiting and Main is running out it's course.  Again, this points at a zombie thread, but that doesn't explain the multiple instance issue.

I spent a couple hours walking our code and a 3rd party library, trying to find a zombie thread or anything that might be doing this to no avail, and then it dawned on me - the app is closing.  We don't need any resources, we just want to end.  Well, let's just be heavy handed

I looked up ExitProcess in the platform headers, and it's and inline that calls TerminateProcess with GetCurrentProcess for the handle.  Digging further leads you to find that GetCurrentProcess() is really just the constant 0x42 (on ARM anyway).  So this is what I ended up with (well trimmed down anyway):

static void Main()
{
    Main main = new Main();
    Application.Run(main);

    if((System.Environment.Version.Major == 1) && (System.Environment.Version.Build < 4292))
    {
        // NOTE:
        // This is a heavy-handed workaround for a bug this app exposes in pre-SP3 CF 1.0 devices
        // For those interested, 0x42 is the expansion of GetCurrentProcess() from the platform SDK headers
        // and ExitProcess is simply an inline calling TerminateProcess with GetCurrentProcess(). 
        TerminateProcess(0x42, 0);
    }
}

[DllImport("coredll.dll", SetLastError=true)]
private static extern int TerminateProcess(uint handle, int uExitCode);

Long live the kludge! 

10/25/2006 6:34:11 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Friday, August 25, 2006

If you read much of what I write, you know that performance is something I like to always keep in mind, and I'm also a big fan of quantitative analysis.  It all started ages ago with a look at the real time capabilities of Windows CE, but most recently I've been looking at managed code.  This entry is the second in a series of looks at performance of managed code, specifically the .NET Compact Framework.  For the first entry, see this blog entry (which became this MSDN article).

Act II - Method calls are Expensive (or Stay in the Shallow End of the Stack!)

As with many of my diatribes into technical problems, this all started with a newsgroup post.  SOmeone posted that the had done a Towers of Hanoi implementation in VB.NET, C# and C++ and that the C# implementation was faster than VB, but got slower in CF 2.0.  They also said that native code was much faster than both (I think he may have said an order of magnitude, but don't quote me on that).

Well he didn't post any code so we could reproduce his results, and I never take anyone on their word on something like this, so I decided to put it to the test.  Before going any further, you may need some background information on what the Towers of Hanoi problem is.  You're probably familiar with it - you just didn't know what it was called.  Go read this Wikipedia entry and come back.

I decide to do a recursive solution, so the meat of the problem is that we have an exponential number of method calls, making the call stack very, very deep.  Just from a memory point of view this is a bad idea (see my MEDC presentation on memory management if you want to know more on the whys of that).  But you'll also see that the expense of method calls in the CF (see Act I) really bites you here as it bites hard.

First, let's look at the code.  In C#, it looks like this:

public class Hanoi
{
  private int[] pegs = new int[3];

  public Hanoi(int totalDisks)
  {
    // start with all disks on peg 0
    pegs[0] = totalDisks;
    pegs[1] = 0;
    pegs[2] = 0;
  }

  public int Solve()
  {
    int et = Environment.TickCount;
    Move(0, 2, 1, pegs[0]);
    return Environment.TickCount - et;
  }

  private void Move(int fromPeg, int toPeg, int intermediatePeg, int disks)
  {
    if(disks == 0) return;

    // move all but one disk to the intermediate peg
    Move(fromPeg, intermediatePeg, toPeg, disks - 1);

    // move the last remaining disk to the destination - no need for the intermediate
    pegs[fromPeg] -= 1;
    pegs[toPeg] += 1;

    // now move all but one off the intermediate peg to the destination peg
    Move(intermediatePeg, toPeg, fromPeg, disks - 1);
  }
}

You can see that Move calls itself twice.  Now try tracing the code in your head if I call Solve when totalDisks is 30.  Ugly.

Now lets look at the C++ implementation.

class Hanoi
{
    private:
        int pegs[3];
        void Move(int fromPeg, int toPeg, int intermediatePeg, int disks);

    public:
        Hanoi(int totalDisks);
        int Solve();
};

Hanoi::Hanoi(int totalDisks)
{
    // start with all disks on peg 0
    pegs[0] = totalDisks;
    pegs[1] = 0;
    pegs[2] = 0;
}

void Hanoi::Move(int fromPeg, int toPeg, int intermediatePeg, int disks)
{
    if(disks == 0) return;

    // move all but one disk to the intermediate peg
    Move(fromPeg, intermediatePeg, toPeg, disks - 1);

    // move the last remaining disk to the destination - no need for the intermediate
    pegs[fromPeg] -= 1;
    pegs[toPeg] += 1;

    // now move all but one off the intermediate peg to the destination peg
    Move(intermediatePeg, toPeg, fromPeg, disks - 1);
}

int Hanoi::Solve()
{
    int et = GetTickCount();
    Move(0, 2, 1, pegs[0]);
    return GetTickCount() - et;
}

You can see that it's really not much different - in fact it's really, really close.  So what do we see for results when we run these? Look at the table below (my device was an Axim X30 with a PXA270 processor)

disks C# - CF 2.0 C++ Diff % improvement
20 744 461 283 38%
21 1518 1148 370 24%
22 2997 2068 929 31%
23 5964 4006 1958 33%
24 11892 7646 4246 36%
25 23942 14875 9067 38%
26 47945 29423 18522 39%
27 95674 58496 37178 39%
28 190917 116837 74080 39%

The C++ version performed nearly 40% better.  Of course the C# JIT compiler is running on a mobile device, with presumably much less power than a desktop machine, so the JITter is built to optimize for compile speed, not execution speed.  To try to level the playing field, I compiled the C++ version in Debug mode to turn off all compiler optimizations.  I can't actually see what the CF JITter creates for assembly, so we can't be sure if the resulting code is the same, but that's the nearest I can come.

So we know that the implementation code is near identical.  We also know from Act I that identical code in a single method has no performance difference between native and managed code.  We also know from Act I that method calls are quite expensive.  A reasonable conclusion then is that the performance degradation we're seeing here is nearly all in the cost of method calls.  Does this mean that managed code performance is terrible?  The answer is "it can be if you don't fully understand what the CLR is doing." The lesson learned here then is to either refactor the algorithm to keep your call stack short (there are non-recursive solutions to this problem - maybe another day I'll test that), or put heavily recursive stuff into a native library and P/Invoke to it.

If you want to try these out on your own device, you can download the source code here (post your results in the comments if you'd like).  The source actually points out another lesson, this time in UI development speed. The C# version has a nice UI that I put together in about 10 minutes.  Writing a nice UI would have taken quite a bit longer in C++ so I didn't even bother. With the C++ version you have to get the results from by running in eVC or Studio and setting a breakpoint in the calling loop and writing down the number after each iteration.

8/25/2006 12:09:58 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Wednesday, August 23, 2006

We've had an idea for a new project for some time, and we've finally decided to put out a code seed for it to see if the community wants to get involved.

The idea is this - Create a native coredll.dll library for the desktop that exactly matches the one exposed by Windows CE (same funtions at the same ordinals).  In theory, this would allow you to run Compact Framework applications against the full framework including code that P/Invokes.  The long-term goal is to implement every funtion (there are about 1800 of them, we've seeded the project with 50), but the milestone I'm shooting for is to get this library to a point that the SDF will run on the desktop.

Why would you want this library you ask?  The answer is fairly simple - to help in debugging and unit testing.  I don't envision you shipping a product that runs on CE and XP, but I do see great value in being able to run your CF assemblies through NUnit or Team Suite unit tests, which today cannot be done on a device.  This project is an enabler for that.

THe project is located at CodePlex as the OpenNETCF Advanced Debugging Toolkit.  Look for more pieces to the toolkit as time progresses.

8/23/2006 2:00:11 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 
 Tuesday, August 22, 2006

The Bitmap class in the Compact Framework is a confusing thing, largely because it has abstracted what the OS is doing underneath a little too far.  For example, look at the following code:

Bitmap bmp1 = new Bitmap(fileStream);
Bitmap bmp2 = new Bitmap(200, 200);

Let's assume that fileStream is a valid stream to a resource bitmap file that is 100x100 in size.  So is there any difference between bmp1 and bmp2, other than the fact bmp1 presumably has some color data in it?  The answer is yes - there's a very big difference, and that difference can have a huge impact on application performace as well as cause exceptions.

So let's look at this a little deeper with some examples.  Here's the first:

int iterations = 0;

while (true)
{
  try
  {
    iterations++;
    Bitmap b = new Bitmap(GetImageStream());
    if (iterations % 100 == 0)
    {
      Debug.WriteLine(string.Format("{0} objects", iterations));
    }
  }
  catch
  {
    Debug.WriteLine(string.Format("Failed after {0} objects", iterations));
    Debugger.Break();
  }
}

If I run this code (GetImageStream() just pulls an image from an embedded resource), the app will run forever, occasionally spitting out the debug port how many hundreds of objects it's created.  If you run RPM on it you'll see memory getting allocated, the GC firing occasionally and resources being freed up.  All is well in the world of managed code and everything is working as expected.  Hooray.

Now let's change that ever so slightly to this:

int iterations = 0;

while (true)
{
  try
  {
    iterations++;
    Bitmap b = new Bitmap(200, 200); // <--- CHANGED HERE
    if (iterations % 100 == 0)
    {
      Debug.WriteLine(string.Format("{0} objects", iterations));
    }
  }
  catch
  {
    Debug.WriteLine(string.Format("Failed after {0} objects", iterations));
    Debugger.Break();
  }
}

Note the single change where the bitmap is created.  Try running this and after a few iterations - the exact number depends on available device memory - it will OOM (throw an out of memory exception).  On the device in front of me it was about 40.

So the first thing to do is theorize why this would happen.  Seems like the Bitmap's resources aren't getting freed after it goes out of scope at the end of the while block.  An explicit call to Dispose() may solve it if that's the case, so let's try another test.

int iterations = 0;

Bitmap b = null;
while (true)
{
  if (b != null)  // explicit disposal
    b.Dispose();

  try
  {
    iterations++;
    b = new Bitmap(200, 200);
    if (iterations % 100 == 0)
    {
      Debug.WriteLine(string.Format("{0} objects", iterations));
    }
  }
  catch
  {
    Debug.WriteLine(string.Format("Failed after {0} objects", iterations));
    Debugger.Break();
  }
}

Sure enough, when we run this, it behaves like the first.  Strange that the Bitmap behaves differently depending on which constructor we use - this is contrary to common sense, right? 

So let's think a little more.  A Bitmap has a large area of unmanaged resources and some managed resources.  It seems that when we create a bitmap using the size ctor, the finalizer doesn't get run when an OOM happens.  Let's test again and see if that really is what's going on. We'll remove the explicit Dispose call and wait for the finalizers and try again when we OOM.

int iterations = 0;

while (true)
{
  try
  {
    iterations++;
    Bitmap b = new Bitmap(200, 200);
    if (iterations % 100 == 0)
    {
      Debug.WriteLine(string.Format("{0} objects", iterations));
    }
  }
  catch (OutOfMemoryException)
  {
    Debug.WriteLine("Waiting for finalizers to run..."));
    GC.WaitForPendingFinalizers();
    Bitmap b = new Bitmap(GetImageStream());
  }
}

When we run this one, again all is well in managed code land, though we see it waiting for finalizers to run a lot, and that catch is an expensive one for perf (as all exceptions are). At this point I think "well that surely has to be a bug" but I often like a second opinion, so I went right to the source and asked the CF team about the behavior.  The response from them is actually quite informative.  Their response in in italics below.

I think you are probably seeing is several interactions that can be quite confusing.

  1. Creating a bitmap using the stream constructor will construct a DIB (Device Independent Bitmap).
  2. Creating a bitmap using the width/height constructor will construct a DDB (Device Dependent Bitmap).
  3. DIB's are allocated out of the virtual address space of the application.
  4. DDB's are allocated by the driver. This typically means that they are allocated in the virtual address space of gwes.exe. Alternatively, the driver could allocate these in dedicated video ram.
  5. Creating a bitmap with the stream constructor will generate a fair amount of garbage as it copies data from one buffer to the other.

When we perform a GC because of an OOM in the stream constructor case, we will almost certainly have some amount of garbage that we can free back to the OS immediately. This will also trigger the finalizer to run on another thread as soon as possible. That should help the next call to bitmap creation.

When we perform a GC because of an OOM in the width/height constructor case, it is fairly likely that the OOM is caused because of virtual memory exhaustion in gwes.exe. Thus freeing memory in our process will not help the memory condition in gwes.exe. We need the bitmap finalizer to run before this would actually free memory in a way that would help this scenario. While the finalizer thread would certainly have been triggered to start, it most likely will not get a chance to free bitmaps before we OOM while trying to allocate a bitmap immediately after triggering a GC on the initial thread.

In short, we have 2 different types of Bitmap in our runtime with varying performance and allocation characteristics. DDBs are generally faster to manipulate and draw to the screen than DIBs, but they are constructed in an external memory space that can cause allocation confusion and cause the performance of calls to LockBits or Save to be slow. If a DIB is desired and you wish to construct it based on width and height, we provide a function that constructs a Bitmap with a width, height, and pixelformat specified. This function will construct a DIB instead of a DDB.

I personally still consider this a bug in the implementation - the CF should catch these occasions and handle it for us rather than OOMing all the way back to the app to wait for the Finalizers and retry - that's an implementation that should be done below us. 

Still the answer sheds light on the fact that how we create a Bitmap should be highly dependent on how we intend to use that Bitmap.

8/22/2006 2:24:50 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [4]  | 
 Friday, June 30, 2006

It occurred to me today that I never posted my presentation on Memory Management in the CF from MEDC 2006, so here it is.

6/30/2006 11:42:23 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [3]  | 
 Thursday, June 29, 2006

Since most people like to try before they buy, we've released Evaluation Versions of our Calendar Controls.  Download the evaluation binaries and a sample project using them here.

6/29/2006 12:37:14 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Friday, June 23, 2006

I recently worked on a project that required me to connect to a printer from a Pocket PC with the Widcomm bluetooth stack on it.  Frustrating as it was, one positive thing came from it - I generated a nice start to a set of classes for using High-Point Software's BTConnect.

Of course it means that you'll need to buy BTConnect to use this library without the "evaluation mode" popup, but it abstracts the ugliness of sommand-line parameters away from the developer so you can focus on creating your app.

I've parked the library here:

www.opennetcf.org/shared

6/23/2006 4:31:35 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Thursday, June 22, 2006

So I spent all of today fighting with a Dell Axim X30, and I've again come to the conclusion that companies who keep things proprietary really suck.

My task was to print over Bluetooth.  I decided that I'd use PrinterCE from Field Software for simplicity, and all worked well if the radio was already on when I tried to print.  If the radio was off, however, things went bad quickly and the device had to be soft reset to recover.  Not a good thing for usability.

Since they use the Widcomm/Broadcomm stack, I decided to look at the BTConnect stuff from High-Point software.  After four or five hours of creating a nice class wrapper for the app (which is going to become a shared-source library from OpenNETCF shortly) I found that the radio is on whenever I am actively searching or connecting, but as soon as it's done, the on-board BT manager shuts the radio off.  That means I can find the printer and connect with it, but as soon as I had off printing to PrinterCE, the radio shuts off and the device hangs.  Beautiful.  A plague upon the engineers at Dell.

Searching the web I find nothing hinting at how to programmatically do this.  Curses on the yet again.

I use Spy++ to see if I'm lucky and any messages might jump out when I use the BT Manager.  Nothing.  My hatred for Dell increases.

I search the registry for anything that might affect behavior and I find only one key that reports the current state.  Damn you Dell!

So I'm now a day into just turning on a Radio for a job I've quoted at 2 days (which I've already used doing the reporting code).  Now not only am I highly irritated, I'm highly irrated on my own dime.

So I make a last ditch effort to get something that at least functions.  I decided for a kludge, and a really nasty one at that.  As soon as I thought of it, I was disgusted by the idea, but I've really been left with no choice.  I'd simulate tapping the screen to turn the radio on.

And without further ado, I give unto you the following ugliness.  Hopefully no one elese ever has to use it, but since I've stooped this far, someone else probably will have to as well.  If you do, please curse Dell as well.

using System;
using Microsoft.Win32;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace OpenNETCF.Devices
{
  public static class AximX30
  {
    public static bool BluetoothRadioState
    {
      get
      {
        RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WIDCOMM\BtConfig\General");
        bool currentState = (((int)key.GetValue("StackMode")) == 1);
        key.Close();
        return currentState;
      }
      set
      {
        // see if any action is needed
        if (BluetoothRadioState == value)
        {
          return;
        }

        // save where we were
        IntPtr topWindow = GetForegroundWindow();

        int bticonX = Screen.PrimaryScreen.Bounds.Width - 10;
        int bticonY = Screen.PrimaryScreen.Bounds.Height - 10;
        int btmenuX = Screen.PrimaryScreen.Bounds.Width - 20;
        int btmenuY = Screen.PrimaryScreen.Bounds.Height - 80;
        int starticonX = 10;
        int starticonY = 10;
        int todayX = 10;
        int todayY = 60;

        // start
         SendTap(starticonX, starticonY);

         // let the menu draw
         System.Threading.Thread.Sleep(300);

         // today screen
         SendTap(todayX, todayY);

         // let the screen draw
         System.Threading.Thread.Sleep(300);

         // Blutooth icon tap
         SendTap(bticonX, bticonY);

         // let the menu draw
         System.Threading.Thread.Sleep(300);

         // bt menu tap
         SendTap(btmenuX, btmenuY);

         System.Threading.Thread.Sleep(300);

         // restore where we were
         ShowWindow(topWindow, SW.NORMAL);
         UpdateWindow(topWindow);
       }
      }

    [DllImport("coredll")]
    private static extern void mouse_event(
            MOUSEEVENTF dwFlags, int dx, int dy, int dwData, int dwExtraInfo);

    [DllImport("coredll")]
    private static extern IntPtr GetForegroundWindow();


    [DllImport("coredll")]
    private static extern bool ShowWindow(IntPtr hWnd, SW nCmdShow);

    [DllImport("coredll")]
    private static extern bool UpdateWindow(IntPtr hWnd);


    [Flags]
    private enum HWND
    {
    TOP = 0,
    BOTTOM = 1,
    TOPMOST = -1,
    NOTOPMOST = -2
    }

    private enum SW
    {
    HIDE = 0,
    SHOWNORMAL = 1,
    NORMAL = 1,
    SHOWMINIMIZED = 2,
    SHOWMAXIMIZED = 3,
    MAXIMIZE = 3,
    SHOWNOACTIVATE = 4,
    SHOW = 5,
    MINIMIZE = 6,
    SHOWMINNOACTIVE = 7,
    SHOWNA = 8,
    RESTORE = 9,
    SHOWDEFAULT = 10,
    FORCEMINIMIZE = 11,
    MAX = 11
    }

    [Flags]
    private enum MOUSEEVENTF
    {
        MOVE = 0x1, /* mouse move */
        LEFTDOWN = 0x2, /* left button down */
        LEFTUP = 0x4, /*left button up */
        RIGHTDOWN = 0x8, /*right button down */
        RIGHTUP = 0x10, /*right button up */
        MIDDLEDOWN = 0x20, /*middle button down */
        MIDDLEUP = 0x40, /* middle button up */
        WHEEL = 0x800, /*wheel button rolled */
        VIRTUALDESK = 0x4000, /* map to entrire virtual desktop */
        ABSOLUTE = 0x8000, /* absolute move */
        TOUCH = 0x100000, /* absolute move */
    }

    private static void SendTap(int x, int y)
    {
      mouse_event(MOUSEEVENTF.LEFTDOWN | MOUSEEVENTF.ABSOLUTE, 
            (int)((65535 / Screen.PrimaryScreen.Bounds.Width) * x), 
            (int)((65535 / Screen.PrimaryScreen.Bounds.Height) * y), 0, 0);
      mouse_event(MOUSEEVENTF.LEFTUP, 0, 0, 0, 0);
    }
  }
}

6/22/2006 5:02:54 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 
 Monday, June 05, 2006

I may be a little biased because they're ours, but our new Calendar Controls really are the best looking ones I've seen.  You can now add a calendar to your managed app that looks just like the Outlook calendar.

6/5/2006 1:30:35 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 

Seems like it took forever, but we've finally released the SDF 2.0 Extensions for Visual Studio 2005.

For more info on what exatly comes with the new SDF Extensions, click here.

6/5/2006 1:26:58 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [1]  | 
 Friday, May 26, 2006

Ever have to do manual serialization of data in a DataSet?  The challenge is being able to call BitConverter.GetBytes on the data element when at compile time you have no idea what it's type is, and GetBytes doesn't accept an Object (though I'd argue maybe it should and attempt to convert the underlying type if it's resolvable).

Here's my solution:

byte[] elementBytes = (byte[])typeof(BitConverter).GetMethod(
   
"GetBytes", new Type[] { dataRow[columnNumber].GetType() }).Invoke(
    null, new object[] { dataRow[columnNumber] });

5/26/2006 1:09:56 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Saturday, April 22, 2006

In case you hadn't heard, CF 2.0 SP1 Beta is now available.

4/22/2006 1:26:29 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0]  | 
 Monday, February 27, 2006

So the OpenNETCF.Desktop.Communication library has been used in the "first malware to cross-infect a handheld phone or PDA from a desktop PC binary file...." (reported by the Mobile Antivirus Research Association).

The quote from the author is as follows:

The crossover virus was written in C# (C Sharp) using Visual Studio .NET 2003, the Communications Library of openNETCF.org was used and a great help

As part of such a "first" I'm not so sure I'm proud of, it does show how pervasive something as useful as our libraries can be.

 

2/27/2006 7:41:29 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Thursday, February 23, 2006

If you're building a CE image and want to include CF 1.0 or CF 2.0, then it's pretty simple - just drag the component from the catalog into your workspace.  But what if you don't want to include the CF at all, but still have the plumbing there so your end-user can install the CF themselves and have it work?  One could look through the cesysgen.bat files and decipher what needs to be set or, if you're lazy like me, you could ask the CF team.

Here's the answer, courtesy of Jim Suplizio:

The appropriate support sysgen will have to be added to the image depending on what version of CF is going to be installed.

  • SYSGEN_DOTNET_SUPPORT is the CF 1.0 support sysgen.
  • SYSGEN_DOTNETV2_SUPPORT is the CF 2.0 support sysgen. SYSGEN_DOTNETV2_SUPPORT is a super-set of the CF 1.0 support.

Effectively if SYSGEN_DOTNETV2_SUPPORT is added to the image then the end user can deploy either 1.0 or 2.0 and all of the required underlying CE OS pieces will be there.

Keep in mind that required means "base functionality". CF 2.0 has optional functionality that requires other SYSGENS and they are as follows:

  • Message Queuing - SYSGEN_MSMQ
  • Soap Reliable Messaging Protocol - SYSGEN_MSMQ_SRMP
  • SQLMobile (2005) requires CoCreateGuid functionality - SYSGEN_OLE_GUIDS
  • IPv6 - SYSGEN_TCPIP6
  • IE, PIE, HTMLView (htmlview.dll) or nothing - SYSGEN_IE, SYSGEN_PIE, SYSGEN_HELP or simply nothing.
  • The following only apply to the MainstoneII, other devices will only have null driver sets:
    • D3D Mobile - BSP_D3DM_XSCALE
    • D3D Mobile - SYSGEN_D3DMXSCALE

 

2/23/2006 4:47:21 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Wednesday, February 22, 2006

It's been a while since we visited the OpenNETCF.Deskop.Communication library, but there have been lingering issues that ActiveSync 4.x have exposed (a stack imbalance exception).  So today I revisited the library and did some fixes to it as well as to the sample app (bad, bad me for not marshaling calls to the UI from a thread.  It's now a Studio 2005 project.

The latest code is in Vault and the downloads on the site are updated to this new version (2.9) as well.

2/22/2006 2:09:33 PM (Eastern Standard Time, UTC-05:00)  #    Comments [2]  | 
 Sunday, February 19, 2006

SDF 2.0 continues to have functionality for working with the time and time zones for devices.  The differences from SDF 1.x are a namespace change, a classname change (don't believe the beta docs or the beta, these have changed since that drop), and under the hood a lot more internal checks are going on to head off errors and to provide correct device support.  Basically this one has been pretty heavily tested across a broad range of devices.

Some quick highlights:

Getting and Displaying a List of Time Zones

using OpenNETCF.WindowsCE;
...
// get and display all available zones
TimeZoneCollection tzc = new TimeZoneCollection();
tzc.Initialize();

foreach (TimeZoneInformation tzi in tzc)
{
    lstZones.Items.Add(tzi);
}

Displaying the Currently Set Time Zone

using OpenNETCF.WindowsCE;
...
// get and display the currrent zone

TimeZoneInformation currentTz = new TimeZoneInformation();
DateTimeHelper.GetTimeZoneInformation(ref currentTz);
lblCurrentZone.Text = currentTz.StandardName;

Setting the Current Time Zone (from the Listing above)

using OpenNETCF.WindowsCE;
...
if
(lstZones.SelectedItem != null)
{
    TimeZoneInformation tzi = (TimeZoneInformation)lstZones.SelectedItem;
    DateTimeHelper.SetTimeZoneInformation(tzi);

    // this verifies that the time zone did indeed get changed
    TimeZoneInformation tz = new TimeZoneInformation(
        (byte[])Registry.LocalMachine.OpenSubKey("Time").GetValue(
            "TimeZoneInformation"));
    MessageBox.Show("Current Timezone in Registry is:\r\n" 
        + tz.StandardName, "Verified");
}

Displaying the Current Time (without DateTime.Now)

using OpenNETCF.WindowsCE;
...
DateTime dt = DateTimeHelper.SystemTime;

txtHour.Text = dt.Hour.ToString();
txtMinute.Text = string.Format("{0:00}", dt.Minute);
txtSecond.Text = string.Format("{0:00}", dt.Second);

Setting The Current System Time (Local Time is Analogous)

using OpenNETCF.WindowsCE;
...
// get the current time so we can copy the date part

DateTime dt = DateTimeHelper.SystemTime;

DateTimeHelper.SystemTime = new DateTime(dt.Year, dt.Month, dt.Day,
    int.Parse(txtHour.Text), int.Parse(txtMinute.Text), int.Parse(txtSecond.Text));

 

2/19/2006 6:17:03 PM (Eastern Standard Time, UTC-05:00)  #    Comments [4]  | 
 Sunday, February 12, 2006

CE supports a Multimedia timer, though it's not in CF 1.0 or 2.0.  We've rectified that in SDF 2.0 (though your platform must support the Multimedia timer to use it - meaning WM and PPC are out).

So what do high-performance timers buy you?  Well if you look at how a regular timer works, they run at a really low priority and are horrible if you want anything that resembles deterministic behavior.  If you set the interval to say 1000ms, it's guaranteed to not fire in less than 1000ms, but there's actually no upper bound at all.  Jitter of 50ms (5%) would not be exceptional and in fact I've seen substantially worse on systems with a high load.

Our Timer2 class (name is still not finalized, so don't finalize on it) is based on the desktop's Timer class and provides things like a one-shot capability (timer fires once and never again without you having to disable it) and if you derive from it you can have it run a callback instead of raising an event.

Here's a quick example of usage:

void StartMyOneshotTimer
{
  // create a timer
  Timer2 oneShot = new Timer2();

  // make it a one-shot timer
  oneShot.AutoReset = false;

  // fire 3 seconds from enabling
  oneShot.Interval = 3000;

  // allow 10ms latitude for when it fires
  // so it will fire between now + 2995 and now + 3005
  oneShot.Resolution = 10;

  oneShot.Elapsed += new ElapsedEventHandler(oneShot_Elapsed);

  oneShot.Start();
}

void oneShot_Elapsed(object sender, ElapsedEventArgs e)
{
  // do something here
}

2/12/2006 2:39:02 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Saturday, February 04, 2006

You might have noticed that SDF 2.0 no longer has serial or GPS classes.  This is intentional.  CF 2.0 now has serial classes and the Windows Mobile AKU includes GPS support.  We realize that there are some of you out there that are using GPSes on non-WM 5.0 devices and that you'd like to be able to use our stuff under Studio 2005.  For those people, we've spun the Serial and GPS classes into a stand-alone assembly: OpenNETCF.IO.Serial.  There are no plans for an installer for this assembly, and unless we get strong feedback it probably won't make it into the SDF either.

2/4/2006 2:41:38 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Thursday, February 02, 2006

If you're a Windows CE device developer and you've got a device with a persistent registry, you're probably already aware that the CF Registry class doesn't help much for saving the registry, restoring branches from a file or creating volatile keys.  Once again the SDF is here to help with CreateVolatileSubkey, RestoreHiveBasedKey, RestoreRamBasedRegistry, SaveHiveBasedKey and SaveRamBasedRegistry.

One note - the doc says they're in the Registry2 class - that's already been changed to RegistryHelper.

 

2/2/2006 11:31:28 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Sunday, January 29, 2006

Making apps power aware, especially through the device Power Manager works, but the code is kind of ugly and a pain to implement.  Once again SDF 2.0 simplifies things:

using System;
using System.Windows.Forms;
using OpenNETCF.WindowsCE;

namespace WindowsCETest
{
  public partial class MyPowerAwareClass
  {
    public MyPowerAwareClass()
    {
      DeviceManagement.DeviceWake +=
       new DeviceNotification(
DeviceManagement_DeviceWake);

      PowerManagement.PowerUp += new DeviceNotification(PowerManagement_PowerUp);
    }

    void PowerManagement_PowerUp()
    {
      MessageBox.Show("The Power Manager says I'm awake!");
    }

    void DeviceManagement_DeviceWake()
    {
      MessageBox.Show("Device notifications say I'm awake!");
    }
  }
}

1/29/2006 9:56:53 AM (Eastern Standard Time, UTC-05:00)  #    Comments [4]  | 
 Saturday, January 28, 2006

The Windows CE operating system supports several notifications for common device events liek changes in AC power, network status changes and time changes.  They are exposed by using the CeRunAppAtEvent or CeSetUserNotification APIs.  While the Windows Mobile Notification Broker provides an interface for some of these, it doesn't provide access to all of them, nor is it available for general Windows CE developers.

SDF 1.4 provided a set of Notification classes that could be used to get these (and those classes are still there in SDF 2.0) but we felt that a simple object model around these would really be a nice thing to have.  So we created the OpenNETCF.WindowsCE.DeviceManagement class.  Now subscribing to the notifications is as simple as this example of detecting when the device time has been modified:

using System;
using System.Windows.Forms;
using OpenNETCF.WindowsCE;

namespace WindowsCETest
{

public class MyClass
{

public MyClass()
{

    DeviceManagement.TimeChanged +=
       
new DeviceNotification(DeviceManagement_TimeChanged);
}

void
DeviceManagement_TimeChanged()
{
    MessageBox.Show(
"The time was just changed.");
}

}

}

1/28/2006 6:26:51 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1]  | 
 Saturday, January 21, 2006

We're extremely close to a beta release of SDF 2.0.  To give you a taste of what's in it, we've posted the online documentation.  Any feedback is appreciated.  Enjoy.

1/21/2006 11:49:17 PM (Eastern Standard Time, UTC-05:00)  #    Comments [3]  | 
 Tuesday, December 20, 2005

So if you're working with Microsoft's new Microsoft.WindowsMobile.PocketOutlook namespace and trying to use the OutlookCollection.Restrict method to filter by a Contact's (or any PimItem's) ItemId field, you will likely run into a problem.

First, let me say the ItemID Class sucks.  It exposes basically nothing useful but a ToString() method, even though it holds numeric data.  And the first item added to POOM gets the ItemId of 0x80000001, which you might note is an *unsigned* number (again, the CLS is a pain for not allowing unsigned numbers).  So if you have an ItemId class and you want to use it, you have to do something like this:

unchecked
{
    int myId = int
.Parse(m_outlookSession.Contacts.Items[0].ItemId.ToString())
}

Really, that's how you have to do it.

So, let's say we have a Contact's ItemId and we want to see if the current session has said Contact.  One might try this:

m_outlookSession.Contacts.Items.Restrict("[ItemId]=" + itemId.ToString());

A nice try, but that gives you the not-so-helpful exception:

The query string is incorrectly formatted.
Parameter name: [ItemId]=-2147483647

Alright, so ItemId only exposes itself as a string, and Restrict has little in the way of useful documentation or samples, so maybe we can try it as a string like so:

m_outlookSession.Contacts.Items.Restrict("[ItemId]='" + itemId.ToString() + “'“);

Well that gives a similar exception, just adding the single quotes:

The query string is incorrectly formatted.
Parameter name: [ItemId]=-'2147483647'

Because I've used POOM from C++, I know that ItemId seems new to me, so just as a lark I figure I'll try 'Oid' as a field name instead, and keep it as a numeric:

m_outlookSession.Contacts.Items.Restrict("[Oid]=" + itemId.ToString());

Lo and behold, success!

Now technically this might not be a true bug, but it's sure not documented anywhere, nor would it be at all intuitive to guess if you'd not used C++ to access an IContact (which exports an Oid field, *not* an ItemId) before.  Bad Windows Mobile Team.  BAD!

Things needed:

  • The ItemID needs to have an explicit operator for conversion to an int at the very least.  A ToInt32 or ToIntPtr or something of the sort would be useful.
  • Either name the field Oid like it's stored in the database, or provide some sort of substitution so when I filter by ItemId the underlying class converts that to Oid.
  • The least they could have done was give exception text like “The field cannot be found in the collection“
  • Documenting how to remove a Restrict once set would be useful (I used Restrict(“[Oid]<>0“) for lack of any better idea)
12/20/2005 10:15:43 PM (Eastern Standard Time, UTC-05:00)  #    Comments [4]  | 
 Tuesday, December 13, 2005

Steven Pratschner has posted a really good blog entry on how the CF 2.0 CLR works.  Take a look.

12/13/2005 6:11:42 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]  | 
 Friday, December 09, 2005
 Tuesday, November 29, 2005

The Crime:  My CE device runs a CF app just fine when I deploy it manually, but once I connect to it from Studio for deployment and then cycle the power, not just my app, but all CF apps no longer work.  Reflashing the image on the device repairs the sitation.  What the hell is going on?

The Evidence:
Article #1: Studio saves connection security info in the device registry
Article #2: Studio saves connection security info in RAM of the device (in the root folder)
Article #3: When a CAB file is expanded, RegFlushKey is called
Article #4: When Studio deploys the CF, it does so as a CAB
Article #5: When a device is reset all RAM is lost
Article #6: The CF requires that the info in RAM and the registry match to launch

Verdict:
When I make a connection some info is stored on the device in RAM and the registry.  When I deploy, half that is persisted.  When I reset the device or pull power the other half is lost, causing all CF apps to no longer run on the device unless I erase the persistent registry (which depending on your device may mean reflashing the whole damned thing).
 
Sentence:
Run DelCryptoKeysDevice.exe (part of Windows CE Utilities for Visual Studio .NET 2003 Add-on Pack 1.1) to erase the info from the registry.  Doing it on startup by adding it to your imag