#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));
    if (pObject!=NULL)
    {
        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
    }
    else
    {
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
    }
    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;
    }

    // We need to check whether there are already some characters waiting.
    // WaitCommEvent will never complete if there are characters waiting
    // and no new characters arrive at the serial port.  It's not cool, but
    // that's the way that it is.
    bResult = lhcpReadCommPort(
        (PSERIALPORT)pObject,
        pBuffer,
        dwSize,
        pdwBytesRead);

    if (*pdwBytesRead==0)
    {
        // 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 NoMutex;
    }

    // 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);
    lhcpDeleteObject(
        (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;
}


extern void APIENTRY lhcUsage()
{
    wprintf(
        L"Serial Port connection string:\n\n"
        L"    <port>@<speed>\n\n"
        L"where <port> is the serial port to use and <speed> is the serial line\n"
        L"speed to use for the connection.  The speed can be one of 9600, 19200,\n"
        L"38400, 57600 or 115200.  for example, com1@115200 would connect using\n"
        L"the serial port com1, and a baud rate of 115.2K bps.\n");
}