Migrating Applications from Windows XP Professional to Windows Embedded Standard 2009

4/24/2012

Gordon H. Smith, Windows Embedded MVP

May 2009

This technical article describes how you can take advantage of embedded features in Windows Embedded Standard 2009 when you migrate your applications from Windows XP Professional.

One of the many benefits of using Windows Embedded Standard 2009 is the application compatibility that you gain by using Windows XP Professional binaries in Windows Embedded Standard 2009 images. The APIs are the same, the DLLs and executables are the same, and their configuration is the same. Running an application developed for Windows XP Professional requires that you include the application’s dependencies in the custom developed operating system image.

However, you can increase the value of embedded applications by taking advantage of Windows Embedded Standard features, such as write filters, Hibernate One/Resume Many (HORM), and the ability to start from USB, features that are not available in Windows XP Professional. This article discusses some aspects to consider when planning your migration from Windows XP Professional to Windows Embedded Standard 2009.

One choice that you may face when you migrate to Windows Embedded Standard 2009 is whether to use the Explorer shell or a custom shell. The obvious advantage of using a custom shell is the increased control that you gain over the device. The only interaction an end user would have with your device is purely what you enable through your custom shell. However, you must consider usability when you use a custom shell. For example, because users would no longer have access to the Start menu, they will rely on your application to provide shutdown or restart functions. Depending on how you design your application, it might shut down in response to a button press, external stimuli, or not at all. Assuming that you want to support shutting down the operating system, you can provide that functionality in a several ways. Some of them include the following:

  • Use the xpepm.exe utility or shutdown.exe utility included with Windows Embedded Standard 2009 from a batch file.
  • Use the ExitWindowsEx Win32 API.
    #include <windows.h>
    
    BOOL MySystemShutdown()
    {
        HANDLE hToken; 
        TOKEN_PRIVILEGES tkp; 
     
        // Get a token for this process. 
        if (!OpenProcessToken(GetCurrentProcess(),
            TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) 
        {
            return FALSE; 
        }
     
        // Get the LUID for the shutdown privilege. 
        LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,
            &tkp.Privileges[0].Luid); 
     
        tkp.PrivilegeCount = 1;  // one privilege to set    
        tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 
     
        // Get the shutdown privilege for this process. 
        AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,
            (PTOKEN_PRIVILEGES)NULL, 0); 
     
        if (GetLastError() != ERROR_SUCCESS) 
        {
            return FALSE; 
        }
     
        // Shut down the system and force all applications to close.
        // If you want to restart instead of shutdown, replace
        // EWX_SHUTDOWN with EWX_REBOOT.
        if (!ExitWindowsEx(EWX_SHUTDOWN | EWX_FORCE, 
                   SHTDN_REASON_MAJOR_OPERATINGSYSTEM |
                   SHTDN_REASON_MINOR_UPGRADE |
                   SHTDN_REASON_FLAG_PLANNED)) 
        {
            return FALSE; 
        }
    
        // Shutdown was successful
        return TRUE;
    }
    
  • Start Shutdown.exe from code with appropriate parameters.
    System.Diagnostic.Process.Start("shutdown.exe", "-s -f -t 00");
    
  • Use the ManagementClass class to request the shutdown from code.
    // Remember to add a reference to the System.Management assembly
    using System.Management;
    
    namespace ShutDownExample
    {
        public partial class Form1 : Form
        {
            private void ShutdownOS()
            {
                ManagementBaseObject mboShutdown = null;
                ManagementClass mcWin32 = new
                    ManagementClass("Win32_OperatingSystem");
                mcWin32.Get();
    
                // You need security privileges to shut down
                mcWin32.Scope.Options.EnablePrivileges = true;
                ManagementBaseObject mboShutdownParams = 
                    mcWin32.GetMethodParameters("Win32Shutdown");
    
                // Flags 1 == shut down the system
                mboShutdownParams["Flags"] = "1";
                mboShutdownParams["Reserved"] = "0";
    
                foreach (ManagementObject manObj in mcWin32.GetInstances())
                {
                    mboShutdown = manObj.InvokeMethod("Win32Shutdown",
                        mboShutdownParams, null);
                }
            }
        }
    }
    

Many embedded devices have a regular operating mode intended for end users and a maintenance operating mode intended for administrative purposes. If you want to lock down your device so that an end user does not unintentionally corrupt the device’s configuration, you have to hide or disable administrative tasks during the device’s usual operating mode. Most developers approach this task in one of the following ways:

  • Separate accounts
    The first approach is to have two separate accounts on the device and configure each account to start a different shell. The typical end-user’s shell can be a custom shell (typically the embedded application) and the administrator’s shell can be the Explorer shell with access to control panel items, the command processor window, and more.
  • Automatic application start
    A second approach is to have only one account based on the Explorer shell and automatically start the application. The application can then intercept keystrokes to lock down the device and have its own mechanism for exposing a maintenance mode (by exiting the application and exposing the Explorer shell).

The first solution requires registry changes. The first registry key to change determines which registry key holds the default shell to load for user logons. This change instructs the operating system to look in each user’s registry tree for the shell to load instead of the one for the whole system.

Key: HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\
IniFileMapping\system.ini\boot\Shell
Type: REG_SZ
Old Value: SYS:Microsoft\Windows NT\CurrentVersion\Winlogon
New Value: USR:Software\Microsoft\Windows NT\CurrentVersion\Winlogon

The registry key that holds the default shell for the whole system can be left as Explorer.exe or can be changed to your application. The particular shell component selected in your Windows Embedded Standard 2009 configuration (.slx) file sets the default shell. If you want to change the default shell, you can do so after the First Boot Agent (FBA) runs.

Key: HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon \Shell
Type: REG_SZ
Old Value: Explorer.exe
New Value: <Complete file name and path of your executable>

With the first registry key now pointing to a registry key per user instead of a single registry key for the whole system, you can now create a registry key in each user account to designate that user’s particular shell. Because user accounts are created during or after FBA runs, you can create the registry keys more easily after FBA is run using Regedit.exe.

Key: HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Winlogon \Shell
Type: REG_SZ
Value: <Complete file name and path of your executable>

For devices that require the shortest possible start times, Windows Embedded Standard 2009 provides Hibernate Once/Read Many (HORM). This feature enables the device to capture the hibernation state, save it to disk, and then reload that state on each successive start. You must take some care when planning a HORM deployment. In addition to the typical concerns, such as guaranteeing enough free disk space to hold a hibernation file that is the same size as the device’s RAM footprint, consider what changes, if any, must persist after restarts.

It is helpful to understand the challenge that HORM introduces when changes to persistent data might occur. The hibernation file saves the RAM state to disk. This file includes any metadata about the state of the file system held in RAM at the time that the hibernation file is generated. The system state may include details about the location and contents of specific sectors on disk. If the underlying storage changes, and the device loads outdated information about that storage, corruption becomes very likely. Because of the risk of corruption, HORM requires that each volume be protected by the Enhanced Write Filter (EWF).

There are two general approaches for accommodating changes to the underlying disks. The first is to disable HORM, flush changes to disk, and then re-enable HORM. The second is to unmount a data volume before enabling HORM and to mount the volume after a HORM image is started (resumed). Both approaches are described here.

  1. Restart the system to discard any unwanted changes, and apply any changes that you want to your run-time image.

  2. At a command prompt, use EWF Manager to commit and disable EWF, as follows:

    ewfmgr c: -commitanddisable -live
    
  3. The hibernated image must be refreshed before your changes will take effect for successive system starts.

    For example, use the Windows XP Embedded Power Management Application component:

    xpepm -hibernate
    

The second approach (unmount/hibernate/mount) requires more work by the developer, but it also has certain advantages. The first solution causes the whole volume to commit. This might result in the writing of unintended changes to disk in addition to the planned changes. Depending on the startup media involved, you may not want the hibernation file to be rewritten. Having all changing parts, such as the application and data, reside on a second volume reduces those concerns.

The rationale and code for dismounting the data volume and putting the system into a hibernation step are covered in the MSDN technical article, Dismounting Volumes in a Hibernate Once/Resume Many Configuration, located at this Microsoft Web site.

It is helpful to understand the notification process before you remount a volume. Windows supports sending messages to an application through its WindowProc function.

Dd873584.note(en-us,MSDN.10).gifNote:
WindowProc is an application-defined callback function of type WNDPROC that processes messages sent from the operating system.

When a change in the power state occurs, Windows sends a WM_POWERBROADCAST message to all registered windows. Among the events supported by WM_POWERBROADCAST are events for suspending or resuming the operating system.

Events for operating system power management

The following table lists all the events related to suspending and resuming the operating system.

Event Name Description

WM_POWERBROADCAST

Message broadcast to the application through the WindowProc function to indicate that a power-management event has occurred or is occurring.

PBT_APMQUERYSUSPEND

Event that requests permission to suspend.

PBT_APMQUERYSUSPENDFAILED

Event that informs of a failed request to suspend.

PBT_APMSUSPEND

Event received just before suspending the system.

PBT_APMRESUMEAUTOMATIC

Event received to indicate that the operation is resuming automatically from a low-power state. This message is sent every time that the system resumes.

PBT_APMRESUMECRITICAL

Event that signals resume operation from an unknown and volatile state.

PBT_APMRESUMESUSPEND

Event received to indicate that operation is resuming from a low-power state. This message is sent after PBT_APMRESUMEAUTOMATIC if the resume operation is triggered by user input, such as pressing a key.

Although it is helpful to understand the sequence of events, in practice, you have to react to just one of these events for remounting your data volume.

Upon a standby or hibernation request, Windows XP first asks all windows if they want to deny the operation by sending a PBT_APMQUERYSUSPEND event to the windows. If any of those windows deny the operation, the operating system then informs all windows by sending a PBT_APMQUERYSUSPENDFAILED event. Otherwise, all windows receive a PBT_APMSUSPEND event.

When the operating system wakes up from a suspended state, it informs all windows by sending one or more resume events. If the system resumes from an unknown state, such as battery failure, the event is PBT_APMRESUMECRITICAL. In a typical process of resuming operation from a hibernated state, all windows receive the PBT_APMRESUMEAUTOMATIC event. If the resume operation occurs because of user input, such as moving the mouse, all windows also receive the PBT_APMRESUMESUSPEND event. In the Windows Embedded Standard 2009 case in which we are cold booting from a hibernation state, the relevant event to trigger your remount is the PBT_APMRESUMEAUTOMATIC event.

If your application is written in the Microsoft .NET Framework, you are abstracted from these details by the SystemEvents.PowerModeChanged event. However, the resume event here is not triggered until user input occurs (APMRESUMESUSPEND). This could lead to a poor user experience. In practice, you should incorporate the C or C++ method shown here to achieve your result. If you want to experiment with the .NET Framework approach, use the following code:

private void Form1_Load(object sender, EventArgs e)
{
    SystemEvents.PowerModeChanged += new
        PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
}

private void SystemEvents_PowerModeChanged(object sender,
    PowerModeChangedEventArgs e)
{
    switch (e.Mode)
    {
        case PowerModes.Suspend:
            // Insert code to close any resources 
            // using your data volume.
            //

            // Unmount your data volume.
            //
            break;

        case PowerModes.Resume:
            // The operating system will automatically mount
            // volumes as soon as they are are accessed.
            // This gives you a location to restore any
            // disconnected resources, which in turn
            // remounts the volume.
            //

            // Insert code to open any resources 
            // on your data volume.
            //
            break;
    }
}

If your application is written in C or C++, your code should resemble the following:

LRESULT CALLBACK DialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_POWERBROADCAST:
            switch (wParam)
            {
                case PBT_APMSUSPEND:
                    // Insert code to close any resources
                    // using your data volume.
                    //

                    // Unmount your data volume.
                    //
                    break;

                case PBT_APMRESUMEAUTOMATIC:
                    // The operating system will automatically mount
                    // volumes as soon as they are are accessed.
                    // This gives you a location to restore any
                    // disconnected resources, which in turn
                    // remounts the volume.
                    //

                    // Insert code to open any resources 
                    // on your data volume.
                    //
                    break;
            }
    }
    return FALSE;
}

What you have learned

This article discussed areas for potential improvement to your applications when you migrate from Windows XP Professional to Windows Embedded Standard 2009. Although applications will work without modification, in some circumstances you may want to change your application to take advantage of embedded features in Windows Embedded Standard.

Show: