Mailslots are very useful if you want an easy way to exchange messages among processes on local machine or across a Windows LAN (including both NT and 9x based kernels). However, only one process at a time can be the owner of a server mailslot - the receiver - on the same machine. In this article, I describe an approach to share a server mailslot across different local processes.
Important note: since there are other articles on Code Project related to mailslots (such as this one), I'm not going to explain how they work again, even if - by disabling the sharing feature -
CSharedMailslot could behave as an easy wrapper of mailslots communication.
Why to share a server mailslot?
Any process on a LAN can have a defined server mailslot; when a client sends a broadcast message, all of them will receive it, regardless of the computer they reside on. However, only one process per computer can create and take the ownership of a mailslot, and any other subsequent attempt will fail. For example, if you developed an application which relies on mailslots, you can't have more than one mailslot-enabled instance executing on your machine. Think about a Windows Terminal Services scenario: the server mailslot will be created - then owned - by the application instance of the first user session only, while the others will receive an error.
The only solution you have here is to encapsulate the server mailslot in an NT Service, then all the application instances will communicate with it. This is a good architectural design, but there could be chances it can't be built. Then, a
CSharedMailslot could be the solution: just use it for wrapping mailslots messaging and forget the whole thing.
The idea behind the scene
Only one process can have the ownership of a given server mailslot, but such a process could create as many mailslots as it needs. The idea here is to create a secondary slave mailslot, unique of the owner process, but whose name could be estimated by the others (see Figure 2). Whenever the main mailslot receives a message, the owner process forwards it to all the other processes, by sending to their slave mailslot.
Figure 2 - the approached architecture
Say we have a
CSharedMailslot based application which uses a mailslot named
CPDEMO; we have three application instances in memory. By design, only one of the three can have the
CPDEMO ownership, then we call it the
instanceOwner. Whenever a remote process sends a message to our machine, it is received by the
instanceOwner; then, by reading the interprocess shared structure
ipcInstances, which contains the Thread ID of each running instance, the message is sent to the other two by constructing their slave mailslot name - derived from main mailslot name and their unique Thread ID - and sending through it.
CSharedMailslot instance checks for new messages both from the main mailslot - if it is the
instanceOwner - and the slave one. Whenever an
instanceOwner exits or crashes, one of the slaves elects itself as the new
About the demo
CSharedMailslot demo is an easy MFC application which demonstrates the usage of the shared mailslot class. Once decided the mailslot name, press open button to create it. If there is another demo instance running, you'll receive an error, unless both have the shared option checked. Once opened a mailslot, you can modify any option, then click on reopen to have it call the
Open() class methods.
The messenger format option is an extra not strictly related to the
CSharedMailslot class: if checked, the message is built by using the Windows Messenger format, enabling message exchanging between the two. The Messenger service defines a mailslot called
messngr, so you have to call it the same; even more, consider it doesn't use
CSharedMailslot :), so you can't define your own
messngr mailslot on the same machine: if you have the messenger service running, stop it.
Note: mailslots are just one of the Messenger service used protocols; replacing it is not in the scope of this article.
Use the other controls to send and receive messages; the to: field become from: once a message is received; however, it works only when the Messenger format is enabled, as - by design - mailslots does not provide sender name. A real application should poll the
Receive() method at a given interval.
Using the code
CSharedMailslot is a C++ class (there are no references to MFC). Using it is very easy; a good approach is to define it at class level:
class CSharedMailslotDemoDlg : public CDialog
then open it when needed:
if (mailslotServer.Open(true, "CPDEMO", NULL, true))
AfxMessageBox("could not open the mailslot", MB_OK+MB_ICONEXCLAMATION);
The first parameter,
isMailslotServer, specifies whether it has to behave as a server (the receiver,
true) or client (the sender,
mailslotName is well explained, while
destinationName is required if it's a client, otherwise pass it a
shared, is the whole thing about: set it to
true to enable the interprocess sharing feature. Note that since the implemented sharing method works on NT platforms only, the option is ignored if the class is running on any 9x system.
Use the following call to read a message:
DWORD bufferSize=0, messagesWaiting=0;
if (mailslotServer.Read(buffer, bufferSize, messagesWaiting))
AfxMessageBox("could not read the mailslot", MB_OK+MB_ICONEXCLAMATION);
bufferSize are handled by the class; once called the
Read() method, if
bufferSize is > 0, then
buffer contains a new received message.
messagesWaiting contains the number of messages still in the queue.
Sending a message is easier; just make a direct call:
sendMailslot.Write(buffer, strlen(buffer), "CPDEMO", "MATRODESKTOP");
The method will open, then close the mailslot for you. It is also possible to
Open() a client type mailslot, then repeatedly call the
Write() method for each message to be sent.
Once used, call the
Close() method to release all the stuff and to notify this to the other processes.
Exploring the code
The interprocess (IPC) shared structure
ipcInstances is implemented through a memory mapped file (see this good [^] article on Code Project for more). In order to share such a mapped file across different processes - including those of NT Services - we have to deal with security through the
SetNamedSecurityInfo(), included in the ADVAPI32.DLL library. The DLL is loaded dynamically because it's supported on NT kernels only, while I like my class to run on any Win32 platform even if it would just behave as a mailslot wrapper:
libADVAPI32 = LoadLibrary( _T("advapi32.dll") );
SetNamedSecurityInfo = (LPSETNAMEDSECURITYINFO)
GetProcAddress( libADVAPI32, "SetNamedSecurityInfoA" );
Most of the class work is done in the
Open() method, as it initializes the library mentioned above, mailslots and IPC structures names, memory mapped file and, of course, the mailslots. Like all the other functions, a proper flag check will let the code to handle a shared or single scenario.
mailslotName parameter in the
Open() method will determine all the names used by the class; by providing the
CPDEMO name, the
BuildSharedNames() method builds the following names:
- CPDEMO -
CSharedMailslot instance name; this is also the public mailslot name
- Global\CSharedMailslotCPDEMO - IPC memory mapped file
- CSharedMailslotCPDEMOMutex - mutex used for thread synchronization (see below)
- Global\C\SharedMa\ilslotCP\DEMO71b - the internal slave mailslot (71b is a Thread ID example)
The Global prefix for (2) is used to enable cross-session IPC sharing when Windows Terminal Services is enabled (this is checked by the
isSharedAllowed(), which eventually returns
CSHAREDMAILSLOT_SYSREQ_OKNOWTS). Note that WTS is also used for the Windows XP Fast User Switching feature.
The name for (4) is derived from (2) plus the current Thread ID; since mailslot names cannot exceed eight characters on 9x platforms, the
BuildSharedMailslotName() method periodically adds a '\' char to transform the plain name to a folder path, which is supported indeed. This workaround is hidden to the class consumer, as the slave mailslot is only used internally.
Once built all the names and defined the memory mapped file, the
Open() method scans the shared
ipcInstancesView structure for an empty Thread ID placeholder - i.e., the first
instances array element which contains 0 - and adds itself. If there is no defined
instanceOwner, then it declares itself. It's worth noting that whenever the class has to read or write the IPC shared structure, it first locks the
ipcMutex mutex by calling the
WaitForSingleObject() API. This is required in order to prevent memory corruption due to not synchronized calls. The mutex is used as a semaphore (read a good article about Thread Synchronization here on Code Project for more):
if (WaitForSingleObject(ipcMutex, CSHAREDMAILSLOT_IPC_WAIT)==WAIT_TIMEOUT)
The rest of the code handles the logic of the sharing approach; further details were included inside as comments.
It should be kept in mind that a message is received by all the
CSharedMailslot instances with the same defined name. This is slightly different from how the Windows Messenger service works, as it displays the message to one user session only. Since the selected session has no special meaning (typically, the first who logged on) as the message was sent to the whole workstation, I preferred to let all the instances to receive it. However, if you want to be aligned with Windows Messenger behavior, just comment the
Forward() call in the
CSharedMailslot implementation covered all my needs, and it's currently used on the latest beta of my freeware application RealPopup. Even if it's going to be released in the real world, it could be extended more: for example, it currently doesn't support callbacks, so you have to poll it to check for new messages.
- 07.Sep.2004 - 0.001 - first build for RealPopup build 149: IPC not implemented yet.
- 09.Nov.2004 - 1.004 - first public release (included in RealPopup build 155).
- 02.Dec.2004 - 1.006 - released for Code Project.