Windows CE: Simple Little Text Editor


 The Simple Little Text Editor can be downloaded from: 
 
I recently decided that it was time to write my own Text Editor. I wrote this for my own needs, but am making it available to you.
My requirements are simple:
1.       Be able to view text files with any file extension.
2.    Be able to edit text files
2.       Need to support multiple CPU families and multiple OS versions – so this targets the .NET CompactFramework version 2.

What I came up with looks like this:


I provide this Simple Little Text Editor to you free of charge and without a license and without a warrantee. You may use it as you see fit, I just ask that you do not provide it to others for download, instead refer them to this page.
This is version 1.x, there will never be a version 2.x because I consider this to be feature complete for a free software download. But if you report bugs using the feedback below, I will work on fixing them. I am not providing the source code like I sometimes do, but if you are interested in obtaining the source code to use to add features I can be talked into that, but not for free.
 The Simple Little Text Editor can be downloaded from: 
  
History
V1.0 - August 19, 2013
First Release
Copyright © 2013 – Bruce Eitman
All Rights Reserved
 

 

author: Bruce Eitman | Posted On Monday, August 19, 2013 10:46 AM | Comments (2)

Simple Little Registry Editor - New Release


I have posted a new release of the Simple Little Registry Editor found in Windows CE: Simple Little Registry Editor

This release fixes a problem with writing DWORD values when the most significant bit is set.  The application uses RegistryKey.SetValue.  There seems to be a problem with how the .NET CompactFramework (and the full framework) handle the second argument during the call which causes an exception.

So the following does not work:

RegistryKey.SetValue( "TestValue", 0xFFFFFFFF, RegistryValueKind.DWord );

But, this does:

RegistryKey.SetValue( "TestValue",unchecked((int) 0xFFFFFFFF), RegistryValueKind.DWord );

Copyright © 2012 – Bruce Eitman
All Rights Reserved

author: Bruce Eitman | Posted On Monday, November 19, 2012 12:40 PM | Comments (0)

C#: My World Clock


 

I have been working on cleaning my office of 8 years of stuff from several engineers working on many projects.  It turns out that we have a few extra single board computers with displays, so at the end of the day last Friday I though why not create a little application to display the time, you know, a clock. 

How difficult could that be?  It turns out that it is quite simple – until I decided to gold plate the project by adding time displays for our offices around the world.

I decided to use C#, which actually made creating the main clock quite easy.   The application was simply a text box and a timer.  I set the timer to fire a couple of times a second, and when it does use a DateTime object to get the current time and retrieve a string to display.

And I could have been done, but of course that gold plating came up.   Seems simple enough, simply offset the time from the local time to the location that I want the time for and display it.    Sure enough, I had the time displayed for UK, Italy, Kansas City, Japan and China in no time at all.

But it is October, and for those of us still stuck with Daylight Savings Time, we know that the clocks are about to change.   My first attempt was to simply check to see if the local time was DST or Standard time, then change the offset for China.  China doesn’t have Daylight Savings Time.

If you know anything about the time changes around the world, you already know that my plan is flawed – in a big way.   It turns out that the transitions in and out of DST take place at different times around the world.   If you didn’t know that, do a quick search for “Daylight Savings” and you will find many WEB sites dedicated to tracking the time changes dates, and times.

Now the real challenge of this application; how do I programmatically find out when the time changes occur and handle them correctly?  After a considerable amount of research it turns out that the solution is to read the data from the registry and parse it to figure out when the time changes occur.

Reading Time Change Information from the Registry

Reading the data from the registry is simple, using the data is a little more complicated.  First, reading from the registry can be done like:

            byte[] binarydata = (byte[])Registry.GetValue("HKEY_LOCAL_MACHINE\\Time Zones\\Eastern Standard Time", "TZI", null);

 

Where I have hardcoded the registry key for example purposes, but in the end I will use some variables.  

We now have a binary blob with the data, but it needs to be converted to use the real data.   To start we will need a couple of structs to hold the data and make it usable.   We will need a SYSTEMTIME and REG_TZI_FORMAT.   You may have expected that we would need a TIME_ZONE_INFORMATION struct, but we don’t.   The data is stored in the registry as a REG_TZI_FORMAT, which excludes some of the values found in TIME_ZONE_INFORMATION.

    struct SYSTEMTIME

    {

        internal short wYear;

        internal short wMonth;

        internal short wDayOfWeek;

        internal short wDay;

        internal short wHour;

        internal short wMinute;

        internal short wSecond;

        internal short wMilliseconds;

    }

 

    struct REG_TZI_FORMAT

    {

        internal long Bias;

        internal long StdBias;

        internal long DSTBias;

        internal SYSTEMTIME StandardStart;

        internal SYSTEMTIME DSTStart;

    }

 

Now we need to convert the binary blob to a REG_TZI_FORMAT.   To do that I created the following helper functions:

        private void BinaryToSystemTime(ref SYSTEMTIME ST, byte[] binary, int offset)

        {

            ST.wYear = (short)(binary[offset + 0] + (binary[offset + 1] << 8));

            ST.wMonth = (short)(binary[offset + 2] + (binary[offset + 3] << 8));

            ST.wDayOfWeek = (short)(binary[offset + 4] + (binary[offset + 5] << 8));

            ST.wDay = (short)(binary[offset + 6] + (binary[offset + 7] << 8));

            ST.wHour = (short)(binary[offset + 8] + (binary[offset + 9] << 8));

            ST.wMinute = (short)(binary[offset + 10] + (binary[offset + 11] << 8));

            ST.wSecond = (short)(binary[offset + 12] + (binary[offset + 13] << 8));

            ST.wMilliseconds = (short)(binary[offset + 14] + (binary[offset + 15] << 8));

        }

 

 

        private REG_TZI_FORMAT ConvertFromBinary(byte[] binarydata)

        {

            REG_TZI_FORMAT RTZ = new REG_TZI_FORMAT();

 

            RTZ.Bias = binarydata[0] + (binarydata[1] << 8) + (binarydata[2] << 16) + (binarydata[3] << 24);

            RTZ.StdBias = binarydata[4] + (binarydata[5] << 8) + (binarydata[6] << 16) + (binarydata[7] << 24);

            RTZ.DSTBias = binarydata[8] + (binarydata[9] << 8) + (binarydata[10] << 16) + (binarydata[11] << 24);

            BinaryToSystemTime(ref RTZ.StandardStart, binarydata, 4 + 4 + 4);

            BinaryToSystemTime(ref RTZ.DSTStart, binarydata, 4 + 16 + 4 + 4);

 

            return RTZ;

        }

 

I am the first to admit that there may be a better way to get the settings from the registry and into the REG_TXI_FORMAT, but I am not a great C# programmer which I have said before on this blog.   So sometimes I chose brute force over elegant.

Now that we have the Bias information and the start date information, we can start to make sense of it.   The bias is an offset, in minutes, from local time (if already in local time for the time zone in question) to get to UTC – or as Microsoft defines it: UTC = local time + bias.  Standard bias is an offset to adjust for standard time, which I think is usually zero.   And DST bias is and offset to adjust for daylight savings time.

Since we don’t have the local time for a time zone other than the one that the computer is set to, what we first need to do is convert local time to UTC, which is simple enough using:

                DateTime.Now.ToUniversalTime();

Then, since we have UTC we need to do a little math to alter the formula to: local time = UTC – bias.  In other words, we need to subtract the bias minutes.

I am ahead of myself though, the standard and DST start dates really aren’t dates.   Instead they indicate the month, day of week and week number of the time change.   The dDay member of SYSTEM time will be set to the week number of the date change indicating that the change happens on the first, second… day of week of the month.  So we need to convert them to dates so that we can determine which bias to use, and when to change to a different bias.   To do that, I wrote the following function:

        private DateTime SystemTimeToDateTimeStart(SYSTEMTIME Time, int Year)

        {

            DayOfWeek[] Days = { DayOfWeek.Sunday,

DayOfWeek.Monday,

DayOfWeek.Tuesday,

DayOfWeek.Wednesday,

DayOfWeek.Thursday,

DayOfWeek.Friday,

DayOfWeek.Saturday };

            DateTime InfoTime = new DateTime(Year,

Time.wMonth,

Time.wDay == 1 ? 1 : ((Time.wDay - 1) * 7) + 1,

Time.wHour,

Time.wMinute,

Time.wSecond,

DateTimeKind.Utc);

            DateTime BestGuess = InfoTime;

            while (BestGuess.DayOfWeek != Days[Time.wDayOfWeek])

            {

                BestGuess = BestGuess.AddDays(1);

            }

            return BestGuess;

        }

 

SystemTimeToDateTimeStart gets two parameters; a SYSTEMTIME and a year.   The reason is that we will try this year and next year because we are interested in start dates that are in the future, not the past.  The function starts by getting a new Datetime with the first possible date and then looking for the correct date.

Using the start dates, we can then determine the correct bias to use, and the next date that time will change:

            NextTimeChange = StandardChange;

            CurrentBias = TimezoneSettings.Bias + TimezoneSettings.DSTBias;

            if (DSTChange.Year != 1 && StandardChange.Year != 1)

            {

                if (DSTChange.CompareTo(StandardChange) < 0)

                {

                    NextTimeChange = DSTChange;

                    CurrentBias = TimezoneSettings.StdBias + TimezoneSettings.Bias;

                }

            }

            else

            {

                // I don't like this, but it turns out that China Standard Time

                // has a DSTBias of -60 on every Windows system that I tested.

                // So, if no DST transitions, then just use the Bias without

                // any offset

                CurrentBias = TimezoneSettings.Bias;

            }

 

Note that some time zones do not change time, in which case the years will remain set to 1.   Further, I found that the registry settings are actually wrong in that the DST Bias is set to -60 for China even though there is not DST in China, so I ignore the standard and DST bias for those time zones.

There is one thing that I have not solved, and don’t plan to solve.  If the time zone for this computer changes, this application will not update the clock using the new time zone.  I tell  you this because you may need to deal with it – I do not because I won’t let the user get to the control panel applet to change the timezone.

Copyright © 2012 – Bruce Eitman

All Rights Reserved

author: Bruce Eitman | Posted On Thursday, October 25, 2012 2:14 PM | Comments (2)

Windows CE: Changing Static IP Address


A customer contacted me recently and asked me how to change a static IP address at runtime.  Of course this is not something that I know how to do, but with a little bit of research I figure out how to do it.

It turns out that the challenge is to request that the adapter update itself with the new IP Address.  Otherwise, the change in IP address is a matter of changing the address in the registry for the adapter.   The registry entry is something like:

[HKEY_LOCAL_MACHINE\Comm\LAN90001\Parms\TcpIp]
    "EnableDHCP"=dword:0
    "IpAddress"="192.168.0.100"
    "DefaultGateway"="192.168.0.1"
    "Subnetmask"="255.255.255.0"

Where LAN90001 would be replace with your adapter name.  I have written quite a few articles about how to modify the registry, including a registry editor that you could use.

Requesting that the adapter update itself is a matter of getting a handle to the NDIS driver, and then asking it to refresh the adapter.  The code is:

#include <windows.h>

#include "winioctl.h"

#include "ntddndis.h"

 

void RebindAdapter( TCHAR *adaptername )

{

      HANDLE hNdis;

      BOOL fResult = FALSE;

      int count;

 

      // Make this function easier to use - hide the need to have two null characters.

      int length = wcslen(adaptername);

      int AdapterSize = (length + 2) * sizeof( TCHAR );

      TCHAR *Adapter = malloc(AdapterSize);

      wcscpy( Adapter, adaptername );

      Adapter[ length ] = '\0';

      Adapter[ length +1 ] = '\0';

 

 

      hNdis = CreateFile(DD_NDIS_DEVICE_NAME,

                  GENERIC_READ | GENERIC_WRITE,

                  FILE_SHARE_READ | FILE_SHARE_WRITE,

                  NULL,

                  OPEN_ALWAYS,

                  0,

                  NULL);

 

      if (INVALID_HANDLE_VALUE != hNdis)

      {

            fResult = DeviceIoControl(hNdis,

                        IOCTL_NDIS_REBIND_ADAPTER,

                        Adapter,

                        AdapterSize,

                        NULL,

                        0,

                        &count,

                        NULL);

            if( !fResult )

            {

                  RETAILMSG( 1, (TEXT("DeviceIoControl failed %d\n"), GetLastError() ));

            }

            CloseHandle(hNdis);

      }

      else

      {

            RETAILMSG( 1, (TEXT("Failed to open NDIS Handle\n")));

      }

 

}

 

 

 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR    lpCmdLine, int       nCmdShow)

{

    RebindAdapter( TEXT("LAN90001") );

    return 0;

}

 

If you don’t want to write any code, but instead plan to use a registry editor to change the IP Address, then there is a command line utility to do the same thing.  NDISConfig.exe can be used:

Ndisconfig adapter rebind LAN90001

  

Copyright © 2012 – Bruce Eitman

All Rights Reserved

author: Bruce Eitman | Posted On Wednesday, October 24, 2012 9:00 PM | Comments (1)

Windows CE: C# Application to Format TFAT


Windows CE: C# Application to Format TFAT
In Windows CE: Formatting TFAT I show how to format a disk with TFAT using C/C++ code. The customer that I wrote that for develops using C#, so I wrote a C# application to test the formatting code. To me, most formatting tools are just too difficult for the average user. In this case, the user will be someone working in the factory or field service. They just need to select a disk and request that it be formatted.
The application dialog look like:
It has a drop down list which I populated with a list of disks, a format button and an exit button. It is simple, and easy to use.
The hard part of this application is populating the drop down list. I did some searching and found suggestion of using System.IO.DirectoryInfo to find the root folders and check the attributes for temporary folders. The following code demonstrates this approach, were DiskSelect is the drop down list:
            System.IO.DirectoryInfo DI = new System.IO.DirectoryInfo(@"\");
 
            System.IO.DirectoryInfo[] Folders = DI.GetDirectories();
            foreach (System.IO.DirectoryInfo FDI in Folders)
            {
                if (FDI.Attributes.CompareTo(System.IO.FileAttributes.Temporary) == 1)
                    DiskSelect.Items.Add(FDI.Name);
            }
            if( DiskSelect.Items.Count > 0 )
               DiskSelect.SelectedIndex = 0;
This code works and can be effective on some systems. But it has a flaw which is that not all temprary folders are disks. If your OS includes the network redirector component, there will be a Network folder which is temporary but is not a disk. The Release folder which is used in debugging the OS using Platform Builder is also temporary, but not a disk.
The only alternative that I could find is to use FindFirstStore()/FindNextStore() and FindFirstPartition()/FindNextPartition() which are included in StoreAPI. The downside of this approach is that StoreAPI is not generaly included in the SDK and worse, it is not a DLL in the system so it cannot be P/Invoked. So if you don’t have access to the StoreAPI, you might as well stop reading now – the rest of this assumes that you do have access to StoreAPI. If you don’t have it, you might want to ask your OEM for it because it has some valuable functions for accessing disk information.
I could have simply wrapped the StoreAPI with a DLL, which would have been as simple as just linking StoreAPI.lib with a DllEntry() function. But I am not a good enough C# programmer to define the structures needed for C# and I felt that for a C# program the functions are just too clunky. Instead I made some assumptions before writing the code. The assumptions are:
1.       The functions that I will be writing will be used for populating a drop down list and nothing more sophisticated
2.       The disks have only one partition, if you are working with systems that have multiple partitions you can extend the code to handle that but I didn’t need it
I realized that I needed to have some data that I could let the application track and pass into function calls. So I started by defining the structure that I needed:
typedef struct __FindDiskInfo__
{
                STOREINFO StoreInfo;
                PARTINFO PartInfo;
} FindDiskInfoT;
STOREINFO and PARTINFO are defined in storemgr.h which is included in the SDK. I plan to create functions that populate the structure and then some functions to get the strings that I need. This should make the C# fairly easy to write. The first step is to initalize the structure and get a pointer/reference from the C/C++ code to the C# code. NewStoreInfo() will allocate the storage and initialize it and DeleteStoreInfo() will release the structure:
__declspec(dllexport) FindDiskInfoT * APIENTRY NewStoreInfo()
{
                FindDiskInfoT *FindDiskInfo;
                RETAILMSG( 1, (TEXT("NewStoreInfo\n")));
                FindDiskInfo = (FindDiskInfoT *)malloc( sizeof(FindDiskInfoT) );
                if( FindDiskInfo == NULL )
                {
                                RETAILMSG( 1, (TEXT("StartFindStore: malloc failed\n")));
                                return NULL;
                }
                memset(FindDiskInfo, 0, sizeof(FindDiskInfoT));
                FindDiskInfo->StoreInfo.cbSize = sizeof(STOREINFO);
                FindDiskInfo->PartInfo.cbSize = sizeof(PARTINFO);
                                wcscpy(FindDiskInfo->PartInfo.szVolumeName, TEXT("TEST VolName"));
 
                RETAILMSG( 1, (TEXT("NewStoreInfo return %X\n"), FindDiskInfo));
                return FindDiskInfo;
}
 
__declspec(dllexport) BOOL APIENTRY DeleteStoreInfo(HANDLE hFind, FindDiskInfoT *FindDiskInfo)
{
                if( FindDiskInfo != NULL )
                {
                                free( FindDiskInfo );
                }
                if( hFind )
                {
                                FindCloseStore( hFind );
                }
                return TRUE;
}
And the C# call to these function is:
IntPtr StoreInfo;
 
StoreInfo = NewStoreInfo();
DeleteStoreInfo(hStore, StoreInfo);
Note that DeleteStoreInfo() has an hStore passed into into it. hStore is HANDLE used for finding the disks. hStore is returned from StartFindDisk() which is called to start finding available disks. StartFindDisk() receives a pointer to the previously allocated FindDiskInfoT:
__declspec(dllexport) HANDLE APIENTRY StartFindDisk( FindDiskInfoT *FindDiskInfo )
{
HANDLE hFind = INVALID_HANDLE_VALUE;
 
                hFind = FindFirstStore(&(FindDiskInfo->StoreInfo));
               
                if( hFind != INVALID_HANDLE_VALUE )
                {
                                HANDLE hStore;
                                hStore = OpenStore( FindDiskInfo->StoreInfo.szDeviceName );               
 
                                if( hStore )
                                {
                                                HANDLE hFind = INVALID_HANDLE_VALUE;
                                                hFind = FindFirstPartition(hStore, &(FindDiskInfo->PartInfo));
 
                                                if(INVALID_HANDLE_VALUE != hFind)
                                                {
                                                                FindClose(hFind);
                                                }
                                                else
                                                                RETAILMSG( 1, (TEXT("No Partitions Found\n")));
                                                CloseHandle(hStore);
                                }
                                else
                                                RETAILMSG(1, (TEXT("OpenSelectedStore failed\n")));
                }
                else
                {
                                RETAILMSG( 1, (TEXT("StartFindStore: FindFirstStore failed %d\n"), GetLastError()));
                }
               
                return hFind;
}
And the C# call is:
            hStore = StartFindDisk(StoreInfo);
StartFindDisk() finds the first available disk and the first partition on the disk and populates the FindDiskInfoT structure with the available information for use later. After calling this, to get the name of the folder, the device name and the partition name, the following functions can be used:
__declspec(dllexport) TCHAR * APIENTRY GetStoreDeviceName( FindDiskInfoT *FindDiskInfo )
{
                TCHAR * RetVal = NULL;
                if( FindDiskInfo )
                {
                                RetVal = FindDiskInfo->StoreInfo.szDeviceName;
                }
                return RetVal;
}
 
__declspec(dllexport) TCHAR * APIENTRY GetStoreFolderName( FindDiskInfoT *FindDiskInfo )
{
                TCHAR * RetVal = NULL;
                if( FindDiskInfo )
                {
                                RetVal = FindDiskInfo->PartInfo.szVolumeName;
                }
                return RetVal;
}
 
__declspec(dllexport) TCHAR * APIENTRY GetStorePartitionName( FindDiskInfoT *FindDiskInfo )
{
                TCHAR * RetVal = NULL;
                if( FindDiskInfo )
                {
                                RetVal = FindDiskInfo->PartInfo.szPartitionName;
                }
                return RetVal;
}
Then to get the rest of the disks, FindNextDisk() is used:
__declspec(dllexport) BOOL APIENTRY FindNextDisk( HANDLE hFind, FindDiskInfoT *FindDiskInfo )
{
BOOL FindResult = FALSE;
 
                if( FindDiskInfo == NULL || hFind == INVALID_HANDLE_VALUE )
                {
                                                RETAILMSG( 1, (TEXT("FindNextDisk: invalid parameters\n")));
                                                return FALSE;
                }
                FindResult = FindNextStore(hFind, &(FindDiskInfo->StoreInfo));
               
                if( FindResult != FALSE )
                {
                                HANDLE hStore;
                                hStore = OpenStore( FindDiskInfo->StoreInfo.szDeviceName );               
 
                                if( hStore )
                                {
                                                HANDLE hFind = INVALID_HANDLE_VALUE;
                                                memset( &(FindDiskInfo->PartInfo), 0, sizeof(PARTINFO));
                                                FindDiskInfo->PartInfo.cbSize = sizeof(PARTINFO);
 
                                                hFind = FindFirstPartition(hStore, &(FindDiskInfo->PartInfo));
 
                                                if(INVALID_HANDLE_VALUE != hFind)
                                                {
                                                                FindClose(hFind);
                                                }
                                                else
                                                {
                                                                RETAILMSG( 1, (TEXT("FindNextDisk: No Partitions Found %d\n"), GetLastError()));
                                                                FindResult = FALSE;
                                                }
                                                CloseHandle(hStore);
                                }
                                else
                                {
                                                RETAILMSG(1, (TEXT("FindNextDisk: OpenSelectedStore failed\n")));
                                                FindResult = FALSE;
                                }
                }
                else
                {
                                RETAILMSG( 1, (TEXT("FindNextDisk: FindNextStore failed %d\n"), GetLastError()));
                                FindResult = FALSE;
                }
               
                return FindResult;
}
Similar to StartFindDisk() this finds the next disk and its first partition and populates the FindDiskInfoT with the available data, so that the strings can be retreived later.
The code for setting up the C# dialog looks like:
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool FormatDiskTFAT(String StoreName, String PartitionName);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool FormatDiskTFATEx(String FolderName);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr StartFindDisk(IntPtr pStoreInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool DeleteStoreInfo(IntPtr hStore, IntPtr FindDiskInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool FindNextDisk(IntPtr hFind, IntPtr pStoreInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr GetStoreDeviceName(IntPtr pStoreInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr GetStoreFolderName(IntPtr pStoreInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr GetStorePartitionName(IntPtr pStoreInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr NewStoreInfo();
 
        DiskInfo[] Disks;
 
        public FormatTool()
        {
            IntPtr StoreInfo;
            IntPtr hStore;
            IntPtr ipName;
            int DiskCount = 0;
 
            InitializeComponent();
          
            StoreInfo = NewStoreInfo();
            hStore = StartFindDisk(StoreInfo);
 
            if (hStore != null)
            {
                Disks = new DiskInfo[1];
                Disks[DiskCount] = new DiskInfo();
 
                ipName = GetStoreDeviceName(StoreInfo);
                Disks[DiskCount].DeviceName = Marshal.PtrToStringUni(ipName).ToString();
 
                ipName = GetStoreFolderName(StoreInfo);
                Disks[DiskCount].FolderName = Marshal.PtrToStringUni(ipName).ToString();
 
                ipName = GetStorePartitionName(StoreInfo);
                Disks[DiskCount].PartitionName = Marshal.PtrToStringUni(ipName).ToString();
 
                DiskSelect.Items.Add(Disks[DiskCount].FolderName);
                DiskSelect.SelectedIndex = 0;
                DiskCount++;
 
                while (FindNextDisk(hStore, StoreInfo))
                {
                    DiskInfo[] NewDisks = new DiskInfo[DiskCount+1];
                    Disks.CopyTo(NewDisks, 0);
                    NewDisks[DiskCount] = new DiskInfo();
 
                    ipName = GetStoreDeviceName(StoreInfo);
                    NewDisks[DiskCount].DeviceName = Marshal.PtrToStringUni(ipName).ToString();
 
                    ipName = GetStoreFolderName(StoreInfo);
                    NewDisks[DiskCount].FolderName = Marshal.PtrToStringUni(ipName).ToString();
 
                    ipName = GetStorePartitionName(StoreInfo);
                    NewDisks[DiskCount].PartitionName = Marshal.PtrToStringUni(ipName).ToString();
 
                    DiskSelect.Items.Add(NewDisks[DiskCount].FolderName);
                    DiskCount++;
                    Disks = NewDisks;
                }
                DeleteStoreInfo(hStore, StoreInfo);
                if (DiskSelect.Items.Count > 0)
                    DiskSelect.SelectedIndex = 0;
            }
            
        }
I probably could have simplified this code, and if it were a long term project I would spend time on that.   This cod uses a class for keeping track of the disk information for use in the call to FormatDiskTFAT:
    public class DiskInfo
    {
        public String FolderName;
        public String DeviceName;
        public String PartitionName;
    }
But in hindsight I wrote FormatDiskTFATEx() which doesn’t need all of that information. So the code can be simplified to:
            String DiskName;
            StoreInfo = NewStoreInfo();
            hStore = StartFindDisk(StoreInfo);
 
            if (hStore != null)
            {
                ipName = GetStorePartitionName(StoreInfo);
                DiskName = Marshal.PtrToStringUni(ipName).ToString();
 
                DiskSelect.Items.Add(DiskName);
                DiskSelect.SelectedIndex = 0;
 
                while (FindNextDisk(hStore, StoreInfo))
                {
                    ipName = GetStorePartitionName(StoreInfo);
                    DiskName = Marshal.PtrToStringUni(ipName).ToString();
 
                    DiskSelect.Items.Add(DiskName);
                }
                DeleteStoreInfo(hStore, StoreInfo);
                if (DiskSelect.Items.Count > 0)
                    DiskSelect.SelectedIndex = 0;
            }
Finally, when the Format button is pressed we need to format the disk. The button handler is:
        private void FormatButton_Click(object sender, EventArgs e)
        {
            int Index = DiskSelect.SelectedIndex;
 
            Exitbutton.Visible = false;
            FormatButton.Visible = false;
            Formatinglabel.Text = "Formatting, Please Wait";
            Formatinglabel.Visible = true;
            this.Refresh();
 
            System.Threading.Thread.Sleep(2000); 
            FormatDiskTFAT(Disks[Index].DeviceName, Disks[Index].PartitionName);
 
            Exitbutton.Visible = true;
            FormatButton.Visible = true;
            Formatinglabel.Text = "Formatting Complete";
            Formatinglabel.Visible = true;
            this.Refresh();
        }
Which uses FormatDiskTFAT(), but FormatDiskTFATEx() can be used as:
            FormatDiskTFATEx(DiskSelect.SelectedItem.ToString());
FormatButton_Click() hides the buttons and puts a message in a static text box to tell the user to wait then formats the disk. After the disk is formatted, it changes the text to Format Complete and puts the buttons back in. The problem that I found is that the format can be very quick, too quick so that the message isn’t displayed and the buttons aren’t removed. So I added a 2 second delay just to let the user know that something happened.
 
Copyright © 2010 – Bruce Eitman
All Rights Reserved

author: Bruce Eitman | Posted On Monday, August 30, 2010 10:34 PM | Comments (3)

Windows CE: Formatting TFAT


A customer came to me for assistance in formatting an SD card with TFAT. I did a quick look in the control panel code to find how it formats disks as TFAT and told the customer to use FormatVolume() and set the FORMAT_OPTIONS dwFlags FATUTIL_FORMAT_TFAT flag.   
I assumed that it was that easy and left the customer to write the code. A few weeks later the customer came back to me and said that they just couldn’t get FormatVolume() to work, let alone format the disk as TFAT. So this time I dug deeper and write some code to test it out before getting back to the customer. The following is what I figured out including the code to format a disk as TFAT.
What went wrong? When I first looked at how to format TFAT, I looked to see what happens when the Format button is selected. What I didn’t look for is what has to happen before the Format button is selectable. It turns out that before the Format button is selectable, the partition must first be dismounted.
__declspec(dllexport) BOOL APIENTRY FormatDiskTFAT( TCHAR *StoreName, TCHAR *PartitionName )
{
                HANDLE hStore;
                HANDLE hPartition;
 
                hStore = OpenStore(StoreName);          
 
                if( INVALID_HANDLE_VALUE != hStore )
                {
                                hPartition = OpenPartition( hStore, PartitionName );
                                if( INVALID_HANDLE_VALUE != hPartition )
                                {
                                                if( DismountPartition(hPartition) )
                                                {   
                                                                FORMAT_OPTIONS fo;
 
                                                                // Get formating options
                                                                fo.dwClusSize = 4 * 1024;
                                                                fo.dwFatVersion = 32;
                                                                fo.dwNumFats = 2;
                                                                fo.dwRootEntries = 512;
 
                                                                fo.dwFlags = 0;
                                                                //fo.dwFlags |= FATUTIL_FULL_FORMAT;
                                                                fo.dwFlags |= FATUTIL_FORMAT_TFAT;
 
                                                                if( ERROR_SUCCESS != pfnFormatVolume( hPartition, NULL, &fo, HandleProgress, HandleMessage ))
                                                                                RETAILMSG( 1, (TEXT("Format Failed\n")));
                                                                MountPartition(hPartition);
                                                }
                                                else
                                                                RETAILMSG( 1, (TEXT("Failed to dismount Partition\n")));
 
                                                CloseHandle( hPartition );
                                }
                                else
                                                RETAILMSG(1, (TEXT("OpenPartition failed\n")));
 
                                CloseHandle(hStore);
                }
                else
                                RETAILMSG(1, (TEXT("OpenSelectedStore failed\n")));
 
                return TRUE;
}
FormatDiskTFAT() takes two strings as arguments:
·         StoreName which is the szDeviceName in the STOREINFO structure returned from FindFirstStore()/FindNextStore() or the szStoreName in the CE_VOLUME_INFO structure returned from CeGetVolumeInfo(). The value of the string will be somethings like “DSK1:”.
·         PartitionName which is the szPartionName in PARTINFO and CE_VOLUME_INFO returned by FindFirstPartition()/FindNextPartition() and CeGetVolumeInfo(). The value of the string will be something like “PART00”.
FormatDiskTFAT() uses the strings to open the store and the partition. Then it dismounts the partition, formats the disk and re-mounts the partition.
I choose the function signature because I was thinking about using FindFirstStore()/FindNextStore(), so the device name and the partition name would be readily available. As an afterthought, I decided that it might be handy to have a function that simply takes the folder name of the disk, so I came up with FormatDiskTFATEx():
__declspec(dllexport) BOOL APIENTRY FormatDiskTFATEx( TCHAR *FolderName )
{
                CE_VOLUME_INFO VolInfo;
                VolInfo.cbSize = sizeof( CE_VOLUME_INFO );
 
                CeGetVolumeInfo(FolderName, CeVolumeInfoLevelStandard, &VolInfo );
                return FormatDiskTFAT( VolInfo.szStoreName, VolInfo.szPartitionName );
}
FormatDiskTFATEx() takes one string as an argument which is the name of the disk folder. This will be something like “Storage Card” or “\Storage Card” depending on how you obtain the string.
One of the interesting problems that I ran into is that building this for Windows CE 5.0 (I have not tested yet with CE 6.0) the call to CeGetVolumeInfo() causes a linker error because CeGetVolumeInfoW() does not exist. The solution to this problem is to undefine CeGetVolumeInfo(). So I added:
#ifdef CeGetVolumeInfo
#undef CeGetVolumeInfo
#endif
Then next problem that I needed to solve is that while I can easily compile this code using Platform Builder, my customer who is using an SDK that I provided cannot. The problem is that FATUTIL.DLL is included in the OS, but FATUTIL.LIB is not included in the SDK and FormatVolume() is in FATUTIL. It would be easy enough to create a new SDK and release it, but that may be overkill for one function. So the solution that I came up with is to use LoadLibrary() and GetProcAddress().   I am including these functions in a DLL, so I did the mapping in DllEntry():
HMODULE hFatUtil = NULL;
DWORD (*pfnFormatVolume)(HANDLE hVolume,void *pdi,FORMAT_OPTIONS *pfo,void *pfnProgress,void *pfnMessage);
 
static void *GetFormatVolume(HMODULE *hFatUtil);
 
 
BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD ul_reason_for_call,
                       LPVOID lpReserved
                                                                                 )
{
                switch( ul_reason_for_call )
                {
                                case DLL_PROCESS_ATTACH:
                                                pfnFormatVolume = GetFormatVolume(&hFatUtil);
                                                break;
                                case DLL_PROCESS_DETACH:
                                                FreeLibrary(hFatUtil);
                                                break;
                }
    return TRUE;
}
The last problem is that FORMAT_OPTIONS needed by FormatVolume() is also not available in the SDK, or more accuratly the header file that they are included in doesn’t compile cleanly, so I included them in the C code:
#define FATUTIL_FULL_FORMAT                 0x1
#define FATUTIL_FORMAT_TFAT                 0x2
 
typedef struct _FORMAT_OPTIONS {
    DWORD   dwClusSize;
    DWORD   dwRootEntries;
    DWORD   dwFatVersion;
    DWORD   dwNumFats;
    DWORD   dwFlags;
} FORMAT_OPTIONS;
In my next article (see Windows CE: C# Application to Format TFAT), I will show how to use these functions from C#. As for using them from C/C++, you may want to review Windows CE: Displaying Disk Information for example code that uses FindFirstStore()/FindNextStore() and FindFirstPartition()/FindNextPartition() to enumerate the mounted disks.
NOTE 1: In FormatDiskTFAT() I have commented out the line “//fo.dwFlags |= FATUTIL_FULL_FORMAT;” because in my testing with both this code and the Storage Manager control panel applet, the full format would not complete.
NOTE 2: I made a conscious choice to hardcode the formatting options in FormatDiskTFAT() because while these options are interesting, they just shouldn’t be user options in an embedded system. I could have pushed them up to the caller function, but that opens the door for a confusing user interface.
Copyright © 2010 – Bruce Eitman
All Rights Reserved

author: Bruce Eitman | Posted On Sunday, August 29, 2010 10:42 PM | Comments (7)

Windows Compact: Discussion Forums


I monitor some Windows Compact (the new name for Windows CE) Forums for a few reasons:
  • I learn a lot from others working on Windows CE, so many of you are running into the same issues that I run into.  Sometimes you run into them before I do, other times I have already run into the issue.
  • I learn a lot from answering questions.  You might be surprised to find that sometimes I don't already know the answer, so I do some investigation to get the answer.  Sometimes I am wrong, or there is a better way, with my answer, and there is always someone to point that out.
  • I get satisfaction from helping others by answering their questions
I monitor the following Forums:
  1. Windows Embedded Compact Managed Application Development which discusses C# and VB .NET managed application development
  2. Windows Embedded Compact Native Application Development is a forum for discussing C/C++ application development
  3. Windows Embedded Compact Platform Development a forum for discussing issues related to use of Platform Builder for Windows Compact OS development

 

I use Internet Explorer to monitor and answer questions. Windows Embedded Compact Category is a page which lists all three of the Windows Compact forums.

Copyright © 2010 – Bruce Eitman
All Rights Reserved

author: Bruce Eitman | Posted On Wednesday, August 18, 2010 7:53 PM | Comments (14)

Platform Builder: Write, Debug, Fix, Debug…


The question came up again about how to quickly test an application or driver using Platform Builder and KITL. This article will show how this test cycle can be done quickly without building a new OS and downloading it.
Of course Platform Builder is a tool for developing an operating system, so if you use its default behavior it will build an operating system. So that means that a small change to source code means building the operating system and deploying it to your device. But we will look at how we can do things a little differently and speed up the process. 
The basic steps are:
1.       Add KITL support
2.       Add Target Control Support (Shell.exe)
3.       Leave your app out of the OS, but leave in the registry settings
4.       Build your OS
5.       Start the OS and connect KITL
6.       When your run the app, it will load over your KITL connection
7.       Update your app
8.       Build your app
9.       Repeat starting at step 6
For this article I am not going to show you everything, but I will describe what I did to write an application and test it using KITL without rebuilding the OS. I am writing this while sitting in an airport, so I am using the emulator but these techniques work just as well with a real device, maybe better.
I started by creating a new project, not that this is important but I didn’t have one to work with. I wanted to keep it simple so I chose the Emulator BSP and the Thin Client template. I left out anything that I didn’t need for this article, like the .NET CompactFramework. I double checked that KITL was enabled and that Target Control Support was included. Target Control Support is important to this because it adds support for the \Release folder which links back to the _FLATRELEASEDIR on the development PC. I change the shell from the Thin Client shell to the standard shell, honestly I am not familiar with the Thin Client shell and felt more comfortable with the standard shell for a quick test.
Next I added a subproject. I am adding it now, before building the OS, so that it more closely simulates a real world project. The project is a WCE Application and I selected a simple Hello World template. This template doesn’t provide a menu with an option to Exit, so using code from Windows CE: Setting an Application to Display Full Screen in C/C++ I added an exit button. Being able to exit the application being tested will be very important for speeding up the test cycle.
We are almost ready to build and test, but first, and this is important for speeding up the test cycle, remove the subproject application from the OS image.   If you are testing a driver, the same thing applies. There are a couple of ways to remove the application from the image:
1.       In the OSDesign Properties, select Configuration Properties\Subproject Image Settings. Double click on the subproject that you are working on, and then select Exclude from Image.
 
 


It took me a long time to find this property. In prior versions of Platform Builder it was much easier to exclude a file from the OS. For that reason, my first test I used option 2.
2.       Comment out the file in the bib file.
It is finally time to build the OS and run the first test. For this, run Sysgen and attach to the device.
Once the OS is running, you can start your application by using Start\Run. Looking at the debug output you will see that the application was loaded by RelFSD, which is the \Release folder.
That means that the application was downloaded from your _FLATRELEASEDIR and was not in the OS image. If it isn’t in the OS image, which means that we can change it without changing the OS image, or for that matter restarting the device and downloading the image. The application looks like this when I run it:
The next step is to modify the application. In this case, I want to app to say “The App is running” instead of “Hello World!” So I changed the string table to modify the string that is displayed.  I also fixed the location of the button so that it doesn't sit on top of the text.
Now we need to build the application, not the whole OS. I am a command line kind of engineer, so I would open a build window, change directories to the subproject folder and run build. If you like to use Visual Studio, then start by unchecking Build\Targeted Build Settings\Make Run-Time Image After Build, which is important because we don’t need to make the run-time image and the process takes considerable time.   Now you can right click on your subproject in the Solution Explorer and select Build (for this to work well, set the environment variable WINCEREL to 1).
Finally, run the application again, now it looks like:
To use this same technique to test a stream interface driver, leave the driver and its registry settings out of the OS. Then call ActivateDevice() and DeactivateDevice() from your application as shown in Windows CE: Loading a Driver with ActivateDeviceEx.
 
Copyright © 2010 – Bruce Eitman
All Rights Reserved

author: Bruce Eitman | Posted On Tuesday, August 17, 2010 8:28 PM | Comments (2)

Windows CE: Disable the Taskbar for Full Screen Applications


In the article Windows CE: Setting an Application to Display Full Screen in C/C++ I discussed how to show an application full screen using C++. I discussed that for a kiosk application that the easiest way to disable the Explorer shell taskbar is to simply not have the Explorer shell launch when the system starts up. But there are cases where having the Explorer shell run underneath the kiosk app has value in some cases. The trouble is that the taskbar will still respond to special keys like the Windows key and suddenly the user has access to the full system. In this article, we will explore disabling the taskbar.
The first step to disable the taskbar is to get a handle to the taskbar window. To do this we will use FindWindow() and the class name of the taskbar which is “HHTaskBar”. We know that the name is HHTaskBar because the code for the taskbar is available in Platform Builder and a review of the code tells us the name. So to get a handle to the taskbar window:
                HWND TaskBar = FindWindow(TEXT("HHTaskBar"), NULL );
Now that we have a handle to the taskbar window, we can send it messages. We could send it a WM_CLOSE message:
                SendMessage(TaskBar, WM_CLOSE, NULL, NULL );
But, that will close the Explore shell, which defeats the purpose which is to keep the Explorer shell running and have access to the APIs that it provides.
Better is to hide the taskbar, not in the sense of setting its auto hide feature which leaves it usable but minimized. Instead we will disable it with a call to EnableWindow():
                EnableWindow( TaskBar, FALSE );
Which also allows us to reenable the taskbar if and when we need to do so.
In the modified full screen application from Windows CE: Setting an Application to Display Full Screen in C/C++, the WinMain() is now:
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPTSTR    lpCmdLine,
                   int       nCmdShow)
{
                MSG msg;
 
                if( lpCmdLine )
                                SignalStarted( _wtol(lpCmdLine) );
                HWND TaskBar = FindWindow(TEXT("HHTaskBar"), NULL );
                if( TaskBar != NULL )
                {
                                EnableWindow( TaskBar, FALSE );
                }
 
                // Perform application initialization:
                if (!InitInstance(hInstance, nCmdShow))
                {
                                if( TaskBar != NULL )
                                {
                                                EnableWindow( TaskBar, TRUE );
                                }
                                return FALSE;
                }
 
                HACCEL hAccelTable;
                hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_DELETEMENEXT));
 
                // Main message loop:
                while (GetMessage(&msg, NULL, 0, 0))
                {
                                if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
                                {
                                                TranslateMessage(&msg);
                                                DispatchMessage(&msg);
                                }
                }
                TaskBar = FindWindow(TEXT("HHTaskBar"), NULL );
                if( TaskBar != NULL )
                {
                                EnableWindow( TaskBar, TRUE );
                }
                return (int) msg.wParam;
}
Which not only disables the taskbar when the application starts, but reenables it when the application exits.
Copyright © 2010 – Bruce Eitman
All Rights Reserved

author: Bruce Eitman | Posted On Sunday, August 15, 2010 10:59 PM | Comments (0)

Windows CE: Setting an Application to Display Full Screen in C/C++


Many, maybe even most, Windows CE devices are single purpose devices that have an application that fills the entire screen. So of course setting the application to entirely fill the display is important. In this article we will explore setting an application to display full screen using C/C++.
My plan is to create a simple application that fills the screen with white and displays two buttons, exit and explore. Those may be odd buttons for a kiosk application, but they will come in handy while working on this application. The exit button will exit the application and the explore button will start the Explorer shell and then exit the application.
Working in Visual Studio 2005, I started by creating a new C++ Win32 Smart Device Application and let the wizard create the starting source code. I then deleted the code for the menu and the about dialog since I don’t need them. If we run the application now we will find two things about it; we can’t exit (now you know why I am going to add the exit button) and the Explorer shell’s task bar is filling part of the screen.
The application almost fills the screen. This is because the CreateWindow() call that was added by Visual Studio set the location and size using CW_USEDEFAULT. This causes the application size to be full screen, minus the task bar area.
Next let’s add the exit button. To do that, I added a global HWND and create the button with CreateWindow() in InitInstance():
 
HWND ExitButton;
 …
      ExitButton = CreateWindow(TEXT("Button"),TEXT("Exit"),
            BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE ,
            20,20,100,40,hWnd,(HMENU)IDM_FILE_EXIT,g_hInst,0);   
      if( ExitButton == NULL )
      {
            DWORD Error = GetLastError();
            RETAILMSG( 1, (TEXT("ExitButton failed %d\r\n"), Error));
            return FALSE;
                }
                              DestroyWindow(ExitButton);
 
Note that the button size and location are hardcoded, we will look at that later. When the button is pressed, the existing action for closing the application will be called, with the edition of a call to DestroyWindow() for the button. If you test the application now, the exit button will be placed in the top left corner and pressing the button will exit the application.
Let’s step back for little bit, this application is going to run in kiosk mode. That is the application will be full screen and we don’t need the Explorer shell. I discussed how to do this in a previous article, Windows CE: Starting an Application when the System Boots. My next step is to add the application to the OS and change the registry so that the Explorer shell doesn’t start and instead this applciation does start. The effect of this change is that the applciation now is full screen. We must add a call to SignalStarted():
      if( lpCmdLine )
            SignalStarted( _wtol(lpCmdLine) );
 
My work here is done. But I will continue.
Now if we test the application by pressing the Exit button, what we will find is that the application stops, but it doesn’t look like it stops because there is nothing to draw on the display. Since we don’t expect this condition in the real world, I am not going to do anything about it. But what I would like to do is start the explorer shell for testing and debugging purposes. To do that lets’s add the Explore button now. The explore button is similar to the exit button, but we will need to add more handling code to support it.
HWND ExplorerShellButton;
      ExplorerShellButton = CreateWindow(TEXT("Button"),TEXT("Explore"),
            BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE ,
            20 + 10 + 100,20,100,40,hWnd,(HMENU)IDM_EXPLORER_BUTTON,g_hInst,0);    
      if( ExplorerShellButton == NULL )
      {
            DWORD Error = GetLastError();
            RETAILMSG( 1, (TEXT("ExplorerShellButton failed %d\r\n"), Error));
            return FALSE;
      }
      case IDM_EXPLORER_BUTTON:
      {
            TCHAR *AppName = TEXT("Explorer.exe");
            TCHAR *CommandLine = NULL;
            PROCESS_INFORMATION pi;
 
            if( CreateProcess( AppName, CommandLine, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi))
            {
                  CloseHandle(pi.hProcess);
                  CloseHandle(pi.hThread);
            }
            else
            {
                  RETAILMSG( 1, (TEXT("Failed to start app: %s error %d\n"), AppName, GetLastError() ));
            }
      }
      //Fall through and exit
      DestroyWindow(ExplorerShellButton);
 
And in resource.h:
#define IDM_EXPLORER_BUTTON                   40003
Now when we test the application, the exit button works like it did before, but we can now use the Explore button to start up the Explorer shell before exiting. If you are testing with the Explorer shell already running, you will find that Windows Explorer starts.
Some of you are problably thinking that for some reason or another you need the Explorer shell running under your application. So you need the application to run full screen even when the task bar is available. To solve this, modify InitInstance() to find out the dimensions of the display and set the application to be top most and use the dimension of the display. The application can find the dimensions of the display using GetDeviceCaps(), and then use the dimensions in a call to SetWindowPosition():
      HDC hDC;
      DWORD Width;
      DWORD Height;
      hDC = GetDC(NULL);
      Width = GetDeviceCaps( hDC, HORZRES );
      Height = GetDeviceCaps( hDC, VERTRES );
 
                SetWindowPos(hWnd, HWND_TOPMOST,0, 0, Width, Height, 0 );
Now that we have the screen dimensions, I can move the buttons down to the bottom right where I think that they will be better placed:
      ExitButton = CreateWindow(TEXT("Button"),TEXT("Exit"),
            BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE ,
            Width - 200 - 20 - 10, Height - 40 - 20,100,40,hWnd,(HMENU)IDM_FILE_EXIT,g_hInst,0);
      ExplorerShellButton = CreateWindow(TEXT("Button"),TEXT("Explore"),
            BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE ,
            Width - 100 - 20, Height - 40 - 20,100,40,hWnd,(HMENU)IDM_EXPLORER_BUTTON,g_hInst,0);
     
Now we have an application that displays full screen. 
 
As you test the application with the Explorer shell still running, you will find that there is still a problem if the user has a full keyboard and can use hot keys like the Windows key. I would strongly urge you to consider running your kiosk application without the Explorer shell, so I won’t go into solving that problem now. If your application isn’t a kiosk application, but needs to be full screen, then you should consider leaving support for the hot keys, but I do know that sometimes that isn’t practical.
 
 
Copyright © 2010 – Bruce Eitman
All Rights Reserved

author: Bruce Eitman | Posted On Friday, August 6, 2010 10:47 PM | Comments (0)