// // SimpleTapi.cpp // // Source code from: // // Serial Communications: A C++ Developer's Guide, 2nd // Edition by Mark Nelson, IDG Books, 1999 // // Please see the book for information on usage. // // This file contains the complete implementation of // the SimpleTapi class. SimpleTapi wraps up a limited // subset of TAPI 1.4, using the SDK only for its modem // control and call setup capabilities. This class // contains several pure virtual functions that are needed // for notification. Before you can use SimpleTapi, you will // need to create a derived class that implements those // functions. A simple example used in the book is // MySimpleTapi, used in the Chapter 15 example. // #include "SimpleTapi.h" // // The static m_Trace object is shared by all extant // SimpleTapi devices. It provices a way to display trace // messages. Calling the Open() and Close() method of this // object will open and close a console window that displays // messages. // ConStream SimpleTapi::m_Trace; // // The SimpleTapi constructor has to do a couple of obvious // things, such as clearing the internal handles and setting // up other variables. One of the things it does that isn't // quite as obvious is to enumerate a list of modems that // can be used with this class. That list is stored in the // m_Devices vector, which is suitable for insertion into // a drop down list for selection by a user. // SimpleTapi::SimpleTapi() { // // No line is active, no call is up // m_hLine = 0; m_hCall = 0; m_bCallHandleValid = false; DWORD version = TAPI_CURRENT_VERSION; // // Note that Microsoft categorizes this as an obsolete // function, but for a class like this that is only // using TAPI for first part call control (setting up // and tearing down our own calls) it is entirely // adequate. It registers our app and gets an app handle // in m_hLineApp, and lets TAPI know that we expect to // see all notification returned via our static function // SimpleTapi::CallBack() // DWORD device_count; LONG result = lineInitialize( &m_hLineApp, GetModuleHandle( NULL ), Callback, "TAPI Application", &device_count ); m_Trace << "SimpleTapi::SimpleTapi() " << "lineInitialize() returned " << hex << result << dec << "\n"; // // If error return, no devices, no app handle, not going // to do anything useful. // if ( result != 0 ) { device_count = 0; m_hLineApp = NULL; } // // We are going to add all data modems to the list of // useful devices. That list is a vector that contains // object of type TapiDevices, a little class used here // to hold a device name and tapi id. // // One thing you see here in the call to lineGetDevCaps() // is a recurrent them in Microsoft SDKs. Instead of // just telling us how much space we will need to get the // information, we're expected to make one call just // to find out how much space is needed, then another // to get the actual data. I buck the system in this // case by grossly overestimating the space needed and // attempting to do it all in one call. // m_Devices.resize( device_count ); int j = 0; for ( int i = 0 ; i < (int) device_count ; i++ ) { char temp[ 4096 ]; LINEDEVCAPS *dev_caps = (LINEDEVCAPS *) temp; dev_caps->dwTotalSize = 4096; result = lineGetDevCaps( m_hLineApp, i, version, 0, dev_caps ); m_Trace << "SimpleTapi::SimpleTapi() " << "lineGetDevCaps(" << i << ") returned " << hex << result << dec << "\n"; // // To qualify as a useful device, I have to get // a successful return from lineGetDevCaps(), the // device has to be a data modem, and I have to have // a valid device name, as denoted by // dwLineNameOffset. If those conditions are met, I // add the device to the list of devices. Note that // the name is stored as a text string that may not // be null terminated. Fortunately there is a string // constructor that easily acommodates this. // if ( result == 0 && dev_caps->dwLineNameOffset != 0 && ( dev_caps->dwMediaModes & LINEMEDIAMODE_DATAMODEM ) ) { m_Devices[ j ].m_sName = string( temp + dev_caps->dwLineNameOffset, dev_caps->dwLineNameSize ); m_Trace << "Device name = " << m_Devices[ j ].m_sName << "\n"; m_Devices[ j++ ].m_iDeviceNumber = i; } } m_Devices.resize( j ); m_ReplyAction = NOTHING; m_dwPendingReplyCode = -1; } // // The destructor shuts down TAPI by calling lineShutdown, // passing the app handle. At that point the TAPI DLL may // be unloaded and its internal resources given back to the // system. // SimpleTapi::~SimpleTapi() { LONG result = -1; if ( m_hLineApp != NULL ) result = lineShutdown( m_hLineApp ); m_Trace << "lineShutdown() result = " << hex << result << dec << "\n"; } // // Many things that we ask TAPI to do take long amounts // of time to accomplish. When we kick off one of these // asynchronous events, our call to TAPI normally returns // immediately. Later, as TAPI makes progress on our // request, it sends notification to us via this callback // function. // // Unfortunately for C++ programmers, callback functions // in Windows are C functions with no concept of a this // pointer. This means that the callback function has to // be a static C++ member function. The good news is that // TAPI makes provision for the callback function to carry // around an untyped pointer that the user provides. In our // case, that pointer is to a SimpleTapi object, giving us // immediate access to the SimpleTapi object that is actually // the source or target of the action. // void PASCAL SimpleTapi::Callback( DWORD hDevice, DWORD dwMsg, DWORD dwCallbackInstance, DWORD dwParam1, DWORD dwParam2, DWORD dwParam3 ) { SimpleTapi *tapi = (SimpleTapi *) dwCallbackInstance; // // Like most Windows callbacks, we have to examine the // message type to decide what to do. SimpleTapi only // responds to two different message types: LINE_REPLY // and LINE_CALLSTATE. LINE_REPLY sends responses to // any of the three asynchronous commands: Make Call, // Drop Call, and Answer. LINE_CALLSTATE keeps us // up with the changes in a call state. It is where we // get the two important events used in the Chapter // 15 demo program, the connected and disconnected // events. // switch ( dwMsg ) { case LINE_REPLY : m_Trace << "LINE_REPLY, request = " << hex << dwParam1 << ", result = " << dwParam2 << dec << "\n"; // // If the user of this class made one of the three // asynchronous function calls, it is presumably // hanging around and waiting for an answer. We // supply the answer by calling one of the three // notification functions. These three notification // functions aren't defined in the base class, they // have to be implemented in a derived class that // is designed to work with a specific application. // // Note that in order for this section of code to // work, we need to only be waiting for one pending // action at a time. The pending action is stored // in the m_ReplyAction member of the object at the // time the asynchronous request is made, using an // enumerated type defined specifically in this // class. We don't do anything with the result code // that TAPI passes in here, we just pass it along // to whoever is at the other end of the notification // call. // if ( dwParam1 == tapi->m_dwPendingReplyCode ) { switch ( tapi->m_ReplyAction ) { case NOTHING: break; case HANDLE_MAKE_CALL_RESULT : tapi->HandleMakeCallResult( dwParam2 ); break; case HANDLE_DROP_RESULT : tapi->HandleDropResult( dwParam2 ); break; case HANDLE_ANSWER_RESULT : tapi->HandleAnswerResult( dwParam2 ); break; } tapi->m_ReplyAction = NOTHING; } break; // // Things are really only interesting in TAPI while a // call is in progress. When that is the case, we get // periodic updates from TAPI telling us how things are // going. As we progress through these actions, we // generate notification function calls that allow the // owner of the TAPI object to do the appropriate things. // There is a general notification function that always // gets called, then specific notification routines for // the all important connected and disconnected events. // case LINE_CALLSTATE : tapi->NotifyCallStateChange( dwParam1 ); // // if dwParam3 is non-zero for the LINE_CALLSATE // event, it is being used to inform us of our // privilege state for the given line. For the type // of call control we are performing here, we should // always be the owner of the call. // switch ( dwParam3 ) { case LINECALLPRIVILEGE_OWNER : m_Trace << "SimpleTapi is now the owner of handle = " << hex << hDevice << dec << "\n"; break; } // // When the LINE_CALLSTATE message is received, // dwParam1 contains the new state of the call. // A few of these states are particularly exciting, // and lead to notification messages. Most of those // cases should be pretty obvious. The TAPI docs // show a few more states than are shown here, and // a full-featured class might implement more // notification messages to deal with them. The ones // used here are adequate for a simple dialing and // connecting app. // switch ( dwParam1 ) { // // A connected state can occur after either side // decides to answer a call. The host app surely // needs to be notified by this callback routine. // case LINECALLSTATE_CONNECTED: tapi->ConnectedEvent(); break; case LINECALLSTATE_IDLE: case LINECALLSTATE_DISCONNECTED: // // Note that when the call is dropped I call // TAPI to free up the m_hCall handle that was // associated with this call way back when it // was first initiated. // if ( tapi->m_hCall ) { HCALL temp_hcall = tapi->m_hCall; tapi->m_hCall = 0; tapi->DisconnectedEvent(); m_Trace << "Call was dropped\n"; lineDeallocateCall( temp_hcall ); tapi->m_bCallHandleValid = 0; if ( tapi->m_ReplyAction == HANDLE_DROP_RESULT ) { //hack, I never see it o/w tapi->HandleDropResult( 0 ); tapi->m_ReplyAction = NOTHING; } } break; // // In SimpleTapi, we will always answer an // incoming call that is being offered if the // line is open. So if you don't want to answer // an incoming call, don't open the line. Note // that this creates the m_hCall handle, which // will be used for the duration of the call. // The call to lineAnswer() will almost always // be given asynchronously, with a result code // of > 0. // case LINECALLSTATE_OFFERING : { tapi->m_hCall = (HCALL) hDevice; LONG result = lineAnswer( tapi->m_hCall, NULL, 0 ); if ( result < 0 ) { tapi->Error( "Error returned from lineAnswer : ", result ); } else if ( result == 0 ) { m_Trace << "lineAnswer returned : " << result << "\n"; tapi->HandleAnswerResult( 0 ); } else { tapi->m_ReplyAction = HANDLE_ANSWER_RESULT; tapi->m_dwPendingReplyCode = result; } } break; } tapi->m_lCallState = dwParam1; } } // // This static member function simply transates one of the // seemingly infinite TAPI error status codes to a string. // This makes the trace information easier to read. // string SimpleTapi::TranslateTapiError( LONG code ) { switch (code ) { case LINEERR_ALLOCATED : return "LINEERR_ALLOCATED"; case LINEERR_BADDEVICEID : return "LINEERR_BADDEVICEID"; case LINEERR_BEARERMODEUNAVAIL: return "LINEERR_BEARERMODEUNAVAIL"; case LINEERR_CALLUNAVAIL: return "LINEERR_CALLUNAVAIL"; case LINEERR_COMPLETIONOVERRUN: return "LINEERR_COMPLETIONOVERRUN"; case LINEERR_CONFERENCEFULL: return "LINEERR_CONFERENCEFULL"; case LINEERR_DIALBILLING: return "LINEERR_DIALBILLING"; case LINEERR_DIALDIALTONE: return "LINEERR_DIALDIALTONE"; case LINEERR_DIALPROMPT: return "LINEERR_DIALPROMPT"; case LINEERR_DIALQUIET: return "LINEERR_DIALQUIET"; case LINEERR_INCOMPATIBLEAPIVERSION: return "LINEERR_INCOMPATIBLEAPIVERSION"; case LINEERR_INCOMPATIBLEEXTVERSION: return "LINEERR_INCOMPATIBLEEXTVERSION"; case LINEERR_INIFILECORRUPT: return "LINEERR_INIFILECORRUPT"; case LINEERR_INUSE: return "LINEERR_INUSE"; case LINEERR_INVALADDRESS: return "LINEERR_INVALADDRESS"; case LINEERR_INVALADDRESSID: return "LINEERR_INVALADDRESSID"; case LINEERR_INVALADDRESSMODE: return "LINEERR_INVALADDRESSMODE"; case LINEERR_INVALADDRESSSTATE: return "LINEERR_INVALADDRESSSTATE"; case LINEERR_INVALAPPHANDLE: return "LINEERR_INVALAPPHANDLE"; case LINEERR_INVALAPPNAME: return "LINEERR_INVALAPPNAME"; case LINEERR_INVALBEARERMODE: return "LINEERR_INVALBEARERMODE"; case LINEERR_INVALCALLCOMPLMODE: return "LINEERR_INVALCALLCOMPLMODE"; case LINEERR_INVALCALLHANDLE: return "LINEERR_INVALCALLHANDLE"; case LINEERR_INVALCALLPARAMS: return "LINEERR_INVALCALLPARAMS"; case LINEERR_INVALCALLPRIVILEGE: return "LINEERR_INVALCALLPRIVILEGE"; case LINEERR_INVALCALLSELECT: return "LINEERR_INVALCALLSELECT"; case LINEERR_INVALCALLSTATE: return "LINEERR_INVALCALLSTATE"; case LINEERR_INVALCALLSTATELIST: return "LINEERR_INVALCALLSTATELIST"; case LINEERR_INVALCARD: return "LINEERR_INVALCARD"; case LINEERR_INVALCOMPLETIONID: return "LINEERR_INVALCOMPLETIONID"; case LINEERR_INVALCONFCALLHANDLE: return "LINEERR_INVALCONFCALLHANDLE"; case LINEERR_INVALCONSULTCALLHANDLE: return "LINEERR_INVALCONSULTCALLHANDLE"; case LINEERR_INVALCOUNTRYCODE: return "LINEERR_INVALCOUNTRYCODE"; case LINEERR_INVALDEVICECLASS: return "LINEERR_INVALDEVICECLASS"; case LINEERR_INVALDEVICEHANDLE: return "LINEERR_INVALDEVICEHANDLE"; case LINEERR_INVALDIALPARAMS: return "LINEERR_INVALDIALPARAMS"; case LINEERR_INVALDIGITLIST: return "LINEERR_INVALDIGITLIST"; case LINEERR_INVALDIGITMODE: return "LINEERR_INVALDIGITMODE"; case LINEERR_INVALDIGITS: return "LINEERR_INVALDIGITS"; case LINEERR_INVALEXTVERSION: return "LINEERR_INVALEXTVERSION"; case LINEERR_INVALGROUPID: return "LINEERR_INVALGROUPID"; case LINEERR_INVALLINEHANDLE: return "LINEERR_INVALLINEHANDLE"; case LINEERR_INVALLINESTATE: return "LINEERR_INVALLINESTATE"; case LINEERR_INVALLOCATION: return "LINEERR_INVALLOCATION"; case LINEERR_INVALMEDIALIST: return "LINEERR_INVALMEDIALIST"; case LINEERR_INVALMEDIAMODE: return "LINEERR_INVALMEDIAMODE"; case LINEERR_INVALMESSAGEID: return "LINEERR_INVALMESSAGEID"; case LINEERR_INVALPARAM: return "LINEERR_INVALPARAM"; case LINEERR_INVALPARKID: return "LINEERR_INVALPARKID"; case LINEERR_INVALPARKMODE: return "LINEERR_INVALPARKMODE"; case LINEERR_INVALPOINTER: return "LINEERR_INVALPOINTER"; case LINEERR_INVALPRIVSELECT: return "LINEERR_INVALPRIVSELECT"; case LINEERR_INVALRATE: return "LINEERR_INVALRATE"; case LINEERR_INVALREQUESTMODE: return "LINEERR_INVALREQUESTMODE"; case LINEERR_INVALTERMINALID: return "LINEERR_INVALTERMINALID"; case LINEERR_INVALTERMINALMODE: return "LINEERR_INVALTERMINALMODE"; case LINEERR_INVALTIMEOUT: return "LINEERR_INVALTIMEOUT"; case LINEERR_INVALTONE: return "LINEERR_INVALTONE"; case LINEERR_INVALTONELIST: return "LINEERR_INVALTONELIST"; case LINEERR_INVALTONEMODE: return "LINEERR_INVALTONEMODE"; case LINEERR_INVALTRANSFERMODE: return "LINEERR_INVALTRANSFERMODE"; case LINEERR_LINEMAPPERFAILED: return "LINEERR_LINEMAPPERFAILED"; case LINEERR_NOCONFERENCE: return "LINEERR_NOCONFERENCE"; case LINEERR_NODEVICE: return "LINEERR_NODEVICE"; case LINEERR_NODRIVER: return "LINEERR_NODRIVER"; case LINEERR_NOMEM: return "LINEERR_NOMEM"; case LINEERR_NOREQUEST: return "LINEERR_NOREQUEST"; case LINEERR_NOTOWNER: return "LINEERR_NOTOWNER"; case LINEERR_NOTREGISTERED: return "LINEERR_NOTREGISTERED"; case LINEERR_OPERATIONFAILED: return "LINEERR_OPERATIONFAILED"; case LINEERR_OPERATIONUNAVAIL: return "LINEERR_OPERATIONUNAVAIL"; case LINEERR_RATEUNAVAIL: return "LINEERR_RATEUNAVAIL"; case LINEERR_RESOURCEUNAVAIL: return "LINEERR_RESOURCEUNAVAIL"; case LINEERR_REQUESTOVERRUN: return "LINEERR_REQUESTOVERRUN"; case LINEERR_STRUCTURETOOSMALL: return "LINEERR_STRUCTURETOOSMALL"; case LINEERR_TARGETNOTFOUND: return "LINEERR_TARGETNOTFOUND"; case LINEERR_TARGETSELF: return "LINEERR_TARGETSELF"; case LINEERR_UNINITIALIZED: return "LINEERR_UNINITIALIZED"; case LINEERR_USERUSERINFOTOOBIG: return "LINEERR_USERUSERINFOTOOBIG"; case LINEERR_REINIT: return "LINEERR_REINIT"; case LINEERR_ADDRESSBLOCKED: return "LINEERR_ADDRESSBLOCKED"; case LINEERR_BILLINGREJECTED: return "LINEERR_BILLINGREJECTED"; case LINEERR_INVALFEATURE: return "LINEERR_INVALFEATURE"; case LINEERR_NOMULTIPLEINSTANCE: return "LINEERR_NOMULTIPLEINSTANCE"; } char buf[ 128 ]; wsprintf( buf, "Unknown error code: %08lx", code ); return buf; } // // This static member function simply transates one of the // TAPI call state codes to a string. This makes the trace // information easier to read. // string SimpleTapi::TranslateCallState( LONG state ) { switch ( state ) { case LINECALLSTATE_IDLE: return "LINECALLSTATE_IDLE"; case LINECALLSTATE_OFFERING: return "LINECALLSTATE_OFFERING"; case LINECALLSTATE_ACCEPTED: return "LINECALLSTATE_ACCEPTED"; case LINECALLSTATE_DIALTONE: return "LINECALLSTATE_DIALTONE"; case LINECALLSTATE_DIALING: return "LINECALLSTATE_DIALING"; case LINECALLSTATE_RINGBACK: return "LINECALLSTATE_RINGBACK"; case LINECALLSTATE_BUSY: return "LINECALLSTATE_BUSY"; case LINECALLSTATE_SPECIALINFO: return "LINECALLSTATE_SPECIALINFO"; case LINECALLSTATE_CONNECTED: return "LINECALLSTATE_CONNECTED"; case LINECALLSTATE_PROCEEDING: return "LINECALLSTATE_PROCEEDING"; case LINECALLSTATE_ONHOLD: return "LINECALLSTATE_ONHOLD"; case LINECALLSTATE_CONFERENCED: return "LINECALLSTATE_CONFERENCED"; case LINECALLSTATE_ONHOLDPENDCONF: return "LINECALLSTATE_ONHOLDPENDCONF"; case LINECALLSTATE_ONHOLDPENDTRANSFER: return "LINECALLSTATE_ONHOLDPENDTRANSFER"; case LINECALLSTATE_DISCONNECTED: return "LINECALLSTATE_DISCONNECTED"; case LINECALLSTATE_UNKNOWN: return "LINECALLSTATE_UNKNOWN"; case -1: return ""; } char buf[ 128 ]; wsprintf( buf, "Unknown call state: %08lx", state ); return buf; } // // TAPI is kind enough to provide a thorough configuration // dialog for the devices it supports, relieving programmers // of the horrible prospect of writing customized dialogs // for any device a user might care to employ. All we have // to do to invoke this ID is to come up with a device ID // number and a parent window handle. // void SimpleTapi::ConfigureDevice( int index ) { int dev_id = m_Devices[ index ].m_iDeviceNumber; LONG result = lineConfigDialog( dev_id, GetWindow(), "comm/datamodem" ); if ( result != 0 ) ::MessageBox( GetWindow(), "Something bad happened", "SimpleTapi Message", MB_OK ); } // // Configuring phone calls is another fairly difficult task // that TAPI takes on for us. All we have to do is pass in // the ID of the device and the phone number to be called. // TAPI then creates a dialog that takes care of thinking // about all sorts of dialing issues. // void SimpleTapi::ConfigureCall( int index, const string &number ) { int dev_id = m_Devices[ index ].m_iDeviceNumber; LONG result = lineTranslateDialog( m_hLineApp, dev_id, TAPI_CURRENT_VERSION, GetWindow(), number.c_str() ); if ( result != 0 ) Error( "Error returned from lineTranslateDialog : ", result ); } // // If a line is open and doesn't have a call in progress, we // can use this routine to initiate a call. Looking down into // the call, you can see that you need to have a valid m_hLine // handle to make the call, along with a number and a device // id. The first part of the function calls // lineTranslateAddress() to mangle the digit string into // something that can be used to make the call. It needs the // device ID and the phone number to do that. // // Once the phone number has been properly mangled, we set up // a LINECALLPARMS structure and make a call to lineMakeCall. // That function can do one of three things. It might fail // with an error, It might return immediately with a valid // result. But most likely, it will tell me that it doesn't // have a result yet, and I'll just have to wait. When that // happens, I set up the m_ReplyAction so that I know I'm // expecting something, and I return. // // Note that when you call this function, or the other // asynchronous functions in SimpleTapi, you never get // anything useful back, which is why these guys are all of // type void. You wait for response to come back from the // callback notification routines. // void SimpleTapi::MakeCall( const string &number ) { // // This first call is another one of those with an // indeterminate requirement for the size of the // data structure. The Redmond way is to call it twice, // the first time you will fail but find out how much // space you need. The second time you call it with the // correct amount of space. I thwart this strategy by // allocating a hugely excessive amount of space so // that I'll never fail. probably. // char buf[ 4096 ]; LINETRANSLATEOUTPUT *lto = (LINETRANSLATEOUTPUT *) buf; lto->dwTotalSize = 4096; LONG result = lineTranslateAddress( m_hLineApp, m_iDeviceId, TAPI_CURRENT_VERSION, number.c_str(), 0, 0, lto ); if ( result != 0 ) { Error( "Error returned from lineTranslateAddress : ", result ); return; } m_sNumber = string( buf + lto->dwDialableStringOffset, lto->dwDialableStringSize ); m_Trace << "Dial number = " << m_sNumber << "\n"; LINECALLPARAMS lcp; memset( &lcp, 0, sizeof( LINECALLPARAMS ) ); lcp.dwTotalSize = sizeof( LINECALLPARAMS ); lcp.dwBearerMode = LINEBEARERMODE_VOICE; lcp.dwMediaMode = LINEMEDIAMODE_DATAMODEM; lcp.dwCallParamFlags = LINECALLPARAMFLAGS_IDLE; lcp.dwAddressMode = LINEADDRESSMODE_ADDRESSID; result = lineMakeCall( m_hLine, &m_hCall, m_sNumber.c_str(), 0, &lcp ); if ( result < 0 ) { Error( "Error returned from lineMakeCall : ", result ); return; } else if ( result == 0 ) { m_Trace << "lineMakeCall returned : " << TranslateTapiError( result ) << "\n"; HandleMakeCallResult( 0 ); } else { m_ReplyAction = HANDLE_MAKE_CALL_RESULT; m_dwPendingReplyCode = result; } } // // Dropping a call is easier than making one, but it still // has to be dealt with asynchronously. All we need to // get the whole thing going is a call handle, which we // pass to lineDrop(). It might generate an eerror, or an // immediate response, but most likely it will let us // know that it will get back to us later via notification // We make a note of that by setting the m_ReplyAction // enumerated value to signify that we are waiting for a // drop result. // void SimpleTapi::DropCall() { LONG result = lineDrop( m_hCall, NULL, 0 ); if ( result < 0 ) { Error( "Error returned from lineDrop : ", result ); return; } else if ( result == 0 ) { m_Trace << "lineDrop returned : " << dec << result << "\n"; HandleDropResult( 0 ); } else { m_ReplyAction = HANDLE_DROP_RESULT; m_dwPendingReplyCode = result; } } // // These three functions are all fundamentally the same. // Each of them is called when a delayed response comes // in to a previous action, which is either a request to // make a call, answer a call, or drop a call. // // Each of the functions does a small amount of internal // class maintenance. Two of the functions then call a // user notification function. The two notification functions // are pure virtual, so they must be defined in a derived // class. // // Why doesn't the Answer handler have a notification // routine? SimpleTapi doesn't have an Answer call that the // user of the class can call. Instead, it automatically // answers any incoming call. So the user doesn't ever have // to worry about a response to an Answer call. A more // completel TAPI class wouldn't automatically answer calls; // it would notify users of incoming offering states and // let the user make some sort of answer response. // void SimpleTapi::HandleMakeCallResult( DWORD result ) { m_Trace << "lineMakeCall returned (delayed) : " << TranslateTapiError( result ) << "\n"; if ( result == 0 ) m_bCallHandleValid = true; else { Error( "Delayed error returned from lineMakeCall : ", result ); m_bCallHandleValid = false; } MakeCallResult( m_bCallHandleValid ); } void SimpleTapi::HandleAnswerResult( DWORD result ) { m_Trace << "lineAnswer returned (delayed) : " << result << "\n"; if ( result == 0 ) m_bCallHandleValid = true; else { Error( "Delayed error returned from lineAnswer : ", result ); m_bCallHandleValid = false; } } void SimpleTapi::HandleDropResult( DWORD result ) { m_Trace << "lineDrop returned (delayed) : " << result << "\n"; if ( result != 0 ) Error( "Delayed error returned from lineDrop : ", result ); DropCallResult( result == 0 ); } // // After creating the TAPI object, the user has to open a // line to do antyhing useful at all. This is done with a // call to lineOpen(). That call expects a valid device // id, which we get from the caller, and an app handle, // which was created when the object was first built. // // The device ID is retrieved from the m_Devices[] vector, // which got copies of all device names and IDs when we // iterated through the list at creation time. // // If we opne the line succesfully, the m_hLine member will // then have a valid handle, and we are ready to make and // accept calls. // int SimpleTapi::OpenLine( int index ) { m_iDeviceId = m_Devices[ index ].m_iDeviceNumber; LONG result = lineOpen( m_hLineApp, m_iDeviceId, &m_hLine, TAPI_CURRENT_VERSION, 0, (DWORD) this, LINECALLPRIVILEGE_OWNER, LINEMEDIAMODE_DATAMODEM, NULL ); if ( result != 0 ) Error( "Error returned in WaitFoprCall from lineOpen : ", result ); else NotifyCallStateChange( LINECALLSTATE_IDLE ); return result; } // // Before shutting down we really need to close the line. // This is also a good way to make sure we don't accidentally // answer any incoming calls. TAPI function lineClose() // takes care of this immediately. int SimpleTapi::CloseLine() { LONG result = lineClose( m_hLine ); m_hLine = 0; if ( result < 0 ) Error( "Error returned from lineClose : ", result ); else { m_Trace << "lineClose returned : " << result << "\n"; NotifyCallStateChange( -1 ); } return result; } // // This function is a little deceptive. After a call is put // through to the connected state, we can take the resulting // handle and treat it as a port handle, using it for normal // serial port functions. In the Chapter 15 example we take // the resulting port handle and turn it into a Win32Port, // and proceed to use it as a terminal emulator. // // Once we get this port handle, we bear a certain measure // of responsibility for it. When we are done using it, we // have to specifically close it with a call to // CloseHandle(), or the books in the O/S won't balance. // HANDLE SimpleTapi::GetPortHandle() { // // This is another one of those calls that take an // unknown number of byte. I drastically overestimate // the amount in hopes that I won't have to deal with // possibility of failure. // char temp[ 4096 ]; VARSTRING *vs = (VARSTRING *) temp; vs->dwTotalSize = 4096; LONG result = lineGetID( 0, //hLine 0, //dwAddressID m_hCall, //hCall LINECALLSELECT_CALL, //dwSelect vs, "comm/datamodem" ); if ( result < 0 ) { Error( "Error returned from lineGetID : ", result ); return 0; } HANDLE *p = (HANDLE *) ((LPSTR) vs + vs->dwStringOffset); return *p; } //EOF SimpleTapi.cpp