// Filename:- CommArea.cpp // // contains low-level code for shared-memory inter-program comms // #include "stdafx.h" #include "includes.h" //#include // #include "CommArea.h" #define sCOMMAREA_NAME "COMMAREA_MODVIEW" #define iCOMMAREA_VERSION 1 typedef enum { cst_READY=0, // nothing pending cst_SERVERREQ, // server has request for client cst_CLIENTREQ, // client has request for server cst_CLIENTERR, // client failed a request, check PassedData.sError[] cst_SERVERERR, // server failed a request, check PassedData.sError[] cst_WAIT, // raised by either side while filling in details of stuff // // these last 2 I could probably get rid of and replace by just setting back to cst_READY, but for now... // cst_CLIENTOK, // client completed a request cst_SERVEROK, // server completed a request } CommStatus_e; typedef struct { // message passing... (arb. buffer sizes, can be increased if you bump the version number as well) // char sCommand[1024]; // used to pass text command (case insensitive) byte bData[65536]; // int iDataSize; // other... // char sError[4096]; // used for reporting problems } PassedData_t; typedef struct { // verification fields... // int iVersion; // version of this struct, should match in client and server int iSize; // size of this struct, belt and braces safety // // the semaphore field... // CommStatus_e eStatus; // message passing... (arb. buffer sizes, can be increased if you bump the version number as well) // PassedData_t PassedData; } CommArea_t; static CommArea_t CommArea_USEONLYDURINGINIT, *gpMappedCommArea = NULL; static HANDLE hFileMap = NULL; static bool bIAmServer = false; // special copy that I can put all recieved data into so that the acknowledging a message doesn't let the other // app zap the results with a new message while you're busy copying the data elsewhere... // static PassedData_t LocalData; // by having a common string at the front of my error strings I can allow the caller to detect // whether they're messages produced by me or by Windows (and allow for stripping the common piece if desired) // #define sERROR_COMMAREAUNINITIALISED "CommArea: function called while comms not initialised, or init failed" #define sERROR_COMMANDSTRINGTOOLONG "CommArea: command string too long" #define sERROR_COMMANDDATATOOLONG "CommArea: command data too long" #define sERROR_BADCOMMANDACKTIME "CommArea: command acknowledge called but no command was pending" #define sERROR_BADCOMMANDCLEARTIME "CommArea: command-clear called while no error or ack pending - commands lost?" #define sERROR_NOTASKPENDING "CommArea: error report attempted while task not pending" #define sERROR_BUSY "CommArea: busy" static PassedData_t *CommArea_LocaliseData(void) { LocalData = gpMappedCommArea->PassedData; return &LocalData; } // buffer-size query for caller-app to setup args legally (if wanting to send big commands and unsure of space)... // int CommArea_GetMaxDataSize(void) { return sizeof(LocalData.bData); } int CommArea_GetMaxCommandStrlen(void) { return sizeof(LocalData.sCommand); } // getting this size wrong while sending error strings will never cause problems, they're just clipped // int CommArea_GetMaxErrorStrlen(void) { return sizeof(LocalData.sError); } void CommArea_ShutDown(void) { if (gpMappedCommArea) { UnmapViewOfFile( gpMappedCommArea ); gpMappedCommArea = NULL; } if (hFileMap) { CloseHandle(hFileMap); hFileMap = NULL; } } static LPCSTR CommArea_MapViewOfFile(void) { gpMappedCommArea = (CommArea_t *) MapViewOfFile(hFileMap, // HANDLE hFileMappingObject, // file-mapping object to map into FILE_MAP_ALL_ACCESS,// DWORD dwDesiredAccess, // access mode 0, // DWORD dwFileOffsetHigh, // high-order 32 bits of file offset 0, // DWORD dwFileOffsetLow, // low-order 32 bits of file offset 0 // DWORD dwNumberOfBytesToMap // number of bytes to map ); return gpMappedCommArea ? NULL : SystemErrorString(); } // return is either error message, or NULL for success... // LPCSTR CommArea_ServerInitOnceOnly(void) { LPCSTR psError = NULL; hFileMap = CreateFileMapping( INVALID_HANDLE_VALUE, // HANDLE hFile // handle to file to map NULL, // LPSECURITY_ATTRIBUTES lpFileMappingAttributes, PAGE_READWRITE, // DWORD flProtect, // protection for mapping object 0, // DWORD dwMaximumSizeHigh, // high-order 32 bits of object size sizeof(CommArea_USEONLYDURINGINIT), // DWORD dwMaximumSizeLow, // low-order 32 bits of object size sCOMMAREA_NAME // LPCTSTR lpName // name of file-mapping object ); DWORD dwError = GetLastError(); if (hFileMap == NULL || dwError == ERROR_ALREADY_EXISTS) { psError = SystemErrorString(dwError); } else { // ok so far, so get a map view of it... // psError = CommArea_MapViewOfFile(); if (!psError) { // Yeeehaaa.... let's init this baby... // ZEROMEMPTR(gpMappedCommArea); gpMappedCommArea->iVersion = iCOMMAREA_VERSION; gpMappedCommArea->iSize = sizeof(*gpMappedCommArea); bIAmServer = true; } } if (psError) { CommArea_ShutDown(); } return psError; } // return is either error message, or NULL for success... // LPCSTR CommArea_ClientInitOnceOnly(void) { LPCSTR psError = NULL; hFileMap = OpenFileMapping( FILE_MAP_ALL_ACCESS, // DWORD dwDesiredAccess, // access mode true, // BOOL bInheritHandle, // inherit flag sCOMMAREA_NAME // LPCTSTR lpName // pointer to name of file-mapping object ); if (!hFileMap) { psError = SystemErrorString(); } else { // ok so far, so get a map view of it... // psError = CommArea_MapViewOfFile(); if (!psError) { // let's give it a quick check for version differences... // static char sError[1024]; if (gpMappedCommArea->iVersion != iCOMMAREA_VERSION) { sprintf(sError,"CommArea version # mismatch, found %d but expected %d!",gpMappedCommArea->iVersion, iCOMMAREA_VERSION); psError = sError; } else { if (gpMappedCommArea->iSize != sizeof(*gpMappedCommArea)) { sprintf(sError,"CommArea struct size mismatch, found %d but expected %d!",gpMappedCommArea->iSize, sizeof(*gpMappedCommArea)); psError = sError; } else { // Yeeehaaa.... everything ok... // bIAmServer = false; // unnec. but clearer } } } } if (psError) { CommArea_ShutDown(); } return psError; } // can be safely called even when init failed... // bool CommArea_IsIdle(void) { if (!gpMappedCommArea) return true; return gpMappedCommArea->eStatus == cst_READY /* || gpMappedCommArea->eStatus == cst_CLIENTOK || gpMappedCommArea->eStatus == cst_SERVEROK */ ; } // call to query if any new commands are waiting for us, and return local (non-shared) ptrs if so. // // return = command string, else NULL for none // // This can be safely called even if the OnceOnlyInit call failed // LPCSTR CommArea_IsCommandWaiting(byte **ppbDataPassback, int *piDatasizePassback) { assert(ppbDataPassback); assert(piDatasizePassback); if (gpMappedCommArea && gpMappedCommArea->eStatus == (bIAmServer ? cst_CLIENTREQ : cst_SERVERREQ) ) { // make local (non-shared) copy of the data so we can access via ptrs without next command overwriting it... // CommArea_LocaliseData(); *ppbDataPassback = &LocalData.bData[0]; *piDatasizePassback = LocalData.iDataSize; return LocalData.sCommand; } return NULL; } // call this only (and always) after you send a command to tell you when the other app has finished responding to it // // return = response string (may be blank), else NULL for none (optional data fields are filled in if supplied) // LPCSTR CommArea_IsAckWaiting(byte **ppbDataPassback /* = NULL */, int *piDatasizePassback /* = NULL */) { assert(gpMappedCommArea); if (gpMappedCommArea && gpMappedCommArea->eStatus == (bIAmServer ? cst_CLIENTOK : cst_SERVEROK) ) { // make local (non-shared) copy of the data so we can access via ptrs without next command overwriting it... // CommArea_LocaliseData(); if ( ppbDataPassback) *ppbDataPassback = &LocalData.bData[0]; if ( piDatasizePassback) *piDatasizePassback = LocalData.iDataSize; return LocalData.sCommand; } return NULL; } // check outgoing data for size-exceeding, and copy to send buffer if everything ok... // // return = errmess or NULL for ok // static LPCSTR CommArea_SetupAndLegaliseOutgoingData(LPCSTR psCommand, byte *pbData, int iDataSize) { if (!gpMappedCommArea) return sERROR_COMMAREAUNINITIALISED; if (psCommand && strlen(psCommand) > sizeof(gpMappedCommArea->PassedData.sCommand)-1) return sERROR_COMMANDSTRINGTOOLONG; if (pbData && iDataSize > sizeof(gpMappedCommArea->PassedData.bData)) return sERROR_COMMANDDATATOOLONG; // seems ok, so copy it to outgoing... // CommStatus_e ePrevStatus = gpMappedCommArea->eStatus; gpMappedCommArea->eStatus = cst_WAIT; { if (psCommand) { strcpy(gpMappedCommArea->PassedData.sCommand,psCommand); } else { strcpy(gpMappedCommArea->PassedData.sCommand,""); } if (pbData) { memcpy(gpMappedCommArea->PassedData.bData, pbData, iDataSize); } else { //ZEROMEM(gpMappedCommArea->PassedData.bData); // not actually necessary } gpMappedCommArea->PassedData.iDataSize = iDataSize; } gpMappedCommArea->eStatus = ePrevStatus; return NULL; } // you can ignore the return code from this if you only call it sensibly, ie when you know you've just completed a // command task // // NOTE: if there's an error then the acknowledge is NOT sent!!!! // LPCSTR CommArea_CommandAck(LPCSTR psCommand /* = NULL */, byte *pbData /* = NULL */, int iDataSize /* = 0 */) { assert(gpMappedCommArea ); if (gpMappedCommArea && gpMappedCommArea->eStatus == (bIAmServer ? cst_CLIENTREQ : cst_SERVERREQ) ) { LPCSTR psError = CommArea_SetupAndLegaliseOutgoingData(psCommand, pbData, iDataSize); if (psError) { assert(0); return psError; } gpMappedCommArea->eStatus = (bIAmServer ? cst_SERVEROK : cst_CLIENTOK); return NULL; } assert(0); return sERROR_BADCOMMANDACKTIME; } // Call this to query for any pending error messages from the other program // // return = error, else NULL for none // // This can be safely called even if the OnceOnlyInit call failed // LPCSTR CommArea_IsErrorWaiting(void) { if (gpMappedCommArea && gpMappedCommArea->eStatus == (bIAmServer ? cst_CLIENTERR : cst_SERVERERR) ) { return CommArea_LocaliseData()->sError; } return NULL; } // Only call this after you've sent a command, then received either an acknowledgement or an error // // You can ignore the return error from this if you only call it sensibly, ie when you know there was an error // that wanted displaying... // LPCSTR CommArea_CommandClear(void) { assert(gpMappedCommArea ); if (gpMappedCommArea && ( gpMappedCommArea->eStatus == (bIAmServer ? cst_CLIENTERR : cst_SERVERERR) || gpMappedCommArea->eStatus == (bIAmServer ? cst_CLIENTOK : cst_SERVEROK) ) ) { gpMappedCommArea->eStatus = cst_READY; return NULL; } return sERROR_BADCOMMANDCLEARTIME; } // Call to report an error, this should only be done on receipt of a command that your app had a problem obeying... // // You can ignore the return code from this if you only call it sensibly, ie when you know you're just reporting an error // // NOTE: if there's an error (like you're calling this at the wrong time) then this error is NOT sent!!!! // LPCSTR CommArea_CommandError(LPCSTR psError) { assert(gpMappedCommArea); if (gpMappedCommArea && gpMappedCommArea->eStatus == (bIAmServer ? cst_CLIENTREQ : cst_SERVERREQ) ) { gpMappedCommArea->eStatus = cst_WAIT; { strncpy(gpMappedCommArea->PassedData.sError, psError, sizeof(gpMappedCommArea->PassedData.sError)-1); gpMappedCommArea->PassedData.sError[sizeof(gpMappedCommArea->PassedData.sError)-1] = '\0'; } gpMappedCommArea->eStatus = (bIAmServer ? cst_SERVERERR : cst_CLIENTERR); return NULL; } return sERROR_NOTASKPENDING; } // return NULL = success, else error message... (pbData can be NULL if desired, ditto iDataSize) // LPCSTR CommArea_IssueCommand(LPCSTR psCommand, byte *pbData /* = NULL */, int iDataSize /* = 0 */) { if (!CommArea_IsIdle()) return sERROR_BUSY; assert(gpMappedCommArea); if (!gpMappedCommArea) return sERROR_COMMAREAUNINITIALISED; LPCSTR psError = CommArea_SetupAndLegaliseOutgoingData(psCommand, pbData, iDataSize); if (psError) { assert(0); return psError; } gpMappedCommArea->eStatus = (bIAmServer ? cst_SERVERREQ : cst_CLIENTREQ); return NULL; } ////////////////////// eof ////////////////