Windows CE .NET Interprocess Communication

Windows CE .NET
 

Avi Kcholi
Sela Software Labs
Israel
Microsoft

January 2004

Applies to:
     Microsoft® Windows® CE .NET

Abstract

Windows CE .NET provides a rich set of synchronization objects; however, interprocess communications (IPC) or interthread communications (referred to collectively as intertask communications) are provided only by point-to-point message queue. This is a user-defined queue that acts as a conduit for passing messages between a writer and a reader. An embedded operating system requires special attention to space and performance, so the Windows CE .NET point-to-point message queue may act as a pipe mechanism for interprocess communications. The majority of embedded real-time operating systems (RTOSs) have an array of intertask communications, such as mailboxes and pipes. Because a point-to-point message queue can be regarded as some sort of pipe, an implementation of a one-way IPC mechanism (a mailbox) is proposed.

There is a proposal to create this mechanism as a service that is loaded and managed by Services Manager. This design strategy will offer lightweight interprocess and interthread communications that are wrapped in a very small package. It will conserve memory and and provide extremely fast performance.

Contents

Design Goals
Mailbox API
Mailbox Service
IOCTL Codes
Testing

Design Goals

The design goals for a message mailbox address the need for a one-way, lightweight, intertask messaging mechanism that is efficient, fast, and broadcasting. The five design goals are:

  • One-way messaging.
  • Efficient—fixed-size, short messages of agreed contents.
  • Fast—low overhead, notifying of the posting of a message, and supporting time-out on messages.
  • One instance of the service to save on thread spawning.
  • Broadcasting.

Because of these design goals, this article will discuss creating a mailbox solution. The Microsoft® Windows NT® family offers a similar mechanism, called a mailslot. Like other systems that offer an IPC mechanism, the mailbox server creates the mailbox and waits for mailbox clients to post messages to it. This process mimics the posting of letters to your mailbox at home. The mailbox needs the capacity to contain as many messages as its clients. This violates the third design goal, low overhead, because each client or subscriber posts a message to this mailbox. Therefore, even if the size of a message is confined to 64 bits (as in this implementation of a mailbox solution), 10 clients, for example, will cause the specific mailbox to have a buffer of 640 bits. If this occurs, memory usage of the service will dramatically increase, which an embedded system cannot afford. Performance will suffer also because the mailbox owner will have to iterate the mailbox buffer and read messages.

If you want to keep memory usage to a minimum and you have real-time reaction to messages, you should reverse the roles of the mailbox owner and subscriber. In other words, the mailbox owner creates the mailbox, and only the mailbox owner posts messages. A subscriber is a task that opens the mailbox for reading. When a message is posted to the mailbox, the subscriber is notified and reads the message.

This scenario also satisfies the fifth design goal, broadcasting (one server, many subscribers). In the future, broadcasting could be extended to sending messages to other devices—for example, using Message Queuing (also known as MSMQ) to distribute messages. The following diagram shows the basic architecture of the mailbox system.

Figure 1. Mailbox message passing

Mailbox API

The Mailbox application programming interface (API) is designed to allow a mailbox owner to create a named mailbox, write messages to it, and then close the mailbox. You need three functions to accomplish these tasks:

  • A function to allow subscribers to attach and detach from a named mailbox
  • A function to read a message that is in the mailbox
  • A function to query the state of the mailbox

Because the API is exposed by the same dynamic-link library (DLL) that the service resides in, you must remember that loading the DLL into a process that is going to use it means that the data of the service is not shared with the instance that is loaded by the process. Remember that the DLL is loaded into the address space of the process that is going to use it, whereas the service is in the address space of Services Manager (Services.exe). Having shared memory in the service DLL requires allocating a file mapping object, and this is something that you should avoid for size and performance reasons. Therefore, you should use the mailbox name rather than a handle to manipulate the service. When you do this, the mailbox can communicate between separate processes without having to duplicate handles. All functions are reentrant.

Within the implementation of the Mailbox API, call the CreateFile function to open the single-instance mailbox service and call the DeviceIoControl function with the appropriate input/output control (IOCTL) code to perform the requested operation. The DeviceIoControl function accepts an input structure that is optionally filled with the data that is needed to perform the task. The following code details the contents of the structure passed to the IOCTL.

typedef struct _DEVIOCTLINPUT {
   TCHAR strName[4];
   DWORD dwMsg[2];
   BOOL  bfIsMsg;
   DWORD dwOwnersKey;
   DWORD dwTimeOut;
}DEVIOCTLINPUT

You can use the IpcMbxCreateMailbox(LPCTSTR lpName, DWORD *pMsg, DWORD dwTimeout) function to create a mailbox. This function returns a DWORD value that contains a mailbox owner's key. Note that a DWORD value of zero (0) indicates that the mailbox was not created. The mailbox may not have been created because of either memory allocation problems or the existence of a mailbox with the same name. The following code provides an example of how to create a mailbox.

DWORD dwMyKey = 0;
DWORD Msg[2] = {1, 2};
HANDLE hService = LoadLibraryEx(L"mailbox.dll", NULL,
 DONT_RESOLVE_DLL_REFERENCES);
dwMyKey = IpcMbxCreateMailbox(L"MBX0", (DWORD*)Msg, 500);

This code example creates a mailbox that is called "MBX0"; however, you can use any four-character name. If the person who created the mailbox creates a message that has a DWORD value of 1 and a DWORD value of 2, the mailbox should time out after 500 milliseconds. The mailbox will reset notification after the time-out period, no matter how many subscribers there are. A value of INFINITE will not time out.

The IpcMbxCreateMailbox function opens the service and calls the DeviceIoControl function with the IOCTL_MBX_CREATE_MAILBOX parameter to do most of the work. The return buffer holds the mailboxes owner's key. This key is crucial to the owner; without it, the owner cannot write or close the mailbox. The following code example shows how to use DeviceIoControl to create the mailbox.

DeviceIoControl(hMbox, IOCTL_MBX_CREATE_MAILBOX, (PDEVIOCTLINPUT)pInBuff, 
sizeof(DEVIOCTLINPUT), &dwRetOK, 4, &dwOutLen, NULL);

The mailbox owner uses the IpcMbxWriteMailbox(LPCTSTR lpName, DWORD* pMsg, DWORD dwCount, DWORD dwOwnerKey) function, and only the mailbox owner can post a message to the mailbox. The function returns a Boolean value to indicate success or failure. Its implementation is similar to the create function; it opens the service and calls the DeviceIoControl function with the IOCTL_MBX_WRITE parameter. However, before it returns, it checks that the message was fully written to the mailbox. The primary feature of this function is to verify that the message was written in full. The following code sample shows how writing to the mailbox is handled.

EnterCriticalSection(&CritSect);
   
hMbox = CreateFile(L"MBX0:",0,0,NULL,OPEN_EXISTING,0,NULL);
if (hMbox != INVALID_HANDLE_VALUE){
   pInBuff = BuildInputBuffer(lpName, pMsg, dwOwnerKey, 0);
   if (pInBuff == NULL)
      return FALSE;
DeviceIoControl(hMbox,IOCTL_MBX_WRITE,(PDEVIOCTLINPUT)pInBuff,
      sizeof(DEVIOCTLINPUT), NULL, 0, &dwOutLen, NULL);
   VirtualFree(pInBuff, sizeof(DEVIOCTLINPUT), MEM_RELEASE);
   CloseHandle(hMbox);
}

if (dwOutLen == dwCount)
   bRet = TRUE;

LeaveCriticalSection(&CritSect);

The mailbox owner uses the IpcMbxCloseMailbox(LPCTSTR lpName, DWORD dwOwnerKey) function to close the mailbox. Only the owner can close the mailbox by using the owner key. The implementation is similar to the preceding functions, but the data input to the DeviceIoControl function and the IOCTL code is different.

A subscriber can use the IpcMbxSubscribeToMailbox(LPCTSTR lpName, BOOL bAttach) function to open and register with a mailbox, or to detach from a mailbox. The return value is a valid handle to the mailbox. A subscriber can block on this handle until the server inserts a message. The implementation of this function:

  • Opens an event by using a concatenation of the mailbox name and the string "Event". For example, if the caller attaches to a mailbox named "mbx3," the event name will be "mbx3Event."
  • Has conditional processing on the bAttach flag that is passed as an argument in the call.

The following example code shows how subscribers are attached to the mailbox.

EnterCriticalSection(&g_CritSect);
// Generate an event name
memcpy(strEventName, lpName, 8);

hMbox = CreateFile(L"MBX0:",0,0,NULL,OPEN_EXISTING,0,NULL);
if (hMbox != INVALID_HANDLE_VALUE){
   hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, strEventName);
   pInBuff = BuildInputBuffer(lpName, NULL, 0, 0);
   if (pInBuff == NULL)
      return 0;
   if (bAttach == TRUE)
bfRet = DeviceIoControl(hMbox, IOCTL_MBX_ATT_SUBSCRIBER, pInBuff,
 sizeof(DEVIOCTLINPUT), NULL, 2, &dwOutLen, NULL);
   else 
   bfRet = DeviceIoControl(hMbox, IOCTL_MBX_DETACH_SUBSCRIBER, pInBuff,
 sizeof(DEVIOCTLINPUT), NULL, 2, &dwOutLen, NULL);

   VirtualFree(pInBuff, sizeof(DEVIOCTLINPUT), MEM_RELEASE);
   CloseHandle(hMbox);
}
LeaveCriticalSection(&g_CritSect);

return hEvent;

The IpcMbxQueryMailboxState(LPCTSTR lpName) function is used to determine whether a mailbox is empty. This function returns a mailbox state structure.

The IpcMbxReadMailbox(LPCTSTR lpName, DWORD* pMsg, DWORD dwCount) function is used by a mailbox subscriber to read a message from a specific mailbox. This function returns a Boolean value to indicate success or failure.

Note that either of the read or write operations addresses the mailbox by its name.

Mailbox Service

The design goals for the message mailbox call for a one-way, lightweight, intertask messaging mechanism that is efficient, fast, and broadcasting. The mailbox service should be implemented as a single-instance service, which means that only one instance of the service will run within a single thread. When this occurs, many context switches do not need to be used. The service will be responsible for managing an array of mailboxes. Therefore, each time a mailbox server thread needs to create a new mailbox, the mailbox service will create and manage a new mailbox resource. This resource will be a mailbox structure. The IpcMbxSubscribeToMailbox function is used to subscribe to a mailbox, and the IpcMbxReadMailbox function is called by a subscriber to read the mailbox.

The structure should include a DWORD value to indicate the size of the structure, a string to hold the name of the mailbox, an event handle, and a message. This implementation of a mailbox service contains two DWORD values. These values provide an efficient way to convey 64 bits of information in a message, or they provide a pointer to a buffer that contains a larger message. The larger message allows a 64-bit pointer if needed. This structure could look like the following structure.

typedef _MAILBOX {
DWORD dwSize;
HANDLE hEvent;
LPCTSTR strName[4];
DWORD dwMsg[2];
UINT nSubsCount;
DWORD dwTimeOut;
   BOOL bfReadByAll_orTimedOut;
   UINT nReadCount;
   DWORD dwOwnersKey; 
} MAILBOX *PMAILBOX;

The naming convention is not strict, as long as you use either letters or numerals. Note that the name is case sensitive. To save memory, we recommend that you use a name that is no longer than four characters.

When a mailbox is created (by calling the IpcMbxCreateMailbox function), the mailbox service first checks that no mailbox of the same name exists. The service then:

  1. Reallocates the memory that is required for the mailboxes array.
  2. Generates a unique 32-bit owner's key.
  3. Creates an event object.
  4. Fills in the name.
  5. Fills in the message (this is optional).
  6. Returns the resulting owner's key if successful or a value of 0 if unsuccessful.

The buffer that holds a list of structures that represent exclusive instances of mailboxes is managed by allocating and reallocating the buffer, and by using the size field to control the structure and the lookup mechanism on the list. We recommend that you limit the mailbox instances to 16.

The owner should use the IpcMbxCloseMailbox function to close the mailbox by using the name and unique owner's key. This method ensures that memory will be released and prevents performance deterioration. The IpcMbxCloseMailbox function double-checks the name of the mailbox against the owner's key. If the two match, the function will extract the given mailbox from the mailboxes array, close its event handle, and decrement the mailbox counter.

A subscriber will use the IpcMbxSubscribeToMailbox function to open and register with the mailbox. The return value would be a valid handle to the mailbox. A subscriber will be able to block on this handle until the server inserts a message, which is waiting on the Mailbox API by using a system function, such as the WaitForSingleObject function.

Because each mailbox is named, this mechanism allows interprocess and interthread communications. This implementation seems to be efficient in terms of both memory usage and performance.

Like a stream device driver, a service must implement a fixed set of functions. For example, a service would implement functions like xxx_Init, xxx_Open, and others. Most of these functions have no implementation in the mailbox service. However, some need to be implemented, or the service will fail. The MBX_Init, MBX_deinit, MBX_Open, and MBX_IOControl functions have been implemented.

Services Manager calls the MBX_Init function to initialize the service, and the implementation of this function sets up the memory needed for the mailbox structure array. If the function succeeds, it returns a virtual pointer to the memory allocation; otherwise, it returns 0. The following code example shows how the service is initialized.

DWORD MBX_Init(DWORD dwContext)
{
    DWORD dwRet = 0;
   DWORD size = sizeof(MAILBOX);
   DEBUGMSG(DEBUGZONE(DBG_INIT),(TEXT("MAILBOX: MBX_Init\n")));
g_pMailBoxes = (PMAILBOX)VirtualAlloc(NULL, size, MEM_COMMIT,
 PAGE_READWRITE); 
   if (g_pMailBoxes == NULL)
      return dwRet;
   g_pMailBoxes->dwSize = size;
   dwRet = (DWORD)g_pMailBoxes;
   g_nMbxCount = 0;
   return dwRet;

The MBX_deinit function is just as straightforward in its implementation as the MBX_Init function is; it frees the memory allocation for the mailbox structure array. The MBX_Open function is more interesting, not so much in its implementation, but in the way it is implemented. If all owners have closed the mailboxes that they created, no allocation will exist for any mailbox, and you can keep the code that handles closure of a mailbox very simple and quick. However, if a task needs to create a new mailbox, it calls the IpcMbxCreateMailbox function. This function, in turn, calls the CreateFile function to open the running mailbox service.

At this point, the service must return a valid DWORD value; otherwise, the call to create the mailbox will fail. The implementation checks whether the allocation is valid. If the allocation is not valid, you should allocate a new structure and return its virtual pointer. If the allocation is valid, it returns the existing value. The following code example shows how the Open function is implemented.

DWORD MBX_Open(DWORD hDeviceContext, DWORD AccessCode, 
DWORD ShareMode)
{
   DWORD dwRet = 0;
   if (g_pMailBoxes == NULL) {
      DWORD size = sizeof(MAILBOX);
      DEBUGMSG(DEBUGZONE(DBG_OPEN),(TEXT("MAILBOX: MBX_Open\n")));
g_pMailBoxes = (PMAILBOX)VirtualAlloc(NULL, size, MEM_COMMIT,
 PAGE_READWRITE);
      if (g_pMailBoxes == NULL)
         return dwRet;
      g_pMailBoxes->dwSize = size;
      dwRet = (DWORD)g_pMailBoxes;
      g_nMbxCount = 0;   
   } else {
       dwRet = (DWORD)g_pMailBoxes;
   }
   return dwRet;

IOCTL Codes

IOCTL codes are designed to be used by calling the DeviceIoControl function and are designed to be used internally. However, this function is at the heart of the mailbox service and does most of the work. The implementation of the DeviceIoControl function is somewhat involved. The code for each of the IOCTL codes will be explained and shown.

IOCTL_MBX_CREATE_MAILBOX

This IOCTL code is used to create a new mailbox object. The mailbox owner uses the IpcMbxCreateMailbox function to create the mailbox object. The following code example shows how the mailbox creation code is implemented.

// First check that a mailbox by that name does not exist
int count = g_nMbxCount;

pActiveMailBox = g_pMailBoxes;
BOOL bRcNameLookup = LookupMailboxName(pInBuff, &pActiveMailBox, count);
if (bRcNameLookup == TRUE) {
   SetLastError(MBX_MAILBOX_ALLREADY_EXISTS);
   return FALSE;
}
// Generate an event name
memcpy(strEventName, pInBuff->strName, 8);
// Dynamically create an array of mailboxes 
if (g_nMbxCount <= 15) {
   EnterCriticalSection(&g_CritSect);
   if (g_nMbxCount > 0) {
pTempMailBox = g_pMailBoxes;
pActiveMailBox = (PMAILBOX)VirtualAlloc(NULL, size * (g_nMbxCount + 1),
 MEM_COMMIT, PAGE_READWRITE); 
      if (pActiveMailBox == NULL) {
         LeaveCriticalSection(&g_CritSect);
         SetLastError(MBX_NOT_ENOUGH_MEMORY);
         return FALSE;
      } else {
         memcpy(pActiveMailBox, g_pMailBoxes, size * g_nMbxCount);
                  g_pMailBoxes = pActiveMailBox;
      }
   } 
   pActiveMailBox = g_pMailBoxes + g_nMbxCount;
   memset(pActiveMailBox, 0, size);
   pActiveMailBox->dwSize = size;
Event = CreateEvent(NULL, TRUE, FALSE, strEventName);
ActiveMailBox->hEvent = hEvent;
ActiveMailBox->dwTimeOut = pInBuff->dwTimeOut;
memcpy(pActiveMailBox->strName, pInBuff->strName, 8);
CeGenRandom(4, (PBYTE)&dwOwnrKey);
pActiveMailBox->dwOwnersKey = dwOwnrKey;
if (pInBuff->bfIsMsg == TRUE) {
pBufIn = pBufIn + MAME_LENGTH;   // Move to pointer to message
      memcpy(pActiveMailBox->dwMsg, pInBuff->dwMsg, MSG_LENGTH);
      SetEvent(hEvent);
      g_dwTicks = GetTickCount();
   }
   LeaveCriticalSection(&g_CritSect);
   if (pTempMailBox != NULL)
      bRet = VirtualFree(pTempMailBox, 0, MEM_RELEASE);
      memcpy(pBufOut, &dwOwnrKey, 4);
      g_nMbxCount++;
      bRet = TRUE;
   } else {
      SetLastError(MBX_MAILBOX_LIMIT_EXCEEDED);
      bRet = FALSE;
   } 

A few decisions had to be made here. The first was easy. An initial design decision was that the mailbox instances should be an array of structures rather than separate instances of the service. Memory had to be reallocated for this array every time the service was called to create a new mailbox. However, using the heap allocation and reallocation functions meant using the heap of Services.exe, which was not the ideal choice. Therefore, the self-inflicted virtual memory allocation and reallocation code was used.

Another decision involved the synchronization object that will allow subscribers to wait for a message. The first thought was a semaphore; however, the more this synchronization object was looked at, the more it seemed to be the wrong solution. Eventually, a named event object was settled on.

The other problem that needed to be addressed was ownership. Because the goal was to make sure that only the owner will be able to write messages to the mailbox and close the mailbox, the idea of an owner's key was created. Therefore, the CeGenRandom function generates a unique DWORD value to be used as an owner key. The DWORD value is returned to the caller so that the caller can use it for subsequent calls.

At the end of this creation process, if the caller passed a message to be inserted during creation of the mailbox, the message is inserted into the newly created mailbox. In addition, the event is set for possible subscribers to be signaled, and the current tick count is saved for time-out calculations.

IOCTL_MBX_ATT_SUBSCRIBER

This IOCTL code is used to attach a subscriber to a named mailbox. Many subscribers can attach to a specific mailbox; it should be quite clear that these need not be threads of the same process. This code is basic because most of the work is done in the IpcMbxSubscribeToMailbox function, and here the subscriber count of the mailbox is incremented. The thought of using interlocking for incrementing and decrementing this value caused some grief, so interlocking was not developed as a solution.

IOCTL_MBX_DETACH_SUBSCRIBER

This IOCTL code is used to detach a subscriber from a named mailbox. The implementation of this code is identical to the attach subscriber code, except that the subscriber count is decremented.

IOCTL_MBX_READ

This IOCTL code is used to read a message from a named mailbox. This code implementation is slightly more complex than the two previous IOCTL code implementations. As before, the first step is to look up the mailbox by its name. Then, check whether it is okay to reset the event. The following code example shows the implementation of IOCTL_MBX_READ.

int count = g_nMbxCount;      // Look up name
pActiveMailBox = g_pMailBoxes; 
BOOL bRcNameLookup = LookupMailboxName(pInBuff, &pActiveMailBox, count);

if (bRcNameLookup == FALSE) {
   SetLastError(MBX_MAILBOX_DOESNOT_EXISTS);
   return FALSE;
} 

memcpy(pBufOut, pActiveMailBox->dwMsg, MSG_LENGTH);
*pdwActualOut = 2;

// Check whether it is okay to reset event
// Need to check timeout
++(pActiveMailBox->nReadCount);

// This did not work very well, so running more tests would give a 
// better algorithm for event reset
dwCurrentTicks = GetTickCount();
if (((pActiveMailBox->nSubsCount - pActiveMailBox->nReadCount) == 0) ||
 ((dwCurrentTicks - g_dwTicks) >= pActiveMailBox->dwTimeOut))
ResetEvent(pActiveMailBox->hEvent);   

IOCTL_MBX_WRITE

The owner of a named mailbox uses this IOCTL code to write a message to that mailbox. This implementation is similar to reading the message, except for the checks made to determine whether whoever is trying to write the message is legitimate, and the checks made to ensure that all the message contents were written to the mailbox. As is the usual practice, first the correct mailbox is looked up. If the mailbox is found, work can proceed. The following code example shows how this IOCTL code is implemented.

int count = g_nMbxCount;      // Look up name
pActiveMailBox = g_pMailBoxes;
BOOL bRcNameLookup = LookupMailboxName(pInBuff, &pActiveMailBox, count);
if (bRcNameLookup == FALSE) {
   SetLastError(MBX_MAILBOX_DOESNOT_EXISTS);
   return FALSE;
} 

if (pActiveMailBox->dwOwnersKey == pInBuff->dwOwnersKey){
   dwCurrentTicks = GetTickCount();
            
if (((pActiveMailBox->nSubsCount - pActiveMailBox->nReadCount) == 0) ||
 ((dwCurrentTicks - g_dwTicks) >= pActiveMailBox->dwTimeOut)) {
      EnterCriticalSection(&g_CritSect);
pDest = (BYTE*)memcpy(pActiveMailBox->dwMsg, pInBuff->dwMsg, MSG_LENGTH);
      pActiveMailBox->bfReadByAll_orTimedOut = FALSE;
      pActiveMailBox->nReadCount = 0;
      LeaveCriticalSection(&g_CritSect);
   } else {
      if (pActiveMailBox->nSubsCount == 0)
         SetLastError(MBX_NO_SUBSCRIBERS);
      else
         SetLastError(MBX_MAILBOX_NOT_READ_OR_TIMEDOUT);
      return FALSE;
   }

   BYTE* pTemp = (BYTE*)pInBuff->dwMsg;
   for (int i = 0; i < 16; i++){
      if (*pDest == *pTemp)
         *pdwActualOut = ++nCount;
         pDest++;
         pTemp++;
      }
         SetEvent(pActiveMailBox->hEvent);
         g_dwTicks = GetTickCount();

         bRet = TRUE;
      } else {
         SetLastError(MBX_MAILBOX_UNAUTHORIZED_ACCESS);
         bRet = FALSE;
}

IOCTL_MBX_CLOSE

This IOCTL code is used to close a named mailbox and free all its memory and handles. Closing a mailbox is as intricate as creating a mailbox; you must deallocate the memory allocated to that mailbox. The following code example shows how this IOCTL code is implemented.

MAILBOX* pTempMailBoxes = NULL;
MAILBOX* pTempPntr = g_pMailBoxes;
// First check name and owner's key
// Look up name
int count = g_nMbxCount;
pActiveMailBox = g_pMailBoxes;
BOOL bRcNameLookup = LookupMailboxName(pInBuff, &pActiveMailBox, count);

if (bRcNameLookup == FALSE) {
   SetLastError(MBX_MAILBOX_DOESNOT_EXISTS);
   return FALSE;
} 

// Compare owner's key
if (pActiveMailBox->dwOwnersKey == pInBuff->dwOwnersKey){
   // Allocate a temp buffer for the remaining mailboxes
   // Iterate on g_pMailBoxes, move each of remaining mailboxes
   // to a temp buffer, and reallocate the buffer to be g_pMailBoxes

   EnterCriticalSection(&g_CritSect);
CloseHandle(pActiveMailBox->hEvent); // Do not forget to 
// close event handle
pTempMailBoxes = (PMAILBOX)VirtualAlloc(NULL, size * (g_nMbxCount - 1),
 MEM_COMMIT, PAGE_READWRITE); 
   for (int i = 0; i < g_nMbxCount; i++){
      if (g_pMailBoxes->dwOwnersKey != pInBuff->dwOwnersKey) {
         memmove(pTempMailBoxes, g_pMailBoxes, size);
         pTempMailBoxes++;
      }
      g_pMailBoxes++;
   }
   g_nMbxCount--;
   bRet = VirtualFree(pTempPntr, 0, MEM_RELEASE);
   g_pMailBoxes = pTempMailBoxes; //- g_nMbxCount;
   LeaveCriticalSection(&g_CritSect);
} else {
   SetLastError(MBX_MAILBOX_UNAUTHORIZED_ACCESS);
   bRet = FALSE;
}

IOCTL_MBX_QUERY_STATE

This IOCTL code is used to get the state of a named mailbox. This is straightforward code that transfers some information from the mailbox back to the caller. The following example shows how the code is implemented.

int nCount = 0;
int count = g_nMbxCount;      // Look up name
pActiveMailBox = g_pMailBoxes;
BOOL bRcNameLookup = LookupMailboxName(pInBuff, &pActiveMailBox, count);

if (bRcNameLookup == FALSE) {
   SetLastError(MBX_MAILBOX_DOESNOT_EXISTS);
   return FALSE;
} 

MAILBOXSTATE MbxState;
MbxState.dwMsg[0] = pActiveMailBox->dwMsg[0];
MbxState.dwMsg[1] = pActiveMailBox->dwMsg[1];
MbxState.dwTimeOut = pActiveMailBox->dwTimeOut;
MbxState.nAttachedSubs = pActiveMailBox->nSubsCount;
MbxState.strName[0] = pActiveMailBox->strName[0];
MbxState.strName[1] = pActiveMailBox->strName[1];
MbxState.strName[2] = pActiveMailBox->strName[2];
MbxState.strName[3] = pActiveMailBox->strName[3];

memcpy(pBufOut, &MbxState, sizeof(MAILBOXSTATE));

Testing

Employees at Elisra Electronic Systems Ltd., a producer of electronic warfare (EW) systems, decided to adopt Microsoft Windows® CE .NET for a new airborne EW system. They were accustomed to high-performance RTOSs like MTOS, so their expectations were high. They first tried message queue point to point for intertask communications, which resulted in 160 ticks for passing a message from a server task to a client task. When they tried the mailbox service, they achieved 40 or fewer (depending on hardware) ticks per transaction between owner and subscriber.

The test program creates two threads, one for the owner and one for the subscriber. The owner sends a synchronization value on the creation of the mailbox. It then loops 1,000 times, sending messages to the mailbox. The second thread is a subscriber thread that reads the messages and makes some decisions after reading a message. The following code represents the test program.

#include <windows.h>
#include "MailboxAPI.h"

// Defines
//
#define MEMORY_MAPPED_FILE_SIZE  0x10000     // 1 Mb
#define BUFFER_DWORD_SIZE        128 
#define BUFFER_BYTE_SIZE         (BUFFER_DWORD_SIZE << 2)
#define MSG_DWORD_SIZE           2
#define MSG_BYTE_SIZE            (MSG_DWORD_SIZE << 2)
#define TEST_MSGP2P_NUM          1000

// Typedefs
//
// Process global variables
//
BOOL                 g_LoopAbort;
HANDLE            g_hEvent_Wait;
HANDLE            g_hEvent_Start;
DWORD             g_Start_msec;
DWORD             g_Elapsed_msec;

// Global handle definitions
HANDLE            g_h_MBX_Service;
HANDLE            g_h_MBX_TA;
HANDLE            g_h_MBX_TB;
DWORD             g_Mbx_Key_A;
DWORD             g_Mbx_Key_B;
DWORD             g_Mbx_Key_0;

// General test definitions
#define TEST_MBX_NUM          1000

// Message buffer fields
#define MBX_MSG_CODE_IDX      0
#define MBX_MSG_DATA_IDX      1
#define MBX_BYTE_MSG_SIZE     8       // 2 DWORDS
#define MBX_DWORD_MSG_SIZE    2       // 2 DWORDS

// Message code definitions
#define MBXA_LIVE_MESSAGE     1
#define MBXA_MESSAGE_CNT      2
#define MBXA_MESSAGE_3        3
#define MBXA_SYNC_WORD        0xAA55

#define MBXB_LIVE_MESSAGE     1
#define MBXB_MESSAGE_CNT      2
#define MBXB_MESSAGE_3        3
#define MBXB_SYNC_WORD        0x55AA

// Prototypes 
DWORD WINAPI MBX_TA(void);
DWORD WINAPI MBX_TB(void);

// -----------------------------------------------------------------------
// Main
//
int main(void)
{

  // Threads
  DWORD     MBX_TidA;
  DWORD     MBX_TidB;
  LPDWORD   lpMBX_TidA = &MBX_TidA;
  LPDWORD   lpMBX_TidB = &MBX_TidB;
//  int       MyPriority;
  int       i = 0;

  g_LoopAbort = FALSE;

  // Create event for synchronization
  // --------------------------------
  g_hEvent_Start = CreateEvent(NULL, FALSE, FALSE, NULL);
  g_hEvent_Wait  = CreateEvent(NULL, FALSE, FALSE, NULL);
  // Load mailbox service DLL
  // ------------------------
  g_h_MBX_Service = LoadLibraryEx(L"mailbox.dll", NULL,
 DONT_RESOLVE_DLL_REFERENCES);

  // Create two threads to perform point-to-point communication
  g_h_MBX_TA = CreateThread( (LPSECURITY_ATTRIBUTES)NULL, 0x1000,
 (LPTHREAD_START_ROUTINE)&MBX_TA, (LPVOID)0, 0, lpMBX_TidA);
  g_h_MBX_TB = CreateThread( (LPSECURITY_ATTRIBUTES)NULL, 0x1000,
 (LPTHREAD_START_ROUTINE)&MBX_TB, (LPVOID)0, 0, lpMBX_TidB);

             
//  if (WaitForSingleObject(g_hEvent_Wait, INFINITE) == WAIT_OBJECT_0)
  if (WaitForSingleObject(g_h_MBX_TB, INFINITE) == WAIT_OBJECT_0)
    RETAILMSG(1,(TEXT("\r\nMBXComm_Test_Rt, Display results:\r\n")));

  if (g_h_MBX_TA) CloseHandle(g_h_MBX_TA);
  if (g_h_MBX_TB) CloseHandle(g_h_MBX_TB);


  // Calculate elapsed time
  RETAILMSG(1,(TEXT(" %d Ticks \r\n"), g_Elapsed_msec - g_Start_msec));
  RETAILMSG(1,(TEXT("MBXComm_Test_Rt, Elapse time (mSec) for %d messages
 with size %d: "), TEST_MSGP2P_NUM, MSG_DWORD_SIZE));
  RETAILMSG(1,(TEXT("MBXComm_Test_Rt, Elapse time per message: %d (uSec)
 \r\n"), ((g_Elapsed_msec - g_Start_msec) * 1000) / TEST_MSGP2P_NUM));

  return 0;
}


// Thread A is mailbox owner
DWORD WINAPI MBX_TA(void)
{
  DWORD             dwMsgCount;
  DWORD             Msg_A[2];
  DWORD *           pMsg_A = &Msg_A[0];
  int               MyPriority;
  MAILBOXSTATE      MbxState;
  // Change the thread's priority
  MyPriority = CeGetThreadPriority(g_h_MBX_TA);
    RETAILMSG(1,(TEXT("MBX_TA, GetThreadPriority start %d
 \r\n"),MyPriority));
  MyPriority = 101;
  CeSetThreadPriority(g_h_MBX_TA, MyPriority);
    RETAILMSG(1,(TEXT("MBX_TA, GetThreadPriority New %d
 \r\n"),CeGetThreadPriority(g_h_MBX_TA)));
// Good practice to check things first
IpcMbxQueryMailboxState(TEXT("MBXA"), &MbxState);   while (
 MbxState.nAttachedSubs == 0) {  // check if there is at least one          
                               // subscriber
   Sleep(50); // Just sleep a quantum to let the first susbscriber attach
   IpcMbxQueryMailboxState(TEXT("MBXA"), &MbxState); 
}


while (!g_LoopAbort)
{
   if (dwMsgCount < TEST_MBX_NUM)
   {
      dwMsgCount++;
      Msg_A [MBX_MSG_CODE_IDX] = MBXA_MESSAGE_CNT;
      Msg_A [MBX_MSG_DATA_IDX] = dwMsgCount;
      
      BOOL bRc = IpcMbxWriteMailbox(TEXT("MBXA"), pMsg_A,                   
                                 MBX_BYTE_MSG_SIZE, g_Mbx_Key_A);
      if ( ! bRc )
      {
         RETAILMSG(1,(TEXT("MBX_TA, Error in IpcMbxWriteMailbox =          
                  %d\r\n"), GetLastError()));
      } else {
         // Display write message
         RETAILMSG(1,(TEXT("Write Msg #%d: 0x%X 0x%X \r\n"), dwMsgCount,    
                              Msg_A[0], Msg_A[1]));
         if (dwMsgCount == 1000) {
            CeSetThreadPriority(g_h_MBX_TB, MyPriority - 2);
RETAILMSG(1,(TEXT("MBX_TB, GetThreadPriority New %d 
\r\n"),CeGetThreadPriority(g_h_MBX_TB)));
         }

      }
   } /* if not reached to TEST_MSGP2P_NUM messages */
} /* While message count */

while ( MbxState.nAttachedSubs != 0) {// Waiting for subscriber to detach
      Sleep(50); // Just sleep a quantum to let susbscribers detach
      IpcMbxQueryMailboxState(TEXT("MBXA"), &MbxState); 
}
if (g_Mbx_Key_A) {
      BOOL brc = IpcMbxCloseMailbox(TEXT("MBXA"), g_Mbx_Key_A);
RETAILMSG(1,(TEXT("MBXA, IpcMbxCloseMailbox returned: %d \r\n"), brc));
}
   return 0;
}

// Thread B is mailbox subscriber
DWORD WINAPI MBX_TB(void)
{
  HANDLE            h_MBX_A_Mailbox;
  DWORD             dwMsgCount;
  DWORD             Msg_B[2];
  DWORD *           pMsg_B = &Msg_B[0];
  int               MyPriority;

  // Change the thread's priority
  MyPriority = CeGetThreadPriority(g_h_MBX_TB);
  RETAILMSG(1,(TEXT("MBX_TB, GetThreadPriority start %d 
\r\n"),MyPriority));
  MyPriority = 100;
  CeSetThreadPriority(g_h_MBX_TB, MyPriority);
  RETAILMSG(1,(TEXT("MBX_TB, GetThreadPriority New %d
\r\n"),CeGetThreadPriority(g_h_MBX_TB)));
h_MBX_A_Mailbox = IpcMbxSubscribeToMailbox(TEXT("MBXA"), TRUE);
  if ( h_MBX_A_Mailbox == NULL )
  {
RETAILMSG(1,(TEXT("MBX_TB, Error in IpcMbxSubscribeToMailbox = %d\r\n"),
 GetLastError()));
     return 0;
  }
  dwMsgCount = 0;
  while (!g_LoopAbort) // Get messages in loop
  {
    // Wait for message events from MBXA
    WaitForSingleObject(h_MBX_A_Mailbox, 500);//INFINITE);
    // MBXA signals a message event, so you read it
    if ( !IpcMbxReadMailbox(TEXT("MBXA"), pMsg_B, MBX_BYTE_MSG_SIZE) )
    {
 RETAILMSG(1,(TEXT("MBX_TA, Error in IpcMbxReadMailbox from MBXA = 
%d\r\n"), GetLastError()));
      g_LoopAbort = TRUE;
      break;
    }
    // Handle messages
    switch (pMsg_B[MBX_MSG_CODE_IDX])
    {
      case MBXA_LIVE_MESSAGE:
      {
        if (pMsg_B[MBX_MSG_DATA_IDX] != MBXA_SYNC_WORD){
          RETAILMSG(1,(TEXT("MBX_TB, Error in IpcMbxReadMailbox from MBXA,
data is: 0x%X 0xX\r\n"), 
                  pMsg_B[MBX_MSG_CODE_IDX], pMsg_B[MBX_MSG_CODE_IDX]));
          g_LoopAbort = TRUE;
        } else {
          // Display write message
          RETAILMSG(1,(TEXT("case 1 - Read Msg #%d: 0x%X 0x%X \r\n"),
 dwMsgCount, Msg_B[0], Msg_B[1]));
        }
        break;
      }
      case MBXA_MESSAGE_CNT:
      {   
        if (pMsg_B[MBX_MSG_DATA_IDX] == 0){
          RETAILMSG(1,(TEXT("MBX_TB, Error in IpcMbxReadMailbox from MBXA,
 data is: 0x%X 0xX\r\n"), 
                  pMsg_B[MBX_MSG_CODE_IDX], pMsg_B[MBX_MSG_CODE_IDX]));
          g_LoopAbort = TRUE;
        } else {
          dwMsgCount++;
          // Display write message
          RETAILMSG(1,(TEXT("case 2 - Read Msg #%d: 0x%X 0x%X \r\n"),
 dwMsgCount, Msg_B[0], Msg_B[1]));
        }
        break;
      }
    } // End switch
    
    if (pMsg_B[MBX_MSG_DATA_IDX] == TEST_MBX_NUM) {
RETAILMSG(1,(TEXT("End condition Read Msg #%d: 0x%X 0x%X \r\n"),
 dwMsgCount, Msg_B[0], Msg_B[1]));
      g_Elapsed_msec = GetTickCount();
      g_LoopAbort = TRUE;
    }
  } /* While not abort */

   HANDLE hRet = IpcMbxSubscribeToMailbox(TEXT("MBXA"), FALSE);
RETAILMSG(1,(TEXT("MBXA, IpcMbxSubscribeToMailbox returned: %d \r\n"),
hRet));
  }
Show: