#include "sig.h"
////////////////////////////////////////////////////////////////////////////////////////////
// Global objects involved in the implementation
////////////////////////////////////////////////////////////////////////////////////////////
// The minimum size of the data to be shared
#define MINIMUM_MMF_SIZE 4096
// The dummy event object whose existence indicates that an instance is already running
HANDLE g_hGuardEvent = NULL;
// The main flag that indicates if an instance is already running
bool g_bRunning = false;
// The memory mapped file used to communicate
HANDLE g_hMMF = NULL;
// The size of the data to be shared
long g_nSharedDataSize = MINIMUM_MMF_SIZE;//default
// The size of the data actually present in the MMF (used in reading data)
long g_nWrittenDataSize = -1;
// Total bytes read from or written into the buffer
long g_nBytesRW = 0;
// The event used to grant access to reading the MMF
HANDLE g_hCanReadData = NULL;
// The event used to grant access to writing the MMF
HANDLE g_hCanWriteData = NULL;
// The flag that allows write operations go on in sequence
bool g_bHasWrittenPreviously = false;
// The ID of the first instance thread to which alert would be sent
DWORD g_dwFirstInstanceThreadId = 0;
// The handle of the first instance main window (set by the client) to which alert would be sent
HWND g_hWndFirstInstanceWindow = NULL;
// The ID to recognise the guard
const char *g_pid = NULL;
////////////////////////////////////////////////////////////////////////////////////////////
// SingleInstanceGuard implementation
////////////////////////////////////////////////////////////////////////////////////////////
bool IsAlreadyRunning(const char*pid)
{
g_hGuardEvent = CreateEvent(NULL, FALSE, FALSE, pid);
return ERROR_ALREADY_EXISTS==GetLastError();;
}
unsigned long _stdcall AlertMonitor(void*)
{
while(1)
{
WaitForSingleObject(g_hCanReadData, INFINITE);
g_nBytesRW = 0;
g_nWrittenDataSize = -1;
if(NULL != g_hWndFirstInstanceWindow)
PostMessage(g_hWndFirstInstanceWindow, SingleInstanceGuard::SIGM_ALERT, 0, 0);
else
PostThreadMessage(g_dwFirstInstanceThreadId, SingleInstanceGuard::SIGM_ALERT, 0, 0);
}
return 0;
}
UINT SingleInstanceGuard::SIGM_ALERT = 0;
SingleInstanceGuard::SingleInstanceGuard(const char *pID, long nSharedDataSize/*-1*/)
{
g_pid = pID;
if(nSharedDataSize > MINIMUM_MMF_SIZE)
g_nSharedDataSize = nSharedDataSize;
g_bRunning = IsAlreadyRunning(g_pid);
/* create/open the other kernel objects */
std::string strMMF = g_pid;
strMMF += "MMF";
std::string strCanRead = g_pid;
strCanRead += "CanRead";
std::string strCanWrite = g_pid;
strCanWrite += "CanWrite";
std::string strAlert = g_pid;
strAlert += "AlertMessage";
if(g_bRunning)
{
/* Open the MMF */
g_hMMF = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, strMMF.c_str());
/* Read out the communication information */
char *pBytes = (char*)MapViewOfFile(g_hMMF, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if(0==pBytes)
throw Exception(Exception::UNABLE_TO_CHECK);
long nSize;
memcpy((void*)&nSize, pBytes, sizeof(nSize));
if(nSize != g_nSharedDataSize)
throw Exception(Exception::DIFFERENT_GUARDS);
g_nBytesRW += sizeof(nSize);
UnmapViewOfFile(pBytes);
/* open the control kernel objects */
g_hCanReadData = CreateEvent(NULL, FALSE, FALSE, strCanRead.c_str());
g_hCanWriteData = CreateEvent(NULL, FALSE, FALSE, strCanWrite.c_str());
}
else
{
/* register the alert mesage */
SIGM_ALERT = ::RegisterWindowMessage(strAlert.c_str());
/* Create the control kernel objects */
g_hCanReadData = CreateEvent(NULL, FALSE, FALSE, strCanRead.c_str());
g_hCanWriteData = CreateEvent(NULL, FALSE,
TRUE, // allow the first copy to proceed to write without waiting
strCanWrite.c_str());
/* create the MMF (backed by the system paging file) */
long nMMFSize = sizeof(long) // space for the said size value itself, to verify later
+ sizeof(long)// size of data actually shared
+ g_nSharedDataSize;// the said size itself
g_hMMF = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, nMMFSize, strMMF.c_str());
/* Write out the communication information */
char *pBytes = (char*)MapViewOfFile(g_hMMF, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if(0==pBytes)
throw Exception(Exception::UNABLE_TO_CHECK);
memcpy(pBytes, (void*)&g_nSharedDataSize, sizeof(g_nSharedDataSize));
g_nBytesRW += sizeof(g_nSharedDataSize);
long W = 0;// written size
memcpy(pBytes+g_nBytesRW, (void*)&W, sizeof(W));
g_nBytesRW += sizeof(W);
UnmapViewOfFile(pBytes);
/* mark the thread id and create the alert monitor */
g_dwFirstInstanceThreadId = GetCurrentThreadId();
DWORD tid;
CreateThread(NULL, 0, AlertMonitor, NULL, 0, &tid);
}
g_nBytesRW = 0;
}
SingleInstanceGuard::~SingleInstanceGuard()
{
// This is necessary when other instances won't share data and simply alert.
if(g_bRunning && (0==g_nBytesRW))
SetEvent(g_hCanWriteData);
CloseHandle(g_hGuardEvent);
CloseHandle(g_hCanWriteData);
CloseHandle(g_hCanReadData);
CloseHandle(g_hMMF);
}
bool SingleInstanceGuard::AlreadyRunning()
{
return g_bRunning;
}
SingleInstanceGuard& SingleInstanceGuard::operator << (const std::string &s)
{
int n = s.size();
Write((void*)&n, sizeof(n));
Write((void*)s.c_str(), s.size());
return *this;
}
bool SingleInstanceGuard::operator >> (std::string &s)
{
int n;
if(!Read((void*)&n, sizeof(n)))
return false;
char *buffer = new char[n+1];
bool bMore = Read(buffer, n);
buffer[n] = 0;
s = buffer;
delete [] buffer;
return bMore;
}
void operator<<(SingleInstanceGuard &t, char *p)
{
throw SingleInstanceGuard::Exception(SingleInstanceGuard::Exception::OPERATION_NOT_SUPPORTED);
}
void operator>>(SingleInstanceGuard &t, char *p)
{
throw SingleInstanceGuard::Exception(SingleInstanceGuard::Exception::OPERATION_NOT_SUPPORTED);
}
SingleInstanceGuard& SingleInstanceGuard::Write(void *pData, int nSize)
{
if(!g_bRunning)// first instance needn't write anything
throw Exception(Exception::SHARING_WITH_SELF);
if(!g_bHasWrittenPreviously)
WaitForSingleObject(g_hCanWriteData, INFINITE);
g_bHasWrittenPreviously = true;
if(g_nBytesRW + nSize > g_nSharedDataSize)
throw Exception(Exception::DATA_TOO_BIG);
char *pBytes = (char*)MapViewOfFile(g_hMMF, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if(0==pBytes)
throw Exception(Exception::UNABLE_TO_WRITE);
memcpy(pBytes+sizeof(long)+sizeof(long)+g_nBytesRW, pData, nSize);
g_nBytesRW += nSize;
UnmapViewOfFile(pBytes);
return *this;
}
bool SingleInstanceGuard::Read(void *pData, int nSize)
{
// This is called only after successful wait on g_hCanRead from the monitor thread.
// So no need to wait here.
if(g_bRunning)// only first instance must read
throw Exception(Exception::SHARING_WITH_SELF);
if(g_nBytesRW == g_nWrittenDataSize)
return false;// no more read
if(g_nBytesRW + nSize > g_nSharedDataSize)
throw Exception(Exception::DATA_TOO_BIG);
char *pBytes = (char*)MapViewOfFile(g_hMMF, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if(0==pBytes)
throw Exception(Exception::UNABLE_TO_READ);
if(g_nWrittenDataSize < 0)
{
g_nWrittenDataSize = *(long*)(pBytes+sizeof(long));
// reset so that an empty alert after a shared data alert won't read wrong data
*(long*)(pBytes+sizeof(long)) = 0;
}
if(0 == g_nWrittenDataSize)
{
SetEvent(g_hCanWriteData);// let others write
return false;
}
memcpy(pData, pBytes+sizeof(long)+sizeof(long)+g_nBytesRW, nSize);
g_nBytesRW += nSize;
UnmapViewOfFile(pBytes);
if(g_nBytesRW == g_nWrittenDataSize)
SetEvent(g_hCanWriteData);// let others write
return true;
}
void SingleInstanceGuard::AlertTheRunningInstance()
{
if(!g_bHasWrittenPreviously)
WaitForSingleObject(g_hCanWriteData, INFINITE);
else
{
// write the number of bytes written into the MMF into the MMF
char *pBytes = (char*)MapViewOfFile(g_hMMF, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if(0==pBytes)
throw Exception(Exception::UNABLE_TO_ALERT);
*(long*)(pBytes + sizeof(long)) = g_nBytesRW;
UnmapViewOfFile(pBytes);
}
g_bHasWrittenPreviously = false;
// alert
SetEvent(g_hCanReadData);
}
void SingleInstanceGuard::SetTargetWindow(HWND h)
{
if(g_bRunning)// only the first instance needs this
throw Exception(Exception::OPERATION_NOT_SUPPORTED);
g_hWndFirstInstanceWindow = h;
}