The Windows CE File System
The default file system, supported on all Windows CE platforms, is the object store. The object store is equivalent to the hard disk on a Windows CE device. It's a subtly complex file storage system incorporating compressed RAM storage for read/write files and seamless integration with ROM-based files. A user sees no difference between a file in RAM in the object store and those files based in ROM. Files in RAM and ROM can reside in the same directory, and document files in ROM can be opened (although not modified) by the user. In short, the object store integrates the default files provided in ROM with the user-generated files stored in RAM.In addition to the object store, Windows CE supports multiple installable file systems that can support up to 256 different storage devices or partitions on storage devices. The interface to these devices is the installable file system (IFS) API. Most Windows CE platforms include an IFS driver for the FAT file system for files stored on ATA flash cards or hard disks. In addition, third-party manufacturers can write an IFS driver to support other file systems.Windows CE doesn't use drive letters as is the practice on PCs. Instead, every storage device is simply a directory off the root directory. Traditionally, the name of each directory is Storage Card. If more than one storage device is inserted, the additional devices are numbered, as in Storage Card 1, Storage Card 2, and so on, all the way up to Storage Card 99 for the 100th card. I say "traditionally" because Windows CE doesn't assume a name. Instead, it asks the driver what it wants to call the directory, and traditionally, the block mode driver returns the name Storage Card. Because the name of the storage device directory can change, you should never assume that these directories will be called Storage Card. I'll demonstrate a method for determining which directories in the root are directories and which are actually storage devices.As should be expected for a Win32-compatible operating system, the filename format for Windows CE is the same as that of its larger counterparts. Windows CE supports long filenames. Filenames and their complete paths can be up to MAX_PATH in length, which is currently defined at 260 bytes. Filenames have the same name.ext format as they do in other Windows operating systems. The extension is the three characters following the last period in the filename and defines the type of file. The file type is used by the shell when determining the difference between executable files and different documents. Allowable characters in filenames are the same as for Windows XP.Windows CE files support many of the same attribute flags as Windows XP, with a few additions. Attribute flags include the standard read-only, system, hidden, compressed, and archive flags. A few additional flags have been included to support the special RAM/ROM mix of files in the object store.
The Object Store vs. Other Storage Media
To the programmer, the difference between files in the RAM part of the object store and the files based in ROM are subtle. The files in ROM can be detected by a special in-ROM file attribute flag. Execute in place (XIP) modules in ROM are marked by an additional ROM-Module attribute indicating their XIP status. XIP files can't be opened using the standard file opening functions such as CreateFile. In addition, some files in the ROM and almost all files in the RAM are compressed and therefore marked with the compressed file attribute.The object store in Windows CE has some basic limitations. First, the size of the object store is currently limited to 256 MB of RAM. Given the compression features of the object store, this means that the amount of data that the object store can contain is somewhere around 512 MB. Individual files in the object store are limited to 32 MB. These file size limits don't apply to files in secondary storage such as hard disks, PC Cards, and CompactFlash Cards.
Standard File I/O
Windows CE supports most of the same file I/O functions found in Windows XP and Windows Me. The same Win32 API calls, such as CreateFile, ReadFile, WriteFile, and CloseFile, are all supported. A Windows CE programmer must be aware of a few differences, however. First of all, the old Win16 standards, _lread, _lwrite, and _llseek, aren't supported. This isn't really a huge problem because all of these functions can easily be implemented by wrapping the Windows CE file functions with a small amount of code. Windows CE does support basic console library functions such as fprintf and printf for console applications if the console is supported on that configuration.Windows CE doesn't support the overlapped I/O that's supported under Windows XP. Files or devices can't be opened with the FILE_ FLAG_OVERLAPPED flag, nor can reads or writes use the overlapped mode of asynchronous calls and returns.
File operations in Windows CE follow the traditional handle-based methodology used since the days of MS-DOS. Files are opened by means of a function that returns a handle. Read and write functions are passed the handle to indicate the file to act on. Data is read from or written to the offset in the file indicated by a system-maintained file pointer. Finally, when the reading and writing have been completed, the application indicates this by closing the file handle. Now on to the specifics.
Creating and Opening Files
Creating a file or opening an existing file or device is accomplished by means of the standard Win32 function:
HANDLE CreateFile (LPCTSTR lpFileName, DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDistribution,
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
The first parameter is the name of the file to be opened or created. The filename should have a fully specified path. Filenames with no path information are assumed to be in the root directory of the object store.The dwDesiredAccess parameter indicates the requested access rights. The allowable flags are GENERIC_READ to request read access to the file and GENERIC_WRITE for write access. Both flags must be passed to get read/write access. You can open a file with neither read nor write permissions. This is handy if you just want to get the attributes of a device. The dwShareMode parameter specifies the access rights that can be granted to other processes. This parameter can be FILE_SHARE_READ and/or FILE_SHARE_WRITE. The lpSecurityAttributes parameter is ignored by Windows CE and should be set to NULL.The dwCreationDistribution parameter tells CreateFile how to open or create the file. The following flags are allowed:
CREATE_NEWCreates a new file. If the file already exists, the function fails.
CREATE_ALWAYS Creates a new file or truncates an existing file.
OPEN_EXISTINGOpens a file only if it already exists.
OPEN_ALWAYS Opens a file or creates a file if it doesn't exist. This differs from CREATE_ALWAYS because it doesn't truncate the file to 0 bytes if the file exists.
TRUNCATE_EXISTING Opens a file and truncates it to 0 bytes. The function fails if the file doesn't already exist.
The dwFlagsAndAttributes parameter defines the attribute flags for the file if it's being created in addition to flags in order to tailor the operations on the file. The following flags are allowed under Windows CE:
FILE_ATTRIBUTE_NORMAL This is the default attribute. It's overridden by any of the other file attribute flags.
FILE_ATTRIBUTE_READONLYSets the read-only attribute bit for the file. Subsequent attempts to open the file with write access will fail.
FILE_ATTRIBUTE_ARCHIVE Sets the archive bit for the file.
FILE_ATTRIBUTE_SYSTEM Sets the system bit for the file indicating that the file is critical to the operation of the system.
FILE_ATTRIBUTE_HIDDEN Sets the hidden bit. The file will be visible only to users who have the View All Files option set in the Explorer.
FILE_FLAG_WRITE_THROUGH Write operations to the file won't be lazily cached in memory.
FILE_FLAG_RANDOM_ACCESSIndicates to the system that the file will be randomly accessed instead of sequentially accessed. This flag can help the system determine the proper caching strategy for the file.
Windows CE doesn't support a number of file attributes and file flags that are supported under Windows XP. The unsupported flags include but aren't limited to the following: FILE_ATTRIBUTE_OFFLINE, FILE_FLAG_OVERLAPPED, FILE_FLAG_NO_BUFFERING, FILE_FLAG_SEQUENTIAL_SCAN, FILE_FLAG_ DELETE_ON_CLOSE, FILE_FLAG_BACKUP_SEMANTICS, and FILE_FLAG_POSIX_ SEMANTICS. Under Windows XP, the flag FILE_ATTRIBUTE_ TEMPORARY is used to indicate a temporary file, but as we'll see later, it's used by Windows CE to indicate a directory that is in reality a separate drive or network share.The final parameter in CreateFile, hTemplate, is ignored by Windows CE and should be set to 0. CreateFile returns a handle to the opened file if the function was successful. If the function fails, it returns INVALID_HANDLE_VALUE. To determine why the function failed, call GetLastError. If the dwCreationDistribution flags included CREATE_ALWAYS or OPEN_ALWAYS, you can determine whether the file previously existed by calling GetLastError to see if it returns ERROR_ALREADY_EXISTS. CreateFile will set this error code even though the function succeeded.
In addition to opening files and devices, CreateFile can open storage volumes such as hard disks and flash cards. To open a volume, pass the name of the volume appended with \Vol:. For example, to open a compact flash card volume represented by the directory name Storage Card, the call would be as follows:
H = CreateFile (TEXT ("\\Storage card\\Vol:"), GENERIC_READ|GENERIC_WRITE,
0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
The handle returned by the CreateFile call can be used to pass IO Control (IOCTL) commands to the volume. Possible IOCTLs include commands to format or verify the volume.
Reading and Writing
Windows CE supports the standard Win32 functions ReadFile and WriteFile; both functions return TRUE if successful and FALSE otherwise. Reading a file is as simple as calling the following:
BOOL ReadFile (HANDLE hFile, LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
The parameters are fairly self-explanatory. The first parameter is the handle of the opened file to read followed by a pointer to the buffer that will receive the data and the number of bytes to read. The fourth parameter is a pointer to a DWORD that will receive the number of bytes that were actually read. Finally, the lpOverlapped parameter must be set to NULL because Windows CE doesn't support overlapped file operations. As an aside, Windows CE does support multiple reads and writes pending on a device; it just doesn't support the ability to return from the function before the operation completes.Data is read from the file starting at the file offset indicated by the file pointer. After the read has completed, the file pointer is adjusted by the number of bytes read.ReadFile won't read beyond the end of a file. If a call to ReadFile asks for more bytes than remain in the file, the read will succeed, but only the number of bytes remaining in the file will be returned. This is why you must check the variable pointed to by lpNumberOfBytesRead after a read completes to learn how many bytes were actually read. A call to ReadFile with the file pointer pointing to the end of the file results in the read being successful, but the number of read bytes is set to 0.
Writing to a file is accomplished with this:
BOOL WriteFile (HANDLE hFile, LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped);
The parameters are similar to ReadFile, with the obvious exception that lpBuffer now points to the data that will be written to the file. As in ReadFile, the lpOverlapped parameter must be NULL. The data is written to the file offset indicated by the file pointer, which is updated after the write so that it points to the byte immediately beyond the data written.
Moving the File Pointer
The file pointer can be adjusted manually with a call to the following:
DWORD SetFilePointer (HANDLE hFile, LONG lDistanceToMove,
PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod);
The parameters for SetFilePointer are the handle of the file; a signed offset distance to move the file pointer; a second, upper 32-bit, offset parameter; and dwMoveMethod, a parameter indicating how to interpret the offset. Although lDistanceToMove is a signed 32-bit value, lpDistanceToMoveHigh is a pointer to a signed 32-bit value. For file pointer moves of greater than 4 GB, the lpDistanceToMoveHigh parameter should point to a LONG that contains the upper 32-bit offset of the move. This variable will receive the high 32 bits of the resulting file pointer. For moves of less than 4 GB, simply set lpDistanceToMoveHigh to NULL. Clearly, under Windows CE, the lpDistanceToMoveHigh parameter is a bit excessive, but having the function the same format as its Windows XP counterpart aids in portability across platforms.The offset value is interpreted as being from the start of the file if dwMoveMethod contains the flag FILE_BEGIN. To base the offset on the current position of the file pointer, use FILE_CURRENT. To base the offset from the end of the file, use FILE_END in dwMoveMethod.SetFilePointer returns the file pointer at its new position after the move has been accomplished. To query the current file position without changing the file pointer, simply call SetFilePointer with a zero offset and relative to the current position in the file, as shown here:
nCurrFilePtr = SetFilePointer (hFile, 0, NULL, FILE_CURRENT);
Closing a File
Closing a file handle is a simple as calling
BOOL CloseHandle (HANDLE hObject);
This generic call, used to close a number of handles, is also used to close file handles. The function returns TRUE if it succeeds. If the function fails, a call to GetLastError will return the reason for the failure.
Truncating a File
When you have finished writing the data to a file, you can close it with a call to CloseHandle and you're done. Sometimes, however, you must truncate a file to make it smaller than it currently is. In the days of MS-DOS, the way to set the end of a file was to make a call to write zero bytes to a file. The file was then truncated at the current file pointer. This won't work in Windows CE. To set the end of a file, move the file pointer to the location in the file where you want the file to end and call:
BOOL SetEndOfFile (HANDLE hFile);
Of course, for this call to succeed, you need write access to the file. The function returns TRUE if it succeeds.To insure that all the data has been written to a storage device and isn't just sitting around in a cache, you can call this function:
WINBASEAPI BOOL WINAPI FlushFileBuffers (HANDLE hFile);
The only parameter is the handle to the file you want to flush to the disk or, more likely in Windows CE, a PC Card.
Getting File Information
A number of calls allow you to query information about a file or directory. To quickly get the attributes knowing only the file or directory name, you can use this function:
DWORD GetFileAttributes (LPCTSTR lpFileName);
In general, the attributes returned by this function are the same ones that I covered for CreateFile, with the addition of the attributes listed here:
FILE_ATTRIBUTE_COMPRESSEDThe file is compressed.
FILE_ATTRIBUTE_INROM The file is in ROM.
FILE_ATTRIBUTE_ROMMODULE The file is an executable module in ROM formatted for execute-in-place loading. These files can't be opened with CreateFile.
FILE_ATTRIBUTE_DIRECTORY The name specifies a directory, not a file.
FILE_ATTRIBUTE_TEMPORARY When this flag is set in combination with FILE_ATTRIBUTE_DIRECTORY, the directory is the root of a secondary storage device, such as a PC Card, a hard disk, or the network share folder.
The attribute FILE_ATTRIBUTE_COMPRESSED is somewhat misleading on a Windows CE device. Files in the RAM-based object store are always compressed, but this flag isn't set for those files. On the other hand, the flag does accurately reflect whether a file in ROM is compressed. Compressed ROM files have the advantage of taking up less space but the disadvantage of not being execute-in-place files.An application can change the basic file attributes, such as read only, hidden, system, and attribute by calling this function:
BOOL SetFileAttributes (LPCTSTR lpFileName, DWORD dwFileAttributes);
This function simply takes the name of the file and the new attributes. Note that you can't compress a file by attempting to set its compressed attribute. Under other Windows systems that do support selective compression of files, the way to compress a file is to make a call directly to the file system driver.A number of other informational functions are supported by Windows CE. All of these functions, however, require a file handle instead of a filename, so the file must have been previously opened by means of a call to CreateFile.
File Times
The standard Win32 API supports three file times: the time the file was created, the time the file was last accessed (that is, the time it was last read, written, or executed), and the last time the file was written to. That being said, the Windows CE object store keeps track of only one time, the time the file was last written to. One of the ways to query the file times for a file is to call this function:
BOOL GetFileTime (HANDLE hFile, LPFILETIME lpCreationTime,
LPFILETIME lpLastAccessTime,
LPFILETIME lpLastWriteTime);
The function takes a handle to the file being queried and pointers to three FILETIME values that will receive the file times. If you're interested in only one of the three values, the other pointers can be set to NULL.
When the file times are queried for a file in the object store, Windows CE copies the last write time into all FILETIME structures. This goes against Win32 documentation, which states that any unsupported time fields should be set to 0. For the FAT file system used on storage cards, two times are maintained: the file creation time and the last write time. When GetFileTime is called on a file on a storage card, the file creation and last write times are returned and the last access time is set to 0.The FILETIME structures returned by GetFileTime and other functions can be converted to something readable by calling
BOOL FileTimeToSystemTime (const FILETIME *lpFileTime,
LPSYSTEMTIME lpSystemTime);
This function translates the FILETIME structure into a SYSTEMTIME structure that has documented day, date, and time fields that can be used. One large caveat is that file times are stored in coordinated universal time format (UTC), also known as Greenwich Mean Time. This doesn't make much difference as long as you're using unreadable FILETIME structures, but when you're translating a file time into something readable, a call to
BOOL FileTimeToLocalFileTime (const FILETIME *lpFileTime,
LPFILETIME lpLocalFileTime);
before translating the file time into system time provides the proper time zone translation to the user.You can manually set the file times of a file by calling
BOOL SetFileTime (HANDLE hFile, const FILETIME *lpCreationTime,
const FILETIME *lpLastAccessTime,
const FILETIME *lpLastWriteTime);
The function takes a handle to a file and three times each in FILETIME format. If you want to set only one or two of the times, the remaining parameters can be set to NULL. Remember that file times must be in UTC time, not local time.For files in the Windows CE object store, setting any one of the time fields results in all three being updated to that time. If you set multiple fields to different times and attempt to set the times for an object store file, lpLastWriteTime takes precedence. Files on storage cards maintain separate creation and last-write times. You must open the file with write access for SetFileTime to work.
File Size and Other Information
You can query a file's size by calling
DWORD GetFileSize (HANDLE hFile, LPDWORD lpFileSizeHigh);
The function takes the handle to the file and an optional pointer to a DWORD that's set to the high 32 bits of the file size. This second parameter can be set to NULL if you don't expect to be dealing with files over 4 GB. GetFileSize returns the low 32 bits of the file size.
I've been talking about these last few functions separately, but an additional function, GetFileInformationByHandle, returns all this information and more. The function prototyped as
BOOL GetFileInformationByHandle (HANDLE hFile,
LPBY_HANDLE_FILE_INFORMATION lpFileInformation);
takes the handle of an opened file and a pointer to a BY_HANDLE_FILE_INFORMATION structure. The function returns TRUE if it was successful.The BY_HANDLE_FILE_INFORMATION structure is defined this way:
typedef struct _BY_HANDLE_FILE_INFORMATION {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD dwVolumeSerialNumber;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD nNumberOfLinks;
DWORD nFileIndexHigh;
DWORD nFileIndexLow;
DWORD dwOID;
} BY_HANDLE_FILE_INFORMATION;
As you can see, the structure returns data in a number of fields that separate functions return. I'll talk about only the new fields here.The dwVolumeSerialNumber field is filled with the serial number of the volume in which the file resides. The volume is what's considered a disk or partition under Windows XP. Under Windows CE, the volume refers to the object store, a storage card, or a disk on a local area network. For files in the object store, the volume serial number is 0.The nNumberOfLinks field is used by Windows XP's NTFS file system and can be ignored under Windows CE. The nFileIndexHigh and nFileIndexLow fields contain a systemwide unique identifier number for the file. This number can be checked to see whether two different file handles point to the same file. The File Index value is used under Windows XP and Windows Me, but Windows CE has a more useful value, the object ID of the file, which is returned in the dwOID field. The object ID is an identifier that can be used to reference directories, files, databases, and individual database records. Handy stuff.
The FileView Sample Program
FileView is an example program that uses the multi-line edit control to display the contents of a file in a window. FileView is simply a file viewer; it doesn't allow you to modify the file. The code for FileView is shown in Listing 8-1.Listing 8-1: The FileView program
FileView.rc
//======================================================================
// Resource file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include "windows.h"
#include "FileView.h" // Program-specific stuff
//----------------------------------------------------------------------
// Icons and bitmaps
ID_ICON ICON "fileview.ico" // Program icon
//----------------------------------------------------------------------
// Menu
ID_MENU MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&Open...", IDM_OPEN
MENUITEM SEPARATOR
MENUITEM "E&xit", IDM_EXIT
END
POPUP "&Help"
BEGIN
MENUITEM "&About...", IDM_ABOUT
END
END
//----------------------------------------------------------------------
// About box dialog template
aboutbox DIALOG discardable 10, 10, 135, 40
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_CENTER |
DS_MODALFRAME
CAPTION "About"
BEGIN
ICON ID_ICON, -1, 3, 5, 10, 10
LTEXT "FileView - Written for the book Programming Windows CE Copyright 2003 Douglas Boling"
-1, 30, 5, 102, 33
END
FileView.h
//======================================================================
// Header file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
// Returns number of elements.
#define dim(x) (sizeof(x) / sizeof(x[0]))
//----------------------------------------------------------------------
// Generic defines and data types
//
struct decodeUINT { // Structure associates
UINT Code; // messages
// with a function.
LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM);
};
struct decodeCMD { // Structure associates
UINT Code; // menu IDs with a
LRESULT (*Fxn)(HWND, WORD, HWND, WORD); // function.
};
//----------------------------------------------------------------------
// Generic defines used by application
#define ID_ICON 1 // Application icon
// Resource ID
#define IDC_CMDBAR 2 // Command band ID
#define ID_MENU 3 // Main menu resource ID
#define ID_VIEWER 4 // View control ID
// Menu item IDs
#define IDM_OPEN 101 // File menu
#define IDM_EXIT 102
#define IDM_ABOUT 120 // Help menu
//----------------------------------------------------------------------
// Function prototypes
//
INT MyGetFileName (HWND hWnd, LPTSTR szFileName, INT nMax);
HWND InitInstance (HINSTANCE, LPWSTR, int);
int TermInstance (HINSTANCE, int);
// Window procedures
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
// Message handlers
LRESULT DoCreateMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoSizeMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoCommandMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM);
// Command functions
LPARAM DoMainCommandOpen (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandExit (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandAbout (HWND, WORD, HWND, WORD);
// Dialog procedures
BOOL CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM);
FileView.cpp
//======================================================================
// FileView - A Windows CE file viewer
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include <windows.h> // For all that Windows stuff
#include <commctrl.h> // Command bar includes
#include <commdlg.h> // Common dialog includes
#include "FileView.h" // Program-specific stuff
#define BUFFSIZE 16384
//----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("FileView");
extern TCHAR szViewerCls[];
HINSTANCE hInst; // Program instance handle
HANDLE g_hFile=INVALID_HANDLE_VALUE; // Handle to the opened file
PBYTE g_pBuff = 0; // Pointer to file data buffer
// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
WM_CREATE, DoCreateMain,
WM_SIZE, DoSizeMain,
WM_COMMAND, DoCommandMain,
WM_DESTROY, DoDestroyMain,
};
Ch08// Command message dispatch for MainWindowProc
const struct decodeCMD MainCommandItems[] = {
IDM_OPEN, DoMainCommandOpen,
IDM_EXIT, DoMainCommandExit,
IDM_ABOUT, DoMainCommandAbout,
};
//======================================================================
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow) {
HWND hwndMain;
MSG msg;
// Initialize this instance.
hwndMain = InitInstance (hInstance, lpCmdLine, nCmdShow);
if (hwndMain == 0) return 0x10;
// Application message loop
while (GetMessage (&msg, NULL, 0, 0)) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}
// Instance cleanup
return TermInstance (hInstance, msg.wParam);
}
//----------------------------------------------------------------------
// InitInstance - Instance initialization
//
HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow){
HWND hWnd;
WNDCLASS wc;
INITCOMMONCONTROLSEX icex;
#if defined(WIN32_PLATFORM_PSPC)
// If Pocket PC, allow only one instance of the application.
HWND hWnd = FindWindow (szAppName, NULL);
if (hWnd) {
SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));
return 0;
}
#endif
// Save program instance handle in global variable.
hInst = hInstance;
// Register application main window class.
wc.style = 0; // Window style
wc.lpfnWndProc = MainWndProc; // Callback function
wc.cbClsExtra = 0; // Extra class data
wc.cbWndExtra = 0; // Extra window data
wc.hInstance = hInstance; // Owner handle
wc.hIcon = NULL, // Application icon
wc.hCursor = LoadCursor (NULL, IDC_ARROW);// Default cursor
wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
wc.lpszMenuName = NULL; // Menu name
wc.lpszClassName = szAppName; // Window class name
if (RegisterClass (&wc) == 0) return 0;
// Load the command bar common control class.
icex.dwSize = sizeof (INITCOMMONCONTROLSEX);
icex.dwICC = ICC_BAR_CLASSES;
InitCommonControlsEx (&icex);
// Create main window.
hWnd = CreateWindow (szAppName, TEXT ("FileView"),
WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL,
hInstance, NULL);
if (!IsWindow (hWnd)) return 0; // Fail code if window not created.
// Standard show and update calls
ShowWindow (hWnd, nCmdShow);
UpdateWindow (hWnd);
return hWnd;
}
//----------------------------------------------------------------------
// TermInstance - Program cleanup
//
int TermInstance (HINSTANCE hInstance, int nDefRC) {
if (g_hFile != INVALID_HANDLE_VALUE)
CloseHandle (g_hFile); // Close the opened file.
if (g_pBuff)
LocalFree (g_pBuff); // Free buffer.
return nDefRC;
}
//======================================================================
// Message handling procedures for MainWindow
//----------------------------------------------------------------------
// MainWndProc - Callback function for application window.
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
INT i;
//
// Search message list to see if we need to handle this
// message. If in list, call function.
//
for (i = 0; i < dim(MainMessages); i++) {
if (wMsg == MainMessages[i].Code)
return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam);
}
return DefWindowProc (hWnd, wMsg, wParam, lParam);
}
//----------------------------------------------------------------------
// DoCreateMain - Process WM_CREATE message for window.
//
LRESULT DoCreateMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
HWND hwndCB, hwndChild;
LPCREATESTRUCT lpcs;
// Convert lParam into pointer to create structure.
lpcs = (LPCREATESTRUCT) lParam;
// Create a minimal command bar that has only a menu and an
// exit button.
hwndCB = CommandBar_Create (hInst, hWnd, IDC_CMDBAR);
// Insert the menu.
CommandBar_InsertMenubar (hwndCB, hInst, ID_MENU, 0);
// Add exit button to command bar.
CommandBar_AddAdornments (hwndCB, 0, 0);
hwndChild = CreateWindowEx (0,TEXT("edit"), TEXT("), WS_VISIBLE |
WS_CHILD | ES_MULTILINE | WS_VSCROLL |
WS_HSCROLL | ES_READONLY, 0, 0, lpcs->cx,
lpcs->cy, hWnd, (HMENU)ID_VIEWER, hInst, 0);
// Destroy frame if window not created.
if (!IsWindow (hwndChild)) {
DestroyWindow (hWnd);
return 0;
}
// Allocate a buffer.
g_pBuff = (PBYTE)LocalAlloc (LMEM_FIXED, BUFFSIZE);
if (!g_pBuff) {
MessageBox (NULL, TEXT ("Not enough memory"),
TEXT ("Error"), MB_OK);
return 0;
}
return 0;
}
//----------------------------------------------------------------------
// DoSizeMain - Process WM_SIZE message for window.
//
LRESULT DoSizeMain (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){
RECT rect;
// Adjust the size of the client rect to take into account
// the command bar height.
GetClientRect (hWnd, &rect);
rect.top += CommandBar_Height (GetDlgItem (hWnd, IDC_CMDBAR));
SetWindowPos (GetDlgItem (hWnd, ID_VIEWER), NULL, rect.left,
rect.top, (rect.right - rect.left),
rect.bottom - rect.top, SWP_NOZORDER);
return 0;
}
//----------------------------------------------------------------------
// DoCommandMain - Process WM_COMMAND message for window.
//
LRESULT DoCommandMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
WORD idItem, wNotifyCode;
HWND hwndCtl;
INT i;
// Parse the parameters.
idItem = (WORD) LOWORD (wParam);
wNotifyCode = (WORD) HIWORD (wParam);
hwndCtl = (HWND) lParam;
// Call routine to handle control message.
for (i = 0; i < dim(MainCommandItems); i++) {
if (idItem == MainCommandItems[i].Code)
return (*MainCommandItems[i].Fxn)(hWnd, idItem, hwndCtl,
wNotifyCode);
}
return 0;
}
//----------------------------------------------------------------------
// DoDestroyMain - Process WM_DESTROY message for window.
//
LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
PostQuitMessage (0);
return 0;
}
//======================================================================
// Command handler routines
//----------------------------------------------------------------------
// DoMainCommandOpen - Process File Open command.
//
LPARAM DoMainCommandOpen (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
TCHAR szFileName[MAX_PATH];
HWND hwndViewer;
DWORD cBytes;
LPTSTR pXLateBuff = 0;
int lFileSize, i;
BOOL fUnicode = TRUE;
HANDLE hFileTmp;
hwndViewer = GetDlgItem (hWnd, ID_VIEWER);
// Ask the user for the file name
if (MyGetFileName (hWnd, szFileName, dim(szFileName)) == 0)
return 0;
// Open the file.
hFileTmp = CreateFile (szFileName, GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if (hFileTmp == INVALID_HANDLE_VALUE) {
MessageBox (hWnd, TEXT("Couldn't open file"), szAppName, MB_OK);
return 0;
}
if (g_hFile) {
CloseHandle (g_hFile);
// clear the edit box
SendMessage (hwndViewer, EM_SETSEL, 0, -1);
SendMessage (hwndViewer, EM_REPLACESEL, 0, (LPARAM)TEXT("));
}
g_hFile = hFileTmp;
// Get the size of the file
lFileSize = (int)GetFileSize (g_hFile, NULL);
// See if file > 2Gig
if (lFileSize < 0) return 0;
if (!ReadFile (g_hFile, g_pBuff, BUFFSIZE-1, &cBytes, NULL))
return 0;
// Trivial check to see if file Unicode. Assumes english
for (i = 0; (i < 16) && (i < (int)cBytes); i++) {
if (*((TCHAR *)g_pBuff+i) > 0x100)
fUnicode = FALSE;
}
if (!fUnicode) {
pXLateBuff = (LPTSTR)LocalAlloc (LPTR, BUFFSIZE*sizeof (TCHAR));
if (pXLateBuff == 0) return 0;
}
while (lFileSize > 0) {
// Remove any selection
SendMessage (hwndViewer, EM_SETSEL, (WPARAM)-1, 0);
*(g_pBuff+cBytes) = 0;
lFileSize -= cBytes;
if (!fUnicode) {
mbstowcs (pXLateBuff, (char *)g_pBuff, cBytes+1);
SendMessage (hwndViewer, EM_REPLACESEL, 0,
(LPARAM)pXLateBuff);
} else
SendMessage (hwndViewer, EM_REPLACESEL, 0, (LPARAM)g_pBuff);
if (!ReadFile (g_hFile, g_pBuff, BUFFSIZE-1, &cBytes, NULL))
break;
}
if (pXLateBuff) LocalFree ((HLOCAL)pXLateBuff);
// Scroll the control to the top of the file
SendMessage (hwndViewer, EM_SETSEL, 0, 0);
SendMessage (hwndViewer, EM_SCROLLCARET, 0, 0);
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandExit - Process Program Exit command.
//
LPARAM DoMainCommandExit (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
SendMessage (hWnd, WM_CLOSE, 0, 0);
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandAbout - Process the Help | About menu command.
//
LPARAM DoMainCommandAbout(HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
// Use DialogBox to create a modal dialog.
DialogBox (hInst, TEXT ("aboutbox"), hWnd, AboutDlgProc);
return 0;
}
//======================================================================
// About Dialog procedure
//
BOOL CALLBACK AboutDlgProc (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
switch (wMsg) {
case WM_COMMAND:
switch (LOWORD (wParam)) {
case IDOK:
case IDCANCEL:
EndDialog (hWnd, 0);
return TRUE;
}
break;
}
return FALSE;
}
//----------------------------------------------------------------------
// MyGetFileName - Returns a filename using the common dialog
//
INT MyGetFileName (HWND hWnd, LPTSTR szFileName, INT nMax) {
OPENFILENAME of;
const LPTSTR pszOpenFilter = TEXT ("All Documents (*.*)\0*.*\0\0");
szFileName[0] = '\0'; // Initial filename
memset (&of, 0, sizeof (of)); // Initial file open structure
of.lStructSize = sizeof (of);
of.hwndOwner = hWnd;
of.lpstrFile = szFileName;
of.nMaxFile = nMax;
of.lpstrFilter = pszOpenFilter;
of.Flags = 0;
if (GetOpenFileName (&of))
return lstrlen (szFileName);
else
return 0;
}

Memory-Mapped Files and Objects
Memory-mapped files give you a completely different method for reading and writing files. With the standard file I/O functions, files are read as streams of data. To access bytes in different parts of a file, the file pointer must be moved to the first byte, the data read, the file pointer moved to the other byte, and then the file read again.With memory-mapped files, the file is mapped to a region of memory. Then, instead of using FileRead and FileWrite, you simply read and write the region of memory that's mapped to the file. Updates of the memory are automatically reflected back to the file itself. Setting up a memory-mapped file is a somewhat more complex process than making a simple call to CreateFile, but once a file is mapped, reading and writing the file is trivial.
Memory-Mapped Files
Windows CE uses a slightly different procedure from Windows XP to access a memory-mapped file. To open a file for memory-mapped access, a new function, unique to Windows CE, is used; it's named CreateFileForMapping. The prototype for this function is the following:
HANDLE CreateFileForMapping (LPCTSTR lpFileName, DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);
The parameters for this function are similar to those for CreateFile. The filename is the name of the file to read. The dwDesiredAccess parameter, specifying the access rights to the file, must be a combination of GENERIC_READ and GENERIC_WRITE, or it must be 0. The security attributes must be NULL, while Windows CE ignores the hTemplateFile parameter.The handle returned by CreateFileForMapping can then be passed to
HANDLE CreateFileMapping (HANDLE hFile,
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
DWORD flProtect, DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow, LPCTSTR lpName);
This function creates a file-mapping object and ties the opened file to it. The first parameter for this function is the handle to the opened file. The security attributes parameter must be set to NULL under Windows CE. The flProtect parameter should be loaded with the protection flags for the virtual pages that will contain the file data. The maximum size parameters should be set to the expected maximum size of the object, or they can be set to 0 if the object should be the same size as the file being mapped. The lpName parameter allows you to specify a name for the object. This is handy when you're using a memory-mapped file to share information across different processes. Calling CreateFileMapping with the name of an already-opened file-mapping object returns a handle to the object already opened instead of creating a new one.Once a mapping object has been created, a view into the object is created by calling
LPVOID MapViewOfFile (HANDLE hFileMappingObject, DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow,
DWORD dwNumberOfBytesToMap);
MapViewOfFile returns a pointer to memory that's mapped to the file. The function takes as its parameters the handle of the mapping object just opened as well as the access rights, which can be FILE_MAP_READ, FILE_MAP_WRITE, or FILE_MAP_ALL_ACCESS. The offset parameters let you specify the starting point within the file that the view starts, while the dwNumberOfBytesToMap parameter specifies the size of the view window.These last three parameters are useful when you're mapping large objects. Instead of attempting to map the file as one large object, you can specify a smaller view that starts at the point of interest in the file. This reduces the memory required because only the view of the object, not the object itself, is backed up by physical RAM.As you write to the memory-mapped file, the changes are reflected in the data you read back from the same buffer. When you close the memory-mapped file, the system writes the modified data back to the original file. If you want to have the data written to the file before you close the file, you can use the following function:
BOOL FlushViewOfFile(LPCVOID lpBaseAddress, DWORD dwNumberOfBytesToFlush);
The parameters are the base address and size of a range of virtual pages within the mapped view that will be written to the file. The function writes only the pages that have been modified to the file.When you're finished with the memory-mapped file, a little cleanup is required. First a call to
BOOL UnmapViewOfFile (LPCVOID lpBaseAddress);
unmaps the view to the object. The only parameter is the pointer to the base address of the view.
Next a call should be made to close the mapping object and the file itself. Both these actions are accomplished by means of calls to CloseHandle. The first call should be to close the memory-mapped object, and then CloseHandle should be called to close the file.The code fragment that follows shows the entire process of opening a file for memory mapping, creating the file-mapping object, mapping the view, and then cleaning up.
HANDLE hFile, hFileMap;
PBYTE pFileMem;
TCHAR szFileName[MAX_PATH];
// Get the filename.
hFile = CreateFileForMapping (szFileName, GENERIC_WRITE,
FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_RANDOM_ACCESS,0);
if (hFile != INVALID_HANDLE_VALUE) {
hFileMap = CreateFileMapping (hFile, NULL, PAGE_READWRITE, 0, 0, 0);
if (hFileMap) {
pFileMem = MapViewOfFile (hFileMap, FILE_MAP_WRITE, 0, 0, 0);
if (pFileMem) {
//
// Use the data in the file.
//
// Start cleanup by unmapping view.
UnmapViewOfFile (pFileMem);
}
CloseHandle (hFileMap);
}
CloseHandle (hFile);
}
A variation of memory-mapped files, memory-mapped objects are great for interprocess communication. I'll cover memory-mapped objects when I discuss interprocess communication in Chapter 10.
Navigating the File System
Now that we've seen how files are read and written, let's take a look at how the files themselves are managed in the file system. Windows CE supports most of the convenient file and directory management APIs, such as CopyFile, MoveFile, and CreateDirectory.
File and Directory Management
Windows CE supports a number of functions useful in file and directory management. You can move files using MoveFile, copy them using CopyFile, and delete them using DeleteFile. You can create directories using CreateDirectory and delete them using RemoveDirectory. While most of these functions are straightforward, I should cover a few intricacies here.To copy a file, call
BOOL CopyFile (LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName,
BOOL bFailIfExists);
The parameters are the name of the file to copy and the name of the destination directory. The third parameter indicates whether the function should overwrite the destination file if one already exists before the copy is made.Files and directories can be moved and renamed using
BOOL MoveFile (LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName);
To move a file, simply indicate the source and destination names for the file. The destination file must not already exist. File moves can be made within the object store, from the object store to an external drive, or from an external drive to the object store. MoveFile can also be used to rename a file. In this case, the source and target directories remain the same; only the name of the file changes.MoveFile can also be used in the same manner to move or rename directories. The only exception is that MoveFile can't move a directory from one volume to another. Under Windows CE, MoveFile moves a directory and all its subdirectories and files to a different location within the object store or different locations within another volume.Deleting a file is as simple as calling
BOOL DeleteFile (LPCTSTR lpFileName);
You pass the name of the file to delete. For the delete to be successful, the file must not be currently open.You can create and destroy directories using the following two functions:
BOOL CreateDirectory (LPCTSTR lpPathName,
LPSECURITY_ATTRIBUTES lpSecurityAttributes);
and
BOOL RemoveDirectory (LPCTSTR lpPathName);
CreateDirectory takes the name of the directory to create and a security parameter that should be NULL under Windows CE. RemoveDirectory deletes a directory. The directory must be empty for the function to be successful.
Creating a Temporary File
At times you will need to create a temporary file. How do you pick a unique filename? You can ask Windows for the name of a temporary file by using the following function:
UINT GetTempFileName (LPCTSTR lpPathName, LPCTSTR lpPrefixString,
UINT uUnique, LPTSTR lpTempFileName);
The first parameter is the path of the temporary file. You can specify a single "." to indicate the current directory, or you can specify an existing directory. The second parameter, lpPrefixString, is the name prefix. The first three characters of the prefix become the first three characters of the temporary filename. The uUnique parameter can be any number you want or 0. If you pass 0, Windows will generate a number based on the system time and use it as the last four characters of the filename. If uUnique is 0, Windows guarantees that the filename produced by GetTempFileName will be unique. If you specify a value other than 0 in uUnique, Windows returns a filename based on that value but doesn't check to see whether the filename is unique. The last parameter is the address of the output buffer to which GetTempFileName returns the filename. This buffer should be at least MAX_PATH characters (not bytes) in length.
Finding Files
Windows CE supports the basic FindFirstFile, FindNextFile, FindClose procedure for enumerating files that is supported under Windows XP. Searching is accomplished on a per-directory basis using template filenames with wild card characters in the template.Searching a directory involves first passing a filename template to FindFirstFile, which is prototyped in this way:
HANDLE FindFirstFile (LPCTSTR lpFileName,
LPWIN32_FIND_DATA lpFindFileData);
The first parameter is the template filename used in the search. This filename can contain a fully specified path if you want to search a directory other than the root. Windows CE has no concept of Current Directory built into it; if no path is specified in the search string, the root directory of the object store is searched.
As you would expect, the wildcards for the filename template are ? and *. The question mark (?) indicates that any single character can replace the question mark. The asterisk (*) indicates that any number of characters can replace the asterisk. For example, the search string \Windows\Alarm?.wav would return the files \Windows\Alarm1.wav, \Windows\Alarm2.wav, and \Windows\Alarm3.wav. On the other hand, the search string \Windows\*.wav would return all files in the windows directory that have the WAV extension.The second parameter of FindFirstFile is a pointer to a WIN32_FIND_DATA structure, as defined here:
typedef struct _WIN32_FIND_DATA {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwOID;
WCHAR cFileName[ MAX_PATH ];
} WIN32_FIND_DATA;
This structure is filled with the file data for the first file found in the search. The fields shown are similar to what we've seen.If FindFirstFile finds no files or directories that match the template filename, it returns INVALID_HANDLE_VALUE. If at least one file is found, FindFirstFile fills in the WIN32_FIND_DATA structure with the specific data for the found file and returns a handle value that you use to track the current search.To find the next file in the search, call this function:
BOOL FindNextFile (HANDLE hFindFile,
LPWIN32_FIND_DATA lpFindFileData);
The two parameters are the handle returned by FindFirstFile and a pointer to a find data structure. FindNextFile returns TRUE if a file matching the template passed to FindFirstFile is found and fills in the appropriate file data in the WIN32_FIND_DATA structure. If no file is found, FindNextFile returns FALSE.When you've finished searching either because FindNextFile returned FALSE or because you simply don't want to continue searching, you must call this function:
BOOL FindClose (HANDLE hFindFile);
This function accepts the handle returned by FindFirstFile. If FindFirstFile returned INVALID_HANDLE_VALUE, you shouldn't call FindClose.
The following short code fragment encompasses the entire file search process. This code computes the total size of all files in the Windows directory.
WIN32_FIND_DATA fd;
HANDLE hFind;
INT nTotalSize = 0;
// Start search for all files in the windows directory.
hFind = FindFirstFile (TEXT ("\\windows\\*.*"), &fd);
// If a file was found, hFind will be valid.
if (hFind != INVALID_HANDLE_VALUE) {
// Loop through found files. Be sure to process file
// found with FindFirstFile before calling FindNextFile.
do {
// If found file is not a directory, add its size to
// the total. (Assume that the total size of all files
// is less than 2 GB.)
if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
nTotalSize += fd.nFileSizeLow;
// See if another file exists.
} while (FindNextFile (hFind, &fd));
// Clean up by closing file search handle.
FindClose (hFind);
}
In this example, the Windows directory is searched for all files. If the found "file" isn't a directory, that is, if it's a true file, its size is added to the total. Notice that the return handle from FindFirstFile must be checked, not only so that you know whether a file was found but also to prevent FindClose from being called if the handle is invalid.A more advanced version of the FindxxxFile API is FindFirstFileEx. The advantage of this function is the added ability to enumerate only directories and even to enumerate the device drivers currently running. The function is prototyped as
HANDLE FindFirstFileEx(LPCTSTR lpFileName, FINDEX_INFO_LEVELS fInfoLevelId,
LPVOID lpFindFileData, FINDEX_SEARCH_OPS fSearchOp,
LPVOID lpSearchFilter, DWORD dwAdditionalFlags);
As in FindFirstFile, the first parameter, lpFileName, specifies the search string. The parameter fInfoLevelId must be set to the constant FindExInfoStandard. Given that the second parameter must be FindExInfoStandard, the third parameter always points to a WIN32_FIND_DATA structure. The final two parameters, lpSearchFilter and dwAdditionalFlags, must be set to 0 on Windows CE.
The fourth parameter, fSearchOp, is what differentiates FindFirstFileEx from FindFirstFile on Windows CE. This parameter can be one of three values: FindExSearchNameMatch, FindExSearchLimitToDirectories, or FindExSearchLimitToDevices. The value FindExSearchNameMatch tells FindFirstFileEx to act just like FindFirstFile, searching for a matching filename. The value FindExSearchLimitToDirectories indicates that the function should search only for directories matching the search specification. This search should be slightly faster than repeatedly calling FindFirstFile and checking for the directory attribute because this check is done inside the file system, thereby reducing the number of FindNextFile calls. The final value, FindExSearchLimitToDevices, is the most interesting. It causes the function to search the names of the loaded device drivers to find a matching name. You shouldn't provide a path, with the exception of an optional leading "\".FindFirstFileEx returns a handle if the search is successful and returns INVALID_HANDLE_VALUE if the search fails. When performing a search, use FindFirstFileEx in place of FindFirstFile. To search for the second and all other files, call FindNextFile. When you have completed the search, call FindClose to close the handle.While FindFirstFileEx is a handy addition to the Windows CE API, some early Pocket PC 2000 systems don't seem to correctly implement this function when enumerating device names. You should be careful when calling this function; couch it in a __try __except block to guard against exceptions. If an exception occurs during the function call, you can assume that that particular aspect of FindFirstFileEx isn't supported on that device.
Distinguishing Drives from Directories
As I mentioned at the beginning of this chapter, Windows CE doesn't support the concept of drive letters so familiar to MS-DOS and Windows users. Instead, file storage devices such as PC Cards or even hard disks are shown as directories in the root directory. That leads to the question, "How can you tell a directory from a drive?" To do this, you need to look at the file attributes for the directory. Directories that are actually secondary storage devices—that is, they store files in a place other than the object store—have the file attribute flag FILE_ATTRIBUTE_TEMPORARY set. Windows CE also uses this attribute flag for other "nondirectory" directories such as the NETWORK and RELEASE folders. The NETWORK folder lists network shares. The RELEASE folder is used during embedded development. So finding storage devices on any version of Windows CE is fairly easy, as is shown in the following code fragment:
WIN32_FIND_DATA fd;
HANDLE hFind;
TCHAR szPath[MAX_PATH];
ULARGE_INTEGER lnTotal, lnFree;
lstrcpy (szPath, TEXT ("\\*.*"));
hFind = FindFirstFile (szPath, &fd);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
(fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY)) {
TCHAR szName[MAX_PATH];
wstrcpy (szName, fd.cFileName);
wstrcat (szName, TEXT (\\Vol:));
HANDLE h = CreateFile (szName,
GENERIC_READ|GENERIC_WRITE,
0, NULL, OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
if (h != INVALID_HANDLE_VALUE) {
CloseHandle (h);
// Get the disk space statistics for drive.
GetDiskFreeSpaceEx (fd.cFileName, NULL, &lnTotal,
&lnFree);
}
}
} while (FindNextFile (hFind, &fd));
FindClose (hFind);
}
This code uses the find first/find next functions to search the root directory for all directories with the FILE_ATTRIBUTE_TEMPORARY attribute set. It then checks to see whether the directory can be opened as a volume. Other directories with the FILE_ATTRIBUTE_TEMPORARY flag can't be opened because they don't represent file system volumes.Notice the call to the following function in the code I just showed you:
BOOL GetDiskFreeSpaceEx (LPCWSTR lpDirectoryName,
PULARGE_INTEGER lpFreeBytesAvailableToCaller,
PULARGE_INTEGER lpTotalNumberOfBytes,
PULARGE_INTEGER lpTotalNumberOfFreeBytes);
This function provides information about the total size of the drive and the amount of free space it contains. The first parameter is the name of any directory on the drive in question. This doesn't have to be the root directory of the drive. GetDiskFreeSpaceEx returns three values: the free bytes available to the caller, the total size of the drive, and the total free space on the drive. These values are returned in three ULARGE_INTEGER structures. These structures contain two DWORD fields named LowPart and HighPart. This allows GetDiskFreeSpaceEx to return 64-bit values. Those 64-bit values can come in handy on Windows XP, where the drives can be large. If you aren't interested in one or more of the fields, you can pass a NULL in place of the pointer for that parameter. You can also use GetDiskFreeSpaceEx to determine the size of the object store.Another function that can be used to determine the size of the object store is
BOOL GetStoreInformation (LPSTORE_INFORMATION lpsi);
GetStoreInformation takes one parameter, a pointer to a STORE_INFORMATION structure defined as
typedef struct STORE_INFORMATION {
DWORD dwStoreSize;
DWORD dwFreeSize;
} STORE_INFORMATION, *LPSTORE_INFORMATION;
As you can see, this structure simply returns the total size and amount of free space in the object store.That covers the Windows CE file API. As you can see, very little Windows CE–unique code is necessary when you're working with the object store. Now let's look at the registry API, where Windows CE also follows the Win32 API quite closely.