Listing 2: CommsChannel implementation
#define STRICT
#include <windows.h>
#include <memory.h>
#include <stdlib.h>
#include "channel.hpp"
struct CommsData {
int bufferSize;
int fsStart;
int fsNext;
bool fsEmpty;
int tsStart;
int tsNext;
bool tsEmpty;
int fsOffset;
int tsOffset;
};
// general delayed open
CommsChannel::CommsChannel() : opened(false), error(objectClosed) {
}
// server open
CommsChannel::CommsChannel(string name, int size) :
opened(false) {
open(name, size);
}
// client open
CommsChannel::CommsChannel(string name) : opened(false) {
open(name);
}
CommsChannel::statusEnum
CommsChannel::open(string name, int size) {
if (opened) {
return objectAlreadyOpen;
}
channelName = name;
opened = true;
error = ok;
string mapName =
"CommsChannel-" + channelName;
string mutexName =
"CommsChannelMutex-" + channelName;
string fsNotEmptySemName =
"CommsChannelFsNotEmptySem-" + channelName;
string fsNotFullSemName =
"CommsChannelFsNotFullSem-" + channelName;
string tsNotEmptySemName =
"CommsChannelTsNotEmptySem-" + channelName;
string tsNotFullSemName =
"CommsChannelTsNotFullSem-" + channelName;
// get and own the mutex first so there isn't a race condition
error = ok;
generalMutex = NULL; // need to be NULL so can close on error
fsNotEmptySem = NULL;
fsNotFullSem = NULL;
tsNotEmptySem = NULL;
tsNotFullSem = NULL;
data = NULL;
handle = NULL;
if (((generalMutex = CreateMutex(
NULL, true, mutexName.c_str())) == NULL) ||
(GetLastError() == ERROR_ALREADY_EXISTS) ) {
error = mutexCreationError;
}
else if (((fsNotEmptySem = CreateSemaphore( /* start out empty */
NULL, 0, 1, fsNotEmptySemName.c_str())) == NULL) ||
(GetLastError() == ERROR_ALREADY_EXISTS) ) {
error = mutexCreationError;
}
else if (((fsNotFullSem = CreateSemaphore( /* start not full */
NULL, 1, 1, fsNotFullSemName.c_str())) == NULL) ||
(GetLastError() == ERROR_ALREADY_EXISTS) ) {
error = mutexCreationError;
}
else if (((tsNotEmptySem = CreateSemaphore( /* start out empty */
NULL, 0, 1, tsNotEmptySemName.c_str())) == NULL) ||
(GetLastError() == ERROR_ALREADY_EXISTS) ) {
error = mutexCreationError;
}
else if (((tsNotFullSem = CreateSemaphore( /* start not full */
NULL, 1, 1, tsNotFullSemName.c_str())) == NULL) ||
(GetLastError() == ERROR_ALREADY_EXISTS) ) {
error = mutexCreationError;
}
if (error == ok) {
handle = CreateFileMapping(
(HANDLE)0xffffffff, NULL, PAGE_READWRITE, 0,
sizeof(CommsData) + (size + 4) * 2,
mapName.c_str());
if (handle == NULL) {
error = channelCreationError;
}
else if (GetLastError() == ERROR_ALREADY_EXISTS) {
// someone has already opened this channel!
error = channelAlreadyExists;
}
}
if (error == ok) {
data = (CommsData*)MapViewOfFile(
handle, FILE_MAP_WRITE, 0, 0, 0);
if (data == NULL) {
error = channelMappingError;
}
else {
// initialise things
server = true;
// align on double word boundary
data->fsOffset = (sizeof(CommsData) + 3) & 0xfffffffc;
data->tsOffset = (data->fsOffset + size + 7) & 0xfffffffc;
fsBuffer = reinterpret_cast<char*>(data + data->fsOffset);
tsBuffer = reinterpret_cast<char*>(data + data->tsOffset);
data->bufferSize = size;
data->fsStart = 0;
data->tsStart = 0;
data->fsNext = 0;
data->tsNext = 0;
data->fsEmpty = true;
data->tsEmpty = true;
}
}
if (error != ok) {
if (data) {UnmapViewOfFile(data);}
if (handle) {CloseHandle(handle);}
if (fsNotEmptySem) {CloseHandle(fsNotEmptySem);}
if (fsNotFullSem) {CloseHandle(fsNotFullSem);}
if (tsNotEmptySem) {CloseHandle(tsNotEmptySem);}
if (tsNotFullSem) {CloseHandle(tsNotFullSem);}
if (generalMutex) {
ReleaseMutex(generalMutex);
CloseHandle(generalMutex);
}
}
else {
ReleaseMutex(generalMutex);
}
return error;
}
CommsChannel::~CommsChannel() {
close();
}
CommsChannel::statusEnum
CommsChannel::close() {
if (!opened) {
return objectClosed;
}
if (!error) {
UnmapViewOfFile(data);
CloseHandle(handle);
ReleaseMutex(generalMutex);
CloseHandle(generalMutex);
}
error = objectClosed;
opened = false;
return ok;
}
// not shown: client side open() function
// ...
int
CommsChannel::write(char* buffer, int size,
bool chunk, DWORD timeout) {
int roomAvailable;
int status = -1;
bool wasEmpty;
if (error < 0) { // if bad channel don't get mutex
return -1;
}
char* channel = server ? fsBuffer : tsBuffer;
int& start = server ? data->fsStart : data->tsStart;
int& next = server ? data->fsNext : data->tsNext;
bool& empty = server ? data->fsEmpty : data->tsEmpty;
HANDLE& notEmptySem = server ? fsNotEmptySem : tsNotEmptySem;
HANDLE& notFullSem = server ? fsNotFullSem : tsNotFullSem;
roomAvailable = blockOnChannel(size, chunk, timeout, false,
generalMutex, notFullSem, start, next);
if (!error) {
// have mutex and semaphore at this point and know not full
wasEmpty = (start == next); // remember if channel
// was empty
size = min(size, roomAvailable); // can only write
status = size; // what's available
int moveSize = min(size, data->bufferSize - next);
memcpy(&channel[next], buffer, moveSize);
next += moveSize; // if copy done then start updated
size -= moveSize;
if (size > 0) {
memcpy(channel, &buffer[moveSize], size);
next = size; // next wasn't quite right
}
if (next == start) { // update the full/empty indicators
// must keep the not-full semaphore because it is
// no longer true
}
else {
ReleaseSemaphore(notFullSem, 1, NULL);
}
if (wasEmpty && (status > 0)) { // we've written something
// so now must release the not-empty semaphore because
// it is now true
ReleaseSemaphore(notEmptySem, 1, NULL);
empty = false;
}
}
// Release the mutex!
ReleaseMutex(generalMutex);
return status;
}
int
CommsChannel::blockOnChannel(int size, bool chunk, DWORD timeout,
bool reading, HANDLE& mutex, HANDLE& semaphore,
int& start, int& next) {
DWORD timeStart = GetTickCount();
DWORD timeLeft = timeout;
DWORD timePassed;
DWORD waitResult;
int amount;
bool enoughData = false;
HANDLE twoHandles[2] = {mutex, semaphore};
do {
waitResult =
WaitForMultipleObjects(2, twoHandles, true, timeLeft);
if (waitResult == WAIT_FAILED) {
error = waitError;
}
else if (waitResult == WAIT_TIMEOUT) {
error = waitTimeout;
}
else {
// we are in with the mutex
error = ok;
}
if (!error) {
// now we have the mutex and know that the buffer
// is not empty
if (reading) {
if (start == next) {amount = data->bufferSize;}
else if (next > start) {amount = next - start;}
else {amount = data->bufferSize - start + next;}
}
else {
if (start == next) {amount = data->bufferSize;}
else if (start > next) {amount = start - next;}
else {amount = data->bufferSize - next + start;}
}
if (!chunk || amount >= size) {
// have enough data - get out of the loop
enoughData = true;
}
else {
// not enough data - have to reblock
ReleaseSemaphore(semaphore, 1, NULL);
ReleaseMutex(mutex);
Sleep(0); // force yield
if (timeout != INFINITE) {
// Don't accumulate instead recalculate for precision
DWORD timeNow = GetTickCount();
if (timeNow < timeStart) {
// this doesn't seem too portable but I guess a
// DWORD will never change!!
timePassed = (timeNow + (0xffffffffL - timeStart));
}
else {
timePassed = timeNow - timeStart;
}
timeLeft = timeout - timePassed;
}
}
}
} while (!error && !enoughData && (timeout > timePassed));
if (!error) {
if (enoughData) {
return amount;
}
else {
// ran out of time
error = waitTimeoutChunkTooSmall;
return -1;
}
}
else {
return -1;
}
}
// not shown: read() member function, roomToWrite(), bytesToRead()
// ...
string
CommsChannel::noName = "No Name";
const string&
CommsChannel::name() const {
if (!opened) {
return noName;
}
else {
return channelName;
}
}
BOOL WINAPI
DllEntryPoint(HINSTANCE,DWORD,LPVOID);
__declspec(dllexport) BOOL WINAPI
DllEntryPoint(HINSTANCE /*myHandle*/,
DWORD reason,
LPVOID /*reserved*/) {
bool success = true;
switch (reason) {
case DLL_PROCESS_ATTACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
default:
success = false;
break;
}
return success;
}
//End of File