#include "std.h" // This is the communications mask used by our serial port. More may be // necessary, but for right now, this seems to work. #define EV_SERIAL EV_RXCHAR | EV_ERR | EV_BREAK #define SERIALPORT_NAME L"Serial Port" // This GUID is used to identify objects opened by this library. It is // placed in the m_Secret member of the SERIALPORT structure. Any external // interface accepting a SERIALPORT object as a parameter should check this // out before using the structure. static const GUID uuidSerialPortObjectGuid = { 0x86ae9c9b, 0x9444, 0x4d00, { 0x84, 0xbb, 0xc1, 0xd9, 0xc2, 0xd9, 0xfb, 0xf3 } }; // Structure defining an open serial port object. All external users of this // library will only have a void pointer to one of these, and the structure is // not published anywhere. This abstration makes it more difficult for the // user to mess things up. typedef struct __SERIALPORT { GUID m_Secret; // Identifies this as a serial port HANDLE m_hPort; // Handle to the opened serial port HANDLE m_hAbort; // Event signalled when port is closing HANDLE m_hReadMutex; // Only one thread allowed to read a port HANDLE m_hWriteMutex; // Only one thread allowed to read a port HANDLE m_hCloseMutex; // Only one thread allowed to close a port HANDLE m_hReadComplete; // Event to signal read completion HANDLE m_hWriteComplete; // Event to signal write completion } SERIALPORT, *PSERIALPORT; extern PVOID APIENTRY lhcOpen( PCWSTR pcszPortSpec); extern BOOL APIENTRY lhcRead( PVOID pObject, PVOID pBuffer, DWORD dwSize, PDWORD pdwBytesRead); extern BOOL APIENTRY lhcWrite( PVOID pObject, PVOID pBuffer, DWORD dwSize); extern BOOL APIENTRY lhcClose( PVOID pObject); extern DWORD APIENTRY lhcGetLibraryName( PWSTR pszBuffer, DWORD dwSize); BOOL lhcpAcquireWithAbort( HANDLE hMutex, HANDLE hAbort); BOOL lhcpAcquireReadWithAbort( PSERIALPORT pObject); BOOL lhcpAcquireWriteWithAbort( PSERIALPORT pObject); BOOL lhcpAcquireCloseWithAbort( PSERIALPORT pObject); BOOL lhcpAcquireReadAndWrite( PSERIALPORT pObject); BOOL lhcpReleaseRead( PSERIALPORT pObject); BOOL lhcpReleaseWrite( PSERIALPORT pObject); BOOL lhcpReleaseClose( PSERIALPORT pObject); BOOL lhcpIsValidObject( PSERIALPORT pObject); PSERIALPORT lhcpCreateNewObject(); void lhcpDeleteObject( PSERIALPORT pObject); BOOL lhcpParseParameters( PCWSTR pcszPortSpec, PWSTR* pszPort, PDWORD pdwBaudRate); void lhcpParseParametersFree( PWSTR* pszPort, PDWORD pdwBaudRate); BOOL lhcpSetCommState( HANDLE hPort, DWORD dwBaudRate); BOOL lhcpWaitForCommEvent( PSERIALPORT pObject, PDWORD pdwEventMask); BOOL lhcpReadCommPort( PSERIALPORT pObject, PVOID pBuffer, DWORD dwSize, PDWORD pdwBytesRead); BOOL lhcpWriteCommPort( PSERIALPORT pObject, PVOID pBuffer, DWORD dwSize); BOOL lhcpAcquireWithAbort(HANDLE hMutex, HANDLE hAbort) { HANDLE hWaiters[2]; DWORD dwWaitResult; hWaiters[0] = hAbort; hWaiters[1] = hMutex; // We should honour the m_hAbort event, since this is signalled when the // port is closed by another thread dwWaitResult = WaitForMultipleObjects( 2, hWaiters, FALSE, INFINITE); if (WAIT_OBJECT_0==dwWaitResult) { goto Error; } else if ((WAIT_OBJECT_0+1)!=dwWaitResult) { // This should never, ever happen - so I will put a debug breapoint // in here (checked only). #ifdef DBG DebugBreak(); #endif goto Error; } return TRUE; // We have acquired the write mutex Error: return FALSE; // We have aborted } BOOL lhcpAcquireReadWithAbort(PSERIALPORT pObject) { return lhcpAcquireWithAbort( pObject->m_hReadMutex, pObject->m_hAbort); } BOOL lhcpAcquireWriteWithAbort(PSERIALPORT pObject) { return lhcpAcquireWithAbort( pObject->m_hWriteMutex, pObject->m_hAbort); } BOOL lhcpAcquireCloseWithAbort(PSERIALPORT pObject) { return lhcpAcquireWithAbort( pObject->m_hCloseMutex, pObject->m_hAbort); } BOOL lhcpAcquireReadAndWrite(PSERIALPORT pObject) { HANDLE hWaiters[2]; DWORD dwWaitResult; hWaiters[0] = pObject->m_hReadMutex; hWaiters[1] = pObject->m_hWriteMutex; dwWaitResult = WaitForMultipleObjects( 2, hWaiters, TRUE, 1000); // Timeout after 1 second if (WAIT_OBJECT_0!=dwWaitResult) { goto Error; } return TRUE; // We have acquired the write mutex Error: return FALSE; // We have aborted } BOOL lhcpReleaseRead(PSERIALPORT pObject) { return ReleaseMutex( pObject->m_hReadMutex); } BOOL lhcpReleaseWrite(PSERIALPORT pObject) { return ReleaseMutex( pObject->m_hWriteMutex); } BOOL lhcpReleaseClose(PSERIALPORT pObject) { return ReleaseMutex( pObject->m_hCloseMutex); } BOOL lhcpIsValidObject(PSERIALPORT pObject) { BOOL bResult; __try { bResult = IsEqualGUID( &uuidSerialPortObjectGuid, &pObject->m_Secret); } __except(EXCEPTION_EXECUTE_HANDLER) { SetLastError( ERROR_INVALID_HANDLE); bResult = FALSE; goto Done; } Done: return bResult; } PSERIALPORT lhcpCreateNewObject() { PSERIALPORT pObject = (PSERIALPORT)malloc( sizeof(SERIALPORT)); pObject->m_Secret = uuidSerialPortObjectGuid; pObject->m_hPort = INVALID_HANDLE_VALUE; pObject->m_hAbort = NULL; pObject->m_hReadMutex = NULL; // Only one thread allowed to read a port pObject->m_hWriteMutex = NULL; // Only one thread allowed to read a port pObject->m_hCloseMutex = NULL; // Only one thread allowed to read a port pObject->m_hReadComplete = NULL; // Event to signal read completion pObject->m_hWriteComplete = NULL; // Event to signal write completion return pObject; } void lhcpDeleteObject(PSERIALPORT pObject) { if (pObject==NULL) { return; } ZeroMemory( &(pObject->m_Secret), sizeof(pObject->m_Secret)); if (pObject->m_hPort!=INVALID_HANDLE_VALUE) { CloseHandle( pObject->m_hPort); } if (pObject->m_hAbort!=NULL) { CloseHandle( pObject->m_hAbort); } if (pObject->m_hReadMutex!=NULL) { CloseHandle( pObject->m_hReadMutex); } if (pObject->m_hWriteMutex!=NULL) { CloseHandle( pObject->m_hWriteMutex); } if (pObject->m_hCloseMutex!=NULL) { CloseHandle( pObject->m_hCloseMutex); } if (pObject->m_hReadComplete!=NULL) { CloseHandle( pObject->m_hReadComplete); } if (pObject->m_hWriteComplete!=NULL) { CloseHandle( pObject->m_hWriteComplete); } FillMemory( pObject, sizeof(SERIALPORT), 0x00); free( pObject); } BOOL lhcpParseParameters(PCWSTR pcszPortSpec, PWSTR* pszPort, PDWORD pdwBaudRate) { PWSTR pszSettings; *pszPort = malloc( (wcslen(pcszPortSpec) + 5) * sizeof(WCHAR)); if (NULL==*pszPort) { SetLastError( ERROR_NOT_ENOUGH_MEMORY); goto Error; } wcscpy( *pszPort, L"\\\\.\\"); // Append the device prefix to the port name wcscat( *pszPort, pcszPortSpec); pszSettings = wcschr( // Find where the settings start *pszPort, L'@'); if (NULL==pszSettings) { SetLastError( ERROR_INVALID_PARAMETER); goto Error; } *pszSettings++ = L'\0'; // Separate the strings *pdwBaudRate = 0; while (*pszSettings!=L'\0' && *pdwBaudRate<115200) { if (L'0'<=*pszSettings && *pszSettings<=L'9') { *pdwBaudRate *= 10; *pdwBaudRate += *pszSettings - L'0'; pszSettings++; } else { break; } } if (*pszSettings!=L'0' && *pdwBaudRate!=9600 && *pdwBaudRate!=19200 && *pdwBaudRate!=38400 && *pdwBaudRate!=57600 && *pdwBaudRate!=115200) { SetLastError( ERROR_INVALID_PARAMETER); goto Error; } return TRUE; Error: lhcpParseParametersFree( pszPort, pdwBaudRate); return FALSE; } void lhcpParseParametersFree(PWSTR* pszPort, PDWORD pdwBaudRate) { if (*pszPort != NULL) { free(*pszPort); *pszPort = NULL; } *pdwBaudRate = 0; } BOOL lhcpSetCommState(HANDLE hPort, DWORD dwBaudRate) { DCB MyDCB; COMMTIMEOUTS CommTimeouts; BOOL bResult; ZeroMemory( &MyDCB, sizeof(DCB)); MyDCB.DCBlength = sizeof(DCB); MyDCB.BaudRate = dwBaudRate; MyDCB.fBinary = 1; MyDCB.fParity = 1; MyDCB.fOutxCtsFlow = 0; MyDCB.fOutxDsrFlow = 0; MyDCB.fDtrControl = 1; MyDCB.fDsrSensitivity = 0; MyDCB.fTXContinueOnXoff = 1; MyDCB.fOutX = 1; MyDCB.fInX = 1; MyDCB.fErrorChar = 0; MyDCB.fNull = 0; MyDCB.fRtsControl = 1; MyDCB.fAbortOnError = 0; MyDCB.XonLim = 0x50; MyDCB.XoffLim = 0xc8; MyDCB.ByteSize = 0x8; MyDCB.Parity = 0; MyDCB.StopBits = 0; MyDCB.XonChar = 17; MyDCB.XoffChar = 19; MyDCB.ErrorChar = 0; MyDCB.EofChar = 0; MyDCB.EvtChar = 0; bResult = SetCommState( hPort, &MyDCB); if (!bResult) { goto Error; } CommTimeouts.ReadIntervalTimeout = 0xffffffff; //MAXDWORD CommTimeouts.ReadTotalTimeoutMultiplier = 0x0; //MAXDWORD CommTimeouts.ReadTotalTimeoutConstant = 0x0; CommTimeouts.WriteTotalTimeoutMultiplier = 0; CommTimeouts.WriteTotalTimeoutConstant = 0; bResult = SetCommTimeouts( hPort, &CommTimeouts); if (!bResult) { goto Error; } bResult = SetCommMask( hPort, EV_SERIAL); if (!bResult) { goto Error; } return TRUE; Error: return FALSE; } BOOL lhcpWaitForCommEvent(PSERIALPORT pObject, PDWORD pdwEventMask) { OVERLAPPED Overlapped; BOOL bResult; HANDLE hWaiters[2]; DWORD dwWaitResult; DWORD dwBytesTransferred; // I have no idea whether this is necessary, so I will do it just to be // on the safe side. ZeroMemory( &Overlapped, sizeof(OVERLAPPED)); Overlapped.hEvent = pObject->m_hReadComplete; // Start waiting for a comm event bResult = WaitCommEvent( pObject->m_hPort, pdwEventMask, &Overlapped); if (!bResult && GetLastError()!=ERROR_IO_PENDING) { goto Error; } hWaiters[0] = pObject->m_hAbort; hWaiters[1] = pObject->m_hReadComplete; // Let's wait for the operation to complete. This will quit waiting if // the m_hAbort event is signalled. dwWaitResult = WaitForMultipleObjects( 2, hWaiters, FALSE, INFINITE); if (WAIT_OBJECT_0==dwWaitResult) { // The m_hAbort event was signalled. This means that Close was called // on this serial port object. So let's cancel the pending IO. CancelIo( pObject->m_hPort); // The serial port object is being closed, so let's call it invalid. SetLastError( ERROR_INVALID_HANDLE); goto Error; } else if ((WAIT_OBJECT_0+1)!=dwWaitResult) { // This should never, ever happen - so I will put a debug breapoint // in here (checked only). #ifdef DBG DebugBreak(); #endif goto Error; } // Check the success or failure of the operation bResult = GetOverlappedResult( pObject->m_hPort, &Overlapped, &dwBytesTransferred, TRUE); if (!bResult) { goto Error; } return TRUE; Error: return FALSE; } BOOL lhcpReadCommPort( PSERIALPORT pObject, PVOID pBuffer, DWORD dwSize, PDWORD pdwBytesRead) { OVERLAPPED Overlapped; BOOL bResult; DWORD dwWaitResult; HANDLE hWaiters[2]; // I have no idea whether this is necessary, so I will do it just to be // on the safe side. ZeroMemory( &Overlapped, sizeof(OVERLAPPED)); Overlapped.hEvent = pObject->m_hReadComplete; // We can now read the comm port bResult = ReadFile( pObject->m_hPort, pBuffer, dwSize, pdwBytesRead, &Overlapped); if (!bResult && GetLastError()!=ERROR_IO_PENDING) { goto Error; } hWaiters[0] = pObject->m_hAbort; hWaiters[1] = pObject->m_hReadComplete; // Let's wait for the operation to complete. This will quit waiting if // the m_hAbort event is signalled. dwWaitResult = WaitForMultipleObjects( 2, hWaiters, FALSE, INFINITE); if (WAIT_OBJECT_0==dwWaitResult) { // The m_hAbort event was signalled. This means that Close was called // on this serial port object. So let's cancel the pending IO. CancelIo( pObject->m_hPort); // The serial port object is being closed, so let's call it invalid. SetLastError( ERROR_INVALID_HANDLE); goto Error; } else if ((WAIT_OBJECT_0+1)!=dwWaitResult) { // This should never, ever happen - so I will put a debug breapoint // in here (checked only). #ifdef DBG DebugBreak(); #endif goto Error; } // Check the success or failure of the read operation bResult = GetOverlappedResult( pObject->m_hPort, &Overlapped, pdwBytesRead, TRUE); if (!bResult) { goto Error; } return TRUE; Error: return FALSE; } BOOL lhcpWriteCommPort( PSERIALPORT pObject, PVOID pBuffer, DWORD dwSize) { OVERLAPPED Overlapped; BOOL bResult; DWORD dwBytesWritten; DWORD dwWaitResult; HANDLE hWaiters[2]; // I have no idea whether this is necessary, so I will do it just to be // on the safe side. ZeroMemory( &Overlapped, sizeof(OVERLAPPED)); Overlapped.hEvent = pObject->m_hWriteComplete; // We can now read the comm port bResult = WriteFile( pObject->m_hPort, pBuffer, dwSize, &dwBytesWritten, &Overlapped); if (!bResult && GetLastError()!=ERROR_IO_PENDING) { goto Error; } hWaiters[0] = pObject->m_hAbort; hWaiters[1] = pObject->m_hWriteComplete; // Let's wait for the operation to complete. This will quit waiting if // the m_hAbort event is signalled. If the read operation completed // immediately, then this wait will succeed immediately. dwWaitResult = WaitForMultipleObjects( 2, hWaiters, FALSE, INFINITE); if (WAIT_OBJECT_0==dwWaitResult) { // The m_hAbort event was signalled. This means that Close was called // on this serial port object. So let's cancel the pending IO. CancelIo( pObject->m_hPort); // The serial port object is being closed, so let's call it invalid. SetLastError( ERROR_INVALID_HANDLE); goto Error; } else if ((WAIT_OBJECT_0+1)!=dwWaitResult) { // This should never, ever happen - so I will put a debug breapoint // in here (checked only). #ifdef DBG DebugBreak(); #endif goto Error; } // Check the success or failure of the write operation bResult = GetOverlappedResult( pObject->m_hPort, &Overlapped, &dwBytesWritten, TRUE); if (!bResult) { goto Error; } return TRUE; Error: return FALSE; } extern PVOID APIENTRY lhcOpen(PCWSTR pcszPortSpec) { BOOL bResult; PWSTR pszPort; DWORD dwBaudRate; PSERIALPORT pObject = NULL; DCB MyDCB; bResult = lhcpParseParameters( pcszPortSpec, &pszPort, &dwBaudRate); if (!bResult) { goto Error; } // Allocate space and initialize the serial port object pObject = lhcpCreateNewObject(); if (NULL==pObject) { goto Error; } // Open the serial port pObject->m_hPort = CreateFileW( pszPort, GENERIC_ALL, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if (INVALID_HANDLE_VALUE==pObject->m_hPort) { goto Error; } // Set the properties of the serial port bResult = lhcpSetCommState( pObject->m_hPort, dwBaudRate); if (!bResult) { goto Error; } // This event will be set when we want to close the port pObject->m_hAbort = CreateEvent( NULL, TRUE, FALSE, NULL); if (NULL==pObject->m_hAbort) { goto Error; } // This event will be used for overlapped reading from the port pObject->m_hReadComplete = CreateEvent( NULL, TRUE, FALSE, NULL); if (NULL==pObject->m_hReadComplete) { goto Error; } // This event will be used for overlapped writing to the port pObject->m_hWriteComplete = CreateEvent( NULL, TRUE, FALSE, NULL); if (NULL==pObject->m_hWriteComplete) { goto Error; } // This mutex will ensure that only one thread can read at a time pObject->m_hReadMutex = CreateMutex( NULL, FALSE, NULL); if (NULL==pObject->m_hReadMutex) { goto Error; } // This mutex will ensure that only one thread can write at a time pObject->m_hWriteMutex = CreateMutex( NULL, FALSE, NULL); if (NULL==pObject->m_hWriteMutex) { goto Error; } // This mutex will ensure that only one thread can close the port pObject->m_hCloseMutex = CreateMutex( NULL, FALSE, NULL); if (NULL==pObject->m_hCloseMutex) { goto Error; } // Free up the temporary memory used to parse the parameters lhcpParseParametersFree( &pszPort, &dwBaudRate); // Return a pointer to the new object return pObject; Error: lhcpParseParametersFree( &pszPort, &dwBaudRate); lhcpDeleteObject( pObject); return NULL; } extern BOOL APIENTRY lhcRead( PVOID pObject, PVOID pBuffer, DWORD dwSize, PDWORD pdwBytesRead) { OVERLAPPED Overlapped; DWORD dwEventMask; BOOL bResult; // Firstly, we need to check whether the pointer that got passed in // points to a valid SERIALPORT object if (!lhcpIsValidObject(pObject)) { goto NoMutex; } bResult = lhcpAcquireReadWithAbort( (PSERIALPORT)pObject); if (!bResult) { SetLastError( ERROR_INVALID_HANDLE); goto NoMutex; } // Wait for something to happen to the serial port bResult = lhcpWaitForCommEvent( (PSERIALPORT)pObject, &dwEventMask); if (!bResult) { goto Error; } // We should now have a valid serial port event, so let's read the port. bResult = lhcpReadCommPort( (PSERIALPORT)pObject, pBuffer, dwSize, pdwBytesRead); if (!bResult) { goto Error; } lhcpReleaseRead( (PSERIALPORT)pObject); return TRUE; Error: lhcpReleaseRead( (PSERIALPORT)pObject); NoMutex: return FALSE; } extern BOOL APIENTRY lhcWrite( PVOID pObject, PVOID pBuffer, DWORD dwSize) { OVERLAPPED Overlapped; BOOL bResult; // Firstly, we need to check whether the pointer that got passed in // points to a valid SERIALPORT object if (!lhcpIsValidObject(pObject)) { goto NoMutex; } // Block until it is your turn bResult = lhcpAcquireWriteWithAbort( pObject); if (!bResult) { SetLastError( ERROR_INVALID_HANDLE); goto NoMutex; } // Wait for something to happen to the serial port bResult = lhcpWriteCommPort( (PSERIALPORT)pObject, pBuffer, dwSize); if (!bResult) { goto Error; } lhcpReleaseWrite( (PSERIALPORT)pObject); return TRUE; Error: lhcpReleaseWrite( (PSERIALPORT)pObject); NoMutex: return FALSE; } extern BOOL APIENTRY lhcClose(PVOID pObject) { BOOL bResult; // Firstly, we need to check whether the pointer that got passed in // points to a valid SERIALPORT object if (!lhcpIsValidObject(pObject)) { goto Error; } // We need to ensure that we are the only thread closing this object bResult = lhcpAcquireCloseWithAbort( pObject); if (!bResult) { SetLastError( ERROR_INVALID_HANDLE); goto NoMutex; } // Signal everyone to quit doing what they're doing. Any new threads // calling lhcRead and lhcWrite will be immediately sent packing, since // the m_hAbort event is waited on along with the relevant mutex. bResult = SetEvent( ((PSERIALPORT)pObject)->m_hAbort); if (!bResult) { goto Error; } // Now acquire the read and write mutexes so that no-one else will try to // access this object to read or write. Abort does not apply, since we // have already signalled it. We know that we are closing, and we need // the read and write mutexes. bResult = lhcpAcquireReadAndWrite( (PSERIALPORT)pObject); if (!bResult) { SetLastError( ERROR_INVALID_HANDLE); goto Error; } // Closes all of the open handles, erases the secret and frees up the // memory associated with the object. We can close the mutex objects, // even though we are the owners, since we can guarantee that no-one // else is waiting on them. The m_hAbort event being signalled will // ensure this. lhcpDeleteObject( (PSERIALPORT)pObject); return TRUE; Error: lhcpReleaseClose( (PSERIALPORT)pObject); NoMutex: return FALSE; } extern DWORD APIENTRY lhcGetLibraryName( PWSTR pszBuffer, DWORD dwSize) { DWORD dwNameSize = wcslen(SERIALPORT_NAME)+1; // If zero is passed in as the buffer length, we will return the // required buffer size in characters, as calulated above. If the // incoming buffer size is not zero, and smaller than the required // buffer size, we return 0 (failure) with a valid error code. Notice // that in the case where the incoming size is zero, we don't touch // the buffer pointer at all. if (dwSize!=0 && dwSize < dwNameSize) { SetLastError( ERROR_INSUFFICIENT_BUFFER); dwNameSize = 0; } else { wcscpy( pszBuffer, SERIALPORT_NAME); } return dwNameSize; }