Introduction
This article concentrates in shared memory design and communication between threads/programs using shared memory. I would break up this article into two sections:
About Shared Memory
When a program loads into the memory, it is broken up into pieces called pages. The communication would exist between the pages of memory or between two independent processes. Anyhow, when a program would like to communicate with another, there should be a common area in the memory for both the programs. This area which is shared between processes is called the Shared Memory. If there was no concept of shared memory, the section of memory occupied by a program could not be accessed by another one thus disabling the concept of sharing data or communication. Then again, in order to reduce integrity of shared data and to avoid concurrent access to the data, kernel provides a mechanism to exclusively access the shared memory resource. This is called mutual exclusion or mutex object.
When a process wants to communicate to another, the following steps take place sequentially:
- Take the mutex object, locking the shared area.
- Write the data to be communicated into the shared area.
- Release the mutex object.
When a process reads from the area, it should repeat the same steps, except that the step 2 should be Read.
About the Code
In order for the program to communicate, the shared memory region should be made when the program starts, at least in this case. This is done by using the following code in the OnCreate
function mapped for WM_CREATE
message. (You can do this explicitly by adding a handler for WM_CREATE
by ClassWizard). Before doing that, write down the global variables (outside any class) as follows:
All these things can be defined in the header file for the implementation file of the shared memory. It can be a dialog box or a document interface.
HANDLE kSendCommand;
HANDLE kReceiveCommand;
HANDLE kSendMessage;
HANDLE kReceiveMessage;
HANDLE kChildAck;
CWinThread* thread;
The shared memory structure runs as below:
#define KILL_APP WM_USER+10
#define RECV_MESSAGE WM_USER+20
#define CHILD_START WM_USER+30
struct KSharedMemory
{
DWORD processID;
BOOL childAck;
char data[1000];
UINT dataSize;
};
UINT StartProbing(LPVOID lParam);
The OnCreate function would look like this.
CString title;
if (CDialog::OnCreate(lpCreateStruct) == -1)
return -1;
kProcessId = ::GetCurrentProcessId();
title.Format("Process: %d",kProcessId);
this->SetWindowText(title);
kMap = CreateFileMapping((HANDLE)0xFFFFFFFF,NULL,PAGE_READWRITE,
0,sizeof(KSharedMemory),"KBuildDevelop");
if(GetLastError() == ERROR_ALREADY_EXISTS)
{
kMap = ::OpenFileMapping(FILE_MAP_WRITE,FALSE,"KBuildDevelop");
kMutex = ::CreateMutex(NULL,FALSE,"KBuildDevelop");
kParentOrChild = FALSE;
}
else
{
kParentOrChild = TRUE;
}
kShMem = (KSharedMemory*)::MapViewOfFile(kMap,FILE_MAP_WRITE,
0,0,sizeof(KSharedMemory));
The CreateFileMapping
function makes the shared memory as a file map. MapViewOfFile
function enables sharing of the area created.
kSendCommand = ::CreateEvent(NULL,FALSE,FALSE,"SendCommand");
kSendMessage = ::CreateEvent(NULL,FALSE,FALSE,"SendMessage");
kReceiveMessage = ::CreateEvent(NULL,FALSE,FALSE,"ReceiveMessage");
kReceiveCommand = ::CreateEvent(NULL,FALSE,FALSE,"ReceiveCommand");
kChildAck = ::CreateEvent(NULL,FALSE,FALSE,"ChildAcknowledge");
The CreateEvent
function creates events for all the states like sending command, receiving command, sending message, receiving message, and acknowledgement from the child.
To enable message mapping for the user defined commands, include the following lines in the message mapping area:
ON_MESSAGE(KILL_APP,OnKillApp)
ON_MESSAGE(RECV_MESSAGE,OnRecvMessage)
ON_MESSAGE(CHILD_START,OnChildStart)
In the InitDialog
function, add the following lines:
if(kParentOrChild)
{
this->SetWindowText("Parent: Receiving Command");
GetDlgItem(IDC_BUTTON_KILL)->DestroyWindow();
GetDlgItem(IDC_EDIT_MESSAGE)->EnableWindow(false);
GetDlgItem(IDC_BUTTON_SEND)->EnableWindow(false);
thread = AfxBeginThread(StartProbing,GetSafeHwnd(),
THREAD_PRIORITY_NORMAL);
if(thread != NULL)
{
UpdateData(true);
m_status = "Parent waiting for messages ...";
UpdateData(false);
}
else
{
UpdateData(true);
m_status = "Thread not started ...";
UpdateData(false);
}
}
else
{
GetDlgItem(IDC_BUTTON_KILL)->EnableWindow(true);
kShMem->childAck = TRUE;
::SetEvent(kChildAck);
this->SetWindowText("Child: Send Command / Message to Parent");
}
The other important functions are:
UINT StartProbing(LPVOID lParam)
{
while(1)
{
if(::WaitForSingleObject(kChildAck,10)== WAIT_OBJECT_0)
PostMessage((HWND) lParam,CHILD_START,0,0);
if(::WaitForSingleObject(kSendCommand, 10) == WAIT_OBJECT_0)
{
PostMessage((HWND) lParam, KILL_APP,0,0);
::SetEvent(kReceiveCommand);
break;
}
else
{
if(::WaitForSingleObject(kSendMessage, 10) == WAIT_OBJECT_0)
{
PostMessage((HWND) lParam, RECV_MESSAGE,0,0);
::SetEvent(kReceiveMessage);
}
}
}
return 0;
}
void COneAtaTimeDlg::OnKillApp()
{
PostQuitMessage(0);
}
void COneAtaTimeDlg::OnButtonKill()
{
::SetEvent(kSendCommand);
::ReleaseMutex(kMutex);
}
void COneAtaTimeDlg::OnButtonSend()
{
UpdateData(true);
char buffer[100];
sprintf(buffer,"%s",m_message);
strcpy(kShMem->data,buffer);
m_message=_T("");
UpdateData(false);
::SetEvent(kSendMessage);
}
void COneAtaTimeDlg::OnRecvMessage()
{
UpdateData(true);
if(strcmp(kShMem->data,"bye")==0)
PostQuitMessage(0);
m_recvlist.AddString(kShMem->data);
UpdateData(false);
}
void COneAtaTimeDlg::OnChildStart()
{
UpdateData(true);
m_status = "Child Started...";
UpdateData(false);
}