mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1075 lines
36 KiB
1075 lines
36 KiB
#include "precomp.h"
|
|
|
|
/* tprtintf.cpp
|
|
*
|
|
* Copyright (c) 1996 by Microsoft Corporation
|
|
*
|
|
* Abstract:
|
|
* This is the implementation file for the Win32 PSTN transport stack.
|
|
* This file contains all of the public functions needed to use
|
|
* the PSTN stack. Transprt.cpp really performs three functions:
|
|
*
|
|
* 1. Since this is a Win32 DLL, it provides a C to C++ conversion
|
|
* layer. The functions called by the user are not processed
|
|
* in this file, they are passed on down to the C++ object that
|
|
* handles it (g_pController). If we supplied a C++
|
|
* interface to the user, it would force the user to use C++ and
|
|
* it would force them to use a C++ compiler that used the same
|
|
* name-mangling algorithm as our compiler(s).
|
|
*
|
|
* 2. It holds the code that acts as the administrator for the PSTN
|
|
* thread. This is a fairly straightforward piece of code that
|
|
* is explained later in this comment block.
|
|
*
|
|
* 3. This file provides thread synchronization to the underlying
|
|
* code by protecting it with a CRITICAL_SECTION. This prevents
|
|
* multiple threads from executing the code at the same time.
|
|
*
|
|
*
|
|
*
|
|
* CREATING A SECONDARY "PSTN" THREAD
|
|
*
|
|
* Since this is a Win32 DLL, we create a seperate thread to maintain the
|
|
* PSTN DLL. During initialization of the DLL, we create the secondary or
|
|
* PSTN thread which runs in response to timeouts and system events. It
|
|
* uses the WaitForMultipleObjects() system call to wait for events that
|
|
* need to be processed.
|
|
*
|
|
* This DLL is event driven. When a new com port is opened and
|
|
* initialized, it uses Win32 event objects to signal significant events.
|
|
* Three events that can occur are read events, write events, and control
|
|
* events. An example of a control event is a change in the carrier
|
|
* detect signal.
|
|
*
|
|
* When the ComPort object initializes an event object, it registers the
|
|
* object with the PSTN thread, by placing it in the PSTN Event_List. It
|
|
* not only registers the event, but also all of the data needed to
|
|
* identify the event. The following structure must be filled out by the
|
|
* ComPort for each event that it registers.
|
|
* struct
|
|
* {
|
|
* HANDLE event; // event object
|
|
* WIN32Event event_type; // READ, WRITE, CONTROL
|
|
* BOOL delete_event; // FLAG, delete it?
|
|
* PhysicalHandle physical_handle; // Handle associated with
|
|
* // ComPort
|
|
* IObject * physical_layer; // this pointer of comport
|
|
* } EventObject;
|
|
*
|
|
* When an event occurs, the PSTN thread makes a call directly to the
|
|
* ComPort object to notify it. If the event that occured was a WRITE
|
|
* event, we also issue a PollTransmitter() function call to the
|
|
* g_pController object so that it can transmit more data.
|
|
*
|
|
* If a READ event occurs, we call the ComPort object directly to notify
|
|
* it that the event occurred. We then issue a PollReceiver() function
|
|
* call to the g_pController object so that we can process the
|
|
* received data. We then issue a PollTransmitter() to the
|
|
* g_pController object. Logically, this doesn't make much sense,
|
|
* but if you study the Q922 code, you'll see that we sometimes don't
|
|
* transmit data until we receive notification that previously transmitted
|
|
* data was successfully transmitted.
|
|
*
|
|
* If a CONTROL event occurs, we call the ComPort object directly to
|
|
* notify it that the event occurred. We then issue a PollReceiver()
|
|
* function call to the g_pController object so that the CONTROL
|
|
* event will be processed.
|
|
*
|
|
*
|
|
*
|
|
* X.214 INTERFACE
|
|
*
|
|
* You will notice that many of the entry points to this DLL were taken
|
|
* from the X.214 service definition. These entry points are consistent
|
|
* between all DataBeam Transport DLLs. This gives the user application
|
|
* a consistent interface.
|
|
*
|
|
*
|
|
* PHYSICAL API
|
|
*
|
|
* Some of the entry points to this DLL are specific to the creation
|
|
* and use of LOGICAL connections, and some of the entry points are
|
|
* specific to the creation and deletion of PHYSICAL connections. The
|
|
* out-of-band physical API is new as of version 2.0 and provides the
|
|
* user with an optional way of making PHYSICAL connections. In earlier
|
|
* versions of the Transport stacks, there was no Physical API and PHYSICAL
|
|
* connections were made in-band using the TConnectRequest() and
|
|
* TDisconnectRequest() calls. This form of call control is still
|
|
* supported in this version.
|
|
*
|
|
* Although the user can use out-of-band (PHYSICAL call control) and
|
|
* in-band call control, the DLL must be put in one mode or the other.
|
|
* The DLL can NOT function in both modes simultaneously. The user
|
|
* determines the DLL's mode of operation by the calls it makes to it.
|
|
* If the user makes the TPhysicalInitialize() function call to the DLL
|
|
* before it issues the TInitialize() function call, the DLL is in the
|
|
* out-of-band call control mode. If the user only makes the TInitialize()
|
|
* function call, by default the DLL will be in the in-band call control
|
|
* mode.
|
|
*
|
|
* If MCS will be using this DLL, it makes the TInitialize() function call.
|
|
* Therefore, if the user wants to use the out-of-band call control mode,
|
|
* it should call TPhysicalInitialize() before it initializes MCS.
|
|
*
|
|
*
|
|
* Static Variables:
|
|
* Static_Instance_Handle - Instance handle passed to the DLL when it
|
|
* is started. The instance handle is used
|
|
* for Win32 system calls.
|
|
* g_pController - This is a pointer to the controller that
|
|
* maintains the T123 stacks.
|
|
* m_cUsers - This is incremented each time someone
|
|
* calls the TInitialize function. It is
|
|
* decremented each time someone calls the
|
|
* TCleanup function. When this value
|
|
* reaches 0, we destroy the transport
|
|
* controller.
|
|
* g_hWorkerThread - This global variable keeps the handle of
|
|
* the thread we spawn on initialization.
|
|
* g_dwWorkerThreadID - Thread identifier
|
|
* g_hWorkerThreadEvent - This is an event object that is used to
|
|
* synchronize actions between the 2 threads.
|
|
* g_hThreadTerminateEvent - This event is used to signal the PSTN
|
|
* thread that it needs to terminate.
|
|
* ComPort_List - This is a list of the ComPort objects
|
|
* serviced by this DLL.
|
|
* class.
|
|
* Number_Of_Diagnostic_Users - This variable keeps up with the number of
|
|
* people using who called T120DiagnosticCreate.
|
|
* We won't delete the Diagnostic class until
|
|
* an equal number of people call
|
|
* T120DiagnosticDestroy.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
** External Interfaces
|
|
*/
|
|
|
|
#include "tprtintf.h"
|
|
#include "comport.h"
|
|
|
|
#define TPRTINTF_MAXIMUM_WAIT_OBJECTS (1 + 16)
|
|
|
|
/*
|
|
** Global variables used within the C to C++ converter.
|
|
*/
|
|
HINSTANCE g_hDllInst = NULL;
|
|
CTransportInterface *g_pTransportInterface = NULL;
|
|
CRITICAL_SECTION g_csPSTN;
|
|
Timer *g_pSystemTimer = NULL;
|
|
TransportController *g_pController = NULL;
|
|
HANDLE g_hWorkerThread = NULL;
|
|
DWORD g_dwWorkerThreadID = 0;
|
|
HANDLE g_hWorkerThreadEvent= NULL;
|
|
HANDLE g_hThreadTerminateEvent = NULL;
|
|
DictionaryClass *g_pComPortList2 = NULL;
|
|
SListClass *g_pPSTNEventList = NULL;
|
|
TransportCallback g_pfnT120Notify = NULL;
|
|
void *g_pUserData = NULL;
|
|
BOOL g_fEventListChanged = FALSE;
|
|
|
|
/*
|
|
** The following defines the MCATPSTN User window class name.
|
|
** This name must be unique, system wide.
|
|
*/
|
|
|
|
#define MCATPSTN_USER_WINDOW_CLASS_NAME "NMPSTN USER Message Window"
|
|
|
|
/*
|
|
/*
|
|
** Timer duration. We will poll the DLL every X milliseconds. During
|
|
** this time, we can do any maintenance that is necessary
|
|
*/
|
|
#define MCATPSTN_TIMER_DURATION 250
|
|
|
|
/*
|
|
** These are prototypes
|
|
*/
|
|
DWORD T123_WorkerThreadProc(DWORD *);
|
|
|
|
|
|
/*
|
|
* BOOL WINAPI DllMain (
|
|
* HINSTANCE instance_handle,
|
|
* DWORD reason,
|
|
* LPVOID)
|
|
*
|
|
* Functional Description:
|
|
* This routine is the DLL startup routine. It is analagous to the
|
|
* constructor of a class. It is called by Windows when the DLL is
|
|
* loaded and when other significant events occur.
|
|
*/
|
|
#ifdef _DEBUG
|
|
|
|
#define INIT_DBG_ZONE_DATA
|
|
#include "fsdiag2.h"
|
|
|
|
#endif /* _DEBUG */
|
|
|
|
|
|
BOOL WINAPI DllMain(HINSTANCE hDllInst, DWORD reason, LPVOID)
|
|
{
|
|
switch (reason)
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
g_hDllInst = hDllInst;
|
|
::DisableThreadLibraryCalls(hDllInst);
|
|
#ifdef _DEBUG
|
|
MLZ_DbgInit((PSTR *) &c_apszDbgZones[0],
|
|
(sizeof(c_apszDbgZones) / sizeof(c_apszDbgZones[0])) - 1);
|
|
#endif
|
|
DBG_INIT_MEMORY_TRACKING(hDllInst);
|
|
::InitializeCriticalSection(&g_csPSTN);
|
|
break;
|
|
|
|
case DLL_PROCESS_DETACH:
|
|
::DeleteCriticalSection(&g_csPSTN);
|
|
DBG_CHECK_MEMORY_TRACKING(hDllInst);
|
|
#ifdef _DEBUG
|
|
MLZ_DbgDeInit();
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
TransportError WINAPI T123_CreateTransportInterface(ILegacyTransport **pp)
|
|
{
|
|
TransportError rc;
|
|
|
|
if (NULL != pp)
|
|
{
|
|
*pp = NULL;
|
|
|
|
if (NULL == g_pTransportInterface)
|
|
{
|
|
rc = TRANSPORT_MEMORY_FAILURE;
|
|
|
|
DBG_SAVE_FILE_LINE
|
|
g_pTransportInterface = new CTransportInterface(&rc);
|
|
if (NULL != g_pTransportInterface && TRANSPORT_NO_ERROR == rc)
|
|
{
|
|
*pp = (ILegacyTransport *) g_pTransportInterface;
|
|
return TRANSPORT_NO_ERROR;
|
|
}
|
|
|
|
delete g_pTransportInterface;
|
|
g_pTransportInterface = NULL;
|
|
return rc;
|
|
}
|
|
|
|
ASSERT(0);
|
|
return TRANSPORT_ALREADY_INITIALIZED;
|
|
}
|
|
|
|
return TRANSPORT_INVALID_PARAMETER;
|
|
}
|
|
|
|
|
|
CTransportInterface::CTransportInterface(TransportError *rc)
|
|
:
|
|
m_cUsers(0)
|
|
{
|
|
g_hWorkerThread = NULL;
|
|
g_dwWorkerThreadID = 0;
|
|
|
|
g_pfnT120Notify = NULL;
|
|
g_pUserData = NULL;
|
|
|
|
g_fEventListChanged = FALSE;
|
|
|
|
DBG_SAVE_FILE_LINE
|
|
g_pSystemTimer = new Timer;
|
|
ASSERT(NULL != g_pSystemTimer);
|
|
|
|
DBG_SAVE_FILE_LINE
|
|
g_hWorkerThreadEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
ASSERT(NULL != g_hWorkerThreadEvent);
|
|
|
|
DBG_SAVE_FILE_LINE
|
|
g_hThreadTerminateEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
ASSERT(NULL != g_hThreadTerminateEvent);
|
|
|
|
DBG_SAVE_FILE_LINE
|
|
g_pComPortList2 = new DictionaryClass(TRANSPORT_HASHING_BUCKETS);
|
|
ASSERT(NULL != g_pComPortList2);
|
|
|
|
DBG_SAVE_FILE_LINE
|
|
g_pPSTNEventList = new SListClass;
|
|
ASSERT(NULL != g_pPSTNEventList);
|
|
|
|
*rc = (g_pSystemTimer &&
|
|
g_hWorkerThreadEvent &&
|
|
g_hThreadTerminateEvent &&
|
|
g_pComPortList2 &&
|
|
g_pPSTNEventList)
|
|
?
|
|
TRANSPORT_NO_ERROR :
|
|
TRANSPORT_INITIALIZATION_FAILED;
|
|
ASSERT(TRANSPORT_NO_ERROR == *rc);
|
|
}
|
|
|
|
|
|
CTransportInterface::~CTransportInterface(void)
|
|
{
|
|
if (m_cUsers > 0)
|
|
{
|
|
// TCleanup() was not properly called enough times in the process.
|
|
// Force final cleanup.
|
|
m_cUsers = 1;
|
|
TCleanup();
|
|
}
|
|
|
|
delete g_pSystemTimer;
|
|
g_pSystemTimer = NULL;
|
|
|
|
delete g_pComPortList2;
|
|
g_pComPortList2 = NULL;
|
|
|
|
delete g_pPSTNEventList;
|
|
g_pPSTNEventList = NULL;
|
|
|
|
g_pfnT120Notify = NULL;
|
|
g_pUserData = NULL;
|
|
|
|
if (NULL != g_hWorkerThreadEvent)
|
|
{
|
|
::CloseHandle(g_hWorkerThreadEvent);
|
|
g_hWorkerThreadEvent = NULL;
|
|
}
|
|
|
|
if (NULL != g_hThreadTerminateEvent)
|
|
{
|
|
::CloseHandle(g_hThreadTerminateEvent);
|
|
g_hThreadTerminateEvent = NULL;
|
|
}
|
|
|
|
if (NULL != g_hWorkerThread)
|
|
{
|
|
::CloseHandle(g_hWorkerThread);
|
|
g_hWorkerThread = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void CTransportInterface::ReleaseInterface(void)
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* TransportError APIENTRY TInitialize (
|
|
* TransportCallback transport_callback,
|
|
* PVoid user_defined)
|
|
*
|
|
* Functional Description:
|
|
* This function must be called by the user of the Transport
|
|
* stack. The PSTN thread is started.
|
|
*
|
|
* Caveats:
|
|
* If this function is successful, the user must also call the
|
|
* TCleanup() function when he is finished with the DLL.
|
|
*/
|
|
TransportError CTransportInterface::TInitialize
|
|
(
|
|
TransportCallback transport_callback,
|
|
void *user_defined
|
|
)
|
|
{
|
|
TransportError rc = TRANSPORT_NO_ERROR;
|
|
|
|
if (! m_cUsers)
|
|
{
|
|
g_pfnT120Notify = transport_callback;
|
|
g_pUserData = user_defined;
|
|
|
|
/*
|
|
** At this point, we need to create another thread that will
|
|
** maintain this DLL.
|
|
*/
|
|
g_hWorkerThread = ::CreateThread(
|
|
NULL, 0, (LPTHREAD_START_ROUTINE) T123_WorkerThreadProc,
|
|
NULL, 0, &g_dwWorkerThreadID);
|
|
if (NULL != g_hWorkerThread)
|
|
{
|
|
if (::SetThreadPriority(g_hWorkerThread, THREAD_PRIORITY_ABOVE_NORMAL) == FALSE)
|
|
{
|
|
WARNING_OUT(("SetThreadPriority ERROR: = %d", ::GetLastError()));
|
|
}
|
|
|
|
/*
|
|
** Wait for the secondary thread to notify us that initialization is
|
|
** complete
|
|
*/
|
|
::WaitForSingleObject(g_hWorkerThreadEvent, 30000); // 30 seconds
|
|
|
|
/*
|
|
** If g_pController == NULL, initialization FAILED. We use
|
|
** this variable to signal us regarding the status of the secondary
|
|
** thread.
|
|
*/
|
|
if (NULL == g_pController)
|
|
{
|
|
ERROR_OUT(("TInitialize: Unable to initialize transport"));
|
|
rc = TRANSPORT_INITIALIZATION_FAILED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rc = TRANSPORT_INITIALIZATION_FAILED;
|
|
}
|
|
}
|
|
|
|
if (TRANSPORT_NO_ERROR == rc)
|
|
{
|
|
m_cUsers++;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/*
|
|
* TransportError TCleanup (void)
|
|
*
|
|
* Functional Description:
|
|
* This function is called to release all system resources that
|
|
* were used during the life of the DLL.
|
|
*
|
|
* Caveats:
|
|
* This function should only be called if the user had previously
|
|
* called the TInitialize() function.
|
|
*/
|
|
TransportError CTransportInterface::TCleanup(void)
|
|
{
|
|
TransportError rc = TRANSPORT_NOT_INITIALIZED;
|
|
|
|
if (m_cUsers > 0)
|
|
{
|
|
if (--m_cUsers == 0)
|
|
{
|
|
/*
|
|
** Set the g_hThreadTerminateEvent to notify the PSTN thread
|
|
** that it should terminate operations.
|
|
*/
|
|
::SetEvent(g_hThreadTerminateEvent);
|
|
|
|
/*
|
|
** Wait for the PSTN thread to notify us that cleanup is
|
|
** complete
|
|
*/
|
|
if (g_hWorkerThreadEvent != NULL)
|
|
{
|
|
// WaitForSingleObject (g_hWorkerThreadEvent, INFINITE);
|
|
::WaitForSingleObject(g_hWorkerThreadEvent, 30000); // 30 seconds
|
|
g_hWorkerThreadEvent = NULL;
|
|
|
|
//
|
|
// Relinquish the remainder of our time slice, to allow trasnsport thread to exit.
|
|
//
|
|
Sleep(0);
|
|
}
|
|
}
|
|
|
|
rc = TRANSPORT_NO_ERROR;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
TransportError CTransportInterface::TCreateTransportStack
|
|
(
|
|
BOOL fCaller,
|
|
HANDLE hCommLink,
|
|
HANDLE hevtClose,
|
|
PLUGXPRT_PARAMETERS *pParams
|
|
)
|
|
{
|
|
TransportError rc = TRANSPORT_NOT_INITIALIZED;
|
|
|
|
if (m_cUsers > 0)
|
|
{
|
|
::EnterCriticalSection(&g_csPSTN);
|
|
if (NULL != g_pController)
|
|
{
|
|
rc = g_pController->CreateTransportStack(fCaller, hCommLink, hevtClose, pParams);
|
|
}
|
|
::LeaveCriticalSection(&g_csPSTN);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
TransportError CTransportInterface::TCloseTransportStack
|
|
(
|
|
HANDLE hCommLink
|
|
)
|
|
{
|
|
TransportError rc = TRANSPORT_NOT_INITIALIZED;
|
|
|
|
if (m_cUsers > 0)
|
|
{
|
|
::EnterCriticalSection(&g_csPSTN);
|
|
if (NULL != g_pController)
|
|
{
|
|
rc = g_pController->CloseTransportStack(hCommLink);
|
|
}
|
|
::LeaveCriticalSection(&g_csPSTN);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/*
|
|
* TransportError APIENTRY TConnectRequest (
|
|
* TransportAddress transport_address,
|
|
* TransportPriority transport_priority,
|
|
* LogicalHandle *logical_handle)
|
|
*
|
|
* Functional Description:
|
|
* This function starts the process of building a transport connection.
|
|
* It uses the transport_address and attempts to make a connection to it.
|
|
*
|
|
* If the DLL is set up for in-band call control, the DLL will choose a
|
|
* modem and attempt a connection. After the physical connection is up,
|
|
* it will create a logical connection that the user application can use.
|
|
*
|
|
* If the DLL is set up for out-of-band call control, the transport address
|
|
* is actually an ascii string that represents a DLL generated
|
|
* physicalhandle. This call can only be made in response to a successful
|
|
* TPhysicalConnectRequest() function. The TPhysicalConnectRequest() call
|
|
* returns a PhysicalHandle that the user must convert to ascii and pass
|
|
* back to the DLL via this call. Although this is very ugly, we did this
|
|
* for a reason. We added the out-of-band call control API to the
|
|
* transports but we did not want to change GCC or MCS to do it. GCC and
|
|
* MCS already expected an ascii string as the transport address. In the
|
|
* future, look for this to change in MCS and GCC.
|
|
*/
|
|
TransportError CTransportInterface::TConnectRequest
|
|
(
|
|
LEGACY_HANDLE *logical_handle,
|
|
HANDLE hCommLink
|
|
)
|
|
{
|
|
TransportError rc = TRANSPORT_NOT_INITIALIZED;
|
|
|
|
if (m_cUsers > 0)
|
|
{
|
|
::EnterCriticalSection(&g_csPSTN);
|
|
if (NULL != g_pController)
|
|
{
|
|
rc = g_pController->ConnectRequest(logical_handle, hCommLink);
|
|
}
|
|
::LeaveCriticalSection(&g_csPSTN);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/*
|
|
* TransportError APIENTRY TDisconnectRequest (
|
|
* LogicalHandle logical_handle,
|
|
* BOOL trash_packets)
|
|
*
|
|
* Functional Description:
|
|
* This function breaks a logical connection. This is also an X.214
|
|
* primitive. Since the DLL may have packets queued up for transmission
|
|
* for this logical connection, the trash_packets parameter determines
|
|
* whether the DLL should trash those packets before disconnecting the
|
|
* logical connection.
|
|
*/
|
|
TransportError CTransportInterface::TDisconnectRequest
|
|
(
|
|
LEGACY_HANDLE logical_handle,
|
|
BOOL trash_packets
|
|
)
|
|
{
|
|
TransportError rc = TRANSPORT_NOT_INITIALIZED;
|
|
|
|
if (m_cUsers > 0)
|
|
{
|
|
::EnterCriticalSection(&g_csPSTN);
|
|
if (NULL != g_pController)
|
|
{
|
|
rc = g_pController->DisconnectRequest(logical_handle, trash_packets);
|
|
}
|
|
::LeaveCriticalSection(&g_csPSTN);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/*
|
|
* TransportError APIENTRY TDataRequest (
|
|
* LogicalHandle logical_handle,
|
|
* LPBYTE user_data,
|
|
* ULONG user_data_length)
|
|
*
|
|
* Functional Description:
|
|
* This function takes the data passed in and attempts to send it
|
|
* to the remote site.
|
|
*/
|
|
TransportError CTransportInterface::TDataRequest
|
|
(
|
|
LEGACY_HANDLE logical_handle,
|
|
LPBYTE user_data,
|
|
ULONG user_data_length
|
|
)
|
|
{
|
|
TransportError rc = TRANSPORT_NOT_INITIALIZED;
|
|
|
|
if (m_cUsers > 0)
|
|
{
|
|
::EnterCriticalSection(&g_csPSTN);
|
|
|
|
if (NULL != g_pController)
|
|
{
|
|
rc = g_pController->DataRequest(logical_handle, user_data, user_data_length);
|
|
/*
|
|
** If the packet was accepted, we need to issue a
|
|
** PollTransmitter() to flush the packet out. Otherwise,
|
|
** we will have a latency problem.
|
|
*/
|
|
if (TRANSPORT_NO_ERROR == rc)
|
|
{
|
|
ComPort *comport;
|
|
PhysicalHandle physical_handle = g_pController->GetPhysicalHandle(logical_handle);
|
|
if (g_pComPortList2->find((DWORD_PTR) physical_handle, (PDWORD_PTR) &comport))
|
|
{
|
|
if (! comport->IsWriteActive())
|
|
{
|
|
g_pController->PollTransmitter(physical_handle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
::LeaveCriticalSection(&g_csPSTN);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/*
|
|
* TransportError APIENTRY TReceiveBufferAvailable (void)
|
|
*
|
|
* Functional Description:
|
|
* This function informs the transport to enable packet transfer from the
|
|
* Transport to the user application. This function makes a call to
|
|
* 'Enable' the receiver and then it issues a PollReceiver() to actually
|
|
* allow any pending packets to be sent on up to the user.
|
|
*/
|
|
TransportError CTransportInterface::TReceiveBufferAvailable(void)
|
|
{
|
|
TransportError rc = TRANSPORT_NOT_INITIALIZED;
|
|
|
|
if (m_cUsers > 0)
|
|
{
|
|
::EnterCriticalSection(&g_csPSTN);
|
|
|
|
if (NULL != g_pController)
|
|
{
|
|
g_pController->EnableReceiver();
|
|
g_pController->PollReceiver();
|
|
|
|
/*
|
|
** We are doing a PollTransmitter() here to allow
|
|
** the Q922 object to exit the 'receiver not ready' mode.
|
|
** If MCS had been rejecting packets, and now it is accepting
|
|
** packets, we need to let the remote site know that it
|
|
** can re-start its transmitter.
|
|
*/
|
|
g_pController->PollTransmitter();
|
|
}
|
|
|
|
::LeaveCriticalSection(&g_csPSTN);
|
|
|
|
rc = TRANSPORT_NO_ERROR;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/*
|
|
* TransportError APIENTRY TPurgeRequest (
|
|
* LogicalHandle logical_handle)
|
|
*
|
|
* Functional Description:
|
|
* This function purges all of the outbound packets for the given
|
|
* logical connection.
|
|
*/
|
|
TransportError CTransportInterface::TPurgeRequest
|
|
(
|
|
LEGACY_HANDLE logical_handle
|
|
)
|
|
{
|
|
TransportError rc = TRANSPORT_NOT_INITIALIZED;
|
|
|
|
if (m_cUsers > 0)
|
|
{
|
|
::EnterCriticalSection(&g_csPSTN);
|
|
if (NULL != g_pController)
|
|
{
|
|
rc = g_pController->PurgeRequest(logical_handle);
|
|
}
|
|
::LeaveCriticalSection(&g_csPSTN);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
TransportError CTransportInterface::TEnableReceiver(void)
|
|
{
|
|
TransportError rc = TRANSPORT_NOT_INITIALIZED;
|
|
|
|
if (m_cUsers > 0)
|
|
{
|
|
::EnterCriticalSection(&g_csPSTN);
|
|
if (NULL != g_pController)
|
|
{
|
|
g_pController->EnableReceiver();
|
|
}
|
|
::LeaveCriticalSection(&g_csPSTN);
|
|
}
|
|
|
|
return TRANSPORT_NO_ERROR;
|
|
}
|
|
|
|
|
|
/*
|
|
* DWORD T123_WorkerThreadProc (LPDWORD)
|
|
*
|
|
* Functional Description:
|
|
* This function is the PSTN thread. It maintains the DLL.
|
|
*/
|
|
DWORD T123_WorkerThreadProc(DWORD *)
|
|
{
|
|
PEventObject event_struct;
|
|
BOOL fContinue = TRUE;
|
|
HANDLE aEvents[TPRTINTF_MAXIMUM_WAIT_OBJECTS];
|
|
ULONG cEvents;
|
|
ULONG last_time_polled;
|
|
ULONG current_time;
|
|
|
|
g_fEventListChanged = FALSE;
|
|
|
|
/*
|
|
** Create the one and only instance of the Transport Controller
|
|
** to be in charge of this DLL. Once it is created, all other
|
|
** primitives are routed to it.
|
|
*/
|
|
DBG_SAVE_FILE_LINE
|
|
g_pController = new TransportController();
|
|
if (g_pController == NULL)
|
|
{
|
|
ERROR_OUT(("PSTN_WorkerThreadProc: cannot allocate TransportController"));
|
|
::SetEvent(g_hWorkerThreadEvent);
|
|
return TRANSPORT_INITIALIZATION_FAILED;
|
|
}
|
|
|
|
/*
|
|
** The DLL needs to be polled every X milliseconds. We do this
|
|
** by checking the system clock and if X milliseconds have elapsed
|
|
** since the last DLL poll, we do it again. This line of code is
|
|
** actually initializing the timer.
|
|
*/
|
|
last_time_polled = ::GetTickCount();
|
|
|
|
/*
|
|
** Notify the primary thread that we are up
|
|
*/
|
|
::SetEvent(g_hWorkerThreadEvent);
|
|
|
|
|
|
// THREAD LOOP
|
|
|
|
/*
|
|
** This while loop is the heart of the PSTN thread. We use the
|
|
** WaitForMultipleObjects() function to wake up our thread when a
|
|
** significant event occurs OR when a timeout occurs.
|
|
**
|
|
** There are four different types of event objects that can occur
|
|
** that will wake up the thread.
|
|
**
|
|
** 1. The primary thread sets the g_hThreadTerminateEvent object.
|
|
** 2. Read event from the ComPort object. If a read of a comm port
|
|
** completes or times out, the event object is set
|
|
** 3. Write event from the ComPort object. If a write on a comm
|
|
** port completes or times out.
|
|
** 4. Control event from the ComPort object. If the carrier detect
|
|
** signal changes.
|
|
**
|
|
** Also, the thread will continue running if the MCATPSTN_TIMER_DURATION
|
|
** expires
|
|
**
|
|
**
|
|
** A ComPort object can add event objects to our PSTN Event_List at any
|
|
** time. When they are added to the Event_List, we add them to the
|
|
** local aEvents and the WaitForMultipleObjects() function waits
|
|
** for them.
|
|
**
|
|
** This approach works very well except that the WaitForMultipleObjects()
|
|
** function can only handle TPRTINTF_MAXIMUM_WAIT_OBJECTS at a time. Currently,
|
|
** this #define is set to 64 by the Windows 95 operating system. Since
|
|
** each ComPort uses 3 event objects, this limits us to a maximum of
|
|
** 21 active ports.
|
|
*/
|
|
aEvents[0] = g_hThreadTerminateEvent;
|
|
cEvents = 1;
|
|
|
|
while (fContinue)
|
|
{
|
|
/*
|
|
** Wait for an event to occur or for the timeout to expire
|
|
*/
|
|
DWORD dwRet = ::WaitForMultipleObjects(cEvents, aEvents, FALSE, MCATPSTN_TIMER_DURATION);
|
|
|
|
/*
|
|
** We MUST enter this critical section of code and lock out the
|
|
** primary thread from enterring it. Both threads executing this
|
|
** code could be disasterous
|
|
*/
|
|
::EnterCriticalSection(&g_csPSTN);
|
|
|
|
if (dwRet == WAIT_FAILED)
|
|
{
|
|
fContinue = FALSE;
|
|
break;
|
|
}
|
|
else
|
|
if (dwRet == WAIT_TIMEOUT)
|
|
{
|
|
if (cEvents > 1)
|
|
{
|
|
last_time_polled = ::GetTickCount();
|
|
g_pController->PollReceiver();
|
|
g_pController->PollTransmitter();
|
|
g_pSystemTimer->ProcessTimerEvents();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
** Determine which object has signalled us
|
|
*/
|
|
ULONG dwIdx = dwRet - WAIT_OBJECT_0;
|
|
if (dwIdx > cEvents)
|
|
{
|
|
ERROR_OUT(("ThreadFunc: Invalid object signalled = %d", dwIdx));
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
** If the object that signalled us is index 0, it is the
|
|
** terminate thread object.
|
|
*/
|
|
if (! dwIdx)
|
|
{
|
|
TRACE_OUT(("ThreadFunc: Terminating thread"));
|
|
fContinue = FALSE;
|
|
}
|
|
else
|
|
{
|
|
dwIdx--;
|
|
event_struct = (PEventObject) g_pPSTNEventList->read((USHORT) dwIdx);
|
|
if (NULL != event_struct)
|
|
{
|
|
/*
|
|
** Check the delete_event, if it is set, we need to
|
|
** terminate the event.
|
|
**
|
|
** By design, other objects throughout the DLL create
|
|
** the events, and this function deletes them. If the
|
|
** creating function deleted them, we could be waiting
|
|
** on an event that gets deleted. That has the potential
|
|
** of being messy.
|
|
*/
|
|
if (event_struct->delete_event)
|
|
{
|
|
/*
|
|
** Remove the ComPort from our list
|
|
*/
|
|
ComPort *comport = NULL;
|
|
if (g_pComPortList2->find((DWORD_PTR) event_struct->hCommLink, (PDWORD_PTR) &comport))
|
|
{
|
|
g_pComPortList2->remove((DWORD_PTR) event_struct->hCommLink);
|
|
comport->Release();
|
|
}
|
|
|
|
/*
|
|
** Close the event
|
|
*/
|
|
::CloseHandle(event_struct->event);
|
|
|
|
/*
|
|
** Remove the event from our list
|
|
*/
|
|
g_pPSTNEventList->remove((DWORD_PTR) event_struct);
|
|
delete event_struct;
|
|
|
|
g_fEventListChanged = TRUE;
|
|
}
|
|
else
|
|
{
|
|
ComPort *comport = event_struct->comport;
|
|
/*
|
|
** Switch on the event_type to determine the
|
|
** operation that needs to take place
|
|
*/
|
|
switch (event_struct->event_type)
|
|
{
|
|
case READ_EVENT:
|
|
comport->ProcessReadEvent();
|
|
|
|
/*
|
|
** If a READ event occurred, we need to issue
|
|
** a PollReceiver() and then issue a
|
|
** PollTransmitter(). We issue the
|
|
** PollTransmitter() because the PollReceiver()
|
|
** may have freed up space for us to send out
|
|
** data.
|
|
*/
|
|
g_pController->PollReceiver(event_struct->hCommLink);
|
|
g_pController->PollTransmitter(event_struct->hCommLink);
|
|
break;
|
|
|
|
case WRITE_EVENT:
|
|
/*
|
|
** If a WRITE_EVENT occurs, this means that
|
|
** space has become available to transmit more
|
|
** data.
|
|
*/
|
|
comport->ProcessWriteEvent();
|
|
g_pController->PollTransmitter(event_struct->hCommLink);
|
|
break;
|
|
|
|
case CONTROL_EVENT:
|
|
/*
|
|
** The CONTROL events are particular to the
|
|
** ComPort object. A change in the CD signal
|
|
** could be a CONTROL event.
|
|
*/
|
|
g_pController->PollReceiver(event_struct->hCommLink);
|
|
break;
|
|
|
|
default:
|
|
ERROR_OUT(("ThreadFunc: Illegal event type = %d", event_struct->event_type));
|
|
break;
|
|
} // switch
|
|
}
|
|
} // if event_struct
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Check to see if enough time has elapsed since the last time we
|
|
** polled the DLL to do it again.
|
|
*/
|
|
if (cEvents > 1)
|
|
{
|
|
current_time = ::GetTickCount();
|
|
if ((current_time - last_time_polled) >= MCATPSTN_TIMER_DURATION)
|
|
{
|
|
last_time_polled = current_time;
|
|
|
|
g_pController->PollReceiver();
|
|
g_pController->PollTransmitter();
|
|
g_pSystemTimer->ProcessTimerEvents();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
** This Event_List_Changed will only be set to TRUE if we need to
|
|
** update our event list
|
|
*/
|
|
if (g_fEventListChanged)
|
|
{
|
|
aEvents[0] = g_hThreadTerminateEvent;
|
|
cEvents = 1;
|
|
|
|
if (g_pPSTNEventList->entries() > (TPRTINTF_MAXIMUM_WAIT_OBJECTS - 1))
|
|
{
|
|
ERROR_OUT(("ThreadFunc: ERROR: Number of event objects = %d",
|
|
g_pPSTNEventList->entries() + 1));
|
|
}
|
|
|
|
/*
|
|
** Go thru the Event_List and create a new aEvents.
|
|
*/
|
|
g_pPSTNEventList->reset();
|
|
while (g_pPSTNEventList->iterate((PDWORD_PTR) &event_struct))
|
|
{
|
|
aEvents[cEvents] = event_struct -> event;
|
|
cEvents++;
|
|
|
|
/*
|
|
** Add the physical_handle to the ComPort_List
|
|
*/
|
|
if (event_struct->event_type == WRITE_EVENT)
|
|
{
|
|
if (! g_pComPortList2->find((DWORD_PTR) event_struct->hCommLink))
|
|
{
|
|
g_pComPortList2->insert((DWORD_PTR) event_struct->hCommLink,
|
|
(DWORD_PTR) event_struct->comport);
|
|
}
|
|
}
|
|
}
|
|
g_fEventListChanged = FALSE;
|
|
}
|
|
|
|
::LeaveCriticalSection(&g_csPSTN);
|
|
} // while
|
|
|
|
|
|
// CLEANUP THE THREAD RESOURCES
|
|
delete g_pController;
|
|
g_pController = NULL;
|
|
|
|
/*
|
|
** Delete all of the event objects
|
|
*/
|
|
g_pPSTNEventList->reset();
|
|
while (g_pPSTNEventList->iterate((PDWORD_PTR) &event_struct))
|
|
{
|
|
::CloseHandle(event_struct->event);
|
|
delete event_struct;
|
|
}
|
|
|
|
/*
|
|
** Notify the primary thread that we are up
|
|
*/
|
|
::SetEvent(g_hWorkerThreadEvent);
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
|
|
ULONG NotifyT120(ULONG msg, void *param)
|
|
{
|
|
if (NULL != g_pfnT120Notify)
|
|
{
|
|
return (*g_pfnT120Notify) (msg, param, g_pUserData);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
IObject::~IObject(void) { }
|
|
|
|
|