Using Memory-Mapped Files

[Previous] [Next]

To use a memory-mapped file, you must perform three steps:

  1. Create or open a file kernel object that identifies the file on disk that you want to use as a memory-mapped file.
  2. Create a file-mapping kernel object that tells the system the size of the file and how you intend to access the file.
  3. Tell the system to map all or part of the file-mapping object into your process's address space.

When you are finished using the memory-mapped file, you must perform three steps to clean up:

  1. Tell the system to unmap the file-mapping kernel object from your process's address space.
  2. Close the file-mapping kernel object.
  3. Close the file kernel object.

The next five sections discuss all these steps in more detail.

Step 1: Creating or Opening a File Kernel Object

To create or open a file kernel object, always call the CreateFile function:

 HANDLE CreateFile( PCSTR pszFileName, DWORD dwDesiredAccess, DWORD dwShareMode, PSECURITY_ATTRIBUTES psa, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); 

The CreateFile function takes quite a few parameters. For this discussion, I'll concentrate only on the first three: pszFileName, dwDesiredAccess, and dwShareMode.

As you might guess, the first parameter, pszFileName, identifies the name (including an optional path) of the file that you want to create or open. The second parameter, dwDesiredAccess, specifies how you intend to access the contents of the file. You can specify one of the four following values.

Value Meaning
0 You cannot read from or write to the file's contents. Specify 0 when you just want to get a file's attributes.
GENERIC_READ You can read from the file.
GENERIC_WRITE You can write to the file.
GENERIC_READ | GENERIC_WRITE You can read from the file and write to the file.

When creating or opening a file for use as a memory-mapped file, select the access flag or flags that make the most sense for how you intend to access the file's data. For memory-mapped files, you must open the file for read-only access or read-write access, so you'll want to specify either GENERIC_READ or GENERIC_READ | GENERIC_WRITE, respectively.

The third parameter, dwShareMode, tells the system how you want to share this file. You can specify one of the four following values for dwShareMode.

Value Meaning
0 Any other attempts to open the file fail.
FILE_SHARE_READ Other attempts to open the file using GENERIC_WRITE fail.
FILE_SHARE_WRITE Other attempts to open the file using GENERIC_READ fail.
FILE_SHARE_READ | FILE_SHARE_WRITE Other attempts to open the file succeed.

If CreateFile successfully creates or opens the specified file, a handle to a file kernel object is returned; otherwise, INVALID_HANDLE_VALUE is returned.

NOTE
Most Windows functions that return a handle return NULL when they are unsuccessful. CreateFile, however, returns INVALID_HANDLE_ VALUE, which is defined as ((HANDLE) -1).

Step 2: Creating a File-Mapping Kernel Object

Calling CreateFile tells the operating system the location of the file mapping's physical storage. The pathname that you pass indicates the exact location on the disk (or on the network or the CD-ROM, for example) of the physical storage that is backing the file mapping. Now you must tell the system how much physical storage the file-mapping object requires. You do this by calling CreateFileMapping:

 HANDLE CreateFileMapping( HANDLE hFile, PSECURITY_ATTRIBUTES psa, DWORD fdwProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, PCTSTR pszName); 

The first parameter, hFile, identifies the handle of the file you want mapped into the process's address space. This handle is returned by the previous call to CreateFile. The psa parameter is a pointer to a SECURITY_ATTRIBUTES structure for the file-mapping kernel object; usually NULL is passed (which provides default security and the returned handle is noninheritable).

As I pointed out at the beginning of this chapter, creating a memory-mapped file is just like reserving a region of address space and then committing physical storage to the region. It's just that the physical storage for a memory-mapped file comes from a file on a disk rather than from space allocated from the system's paging file. When you create a file-mapping object, the system does not reserve a region of address space and map the file's storage to the region. (I'll describe how to do this in the next section.) However, when the system does map the storage to the process's address space, the system must know what protection attribute to assign to the pages of physical storage. CreateFileMapping's fdwProtect parameter allows you to specify the protection attributes. Most of the time, you will specify one of the three protection attributes listed in the table below.

Protection Attribute Meaning
PAGE_READONLY When the file-mapping object is mapped, you can read the file's data. You must have passed GENERIC_READ to CreateFile.
PAGE_READWRITE When the file-mapping object is mapped, you can read and write the file's data. You must have passed GENERIC_READ | GENERIC_WRITE to CreateFile.
PAGE_WRITECOPY When the file-mapping object is mapped, you can read and write the file's data. Writing causes a private copy of the page to be created. You must have passed either GENERIC_READ or GENERIC_READ | GENERIC_WRITE to CreateFile

Windows 98
Under Windows 98, you can pass the PAGE_WRITECOPY flag to CreateFileMapping; this tells the system to commit storage from the paging file. This paging file storage is reserved for a copy of the data file's data; only modified pages are actually written to the paging file. Any changes you make to the file's data are not propagated back to the original data file. The end result is that the PAGE_WRITECOPY flag has the same effect on both Windows 2000 and Windows 98.

In addition to the above page protections, there are four section attributes that you can bitwise OR in the CreateFileMapping function's fdwProtect parameter. A section is just another word for a memory mapping.

The first of these attributes, SEC_NOCACHE, tells the system that none of the file's memory-mapped pages are to be cached. So as you write data to the file, the system will update the file's data on the disk more often than it normally would. This flag, like the PAGE_NOCACHE protection attribute, exists for the device driver developer and is not usually used by applications.

Windows 98
Windows 98 ignores the SEC_NOCACHE flag.

The second section attribute, SEC_IMAGE, tells the system that the file you are mapping is a portable executable (PE) file image. When the system maps this file into your process's address space, the system examines the file's contents to determine which protection attributes to assign to the various pages of the mapped image. For example, a PE file's code section (.text) is usually mapped with PAGE_EXECUTE_READ attributes, whereas the PE file's data section (.data) is usually mapped with PAGE_READWRITE attributes. Specifying the SEC_IMAGE attribute tells the system to map the file's image and to set the appropriate page protections.

Windows 98
Windows 98 ignores the SEC_IMAGE flag.

The last two attributes, SEC_RESERVE and SEC_COMMIT, are mutually exclusive and do not apply when you are using a memory-mapped data file. These two flags will be discussed in the section "Sparsely Committed Memory-Mapped Files" later in this chapter. When creating a memory-mapped data file, you should not specify either of these flags. CreateFileMapping will ignore them.

CreateFileMapping's next two parameters, dwMaximumSizeHigh and dwMaximumSizeLow, are the most important parameters. The main purpose of the CreateFileMapping function is to ensure that enough physical storage is available for the file-mapping object. These two parameters tell the system the maximum size of the file in bytes. Two 32-bit values are required because Windows supports file sizes that can be expressed using a 64-bit value; the dwMaximumSizeHigh parameter specifies the high 32 bits, and the dwMaximumSizeLow parameter specifies the low 32 bits. For files that are 4 GB or less, dwMaximumSizeHigh will always be 0.

Using a 64-bit value means that Windows can process files as large as 16 EB (exabytes). If you want to create the file-mapping object so that it reflects the current size of the file, you can pass 0 for both parameters. If you intend only to read from the file or to access the file without changing its size, pass 0 for both parameters. If you intend to append data to the file, you will want to choose a maximum file size that leaves you some breathing room. If the file on disk currently contains 0 bytes, you can't pass two zeros to CreateFileMapping's dwMaximumSizeHigh and dwMaximumSizeLow parameters. Doing so tells the system that you want a file-mapping object with 0 bytes of storage in it. This is an error and CreateFileMapping will return NULL.

If you've paid attention so far, you must be thinking that something is terribly wrong here. It's nice that Windows supports files and file-mapping objects that can be anywhere up to 16 EB, but how are you ever going to map a file that big into a 32-bit process's address space, which has a maximum limit of 4 GB (little of which is even usable)? I'll explain how you can accomplish this in the next section. Of course, a 64-bit process has a 16-EB address space so you can work with much larger file mappings, but a similar limitation still exists if the file is super-big.

To really understand how CreateFile and CreateFileMapping work, I suggest you try the following experiment. Take the code below, build it, and then run it in a debugger. As you single-step through each statement, jump to a command shell and execute a "dir" command on the C:\ directory. Notice the changes that are appearing in the directory as you execute each statement in the debugger.

 int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int nCmdShow) { // Before executing the line below, C:\ does not have // a file called "MMFTest.Dat." HANDLE hfile = CreateFile("C:\\MMFTest.dat", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); // Before executing the line below, the MMFTest.Dat // file does exist but has a file size of 0 bytes. HANDLE hfilemap = CreateFileMapping(hfile, NULL, PAGE_READWRITE, 0, 100, NULL); // After executing the line above, the MMFTest.Dat // file has a size of 100 bytes. // Cleanup CloseHandle(hfilemap); CloseHandle(hfile); // When the process terminates, MMFTest.Dat remains // on the disk with a size of 100 bytes. return(0); } 

If you call CreateFileMapping, passing the PAGE_READWRITE flag, the system will check to make sure that the associated data file on the disk is at least the same size as the size specified in the dwMaximumSizeHigh and dwMaximumSizeLow parameters. If the file is smaller than the specified size, CreateFileMapping will make the file on the disk larger by extending its size. This enlargement is required so that the physical storage will already exist when the file is used later as a memory-mapped file. If the file-mapping object is being created with the PAGE_READONLY or the PAGE_WRITECOPY flag, the size specified to CreateFileMapping must be no larger than the physical size of the disk file. This is because you won't be able to append any data to the file.

The last parameter of CreateFileMapping, pszName, is a zero-terminated string that assigns a name to this file-mapping object. The name is used to share the object with another process. (An example of this is shown later in this chapter. Chapter 3 also discusses kernel object sharing in greater detail.) A memory-mapped data file usually doesn't need to be shared; therefore, this parameter is usually NULL.

The system creates the file-mapping object and returns a handle identifying the object back to the calling thread. If the system cannot create the file-mapping object, a NULL handle value is returned. Again, please remember that CreateFile returns INVALID_HANDLE_VALUE (defined as -1) when it fails and CreateFileMapping returns NULL when it fails. Don't get these error values confused.

Step 3: Mapping the File's Data into the Process's Address Space

After you have created a file-mapping object, you still need to have the system reserve a region of address space for the file's data and commit the file's data as the physical storage that is mapped to the region. You do this by calling MapViewOfFile:

 PVOID MapViewOfFile( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap); 

The hFileMappingObject parameter identifies the handle of the file-mapping object, which was returned by the previous call to either CreateFileMapping or OpenFileMapping (discussed later in this chapter). The dwDesiredAccess parameter identifies how the data can be accessed. That's right, we must again specify how we intend to access the file's data. You can specify one of four possible values shown in the table below.

Value Meaning
FILE_MAP_WRITE You can read and write file data. CreateFileMapping had to be called by passing PAGE_READWRITE.
FILE_MAP_READ You can read file data. CreateFileMapping could be called with any of the protection attributes: PAGE_READONLY, PAGE_READWRITE, or PAGE_WRITECOPY.
FILE_MAP_ALL_ACCESS Same as FILE_MAP_WRITE.
FILE_MAP_COPY You can read and write file data. Writing causes a private copy of the page to be created. In Windows 2000, CreateFileMapping could be called with any of the protection attributes: PAGE_READONLY, PAGE_READWRITE, or PAGE_WRITECOPY. Windows 98 requires that CreateFileMapping be called with PAGE_WRITECOPY.

It certainly seems strange and annoying that Windows requires all these protection attributes to be set over and over again. I assume this was done to give an application as much control as possible over data protection.

The remaining three parameters have to do with reserving the region of address space and mapping the physical storage to the region. When you map a file into your process's address space, you don't have to map the entire file at once. Instead, you can map only a small portion of the file into the address space. A portion of a file that is mapped into your process's address space is called a view, which explains how MapViewOfFile got its name.

When you map a view of a file into your process's address space, you must specify two things. First, you must tell the system which byte in the data file should be mapped as the first byte in the view. You do this using the dwFileOffsetHigh and dwFileOffsetLow parameters. Because Windows supports files that can be up to 16 EB, you must specify this byte-offset using a 64-bit value of which the high 32 bits are passed in the dwFileOffsetHigh parameter and the low 32 bits are passed in the dwFileOffsetLow parameter. Note that the offset in the file must be a multiple of the system's allocation granularity. (To date, all implementations of Windows have an allocation granularity of 64 KB.) The section "System Information" in Chapter 14 shows how to obtain the allocation granularity value for a given system.

Second, you must tell the system how much of the data file to map into the address space. This is the same thing as specifying how large a region of address space to reserve. You specify this size using the dwNumberOfBytesToMap parameter. If you specify a size of 0, the system will attempt to map a view starting with the specified offset within the file to the end of the entire file.

Windows 98
In Windows 98, if MapViewOfFile cannot find a region large enough to contain the entire file-mapping object, MapViewOfFile returns NULL regardless of the size of the view requested.

Windows 2000
In Windows 2000, MapViewOfFile needs only to find a region large enough for the view requested, regardless of the size of the entire file-mapping object.

If you specify the FILE_MAP_COPY flag when calling MapViewOfFile, the system commits physical storage from the system's paging file. The amount of space committed is determined by the dwNumberOfBytesToMap parameter. As long as you do nothing more than read from the file's mapped view, the system never uses these committed pages in the paging file. However, the first time any thread in your process writes to any memory address within the file's mapped view, the system will grab one of the committed pages from the paging file, copy the page of original data to this paging-file page, and then map this copied page into your process's address space. From this point on, the threads in your process are accessing a local copy of the data and cannot read or modify the original data.

When the system makes the copy of the original page, the system changes the protection of the page from PAGE_WRITECOPY to PAGE_READWRITE. The following code fragment explains it all:

 // Open the file that we want to map. HANDLE hFile = CreateFile(pszFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); // Create a file-mapping object for the file. HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_WRITECOPY, 0, 0, NULL); // Map a copy-on-write view of the file; the system will commit // enough physical storage from the paging file to accommodate // the entire file. All pages in the view will initially have // PAGE_WRITECOPY access. PBYTE pbFile = (PBYTE) MapViewOfFile(hFileMapping, FILE_MAP_COPY, 0, 0, 0); // Read a byte from the mapped view. BYTE bSomeByte = pbFile[0]; // When reading, the system does not touch the committed pages in // the paging file. The page keeps its PAGE_WRITECOPY attribute. // Write a byte to the mapped view. pbFile[0] = 0; // When writing for the first time, the system grabs a committed // page from the paging file, copies the original contents of the // page at the accessed memory address, and maps the new page // (the copy) into the process's address space. The new page has // an attribute of PAGE_READWRITE. // Write another byte to the mapped view. pbFile[1] = 0; // Because this byte is now in a PAGE_READWRITE page, the system // simply writes the byte to the page (backed by the paging file). // When finished using the file's mapped view, unmap it. // UnmapViewOfFile is discussed in the next section. UnmapViewOfFile(pbFile); // The system decommits the physical storage from the paging file. // Any writes to the pages are lost. // Clean up after ourselves. CloseHandle(hFileMapping); CloseHandle(hFile); 

Windows 98
As mentioned earlier, Windows 98 must commit storage in the paging file for the memory-mapped file up front. However, it will write modified pages to the paging file only as necessary.

Step 4: Unmapping the File's Data from the Process's Address Space

When you no longer need to keep a file's data mapped to a region of your process's address space, you can release the region by calling

 BOOL UnmapViewOfFile(PVOID pvBaseAddress); 

The only parameter, pvBaseAddress, specifies the base address of the returned region. This value must be the same value returned from a call to MapViewOfFile. You must remember to call UnmapViewOfFile. If you do not call this function, the reserved region won't be released until your process terminates. Whenever you call MapViewOfFile, the system always reserves a new region within your process's address space—any previously reserved regions are not released.

In the interest of speed, the system buffers the pages of the file's data and doesn't update the disk image of the file immediately while working with the file's mapped view. If you need to ensure that your updates have been written to disk, you can force the system to write a portion or all of the modified data back to the disk image by calling FlushViewOfFile:

 BOOL FlushViewOfFile( PVOID pvAddress, SIZE_T dwNumberOfBytesToFlush); 

The first parameter is the address of a byte contained within a view of a memory-mapped file. The function rounds down the address you pass here to a page boundary. The second parameter indicates the number of bytes that you want flushed. The system will round this number up so that the total number of bytes is an integral number of pages. If you call FlushViewOfFile and none of the data has been changed, the function simply returns without writing anything to the disk.

For a memory-mapped file whose storage is over a network, FlushViewOfFile guarantees that the file's data has been written from the workstation. However, FlushViewOfFile cannot guarantee that the server machine that is sharing the file has written the data to the remote disk drive because the server might be caching the file's data. To ensure that the server writes the file's data, you should pass the FILE_FLAG_WRITE_THROUGH flag to the CreateFile function whenever you create a file-mapping object for the file and then map the view of the file-mapping object. If you use this flag to open the file, FlushViewOfFile will return only when all of the file's data has been stored on the server's disk drive.

Keep in mind one special characteristic of the UnmapViewOfFile function. If the view was originally mapped using the FILE_MAP_COPY flag, any changes you made to the file's data were actually made to a copy of the file's data stored in the system's paging file. In this case, if you call UnmapViewOfFile, the function has nothing to update on the disk file and simply causes the pages in the paging file to be freed—the data is lost.

If you want to preserve the changed data, you must take additional measures yourself. For example, you might want to create another file-mapping object (using PAGE_READWRITE) from the same file and map this new file-mapping object into your process's address space using the FILE_MAP_WRITE flag. Then you could scan the first view looking for pages with the PAGE_READWRITE protection attribute. Whenever you found a page with this attribute, you could examine its contents and decide whether to write the changed data to the file. If you do not want to update the file with the new data, keep scanning the remaining pages in the view until you reach the end. If you do want to save the changed page of data, however, just call MoveMemory to copy the page of data from the first view to the second view. Because the second view is mapped with PAGE_READWRITE protection, the MoveMemory function will be updating the actual contents of the file on the disk. You can use this method to determine changes and preserve your file's data.

Windows 98
Windows 98 does not support the copy-on-write protection attribute, so you cannot test for pages marked with the PAGE_READWRITE flag when scanning the first view of the memory-mapped file. You will have to devise a method of your own for determining which pages in the first view you have actually modified.

Steps 5 and 6: Closing the File-Mapping Object and the File Object

It goes without saying that you should always close any kernel objects you open. Forgetting to do so will cause resource leaks while your process continues to run. Of course, when your process terminates, the system automatically closes any objects your process opened but forgot to close. But if your process does not terminate for a while, you will accumulate resource handles. You should always write clean, "proper" code that closes any objects you have opened. To close the file-mapping object and the file object, you simply need to call the CloseHandle function twice—once for each handle.

Let's look at this process a little more closely. The following pseudocode shows an example of memory-mapping a file:

 HANDLE hFile = CreateFile(...); HANDLE hFileMapping = CreateFileMapping(hFile, ...); PVOID pvFile = MapViewOfFile(hFileMapping, ...); // Use the memory-mapped file. UnmapViewOfFile(pvFile); CloseHandle(hFileMapping); CloseHandle(hFile); 

The above code shows the "expected" method for manipulating memory-mapped files. However, what it does not show is that the system increments the usage counts of the file object and the file-mapping object when you call MapViewOfFile. This side effect is significant because it means that we could rewrite the code fragment above as follows:

 HANDLE hFile = CreateFile(...); HANDLE hFileMapping = CreateFileMapping(hFile, ...); CloseHandle(hFile); PVOID pvFile = MapViewOfFile(hFileMapping, ...); CloseHandle(hFileMapping); // Use the memory-mapped file. UnmapViewOfFile(pvFile); 

When you work with memory-mapped files, you will commonly open the file, create the file-mapping object, and then use the file-mapping object to map a view of the file's data into the process's address space. Because the system increments the internal usage counts of the file object and the file-mapping object, you can close these objects at the beginning of your code and eliminate potential resource leaks.

If you will be creating additional file-mapping objects from the same file or mapping multiple views of the same file-mapping object, you cannot call CloseHandle early—you'll need the handles later to make the additional calls to CreateFileMapping and MapViewOfFile, respectively.

The File Reverse Sample Application

The FileRev application ("17 FileRev.exe") listed in Figure 17-2 demonstrates how to use memory-mapped files to reverse the contents of an ANSI or a Unicode text file. The source code and resource files for the application are in the 17-FileRev directory on the companion CD-ROM. When you start the program, the following window appears.

FileRev first allows you to select a file and then, when you click on the Reverse File Contents button, the function reverses all of the characters contained within the file. The program will work correctly only on text files; it will not work correctly on binary files. FileRev determines whether the text file is ANSI or Unicode by calling the IsTextUnicode function (discussed in Chapter 2).

Windows 98
In Windows 98, the IsTextUnicode function has no useful implementation and simply returns FALSE; calling GetLastError returns ERROR_CALL_NOT_IMPLEMENTED. This means that the FileRev sample application always thinks that it is manipulating an ANSI text file when it is run under Windows 98.

When you click on the Reverse File Contents button, FileRev makes a copy of the specified file called FileRev.dat. It does this so that the original file won't become unusable because its contents have been reversed. Next FileRev calls the FileReverse function, which is responsible for reversing the file; FileReverse calls the CreateFile function, opening FileRev.dat for reading and writing.

As I said earlier, the easiest way to reverse the contents of the file is to call the C run-time function _strrev. As with all C strings, the last character of the string must be a zero terminator. Because text files do not end with a zero character, FileRev must append one to the file. It does so by first calling GetFileSize:

 dwFileSize = GetFileSize(hFile, NULL); 

Now that you're armed with the length of the file, you can create the file-mapping object by calling CreateFileMapping. The file-mapping object is created with a length of dwFileSize plus the size of a wide character (for the zero character). After the file-mapping object is created, a view of the object is mapped into FileRev's address space. The pvFile variable contains the return value from MapViewOfFile and points to the first byte of the text file.

The next step is to write a zero character at the end of the file and to reverse the string:

 PSTR pchANSI = (PSTR) pvFile; pchANSI[dwFileSize / sizeof(CHAR)] = 0; 

In a text file, every line is terminated by a return character ('\r') followed by a newline character ('\n'). Unfortunately, when we call _strrev to reverse the file, these characters also get reversed. For the reversed text file to be loaded into a text editor, every occurrence of the "\n\r" pair needs to be converted back to its original "\r\n" order. This conversion is the job of the following loop:

 while (pchANSI != NULL) { // We have found an occurrence.... *pchANSI++ = '\r'; // Change '\n' to '\r'. *pchANSI++ = '\n'; // Change '\r' to '\n'. pchANSI = strchr(pchANSI, '\n'); // Find the next occurrence. } 

When you examine simple code like this, you can easily forget that you are actually manipulating the contents of a file on a disk drive (which shows you how powerful memory-mapped files are).

After the file has been reversed, FileRev must clean up by unmapping the view of the file-mapping object and closing all the kernel object handles. In addition, FileRev must remove the zero character added to the end of the file (remember that _strrev doesn't reverse the position of the terminating zero character). If you don't remove the zero character, the reversed file would be 1 character larger, and calling FileRev again would not reverse the file back to its original form. To remove the trailing zero character, you need to drop back a level and use the file-management functions instead of manipulating the file through memory mapping.

Forcing the reversed file to end at a specific location requires positioning the file pointer at the desired location (the end of the original file) and calling the SetEndOfFile function:

 SetFilePointer(hFile, dwFileSize, NULL, FILE_BEGIN); SetEndOfFile(hFile); 

NOTE
SetEndOfFile must be called after the view is unmapped and the file-mapping object is closed; otherwise, the function returns FALSE and GetLastError returns ERROR_USER_MAPPED_FILE. This error indicates that the end-of-file operation cannot be performed on a file that is associated with a file-mapping object.

The last thing FileRev does is spawn an instance of Notepad so that you can look at the reversed file. The following window shows the result of running FileRev on its own FileRev.cpp file.

click to view at full size.

 

Figure 17-2. The FileRev sample application

FileRev.cpp

 /****************************************************************************** Module: FileRev.cpp Notices: Copyright (c) 2000 Jeffrey Richter ******************************************************************************/ #include "..\CmnHdr.h" /* See Appendix A. */ #include <windowsx.h> #include <tchar.h> #include <commdlg.h> #include <string.h> // For _strrev #include "Resource.h" /////////////////////////////////////////////////////////////////////////////// #define FILENAME TEXT("FILEREV.DAT") /////////////////////////////////////////////////////////////////////////////// BOOL FileReverse(PCTSTR pszPathname, PBOOL pfIsTextUnicode) { *pfIsTextUnicode = FALSE; // Assume text is Unicode. // Open the file for reading and writing. HANDLE hFile = CreateFile(pszPathname, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { chMB("File could not be opened."); return(FALSE); } // Get the size of the file (I assume the whole file can be mapped). DWORD dwFileSize = GetFileSize(hFile, NULL); // Create the file-mapping object. The file-mapping object is 1 character // bigger than the file size so that a zero character can be placed at the // end of the file to terminate the string (file). Because I don't yet know // if the file contains ANSI or Unicode characters, I assume worst case // and add the size of a WCHAR instead of CHAR. HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, dwFileSize + sizeof(WCHAR), NULL); if (hFileMap == NULL) { chMB("File map could not be opened."); CloseHandle(hFile); return(FALSE); } // Get the address where the first byte of the file is mapped into memory. PVOID pvFile = MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, 0); if (pvFile == NULL) { chMB("Could not map view of file."); CloseHandle(hFileMap); CloseHandle(hFile); return(FALSE); } // Does the buffer contain ANSI or Unicode? int iUnicodeTestFlags = -1; // Try all tests. *pfIsTextUnicode = IsTextUnicode(pvFile, dwFileSize, &iUnicodeTestFlags); if (!*pfIsTextUnicode) { // For all the file manipulations below, we explicitly use ANSI // functions because we are processing an ANSI file. // Put a zero character at the very end of the file. PSTR pchANSI = (PSTR) pvFile; pchANSI[dwFileSize / sizeof(CHAR)] = 0; // Reverse the contents of the file. _strrev(pchANSI); // Convert all "\n\r" combinations back to "\r\n" to // preserve the normal end-of-line sequence. pchANSI = strchr(pchANSI, '\n'); // Find first '\n'. while (pchANSI != NULL) { // We have found an occurrence.... *pchANSI++ = '\r'; // Change '\n' to '\r'. *pchANSI++ = '\n'; // Change '\r' to '\n'. pchANSI = strchr(pchANSI, '\n'); // Find the next occurrence. } } else { // For all the file manipulations below, we explicitly use Unicode // functions because we are processing a Unicode file. // Put a zero character at the very end of the file. PWSTR pchUnicode = (PWSTR) pvFile; pchUnicode[dwFileSize / sizeof(WCHAR)] = 0; if ((iUnicodeTestFlags & IS_TEXT_UNICODE_SIGNATURE) != 0) { // If the first character is the Unicode BOM (byte-order-mark), // 0xFEFF, keep this character at the beginning of the file. pchUnicode++; } // Reverse the contents of the file. _wcsrev(pchUnicode); // Convert all "\n\r" combinations back to "\r\n" to // preserve the normal end-of-line sequence. pchUnicode = wcschr(pchUnicode, L'\n'); // Find first '\n'. while (pchUnicode != NULL) { // We have found an occurrence.... *pchUnicode++ = L'\r'; // Change '\n' to '\r'. *pchUnicode++ = L'\n'; // Change '\r' to '\n'. pchUnicode = wcschr(pchUnicode, L'\n'); // Find the next occurrence. } } // Clean up everything before exiting. UnmapViewOfFile(pvFile); CloseHandle(hFileMap); // Remove trailing zero character added earlier. SetFilePointer(hFile, dwFileSize, NULL, FILE_BEGIN); SetEndOfFile(hFile); CloseHandle(hFile); return(TRUE); } /////////////////////////////////////////////////////////////////////////////// BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) { chSETDLGICONS(hwnd, IDI_FILEREV); // Initialize the dialog box by disabling the Reverse button. EnableWindow(GetDlgItem(hwnd, IDC_REVERSE), FALSE); return(TRUE); } /////////////////////////////////////////////////////////////////////////////// void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) { TCHAR szPathname[MAX_PATH]; switch (id) { case IDCANCEL: EndDialog(hwnd, id); break; case IDC_FILENAME: EnableWindow(GetDlgItem(hwnd, IDC_REVERSE), Edit_GetTextLength(hwndCtl) > 0); break; case IDC_REVERSE: GetDlgItemText(hwnd, IDC_FILENAME, szPathname, chDIMOF(szPathname)); // Make a copy of input file so that we don't destroy it. if (!CopyFile(szPathname, FILENAME, FALSE)) { chMB("New file could not be created."); break; } BOOL fIsTextUnicode; if (FileReverse(FILENAME, &fIsTextUnicode)) { SetDlgItemText(hwnd, IDC_TEXTTYPE, fIsTextUnicode ? TEXT("Unicode") : TEXT("ANSI")); // Spawn Notepad to see the fruits of our labors. STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; TCHAR sz[] = TEXT("Notepad ") FILENAME; if (CreateProcess(NULL, sz, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { CloseHandle(pi.hThread); CloseHandle(pi.hProcess); } } break; case IDC_FILESELECT: OPENFILENAME ofn = { OPENFILENAME_SIZE_VERSION_400 }; ofn.hwndOwner = hwnd; ofn.lpstrFile = szPathname; ofn.lpstrFile[0] = 0; ofn.nMaxFile = chDIMOF(szPathname); ofn.lpstrTitle = TEXT("Select file for reversing"); ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST; GetOpenFileName(&ofn); SetDlgItemText(hwnd, IDC_FILENAME, ofn.lpstrFile); SetFocus(GetDlgItem(hwnd, IDC_REVERSE)); break; } } /////////////////////////////////////////////////////////////////////////////// INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog); chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand); } return(FALSE); } /////////////////////////////////////////////////////////////////////////////// int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) { DialogBox(hinstExe, MAKEINTRESOURCE(IDD_FILEREV), NULL, Dlg_Proc); return(0); } //////////////////////////////// End of File////////////////////////////////// 

FileRev.rc

 //Microsoft Developer Studio generated resource script. // #include "Resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_FILEREV ICON DISCARDABLE "FileRev.Ico" #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "Resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_FILEREV DIALOG DISCARDABLE 15, 24, 216, 46 STYLE WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "File Reverse" FONT 8, "MS Sans Serif" BEGIN LTEXT "&Pathname:",IDC_STATIC,4,4,35,8 EDITTEXT IDC_FILENAME,44,4,168,12,ES_AUTOHSCROLL PUSHBUTTON "&Browse...",IDC_FILESELECT,4,16,36,12,WS_GROUP DEFPUSHBUTTON "&Reverse file contents",IDC_REVERSE,4,32,80,12 LTEXT "Type of characters in file:",IDC_STATIC,88,34,80,8 LTEXT "(unknown)",IDC_TEXTTYPE,172,34,34,8 END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_FILEREV, DIALOG BEGIN RIGHTMARGIN, 192 BOTTOMMARGIN, 42 END END #endif // APSTUDIO_INVOKED #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED 



Programming Applications for Microsoft Windows
Programming Applications for Microsoft Windows (Microsoft Programming Series)
ISBN: 1572319968
EAN: 2147483647
Year: 1999
Pages: 193

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net