Click here to Skip to main content
15,895,084 members
Articles / Desktop Programming / MFC

CSharedMailslot: a shared server mailslot

Rate me:
Please Sign up or sign in to vote.
4.68/5 (10 votes)
1 Dec 20048 min read 45.7K   780   17  
An interprocess communication approach to share a server mailslot.
/*

CSharedMailslot build 1.006

code by matro
rome, italy, 2004
matro$realpopup%it

07.sep.2004 - 0.001 - first build for RealPopup build 149: IPC not implemented yet
09.nov.2004 - 1.004 - first public release (included in RealPopup build 155)
02.dec.2004 - 1.006 - released for Code Project

*warning* please update CSHAREDMAILSLOT_BUILD accordingly.

this code may be used in compiled form in any way you desire. you are not 
allowed to redistribute your version of CSharedMailslot: please change project 
name (this is to maintain a single distribution point for the source code). if 
you use this code, it would be nice the presence of an author acknowledgement. 
this code is released "as is": use it at your *own* risk and *expect bugs*. 
write me for comments, suggestions or to tell your story.

matro

*/

#include "stdafx.h"
#include ".\sharedmailslot.h"

typedef enum _SE_OBJECT_TYPE
{
	SE_UNKNOWN_OBJECT_TYPE = 0,
	SE_FILE_OBJECT,
	SE_SERVICE,
	SE_PRINTER,
	SE_REGISTRY_KEY,
	SE_LMSHARE,
	SE_KERNEL_OBJECT,
	SE_WINDOW_OBJECT,
	SE_DS_OBJECT,
	SE_DS_OBJECT_ALL,
	SE_PROVIDER_DEFINED_OBJECT,
	SE_WMIGUID_OBJECT,
	SE_REGISTRY_WOW64_32KEY
} SE_OBJECT_TYPE;

typedef DWORD (STDMETHODCALLTYPE FAR* LPSETNAMEDSECURITYINFO) (LPTSTR, SE_OBJECT_TYPE, SECURITY_INFORMATION, PSID, PSID, PACL, PACL);

CSharedMailslot::CSharedMailslot(void)
{
	mailslotSlaveName=(char *)malloc(CSHAREDMAILSLOT_CHARS_MAX);
	mailslotName=(char *)malloc(CSHAREDMAILSLOT_CHARS_MAX);
	ipcMutexName=(char *)malloc(CSHAREDMAILSLOT_CHARS_MAX);
	ipcName=(char *)malloc(CSHAREDMAILSLOT_CHARS_MAX);
	mailslotSlaveHandle=INVALID_HANDLE_VALUE;
	mailslotHandle=INVALID_HANDLE_VALUE;
	ipcFileMapping=NULL;
	libADVAPI32=NULL;
	isBufferUsed=false;
	isMailslotServer=false;
}

CSharedMailslot::~CSharedMailslot(void)
{
	Close();
	free(ipcName);
	free(ipcMutexName);
	free(mailslotName);
	free(mailslotSlaveName);

	if (libADVAPI32!=NULL)
		FreeLibrary(libADVAPI32);
}

bool CSharedMailslot::isOpen(void)
{
	return ( (mailslotHandle != INVALID_HANDLE_VALUE) || (isShared()&&!isInstanceOwner()) );
}

bool CSharedMailslot::isShared(void)
{
	return (ipcFileMapping != NULL);
}

bool CSharedMailslot::isServer(void)
{
	return (isMailslotServer);
}

bool CSharedMailslot::isInstanceOwner(void)
{
	if (ipcFileMapping==NULL)
		return false;

	return (ipcInstancesView->instanceOwner==instanceIndex);
};

bool CSharedMailslot::Close(void)
{
	if (isShared())
	{

		if (WaitForSingleObject(ipcMutex, CSHAREDMAILSLOT_IPC_WAIT)==WAIT_TIMEOUT)
			return false;

		if (isInstanceOwner())
		{
			DWORD bufferSizeUsed, messagesWaiting=-1;
			char *dummy;

			// flushes pending messages to the other slave mailslots
			while (messagesWaiting!=0)
				Read(dummy, bufferSizeUsed, messagesWaiting, true);

			ipcInstancesView->instanceOwner=0;
		}
		ipcInstancesView->instances[instanceIndex]=0;
		instanceIndex=0;

		ReleaseMutex(ipcMutex);

		UnmapViewOfFile(ipcInstancesView);
		CloseHandle(mailslotSlaveHandle);
		CloseHandle(ipcFileMapping);
		CloseHandle(ipcMutex);
		ipcFileMapping=NULL;
		mailslotSlaveHandle=INVALID_HANDLE_VALUE;
	}

	if (mailslotHandle != INVALID_HANDLE_VALUE)
	{
		CloseHandle(mailslotHandle);
		mailslotHandle=INVALID_HANDLE_VALUE;
		isMailslotServer=false;
	}

	if (isBufferUsed)
	{
		free(mailslotBuffer);
		isBufferUsed=false;
	}

	return true;
}

char *CSharedMailslot::GetMailslotName(bool full)
{
	// this method returns either the given mailslot name (full=false) or the
	// full mailslot name (full=true) constructed by the BuildSharedNames() method
	if (isOpen())
	{
		if (full)
			return mailslotName;
		else
			return strrchr(mailslotName, '\\')+1;
	}

	return NULL;
}

void CSharedMailslot::BuildSharedNames(char *mailslotName)
{
	// NT4 does not permit Global session name space prefix 
	if (isSharedAllowed()==CSHAREDMAILSLOT_SYSREQ_OKNOWTS)
		strcpy(ipcName, "CSharedMailslot");
	else
		strcpy(ipcName, "Global\\CSharedMailslot");

	strcat(ipcName, mailslotName);
	strcpy(ipcMutexName, ipcName);
	strcat(ipcMutexName, "Mutex");

	BuildSharedMailslotName(mailslotSlaveName, GetCurrentThreadId(), true);
}

void CSharedMailslot::BuildSharedMailslotName(char *nameBuffer, DWORD id, bool owned)
{
	char mailslotPrefix[CSHAREDMAILSLOT_CHARS_MAX], buffer[CSHAREDMAILSLOT_CHARS_MAX], threadId[8];
	int k=0, d, buflen;

	sprintf(mailslotPrefix, "\\\\%s\\mailslot\\", (owned?".":"."));
	itoa(id, threadId, 16);
	strcpy(nameBuffer, mailslotPrefix);
	strcpy(buffer, ipcName);
	strcat(buffer, threadId);
	buflen=strlen(buffer);
	d=strlen(mailslotPrefix);

	// looks tricky, and it is :-)
	while(k<buflen)
	{
		// basically, the loop adds a '\' char every 8 characters, converting
		// a long name (not supported on 9x platforms) to a folder path
		// (which is supported instead)
		strncpy(&nameBuffer[d], &buffer[k], 8);
		d+=(strlen(&buffer[k])>7?8:strlen(&buffer[k])); k+=8;
		strcpy(&nameBuffer[d++], "\\");
	}
	nameBuffer[--d]=0;
}

bool CSharedMailslot::Open(bool isMailslotServer, char *mailslotName, char *destinationName, bool shared)
{
	this->isMailslotServer=isMailslotServer;

	if (isMailslotServer)
	{
		LPSETNAMEDSECURITYINFO SetNamedSecurityInfo;

		if (shared)
		{
			// the shared feature is supported on NT-based kernels only,
			// but since this class can act as a normal mailslots wrapper,
			// the SetNamedSecurityInfo() is loaded dynamically to permit
			// execution on win9x platforms
			if (isSharedAllowed()!=CSHAREDMAILSLOT_SYSREQ_KO)
			{
				if (libADVAPI32==NULL)
				{
					libADVAPI32 = LoadLibrary( _T("advapi32.dll") );
					if (!libADVAPI32)
						return false;

					SetNamedSecurityInfo = (LPSETNAMEDSECURITYINFO) GetProcAddress( libADVAPI32, "SetNamedSecurityInfoA" ); 
					if (!SetNamedSecurityInfo)
						return false;
				}
			}
			else
				shared=false;
		}

		if (shared)
		{
			// if this is a server (ie. listen for incoming messages),
			// all required shared structs are defined and opened

			int counter=1;

			BuildSharedNames(mailslotName);

			ipcMutex=CreateMutex(NULL, FALSE, ipcMutexName);
			if (ipcMutex==NULL)
				return false;

			ipcFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(ipcInstances), ipcName);
			if (ipcFileMapping==NULL)
			{
				CloseHandle(ipcMutex);
				return false;
			}

			SetNamedSecurityInfo(ipcName, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, 0, 0, (PACL) NULL, NULL);

			ipcInstancesView=(ipcInstances *)MapViewOfFile(ipcFileMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
			if (ipcInstancesView==NULL)
			{
				CloseHandle(ipcFileMapping);
				CloseHandle(ipcMutex);
				ipcFileMapping=NULL;
				return false;
			}

			// this is the 'slave mailslot', ie. the CSharedMailslot internal mailslot which
			// listens for incoming messages from the instanceOwner
			mailslotSlaveHandle = CreateMailslot(mailslotSlaveName, 0, MAILSLOT_WAIT_FOREVER, NULL);
			if (mailslotSlaveHandle==INVALID_HANDLE_VALUE)
			{
				UnmapViewOfFile(ipcInstancesView);
				CloseHandle(ipcFileMapping);
				CloseHandle(ipcMutex);
				ipcFileMapping=NULL;
				return false;
			}

			// the CSHAREDMAILSLOT_IPC_WAIT is an arbitrary timeout value
			if (WaitForSingleObject(ipcMutex, CSHAREDMAILSLOT_IPC_WAIT)==WAIT_TIMEOUT)
			{
				UnmapViewOfFile(ipcInstancesView);
				CloseHandle(ipcFileMapping);
				CloseHandle(ipcMutex);
				ipcFileMapping=NULL;
				return false;
			}

			while (counter<CSHAREDMAILSLOT_SLOTS_MAX)
			{
				if (ipcInstancesView->instances[counter]==0)
				{
					ipcInstancesView->instances[counter]=GetCurrentThreadId();
					instanceIndex=counter;

					if (ipcInstancesView->instanceOwner==0)
						ipcInstancesView->instanceOwner=counter;
					break;
				}

				counter++;
			}

			ReleaseMutex(ipcMutex);

			if (counter==CSHAREDMAILSLOT_SLOTS_MAX)
			{
				// no more available shared slots
				UnmapViewOfFile(ipcInstancesView);
				CloseHandle(ipcFileMapping);
				CloseHandle(ipcMutex);
				ipcFileMapping=NULL;
				return false;
			}
		}

		sprintf(this->mailslotName, "\\\\.\\mailslot\\%s", mailslotName);
		if ( !shared || (shared && isInstanceOwner()) )
		{
			// this is the main mailslot, ie. the one which listens for messages coming
			// from other processes\workstations. if this is a shared server, then this
			// mailslot is created only if this is the instanceOwner
			this->mailslotHandle = CreateMailslot(this->mailslotName, 0, MAILSLOT_WAIT_FOREVER,	NULL);

			if (!isOpen())
			{
				if (shared)
				{
					UnmapViewOfFile(ipcInstancesView);
					CloseHandle(mailslotSlaveHandle);
					CloseHandle(ipcFileMapping);
					CloseHandle(ipcMutex);
					ipcFileMapping=NULL;
				}
				
				return false;
			}
		}		
		
		return true;
	}

	if (destinationName==NULL)
		return false;

	// this is the client mailslot (ie. the one used to send messages)
	sprintf(this->mailslotName, "\\\\%s\\mailslot\\%s", destinationName, mailslotName);
	this->mailslotHandle = CreateFile(this->mailslotName, GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,	NULL);

	return (isOpen());
}

bool CSharedMailslot::Write(char *text, int textLength, char *mailslotName, char *destinationName)
{
	DWORD writtenLength;
	int towriteLength;

	if (text==NULL || isMailslotServer)
		return false;

	if (textLength==NULL)
		towriteLength=sizeof(text);
	else
		towriteLength=textLength;

	if (isOpen())
		return (WriteFile(mailslotHandle, text, towriteLength, &writtenLength, NULL));
	else
	{
		if (mailslotName==NULL || destinationName==NULL)
			return false;

		// if the mailslot is not open, then it is automatically opened then closed
		// (user can choose to call the Write() function only)
		if (Open(false, mailslotName, destinationName))
			if (WriteFile(mailslotHandle, text, towriteLength, &writtenLength, NULL))
			{
				Close();
				return true;
			}
	}

	return false;
}

bool CSharedMailslot::Read(char *&buffer, DWORD &bufferSize, DWORD &messagesWaiting, bool isThreadSafe)
{
	DWORD bufferSizeUsed, otherMessagesWaiting;
	char *dummy;

	if (!isOpen() || !isMailslotServer)
		return false;

	if (isShared())
	{
		if (isInstanceOwner())
		{
			// even if this is the instanceOwner, maybe there are some msgs pending on the slave mailslot
			if (!ReadMailslot(mailslotSlaveHandle, buffer, bufferSize, messagesWaiting))
				return false;

			if (bufferSize>0)
			{
				if (!ReadMailslot(mailslotHandle, dummy, bufferSizeUsed, otherMessagesWaiting, true))
					return false;

				messagesWaiting+=otherMessagesWaiting;
				return true;
			}

			// pending messages on main mailslot (usual condition if this is the instanceOwner)
			if (!ReadMailslot(mailslotHandle, buffer, bufferSize, messagesWaiting))
				return false;

			// forward message to all non-instanceOwner instances; comment this call to
			// behave as the messenger service does (only this instance will receive the message)
			if (bufferSize>0)
				Forward(buffer, bufferSize);

			return true;
		}
		else
		{
			// pending messages on slave mailslot (usual condition if this is a slave instance)
			if (!ReadMailslot(mailslotSlaveHandle, buffer, bufferSize, messagesWaiting))
				return false;

			if (!isThreadSafe)
				if (WaitForSingleObject(ipcMutex, CSHAREDMAILSLOT_IPC_WAIT)==WAIT_TIMEOUT)
					return false;

			// try to become instanceOwner (in the case the instanceOwner thread exited or crashed)
			HANDLE gainMailslotHandle=CreateMailslot(this->mailslotName, 0, MAILSLOT_WAIT_FOREVER, NULL);
			if (gainMailslotHandle!=INVALID_HANDLE_VALUE)
			{
				mailslotHandle=gainMailslotHandle;
				ipcInstancesView->instances[ipcInstancesView->instanceOwner]=0;
				ipcInstancesView->instanceOwner=instanceIndex;
			}

			if (!isThreadSafe)
				ReleaseMutex(ipcMutex);

			return true;
		}
	}
	else
	{
		if (ReadMailslot(mailslotHandle, buffer, bufferSize, messagesWaiting))
			return true;
	}

	return false;
}

bool CSharedMailslot::ReadMailslot(HANDLE mailslotHandle, char *&buffer, DWORD &bufferSize, DWORD &messagesWaiting, bool messagesWaitingOnly)
{
	DWORD bufferSizeUsed;

	if (GetMailslotInfo(mailslotHandle, NULL, &bufferSize, &messagesWaiting, NULL))
	{
		// the following check for nonsense could detect a mailslot abnormal condition
		if ( (messagesWaiting>0 && (bufferSize<0 || bufferSize>65535)) || messagesWaiting>255)
			return false;

		if (bufferSize == MAILSLOT_NO_MESSAGE || messagesWaitingOnly)
		{
			bufferSize=0;
			return true;
		}

		if (isBufferUsed)
			free(mailslotBuffer);

		isBufferUsed=true;
		mailslotBuffer=(char *)calloc(bufferSize, 1);
		if (ReadFile(mailslotHandle, mailslotBuffer, bufferSize, &bufferSizeUsed, NULL))
		{
			buffer=mailslotBuffer;
			messagesWaiting--;
			return true;
		}
		else
		{
			free(mailslotBuffer);
			isBufferUsed=false;
			return false;
		}
	}

	return false;
}

bool CSharedMailslot::Forward(char *buffer, DWORD bufferSize)
{
	// forward message to all non-instanceOwner instances

	// even if this is normally used in Read() method, it can be called
	// from the external too: for example, it is called from the NetBios
	// listener in RealPopup whenever a msg is received from it

	char ipcMailslotSlaveName[CSHAREDMAILSLOT_CHARS_MAX];
	HANDLE ipcMailslotSlaveHandle;
	int towriteLength, counter=1;
	DWORD writtenLength;

	towriteLength=bufferSize;

	if (WaitForSingleObject(ipcMutex, CSHAREDMAILSLOT_IPC_WAIT)==WAIT_TIMEOUT)
		return false;

	while (counter<CSHAREDMAILSLOT_SLOTS_MAX)
	{
		if (counter!=instanceIndex)
		{
			if (ipcInstancesView->instances[counter]!=0)
			{
				BuildSharedMailslotName(ipcMailslotSlaveName, ipcInstancesView->instances[counter], false);

				ipcMailslotSlaveHandle = CreateFile(ipcMailslotSlaveName, GENERIC_WRITE,
					FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,	NULL);

				WriteFile(ipcMailslotSlaveHandle, buffer, towriteLength, &writtenLength, NULL);
				CloseHandle(ipcMailslotSlaveHandle);
			}
		}
		counter++;
	}

	ReleaseMutex(ipcMutex);

	return true;
}

int CSharedMailslot::isSharedAllowed()
{
	// shared feature is allowed on NT kernel based platforms;
	// NT4 does not permit Global session name space prefix 

	OSVERSIONINFO vi = {sizeof(OSVERSIONINFO)};
	GetVersionEx(&vi);

	if (vi.dwPlatformId!=VER_PLATFORM_WIN32_NT || vi.dwMajorVersion<4)
		return CSHAREDMAILSLOT_SYSREQ_KO;

	if (vi.dwMajorVersion==4)
		return CSHAREDMAILSLOT_SYSREQ_OKNOWTS;

	return (CSHAREDMAILSLOT_SYSREQ_OK);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Architect
Italy Italy
I'm Francesco "Matro" Martire, a freeware developer. in the real world, I am an Architect and Project Manager.

I live in Switzerland.

Comments and Discussions