November 1998
Download Nov98Wicked.exe (82KB)
Jeff Prosise is the author of Programming Windows 95 with MFC (Microsoft Press, 1996). He also teaches Visual C++®/MFC programming seminars. For more information, visit
http://www.solsem.com.
|
Windows®-based programming is full of myths.
One is that it's difficult to allocate shared memorymemory that's visible to two or more processesin Win32®-based applications.
In truth, shared memory is a breeze to allocate and use; you just have to know how. The secret is a pair of API functions named CreateFileMapping and MapViewOfFile. CreateFileMapping creates file mapping objects that permit files to be browsed as easily as memory. MapViewOfFile converts file mapping object handles into pointers. Passing CreateFileMapping a file handle equal to 0xFFFFFFFF creates a file mapping object that's backed by the system's paging file. Calling MapViewOfFile on the resultant file mapping object is a cheap and effective way to access a region of memory from multiple processes. Aside from a few well-known pitfalls, such as passing in shared memory a pointer to an item elsewhere in shared memory (such pointers require special handling because shared memory may be mapped to a different address in each process that it's mapped into), shared memory could hardly be easier. But it would be simpler if the semantics for allocating and using blocks of shared memory were encapsulated in a reusable C++ class. Encapsulation could also eliminate some sources of resource leaks, such as the failure to call UnmapViewOfFile on a pointer returned by MapViewOfFile. |
Figure 1 Share |
Writing a class that provides an interface to shared memory isn't rocket science. Share, the sample program shown in Figures 1 and 2, features an MFC-style class named CSharedMemory that's perfect for programming projects that require shared memory. You can see CSharedMemory at work by running two instances of the application. Type a string of text into the box next to the Put button in one instance and click Put. Then click the Get button in the other instance. The text you typed in the first instance should appear in the second instance. This transfer is performed using a block of shared memory that's created when each process is started.
Creating and Initializing CSharedMemory Objects
The first parameter passed to CSharedMemory's constructor is the size (in bytes) of the shared memory block; the second is the object name. Share guards against possible out-of-memory errors by enclosing the call to new in a try/catch handler. CSharedMemory throws an MFC CMemoryException if its constructor fails.
CSharedMemory also supports the creation of uninitialized shared memory objects and the recycling of existing ones. To create an uninitialized shared memory object, use CSharedMemory's default constructor, like this:
Then call CSharedMemory::Create to initialize the object:
If you want to recycle the object, call CSharedMemory::Delete on it before calling Create a second time:
There's no need to call Delete explicitly if the object instance won't be recycled because CSharedMem-ory's destructor will call it for you.You may specify NULL for the object name (or simply omit the object name altogether) when creating or initializing a CSharedMemory object, and CSharedMemory will pick a name for you. The name is guaranteed to be unique because CSharedMemory uses a stringified form of the value returned by COM's CoCreateGuid function to form the object name. To retrieve the name, call CSharedMemory:: GetName:
To connect to this shared memory object from another process, you must use some form of IPC to supply the object name to the other process. Using the same object name in two or more processes is the only way to join two CSharedMemory objects across process boundaries.
In some cases, the first process to allocate a block of shared memory needs to initialize it further by copying data to it. So that callers can determine whether a newly constructed CSharedMemory object allocated a new block of shared memory (that is, created a new file mapping object) or connected to an existing one, CSharedMemory includes a member function named MeFirst. MeFirst returns nonzero if and only if the calling process was the first to create the corresponding shared memory object. Thus, the code to create and fully initialize a shared memory object might look like this:
The value returned by MeFirst comes from a member variable that is initialized following a call to GetLastError during the shared memory object's creation. GetLastError returns ERROR_ALREADY_EXISTS if CreateFileMapping returns a handle to an existing file mapping object. MeFirst returns FALSE if GetLastError returned ERROR_
ALREADY_EXISTS, or TRUE if it returns anything else.
Reading and Writing Shared Memory
The cast is required because p is a void*.
One drawback to using pointers for memory access is the lack of protection against accidental buffer overruns. For this reason, CSharedMemory provides alternative methods for reading and writing shared memory in the form of member functions named Read and Write. If you add the beginning offset for a read or write operation to the size of the data being read or written and compare the result to the size of the shared memory buffer, both functions can prevent illicit reads and writes. The following code snippet uses CSharedMemory::Read to copy a DATASTRUCT from shared memory:
This code is functionally equivalent to the sample code shown in the previous paragraph.Both Read and Write return BOOLs that indicate whether the operation succeeded or failed. A zero (FALSE) return indicates that no data was read or written because the starting offset specified a location that lay beyond the end of the shared memory block. A nonzero (TRUE) return means that a read or write was performed, either in full or in part. It does not mean that the number of bytes read or written matches the requested byte count. If, for example, an application attempted to write 10 bytes to a location just two bytes short of the end of the shared memory buffer, Write would return true but set the variable passed by address in Write's third parameter to 2.
Synchronizing Reads and Writes
Calls to CSharedMemory::Read and CSharedMemory::
Write must also be bracketed in this manner to ensure thread-safe access.
Have a tricky issue dealing with Windows? Send your questions via email to Jeff Prosise: JeffPro@msn.com |
From the November 1998 issue of Microsoft Systems Journal.