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