Creating
a TAPI Connection Using CTapiConnection
This article demonstrates how the developer can add some simple telephony
capabilities to an application by using the Microsoft Foundation Class Library
(MFC). I created a class, CTapiConnection, that supports the basic
functionality needed to use the Microsoft Windows Telephony Application
Programming Interface (TAPI) to automatically dial the telephone for a voice
connection. This article takes the reader through the steps necessary to do the
following:
·
Initialize TAPI
·
Obtain a phone line
·
Place a call
·
End a call
The DIALIT Sample
When I decided to write a sample that uses the Telephony
Application Programming Interface (TAPI), the first thing I did was look
through the Platform SDK for any and all information about it. Happily, I found
the Microsoft Telephony Programmer's Reference and the Microsoft
Telephony Service Provider Interface (TSPI) for Telephony. The programmer's
reference is intended to document the functionality that an application using
TAPI will need. The service provider documentation is for developers who are
going to write their own TAPI services (that is, vendors of telephony
equipment).
With the documentation in hand, I also wanted to find some
sample source code to look at. Tucked (or should I say buried?) in the Product
Sample tree of the MSDN Library, I found the TAPICOMM (MSDN Library, Sample
Code, Product Samples) sample, which demonstrates how to use TAPI for data
transmission. It also demonstrates how to use the Communications (COMM) API and
how to use multiple threads in your application. In short, it's terrific.
Unfortunately, it's also a really big sample. To get to the generic TAPI code
is a navigational challenge. As a result, I knew that it would be helpful to
give you a simple sample and a Microsoft Foundation Class Library (MFC) class
to help you learn about TAPI.
Thus, I decided to create a simple telephone dialer, and I
came up with the DIALIT sample. It was written using Visual C++ 2.2 and MFC
version 3.1. It is a Win32-based application that has been built and tested on
Windows 95. As you can see from Figure 1, the user interface is minimal. (Yes,
you are reading this correctly. I really did write something non-GUI. Amazing!
The CTapiConnection
Class
The CTapiConnection class was designed for the
DIALIT sample to allow the application developer a simple method of
establishing a TAPI connection. The class, as defined in the TAPIUTILS.H file,
is shown below. The member functions will be described in more detail later in this
article.
class CTapiConnection
{
protected:
// This
area contains the protected members of the CTapiConnection class.
DWORD m_dwNumDevs; //
the number of line devices available
DWORD m_dwDeviceID; //
the line device ID
DWORD m_dwRequestedID;
LONG m_lAsyncReply;
// BOOLEANS to handle
reentrancy.
BOOL m_bShuttingDown;
BOOL m_bStoppingCall;
BOOL m_bInitializing;
BOOL m_bReplyReceived;
BOOL m_bTapiInUse; // whether TAPI is in use or not
BOOL m_bInitialized; // whether TAPI has been initialized
public:
// This
area contains the public members of the CTapiConnection class.
HLINEAPP m_hLineApp; //
the usage handle of this application for TAPI
HCALL m_hCall;
// handle to the call
HLINE m_hLine;
// handle to the open line
DWORD m_dwAPIVersion;
// the API version
char
m_szPhoneNumber[64];
// the phone number to call
protected:
// Here is where I put
the protected (internal) functions.
BOOL ShutdownTAPI();
BOOL HandleLineErr(long lLineErr);
LPLINEDEVCAPS GetDeviceLine(DWORD
*dwAPIVersion,
LPLINEDEVCAPS lpLineDevCaps);
LPLINEDEVCAPS MylineGetDevCaps(LPLINEDEVCAPS
lpLineDevCaps,
DWORD dwDeviceID, DWORD dwAPIVersion);
LPVOID CheckAndReAllocBuffer(LPVOID lpBuffer, size_t sizeBufferMinimum);
LPLINEADDRESSCAPS MylineGetAddressCaps (LPLINEADDRESSCAPS lpLineAddressCaps,
DWORD dwDeviceID, DWORD dwAddressID,
DWORD dwAPIVersion,
DWORD dwExtVersion);
BOOL MakeTheCall(LPLINEDEVCAPS lpLineDevCaps,LPCSTR lpszAddress);
LPLINECALLPARAMS CreateCallParams (LPLINECALLPARAMS lpCallParams,
LPCSTR lpszDisplayableAddress);
long
WaitForReply (long lRequestID);
void
HandleLineCallState(DWORD dwDevice,
DWORD dwMessage,
DWORD dwCallbackInstance,
DWORD dwParam1,
DWORD dwParam2, DWORD dwParam3);
private:
// This
section is for private functions.
public:
// Public functions.
CTapiConnection();
~CTapiConnection();
BOOL Create(char
*szPhoneNumber = NULL);
BOOL DialCall(char *szPhoneNumber = NULL);
BOOL HangupCall();
static
void CALLBACK lineCallbackFunc(
DWORD dwDevice, DWORD dwMsg, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD
dwParam2, DWORD dwParam3);
};
As you can glean from the class definition, implementing
basic telephony requires a lot of work. But you will notice that the member
functions that I have defined are simple:
·
Create
·
DialCall
·
HangupCall
·
lineCallbackFunc
Note The line callback
function is not called directly by the application using the class; rather, it
is called by the system for line notifications. I'll talk more about the line
callback function later in this article.
Step One: Initializing
TAPI
The first thing an application must do before it uses any
telephony services is to initialize TAPI. This means that the application must
establish some way to communicate between itself and TAPI. TAPI uses a callback
function to facilitate this communication. The application tells TAPI the
address of this callback function when the application makes a call to lineInitialize.
The lineInitialize
function fills in two values passed to it: a usage handle (shown in the
following example as m_hLineApp) and the
number of line devices available to the application (shown in the following
example as m_dwNumDevs). If the call to lineInitialize is successful, a value of zero is
returned. If an error occurs, a negative value is returned. All TAPI functions
return a value of zero to signal success. To make it easier for me, I defined a
constant called SUCCESS to be zero and used that in my code.
The following example is the code I wrote to initialize
TAPI. It takes a pointer to a string containing the phone number to be dialed.
By default, this pointer is NULL. The member function that actually dials the
phone number, DialCall, also takes as a
parameter the telephone number to dial. The user of the class can specify the
number in either place.
BOOL CTapiConnection::Create(char *szPhoneNumber)
{
long
lReturn;
// If we're already
initialized, then initialization succeeds.
if
(m_hLineApp)
return
TRUE;
// If we're in the
middle of initializing, then fail, we're not done.
if
(m_bInitializing)
return
FALSE;
m_bInitializing
= TRUE;
// Initialize TAPI.
do
{
lReturn = ::lineInitialize(&m_hLineApp,
AfxGetInstanceHandle(),
lineCallbackFunc,
"DialIt",
&m_dwNumDevs);
if
(m_dwNumDevs == 0)
{
AfxMessageBox("There
are no telephony devices installed.");
m_bInitializing = FALSE;
return FALSE;
}
if
(HandleLineErr(lReturn))
continue;
else
{
OutputDebugString("lineInitialize unhandled
error\n");
m_bInitializing = FALSE;
return FALSE;
}
}
while(lReturn != SUCCESS);
// If the user furnished
a phone number, copy it over.
if
(szPhoneNumber)
strcpy(m_szPhoneNumber,
szPhoneNumber);
m_bInitializing
= FALSE;
return
TRUE;
}
Step Two: Obtaining a
Line
Now that TAPI has been initialized, the application needs
to interact with the user to know what type of call to make. The application
does this by building a dialog box or property sheet using the standard Win32
functions. There isn't a handy common dialog for this. (Drats!) In the DIALIT sample, a simple dialog box
lets the user enter the desired phone number to dial in the edit box and then
click the Dial button to dial.
When we know the number to dial and that we should dial
it, the application needs to obtain a line handle. This is done by a call to
the lineOpen function. Before the application
can make a call to lineOpen, though, it has to
make sure that the line can support the type of call that the application is
trying to make. In the case of the DIALIT sample, this is a line that is
capable of voice transmission.
Getting the Line
Capabilities
The application calls the lineGetDevCaps
function to determine the capabilities of a given phone line. The function
fills in a structure, LINEDEVCAPS, containing this information. Sounds
straightforward, doesn't it. Well, there's a bit of a catch: the LINEDEVCAPS
structure is defined by the telephony service provider and, as such, is
variable length. After the application makes the call to lineGetDevCaps,
it must check to see if the amount of space supplied for the structure was
sufficient for the size of the provider's structure. This is done by comparing
the dwNeededSize and dwTotalSize
fields. If the needed size is larger than the total size, the application needs
to pass a larger buffer to the function and try again. I created a function
that calls lineGetDevCaps until a sufficient sized
buffer is returned for this purpose. This function calls another function, CheckAndReAllocBuffer, that checks to see if the buffer exists and is large
enough; it also fills in the dwTotalSize field
of the LINEDEVCAPS structure. Filling in this field is imperative. If
you don't set this correctly, the call to lineGetDevCaps
will most likely fail. This function is called from the DialCall
member function of the CTapiConnection class.
LPLINEDEVCAPS CTapiConnection::MylineGetDevCaps(
LPLINEDEVCAPS lpLineDevCaps,
DWORD dwDeviceID, DWORD dwAPIVersion)
{
// Allocate
enough space for the structure plus 1024.
size_t sizeofLineDevCaps = sizeof(LINEDEVCAPS) + 1024;
long
lReturn;
// Continue
this loop until the structure is big enough.
while(TRUE)
{
// Make sure the
buffer exists, is valid, and is big enough.
lpLineDevCaps =
(LPLINEDEVCAPS) CheckAndReAllocBuffer(
(LPVOID) lpLineDevCaps, //
pointer to existing buffer, if any
sizeofLineDevCaps); // minimum size the buffer should be
if
(lpLineDevCaps == NULL)
return NULL;
// Make the call to
fill the structure.
do
{
lReturn =
::lineGetDevCaps(m_hLineApp, dwDeviceID,
dwAPIVersion, 0, lpLineDevCaps);
if (HandleLineErr(lReturn))
continue;
else
{
LocalFree(lpLineDevCaps);
return NULL;
}
}
while
(lReturn != SUCCESS);
// If the buffer was
big enough, then succeed.
if
((lpLineDevCaps -> dwNeededSize)
<= (lpLineDevCaps -> dwTotalSize))
return lpLineDevCaps;
// Buffer wasn't big
enough. Make it bigger and try again.
sizeofLineDevCaps = lpLineDevCaps
-> dwNeededSize;
}
}
LPVOID CTapiConnection::CheckAndReAllocBuffer(LPVOID lpBuffer,
size_t sizeBufferMinimum)
{
size_t sizeBuffer;
if
(lpBuffer == NULL)
// allocate the buffer if necessary
{
sizeBuffer = sizeBufferMinimum;
lpBuffer = (LPVOID) LocalAlloc
(LPTR, sizeBuffer);
if
(lpBuffer == NULL)
{
OutputDebugString("LocalAlloc failed in CheckAndReAllocBuffer./n");
return NULL;
}
}
else // if the structure already exists, make
sure it is good
{
sizeBuffer = LocalSize((HLOCAL)
lpBuffer);
if
(sizeBuffer == 0)
// bad pointer?
return NULL;
// Was the buffer big enough for the structure?
if
(sizeBuffer < sizeBufferMinimum)
{
LocalFree(lpBuffer);
return CheckAndReAllocBuffer(NULL,
sizeBufferMinimum);
}
}
// Set the dwTotalSize field to the size of the buffer or the call
will
// fail.
memset(lpBuffer, 0, sizeBuffer);
((LPVARSTRING) lpBuffer )
-> dwTotalSize = (DWORD) sizeBuffer;
return
lpBuffer;
}
Another task that must be accomplished before actually
obtaining the line is to call the lineNegotiateAPIVersion
function. This function indicates the version of TAPI that the application was
written to support. In the DIALIT sample, I pass in constants that are defined
at the top of the TAPIUTILS.CPP file to indicate which versions of TAPI support
my sample. This is done in the GetDeviceLine
member function, which obtains the first usable voice line available to me.
// TAPI version that this sample is designed to use.
#define SAMPLE_TAPI_VERSION 0x00010004
// Early TAPI version.
#define EARLY_TAPI_VERSION 0x00010003
LPLINEDEVCAPS CTapiConnection::GetDeviceLine(DWORD *pdwAPIVersion,
LPLINEDEVCAPS lpLineDevCaps)
{
DWORD dwDeviceID;
char
szLineUnavail[] = "Line Unavailable";
char
szLineUnnamed[] = "Line Unnamed";
char
szLineNameEmpty[] = "Line Name is Empty";
LPSTR lpszLineName;
long
lReturn;
char
buf[MAX_PATH];
LINEEXTENSIONID lineExtID;
BOOL bDone
= FALSE;
for
(dwDeviceID = 0; (dwDeviceID
< m_dwNumDevs) && !bDone;
dwDeviceID ++)
{
lReturn = ::lineNegotiateAPIVersion(m_hLineApp, dwDeviceID,
EARLY_TAPI_VERSION, SAMPLE_TAPI_VERSION,
pdwAPIVersion, &lineExtID);
if
((HandleLineErr(lReturn))&&(*pdwAPIVersion))
{
lpLineDevCaps = MylineGetDevCaps(lpLineDevCaps, dwDeviceID,
*pdwAPIVersion);
if ((lpLineDevCaps -> dwLineNameSize) &&
(lpLineDevCaps -> dwLineNameOffset) &&
(lpLineDevCaps -> dwStringFormat == STRINGFORMAT_ASCII))
{
// This is the name of the device.
lpszLineName = ((char *) lpLineDevCaps) +
lpLineDevCaps -> dwLineNameOffset;
sprintf(buf, "Name of device is: %s\n", lpszLineName);
OutputDebugString(buf);
}
else // DevCaps doesn't have a valid line name. Unnamed.
lpszLineName = szLineUnnamed;
}
else // Couldn't NegotiateAPIVersion.
Line is unavail.
lpszLineName = szLineUnavail;
// If this line is
usable and we don't have a default initial
// line yet, make
this the initial line.
if
((lpszLineName != szLineUnavail)
&&
(lReturn == SUCCESS ))
{
m_dwDeviceID = dwDeviceID;
bDone = TRUE;
}
else
m_dwDeviceID = MAXDWORD;
}
return
(lpLineDevCaps);
}
In your application, you may wish to get the line
information and present it to the user. For example, on my laptop computer I
have two modems: one that I use when I am running without using the docking
station and one that I use when I am docked. The way my sample is written now,
the first modem selected is the one that I use when I am docked. This means
that I can't test the sample at home. (Too bad. How
sad.)
Calling lineOpen
We've chosen a line, and now we want to know whether this
is the line we want to use. In the DIALIT sample, we check to see if the line
is usable, if it can handle voice calls, and if it is capable of dialing out.
This is done by checking the values filled into the LINEDEVCAPS
structure I passed in.
if (!(lpLineDevCaps->dwBearerModes & LINEBEARERMODE_VOICE ))
{
AfxMessageBox("The
selected line doesn't support VOICE capabilities");
goto DeleteBuffers;
}
// Does this line have the capability to make calls?
if (!(lpLineDevCaps->dwLineFeatures & LINEFEATURE_MAKECALL))
{
AfxMessageBox("The
selected line doesn't support MAKECALL capabilities");
goto DeleteBuffers;
}
// Does this line have the capability for interactive voice?
if (!(lpLineDevCaps->dwMediaModes & LINEMEDIAMODE_INTERACTIVEVOICE))
{
AfxMessageBox("The
selected line doesn't support INTERACTIVE VOICE
capabilities");
goto DeleteBuffers;
}
An application uses the lineOpen
function to place calls and to monitor incoming calls. When opening a line for
an outgoing call, the DIALIT sample sets the privilege level to
LINECALLPRIVILEGE_NONE to insulate it from incoming calls and to allow outgoing
calls. You can set other privileges, but these are for call monitoring.
// Open the line for an outgoing call.
do
{
lReturn = ::lineOpen(m_hLineApp, m_dwDeviceID, &m_hLine,
m_dwAPIVersion,
0, 0, LINECALLPRIVILEGE_NONE, 0, 0);
if((lReturn == LINEERR_ALLOCATED) ||(lReturn
== LINEERR_RESOURCEUNAVAIL))
{
HangupCall();
OutputDebugString("Line
is already in use by a non-TAPI application"
" or by another TAPI Service Provider.\n");
goto DeleteBuffers;
}
if
(HandleLineErr(lReturn))
continue;
else
{
OutputDebugString("Unable
to Use Line\n");
HangupCall();
goto DeleteBuffers;
}
}
while(lReturn != SUCCESS);
Step Three: Placing
the Call
That's right. We haven't even placed the call yet.
An application uses the lineMakeCall
function to place a call. This function takes the following parameters:
·
A handle to the open line obtained from the lineOpen
call.
·
A pointer to the handle for the call. This will be filled in by lineMakeCall.
·
The destination address (the area code
and telephone number).
·
The country code (which can be set to zero to use the default
value).
·
A pointer to line parameters. This allows the application to
specify how the call should be set up (that is, the data rate, dialing
parameters, and so on). If this is set to NULL, the default setup is used.
The lineMakeCall function
returns a positive "request ID" (which I saved in the m_dwRequestID member variable of my class) if the
function will be completed asynchronously, or a negative error number if an
error has occurred. The following function demonstrates how the LPCALLPARAMS
structure can be filled in to support a simple interactive voice call.
LPLINECALLPARAMS CTapiConnection::CreateCallParams
(
LPLINECALLPARAMS lpCallParams, LPCSTR lpszDisplayableAddress)
{
size_t sizeDisplayableAddress;
if
(lpszDisplayableAddress == NULL)
lpszDisplayableAddress = "";
sizeDisplayableAddress = strlen(lpszDisplayableAddress) + 1;
lpCallParams = (LPLINECALLPARAMS) CheckAndReAllocBuffer(
(LPVOID) lpCallParams,
sizeof(LINECALLPARAMS) + sizeDisplayableAddress);
if
(lpCallParams == NULL)
return
NULL;
// This is where we configure the line.
lpCallParams -> dwBearerMode
= LINEBEARERMODE_VOICE;
lpCallParams -> dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE;
// This
specifies that we want to use only IDLE calls and
// don't want to cut
into a call that might not be IDLE (ie, in use).
lpCallParams -> dwCallParamFlags
= LINECALLPARAMFLAGS_IDLE;
// If there are multiple
addresses on line, use first anyway.
// It will take a more
complex application than a simple tty
app
// to use multiple
addresses on a line anyway.
lpCallParams -> dwAddressMode
= LINEADDRESSMODE_ADDRESSID;
// Address we are
dialing.
lpCallParams -> dwDisplayableAddressOffset
= sizeof(LINECALLPARAMS);
lpCallParams -> dwDisplayableAddressSize
= sizeDisplayableAddress;
strcpy((LPSTR)lpCallParams + sizeof(LINECALLPARAMS),
lpszDisplayableAddress);
return
lpCallParams;
}
After the lineMakeCall
function successfully sets up the call, the application receives a LINE_REPLY
message (the asynchronous reply to lineMakeCall).
The application gets this message through its callback function. This means
that a call at the local end has been established (that is, we have a dial
tone). The LINE_REPLY message also informs the application that the call handle
returned by lineMakeCall is valid. The
following code shows the line callback function that the DIALIT sample uses.
void CALLBACK CTapiConnection::lineCallbackFunc(
DWORD dwDevice, DWORD dwMsg, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD
dwParam2, DWORD dwParam3)
{
// Handle the line
messages.
switch(dwMsg)
{
case
LINE_CALLSTATE:
MyThis->HandleLineCallState(dwDevice, dwMsg,
dwCallbackInstance,
dwParam1,
dwParam2, dwParam3);
break;
case
LINE_CLOSE:
// Line has been
shut down.
ASSERT(MyThis);
MyThis->m_hLine = NULL;
MyThis->m_hCall = NULL;
MyThis->HangupCall(); // all handles
invalidated by this time
break;
case
LINE_REPLY:
if ((long) dwParam2 != SUCCESS)
OutputDebugString("LINE_REPLY error\n");
else
OutputDebugString("LINE_REPLY: successfully replied\n");
break;
case
LINE_CREATE:
ASSERT(MyThis);
if (MyThis->m_dwNumDevs
<= dwParam1)
MyThis->m_dwNumDevs =
dwParam1+1;
break;
default:
OutputDebugString("lineCallbackFunc message
ignored\n");
break;
}
return;
}
Several other messages or notifications are sent to the
application's callback function. For instance, as the call is placed, the call
passes through a number of states, each of which results in a LINE_CALLSTATE
message sent to the callback function. After the message indicating the
connected state is received, the application can begin sending data. I used a
handler for the LINE_CALLSTATE message that prints debug messages indicating
the current call status. For your application, you may wish to write these
messages to a status bar.
void CTapiConnection::HandleLineCallState(
DWORD dwDevice, DWORD dwMessage, DWORD dwCallbackInstance,
DWORD dwParam1, DWORD
dwParam2, DWORD dwParam3)
{
// Error if this
CALLSTATE doesn't apply to our call in progress.
if
((HCALL) dwDevice != m_hCall)
{
OutputDebugString("LINE_CALLSTATE:
Unknown device ID");
return;
}
// dwParam1 is the
specific CALLSTATE change that is occurring.
switch
(dwParam1)
{
case
LINECALLSTATE_DIALTONE:
OutputDebugString("Dial Tone\n");
break;
case
LINECALLSTATE_DIALING:
OutputDebugString("Dialing\n");
break;
case
LINECALLSTATE_PROCEEDING:
OutputDebugString("Proceeding\n");
break;
case
LINECALLSTATE_RINGBACK:
OutputDebugString("RingBack\n");
break;
case
LINECALLSTATE_BUSY:
OutputDebugString("Line busy, shutting down\n");
HangupCall();
break;
case
LINECALLSTATE_IDLE:
OutputDebugString("Line idle\n");
HangupCall();
break;
case
LINECALLSTATE_SPECIALINFO:
OutputDebugString("Special Info, probably couldn't dial number\n");
HangupCall();
break;
case
LINECALLSTATE_DISCONNECTED:
{
LPSTR pszReasonDisconnected;
switch (dwParam2)
{
case LINEDISCONNECTMODE_NORMAL:
pszReasonDisconnected =
"Remote Party Disconnected";
break;
case LINEDISCONNECTMODE_UNKNOWN:
pszReasonDisconnected =
"Disconnected: Unknown reason";
break;
case LINEDISCONNECTMODE_REJECT:
pszReasonDisconnected =
"Remote Party rejected call";
break;
case LINEDISCONNECTMODE_PICKUP:
pszReasonDisconnected =
"Disconnected: Call was picked up on another phone";
break;
case LINEDISCONNECTMODE_FORWARDED:
pszReasonDisconnected =
"Disconnected: Forwarded";
break;
case LINEDISCONNECTMODE_BUSY:
pszReasonDisconnected =
"Disconnected: Busy";
break;
case LINEDISCONNECTMODE_NOANSWER:
pszReasonDisconnected =
"Disconnected: No Answer";
break;
case LINEDISCONNECTMODE_BADADDRESS:
pszReasonDisconnected =
"Disconnected: Bad Address";
break;
case LINEDISCONNECTMODE_UNREACHABLE:
pszReasonDisconnected =
"Disconnected: Unreachable";
break;
case
LINEDISCONNECTMODE_CONGESTION:
pszReasonDisconnected =
"Disconnected: Congestion";
break;
case LINEDISCONNECTMODE_INCOMPATIBLE:
pszReasonDisconnected =
"Disconnected: Incompatible";
break;
case LINEDISCONNECTMODE_UNAVAIL:
pszReasonDisconnected =
"Disconnected: Unavailable";
break;
case LINEDISCONNECTMODE_NODIALTONE:
pszReasonDisconnected = "Disconnected: No Dial
Tone";
break;
default:
pszReasonDisconnected =
"Disconnected: LINECALLSTATE; Bad Reason";
break;
}
OutputDebugString(pszReasonDisconnected);
OutputDebugString("\n");
HangupCall();
break;
}
case
LINECALLSTATE_CONNECTED: //
CONNECTED!!!
OutputDebugString("Connected!\n");
break;
default:
OutputDebugString("Unhandled LINECALLSTATE message\n");
break;
}
}
Step Four: Sending
Data
If the DIALIT sample supported data transmission, now
would be the time that it would send the data. The user would specify, through
some user interface widget, what file or data to send and then initiate the
data transmission. The TAPI functions continue to manage the opened line and
the call in progress, but the actual transmission is started and controlled by
non-TAPI functions (that is, COMM functions). This is the type of transmission
that the TAPICOMM sample demonstrates.
In our case, we are establishing a voice call. TAPI
continues to monitor the line and call, but if there is anything special we
need to do, it is up to us. When a call is connected, the service provider may
display a dialog box asking the user to lift the telephone receiver and click
the Talk button.
A Call Status dialog box
In our sample, we use a function (borrowed from the
TAPICOMM sample) that allows us to resynchronize a TAPI line call by waiting
for the LINE_REPLY message. In other words, it waits until a LINE_REPLY is
received or the line is shut down. The function is called from the same thread
that made the call to lineInitialize.
Note If you try to call this
function from a different thread than the thread that called the lineInitialize function, the PeekMessage
function will not be synchronized with the correct thread. Instead, it will be
peeking at the message pump for the wrong thread. TAPI is designed such that a
hidden window is created by TAPI on behalf of the application when the lineInitialize function is called. All notifications
and messages are sent to the callback function specified in the lineInitialize function. Thus the thread that called
lineInitialize contains the message pump for
the messages that will be sent to the callback function.
If the call was dropped while in a wait state, this
function can potentially be re-entered. We handle this by dropping out of the
function and assuming that the call was canceled. This is the reason for the bReentered flag. This flag is set to FALSE when the
function is entered and TRUE when the function is exited. If bReentered is ever TRUE during the function
processing, then the function was re-entered.
long CTapiConnection::WaitForReply
(long lRequestID)
{
static
BOOL bReentered;
bReentered = FALSE;
if
(lRequestID > SUCCESS)
{
MSG msg;
DWORD dwTimeStarted;
m_bReplyReceived
= FALSE;
m_dwRequestedID
= (DWORD) lRequestID;
// Initializing this just in case there is a bug
// that sets m_bReplyReceived without setting the reply value.
m_lAsyncReply
= LINEERR_OPERATIONFAILED;
dwTimeStarted = GetTickCount();
while(!m_bReplyReceived)
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// This should only occur if the line is shut down while
waiting.
if ((m_hCall != NULL) &&(!m_bTapiInUse || bReentered))
{
bReentered = TRUE;
return WAITERR_WAITABORTED;
}
// It's a really
bad idea to timeout a wait for a LINE_REPLY.
// If we are
expecting a LINE_REPLY, we should wait till we get
// it; it might
take a long time to dial (for example).
// If 5 seconds
go by without a reply, it might be a good idea
// to display a
dialog box to tell the user that a
// wait is in
progress and to give the user the capability to
// abort the
wait.
}
bReentered = TRUE;
return
m_lAsyncReply;
}
bReentered = TRUE;
return
lRequestID;
}
Step Five: Ending the
Call
When the user finishes the phone call, the application
receives a LINE_CALLSTATE message telling it that the state of a line device
has changed. At this point the application can disconnect the call. In the
DIALIT sample, the application disconnects the call when the user clicks the
Hang Up button. The sample ends the calls, closes the line, shuts down TAPI,
and exits.
Before the application disconnects the call, it checks to
see if a call is already in progress. If not, the application calls the lineDrop function to place the call in the IDLE
state. The call handle must then be released for the finished call. This is
done by the lineDeallocateCall function.
Finally, lineClose is called to close the
line. At this point, there will be no more incoming or outgoing calls using
that line handle.
BOOL CTapiConnection::HangupCall()
{
LPLINECALLSTATUS pLineCallStatus = NULL;
long
lReturn;
// Prevent
HangupCall re-entrancy
problems.
if
(m_bStoppingCall)
return
TRUE;
// If TAPI is not being
used right now, then the call is hung up.
if
(!m_bTapiInUse)
return
TRUE;
m_bStoppingCall
= TRUE;
// If there is a call in
progress, drop and deallocate it.
if
(m_hCall)
{
pLineCallStatus = (LPLINECALLSTATUS)malloc(sizeof(LINECALLSTATUS));
if
(!pLineCallStatus)
{
ShutdownTAPI();
m_bStoppingCall = FALSE;
return FALSE;
}
lReturn = ::lineGetCallStatus(m_hCall, pLineCallStatus);
// Only drop the call when the line is not IDLE.
if
(!((pLineCallStatus -> dwCallState)
& LINECALLSTATE_IDLE))
::lineDrop(m_hCall,
NULL, 0);
// The call is now idle. Deallocate
it!
do
{
lReturn = ::lineDeallocateCall(m_hCall);
if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugString("lineDeallocateCall
unhandled error\n");
break;
}
}
while(lReturn != SUCCESS);
}
// If we have a line
open, close it.
if (m_hLine)
{
do
{
lReturn = ::lineClose(m_hLine);
if (HandleLineErr(lReturn))
continue;
else
{
OutputDebugString("lineClose unhandled
error\n");
break;
}
}
while(lReturn != SUCCESS);
}
// Clean up.
m_hCall
= NULL;
m_hLine
= NULL;
m_bTapiInUse
= FALSE;
m_bStoppingCall
= FALSE; // allow HangupCall
to be called again
// Need to free buffer
returned from lineGetCallStatus.
if
(pLineCallStatus)
free(pLineCallStatus);
return
TRUE;
}
Next, the application calls lineShutdown
to end the use of TAPI.
BOOL CTapiConnection::ShutdownTAPI()
{
long
lReturn;
// If we aren't
initialized, then Shutdown is unnecessary.
if
(m_hLineApp == NULL)
return
TRUE;
// Prevent
ShutdownTAPI re-entrancy
problems.
if
(m_bShuttingDown)
return
TRUE;
m_bShuttingDown
= TRUE;
HangupCall();
lReturn = ::lineShutdown(m_hLineApp);
if
(!HandleLineErr(lReturn))
OutputDebugString("lineShutdown unhandled error\n");
m_bTapiInUse
= FALSE;
m_hLineApp
= NULL;
m_hCall
= NULL;
m_hLine
= NULL;
m_bShuttingDown
= FALSE;
return
TRUE;
}
At this point the application is finished using TAPI and
can continue to do whatever else it was designed to do. In the DIALIT sample,
the application exits.