Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CSharedMailslot: a shared server mailslot

0.00/5 (No votes)
1 Dec 2004 1  
An interprocess communication approach to share a server mailslot.

Sample Image - CSharedMailslot1.gif

Introduction

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.

A 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 instanceOwner.

About the demo

The 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 Close(), then 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
{
protected:
  CSharedMailslot mailslotServer;
};

then open it when needed:

if (mailslotServer.Open(true, "CPDEMO", NULL, true))
{
   // OK

}
else
{
   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, false); mailslotName is well explained, while destinationName is required if it's a client, otherwise pass it a NULL.

Last parameter, 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:

char *buffer=NULL;
DWORD bufferSize=0, messagesWaiting=0;

if (mailslotServer.Read(buffer, bufferSize, messagesWaiting))
{
   if (bufferSize>0)
   {
      // a new received message is in the buffer

   }
}
else
{
   AfxMessageBox("could not read the mailslot", MB_OK+MB_ICONEXCLAMATION);
}

Both buffer and 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") );
if (!libADVAPI32)
   return false;

SetNamedSecurityInfo = (LPSETNAMEDSECURITYINFO) 
        GetProcAddress( libADVAPI32, "SetNamedSecurityInfoA" ); 
if (!SetNamedSecurityInfo)
   return false;

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.

The 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:

  1. CPDEMO - CSharedMailslot instance name; this is also the public mailslot name
  2. Global\CSharedMailslotCPDEMO - IPC memory mapped file
  3. CSharedMailslotCPDEMOMutex - mutex used for thread synchronization (see below)
  4. 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):

// the CSHAREDMAILSLOT_IPC_WAIT is an arbitrary timeout value

if (WaitForSingleObject(ipcMutex, CSHAREDMAILSLOT_IPC_WAIT)==WAIT_TIMEOUT)
{
   // close anything declared, then exit

}

while (counter<CSHAREDMAILSLOT_SLOTS_MAX)
{
   if (ipcInstancesView->instances[counter]==0)
   {
      ipcInstancesView->instances[counter]=GetCurrentThreadId();
      instanceIndex=counter;

      if (ipcInstancesView->instanceOwner==0)
      ipcInstancesView->instanceOwner=counter;
      break;
   }

   counter++;
}

ReleaseMutex(ipcMutex);

The rest of the code handles the logic of the sharing approach; further details were included inside as comments.

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 Read() method.

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.

History

  • 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here