The Windows CE API contains several functions that are useful for retrieving file information. For example, you can find out when a file was last modified, how its attribute bits are currently set, and the size of the file. The following sections detail the different capabilities that are available. Several of these functions require an open file handle rather than the file's name. Getting the File TimesThe GetFileTime function retrieves three different pieces of time information from an open file: the Creation time, the Last Access time, and the Last Write time.
In Listing 2.3, the CreateFile function opens the requested file name. GetFileTime uses the handle that it returns to access the file times, and then passes the last write time up to the ShowTime function to dump the time to cout. Listing 2.3 Displays the file times associated with the given filevoid ShowTime(FILETIME t) { FILETIME ft; SYSTEMTIME st; FileTimeToLocalFileTime(&t, &ft); FileTimeToSystemTime(&ft, &st); cout st.wMonth _T("/") st.wDay _T("/") st.wYear _T(" ") st.wHour _T(":") st.wMinute endl; } void Listing2_3() { HANDLE hFile; TCHAR szFilename[MAX_PATH + 1]; FILETIME ftCreate, ftLastWrite, ftLastAccess; if(!GetFilename(_T("Enter filename:"), szFilename, MAX_PATH)) return; hFile = CreateFile(szFilename, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0); if(hFile == INVALID_HANDLE_VALUE) { cout _T("Could not open file. Error:") GetLastError(); return; } if(GetFileTime(hFile, &ftCreate, &ftLastWrite, &ftLastAccess)) { cout _T("Create time: "); ShowTime(ftCreate); cout _T("Last write time: "); ShowTime(ftLastWrite); cout _T("Last Access time: "); ShowTime(ftLastAccess); } else cout _T("Could not file times. Error: ") GetLastError(); CloseHandle(hFile); } FILETIME is a structure that contains two 32-bit values. The 64 bits together represent the number of 100-nanosecond time increments that have passed since January1, 1601. The FileTimeToLocalTime and FileTimeToSystemTime functions convert the 64-bit value to local time and then to a form suitable for output. The times returned by GetFileTime are in UTC (Universal Coordinated Time, otherwise known as Greenwich Mean Time or GMT), and so should be converted to local time when displayed to users. The function SetFileTime can be used to set one or all of the three file times. Note that when changing just one of the times on an object store file, the other two file times are updated by default. This behavior does not occur with FAT files. Getting File SizeThe GetFileSize function returns the size of the file in bytes, or 0xFFFFFFFF on error. The file size returned is the uncompressed file size files in the object store are automatically compressed. In the Object Store the largest file size possible can be represented in less than 32 bits, but NTFS (which you may connect to through the network) is a 64-bit file system. GetFileSize therefore returns 64 bits of size information if you request it. There is currently no easy way to deal with integers larger than 32bits.
The low-order 32 bits of size information comes from the return value, while the high-order 32 bits come from the fileSizeHigh parameter when you pass in a pointer to a DWORD. You can also pass in NULL for this parameter if you are not interested in receiving the high-order 32 bits of information. Listing 2.4 shows how to access the information. Listing 2.4 Reports size of file in bytesvoid Listing2_4() { HANDLE hFile; TCHAR szFilename[MAX_PATH + 1]; DWORD dwSizeLo, dwSizeHi; if(!GetFilename(_T("Enter filename:"), szFilename, MAX_PATH)) return; hFile = CreateFile(szFilename, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0); if(hFile == INVALID_HANDLE_VALUE) { cout _T("Could not open file. Error:") GetLastError(); return; } dwSizeLo = GetFileSize (hFile, &dwSizeHi); if(dwSizeLo == 0xFFFFFFFF && GetLastError() != NO_ERROR) cout _T("Error getting file size: ") GetLastError(); else cout _T("Filesize (Low, High) : ") dwSizeLo _T(",") dwSizeHi; CloseHandle(hFile); } Getting File AttributesFiles have associated with them attribute bits that hold special information about the file. You can view most of the attributes from the Explorer by selecting a file and then choosing the Properties option in the File menu. Inside a program you can examine attribute bits with the GetFileAttributes function.
Listing 2.5 demonstrates how to acquire and examine the attribute bits. The system returns not only the four standard bits seen in the Explorer (archive, read only, system, and hidden), but also bits indicating that the file name is actually a directory, as well as In-ROM and related attributes. Note that not all the available attributes are listed in the code sample. Listing 2.5 Reports file attributesvoid ShowAttributes(DWORD dwAttributes) { if(dwAttributes & FILE_ATTRIBUTE_READONLY) cout _T("Read only") endl; if(dwAttributes & FILE_ATTRIBUTE_HIDDEN) cout _T("Hidden") endl; if(dwAttributes & FILE_ATTRIBUTE_SYSTEM) cout _T("System") endl; if(dwAttributes & FILE_ATTRIBUTE_DIRECTORY) cout _T("Directory") endl; if(dwAttributes & FILE_ATTRIBUTE_ARCHIVE) cout _T("Archive") endl; if(dwAttributes & FILE_ATTRIBUTE_INROM) cout _T("In ROM") endl; if(dwAttributes & FILE_ATTRIBUTE_NORMAL) cout _T("Normal") endl; if(dwAttributes & FILE_ATTRIBUTE_TEMPORARY) cout _T("Temporary") endl; if(dwAttributes & FILE_ATTRIBUTE_COMPRESSED) cout _T("Compressed") endl; if(dwAttributes & FILE_ATTRIBUTE_ROMSTATICREF) cout _T("ROM Static Ref") endl; if(dwAttributes & FILE_ATTRIBUTE_ROMMODULE) cout _T("ROM Module") endl; } void Listing2_5() { TCHAR szFilename[MAX_PATH + 1]; DWORD dwAttributes; if(!GetFilename(_T("Enter filename:"), szFilename, MAX_PATH)) return; dwAttributes = GetFileAttributes(szFilename); ShowAttributes(dwAttributes); } It is also possible to set some file attributes using the SetFileAttributes function. This function accepts a file name and one or more attribute constants, and returns a Boolean value indicating success or failure.
The same attribute constants seen in the ShowAttributes function of Listing 2.5 are available. For example, you might set a file as hidden and read-only with the following statement: success = SetFileAttributes(_T("xxx"), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY); Generally those are the only two attributes you will want to set. The other bits, for example the directory bit, are set automatically by system calls when they are appropriate and should not be altered. File attributes can be set when the file is created using CreateFile. Table 2.8 shows the Windows CE file attributes; indicates whether they can be accessed using GetFileAttributes, SetFileAttributes, and CreateFile; and gives a brief definition.
All files in the object store are compressed, and will have the FILE_ATTRIBUTE_COMPRESSED attribute. You cannot set this attribute to compress a file as you can with Windows NT and 2000. Getting All File InformationThe function GetFileInformationByHandle returns all of the information described in the previous three sections in one call. It is useful when you want to access or display all information about a file in one call.
The information comes back in a structure that contains the attributes, size, and time data discussed in the previous sections, along with volume, index, and link information not available anywhere else. The volume serial number is a unique number assigned to the volume when it was formatted. The file index is a unique identifier attached to the file while it is open. Listing 2.6 demonstrates the process. Listing 2.6 Lists all information for a given filevoid Listing2_6() { HANDLE hFile; TCHAR szFilename[MAX_PATH + 1]; BY_HANDLE_FILE_INFORMATION fiInfo; if(!GetFilename(_T("Enter filename:"), szFilename, MAX_PATH)) return; hFile = CreateFile(szFilename, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0); if(hFile == INVALID_HANDLE_VALUE) { cout _T("Could not open file. Error:") GetLastError(); return; } if(GetFileInformationByHandle(hFile, &fiInfo)) { ShowAttributes(fiInfo.dwFileAttributes); cout _T("Create time: "); ShowTime(fiInfo.ftCreationTime); cout _T("Last write time: "); ShowTime(fiInfo.ftLastWriteTime); cout _T("Last Access time: "); ShowTime(fiInfo.ftLastAccessTime); cout _T("Volume serial number: ") fiInfo.dwVolumeSerialNumber endl; cout _T("File size: ") fiInfo.nFileSizeLow endl; cout _T("High index: ") fiInfo.nFileIndexHigh endl; cout _T("Low index: ") fiInfo.nFileIndexLow endl; cout _T("Object ID: ") fiInfo.dwOID endl; } CloseHandle(hFile); } File OperationsThe API provides three functions for the common file operations of moving, copying, and deleting files. You can use these functions in your programs to duplicate the functionality of the command line equivalents. The CopyFile function copies the source file to the destination file name. If an error occurs during the copy, GetLastError contains the error code.
The existFail parameter controls the behavior of the function when the destination file name already exists. If you set it to TRUE, then the function fails when the destination file name already exists. When set to FALSE, the function overwrites an existing file. This code fragment demonstrates the use of this function. success = CopyFile(sourceFilename, destFilename, TRUE); if (!success) cout _T("Error code = ") GetLastError(); else cout _T("success\n"); Files can be deleted using the DeleteFile function, which is passed the filename to be deleted (Table 2.11). If the return value is FALSE, use the GetLastError function to retrieve the error code, as shown in this code fragment.
success = DeleteFile(filename); if (success) cout _T("success\n"); else cout _T("Error number: ") " GetLastError(); File Reading and WritingThe section "Opening and Reading from a File" in this chapter briefly introduced simple file reading using CreateFile, ReadFile, and CloseHandle. In this section we will examine file seeking, reading, and writing in more detail, and look at the CreateFile function more carefully. The operations here are all synchronous, so they block (that is, do not return) until complete. Asynchronous file operations are not supported in Windows CE. Listing 2.7 demonstrates a file-write operation that writes structures to a new file. Listing 2.7 Writes structures to a filetypedef struct { int a, b, c; } DATA; void Listing2_7() { HANDLE hFile; TCHAR szFilename[MAX_PATH + 1]; BOOL bSuccess; DATA dataRec; int x; DWORD numWrite; if(!GetFilename(_T("Enter filename to create:"), szFilename, MAX_PATH, TRUE)) return; cout szFilename; hFile = CreateFile(szFilename, GENERIC_WRITE, 0, 0, CREATE_NEW, 0, 0); if(hFile == INVALID_HANDLE_VALUE) { cout _T("Could not open file. Error:") GetLastError(); return; } = 0; do { dataRec.a = dataRec.b = dataRec.c = x; bSuccess = WriteFile(hFile, &dataRec, sizeof(dataRec), &numWrite, 0); } while(bSuccess && x++ 10); CloseHandle(hFile); } The WriteFile function is similar to the ReadFile function, writing the specified number of bytes to disk. The function does not care what the bytes represent, so you can use it to write text or structures. In Listing 2.7, the program writes one structure's set of bytes in a single operation and repeats the operation ten times.
Listing 2.7 uses the CreateFile function in its simplest configuration. For example, in Listing 2.7 the GENERIC_WRITE constant indicates that we need write access to the file, and the CREATE_NEW constant indicates that the system should create a new file rather than overwrite an existing one (if the file name already exists, the function fails). However, CreateFile has many other capabilities. When using the CreateFile function, you have control over several different properties:
The first parameter contains the name of the file to be opened. The function GetTempFileName can be used to obtain a valid temporary filename from the operating system. The second parameter passed to CreateFile controls read and write access. You can pass in any of the following three combinations:
Generally you use the third option when you plan to open a file of structures that you will read and modify simultaneously. You use GENERIC_READ when you want read-only access, and GENERIC_WRITE when you need write-only access. The third parameter passed to CreateFile controls the share mode of the file. You control access to the entire file using this parameter. Four variations are possible (Table 2.15).
If you pass 0 to the shareMode parameter, then the entire file is locked while you have it open. Any other process attempting to open the file will receive a share violation. The remaining options grant increasing levels of access to other processes. The Create parameter controls the failure behavior of CreateFile during creation. Any of the options in Table 2.16 may be used. If you create a new file with the same name as a file in ROM, the ROM file will be "shadowed." Your new file will replace the ROM file. If your file is deleted, the ROM file comes back into use.
The Attributes parameter lets you set the file attributes, and it also lets you tell the system your intended use of the file so that you can improve overall system performance. Table 2.17 shows all the available attributes and indicates which ones can be used in CreateFile, GetFileAttributes, and SetFileAttributes. Table 2.8 provides a description of the attributes. You can OR together nonconflicting combinations shown in Table 2.17 as needed in an application. Many of the flag options are hints that you give to help the operating system improve its overall performance. For example, if you know you are opening a 1-MB file that you will read from beginning to end and never use again, then it is a waste for the operating system to cache any of it. You should therefore use the FILE_FLAG_SEQUENTIAL_SCAN option. It is possible to read from or write to a file either sequentially or at random byte offsets in the file. You typically use random offsets when the file contains a set of structures. The SetFilePointer function moves the file pointer to the indicated position. The new file position can move a distance that is relative to the beginning of the file, the end of the file, or the current position. Positive values move forward, and negative values move backward. Listing 2.8 demonstrates a program that sets the file pointer to the fifth structure in the file written by Listing 2.7.
Listing 2.8 Gets 5th record from file created in Listing 2.7 and displays itvoid Listing2_8() { HANDLE hFile; DWORD dwNumRead; TCHAR szFilename[MAX_PATH + 1]; DATA dataRec; if(!GetFilename(_T("Enter filename:"), szFilename, MAX_PATH)) return; hFile = CreateFile(szFilename, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0); if(hFile == INVALID_HANDLE_VALUE) { cout _T("Could not open file. Error:") GetLastError(); return; } SetFilePointer(hFile, 5 * sizeof(DATA), 0, FILE_BEGIN); if(GetLastError() != NO_ERROR) { cout _T("Could not seek to file. Error:") GetLastError(); } else { if(ReadFile(hFile, &dataRec, sizeof(DATA), &dwNumRead, 0)) { cout _T("Record 5: ") dataRec.a _T(" ") dataRec.b _T(" ") dataRec.c endl; } else { cout _T("Could not read file. Error: ") GetLastError(); } } CloseHandle(hFile); } File MappingThe Win32 API provides a feature called file mapping that allows you to map a file directly into the Windows CE virtual memory space. This capability is often used to implement interprocess communication schemes and is also useful for simplifying or speeding file access. You can map a file either for read-only or read-write access. Once mapped, you access the file by address (using array or pointer syntax) rather than using file access functions such as ReadFile or WriteFile. For example, say that you need to access data in a file and you know that you will make a large number of writes to the file in rapid succession. Also imagine that, for performance reasons, you cannot afford the time it takes to perform all of those writes. Typically you would solve this problem by reading the file to an array, accessing the array, and then writing the array back to disk. File mapping does this automatically it maps the file into memory for you. In addition, you can share the memory image among multiple processes, and the image will remain coherent to all viewers on a single machine. If several processes all use the same file-mapping object, all changes to the mapped file will be reflected in the data read by all processes. Listing 2.9 shows how to use file mapping in read-only mode. Listing 2.9 Displays Unicode text file using file mappingvoid Listing2_9() { HANDLE hFile; TCHAR szFilename[MAX_PATH + 1]; HANDLE hFileMap; LPTSTR lpFile; if(!GetFilename(_T("Enter filename:"), szFilename, MAX_PATH)) return; hFile = CreateFileForMapping(szFilename, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0); if(hFile == INVALID_HANDLE_VALUE) { cout _T("Could not open file. Error:") GetLastError(); return; } hFileMap = CreateFileMapping(hFile, 0, PAGE_READONLY, 0, 0, NULL); if(hFileMap == NULL) { cout _T("Could not create file mapping:") GetLastError(); CloseHandle(hFile); return; } lpFile = (LPTSTR) MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0); if(lpFile == NULL) cout _T("Could not create view of map:") GetLastError(); else { if((DWORD)*lpFile != 0xFEFF) cout _T("Not a Unicode file"); else { lpFile++; // skip over first two bytes. // DANGEROUS! Assumes '\0' terminated file cout lpFile; } UnmapViewOfFile(lpFile); } CloseHandle(hFileMap); CloseHandle(hFile); } The program in Listing 2.9 begins by asking the user for a filename and opening the file with CreateFileForMapping. In Windows CE, CreateFileForMapping should be used to open a file ready for file mapping, instead of CreateFile. As Table 2.19 shows, this function takes the same arguments as CreateFile.
Listing 2.9 then calls the CreateFileMapping function to create the mapping. This step determines the size of the mapping as well as its data. The protection is set to read-only, and setting sizeLow and sizeHigh to zero sets the size to the current file size.
The MapViewOfFile function reserves data in an address range set aside for memory-mapped files, and returns the new address of the data. The address range for memory-mapped files is above the address range used for processes. The data from the file will be paged into this memory space as you access it. In Listing 2.9, lpFile is declared as a pointer to a character so that the data can be treated text. You can declare lpFile to be of any type. For example, if the file contains a set of structures, let lpFile be a pointer to that type of structure.
In Listing 2.9, the code maps the entire file with read-only access. Once mapped, lpFile points to the address of the mapping, and you use it just like any other pointer or array. If you load a text file with this program, the cout statement displays the entire file, as shown. This is dangerous, since cout will assume that whatever lpFile points at is null-character terminated, but this is not generally the case for text files. The code will work until you try to open a file that contains an exact number of memory pages. In this situation, cout will look beyond the last page for the null character, and this will often cause a page fault. Once you have finished with the file, use UnmapViewOfFile to unload the memory and write any changes back to the original file. No changes were made here, but the next example makes use of this feature.
Listing 2.10 shows a second example of file mapping. Here the program opens the mapped file for read-write access and then writes to the file. The changes are flushed to disk only when the program calls UnmapViewOfFile. Listing 2.10 Displays Unicode text file using writable file mappingvoid Listing2_10() { HANDLE hFile; TCHAR szFilename[MAX_PATH + 1]; HANDLE hFileMap; LPTSTR lpFile; DWORD dwSizeLo; if(!GetFilename(_T("Enter filename:"), szFilename, MAX_PATH)) return; hFile = CreateFileForMapping(szFilename, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0); if(hFile == INVALID_HANDLE_VALUE) { cout _T("Could not open file. Error:") GetLastError(); return; } // assume < 4 gigabytes dwSizeLo = GetFileSize (hFile, NULL); hFileMap = CreateFileMapping(hFile, 0, PAGE_READWRITE, 0, dwSizeLo + 1, NULL); if(hFileMap == NULL) { cout _T("Could not create file mapping:") GetLastError(); CloseHandle(hFile); return; } lpFile = (LPTSTR) MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, 0); if(lpFile == NULL) cout _T("Could not create view of map:") GetLastError(); else { if((DWORD)*lpFile != 0xFEFF) cout _T("Not a Unicode file"); else { // add terminating NULL character lpFile[dwSizeLo] = '\0'; // skip over first two bytes. lpFile++; cout lpFile; } UnmapViewOfFile(lpFile); } CloseHandle(hFileMap); // remove NULL character at end of file SetFilePointer(hFile, -2, NULL, FILE_END); SetEndOfFile(hFile); CloseHandle(hFile); } Listing 2.10 opens the mapping for reading and writing. A null character is appended to the end of the file, and this makes writing the contents of the file to cout safe. The null character needs to be removed once the mapping is closed. This can be done by moving the file pointer to the byte before the null character and then calling SetEndOfFile to set the end of file to the current file position.
The function FlushViewOfFile can be used to write any changed data out to the Object Store. This function is also useful when using a read-only mapped file. As you read through a file, pages of memory are used to store the data. If you are reading a large file, significant amounts of the device's scarce memory can be used up. Calling FlushViewOfFile will release these pages of memory.
When using FlushViewOfFile, you generally flush the entire file. The system is smart enough to write back to disk only those memory pages that actually contain modified data.
|