Windows is not only drawing of course. It has come a long way since windows 3.0, and is now a sophisticated operating system. You can do things like memory-mapped files for instance, that formerly were possible only under UNIX. Yes, "mmap" exists now under windows, and it is very useful.
Memory mapped files allow you to see a disk file as a section of RAM. The difference between the disk and the RAM disappears. You can just seek to a desired position by incrementing a pointer, as you would do if you had read the whole file into RAM, but more efficiently. It is the operating system that takes care of accessing the disk when needed. When you close the connection, the operating system handles the clean up.
Here is a small utility that copies a source disk file to a destination using memory-mapped files.
int main (int argc, char **argv)
pszSrcFileName = argv[argc-2]; // Src is second to last argument
pszDstFileName = argv[argc-1]; // Dst is the last argument
We open the source file for reading only, and make it available for other processes
even if we are copying it. We demand to the operating system to ensure that the file
exists already
hSrcFile = CreateFile (pszSrcFileName,
GENERIC_READ, //Source file is opened for reading only
FILE_SHARE_READ,// Shareable
0, OPEN_EXISTING, 0, 0);
if (INVALID_HANDLE_VALUE == hSrcFile)
/* We open the destination file for reading and writing with no other access allowed.
We demand the operating system to create the file if it doesn't exist.
*/
hDstFile = CreateFile (pszDstFileName,
GENERIC_READ|GENERIC_WRITE, 0,
0, CREATE_ALWAYS, 0, 0);
if (INVALID_HANDLE_VALUE == hDstFile)
/*
We need to query the OS for the size of this file. We will need this information later when we create the file-mapping object. Note that we receive a 64 bit number splitted in two. We receive a 32-bit integer containing the result's lower 32 bits, and we pass to the function the address where it should put the remaining bits! Well, if you find this interface strange (why not return a 64 bit integer?) please do not complain to me. Note too the strange form of the error checking afterwards: we check for a return value of all bits set to one, and check the result of the GetLastError() API.
*/
SetLastError(0);
liSrcFileSize.LowPart = GetFileSize(hSrcFile,
&liSrcFileSize.HighPart)
if (0xFFFFFFFF == liSrcFileSize.LowPart &&
GetLastError() != NO_ERROR)
/*
Special case: If the source file is zero bytes, we don't map it because there's no need to and anyway CreateFileMapping cannot map a zero-length file. But since we've created the destination, we've successfully "copied" the source.
*/
if (0 == liSrcFileSize.QuadPart)
/*
Map the source file into memory. We receive from the OS a HANDLE that corresponds to the opened mapping object.
*/
hSrcMap = CreateFileMapping (hSrcFile,
0, PAGE_READONLY, 0, 0, 0);
if (!hSrcMap)
/*
Now we create a file mapping for the destination file using the size parameters we got above.
*/
hDstMap = CreateFileMapping (hDstFile, 0,
PAGE_READWRITE,
liSrcFileSize.HighPart,
liSrcFileSize.LowPart, 0);
if (!hDstMap)
/*
Now that we have the source and destination mapping objects, we build two map views of the source and destination files, and do the file copy.
To minimize the amount of memory consumed for large files and make it possible to copy files that couldn't be mapped into our virtual address space entirely (those over 2GB), we limit the source and destination views to the smaller of the file size or a specified maximum view size (MAX_VIEW_SIZE--which is 96K).
If the size of file is smaller than the max view size, we'll just map and copy it. Otherwise, we'll map a portion of the file, copy it, then map the next portion, copy it, etc. until the entire file is copied.
MAP_SIZE is 32 bits because MapViewOfFile requires a 32-bit value for the size of the view. This makes sense because a Win32 process's address space is 4GB, of which only 2GB (2^31) bytes may be used by the process. However, for the sake of making 64-bit arithmetic work below for file offsets, we need to make sure that all 64 bits of liMapSize are initialized correctly.
*/
liBytesRemaining.QuadPart = liSrcFileSize.QuadPart;
/* This assignment sets all 64 bits to this value
liMapSize.QuadPart = MAX_VIEW_SIZE;
do
while (liBytesRemaining.QuadPart > 0);
fResult = SUCCESS;
DONE:
/*
We are done, Note the error treatment of this function. We use gotos to reach the end of the function, and here we cleanup everything.
*/
if (hDstMap) CloseHandle (hDstMap);
if(hDstFile!=INVALID_HANDLE_VALUE) CloseHandle(hDstFile);
if (hSrcMap) CloseHandle(hSrcMap);
if (hSrcFile != INVALID_HANDLE_VALUE)
CloseHandle (hSrcFile);
if (fResult != SUCCESS)
return (fResult);
Summary:
To get a pointer to a memory mapped file do the following:
Open the file with CreateFile
Create a mapping object with CreateFileMapping using the handle you receive from CreateFile.
Map a portion (or all) of the file contents, i.e. create a view of it, with MapViewOfFile.
A common task in many programming situations is to let the user find a folder (directory) in the file system hierarchy. When you want to search for certain item, for instance, or when you need to allow the user to change the current directory of your application. The windows shell offers a ready-made set of functions for you, and the resulting function is quite short. Let's first see its specification, i.e. what do we want as an interface to this function.
Required is a character string where the result will be written to, and a title for the user interface element. The result should be 1 if the path contains a valid directory, 0 if there was any error, the user cancelled, whatever.
To be clear about the specifications let's look at this example:
int main(void)
else printf("action cancelled\n");
return 0;
How do we write "BrowseDir" in windows?
Here it is:
#include <shlobj.h>
#include <stdio.h>
int BrowseDir(unsigned char *Title,char *result)
}
pMalloc->lpVtbl->Release(pMalloc); (15)
return r;
Small isn't it?
Let's see the gory details.
We need a local variable that will hold a pointer to a shell defined function that will allocate and release memory. The shell returns us a result that needs memory to exist. We need to free that memory, and we have to take care of using the same function that the shell uses to allocate memory. This pointer to an interface (the malloc interface) will be in our local variable pMalloc.
The shell needs information about the environment, and some pointers to put the results of the interaction with the user. We will see more of this when we fill this structure below.
The shell uses a lot of stuff, and we have to take care to avoid filling our brain with unending details. What is an ITEMLIST? Actually I haven't even bothered to read the docs about it, since the only use I found is to pass it around to other shell functions.
The result of the function is initialized to zero, i.e. we will set this result to 1 only and only if there is a valid path in the buffer.
OK. Here we start. The first thing to do then, is to get the pointer to the shell allocator. If anything goes wrong there, there is absolutely nothing we can do and the best thing is to return immediately with a FALSE result.
We have to clean up the structure (note that this is a local variable, so its contents are as yet undefined). We use the primitive memset and set all members of the structure to zero. This is a common idiom in C: clear all memory before using and assign to it a known value. Since the default value of many structure members is zero, this easies the initialization of the structure since we do not have to explicitly set them to NULL or zero.
We start the filling of the relevant members. We need to put the owner window handle in the hwndOwner member. We get the handle of the current active window using a call to a windows API.
The shell needs place to store the display name of the chosen path. Note that this is not what we want (the full path) but just the last item in the path, i.e. the last directory. Why this is so? Because the user could choose a path like "My documents", and in this case we could detect that the user has chosen a "standard well known name" for the directory. But we do not use this feature, and just give the shell some place in our. result variable. Since we overwrite this data later, this is harmless and avoids a new variable.[2]
We set the lpszTitle member to the value we passed in. This is a text that will be displayed at the top of the user interface window that appears, and should remain the user what kind of folder he/she is looking for.
As flags we just pass BFID_USENEWUI, meaning that we want the shell to use the improved user interface, with drag and drop, new folder, the possibility of deleting folders, whatever.
And we are done with the filling of the structure! We just have to call our famous SHBrowseForFolder, and assign the result to ItemIdList. Here is an image of the user interface display that appears in a windows 2000 machine; in other versions of windows it will look different. The user interface is quite sophisticated, and it is all at our disposal without writing any code (well almost!). What is better; even if we had spent some months developing a similar thing, we would have to maintain it, test it, etc. Note that you can transparently browse the network, give a symbolic path like "My computer" or other goodies.
If the call worked, i.e. if the user interface returns a valid pointer and not NULL, we should translate the meaningless stuff we receive into a real path, so we call SHGetPathFromIDList, to do exactly that. We pass it a pointer to the result character string that we receive as second argument.
If that function returns OK, we verify that the returned path is not empty, and if it is, we set the result of this function to TRUE.
Now we have to clean-up. We use the COM interface pointer we received from SHGetMalloc, and use its only member (lpVtbl) to get into the Free function pointer member. There are other function pointers in that structure for sure, but we do not use them in this application. We pass to that Free function a pointer to the interface it gave to us, and then the object to free.
When we are done with a COM interface we have to remember to call a function to release it, passing it again a pointer to the returned object. We are done now, we return the result and exit.
How can this program fail?
There are only three API calls here, and all of them are tested for failure. In principle there is no way this can fail, although it could fail for other reasons: it could provoke a memory leak (for instance if we had forgotten the call to the Release method at the end), or it could use resources that are never released (the list of identifiers that we obtain from the system, etc
ULARGE_INTEGER is defined in the windows headers like this:
typedef union _ULARGE_INTEGER ;
long long QuadPart;
} ULARGE_INTEGER,*PULARGE_INTEGER;
The union has two members: a anonymous one with two 32 bit integers, and another with a long long integer, i.e. 64 bits. We can access the 64-bit integer's low and high part as 32 bit numbers. This is useful for functions returning two results in a 64 bit number.
|