Blog

What does the internet think about...

The war is settled! After years of not knowing, the definitive answer is finally clear:

The internet thinks this about Windows CE and this about Linux.

;)

Communicating with your MicroFramework Application over USB

Version 3.0 of the MicroFramework SDK (finally!) supports configuring USB for other functions than just debug. This means you can now use USB as a communications medium for your applications. Microsoft ships a sample that shows one of those possibilities: Transforming your device into a mouse. In this blog post I'll show a more useful solution: How to setup a MicroFramework device so you can send and receive data over USB to and from the device.

Independent hardware vendors (IHVs) who manufacture USB devices must typically provide a way for applications to access the device’s features. Historically, this has meant using the Windows® Driver Model (WDM) to implement a function driver for the device and installing the driver in the device stack. However, drivers require a significant development effort, and some devices are simple enough that they do not require the full support of a custom function driver.
The USB functionality we'll be developing will be simple, so writing a full function driver will be overkill. Luckily Microsoft introduced the user-mode driver framework (UMDF) and part of this framework is WinUSB. WinUSB was developed concurrently with the Windows Driver Foundation (WDF) and is available for Windows XP and later versions of Windows. It includes a kernel-mode driver, WinUsb.sys, which is an integral part of the WDF-UMDF support for USB drivers. For USB devices that are accessed by only a single application, you can often install WinUsb.sys as the device’s function driver instead of implementing a custom driver. The application can then configure the device and access its endpoints by using the WinUSB API, and that's exactly what I'll do in this example!

WinUSB consists of two primary components:

  • WinUsb.sys is a kernel-mode driver that can be installed as either a filter or function driver, above the protocol drivers in a USB device’s kernel-mode device stack.
  • WinUsb.dll is a user-mode DLL that exposes the WinUSB API. Applications can use this API to communicate with WinUsb.sys when it is installed as a device’s function driver.

For devices that do not require a custom function driver, WinUsb.sys can be installed in the device’s kernel-mode stack as the function driver. User-mode processes can then communicate with WinUsb.sys through a set of device I/O control requests. The WinUSB API, exposed by WinUSB.dll, simplifies this communication process. Instead of constructing device I/O control requests to perform standard USB operations such as configuring the device, sending control requests, and transferring data to or from the device, applications call equivalent WinUSB API functions. Internally, WinUsb.dll uses the data that the application passes to the WinUSB function to construct the appropriate device I/O control request and sends the request to WinUsb.sys for processing. When the request is complete, the WinUSB function passes any information returned by WinUsb.sys such as data from a read request—back to the calling process.

Using the WinUSB API to communicate with a device is much simpler than implementing a driver, but has some corresponding limitations:

  • The WinUSB API allows only one application at a time to communicate with the device. If more than one application must be able to communicate concurrently with a device, you must implement a function driver.
  • The WinUSB API does not support streaming data to or from isochronous endpoints. Isochronous transfers require a kernel-mode function driver.
  • The WinUSB API does not support devices that already have kernel-mode support. Examples of such devices include modems and network adaptors, which are supported by the telephony API (TAPI) and NDIS, respectively.
  • For multifunction devices, you can use the device’s INF to specify either an in-box kernel-mode driver or WinUsb.sys for each USB function separately. However, you can specify only one of these options for a particular function, not both.

WinUSB is natively supported on all Windows Vista SKU's and is supported on all SKU's of the 32 bit versions of Windows XP SP2 and later service packs. WinUSB is not native to Windows XP; it must be installed with the WinUSB co-installer.

The WinUSB co-installer package can be found in the Windows Driver Kit under the WinDDK\BuildNumber\Redist\Winusb folder. The DLLs are signed and can be redistributed by IHVs. So if you want to develop an application that talks to your MicroFramework device over USB under XP, you'll have to install the WinDDK. After you've completed your development and are ready to ship your device your customers of course don't have to install the WinDDK to use your application because you'll ship your device with an installer that includes the redistributable co-installer package.

Read "How to Get the WDK and the WLK" to obtain the latest WDK version.

Before your application can use the WinUSB API to communicate with your device, you must install WinUsb.sys as the device’s function driver. To do so, you have to create a package that includes:

  • The WinUSB co installer, which installs WinUSB on the target system, if necessary. The WDK includes two versions of the co-installer, one for x86 systems and one for x64 systems. They are both named WinUSBCoInstaller.dll and are located in the WinDDK\BuildNumber\redist\winusb folder.
  • The Kernel Mode Driver Framework (KMDF) co installer, which installs the correct version of KMDF on the target system, if necessary. This co-installer is required because WinUsb.sys depends on KMDF. The x86 and x64 versions of WdfCoInstaller01005.dll are included with the WDK under the WinDDK\BuildNumber\redist\wdf folder (but you need WDK version 6001.18002 or higher).
  • An INF that installs WinUsb.sys as the device’s function driver.
  • A signed catalog file for the package. This file is required to install WinUSB on x64 versions of Windows Vista. For more information on how to create and test signed catalog files, see "Kernel-Mode Code Signing Walkthrough". Note that this document states that Inf2Cat is not currently part of the WDK tools, but in fact it is (you can find it in \WinDDK\BuildNumber\bin\SelfSign).

Before we can create the INF, let's define what we want our MicroFramework device to do, and develop the device side code!

In this example we want to keep it as simple as possible; all we want is two-way communication with our device. The best option for this is to create 2 bulk endpoints; one for reading and one for writing data. I'll be using the DeviceSolutions TahoeII board to develop the application on.

[Note: Download all code below]

Let's start coding!

  1. Start Visual Studio 2008
    I'm assuming you have Visual Studio 2008 with SP1, the .NET MicroFramework SDK v3.0 and the TahoeII SDK v3.0 installed.
  2. Create a new MicroFramework Console Application
  3. First let's add the needed references. Right click on your project in the solution explorer and select "Add reference...". Now add the following references:
    • Microsoft.SPOT.Hardware.Usb
  4. Open program.cs and add the following to the top of the file:
    using Microsoft.SPOT.Hardware.UsbClient;
  5. Add the following constants to class Program:
    private const int WRITE_EP = 1;
    private const int READ_EP = 2;
    We use these constants so we can easily change the used endpoints if needed.
  6. Delete the standard Debug.Print line from the function Main()
  7. Now we add code to function Main() to detect the number of USB Controllers supported by the hardware:
    // See if the hardware supports USB
    UsbController[] controllers = UsbController.GetControllers();

    // Bail out if USB is not supported on this hardware!
    if (0 == controllers.Length)
    {
        Debug.Print("USB is not supported on this hardware!");
        return;
    }
  8. At this moment the .NET MicroFramework 3.0 does not support changing the USB configuration on the fly so using USB for application communication and Visual Studio Debugging is not possible when configuring USB from C# code running on the device. If you update the device's USB descriptors through XML using MFDeploy it is possible to debug and communicate at the same time (over multiple USB interfaces descriptors that is! More about that in a follow up post about CDC).
    // Find a free USB controller
    UsbController usbController = null;
    foreach (UsbController controller in controllers)
    {
        if (UsbController.PortState.Stopped == controller.Status)
        {
            usbController = controller;
            break;
        }
    }

    // If no free USB controller
    if (null == usbController)
    {
        Debug.Print("All available USB controllers already in use. Set the device to use Ethernet or Serial debugging.");
        return;
    }
    Setting the TahoeII to use Ethernet or serial debugging is easy; just hold SW4 while resetting the device for debugging over Ethernet, or hold SW2 for debugging over serial. If you set it to debug over Ethernet then don't forget to configure the TahoeII's Ethernet settings using MFDeploy (see "Configuring Ethernet on the Tahoe-II" in the TahoeII SDK documentation -> press F1 in Visual Studio)
  9. If we found a free USB controller we can now start to configure that controller. We'll put the configuration code in a separate function to keep it all together. Add the following function to the Program class:
    1. private static bool ConfigureUSBController(UsbController usbController)
    2. {
    3.     bool bRet = false;
    4.  
    5.     // Create the device descriptor
    6.     Configuration.DeviceDescriptor device = new Configuration.DeviceDescriptor(0xDEAD, 0x0001, 0x0100);
    7.     device.bcdUSB           = 0x110;
    8.     device.bDeviceClass     = 0xFF;     // Vendor defined class
    9.     device.bDeviceSubClass  = 0xFF;     // Vendor defined subclass
    10.     device.bDeviceProtocol  = 0;
    11.     device.bMaxPacketSize0  = 8;        // Maximum packet size of EP0
    12.     device.iManufacturer    = 1;        // String #1 is manufacturer name (see string descriptors below)
    13.     device.iProduct         = 2;        // String #2 is product name
    14.     device.iSerialNumber    = 3;        // String #3 is the serial number
    15.  
    16.     // Create the endpoints
    17.     Configuration.Endpoint writeEP = new Configuration.Endpoint(WRITE_EP, Configuration.Endpoint.ATTRIB_Bulk | Configuration.Endpoint.ATTRIB_Write);
    18.     writeEP.wMaxPacketSize  = 64;
    19.     writeEP.bInterval       = 0;
    20.  
    21.     Configuration.Endpoint readEP = new Configuration.Endpoint(READ_EP, Configuration.Endpoint.ATTRIB_Bulk | Configuration.Endpoint.ATTRIB_Read);
    22.     readEP.wMaxPacketSize   = 64;
    23.     readEP.bInterval        = 0;
    24.  
    25.     Configuration.Endpoint[] usbEndpoints = new Configuration.Endpoint[] { writeEP, readEP };
    26.  
    27.     // Set up the USB interface
    28.     Configuration.UsbInterface usbInterface = new Configuration.UsbInterface(0, usbEndpoints);
    29.     usbInterface.bInterfaceClass    = 0xFF; // Vendor defined class
    30.     usbInterface.bInterfaceSubClass = 0xFF; // Vendor defined subclass
    31.     usbInterface.bInterfaceProtocol = 0;
    32.  
    33.     // Create array of USB interfaces
    34.     Configuration.UsbInterface[] usbInterfaces = new Configuration.UsbInterface[] { usbInterface };
    35.    
    36.     // Create configuration descriptor
    37.     Configuration.ConfigurationDescriptor config = new Configuration.ConfigurationDescriptor(180, usbInterfaces);
    38.  
    39.     // Create the string descriptors
    40.     Configuration.StringDescriptor manufacturerName = new Configuration.StringDescriptor(1, "GuruCE");
    41.     Configuration.StringDescriptor productName      = new Configuration.StringDescriptor(2, "MicroFramework WinUSB");
    42.     Configuration.StringDescriptor serialNumber     = new Configuration.StringDescriptor(3, "0000-0000-0000-0001");
    43.     Configuration.StringDescriptor displayName      = new Configuration.StringDescriptor(4, "MicroFramework WinUSB");
    44.     Configuration.StringDescriptor friendlyName     = new Configuration.StringDescriptor(5, "NetMF_WinUSB");
    45.  
    46.     // Create the final configuration
    47.     Configuration configuration = new Configuration();
    48.     configuration.descriptors = new Configuration.Descriptor[]
    49.     {
    50.         device,
    51.         config,
    52.         manufacturerName,
    53.         productName,
    54.         serialNumber,
    55.         displayName,
    56.         friendlyName
    57.     };
    58.  
    59.     try
    60.     {
    61.         // Set the configuration
    62.         usbController.Configuration = configuration;
    63.         if (UsbController.ConfigError.ConfigOK != usbController.ConfigurationError)
    64.             throw new ArgumentException();
    65.         // If all ok, start the USB controller.
    66.         bRet = usbController.Start();
    67.     }
    68.     catch (ArgumentException)
    69.     {
    70.         Debug.Print("Can't configure USB controller, error " + usbController.ConfigurationError.ToString());
    71.     }
    72.     return bRet;
    73. }
    The MicroFramework SDK v3.0 shipped without documentation on the new USB classes, but an extra download updating the documentation fixes that.

    Let's step through the code:

    • Line 6-14: We start by creating the device descriptor. The constructor of the Configuration.DeviceDescriptor Class has the following signature:
      public DeviceDescriptor(ushort Vendor, ushort Product, ushort DeviceVersion)
      Using parameter "Vendor" we supply the VID of the device. In this example I'm using a non-existing VID (0xDEAD), but in your final commercial device you'd have to use your own registered VID (check out http://usb.org for information on how to acquire a VID/PID). Note that in most countries using someone else's VID is considered theft and thus a criminal offense, not just a civil one. The parameter "Product" is used to supply the PID. In this case we'll just use a PID of 0x0001. The "DeviceVersion" parameter can be used to indicate device version and you are free to set this to anything you like (I set it to "1.0").
      The next 4 lines set the USB version in bcdUSB (USB 1.1), the device class, subclass & protocol. We set these to indicate our device is a custom vendor defined class; it doesn't fit in one of the predefined USB classes. The MicroFramework firmware already sets up control endpoint 0 for us so all we have to do is supply the maximum packet size of EP0. Even though the maximum packet size for EP0 is 32 bytes (see the iMXS datasheet; the processor the Meridian module on the TahoeII board is using) the TahoeII firmware sets it up as 8 bytes, so we have to set this field to 8. The iManufacturer, iProduct and iSerialNumber fields are indexes pointing to strings (that are defined in lines 40 - 44).
    • Line 17-25: Here we create our two endpoints: EP1 BULK IN (which means data flows from device to host, so we'll call this WRITE) and EP2 BULK OUT (which means data flows from host to device so we'll call this READ). The class constructor:
      public Endpoint(byte EndpointAddress, byte Attributes)
      . The EndpointAddress indicates the physical endpoint number. The attributes indicate the type of transfer, the type of synchronization and the usage type for the endpoint.
      After creating the two endpoints we store them in an array of endpoints so we can include them in the UsbInterface:
    • Line 28-34: The UsbInterface class constructor has the following signature:
      public UsbInterface(byte InterfaceNumber, Microsoft.SPOT.Hardware.UsbClient.Configuration.Endpoint[] Endpoints)
      The parameter InterfaceNumber allows us to define multiple interfaces with a different set of endpoints. In this case I define only 1 interface with 2 endpoints in the array. We set the InterfaceClass and InterfaceSubClass again to "vendor defined" and we put our interface in an array so we can include them in the configuration descriptor:
    • Line 37: The ConfigurationDescriptor class constructor has the following signature:
      public ConfigurationDescriptor(ushort MaxPower_mA, Microsoft.SPOT.Hardware.UsbClient.Configuration.UsbInterface[] Interfaces)
      The parameter MaxPower_mA is used to set the maximum power in mA. The TahoeII draws an absolute maximum of 180 mA, so that's what we set. The 2nd parameter holds our interfaces.
    • Line 40-44: Here we create our string descriptors that we point at in line 12 - 14.
    • Line 46-57: After creating the various descriptors we can now put them all together in the Configuration class.
    • Line 59-71: And finally we apply the configuration and start the USB controller.
  10. Now that we've finished that function, we can continue adding code to the end of the Main() function:
    UsbStream usbStream = null;
    try
    {   // Configure the USB controller
        if (ConfigureUSBController(usbController))
            usbStream = usbController.CreateUsbStream(WRITE_EP, READ_EP);
        else
            throw new Exception();
    }
    catch (Exception)
    {
        Debug.Print("USB stream could not be created, error " + usbController.ConfigurationError.ToString());
        return;
    }
    The code above calls the ConfigureUSBController function we created before and creates the USB stream.
  11. All we want our program to do is echo back whatever our application running on the PC sends to it. Add the following function to the Program class:
    static void USBLoop(UsbStream usbStream)
    {
        byte[] readData = new byte[100];
        for (; ; )
        {
            int bytesRead = usbStream.Read(readData, 0, readData.Length);
            if (bytesRead > 0)
            {   // echo back!
                usbStream.Write(readData, 0, bytesRead);
            }
        }
    }
  12. And finally we'll call this never ending loop by adding the following code to the end of the Main() function:
    // Start our communication loop
    USBLoop(usbStream);
    usbStream.Close();
    Of course the usbStream.Close() function will never be reached in our case, but we could add code to return from the loop if "Quit" is received for instance...
  13. Now that our program is finished we have to set up the correct debug transport to the TahoeII. Before switching the TahoeII to Ethernet you'll have to configure the Ethernet settings using MFDeploy:
    • Make sure the TahoeII is using USB as a debug transport by holding SW3 while resetting the TahoeII
    • Now start MFDeploy and select "USB" from the Device dropdown. The TahoeII should immediately pop up (as "Meridian_XXXXXXXX"). Ping the device to make sure you can reach it.
    • From the Target menu, click "Configuration" and select "Network".
    • Configure the TahoeII to match your network (set a static IP and subnetmask within range of your PC's IP or enable DHCP if you have a DHCP server on your network)
    • Now set the TahoeII to use TCP/IP as debug transport by holding SW4 while resetting the device.

    If the TahoeII is setup for TCP/IP we have to setup Visual Studio 2008 to use TCP/IP as well: Right click on your project in the solution explorer, choose "Properties" and click on the ".NET Micro Framework" tab. Select "All Configurations" from the "Configuration" dropdown list, set "Transport" to "TCP/IP" and you should see the device's IP and MAC address appearing in the "Device" dropdown list. Select the correct one (if you have more than one TahoeII connected) and close this window.

If all is well you can now press F5 to build and deploy the program. If you keep a close eye on your taskbar notification area you'll see the following notifications pop up:

And after that XP will ask you for the driver:

Since we'll be using WinUSB as the driver, all we need to do now is create the .inf file for our device. The document "How to Use WinUSB to Communicate with a USB Device" explains in detail how to do exactly that. If we modify the sample from that document according to the instructions we'll end up with an inf that looks like this:

[Version]
Signature   = "$Windows NT$"
Class       = MicroFrameworkWinUSB
ClassGuid   = {F80EAFB4-8422-446d-BC18-63B1BC21843E}
Provider    = %ProviderName%
CatalogFile = MFWinUSB.cat
DriverVer   = 05/30/2009,1.0.0.0

; ========== Manufacturer/Models sections ===========
[Manufacturer]
%ProviderName% = MicroFramework_WinUSB,NTx86,NTamd64,NTia64

[MicroFramework_WinUSB.NTx86]
%USB\MFWinUSB.DeviceDesc% = USB_Install, USB\VID_DEAD&PID_0001

[MicroFramework_WinUSB.NTamd64]
%USB\MFWinUSB.DeviceDesc% = USB_Install, USB\VID_DEAD&PID_0001

[MicroFramework_WinUSB.ntia64]
%USB\MFWinUSB.DeviceDesc% = USB_Install, USB\VID_DEAD&PID_0001

; =================== Installation ===================
;[0]
; If the class used in the Version section above is not a standard one
; (see <a href="http://msdn.microsoft.com/en-us/library/ms791134.aspx" title="http://msdn.microsoft.com/en-us/library/ms791134.aspx">http://msdn.microsoft.com/en-us/library/ms791134.aspx</a>)
; then it  must install information about the device class so it is displayed
; and processed correctly by various tools like the device manager, WMI, etc...
[ClassInstall32]
AddReg = MicroFrameworkWinUSB_addreg
 
[MicroFrameworkWinUSB_addreg]
HKR,,,,%ClassName%
HKR,,Icon,,"18"

;[1]
[USB_Install]
Include = WinUSB.inf
Needs   = WinUSB.NT

;[2]
[USB_Install.Services]
Include    = WinUSB.inf
AddService = WinUSB,0x00000002,WinUSB_ServiceInstall

;[3]
[WinUSB_ServiceInstall]
DisplayName   = %WinUSB_SvcDesc%
ServiceType   = 1
StartType     = 3
ErrorControl  = 1
ServiceBinary = %12%\WinUSB.sys

;[4]
[USB_Install.Wdf]
KmdfService      = WinUSB, WinUSB_Install

[WinUSB_Install]
KmdfLibraryVersion = 1.7

;[5]
[USB_Install.HW]
AddReg = Dev_AddReg

[Dev_AddReg]
HKR,,DeviceInterfaceGUIDs,0x10000,"{77C99034-2428-424a-8130-DC481841429B}"

;[6]
[USB_Install.CoInstallers]
AddReg    = CoInstallers_AddReg
CopyFiles = CoInstallers_CopyFiles

[CoInstallers_AddReg]
HKR,,CoInstallers32,0x00010000,"WinUSBCoInstaller.dll","WdfCoInstaller01007.dll,WdfCoInstaller"

[CoInstallers_CopyFiles]
WinUSBCoInstaller.dll
WdfCoInstaller01007.dll

[DestinationDirs]
CoInstallers_CopyFiles = 11

; ================= Source Media Section =====================
;[7]
[SourceDisksNames]
1 = %DISK_NAME%,,,\x86
2 = %DISK_NAME%,,,\amd64
3 = %DISK_NAME%,,,\ia64

[SourceDisksFiles.x86]
WinUSBCoInstaller.dll   = 1
WdfCoInstaller01007.dll = 1

[SourceDisksFiles.amd64]
WinUSBCoInstaller.dll   = 2
WdfCoInstaller01007.dll = 2

[SourceDisksFiles.ia64]
WinUSBCoInstaller.dll   = 3
WdfCoInstaller01007.dll = 3

; =================== Strings ===================
[Strings]
ProviderName            = "MicroFramework WinUSB"
USB\MFWinUSB.DeviceDesc = "MicroFramework WinUSB Device"
WinUSB_SvcDesc          = "MicroFramework WinUSB"
DISK_NAME               = "MicroFramework WinUSB Driver Disk"
ClassName               = "MicroFramework WinUSB Devices"

If you don't use one of the System-Supplied Device Setup Classes you'll have to add a ClassInstall32 section to the inf that adds the class name to the registry. Even though our device would seem to fit the USB class we can't use that class because it's only meant for USB Host controllers and hubs (not for USB peripherals).

Note that if you want to ship a device using the above inf you'll have to change the following:

  • Class: Change to something meaningful for your device
  • ClassGuid: Generate a new GUID for the class
  • CatalogFile: Change to something meaningful for your device
  • DriverVer: Change to something meaningful for your device
  • Manufacturer/Models sections: Change to something meaningful for your device and make sure the VID/PID matches your device.
  • [Dev_AddReg]: Generate a new GUID for the device
  • [Strings]: Change to something meaningful for your device

With this inf file we can now create our driver package. The drivers and tools required are all in the Windows Driver Kit, so you'll need to install that first.

After you have installed the WDK create a new folder, let's say C:\MFWinUSBDriver, and create the inf file in that folder. Now open a build environment matching your operating system by clicking the appropriate shortcut in the "Windows Driver Kits" start menu group. Now "cd" (change directory) to C:\MFWinUSBDriver.

The next step is to create a catalog file out of our inf. The process of creating a catalog file (and signing) is described in "Kernel-Mode Code Signing Walkthrough" and the tool to use is Inf2Cat. If you are planning on shipping your driver to your customers you should properly sign your catalog. For the sake of simplicity this blogpost will not discuss official signing (but will focus on getting the driver to load asap!).

As you can see, the inf file lists a couple of dll's. These dll's are part of the WDF redistributables that can be found in the WDK at this location: C:\WinDDK\6001.18002\redist

Copy the amd64, ia64 and x86 folders from C:\WinDDK\6001.18002\redist\wdf to C:\MFWinUSBDriver so that you now have the following 3 folders (with the corresponding driver files in those folders):

C:\MFWinUSBDriver\amd64
C:\MFWinUSBDriver\ia64
C:\MFWinUSBDriver\x86
Now copy the amd64, ia64 and x86 folders from C:\WinDDK\6001.18002\redist\winusb to C:\MFWinUSBDriver and click "Yes to All" in the "Confirm Folder Replace" dialog.

You should now have the following files in those 3 folders listed above:

WdfCoInstaller01007.dll
WdfCoInstaller01007_chk.dll
WinUSBCoInstaller.dll
WinUSBCoInstaller_chk.dll
WUDFUpdate_01007.dll
WUDFUpdate_01007_chk.dll

The _chk files are for checked (debug) builds of the operating system and since we won't be using those you can delete all the _chk files from the 3 folders listed above.
You can also remove the WUDFUpdate*.dll files because we are going to be talking to the WinUSB driver directly from our application. If you want to write a UMDF driver instead you'll need to add the UMDF coinstaller and have all of the appropriate entries in the inf (like UmdfServiceOrder). The ZIP file contains an example inf file for UMDF as well.

Now that everything is in place we can finally call Inf2Cat:

inf2cat /driver:C:\MFWinUSBDriver /os:XP_X86,Server2003_X86,Vista_X86,XP_X64,Server2003_X64,Vista_X64,Server2003_IA64
If all went well the inf2cat output should show:
............................
Signability test complete.

Errors:
None

Warnings:
None

Catalog generation complete.
C:\MFWINUSBDRIVER\mfwinusb.cat

If you still have the "Found New Hardware Wizard" open you can now point the wizard to C:\MFWinUSBDriver and have it install the driver. If you don't have the wizard open anymore; go to the Device Manager, delete the unknown USB device from the "Other devices" node and re-attach (or reset) the TahoeII.





Now everything is ready and we can start the development of the PC application. Fortunately Jan Axelson already developed an application in C# that can talk to devices utilizing WinUSB. He provides full source code and it's a real good starting point for your own applications. Download the latest version from his WinUSB page.

Download and extract winusb_cs_xxx.zip to a folder of your choice and load the solution in Visual Studio 2008. Now open the frmMain.cs code and change the WINUSB_DEMO_GUID_STRING to match our inf file: "{77C99034-2428-424a-8130-DC481841429B}".

Now press F5 to run the solution. Enter some text in the "Bulk Transfers" textbox and click on the "Send" button:

Eureka! The TahoeII is echoing back whatever we send to it. You can download the MicroFramework code and the MFWinUSB Driver here.

I'll try to follow up this article with an article showing how to setup the TahoeII as a USB Communication Device Class (CDC) so that you can communicate with your device without using WinUSB (and without having to sign your "driver").


Good luck in creating your next USB connected device running the MicroFramework!

Some portions of the text in this article were taken (and modified/updated/added to where necessary) from "How to Use WinUSB to Communicate with a USB Device".

Creating an OS Design containing VoIP in CE R2

If you try to sysgen a Windows CE R2 OS Design that contains the VoIP components you'll encounter an "error in sysgen phase" when sysgenning FP_VOIP. Build.log will show the error occurred when trying to link phsettings.exe:

phsettings.exe : fatal error LNK1120: 4 unresolved externals

The reason for this is an error in the "sources." file of \WINCE600\PUBLIC\FP_VOIP\OAK\PHONE\COMMON\UTILS:

SSOURCES= \
    DisplayItem.cpp      \
    GDICache.cpp         \
    LogoScreen.cpp       \
    LineHelper.cpp       \
    NetworkUtils.cpp     \
    PaintHelper.cpp      \
    Poom.cpp             \
    RingtoneIterator.cpp \
    SecurityUtils.cpp  

\
    SystemTimeUtils.cpp  \
    Timers.cpp           \
    TimeUtilities.cpp

No files after SecurityUtils.cpp will be built because of the extra return (actually a couple of funny characters messing up the build parser). The shipped libraries were built using this faulty "sources." file, hence the unresolved external symbols when phsettings links to voip_common.lib.

What I'm going to tell you now is against all my beliefs: To (quickly) fix this, you'll have to modify some files in the PUBLIC tree (did I really say that?!)... Well, at least until Microsoft releases the QFE (I've opened a support incident with Microsoft and they are working on fixing this bug at this very moment).

First copy \WINCE600\PUBLIC\FP_VOIP to \WINCE600\ORG_PUBLIC_FP_VOIP (so that you can restore the original FP_VOIP folder when Microsoft releases the QFE).

Now change the "sources." file in \WINCE600\PUBLIC\FP_VOIP\OAK\PHONE\COMMON\UTILS to match this:

!if 0
Copyright (c) Microsoft Corporation.  All rights reserved.
!endif
!if 0
Use of this sample source code is subject to the terms of the Microsoft
license agreement under which you licensed this sample source code. If
you did not accept the terms of the license agreement, you are not
authorized to use this sample source code. For the terms of the license,
please see the license agreement between you and Microsoft or, if applicable,
see the LICENSE.RTF on your install media or the root of your tools installation.
THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
!endif

TARGETNAME=voip_common
TARGETTYPE=LIBRARY
RELEASETYPE=OAK
WINCEOEM=1

WINCEATL80=1

INCLUDES=\
    ..\inc; \
    ..\..\inc; \
    $(_PUBLICROOT)\wceappsfe\sdk\inc; \
    ..\ResourceUtils; \
    $(_PUBLICROOT)\common\ddk\inc;

SOURCES= \
    DisplayItem.cpp      \
    GDICache.cpp         \
    LogoScreen.cpp       \
    LineHelper.cpp       \
    NetworkUtils.cpp     \
    PaintHelper.cpp      \
    Poom.cpp             \
    RingtoneIterator.cpp \
    SecurityUtils.cpp    \
    SystemTimeUtils.cpp  \
    Timers.cpp           \
    TimeUtilities.cpp    

Once you've done this, browse to PUBLIC\FP_VOIP in the Solution Explorer (in Visual Studio 2005 with your OS Design open), right click on FP_VOIP and choose "Rebuild" from the context menu. After the rebuild has completed you can sysgen your OS Design successfully.

Unfortunately when you run your kernel you will encounter some more bugs in FP_VOIP... When you add the portrait resources the settings application (phsettings.exe) won't start because it can't find the correct menu structure (breaks at networkdnssettings.cpp line 330 (function NetworkDNSSettingsDialog_t::CreateSpinnerDisplayItem) and when you add the landscape resources the settings application will run correctly, but the homescreen application won't show any text on the buttons (and the buttons don't work).

So, the out-of-the-box applications don't work as expected, but again, Microsoft will release a QFE fixing these issues soon. In the meantime you can play around with it (just make sure you select the landscape resources) by manually starting phsettings.exe so you can setup VoIP. You can even make a phone call (wow! ;), but you'll have to manually start the correct applications.

Seems like this R2 VOIP stuff was never really tested...

CE XAML

I've worn out the newsgroups...

Yes, it's official: I've posted too many answers to the newsgroups in the past 9 years.

They blacklisted me! No, just kidding (I hope). I'm not sure what is going on, but the reason for me being really quiet on the newsgroups is because my posts seem to be blocked by the NNTP server...

If I try to post using any newsreader (I use Thunderbird but also tried Agent) my posts get sent correctly but never appear on the server. If I try to post using the web front-end on microsoft.com, my posts do show up on the web front-end but again never make it to the NNTP server (so they are not being propagated to my (and your) newsreader and also don't appear in Google's Group archive/DejaNews). I've even tried the shadowing news server of my ISP provider. My posts do show up on the news server of my provider, but again don't make it to the news.microsoft.com server.

For example these posts are on the web, but not on the NNTP server:
how to use .CAB file provided by driver vendor
USB unmount in CE 6.0
QFE updates
Strange PM timeouts behaviour

And since nobody is responding to my posts I can only conclude nobody is actually using the web front-end on microsoft.com, well, except for this user (because he responded to my post, yeah!):
Parallel port interrupt

So it all points to me being blacklisted... Maybe there's a spam rule: "if [user's post count] > 10000; "it must be a spambot; block it!"

Microsoft is looking into it, so hopefully I'm back soon...

If you have seen the same behavior or may know what's going on, please leave a comment or send me an email (through the contact form on this site)

UPDATE: It indeed seems to be a spam filter thing. I tried changing my email address but that didn't seem to help. Then I tried changing my "Display Name" as well and now I can post again. If you find yourself unable to post; change both your email address (you don't want to use your real email address on NG's anyway because of email-harvester-bots) and your display name and you should be good to go again!

Programmatically instantiate the password login dialog

In Windows CE there is built in functionality that allows a user to lock and login the system, just like you are used to on "big" desktop systems. To activate this functionality you simply set the system password in Windows CE through the control panel applet. You can also do this programmatically using the SetPassword() and SetPasswordStatus() API's.

When the password is set at boot the system will show the standard Windows CE login screen:

Recently I got a question whether it is possible to programmatically bring up the password dialog. After some analysis the answer was yes. I found out that the Power Manager instantiates the password dialog when the system returns from showing a screensaver. The password dialog is instantiated by calling ShowStartupWindow(). Here's the code used by the Power Manager (from "<WINCEROOT>\PUBLIC\COMMON\OAK\DRIVERS\PM"):

hmCoreDll = (HMODULE) LoadLibrary(_T("coredll.dll"));
gpfnShowStartupWindow = (PFN_ShowStartupWindow) GetProcAddress(hmCoreDll, _T("ShowStartupWindow"));

The example below shows you how to use the exact same technique inside your applications to instantiate the password dialog:

#include <windows.h>

// We need to declare these functions here because they are defined
// in pwinbase.h (contrary to what MSDN help for "SetPassword" tells
// us!) and pwinbase.h is not included as an SDK header file.
// Therefore to use these functions without needing access to all
// Platform Builder include files, we have to define them ourselves.
extern "C"
{
    BOOL SetPassword (LPWSTR lpszOldpassword, LPWSTR lspzNewPassword);
    BOOL SetPasswordStatus(DWORD dwStatus, LPWSTR lpszPassword);
}

// Forward declarations
bool InitPassword(LPWSTR pszOldPassword, LPWSTR pszNewPassword);

// Defines
#define PASSWORD_STATUS_ACTIVE              1
#define PASSWORD_STATUS_SCREENSAVERPROTECT  2

// Typedef'ed functions
typedef BOOL (WINAPI *PFN_ShowStartupWindow)(void);

int _tmain(int argc, _TCHAR* argv[])
{
    BOOL bRet = false;
    HMODULE hCoreDll = NULL;
    PFN_ShowStartupWindow pfnShowStartupWindow = NULL;

    // Create and activate password
    // (you can supply the current ("old") password on the command line)
    LPWSTR pszOldPassword = (argc > 1) ? argv[1] : NULL;
    if (InitPassword(pszOldPassword, L"password"))
    {
        hCoreDll = (HMODULE) LoadLibrary(L"coredll.dll");
        if (hCoreDll)
        {
            pfnShowStartupWindow = (PFN_ShowStartupWindow)GetProcAddress(hCoreDll, L"ShowStartupWindow");
            if (pfnShowStartupWindow)
                bRet = pfnShowStartupWindow();
            else
                RETAILMSG(1, (L"Failed to get address of \"ShowStartupWindow\" in coredll.dll, error %d\r\n", GetLastError()));
            FreeLibrary(hCoreDll);
        }
        else
            RETAILMSG(1, (L"Failed to load coredll.dll, error %d\r\n", GetLastError()));
    }
    return (int)bRet;
}

bool InitPassword(LPWSTR pszOldPassword, LPWSTR pszNewPassword)
{
    bool bRet = false;
    // Please note that the password must be lower case when you set it!
    // The password dialog box (code in startui.cpp) always converts the
    // user entered password to lower case before the compare!
    if (!SetPassword(pszOldPassword, pszNewPassword))
        RETAILMSG(1, (L"Failed to set the password, error %d\r\n", GetLastError()));
    //Set the password status
    else if (!SetPasswordStatus(PASSWORD_STATUS_ACTIVE | PASSWORD_STATUS_SCREENSAVERPROTECT, pszNewPassword))
        RETAILMSG(1, (L"Failed to set the password status, error %d\r\n", GetLastError()));
    else
        bRet = true;
    return bRet;
}

Of course you can always create your own password dialog (because let's be honest; it's not the most beautiful dialog you've ever seen, right?!), but if you don't mind the ugly box the technique above just uses the existing functionality of CE and will save you a bit of time developing a password dialog yourself.

From the comments in the code above you can see I found a rather interesting little fact: Windows CE passwords are CASE INSENSITIVE! That's old-school, isn't it?!

The code that handles the GUI portion of the password handling code is in startui.cpp (here "<WINCEROOT>\PUBLIC\COMMON\OAK\DRIVERS\STARTUI"):

//      Get text from password window.
SendMessage(hwndPass, WM_GETTEXT, PASSWORD_LENGTH + 1, (LPARAM)szText);
_wcslwr(szText);
isAuthValid = (CheckPassword(szText) != FALSE);

The _wcslwr function converts the password string to lowercase before checking it, but SetPassword allows you to set a password using mixed case! Keep that in mind next time you locked yourself out of your Windows CE device... ;o)

PB 5.0 crash hang dead

Today all of a sudden Platform Builder 5.0 didn't want to load my workspace anymore. Just before I updated the machine using Windows Update so I figured it must have had something to do with that. Platform Builder would start loading the workspace, show "Refreshing the Catalog" in the status bar, then show "Getting variables that correspond with catalog items" and then it would just sit there consuming 50% CPU and not responding to anything else. Luckily I'm using VMWare for all my CE development work so it was easy to revert back to an earlier snapshot before I updated XP. Unfortunately, this didn't solve the problem! I continued my search and after some frustrating hours I found the cause:

A bit earlier in the day I added a whole lot of header files (121 to be precise) to one of my subprojects sources file (using the FILE_VIEW_INCLUDES_FOLDER macro), and this proved to be the problem. After I reverted the sources file back to its previous revision in SVN the project loaded again.

Another one of those Platform Builder 5.0 mysteries solved...

Manual Clone of Public Code

If sysgen_capture doesn't work for some reason you can always manually clone the component you want to modify. By following the instructions on the blog post "Cloning Public Code: An Example" you should be able to get this working, but to help you a bit more here are some step by step instructions for manually cloning public code.

In this blogpost we'll be cloning TELNETD because for some reason sysgen_capture doesn't work for this component. TELNETD is located under the WINCE project tree "servers". I know the exact name of the WINCEPROJ by opening up the makefile in \PUBLIC\SERVERS\CESYSGEN and searching for WINCEPROJ. The correct sysgen_capture command for TELNETD would be:

sysgen_capture -p servers telnetd

The -p parameter indicates the WINCEPROJ, so in this case it's of course not "common", but "servers". Unfortunately for some reason this command doesn't output a sources.telnetd file (in fact it doesn't output any file!). No stress, let's just clone manually:

  1. Copy the TELNETD folder to your OS Design (\WINCE500\PBWorkspaces\<MyOSDesign>\TELNETD or \WINCE600\OSDesigns\<MyOSDesign>\<MyOSDesign>\TELNETD)
  2. Open the sources. file in the folder you just copied
  3. Set TARGETTYPE to DYNLINK
  4. Set RELEASETYPE to LOCAL
  5. Delete WINCETARGETFILE0=$(_RELEASELIBDIR)\$(TARGETDEFNAME).def
  6. Open the makefile. in <WINCEROOT>\PUBLIC\SERVERS\CESYSGEN and search for telnetd
  7. Copy the line @set TARGETLIBS... and @set DLLENTRY...
  8. Paste into your sources. file
  9. Change @set TARGETLIBS... in your sources. file to:
    TARGETLIBS=$(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\ws2.lib \
               $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\coredll.lib \
               $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\ceosutil.lib  \
               $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\authhlp.lib
  10. Change @set DLLENTRY... in your sources. file to:
    DLLENTRY=DllEntry
  11. Add the include paths:
    _OEMINCPATH=$(_WINCEROOT)\public\common\oak\inc; $(_WINCEROOT)\public\common\sdk\inc; $(_WINCEROOT)\public\common\ddk\inc
    NOTE: DO NOT PUT SPACES BETWEEN THE PATHS! I know it is confusing because I do it above, but I have to to support line breaking (otherwise this page will look ugly...).
  12. And build...

Easy as that! Now that you have the TELNETD component successfully cloned you can continue following the instructions from step 7 in the blog post "Cloning Public Code: An Example" (of course replacing "netui" with "telnetd" in the text).

Here's the complete sources. file for reference (don't forget to delete the spaces in the _ISVINCPATH, _OEMINCPATH and INCLUDES macros! I need those spaces here to format the HTML nicely, but you will get compiler error "/I" requires an argument if you leave those spaces):

WINCEOEM=1
_OEMINCPATH=$(_WINCEROOT)\public\common\oak\inc; $(_WINCEROOT)\public\common\sdk\inc; $(_WINCEROOT)\public\common\ddk\inc

TARGETNAME=telnetd
TARGETTYPE=DYNLINK
RELEASETYPE=LOCAL

TARGETDEFNAME=$(TARGETNAME)
DEFFILE=$(TARGETNAME).def
CDEFINES=$(CDEFINES) -DwinCE
DLLENTRY=DllEntry

SOURCES=telnetd.cpp telndev.cpp consemu.cpp

TARGETLIBS=$(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\ws2.lib \
           $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\coredll.lib \
           $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\ceosutil.lib  \
           $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\authhlp.lib

Cloning public code: An example

For some strange reason, people are still changing public code and doing build and sysgen's on their Windows CE tree (read this to learn why this is a bad thing). Changing code in the PUBLIC and PRIVATE trees might seem like a shortcut but actually it is everything but a shortcut. When you clone the code before changing it in place you will save yourself hours and hours of build time. A targeted build of a cloned driver takes seconds, a clean build and sysgen takes hours.

Cloning is not difficult at all, so there is really no reason to change PUBLIC or PRIVATE code. To prove that it is not difficult (once you know what you are doing) I will show you how to clone PUBLIC code in this blog post. Note that there is an article by Steve Maillet and Mike Hall in MSDN about cloning public code too. They split the clone into a "LIB" part and a "DLL" part. I don't really like that method; I just merge the sources file into one that creates the DLL in one go. Have a look at the "split" method here.

I chose to clone NETUI, since this component is responsible for a lot of dialog boxes that you may want to change to match your systems specifications.

I am cloning NETUI on a Windows CE 6.0 tree, but it works exactly the same on a Windows CE 5.0 tree (apart from the OSDesign folder which is called PBWorkspaces in CE 5.0).

Prerequisites: A sysgenned OSDesign with the "Network UI" component included.

Let's start!

  1. Copy the entire \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\NETUI folder to your OSDesign, for instance \WINCE600\OSDesigns\MyOS\MyOS
  2. Open a build window (in Platform Builder / VS2005 click "Open Release Directory in Build Window" from the menu "Build"
  3. In the build window, type:
    cd..
    cd..
    cd netui
  4. Now type:
    sysgen_capture -p common netui
    This command will output a set of files in the current directory.
  5. Since we only want to build netui, you can delete:
    sources.btdrt
    sources.ceddk
    sources.iphlpapi
    sources.k.iphlpapi
    sources.ws2
    sources.ws2k
  6. Now intelligently merge sources. with sources.netui (we make the changes in the sources. file):
    • Make a backup of the sources. file (copy to sources.org) so you can always go back if needed
    • We are trying to build a DLL, so change TARGETTYPE=LIBRARY to TARGETTYPE=DYNLINK
    • We are building NETUI as a project in our workspace, so set the RELEASETYPE to LOCAL (RELEASETYPE=LOCAL)
    • We are not building a LIB anymore, so there's no need to preprocess the def file. Remove the following lines:
      WINCETARGETFILE0=$(_RELEASELIBDIR)\$(TARGETDEFNAME).def
      PREPROCESSDEFFILE=1
    • And add this line to tell the build system to use the local def file for the exports:

      DEFFILE=$(TARGETDEFNAME).def

    • We don't need the resource file later (because we are not creating a lib that has to be transformed into a dll later), so remove the COPYRES=1 line and the WINCETARGETFILES line pointing to the res.
    • OSDesign SubProjects get build last, so we don't need to copy SYNCHRONIZE_DRAIN=1 from sources.netui to our sources. file
    • Since we are now building a DLL, copy the DLLENTRY line from sources.netui to sources.
    • Now move the entire SOURCELIBS and TARGETLIBS from sources.netui to sources.
    • and delete any references to netui (netui.lib/netui.res) itself (because we are building that now)
    • Change the paths to the target libraries from using the PUBLIC sysgened libraries to the libraries sysgened to your workspace, so change $(_SYSGENSDKROOT)\ to $(_PROJECTROOT)\cesysgen\sdk\ and $(_SYSGENOAKROOT)\ to $(_PROJECTROOT)\cesysgen\oak\
    • You can also delete the #xref lines, they are remains of older days (CE 2.11 era).
    • Now try to build (by typing "build" in the build window you still have open)
    • oops! Doesn't build. It can't find the standard include files... Add the following to the top of the sources file:
      _ISVINCPATH=$(_WINCEROOT)\public\common\sdk\inc;
      _OEMINCPATH=$(_WINCEROOT)\public\common\ddk\inc; $(_WINCEROOT)\public\common\oak\inc; $(_WINCEROOT)\public\common\sdk\inc;
      NOTE: DO NOT PUT SPACES BETWEEN THE PATHS! I know it is confusing because I do it above, but I have to to support line breaking (otherwise this page will look ugly...).
    • And since this project is building OEM code (a kernel component) set WINCEOEM=1 to indicate we want to use the _OEMINCPATH (so if you really want to you can delete the _ISVINCPATH since we don't use that one).
    • Try to build again:
      BUILD: [01:0000000048:ERRORE] \WINCE600\OSDesigns\MyOS\MyOS\NETUI\.\btmgmtui.cpp(39) : fatal error C1083: Cannot open include file: '../bluetooth/sample/btenum/btenum.hxx': No such file or directory

      So, one of the sources files is trying to include a header on a relative path. Since NETUI came from \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\NETUI this path must be \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\BLUETOOTH

      Now there are 3 ways to solving this: Either add the \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\NETUI folder to the include path (add INCLUDES=$(INCLUDES);\WINCE600\PUBLIC\COMMON\OAK\DRIVERS\NETUI) so that the header file can be found off the relative path, or change the source file including the header to use a full path to the header file, or copy the btenum.hxx file into the NETUI folder and change the source file including the header to use the local header file. I think the last option is the cleanest, so let's copy \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\BLUETOOTH\SAMPLE\BTENUM\btenum.hxx to \WINCE600\OSDesigns\MyOS\MyOS\NETUI\btenum.hxx and change btmgmtui.cpp to #include "btenum.hxx"

    • Build again: 0 Warnings,  0 Errors. Whuuhooo!
    • Now there is one final thing we have to add to the NETUI sources. file. We have to tell the build system we want to copy the final binary to the _FLATRELEASEDIR so our netui.dll will overwrite the default one from the PUBLIC\COMMON folder. Add WINCEREL=1 to the top of the sources. file.
  7. Now add the NETUI project to your workspace. In the Platform Builder VS IDE click "Add Existing Subproject..." from the "Project" menu. Browse to the \WINCE600\OSDesigns\MyOS\MyOS\NETUI folder, select "Sources/Dirs Files" from the "Files of type" dropdown list, select the sources. file, and click "Open"
  8. In the Solution Explorer window you can now find your NETUI component under the "SubProjects" node. Right click NETUI and click "Rebuild" to see if you can build the project from the IDE. It should all build fine.
  9. If all builds fine, you can delete the sources.netui and the sources.org files since you no longer need them now.
  10. The "Insert existing project" action created a couple of files for you. One of those files is a bib file. BIB files are used to indicate which files need to be included in the image. Since we cloned NETUI, but did not remove it from the OSDesign features, we now have 2 references to NETUI in the combined BIB files (one is in COMMON.BIB and the other in our newly generated NETUI.BIB). We don't need an extra reference in the BIB files, all we want is overwrite the default NETUI.DLL in the _FLATRELEASEDIR, so you can remove this reference from the newly generated NETUI.bib.

Now you can change whatever you need to change in NETUI without worrying about messing up the PUBLIC portion of the WINCE tree or having to do a "Clean Build and Sysgen".

If you have followed all the steps above your final NETUI sources. file should look like this (except the spaces in the _ISVINCPATH, _OEMINCPATH and INCLUDES macros!):

_ISVINCPATH=$(_WINCEROOT)\public\common\sdk\inc;
_OEMINCPATH=$(_WINCEROOT)\public\common\ddk\inc; $(_WINCEROOT)\public\common\oak\inc; $(_WINCEROOT)\public\common\sdk\inc;

WINCEOEM=1
WINCEREL=1

TARGETNAME=netui
TARGETTYPE=DYNLINK
RELEASETYPE=LOCAL

TARGETDEFNAME=$(TARGETNAME)
DEFFILE=$(TARGETDEFNAME).def

DLLENTRY=_DllEntryCRTStartup

CONDITIONAL_INCLUDES=prshtp.h

SOURCELIBS=

TARGETLIBS=\
    $(_PUBLICROOT)\common\oak\lib\$(_CPUINDPATH)\btenum.lib \
    $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\iphlpapi.lib \
    $(_PROJECTROOT)\cesysgen\oak\lib\$(_CPUINDPATH)\btdrt.lib \
    $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\ws2.lib \
    $(_PROJECTROOT)\cesysgen\oak\lib\$(_CPUINDPATH)\ceddk.lib \
    $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\coredll.lib

SOURCES= \
    netui.c \
    getip.c \
    ipaddr.c \
    getuser.c \
    linecnfg.c \
    wnet.c \
    netcard.c \
    certui.cpp  \
    showcert.cpp \
    eaptlscfg.cpp \
    network.c \
    transdlg.c \
    reg.c \
    util.c \
    wzcui.c \
    wzcprops.c \
    btmgmtui.cpp \
    WzcQuickCfgUi.c \
    IpQuickCfgUi.c \
    QuickConfigUi.c \
    WzcLogging.c \
    WzcPopup.c \
    netui.rc  \
    wnet_wrapper.cpp \
    netui_wrapper.cpp \
    getuser_wrapper.cpp \
    gwes_wrapper.cpp \
    getip_wrapper.cpp \
    linecnfg_wrapper.cpp \
    transdlg_wrapper.cpp \
    network_wrapper.cpp \
    netcard_wrapper.cpp

FILE_VIEW_PARAMETER_FOLDER= \
    NETUI.bib \
    NETUI.reg \
    NETUI.dat \
    NETUI.db \
    ProjSysgen.bat \

FILE_VIEW_ROOT_FOLDER= \
    prelink.bat \
    postlink.bat \

PRELINK_PASS_CMD=prelink.bat
POSTLINK_PASS_CMD=postlink.bat

Filtering dat files

If you read this blog or the CE newsgroups you should by now know you should never change any files in the PUBLIC or PRIVATE folders of your WINCE installation tree. So what if you want to change a setting in common.reg (which is located in the PUBLIC folder)? Well, add the same entry to your platform.reg (in your PLATFORM\BSP folder) and set it to whatever you like. So, what if you want to delete a setting from common.reg? Again, add the same entry to your platform.reg but precede the key name with a "-". This works because platform.reg is merged into reginit.ini after common.reg, so any duplicate entries or deletion commands (the "-") will override the settings in common.reg.

So, what about dat files? Unfortunately, deleting dat entries with the "-" tag is not supported. Dat files are responsible for the initial copy of files and creation of folders during boot. For instance, dat files are used to copy shortcuts from the \Windows folder to the desktop of your CE device. Now what if you don't want to have icons on your desktop? The only way that seems possible is to actually change the public dat files in place, but you know you should never change any files in the PUBLIC tree! We just have to come up with a different solution...

The solution in this case is to filter the dat files and remove any unwanted lines before creating the final image but after the sysgen phase and without touching the original files in PUBLIC. Windows CE calls out to a number of batch files during the makeimg process to let you write your own scripts to affect the image at different points during the image building process. One of those batch files is "PostFmergeObj.bat" which, as the name suggests, is called just after fmerge merged the "Obj" files (which are in fact "Dat" files... confused already? ;o) into your FLATRELEASEDIR.

By using a bit of scripting magic we can use this batch file to filter out unwanted lines from the final merged InitObj.dat file:

  1. REM   This batch file filters the DAT files
  2. REM   CE merges all DAT files into initobj.tmp
  3. REM   and then transforms initobj.tmp into a
  4. REM   UNICODE version initobj.dat.
  5. REM   Just before the conversion to unicode
  6. REM   the build system calls PostFmergeObj.bat
  7. REM   (this file). We filter out all the
  8. REM   strings defined in PostFmergeObj.txt
  9. REM   from initobj.tmp
  10.  
  11. @echo off
  12. echo PostFmergeObj.bat entry.
  13. pushd %_FLATRELEASEDIR%
  14. echo Deleting root:- entries from initobj.tmp
  15. del initobj.org
  16. ren initobj.tmp initobj.org
  17. findstr /i /v /g:PostFmergeObj.txt initobj.org > initobj.tmp
  18. popd
  19. echo PostFmergeObj.bat exit.
  20. @echo on

The core of the script is from line 15 onwards. In line 15 we delete any previous initobj.org file this batch file may have left behind. Then, in line 16, we rename initobj.tmp (all dat files are merged into this file by fmerge) to initobj.org and in line 17 we call the dos utility Findstr:

>findstr /?
Searches for strings in files.

FINDSTR [/B] [/E] [/L] [/R] [/S] [/I] [/X] [/V] [/N] [/M] [/O] [/P] [/F:file]
        [/C:string] [/G:file] [/D:dir list] [/A:color attributes] [/OFF[LINE]]
        strings [[drive:][path]filename[ ...]]

  /B         Matches pattern if at the beginning of a line.
  /E         Matches pattern if at the end of a line.
  /L         Uses search strings literally.
  /R         Uses search strings as regular expressions.
  /S         Searches for matching files in the current directory and all
             subdirectories.
  /I         Specifies that the search is not to be case-sensitive.
  /X         Prints lines that match exactly.
  /V         Prints only lines that do not contain a match.
  /N         Prints the line number before each line that matches.
  /M         Prints only the filename if a file contains a match.
  /O         Prints character offset before each matching line.
  /P         Skip files with non-printable characters.
  /OFF[LINE] Do not skip files with offline attribute set.
  /A:attr    Specifies color attribute with two hex digits. See "color /?"
  /F:file    Reads file list from the specified file(/ stands for console).
  /C:string  Uses specified string as a literal search string.
  /G:file    Gets search strings from the specified file(/ stands for console).
  /D:dir     Search a semicolon delimited list of directories
  strings    Text to be searched for.
  [drive:][path]filename
             Specifies a file or files to search.

Use spaces to separate multiple search strings unless the argument is prefixed
with /C.  For example, 'FINDSTR "hello there" x.y' searches for "hello" or
"there" in file x.y.  'FINDSTR /C:"hello there" x.y' searches for
"hello there" in file x.y.

Regular expression quick reference:
  .        Wildcard: any character
  *        Repeat: zero or more occurances of previous character or class
  ^        Line position: beginning of line
  $        Line position: end of line
  [class]  Character class: any one character in set
  [^class] Inverse class: any one character not in set
  [x-y]    Range: any characters within the specified range
  \x       Escape: literal use of metacharacter x
  \<xyz    Word position: beginning of word
  xyz\>    Word position: end of word

For full information on FINDSTR regular expressions refer to the online Command
Reference.

By calling findstr with options /I and /V we effectively filter out the lines we specify in PostFmergeObj.txt and we pipe the output of findstr into InitObj.tmp. After this batch file returns the build system will call the "Text to Unicode" tool (Txt2ucde.exe) that converts any ASCII text strings in Initobj.tmp to Unicode text strings and creates the file Initobj.dat which then contains the Unicode conversion of InitObj.tmp (see http://msdn.microsoft.com/en-us/library/ms930234.aspx).

Note that you can also use regular expressions to find strings with findstr. Using this you have a very powerful tool to filter whatever file you want filtered and of course this trick doesn't only work on dat files, it works on any ascii text file (like bib, reg, etc).

All you have to do for all this to start working in your build is create PostFmergeObj.bat in your PLATFORM\BSP\FILES folder and copy'n'paste the above script. The Windows CE build system will automatically search for this batch file and if it finds it execute it just after it has merged the various configuration files. Don't forget to create a "PostFmergeObj.txt" file with the lines you want to delete from the final InitObj.dat file in the same folder as the batch file, like this:

root:-Directory("\Windows"):-Directory("www")
Directory("\Windows\www"):-Directory("wwwpub")
root:-Directory("Program Files")
root:-Directory("My Documents")
root:-File("Control Panel.lnk", "\Windows\control.lnk")
Directory("\Windows"):-Directory("Desktop")
Directory("\Windows"):-Directory("Programs")
Directory("\Windows"):-Directory("Recent")
Directory("\Program Files"):-File("Command Prompt.lnk","\Windows\cmd.lnk")
Directory("\Windows\Programs"):-File("Windows Explorer.lnk", "\Windows\explore.lnk")
Directory("\Windows\Programs"):-File("Command Prompt.lnk","\Windows\cmd.lnk")
Directory("\Program Files"):-File("Command Prompt.lnk","\Windows\cmd.lnk")

When you now perform a "make image" this batch file should automatically execute and its output (the "echo" commands in the batch file) should appear in the build output window.

.NET Micro Framework v3.0 SDK Beta

A template for debug messages

In all the drivers and applications I develop, I always take the time to add plenty of logging because I know good logging will save me loads of time debugging my code (as if I ever have to "debug" my perfect code, ha! ;o).

Windows CE has a really great mechanism for debug logging called Debug Zones. This mechanism allows you to divide your debug messages into 16 zones and each of these zones can be turned on or off at run-time. This allows you to control the verbosity of your debug output and helps prevent cluttering of messages. Read this excellent blog post by Travis first before reading on: http://blogs.msdn.com/ce_base/archive/2006/12/18/debug-messages-and-debu...

To add debug logging using Debug Zones to your application or driver do the following:

1. Define your zones using the DEBUGZONE macro
1. Define a DBGPARAM structure and fill it
2. Call either DEBUGREGISTER or RETAILREGISTERZONES from your DllMain (driver) or WinMain (application)
3. Log messages using either DEBUGMSG or RETAILMSG

Sounds easy (and actually it is) but to make it more easy (and because I found myself doing the same thing over and over) I created a template I now use in all my driver and application code, even on projects targeted for Desktop Windows. Even though "Big" Windows doesn't have a "Debug Zone" mechanism you can still use the template. The big benefit of course being your code is completely portable between desktop PC's and CE devices (and also MBS/UNICODE)! The only thing you can't do is switch debug zones at run-time, but you can still select which zones should be on or off at compile-time.

Let me explain the template by walking through it; I'll start with DebugZones.cpp (the line numbers reflect the actual line numbers in the template files):

  1. #include <windows.h>
  2. #include "DebugZones.h"
  3.  
  4. // Do NOT change anything in this file.
  5. // See DebugZones.h for things you can change!
  6. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  7. DBGPARAM dpCurSettings =
  8. {
  9.     __TMODULE__,
  10.     {
  11.         ZONE0_TEXT,ZONE1_TEXT,ZONE2_TEXT,ZONE3_TEXT,
  12.         ZONE4_TEXT,ZONE5_TEXT,ZONE6_TEXT,ZONE7_TEXT,
  13.         ZONE8_TEXT,ZONE9_TEXT,ZONE10_TEXT,ZONE11_TEXT,
  14.         ZONE12_TEXT,ZONE13_TEXT,ZONE14_TEXT,ZONE15_TEXT
  15.     },
  16. #ifndef DEBUG // IN RETAIL...
  17.     RETAILZONES
  18. #else //IN DEBUG...
  19.     DEBUGZONES
  20. #endif
  21. };

The first and most important thing to note is that you should not change anything in this file. You configure the template all through the header file, which I will be discussing later.

In this first part of DebugZones.cpp I simply define a global variable dpCurSettings of type DBGPARAM and fill the structure with defines from DebugZones.h. The __TMODULE__ corresponds to the "lpszName" member, the ZONEx_TEXT defines fill the "rglpszZones" array and finally RETAILZONES or DEBUGZONES (depending on whether you build a "debug" or "release" version of the code) corresponds to the "ulZoneMask" member of the DBGPARAM structure. The CE debug system uses this structure to get Debug Zone information from the module and to set or clear debug zones during execution of the module. When you use the template for code targeted at Desktop Windows this structure will only be used by the DEBUGMSG/RETAILMSG macros (they use the ulZoneMask member as a display condition).

  1. #ifndef UNDER_CE
  2.   #include <stdio.h>
  3.   void WINAPIV NKDbgPrintf(LPCTSTR lpszFmt, ...)
  4.   {
  5.       #define BUFFER_SIZE 1000
  6.       va_list argptr;
  7.       va_start(argptr, lpszFmt);
  8.       TCHAR szBuffer[BUFFER_SIZE];
  9.       _vstprintf_s(szBuffer, BUFFER_SIZE, lpszFmt, argptr);
  10.       OutputDebugString(szBuffer);
  11.       va_end(argptr);
  12.   }
  13. #endif

This code is only used when you target Desktop Windows. Only Windows CE defines NKDbgPrintf so for Desktop Windows I had to create my own implementation of this function to make the Debug Zones system portable. This function simply takes a format string followed by a variable number of parameters and transforms that into a formatted string and outputs it to the debug output window of Visual Studio. Note that the maximum length of the formatted string is BUFFER_SIZE characters.

  1. // Helper function for runtime conversion of char* to _T
  2. LPCTSTR to_T(LPCSTR pStr)
  3. {
  4.     #define MAX_CALLS   4
  5.     #define MAX_MSG     500
  6.  
  7.     static TCHAR buffer[MAX_CALLS][MAX_MSG];
  8.     static DWORD index = 0;
  9.     LPCTSTR pszRet = buffer[index];
  10.     size_t conv;
  11.  
  12.     #ifdef UNICODE
  13.       mbstowcs_s(&conv, buffer[index], MAX_MSG, pStr, MAX_MSG);
  14.     #else //ANSI
  15.       strcpy_s(buffer[index], MAX_MSG, pStr);
  16.     #endif
  17.  
  18.     index++;
  19.     if (MAX_CALLS==index)
  20.         index = 0;
  21.     return pszRet;
  22. }

This next bit of code is a helper function that solves a problem when you want your code to be portable between MBS and UNICODE. MBS is multi-byte string, also called ANSI string, and UNICODE is, well, UNICODE strings. Let's say you are compiling for UNICODE but want to display a multi-byte string using a function that uses a format string. The way to do that is to use %S instead of %s in the format string. When you use %s in code compiled for UNICODE the variable indicated by %s should be in UNICODE. When you compile for MBS the variable indicated by %s should be MBS. In other words, %s indicates the "native" string format. If you want to display a multi-byte string in code compiled for UNICODE you'd use %S, and if you want to display a UNICODE string in code compiled for MBS you'd use %S as well. In other words, %S indicates the "opposite" string format. And here lies the problem: What if you want to display a multi-byte string regardless of whether the code is compiled for MBS or UNICODE?

Take a look at the following code:

NKDbgPrintf(_T("Display a native format string \"%s\"\r\n"), _T("This is a native string format"));
NKDbgPrintf(_T("Display opposite string format \"%S\"\r\n"), "This is a multi-byte string");

Note that the _T macro is defined as L in UNICODE (making the following string a UNICODE string) and as nothing in MBS (leaving the following string a multi-byte string).

When compiled for UNICODE the above code works perfectly:

Display a native format string "This is a native string format"
Display opposite string format "This is a multi-byte string"

But now see what happens when you compile the above code for MBS:

Display a native format string "This is a native string format"

You only get the output of the first line! So what just happened? Well, the _vstprintf_s function in NKDbgPrintf expected the first argument to be of the opposite string format. Since you compiled for MBS this means it expects a UNICODE string. The _vstprintf_s function detects it is not and bails out, setting szBuffer to an empty string.

The to_T function solves this problem:

NKDbgPrintf(_T("Display a native format string \"%s\"\r\n"), _T("This is a native string format"));
NKDbgPrintf(_T("Display opposite string format \"%s\"\r\n"), to_T("This is a multi-byte string"));

When compiled for MBS the output is:

Display a native format string "This is a native string format"
Display opposite string format "This is a multi-byte string"

I can here you say: So why don't you just use _T() in that 2nd call to NkDbgPrintf? And yes, you are right, in the above code that would work, but what if it's a variable pointing to a multi-byte string? You can't use _T() around a variable:

NKDbgPrintf(_T("IPAddress \"%s\"\r\n"), to_T(inet_ntoa(ipAddress)));

Line 64 defines the maximum number of times you can call this function before it will start overwriting its internal buffers. Increase this value if you want to use more than 4 parameters that need to be converted in one call. To understand this, see the following code and its output:

char* s1 = "String 1";
char* s2 = "String 2";
char* s3 = "String 3";
char* s4 = "String 4";
char* s5 = "String 5";
char* s6 = "String 6";

NKDbgPrintf(_T("%s %s %s %s %s %s\r\n"), to_T(s1), to_T(s2), to_T(s3), to_T(s4), to_T(s5), to_T(s6));

Output:

String 1 String 2 String 3 String 4 String 1 String 2



Let's move on to the real core of the template; DebugZones.h:

  1. #pragma once
  2.  
  3. // Change the following definitions:
  4. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  5. #define __MODULE__ "MyDriver"  // The module name
  6.  
  7. // Definitions for our debug zones                              
  8. #define ZONE0_TEXT          _T("Init")
  9. #define ZONE1_TEXT          _T("Function")
  10. #define ZONE2_TEXT          _T("Memory")
  11. #define ZONE3_TEXT          _T("")
  12. #define ZONE4_TEXT          _T("")
  13. #define ZONE5_TEXT          _T("")
  14. #define ZONE6_TEXT          _T("")
  15. #define ZONE7_TEXT          _T("")
  16. #define ZONE8_TEXT          _T("")
  17. #define ZONE9_TEXT          _T("")
  18. #define ZONE10_TEXT         _T("")
  19. #define ZONE11_TEXT         _T("")
  20. #define ZONE12_TEXT         _T("")
  21. #define ZONE13_TEXT         _T("Info")
  22. #define ZONE14_TEXT         _T("Warning")
  23. #define ZONE15_TEXT         _T("Error")
  24.  
  25. // These macros can be used as condition flags for LOGMSG
  26. #define ZONE_INIT           DEBUGZONE(0)
  27. #define ZONE_FUNCTION       DEBUGZONE(1)
  28. #define ZONE_MEMORY         DEBUGZONE(2)
  29. #define ZONE_3              DEBUGZONE(3)
  30. #define ZONE_4              DEBUGZONE(4)
  31. #define ZONE_5              DEBUGZONE(5)
  32. #define ZONE_6              DEBUGZONE(6)
  33. #define ZONE_7              DEBUGZONE(7)
  34. #define ZONE_8              DEBUGZONE(8)
  35. #define ZONE_9              DEBUGZONE(9)
  36. #define ZONE_10             DEBUGZONE(10)
  37. #define ZONE_11             DEBUGZONE(11)
  38. #define ZONE_12             DEBUGZONE(12)
  39. #define ZONE_INFO           DEBUGZONE(13)
  40. #define ZONE_WARNING        DEBUGZONE(14)
  41. #define ZONE_ERROR          DEBUGZONE(15)
  42.  
  43. // These macros can be used to indicate which zones to enable by default (see RETAILZONES and DEBUGZONES below)
  44. #define ZONEMASK_INIT       ZONEMASK(0)
  45. #define ZONEMASK_FUNCTION   ZONEMASK(1)
  46. #define ZONEMASK_MEMORY     ZONEMASK(2)
  47. #define ZONEMASK_3          ZONEMASK(3)
  48. #define ZONEMASK_4          ZONEMASK(4)
  49. #define ZONEMASK_5          ZONEMASK(5)
  50. #define ZONEMASK_6          ZONEMASK(6)
  51. #define ZONEMASK_7          ZONEMASK(7)
  52. #define ZONEMASK_8          ZONEMASK(8)
  53. #define ZONEMASK_9          ZONEMASK(9)
  54. #define ZONEMASK_10         ZONEMASK(10)
  55. #define ZONEMASK_11         ZONEMASK(11)
  56. #define ZONEMASK_12         ZONEMASK(12)
  57. #define ZONEMASK_INFO       ZONEMASK(13)
  58. #define ZONEMASK_WARNING    ZONEMASK(14)
  59. #define ZONEMASK_ERROR      ZONEMASK(15)
  60.  
  61. #define RETAILZONES     ZONEMASK_INIT | ZONEMASK_WARNING | ZONEMASK_ERROR
  62. #define DEBUGZONES      RETAILZONES | ZONEMASK_FUNCTION | ZONEMASK_MEMORY | ZONEMASK_INFO
  63.  
  64. // Define FULL_CONTEXT to show full context in debug messages:
  65. // "Module: [full path to file name:line number] Function>"
  66. //#define FULL_CONTEXT
  67. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

This is the part of the template you should change to match your driver or application. First, in line 30, change the __MODULE__ define to the name of your driver or application. If you are creating a flash driver you'd set this to "FLASH" for instance.
Next, in lines 33 to 48, you define the friendly names of the debug zones you want. These names will show up when you use the Debug Zones dialog or the "zo" command from Platform Builder. The predefined ones are always useful:

  • Init
  • Use this zone to display debug messages during the initialization phase of your driver. I use this zone in the Init function of drivers.

  • Function
  • This zone is used to display function entry and exit. Used by the FUNC_ENTRY and FUNC_EXIT macros (I'll discuss those later)

  • Memory
  • This zone is used to display memory allocation and de-allocation. Having a separate zone for memory really helps in identifying memory leaks or memory related problems in your driver or application (besides CeDebugX)

  • Info
  • This zone is used to display general information. I use this zone a lot through-out my code and enabling this zone usually means I get heaps of debug messages, but also heaps of information about the flow of my code

  • Warning
  • Use this zone for non-fatal errors (also known as warnings ;o)

  • Error
  • Use this zone for fatal errors; situations where you had to abort a certain operation

Another zone I use often is "IOCTL". If my driver handles custom IOCTLs I use that zone to display information when those IOCTLs are handled. If you want to add the IOCTL zone you'd change line 36 to:

  1. #define ZONE3_TEXT          _T("IOCTL")

Lines 51 to 66 define the actual zone IDs. You use these defines as the first parameter of LOG_MSG (that I'll discuss later) to indicate the zone this message belongs to. After you added the IOCTL zone to the friendly names list, you'd change line 54 to:

  1. #define ZONE_IOCTL          DEBUGZONE(3)

Lines 69 to 84 define the zone masks. You use these defines to indicate which zones should be enabled by default. After you added the IOCTL zone to the zone ID list, you'd change line 72 to:

  1. #define ZONEMASK_IOCTL      ZONEMASK(3)

Line 86 and 87 define the default zones to use in a release build and a debug build. I enable zones INIT, WARNING and ERROR in a release build and add FUNCTION, MEMORY and INFO to debug builds. Note that in a "ship" build (on Windows CE, WINCESHIP=1) debug/retail messages are defined as "nothing" (so all the logging code is effectively removed from the final binary).

If you define FULL_CONTEXT (line 91) the LOGMSG macro will output the full path to the source code filename in front of the message, instead of just the module name. I don't enable this by default because it makes the debug output cluttered and less readable (but it can be very handy in some cases).

This concludes the part of DebugZones.h that you should change. The remainder of the code mostly deals with the differences between Desktop Windows and Windows CE:

  1. // Do NOT change any of the following definitions:
  2. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  3. LPCTSTR to_T(LPCSTR pStr);
  4.  
  5. #ifdef UNDER_CE
  6.   #ifndef DEBUG
  7.     #include <windows.h>
  8.     extern DBGPARAM dpCurSettings;
  9.   #endif
  10. #else
  11.     #include <windows.h>
  12.     #include <tchar.h>
  13.     #include <stdio.h>
  14.  
  15.     typedef struct _DBGPARAM {
  16.             TCHAR   lpszName[32];           // @field Name of module
  17.             TCHAR   rglpszZones[16][32];    // @field names of zones for first 16 bits
  18.             ULONG   ulZoneMask;             // @field Current zone Mask
  19.     } DBGPARAM, *LPDBGPARAM;
  20.  
  21.     #ifndef DEBUG
  22.       #ifdef _DEBUG
  23.         #define DEBUG
  24.       #endif
  25.     #endif
  26.  
  27.     extern DBGPARAM dpCurSettings;
  28.     void WINAPIV NKDbgPrintf(LPCTSTR lpszFmt, ...);
  29.  
  30.     #define RETAILMSG(cond,printf_exp)   \
  31.        ((cond)?(NKDbgPrintf printf_exp),1:0)
  32.  
  33.     #define DEBUGZONE(n)  (dpCurSettings.ulZoneMask & (0x00000001<<(n)))
  34.  
  35.     #define RETAILREGISTERZONES(hMod)
  36.  
  37.     #ifdef DEBUG
  38.         #define DEBUGMSG(cond,printf_exp)   \
  39.            ((void)((cond)?(NKDbgPrintf printf_exp),1:0))
  40.        
  41.         #define DBGCHK(module,exp) \
  42.            ((void)((exp)?1:(          \
  43.                NKDbgPrintf ( _T("%s: DEBUGCHK failed in file %s at line %d \r\n"), \
  44.                          module, _T(__FILE__) ,__LINE__ ),    \
  45.                DebugBreak(), \
  46.                    0  \
  47.            )))
  48.  
  49.         #define DEBUGCHK(exp) DBGCHK(dpCurSettings.lpszName, exp)
  50.         #define DEBUGREGISTER(hMod)
  51.     #endif //DEBUG
  52. #endif
  53.  
  54. #ifndef __FUNCTION__
  55. #define __FUNCTION__ __FILE__
  56. #endif
  57.  
  58. #ifdef UNICODE
  59.   #define PREPT(x) L ## x
  60. #else
  61.   #define PREPT(x) x
  62. #endif
  63.  
  64. #define TOTSTR(x) PREPT(x)
  65. #define __TFUNCTION__ TOTSTR(__FUNCTION__)
  66. #define __TMODULE__ TOTSTR(__MODULE__)
  67. #define __TFILE__ TOTSTR(__FILE__)
  68.  
  69. #define ZONEMASK(n) (0x00000001<<(n))
  70.  
  71. #ifdef FULL_CONTEXT
  72.   #define FUNC_ENTRY(fmt, ...)    RETAILMSG(ZONE_FUNCTION, (__TMODULE__ _T(": +[") __TFILE__ _T(":%d] ") __TFUNCTION__ _T("(") fmt _T(")+\r\n"), __LINE__, __VA_ARGS__))
  73.   #define FUNC_EXIT(fmt, ...)     RETAILMSG(ZONE_FUNCTION, (__TMODULE__ _T(": -[") __TFILE__ _T(":%d] ") __TFUNCTION__ _T("(") fmt _T(")-\r\n"), __LINE__, __VA_ARGS__))
  74.   #define LOGMSG(cond, fmt, ...)  RETAILMSG(cond, (__TMODULE__ _T(":  [") __TFILE__ _T(":%d] ") __TFUNCTION__ _T("> ") fmt, __LINE__, __VA_ARGS__))
  75. #else
  76.   #define FUNC_ENTRY(fmt, ...)    RETAILMSG(ZONE_FUNCTION, (__TMODULE__ _T(": +") __TFUNCTION__ _T("(") fmt _T(")+\r\n"), __VA_ARGS__))
  77.   #define FUNC_EXIT(fmt, ...)     RETAILMSG(ZONE_FUNCTION, (__TMODULE__ _T(": -") __TFUNCTION__ _T("(") fmt _T(")-\r\n"), __VA_ARGS__))
  78.   #define LOGMSG(cond, fmt, ...)  RETAILMSG(cond, (__TMODULE__ _T(":  ") __TFUNCTION__ _T("> ") fmt, __VA_ARGS__))
  79. #endif
  80. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

The interesting part is from line 165 to 173 where I define three macros that allow you to log messages in a good format (in my humble opinion):

  • FUNC_ENTRY
  • Usage:

    FUNC_ENTRY(format_string [, arg1, arg2, ...]);

    Examples:
    FUNC_ENTRY(_T("void"));
    FUNC_ENTRY(_T("lpParameter = 0x%08X"), lpParameter);

    Output:
    (Note the '+' characters that indicate function entry)
    ModuleName: +FunctionName(void)+
    ModuleName: +FunctionName(lpParameter = 0xA5A5A5A5)+

  • FUNC_EXIT
  • Usage:

    FUNC_EXIT(format_string [, arg1, arg2, ...]);

    Examples:
    FUNC_EXIT(_T(""));
    FUNC_EXIT(_T("return %s"), bRet ? _T("true") : _T("false"));

    Output:
    (Note the '-' characters that indicate function exit)
    ModuleName: -FunctionName()-
    ModuleName: -FunctionName(return false)-

  • LOGMSG
  • Usage:

    LOGMSG(ZONE_ID, format_string [, arg1, arg2, ...]);

    Examples:
    LOGMSG(ZONE_INIT, _T("Started successfully!\r\n"));
    LOGMSG(ZONE_ERROR, _T("ERROR: Invalid parameter (dwParam = %d)\r\n"), dwParam);
    LOGMSG(ZONE_WARNING, _T("WARNING: Could not reach IP %s\r\n"), to_T(inet_ntoa(IPAddress)));

    Output:
    ModuleName:  FunctionName> Started successfully!
    ModuleName:  FunctionName> ERROR: Invalid parameter (dwParam = 1234)
    ModuleName:  FunctionName> WARNING: Could not reach IP 192.168.1.1

To use the Debug Zones template in your projects you simply add DebugZones.cpp and DebugZones.h to your solution and call RETAILREGISTERZONES(hMod) from your entry point function:

BOOL WINAPI DriverEntry(HANDLE hInstance, DWORD dwReason, LPVOID lpReserved)
{
    FUNC_ENTRY(_T("hInstance = 0x%08X, dwReason = 0x%08X"), hInstance, dwReason);

    switch(dwReason)
    {
    case DLL_PROCESS_ATTACH:
    {
        // Disable thread creation/deletion calls into this function
        DisableThreadLibraryCalls((HMODULE)hInstance);
        // Register with debug subsystem
        RETAILREGISTERZONES((HMODULE)hInstance);
        break;
    }
    case DLL_PROCESS_DETACH:
    {
        break;
    }
    default:
        break;
    }

    FUNC_EXIT(_T("TRUE"));
    return TRUE;
}

or in case of an application:

int WINAPI WinMain (HINSTANCE hInstance,
                    HINSTANCE hInstPrev,
                    LPWSTR pCmdLine,
                    int nCmdShow)
{
    // Register with debug subsystem
    RETAILREGISTERZONES((HMODULE)hInstance);
    .
    .
    .

If you are using precompiled headers you have to set DebugZones.cpp to "Not Using Precompiled Headers" (in VS2008 right click DebugZones.cpp in your solution, click "Properties", then select "Precompiled Headers" under "Configuration Properties -> C/C++" and change the "Create/Use Precompiled Header" value). Now include "DebugZones.h" in any file you want to use debug zone logging.

The attached ZIP file contains the complete template.

I hope this template will prove as useful to you as it is to me!

SD MMC and Windows CE

Lately there have been a lot of questions about SD and MMC in the newsgroups, especially about what is supported by the Microsoft SD bus driver. This blog post hopefully helps clear up some things about SD/MMC support in Windows CE.

First let’s look at an overview of the MMC and SD specifications and who supports what:

Specification overview
CE Version SD Spec MMC Spec
Win CE 5.0 RTM 1.1 3.x
Win CE 5.0 QFE (April 2007 onward) 2.0 4.3
Win Mobile 6.0 RTM 1.1 3.x
Win Mobile 6.x (AKU 0.2 onward) 2.0 4.3
Win CE 6.0 RTM 1.1 3.x
Win CE 6.0 R2 2.0 4.3

From the table above it looks like the SD bus driver in CE 6.0 R2 supports the MMC 4.3 specification, but actually it doesn’t completely (as we’ll see a bit later in this blog post).

The Microsoft SD bus driver (sdbus2.dll) is fully supporting the SD 2.0 specification. Because the MMC 4.3 specification is quite similar to SD 2.0, CE “somewhat” supports MMC 4.3.

So what does this mean? Well, for example, the number of data lines (bus width) of MMC supported by the Microsoft SD bus driver differs from the MMC specification:

8/4 or 1-bit mode

The MMC specification tells us it supports 8-bit wide bus mode (8-data lines). However, the SD 2.0 specification does not support 8 bit bus mode. Since CE officially supports SD but not MMC, CE does not support the 8-bit mode for any MMC/SD card. Unfortunately it appears the 4-bit mode is also not supported for MMC cards by the Microsoft SD bus driver even though the SD specification does support this mode. This leaves 1-bit mode as the only supported mode for MMC.

Here’s an overview of the supported modes by the SD bus driver (sdbus2.dll):

Bus mode SD MMC
1 Y Y
4 Y N
8 N N

So does this mean that MMC cannot support 4 or 8 bit at all? Well no, not really... You can always CLONE the SDBUS driver and modify it according to your needs or you can develop your own driver without using the SD bus driver at all.

High Capacity

According to the SD 2.0 specification "High Capacity" means cards with sizes larger then 2GB. SDHC cards are supported by the SD bus driver version 2.0. However, because of a bug in the SD bus driver high capacity MMC cards don’t work. You need to fix one line of code to support HC MMC cards. Besides this bug there is also a difference in the protocol for MMC and SD 2.0 regarding High Capacity, more about that later.

First let’s fix the obvious bug so that your High Capacity MMC card will be recognized and mounted properly:

The first step is to clone the SD Bus driver located at <WINCEROOT>\PUBLIC\COMMON\OAK\DRIVER\SDCARD\SDBUS

  1. Open a build release window
  2. Type "cd %_targetplatroot%"
  3. Type "cd src"
  4. Type "cd drivers"
  5. Type "md sdbus2"
  6. Type "cd sdbus2"
  7. Type "sysgen_capture -p common sdbus"
  8. Now copy the files from <WINCEROOT>\PUBLIC\COMMON\OAK\DRIVER\SDCARD\SDBUS into <WINCEROOT>\PLATFORM\<YourBSP>\SRC\DRIVERS\SDBUS2
  9. And merge sources.sdbus and sources so you end up with this sources file:
  10. SYNCHRONIZE_BLOCK=1

    TARGETNAME=sdbus
    TARGETDEFNAME=SDBus2
    DEFFILE=$(TARGETDEFNAME).def

    TARGETTYPE=DYNLINK
    RELEASETYPE=PLATFORM

    DLLENTRY=_DllEntryCRTStartup

    SOURCES = sdbusreq.cpp \
              sddevice.cpp \
              sdbus.cpp \
              sdslot.cpp \
              sdclient.cpp \
              sddevinf.cpp \
              sdiofeat.cpp \
              sdworki.cpp \
              sddebug.cpp \

    TARGETLIBS=                                           \
      $(_SYSGENOAKROOT)\lib\$(_CPUINDPATH)\defbuslib.lib  \
      $(_SYSGENSDKROOT)\lib\$(_CPUINDPATH)\coredll.lib    \
      $(_SYSGENOAKROOT)\lib\$(_CPUINDPATH)\ceddk.lib    

    Note that I added SYNCHRONIZE_BLOCK=1 to make sure any other component that will link to the sdbus library will be able to find it. SYNCHRONIZE_BLOCK=1 makes sure this folder is built before any other folder in the DRIVERS folder is built.

  11. Try to build the SDBUS2 driver; it should build without errors. If it doesn’t, make sure you selected the SD Bus Driver in the catalog and performed a sysgen on your OSDesign.
  12. And add the SDBUS2 folder to the dirs file in the DRIVERS folder.
  13. Don't forget to change any BSP component that links to $(_COMMONOAKROOT)\lib\$(_CPUINDPATH)\sdbus.lib to use the cloned SDBUS library, located at $(_TARGETPLATROOT)\lib\$(_CPUINDPATH)\sdbus.lib

Now change StringCchCopy to StringCchCat in line 1394 of sddevice.cpp (in your cloned folder of course!). This will append the "\\High_Capacity" string to the Client driver registry entry instead of replacing it (which leads to not finding the correct registry path for the profile because "HKLM\\High_Capacity" is not a valid registry path). Even with this fix it won’t find that path because Microsoft did not provide registry settings for High Capacity MMC. Let’s fix that by adding the following to platform.reg:

; SDHC Memory Storage class driver
[HKEY_LOCAL_MACHINE\Drivers\SDCARD\ClientDrivers\Class\MMC_Class\High_Capacity]
   "Dll"="SDMemory.dll"
   "Prefix"="DSK"
   "BlockTransferSize"=dword:40  ; send no more than 64 blocks of data per bus transfer
   ;"SingleBlockWrites"=dword:1  ; alternatively force the driver to use single block access
   ;"IdleTimeout"=dword:7D0      ; 2000 milliseconds
   ;"IdlePowerState"=dword:2     ; 0 == D0, 1 == D1, etc.
   ;"DisablePowerManagement"=""  ; if value present, then disable (remove value to enable)

   "Profile"="MMC"
   "IClass"=multi_sz:"{A4E7EDDA-E575-4252-9D6B-4195D48BB865}",
                     "{8DD679CE-8AB4-43c8-A14A-EA4963FAA715}"

Now you can Build and Sysgen your BSP by right clicking on your BSP node in the Solution Explorer and choosing Build and Sysgen. NEVER EVER click Build and Sysgen from the Build menu, please read the blog post http://www.guruce.com/blogpost/whattobuildwhen to understand why you should delete that option from the build menu. After the Build and Sysgen of the BSP perform a Copy files to release directory followed by a Make image and download your kernel to your device. When you now insert a high capacity MMC card it should be recognized and mounted correctly, well, almost! The size is most probably still reported wrong. This is because of a difference in the protocol. CMD 8 is handled differently in the SD 2.0 and MMC 4.3 specification:

COMMAND 8 (CMD8)
Command Type Response Abbreviation Command Description
CMD8 MMC R1 SEND_EXT_CSD The card sends its EXT_CSD register as a Block of data.
CMD8 SD R7 SEND_IF_COND Sends SD Memory Card interface condition

In the SD protocol CMD8 is used to identify high capacity cards and it is send at the beginning of the initialization process. When CMD8 is getting a response it means that it is identified as a High Capacity card. If sending a CMD8 results in a response timeout it is not a High Capacity card.

For MMC the CMD8 means retrieving the density of the card from the EXT_CSD register instead of the CDS register. CMD8 must be send after a CMD7 command which will place the card into "Tran" state. Since the SD bus driver doesn't do that the CMD8 will time out.

Like I said the SD bus driver fully supports the SD 2.0 specification and it reads the card density from the CSD register. This works for SD 2.0, but not for MMC. To fully support High Capacity MMC cards you should modify the SD bus driver so it can handle CMD8 instead of CMD9 to retrieve the card density for MMC high capacity cards.

SDBUS or SDBUS2

Now how do you select the correct SD bus driver? First remember that you have to install the correct version of Windows CE and install all required QFEs (see specification overview above). For Windows Embedded CE 6.0 there are two catalog components for SDBUS; "SD Bus Legacy" and "SD Bus driver". The legacy one is the SD bus driver that supports SD specification 1.1 and the "SD Bus Driver" item supports the 2.0 specification.

For all other versions of CE there is only one catalog component which automatically selects the SD bus 1.1 specification. To support the 2.0 specification in CE versions prior to CE 6.0 you have to set the IMGSDBUS2 environment variable to 1.

Conclusion

Windows CE fully supports the SD 2.0 specification, but as we saw this doesn’t automatically mean it also fully supports High Capacity MMC cards. Fortunately with a couple modifications here and there you can now fully support High Capacity MMC cards as well!

How to ask questions on newsgroups

This page is also available in Italian. Want to translate into your language? Let me know!

There are certain rules when you use a community driven newsgroup to get answers to your questions. The Microsoft newsgroups that deal with Platform Builder and Windows CE development (like microsoft.public.windowsce.platbuilder, microsoft.public.windowsce.embedded & microsoft.public.windowsce.embedded.vc) are not actively monitored by Microsoft. The people answering your questions are your peers; volunteers who spend their time researching your problem because they find it interesting and want to learn more.

The people that post most answers are in many cases MVPs, Most Valuable Professionals, a status awarded by Microsoft to people who help out the community by running user groups, answering questions in public newsgroups, etc.

It is important to note that nobody is being paid to answer questions. They're all volunteers.

Now that you know this, you also know that there is no guarantee to get an answer. The most common reasons for not getting an answer are:

  • Your question has been answered on the newsgroup in the past
  • Search before you ask!

  • You didn't supply enough information
  • "I call this function and it doesn't work, please help" is useless.

  • You keep on posting the same question over and over
  • If nobody answered your question after a couple of days it is because you didn't read this blog post on how to ask questions or simply because nobody knows an answer. If you want to "reactivate" your question, post a reply to your own question with something like "Anybody? Please?" and supply any other relevant information you may have gathered over the past few days. Do not start another thread for the same question. It is annoying and will result in people ignoring you.

  • You demand a quick answer
  • Demanding things from people you do not pay any money is generally counterproductive.

  • You post the same question separately to different newsgroups
  • If you think your question is relevant to multiple newsgroups post one question to multiple newsgroups. Please do not post the same question separately to different newsgroups. If you want to crosspost that's fine; just create one message with all the newsgroups you want to post to listed in the To: field (just as you would sent one email to multiple email addresses using the To, CC, or BCC fields). Replies to your question will then appear in all newsgroups you posted to. If you post the same question separately to multiple newsgroups the reply will only appear in the newsgroup that was replied to, leaving the question in the other newsgroups unanswered.

Note that the above says nothing about "stupid" questions. That is because stupid questions do not exist! Everybody on the newsgroups started with Windows CE at some point, and everybody struggled with the (sometimes) steep learning curve in the beginning. We know where you are coming from so don't be shy and ask, but before you do, take note of the following "rules" that will increase your chances in getting a useful answer:

  • Read MSDN Library
  • This sounds logical, but you'd be surprised how many questions are asked that can be easily found in MSDN. Click here for the Windows CE documentation and use the search box in the top right hand corner to search. When you use search, make sure you check if the description you see is for "desktop" Windows, or for Windows Embedded. As you know, Windows CE uses a subset of the big Win32 API and some API parameters have different meaning in CE, so be careful there! That said, sometimes the big Win32 API description lists more information that also applies to and is still useful for CE. First thing to do when you encounter a problem with an API is to open the description of that API in MSDN, check all the parameters and read the remarks section. The remarks section has useful information about error conditions and special requisites that may be needed for the API. If you encounter a problem in the documentation, or missing information or something else wrong with the documentation: Use the "Click to Rate and Give Feedback" link in the top right corner. The people at Microsoft responsible for the documentation really read this feedback and will update the documentation where necessary. Help make the documentation better!

  • Before asking a question, search the newsgroups for an answer
  • Use Google Group Search to search the newsgroups. Type some keywords in the "with all of the words" box, then in the "Return only messages from the group at this location" box type *windowsce* to search all newsgroups related to Windows CE. I also usually set the form to "Return 100 messages" and "Sort by Date" so I get the latest answers on the top of the list.

  • Subscribe to blogs
  • Most MVPs and some people within the Microsoft Product Team have blogs. Blog posts usually deal with a FAQoaN (a Frequently Asked Question on a Newsgroup ;o), like the "What to build when" post on this blog. Some of the blogs that I subscribe to are the Windows CE Base Team Blog, Mike Hall's Windows Embedded Blog, the Windows Mobile Team Blog and of course the GuruCE Blog.

If your search did not return a useful answer you can post a question to the newsgroup, but before you do: try a debug build, get KITL going and analyse the debug messages. If you can't build a debug kernel or get KITL going it will be difficult to get to the root of the problem, but not always. If you can't get KITL or a debug build going list that in your question!

Information that should be in your question:

  • OS version / hardware / board / kernel / QFE's
  • List the OS version, what hardware, what processor, what board, what BSP, debug or retail kernel, KITL yes/no, and what QFEs you installed

  • Specific Component
  • If you have problems with an existing driver or a specific component; tell us! Saying "I've got a problem with the touch driver" is useless, because there is no such thing as the touch driver. If you made the driver; tell us. If you cloned a driver, tell us which driver you cloned (including the original path you cloned from). If you are having problems with a standard driver, tell us exactly which one including the complete path. Example: "I've got problems with the MainstoneIII touch driver located in C:\WINCE600\PLATFORM\MAINSTONEIII\SRC\DRIVERS\TOUCH"

  • Big picture
  • Explain what you want to accomplish, not how you want to accomplish it. There may be better ways to get where you want. Describe the bigger picture.

  • What did you try
  • List the things you tried, the tests you performed and why it didn't work. If people ask you to perform another test: do it! Don't expect an answer if you are unwilling to follow advice that may lead to the solution of your problem. Also, if your search found a solution to your problem but somehow it didn't work, list that too.

  • Detailed error messages
  • If you got an error, list it. If an API returns an error, get the error number by calling GetLastError() and list it.

  • Detailed debug messages
  • Post the debug message lines that you think are relevant to the problem. Do not post 6000 lines of debug log because nobody will want to read through all of that.

  • Be polite
  • Don't demand an answer. Remember nobody is being paid to answer your questions. If you want paid support, buy support from Microsoft or one of the Embedded Partners, like GuruCE. Same goes for people answering questions; be polite, remember your own struggles when you started with CE.

  • Proof read / Spell check
  • We understand English is not the language the entire world speaks fluently; it's not my native language either. Spelling mistakes are common and not a problem at all, but before you post; at least read back what you wrote and make sure it is understandable. Don't just check for funny sentences and spelling mistakes, make sure there is enough information in the message for other people to understand your problem. The quality of the answer can only be as good as the quality of the question!

  • MSN/SMS/Text Generation
  • Remember you are not paying per-byte on the newsgroups so please refrain from trying to shorten your messages. Do not use "ur" instead of "you're" or "2" instead of "to". It makes the message unreadable/annoying. R3m3mb3r typing l1k3 th1s do3s n0t m@k3 u 3733T, 1t 1ly m@k3$ u l00k st00p1d!

If you follow these rules (that are valid for any community driven forum) then there are no stupid questions and you will most certainly get an answer from one of the many volunteers answering questions in the newsgroups.

Here's a template you can use to ask questions in the CE newsgroups:

OS........: [CE 4.2 / CE 5.0 / CE 6.0 R2] [with QFE's until March 2008]
Hardware..: [Custom board / Device Solutions F200 / etc]
Processor.: [PXA270 / AMD AU1500 / etc]
BSP.......: [Custom BSP based on Mainstone III / T5530 / etc]
Kernel....: [Debug / Retail] [KITL enabled/disabled]

I am trying to [explain what you are trying to accomplish]

I am using [list the related component/driver including the full path]

I've tried to [explain what you have tried so far, how you tested, etc]

I searched the web and the newsgroups and found [this/nothing]:

[url to something relevant]

I've tried to implement that but it didn't solve my problem because [explain why not]


Here's the code I use:

[show a code snippet if relevant]

The call returns [whatever the call returns] and GetLastError() returns [whatever error it returns]

The CE debug output window shows this:

[list a couple of lines around the debug log lines that you think are relevant]


I would much appreciate any pointers you can give me.

Thanks!

[Your Name]

One final request: Return the favor!

Once you find a solution to your problem, either by yourself or because of a tip from somebody on the newsgroup, please spend 5 minutes to write it down and post it as a reply to your original question. Your solution may save somebody a lot of time and frustration in the future.

Why not become part of the community? Don't be afraid to answer questions if you think you know the answer!

CE 6.0 / R2 BSP template

The Windows CE Base Team just posted a CE 6.0 template for a BSP on their blog:

http://blogs.msdn.com/ce_base/archive/2008/05/30/bsp-template-now-available-for-ce-6-0-ce6r2.aspx

The template contains help and instructions on how to use the template as a base for your own BSP:

The BSP Template is designed to help developers who are new to Windows CE BSPs. It is also designed to assist developers who want more information on a specific CE BSP technology.

It contains information on KITL, bootloader, drivers and the OAL. If you are developing a BSP, porting a BSP from 5.0 to 6.0, developing drivers or are just interested in BSP development for CE 6.0 then download it and learn!

Download QFE's

How do you find the list of updates available for CE 4.2, 5.0 or 6.0?

The official way is to go to http://msdn.microsoft.com/en-us/embedded/aa731256.aspx and yes, it does show the QFE's for the various versions but wait...

...there's more!

Like the USB 6.0 webcam source, or the various help updates, or eVC 4.0 SP4, or the run-time assesment tool, or the mainstone BSP update, or ...

All things you may like to download too but that you won't find on the "official" page for downloading CE updates. If you'd like the complete list try these links:

CE 4.2

CE 5.0

CE 6.0

They show you 50 downloads per page sorted from new to old. Handy!

PS. Don't forget to download the QFEInstaller so you don't have to click the QFE installation wizard a million times: QFEInstaller

Command Line Build

This post will show you how to create a batch file that will build your Windows CE OS without using the Visual Studio/Platform Builder IDE.

Often the question is asked how to setup an environment which automatically extracts all information from version control and then builds your code. There are a lot of tools that can help you do that: an open source alternative can be found at http://cruisecontrol.sourceforge.net.

To build your kernel without having to use the IDE create a batch file with the following content:

@echo off
SET _WINCEROOT=C:\WINCE500
SET _OSDESIGNDIR=%_WINCEROOT%\PBWorkspaces\YOUR_WORKSPACE_FOLDER
SET _OSDESIGN=%_OSDESIGNDIR%\YOUR_WORKSPACE_FILE.pbxml
SET _OSDESIGNCONFIG=YOUR_OSDESIGN_CONFIG_NAME

"%ProgramFiles%\Windows CE Platform Builder\5.00\CEPB\BIN\pbxmlutils" /getbuildenv /workspace "%_OSDESIGN%" /config "%_OSDESIGNCONFIG%" > SetEnv.bat

cd "%_OSDESIGNDIR%"
call SetEnv.bat
delete SetEnv.bat
cd "%_OSDESIGNDIR%"
blddemo clean -q

Note that for CE 6.0 pbxmlutils is located in "%ProgramFiles%\Microsoft Platform Builder\6.00\cepb\IdeVS\" and you use SET _WINCEROOT=C:\WINCE600

You need to replace the first 3 SET variables to match your specific project:

YOUR_WORKSPACE_FOLDER: Workspace folder which is located under the PBWorkspaces (CE 5.0) or OSDesigns (CE 6.0) folder. This is the folder that contains YOUR_WORKSPACE_FILE.

YOUR_WORKSPACE_FILE: The name of your OS Design workspace; the file with extension .pbxml. This file is located in YOUR_WORKSPACE_FOLDER.

YOUR_OSDESIGN_CONFIG_NAME: This is the configuration name you select in the IDE of Platform Builder, eg Emulator: x86_Release. You can also open your .pbxml file with notepad to find out what the configuration name is (search for Configuration Name).

Happy building!

Windows CE Task Manager

I found a great tool to monitor the CPU load on your Windows CE device, which includes the source code!

http://urana.info/mobile/wince/itaskmgr/index.html

Offline installation of the CE 6.0 R2 update

The Microsoft installer for the CE 6.0 R2 update is a webinstaller. This means there is no way to download once - install often. Since the complete R2 update is over 1 GB of data it would be nice to be able to download the package to a folder from which you can install the R2 update offline. This is especially handy if you need to install the R2 update on multiple machines.

Another reason would be if you, like me, get errors during installation. When I tried to install the R2 update I got error messages like:

"Error 1335. The cabinet file 'MSI4E0.tmp' required for this installation is corrupt and cannot be used. This could indicate a network error, an error reading from the CD-ROM, or a problem with this package.".

So I dissected the MSI log files and was able to determine some downloaded cabinets were in fact corrupted. The corruption only seemed to occur when downloading using the Microsoft Installer package. When I manually downloaded the cabinets there was no corruption. By looking at the log files I could get a list of all required cabinet files. Since there are quite a few of those cabinets, and I figured this could be handy for more people, I wrote a tool that downloads the entire package from the Microsoft download server into a folder. Once the download is complete (1.1 GB) run the installer (Windows Embedded CE 6.0 R2.msi) to install the R2 update. You don't need to be connected to the internet while installing.

Download the tool (including source): DownloadCE6R2.zip

What to build when...

A question that keeps coming back on the newsgroups is "I changed some code, but it does not end up in my image", or "I changed some registry values in platform.reg, but if I look at the device registry, it's not there!".

The source of these problems is build-related. You've got to understand the build system in order to know exactly what to do. This blog post aims to give you a clear handle on "What to build when"!

The first and most important thing to do is to delete two options from the Platform Builder Build menu: Build and Sysgen and Rebuild and Clean Sysgen. These commands will build the entire tree (so including any source in PUBLIC and PRIVATE). For us normal developers these commands do not make any sense. If you're not in the CE development team working at Microsoft you simply don't have all the source code. Rebuilding the entire tree may work, but it will override any installed QFE (QFE's contain updated binaries and sometimes, but not always, updated source code). After rebuilding the entire tree you will sooner or later encounter strange errors that are either very hard to fix, or just impossible to fix. There is no way to revert back to a clean tree once you've done a (Re)Build and (Clean) Sysgen. The only way is to completely remove Windows CE and reinstall. Don't forget to reinstall all QFE's after that as well (download the QFEInstaller tool to help with that).

Now you know why you often see me shouting NEVER EVER DO A BUILD AND SYSGEN! in the newsgroups...

The original menu
The original Advanced Build Commands menu

Step 1.
Customize toolbar
Right click on the toolbar and click Customize...


Step 2.
Delete Build and Sysgen
Click on menu Build, submenu Advanced Build Commands, then right click on Build and Sysgen and click Delete


Step 3.
Delete Rebuild and Clean Sysgen
Do the same for Rebuild and Clean Sysgen


Step 4.
The original menu
Close the Customize window. The Advanced Build Commands menu should now have only 4 options: [Sysgen], [Clean Sysgen], [Build Current BSP and Subprojects] and [Rebuild Current BSP and Subprojects].



If you are still using Platform Builder for Windows CE 5.0 the instructions stay the same except the Build menu is Build OS and there's no submenu (everything is under the Build OS menu).

I know that some developers working with CE change code in the PUBLIC and PRIVATE trees. If you do that you need to do a build and sysgen to include your changes in your kernels BUT: You will end up with a corrupted CE installation. If you need to change PUBLIC or PRIVATE code you have to clone the code. Cloning code is in most cases not much more than a simple copy. Copy the sources you want to change to a folder in your BSP, change the sources file so that it builds a DLL instead of a lib and voila, you're done. I know I make it sound easier than it is, but taking a shortcut and modifying code in the PUBLIC or PRIVATE trees will prove to be a long way around in the long run.

Phew! Now that we got that out of the way let's see what build command we've got to use in what situation:

  • Change Platform Settings: Make image
  • Platform Settings
    If you change any Platform Settings (like IMGNOKITL, IMGNODEBUGGER, IMGPROFILER) all you have to do is a Make Image.

  • Change driver source code in your BSP: Build the driver and Make Image
  • If you change driver source code, all you have to do is just build the driver (WINCEREL must be set to 1 but it is set by default so unless you changed it there's no need to worry) and do a makeimg. If you only want to debug the driver you can also add the DLL to the Release Directory Modules list (menu Tools) and just restart the device (or reload the device driver on the device) without having to do a makeimg and download to device. Building just the driver is a simple right-click on the driver and Build in the IDE or "build" in the driver's folder on the command line.

  • Change multiple source code files in your BSP: Build the BSP and Make Image
  • The safe option, this way you can't forget to rebuild anything. Building the BSP is a simple right-click on the PLATFORM\BSP folder and Build in the IDE or "build" in the BSP's root folder on the command line.

  • Change platform.reg, bib, dat or db files in your BSP: Sysgen BSP, Copy Files to Release Directory, Build All Projects and Make Image
  • A lot of steps, but this will still not take longer than a couple of minutes. If you change any of the platform.* files we need to re-filter (Sysgen) those files and make sure the filtered files are copied into the FLATRELEASEDIR (Copy Files to Release Directory). That last action did however clear the project binaries from the FLATRELEASEDIR so we need to make sure those binaries and settings are getting copied into the FLATRELEASEDIR again (Build All Projects) and finally we are ready to Make Image. Now your registry changes will be in the image (check reginit.ini to make sure, last entry wins).

  • Change some source files and platform.reg, bib, dat or db files in your BSP: Build and Sysgen the BSP, Copy Files to Release Directory, Build All Projects and Make Image
  • Only difference with previous situation is that you now have to build the BSP to include the source code changes.

  • Change the workspace configuration (add or delete a component): Sysgen the entire workspace
  • For most components a simple Sysgen is enough. For some components (like when changing from RAM based registry to Hive based Registry) a Clean Sysgen is needed. This action takes the longest (anywhere from 5 minutes for a small workspace configuration on a very fast machine to a couple of hours for a really big configuration and a very slow machine). A Sysgen is a right-click on the workspace, Advanced Build Commands->Sysgen in the IDE or "blddemo -q" on the command line.

Sometimes it's easier to build from the command line. If you are unsure what command to type you can always perform the action in the IDE first and watch the 3rd line in the build output window starting with "Starting Build:". Behind the colon is the exact command line for that action, eg Sysgen on the BSP: "Starting Build: SysgenPlatform %_TARGETPLATROOT% preproc&&SysgenPlatform %_TARGETPLATROOT% postproc", so on the command line you would type "SysgenPlatform %_TARGETPLATROOT% preproc" followed by enter and the 2nd command "SysgenPlatform %_TARGETPLATROOT% postproc" followed by enter.

If you use the commandline, make sure you never forget "-q" when running "blddemo" as "blddemo" without "-q" is a Build and Sysgen!

I hope this blog post will help you speed up your builds and lower your frustration with the build system!

Good luck!

Air New Zealand Crash!

Last Sunday I got the biggest scare I have ever had on a plane. I am not scared of flying, in fact I love flying, but this time I got really, really, really scared...

I was watching a movie on the in-flight entertainment system. It was the middle of the night, half the plane was asleep when all of a sudden the stewardess taps me on the shoulder and says:

"Sir, we are shutting down the entertainment system because we are crashing..."

At that moment my heart rate went through the roof, I immediately started looking around to see if there was turmoil in the plane, then looked at her and said "What? Are we crashing?!". Apparently I had misunderstood her, she must've said "it [the entertainment system] is crashing". The stewardess apologized for the bad choice of words and we had a good (& very relieved) laugh about it.

Of course all this would not have happened if the in-flight entertainment system had not crashed...

In retrospect I am pleased it crashed because it gave me some insight in the system behind the Air New Zealand in-flight entertainment system: It is running CE.

CE.NET 4.xI should've been excited about that and used an exclamation mark on that last sentence, but how can I be excited about a system that has to be rebooted every 6 hours? I can imagine an already nervous passenger would become very nervous when he realizes even something simple like the in-flight entertainment system crashes!

Besides that obvious "crashing" problem the control of the system is sluggish, sometimes even completely non-responsive. The graphics of the "Air Show", the application that shows where the plane is, is however very impressive and looks very smooth. The board running CE is definitely fast enough to support fast 3D graphics and streaming video so why is control so slow? And why the crash?

I think the problem is that the company that designed the in-flight entertainment system used (this is an educated guess) the Internet Explorer as a host for the "Entertainment" application. The crash is probably caused by a memory leak in the browser application. Bad design? I think so...

Windows CE is a real-time operating system that can run 24/7 without ever having to reboot the system. In fact, several customers have been running CE devices for several years without ever having to reboot their device. I'm just saying I would love to take a closer look at the Air New Zealand entertainment system and see whether my educated guess is close to the truth.

So... Rockwell Collins (or Air NZ): Give us a call. I'm sure we can help you create a much more robust and responsive system.

Here's a funny side effect of a "mid-air crash" of the Air NZ in-flight entertainment system: LA to Auckland in a little over 6 hours with a Boeing 747-400, now THAT'S impressive! (Normal flying time is around 12 hours, video is bad quality but it says 6 hours 13 minutes...)
CE Boot 2
More info:
http://www.rockwellcollins.com/news/page5495.html

Welcome!

The first entry of the GuruCE blog of course has to be a special one!

Therefore, we start this blog with a tool I developed in the past:

The QFE Installer

Ever had to reinstall Windows CE? Then you had to reinstall all the QFE's as well. Not a nice task, it's lengthy and you have to stay close to your computer because there are about 12 screens you have click through before it actually installs. There is unfortunately no way to install all QFE's without a user controlling the mouse... until now!
The QFE installer installs all the QFE's you downloaded in the exact right order, from oldest to newest, without the need for one mouse click. Just copy the QFEInstaller.exe in the folder containing all the Windows CE QFE's and run it. Source available for free, just drop us a line.

Download QFEInstaller

Keep an eye on this blog for future tips & tricks and more tools for the CE developer!

Syndicate content