// SharedMemory.h: interface for the CSharedMemory class.
//
//
#if !defined(AFX_SHAREDMEMORY_H__86467BA6_5AFA_11D3_863D_00A0244A9CA7__INCLUDED_)
#define AFX_SHAREDMEMORY_H__86467BA6_5AFA_11D3_863D_00A0244A9CA7__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <process.h>
class CSharedMemory
{
private:
class CWriteQueue
{
// This class is the queue, it contains a pointer to
// a data block and a pointer to the next queue item.
friend class CSharedMemory;
private:
CWriteQueue(int nDataSize)
{
pData = new BYTE[nDataSize];
pNext = NULL;
};
~CWriteQueue()
{
delete [] pData;
};
void *pData;
CWriteQueue *pNext;
};
public:
enum
{
// Return values of the class-functions.
MEM_ERROR_UNKNOWN = -1,
MEM_SUCCESS = 0,
MEM_ERROR_CLOSED = 1,
MEM_ERROR_TIMEOUT = 2,
MEM_ERROR_OTHERPARTY = 3,
MEM_ERROR_DATASIZE = 4
};
CSharedMemory()
{
m_nOtherInstanceID = 0;
m_nInstanceID = 0;
// Create an event that indicates wether the connection
// is open or not.
m_hClosed = CreateEvent(NULL, TRUE, TRUE, NULL);
m_hDataWrit[0] = NULL;
m_hDataWrit[1] = NULL;
m_hDataRead[0] = NULL;
m_hDataRead[1] = NULL;
m_hDataInQueue = NULL;
m_hQueueMutex = NULL;
};
virtual ~CSharedMemory()
{
Close();
CloseHandle(m_hClosed);
};
bool Open(char* sName, int nDataSize, int nTimeOut = INFINITE)
{
m_pFirst = NULL;
// The connection must be closed before it can be opened.
if (WaitForSingleObject(m_hClosed, 0) == WAIT_OBJECT_0)
{
// The name may not exceed MAX_PATH, we substract 10 because we
// add some strings to the name in some code.
if (strlen(sName) != 0 && strlen(sName) < MAX_PATH - 10)
{
// The datasize must be larger than 0.
if (nDataSize > 0)
{
// The following mutexes can indicate 4 things:
// - No instance of this shared memory class was created.
// - The first instance of this class was created.
// - The second instance of this shared memory class was created.
// - Both instances were created.
char sMutex0 [MAX_PATH];
char sMutex1 [MAX_PATH];
strcpy(sMutex0 , sName);
strcpy(sMutex1 , sName);
strcat(sMutex0 , "Mutex0");
strcat(sMutex1 , "Mutex1");
m_hSharedMemoryMutex[0] = CreateMutex(NULL, FALSE, sMutex0);
m_hSharedMemoryMutex[1] = CreateMutex(NULL, FALSE, sMutex1);
if (m_hSharedMemoryMutex[0] && m_hSharedMemoryMutex[1])
{
// Only two instances of this class (with this name) may reside on
// one system. These will be referred to as 'm_nInstanceID and m_nOtherInstanceID'
HANDLE hWait[2] = {m_hSharedMemoryMutex[0], m_hSharedMemoryMutex[1]};
DWORD dwResult = WaitForMultipleObjects(2, hWait, FALSE, 0);
if (dwResult == WAIT_OBJECT_0 || dwResult == (WAIT_OBJECT_0 + 1))
{
if ((m_nInstanceID = dwResult - WAIT_OBJECT_0) == 0)
m_nOtherInstanceID = 1;
else
m_nOtherInstanceID = 0;
char sName0 [MAX_PATH];
char sName1 [MAX_PATH];
strcpy(sName0 , sName);
strcpy(sName1 , sName);
strcat(sName0 , "0");
strcat(sName1 , "1");
// We will use two shared memory pools to provide duplex
// communication.
if ((m_hSharedMemory[0] = CreateFileMapping( (HANDLE)0xFFFFFFFF,
NULL,
PAGE_READWRITE,
0,
sizeof(int) + nDataSize,
sName0)) != NULL
&&
(m_hSharedMemory[1] = CreateFileMapping( (HANDLE)0xFFFFFFFF,
NULL,
PAGE_READWRITE,
0,
sizeof(int) + nDataSize,
sName1)) != NULL)
{
bool bFileMappingAlreadyExists = (GetLastError() == ERROR_ALREADY_EXISTS);
// Now map a pointer to the size tag in the shared memory.
m_pSize = (int*)MapViewOfFile( m_hSharedMemory[0],
FILE_MAP_ALL_ACCESS,
0,
0,
sizeof(int));
if (m_pSize)
{
bool bSharedMemorySizeOk = false;
if (bFileMappingAlreadyExists)
{
// We will check if the size of the memory block is of the
// same size as the block that was already allocated by another
// instance of the shared memory class.
// The size of the memory block is saved in the first integer
// at the specified shared memory address.
if (*m_pSize == nDataSize)
bSharedMemorySizeOk = true;
}
else
{
// The memory was not allocated by another instance so we
// have the honors to allocate it. This means also that we should
// set the size of the memory that we have allocated in the first
// integer of the shared memory space.
*m_pSize = nDataSize;
bSharedMemorySizeOk = true;
}
if (bSharedMemorySizeOk)
{
m_pSharedMemory[0] = (BYTE*)MapViewOfFile( m_hSharedMemory[0],
FILE_MAP_ALL_ACCESS,
0,
0,
nDataSize);
m_pSharedMemory[1] = (BYTE*)MapViewOfFile( m_hSharedMemory[1],
FILE_MAP_ALL_ACCESS,
0,
0,
nDataSize);
if (m_pSharedMemory[0] && m_pSharedMemory[1])
{
// Move the pointer a little further so that it does not point to
// the size tag, but to the address of the data that we want to share.
m_pSharedMemory[0] += sizeof(int);
m_pSharedMemory[1] += sizeof(int);
// The following events make sure that data can only
// be read when data was written and vise versa.
char sDataWrit0 [MAX_PATH];
char sDataWrit1 [MAX_PATH];
char sDataRead0 [MAX_PATH];
char sDataRead1 [MAX_PATH];
strcpy(sDataWrit0 , sName);
strcpy(sDataWrit1 , sName);
strcpy(sDataRead0 , sName);
strcpy(sDataRead1 , sName);
strcat(sDataWrit0 , "DataWrit0");
strcat(sDataWrit1 , "DataWrit1");
strcat(sDataRead0 , "DataRead0");
strcat(sDataRead1 , "DataRead1");
m_hDataWrit[0] = CreateEvent(NULL, FALSE, FALSE, sDataWrit0);
m_hDataWrit[1] = CreateEvent(NULL, FALSE, FALSE, sDataWrit1);
m_hDataRead[0] = CreateEvent(NULL, FALSE, TRUE, sDataRead0);
m_hDataRead[1] = CreateEvent(NULL, FALSE, TRUE, sDataRead1);
if (m_hDataWrit[0] && m_hDataWrit[1] && m_hDataRead[0] && m_hDataRead[1])
{
m_hSecondInstanceAvailable = CreateEvent(NULL, FALSE, FALSE, sName);
if (m_hSecondInstanceAvailable)
{
if (m_nInstanceID == 0)
{
// We are the first instance, wait for the second instance
// to come this far, then we can assume that the connection
// is fully open.
if (WaitForSingleObject(m_hSecondInstanceAvailable, nTimeOut) == WAIT_OBJECT_0)
{
CloseHandle(m_hSecondInstanceAvailable);
ResetEvent(m_hClosed);
m_hQueueMutex = CreateMutex(NULL, FALSE, NULL);
m_hDataInQueue = CreateEvent(NULL, FALSE, FALSE, NULL);
m_hQueueThread = (HANDLE)_beginthread(QueueThread, 0, this);
return true;
}
}
else if (m_nInstanceID == 1)
{
// We are the second instance, signal the other instance that
// we have come this far.
// Immediately wait 0 seconds for the event, if it is still signaled
// we know that the other instance was not waiting, the connection
// has failed.
SetEvent(m_hSecondInstanceAvailable);
if (WaitForSingleObject(m_hSecondInstanceAvailable, 0) == WAIT_TIMEOUT)
{
CloseHandle(m_hSecondInstanceAvailable);
ResetEvent(m_hClosed);
m_hQueueMutex = CreateMutex(NULL, FALSE, NULL);
m_hDataInQueue = CreateEvent(NULL, FALSE, FALSE, NULL);
m_hQueueThread = (HANDLE)_beginthread(QueueThread, 0, this);
return true;
}
}
CloseHandle(m_hSecondInstanceAvailable);
}
else
{
// We could not create the required event.
}
}
else
{
// We could not create any event handles.
}
UnmapViewOfFile(m_pSharedMemory[0]);
UnmapViewOfFile(m_pSharedMemory[1]);
}
else
{
// We could not get a pointer to the actual data.
}
}
else
{
// The datasize of the already allocated memory, and the size of this
// instance do not match.
}
UnmapViewOfFile(m_pSize);
}
else
{
// We could not map to the integer that contains the size of the memory block.
}
CloseHandle(m_hSharedMemory[0]);
CloseHandle(m_hSharedMemory[1]);
}
else
{
// The memory handles could not be created.
}
}
else
{
// There was no mutex available, this can mean that there are
// already two instances of this object with the same name
// in use on this system.
}
CloseHandle(m_hSharedMemoryMutex[0]);
CloseHandle(m_hSharedMemoryMutex[1]);
}
else
{
// The mutexes could not be created.
}
}
else
{
// The datasize is not > 0.
}
}
else
{
// The name of the shared memory is not valid, or the datasize is not larger than 0.
}
}
else
{
// This instance is already open.
}
return false;
};
void Close()
{
if (WaitForSingleObject(m_hClosed, 0) == WAIT_TIMEOUT)
{
// Indicate that this instance is closed
SetEvent(m_hClosed);
// Release your own mutex. This will auitomatically signal
// the other instance of this class that this instance broke
// the connection.
ReleaseMutex(m_hSharedMemoryMutex[m_nInstanceID]);
// The writequeue may still contain elements, empty
// it.
EmptyWriteQueue();
WaitForSingleObject(m_hQueueThread, INFINITE);
CloseHandle(m_hQueueMutex);
CloseHandle(m_hDataInQueue);
m_hQueueMutex = NULL;
m_hDataInQueue = NULL;
// Cleanup some stuff.
CloseHandle(m_hDataWrit[0]);
CloseHandle(m_hDataWrit[1]);
CloseHandle(m_hDataRead[0]);
CloseHandle(m_hDataRead[1]);
m_hDataWrit[0] = NULL;
m_hDataWrit[1] = NULL;
m_hDataRead[0] = NULL;
m_hDataRead[1] = NULL;
UnmapViewOfFile(m_pSize);
m_pSharedMemory[0] -= sizeof(int);
m_pSharedMemory[1] -= sizeof(int);
UnmapViewOfFile(m_pSharedMemory[0]);
UnmapViewOfFile(m_pSharedMemory[1]);
CloseHandle(m_hSharedMemory[0]);
CloseHandle(m_hSharedMemory[1]);
CloseHandle(m_hSharedMemoryMutex[0]);
CloseHandle(m_hSharedMemoryMutex[1]);
}
};
int Write(void *pData, int nDataSize, DWORD dwTimeOut)
{
// The 'Write' and 'WriteToQueue' functions can be used promiscously.
// This function writes to the shared memory pool, it can only write
// to that pool if existing data in the pool has been read by the
// other instance of this class.
// If this function returns MEM_ERROR_OTHERPARTY, the calling
// process should close the connection and re-open it to create a new
// valid connection.
HANDLE hWait[3];
hWait[0] = m_hClosed;
hWait[1] = m_hSharedMemoryMutex[m_nOtherInstanceID];
hWait[2] = m_hDataRead[m_nOtherInstanceID];
DWORD dwWaitResult = WaitForMultipleObjects(3, hWait, FALSE, dwTimeOut);
switch(dwWaitResult)
{
case WAIT_OBJECT_0 + 2:
if (nDataSize > *m_pSize)
return MEM_ERROR_DATASIZE;
// Data was read from the shared memory pool, write new data
// and notify any listener that new data was written.
memcpy(m_pSharedMemory[m_nOtherInstanceID], pData, nDataSize);
SetEvent(m_hDataWrit[m_nOtherInstanceID]);
return MEM_SUCCESS;
case WAIT_OBJECT_0:
// The close function of this instance was called.
return MEM_ERROR_CLOSED;
case WAIT_OBJECT_0 + 1:
// The other instance closed.
// Since we locked the mutex by waiting for it, we have to release
// it again.
ReleaseMutex(m_hSharedMemoryMutex[m_nOtherInstanceID]);
return MEM_ERROR_OTHERPARTY;
case WAIT_ABANDONED_0 + 1:
// The other instance left without a trace, this means probably that
// it crashed.
// Since we locked the mutex by waiting for it, we have to release
// it again.
ReleaseMutex(m_hSharedMemoryMutex[m_nOtherInstanceID]);
return MEM_ERROR_OTHERPARTY;
case WAIT_FAILED:
if (!m_hDataRead[m_nOtherInstanceID])
return MEM_ERROR_CLOSED;
// I don't know wat happened, you should call 'GetLastError()'.
return MEM_ERROR_UNKNOWN;
case WAIT_TIMEOUT:
// There was a timeout, the other party has not yet read previous data.
return MEM_ERROR_TIMEOUT;
}
return MEM_ERROR_UNKNOWN;
}
int WriteToQueue(void *pData, int nDataSize)
{
// The 'Write' and 'WriteToQueue' functions can be used promiscously.
// This function is somewhat the same as the previous function, however,
// this function is non-blocking. As long as the connection is valid this
// function can write new data into a queue. The queue is read by a thread
// that calls the previous 'Write' function.
HANDLE hWait[3];
hWait[0] = m_hClosed;
hWait[1] = m_hSharedMemoryMutex[m_nOtherInstanceID];
hWait[2] = m_hQueueMutex;
switch (WaitForMultipleObjects(3, hWait, FALSE, INFINITE))
{
case WAIT_OBJECT_0:
return MEM_ERROR_CLOSED;
case WAIT_OBJECT_0 + 2:
{
if (nDataSize > *m_pSize)
return MEM_ERROR_DATASIZE;
CWriteQueue *pNew = new CWriteQueue(*m_pSize);
memcpy(pNew->pData, pData, *m_pSize);
if (!m_pFirst)
m_pFirst = pNew;
else
{
CWriteQueue *pCurrent = m_pFirst;
while (pCurrent->pNext)
pCurrent = pCurrent->pNext;
pCurrent->pNext = pNew;
}
SetEvent(m_hDataInQueue);
ReleaseMutex(m_hQueueMutex);
}
return MEM_SUCCESS;
case WAIT_OBJECT_0 + 1:
// The other instance closed.
// Since we locked the mutex by waiting for it, we have to release
// it again.
ReleaseMutex(m_hSharedMemoryMutex[m_nOtherInstanceID]);
return MEM_ERROR_OTHERPARTY;
case WAIT_ABANDONED_0 + 1:
// The other instance left without a trace, this means probably that
// it crashed.
// Since we locked the mutex by waiting for it, we have to release
// it again.
ReleaseMutex(m_hSharedMemoryMutex[m_nOtherInstanceID]);
return MEM_ERROR_OTHERPARTY;
case WAIT_FAILED:
// This can happen when the connection was not opened yet.
// It is caused by an invalid or NULL handle.
if (!m_hQueueMutex)
return MEM_ERROR_CLOSED;
// This must never happen.
return MEM_ERROR_UNKNOWN;
}
return MEM_ERROR_UNKNOWN;
}
int Read(void *pData, int nDataSize, DWORD dwTimeOut)
{
// This function reads from the shared memory pool, it can
// only read data when data was written to the pool.
// It is always a blocking function.
// It reads data that was written to the shared memory pool by
// the 'Write' or 'WriteToQueue' functions.
HANDLE hWait[3];
hWait[0] = m_hDataWrit[m_nInstanceID];
hWait[1] = m_hClosed;
hWait[2] = m_hSharedMemoryMutex[m_nOtherInstanceID];
DWORD dwWaitResult = WaitForMultipleObjects(3, hWait, FALSE, dwTimeOut);
switch(dwWaitResult)
{
case WAIT_OBJECT_0:
// This happens when data is written into the shared memory pool.
// It indicates that the data can be copied into a memory
// buffer.
if (nDataSize > *m_pSize)
return MEM_ERROR_DATASIZE;
memcpy(pData, m_pSharedMemory[m_nInstanceID], nDataSize);
SetEvent(m_hDataRead[m_nInstanceID]);
return MEM_SUCCESS;
case WAIT_OBJECT_0 + 1:
// This happens when no connection was made yet, or this
// instance was closed.
return MEM_ERROR_CLOSED;
case WAIT_OBJECT_0 + 2:
// This happens when the other party closes its connection.
ReleaseMutex(m_hSharedMemoryMutex[m_nOtherInstanceID]);
return MEM_ERROR_OTHERPARTY;
case WAIT_ABANDONED_0 + 2:
// This happens when the other party gracefully closes its connection.
// This can be caused by an unexpected termination of the host
// process of the other instance.
ReleaseMutex(m_hSharedMemoryMutex[m_nOtherInstanceID]);
return MEM_ERROR_OTHERPARTY;
case WAIT_FAILED:
// This can happen when the connection was not opened yet.
// It is caused by an invalid or NULL handle.
if (!m_hDataWrit[m_nInstanceID])
return MEM_ERROR_CLOSED;
return MEM_ERROR_UNKNOWN;
case WAIT_TIMEOUT:
// This indicates that the maximum wait time (dwTimeOut) has passed.
return MEM_ERROR_TIMEOUT;
}
return MEM_ERROR_UNKNOWN;
}
private:
void EmptyWriteQueue()
{
// This private function avoids memory leaking of items in the send-queue.
while (m_pFirst && WaitForSingleObject(m_hQueueMutex, INFINITE) == WAIT_OBJECT_0)
{
if (m_pFirst)
{
// First get the first element of the queue
CWriteQueue *pQueue = m_pFirst;
m_pFirst = pQueue->pNext;
delete pQueue;
}
ReleaseMutex(m_hQueueMutex);
}
}
static void QueueThread(void *pArg)
{
// This thread writes every packet that enteres the queue to the shared memory pool.
// It ensures that the 'WriteToQueue' function is nonblocking.
// The queue access is mutexed to avoid simultaneous access to the queue by
// this thread and the 'WriteToQueue' function.
CSharedMemory *pThis = (CSharedMemory*)pArg;
HANDLE hWait[2] = {pThis->m_hClosed, pThis->m_hDataInQueue};
bool bQuit = false;
while (!bQuit)
{
switch (WaitForMultipleObjects(2, hWait, FALSE, INFINITE))
{
case WAIT_OBJECT_0 + 1:
{
BYTE *pData = NULL;
while (pThis->m_pFirst && WaitForSingleObject(pThis->m_hQueueMutex, INFINITE) == WAIT_OBJECT_0)
{
if (pThis->m_pFirst)
{
// First get the first element of the queue
CWriteQueue *pQueue = pThis->m_pFirst;
pData = new BYTE[*pThis->m_pSize];
memcpy(pData, pThis->m_pFirst->pData, *pThis->m_pSize);
pThis->m_pFirst = pQueue->pNext;
delete pQueue;
ReleaseMutex(pThis->m_hQueueMutex);
pThis->Write(pData, *pThis->m_pSize, INFINITE);
delete [] pData;
}
else
ReleaseMutex(pThis->m_hQueueMutex);
}
}
break;
case WAIT_OBJECT_0:
bQuit = true;
break;
}
}
}
private:
// We will use two shared memory pools to create a transparant memory 'pipe'.
// One pool will be used as destination for one instance, and source for the other
// instance, the other will be used the other way around.
// The two mutexes will indicate which instance is already available.
HANDLE m_hSharedMemoryMutex[2];
int m_nInstanceID;
int m_nOtherInstanceID;
HANDLE m_hSharedMemory[2];
BYTE* m_pSharedMemory[2];
int* m_pSize;
HANDLE m_hClosed; // This handle indicates wether this instance
// is open or closed.
HANDLE m_hDataWrit[2];
HANDLE m_hDataRead[2];
HANDLE m_hSecondInstanceAvailable;
// Queue stuff
HANDLE m_hQueueThread;
HANDLE m_hDataInQueue;
CWriteQueue *m_pFirst;
HANDLE m_hQueueMutex;
};
#endif // !defined(AFX_SHAREDMEMORY_H__86467BA6_5AFA_11D3_863D_00A0244A9CA7__INCLUDED_)