Chapter 24

TAPI Basics


CONTENTS


In this chapter, you'll learn how to build a simple TAPI dialer application in C using the Basic Telephony level of service. This application will be used to highlight the basic operations required to build TAPI applications (in any language).

You'll learn how to perform line initialization, locate a usable outbound line, and open it in preparation for dialing. You'll also learn how to place an outbound call and use the TAPI line callback function to monitor call progress. Finally, you'll learn how to safely close down a line after the call has been completed.

When you are done with the example in this chapter, you'll understand the basics of writing TAPI applications and know how to use Basic Telephony services in your own applications.

Note
The project in this chapter was written using the Microsoft Visual C++ 4.1 compiler. However, the code is compatible with the Microsoft VC 2.0 compiler. If you do not have a C compiler, you can still get a lot out of the chapter. The same techniques covered here will be used when you build TAPI applications in Microsoft Visual Basic 4.0 later in this section of the book.

Using TAPI to Place Outbound Calls

Before starting the review of the TAPIOUT project, it is a good idea to cover the minimal steps needed to place an outbound call using TAPI services. There are really only a few steps:

After you complete these steps, you can use the messages received by the registered callback function to track call progress and respond accordingly. The next few sections cover the outbound calling steps in greater detail.

Calling lineInitialize to Start the TAPI Session

The first thing you need to do to start a TAPI session is to call the lineInitialize routine to initialize the link between your application and the TAPI service provider. The lineInitialize routine includes a pointer to the callback function in your code that will handle all messages.

After successful initialization, the routine returns a value in the lineHandle parameter. You'll use this value throughout your TAPI session. You will also get a count of the total number of TAPI lines defined for this workstation. You'll use that information to check the API version and line parameters of each line before you attempt to place a call.

If an error occurs, a non-zero value is returned. You can check the errors using a case switch and present a message to the user.

Calling lineNegotiateAPIVersion to Check TAPI Services

After you successfully open the TAPI session, you need to call the lineNegotiateAPIVersion function for each line in the collection. The total number of lines was returned as part of the lineInitialize routine. You need to check this value because it is possible that you will be requesting a version of TAPI this is not available for this machine.

You pass your API version request to the function and get a value back that is the version of TAPI that the workstation can provide to your application. You can also get a pointer to a structure that holds information about vendor-specific extension services available on this workstation. This is a method for allowing non-TAPI services to be recognized using the TAPI interface.

Using lineOpen to Locate an Appropriate TAPI Line Device

Once you have successfully negotiated an API version, you must use the lineOpen function to pass through each line and request the appropriate level of service. For example, if you wanted to place an interactive voice call, you'd use the lineOpen function to locate a line that supports interactive voice.

This is an important point. It is quite possible that the workstation will have several TAPI devices defined, but only one may provide the type of service you need (voice, fax, data, and so on). If there is no device available (none exists or the current one is busy), you'll get an error message. However, if an appropriate line is available, you'll receive a zero as a return code and a value indicating the handle of the open line. You'll use this value in subsequent TAPI calls.

Setting Call Parameters with the LINECALLPARAMS Structure

Once you locate an appropriate line, you can set calling parameters using the LINECALLPARAMS structure. You use this structure to tell TAPI the speed and media type (data, voice, and so on) of your call and other values.

Setting the LINECALLPARAMS structure is optional. If you do not set any value for the LINECALLPARAMS, Microsoft TAPI will use default values. For most calls, the default values will work just fine.

Using lineMakeCall to Place the Call

The last step in the process is actually placing the call using the lineMakeCall function. This function passes the string that contains the phone number to call, a handle for the open line (you got that from lineOpen) and, optionally, a pointer to the LINECALLPARAMS structure.

If the call is placed successfully, a call handle is returned. You'll use this call handle in subsequent TAPI functions. If there is trouble making the call, the return code is non-zero and can be checked for appropriate action.

It is important to note that at this point the call has been placed but not completed. All TAPI knows for sure is that digits have been dialed and the phone line is active. TAPI will continue to receive status information and route that to your application through the callback function registered when you called lineInitialize. The quality of the status information (dialing, ringing, busy, idle, and so on) is all determined by the hardware vendor and TAPI service provider application. The more sophisticated the hardware, the more accurate the progress information.

For example, standard data/fax modems do not report call progress information. When you place your TAPI call, you'll be notified by the hardware that the call is in progress and will see nothing else until the call is completed or a time-out occurs. Other hardware (advanced voice/data modems) may provide additional call-progress data. High-end telephony cards provide the most accurate information.

Now that you know the basics of placing an outbound call using TAPI, it's time to review the TAPIOUT project on the CD-ROM that accompanies this book.

The TAPIOut Project

The TAPIOUT project that ships on the CD-ROM is a C program that allows users to enter a phone number and use TAPI to place an outbound call. This project is very rudimentary. There are no extra bells and whistles. However, the code in this project gives a good review of what it takes to provide basic TAPI services. The next several sections of this chapter review the TAPIOUT project step-by-step. You'll see how you can use the TAPI functions described earlier in the chapter to create a functional TAPI dialer.

If you have a copy of Microsoft VC++ 2.0 or later, start it now and load the TAPIOUT.MAK (or TAPIOUT.MDP) project from the CD-ROM. You can follow along with the examples in the chapter

Tip
You'll need to have the TAPI SDK installed on your machine before you can compile this project. Once you load the project, be sure to select Tools | Update All Dependencies to resolve references to the TAPI.H and TAPI32.LIB files in the project. You may need to reload these files into the project using Insert | Files into Project...

The Initial Declarations

This first step in the process is declaring all the needed includes, defines, function prototypes, and global variables. Listing 24.1 shows how this looks in the TAPIOUT project.


Listing 24.1. The initial declarations of the TAPIOUT project.
// ******************************************************************
// SIMPLE OUTBOUND TAPI DIALER APPLICATION
// ******************************************************************
//
// Title:    TAPIOut
// Version:    1.0 - 05/24/96 (MCA)
//
// Equip:    VC++ 4.0 / Win95 / TAPI SDK
//
// Client:    MAPI, SAPI, TAPI Developer's Guide (SAMS 1996)
//
// Desc:    Simple dialog to show how to use TAPI to place outbound
//            calls. Takes dialing string and shows progress as the
//            program attempts to complete the call.
//
// Files:    TAPIOUT.C
//            TAPIOUT.RC
//            TAPIOUT.DEF
//            RESOURCE.H
//            TAPI.H
//            TAPI32.LIB
//
// ******************************************************************

// ****************************************
// Includes and defines
//
#include "windows.h"
#include "tapi.h"
#include "resource.h"

#define tapiVersionCur  (MAKELONG(4,1)) // ver 1.4
#define    TAPI_LINE_REPLY            5000
#define TAPI_LINECALLSTATE_CONNECTED        5001
#define TAPI_LINECALLSTATE_IDLE        5002
#define TAPI_LINECALLSTATE_DISCONNECTED    5003
#define TAPI_LINECALLSTATE_BUSY        5004
#define TAPI_LINECALLSTATE_AccEPTED        5005
#define TAPI_LINECALLSTATE_PROCEEDING        5006
#define TAPI_LINECALLSTATE_OFFERING        5007
#define TAPI_LINECALLSTATE_DIALTONE        5008
#define TAPI_LINECALLSTATE_DIALING        5009

// ******************************************
// global declares
//
LONG PlaceCall( HWND, LPTSTR );
void CALLBACK LineCallBackProc(DWORD hDevice,DWORD dwMessage,DWORD dwInstance,DWORD ÂdwParam1,DWORD dwParam2,DWORD dwParam3);
BOOL WINAPI MainDialog(HWND hDlg, WORD msg, WORD wParam, LONG lParam);
void ShowProgress( HWND hWnd, LPTSTR OutputString );
void SetVarProps( HWND hWnd, DWORD hDevice );

LINECALLPARAMS LineParams;    // need this structure
DWORD lines;            // count of available lines
HINSTAncE hInst;        // this instance of the app
HWND MainWin, ButtonWnd;    // window handles
HLINEAPP LineHandle = NULL; // tapi line handle

Notice the inclusion of the TAPI.H and WINDOWS.H files. You'll need these on your system if you want to compile this project. You'll also need the TAPI32.LIB file.

The defines added here make it easy to provide a local message handler that responds to predefined TAPI messages. You'll see how these are used in the MainDialog and lineCallBack routines later in this chapter.

Notice also the declaration of global handles and a lineParams structure. You'll use these throughout the project.

The User Dialog Box and the WinMain Procedure

The WinMain code for this project is quite simple. Declare a message queue, get the current instance of this program, and then call the main dialog box. All other activity is generated by the dialog box. Listing 24.2 shows the code for the WinMain routine.


Listing 24.2. The WinMain routine for the TAPIOUT project.
// *******************************************
// Initial Entry
//
// establish a message queue
// get the instance handle
// start the user dialog
//
int PASCAL WinMain( HANDLE hInstance, HANDLE hPrev, LPSTR lpCmd, int nShow )
{
       SetMessageQueue( 100 );
       hInst = hInstance;
    DialogBox( hInstance, MAKEINTRESOURCE( ID_MAIN_SCREEN ), NULL, MainDialog );
       return( FALSE );
}

The main dialog box contains only a few controls. An input box for the phone number, a list box to show the status messages supplied by TAPI, and three command buttons (PlaceCall, Disconnect, and Exit). Figure 24.1 shows the layout of the main dialog box.

Figure 24.1 : The main dialog box of the TAPIOUT project

The code for the main dialog box is a bit lengthy; however, it is rather simple, too. The code can be broken down into three main sections:

The first part of the MainDialog code responds to the initial loading of the dialog box and to user actions on the command buttons. Listing 24.3 shows how this code looks.


Listing 24.3. The first part of the MainDialog code.
// ********************************************
// Main Dialog to handle user interface
//
BOOL WINAPI MainDialog(HWND hDlg, WORD msg, WORD wParam, LONG lParam)
{
    switch (msg)
    {
        case WM_INITDIALOG:    // when dialgo first starts up
        {
            // Set the necessary properties to null
            SetProp( hDlg, "HCALL", NULL );
            SetProp( hDlg, "HLINE", NULL );
            SetProp( hDlg, "HCOMM", NULL );
            break;
        }
        case WM_COMMAND:    // user pressed a button
        {
            switch( wParam )
            {
                case ID_CALL:    // user press PLACE CALL
                {
                    char PhoneNumber[ 100 ];    // save some space
                    HCALL hCall;            // declare a local handle
                    //
                    // Gotta call going? - Uh, oh!
                    hCall = (HCALL)GetProp( hDlg, "HCALL" );
                    if( hCall != NULL  )
                    {
                        MessageBox( hDlg,"Please Disconnect before making another
                                          &nbs p;                 Âcall!",
                                          &nbs p;          " Tapi Error",
                                          &nbs p;          MB_ICONSTOP );
                        break;
                    }
                    //
                    // Get digits from input box
                    GetDlgItemText( hDlg, ID_PHONE, PhoneNumber, sizeof( ÂPhoneNumber ) );
                    //
                    // place the call (check return value)
                    if( PlaceCall( hDlg, PhoneNumber ) < 0 )
                        ShowProgress( hDlg, "Unable to start a TAPI Function" );
                    break;
                }
                case ID_DISCONNECT:    // user press DISCONNECT
                {
                    LONG retcode;    // some local stuff
                    HCALL hCall;
                    HANDLE hComm;
                    //
                    // try to get the handles
                    hCall = (HCALL)GetProp( hDlg, "HCALL" );
                    hComm = (HANDLE)GetProp( hDlg, "HCOMM" );
                    //
                    // if we have a comm handle, drop it
                    if( hComm != NULL )
                    {
                        CloseHandle( hComm );
                        SetProp( hDlg, "HCALL", NULL );
                    }
                    //
                    // if we have a call handle, drop it
                    if( hCall != NULL )
                    {
                        retcode = lineDrop( hCall, NULL, 0 );
                        ShowProgress( hDlg, "Call is Dropped" );
                        SetProp( hDlg, "HCALL", NULL );
                    }
                    break;
                }
                case IDOK:    // user pressed the EXIT button
                {
                    HCALL hCall; // declare some local vars
                    HLINE hLine;
                    HANDLE hComm;
                    //
                    // load the values
                    hCall = (HCALL)GetProp( hDlg, "HCALL" );
                    hLine = (HLINE)GetProp( hDlg, "HLINE" );
                    hComm = (HANDLE)GetProp( hDlg, "HCOMM" );
                    //
                    // if we have a comm handle, close it
                    if( hComm != NULL )
                    {
                        CloseHandle( hComm );
                        SetProp( hDlg, "HCOMM", NULL );
                    }
                    //
                    // if we have a call handle, close it
                    if( hCall != NULL )
                    {
                        lineDrop( hCall, NULL, 0 );
                        SetProp( hDlg, "HCALL", NULL );
                    }
                    //
                    // if we have a line handle, close it
                    if( hLine != NULL )
                    {
                        lineClose( hLine );
                        SetProp( hDlg, "HLINE", NULL );
                    }
                    //
                    // close down open line
                    if( LineHandle != NULL )
                    {
                        lineShutdown( LineHandle );
                        LineHandle = NULL;
                    }
                    //
                    // drop the save properties
                    RemoveProp( hDlg, "HCALL" );
                    RemoveProp( hDlg, "HLINE" );
                    RemoveProp( hDlg, "HCOMM" );
                    //
                    // close down the dialog
                    EndDialog( hDlg, FALSE );
                    break;
                                          &nbs p;                                       }

The code here deserves some review. First, when the dialog box first starts, three properties are created. These will hold values used throughout the dialog. The next event is the pressing of the ID_CALL button. This tells the dialog box to attempt to place a call. The first step is to check to see if a call is already in progress. If so, a message is displayed to the user. If no call is currently in progress, the phone number is gathered from the input box and then passed to the PlaceCall function for final dispatch (you'll see the PlaceCall function in the next section of this chapter).

If the user presses the ID_DISCONNECT button, the program checks the comm and call handles and, if they are set, clears them using the ClearHandle and lineDrop functions.

Finally, when the user presses the Exit button (IDOK), the same types of routines executed in ID_DISCONNECT must also occur here. In addition to the ClearHandle and lineDrop functions, the lineClose and lineShutdown routines are called. This performs final closure on all TAPI services for this session.

The second section of the MainDialog is used to respond to TAPI messages received via the lineCallBack function and passed onto the MainDialog. The only message that needs attention is when a call goes idle. If a call goes idle, the MainDialog needs to close down the line resources just as if the user had pressed the Disconnect button. Listing 24.4 shows how this code looks in the TAPIOUT project.


Listing 24.4. Responding to the TAPI Messages in the MainDialog.
                //
                // **************************************
                // respond to TAPI Messages
                // **************************************
                //
                case TAPI_LINE_REPLY:
                {
                    ShowProgress( hDlg, "Line Reply" );
                     break;
                }
                //
                case TAPI_LINECALLSTATE_CONNECTED:
                {
                    ShowProgress( hDlg, "Line Call State is Connected" );
                    break;
                }
                //
                case TAPI_LINECALLSTATE_IDLE:
                {
                    LONG retcode;    // local stuff
                    HLINE hLine;
                    //
                    // call went idle, do cleanup
                    hLine = (HLINE)GetProp( hDlg, "HLINE" );
                    //
                    // if we have a live line, close it
                    if( hLine != NULL )
                    {
                        retcode = lineClose( hLine );
                        SetProp( hDlg, "HLINE", (HANDLE)NULL );
                    }
                    ShowProgress( hDlg, "Line Call State is idle" );
                    break;
                                          &nbs p;                                       }

Notice that, unlike the disconnect which performs both a lineDrop and a lineClose, the idle line handler only calls lineClose. This is because idle lines have already experienced the lineDrop. This is why they are idle!

The last section of the MainDialog is used to simply post status messages to the list box on the dialog box. The code is added here to show you progress during the call. You may not need to code these messages at all in production applications. Listing 24.5 shows how this code looks.


Listing 24.5. Posting TAPI messages from the MainDialog.
                //
                // *********************************************
                // respond to forwarded TAPI messages
                // *********************************************
                //
                case TAPI_LINECALLSTATE_DISCONNECTED:
                {
                    ShowProgress( hDlg, "Line Call State is Disconnected" );
                    break;
                }
                //
                case TAPI_LINECALLSTATE_BUSY:
                {
                    ShowProgress( hDlg, "Line Call State is Busy" );
                    break;
                }
                //
                case TAPI_LINECALLSTATE_AccEPTED:
                {
                    ShowProgress( hDlg, "Line Call State is Accepted" );
                    break;
                }
                //
                case TAPI_LINECALLSTATE_PROCEEDING:
                {
                    ShowProgress( hDlg, "Line Call State is Proceeding" );
                    break;
                }
                //
                case TAPI_LINECALLSTATE_OFFERING:
                {
                    ShowProgress( hDlg, "Line Call State is Offering" );
                    break;
                }
                //
                case TAPI_LINECALLSTATE_DIALTONE:
                {
                    ShowProgress( hDlg, "Line Call State is DialTone" );
                    break;
                }
                //
                case TAPI_LINECALLSTATE_DIALING:
                {
                    ShowProgress( hDlg, "Line Call State is Dialing" );
                    break;
                }
                default:
                    break;
            } // switch (wParam)
            break;
        } // case WM_COMMAND
        default:
            break;
    } // switch (msg)
    return (FALSE);
} // main dialog

The PlaceCall Function

The real heart of the project is the PlaceCall function. This routine is the one that actually places the requested call. The code here follows the outline in the first part of this chapter. The steps of initialize, check API, look for open line, set call parameters, and place call are all here in this one routine. You can use this code as a shell routine to place in your other TAPI applications. Listing 24.6 shows the code for the PlaceCall function.


Listing 24.6. The code for the PlaceCall function.
// ***********************************************
// This routine places the actual call
//
LONG PlaceCall( HWND hWnd, LPTSTR PhoneNumber )
{
    LONG    retcode;                // local returns
    DWORD    i;                        // counter for lines
    DWORD    ApiVersion;                // expected API version
    DWORD    RetApiVersion;            // return version
    LINEEXTENSIONID    ExtensionID;    // struc for API call
    HLINE    hLine;                    // local line handle
    HCALL    hCall;                    // local call handle
    //
    // make sure you have a phone number
    if( lstrlen( PhoneNumber ) < 1 )
        return( -1 );
    //
    // Initialize the line, register the callback
    if( LineHandle == NULL )
        retcode = lineInitialize( &LineHandle, hInst, Â(LINECALLBACK)LineCallBackProc, "TAPI Out", &lines );
    if( retcode < 0 )
        return( retcode );
    //
    // go through all lines to get API and properties
    // if you find one that has the right properties,
    // jump out and continue to next section of code
    //
    hLine = (HLINE)GetProp( hWnd, "HLINE" );
    if( hLine == NULL )
    {
        for( i=0; i < lines; i++ )
        {
            // Negotiate the API Version for each line
            ApiVersion = tapiVersionCur;
            retcode = lineNegotiateAPIVersion( LineHandle, i, ApiVersion, ÂApiVersion, &RetApiVersion,
                &ExtensionID );
            retcode = lineOpen( LineHandle, i, &hLine, RetApiVersion, 0, Â(DWORD)hWnd,
                LINECALLPRIVILEGE_OWNER | LINECALLPRIVILEGE_MONITOR,
                LINEMEDIAMODE_DATAMODEM, NULL );
            if( retcode == 0 )
                break;
        }
        if( retcode != 0 )
            return( -1 );
    }
    //
    // found a good line
    SetProp( hWnd, "HLINE",(HANDLE)(HLINE)hLine );
    //
    // now set of properties of the line for outbound dialing
    memset( &LineParams, 0, sizeof( LINECALLPARAMS ) );
    LineParams.dwTotalSize = sizeof( LINECALLPARAMS );
    LineParams.dwMinRate = 9600;    // setting data rates
    LineParams.dwMaxRate = 9600;    //
    LineParams.dwMediaMode = LINEMEDIAMODE_DATAMODEM;    // doing a data call
    //
    // finally place the call!
    retcode = lineMakeCall( hLine, &hCall, PhoneNumber, 0, &LineParams );
    return( retcode );    // tell'em how it turned out!
}

This code hardly needs review-you've seen this explained before. The key points to remember are:

It is also important to remember that once TAPI performs the lineMakeCall successfully you still are not connected to your called party. You need to check callback messages to check the status of your call.

The ShowProgress and SetVarProps Procedures

There are two helper routines in the TAPIOUT project. The first, ShowProgress, is used to post messages to the list box on the main dialog form. Listing 24.7 shows how this code looks.


Listing 24.7. Posting messages to the main dialog box.
// ************************************************
// update list box to show TAPI progress
//
void ShowProgress( HWND hDlg, LPTSTR OutputString )
{
    DWORD dwIndex;
    int i;
    dwIndex = SendDlgItemMessage( hDlg, ID_STATUS_LIST, LB_ADDSTRING, 0, Â(LPARAM)(LPSTR)OutputString );
    if( dwIndex == LB_ERR )        // clear some space for full box
    {
        for( i = 0; i < 10; i++ )
            SendDlgItemMessage( hDlg, ID_STATUS_LIST, LB_DELETESTRING, 0, 0 );
        // now send the message
        dwIndex = SendDlgItemMessage( hDlg, ID_STATUS_LIST, LB_ADDSTRING, 0, Â(LPARAM)(LPSTR)OutputString );
    }
    SendDlgItemMessage( hDlg, ID_STATUS_LIST, LB_SETCURSEL, (WPARAM)dwIndex, 0 );
    return;
}
The second routine is used to pick the line ID out of the LINECALLINFO structure and store it in the dialog properties set. Listing 24.8 shows the SetVarProps routine.
Listing 24.8. The code for the SetVarProps routine.
// ***************************************************
// get line handle from LINECALLINFO structure
//
void SetVarProps( HWND hWnd, DWORD hDevice )
{
    LINECALLINFO LineCallInfo;
    memset( &LineCallInfo, 0, sizeof( LINECALLINFO ) );
    SetProp( hWnd, "HCALL", (HANDLE)(HCALL)hDevice );
    LineCallInfo.dwTotalSize = sizeof( LINECALLINFO );
    lineGetCallInfo( (HCALL)hDevice, &LineCallInfo );
     SetProp( hWnd, "HLINE", (HANDLE)(HLINE)LineCallInfo.hLine );
    return;
}

The lineCallBackProc Procedure

The lineCallBackProc is the routine registered (using lineInitialize) to receive all TAPI messages for this application. Since this routine must handle all messages from TAPI, it can get a bit long. In this chapter, the code is broken into three segments:

The first section of the lineCallBackProc contains code to respond to changes in the LINE_CALLSTATE message. Only two messages get our attention here: LINE_CALLSTATE_IDLE and LINE_CALLSTATE_CONNECTED. Listing 24.9 shows the code that responds to these two messages.


Listing 24.9. Responding to the LINE_CALLSTATE_IDLE and LINE_CALLSTATE_CONNECTED messages.
// *******************************************
// The callback to handle TAPI messages
//
// This routine handles all messages generated by TAPI services.
// Most of these messages are ignored here or just passsed on to
// the main dialog for posting to the progress window.
//
void CALLBACK LineCallBackProc(DWORD hDevice,DWORD dwMessage,DWORD dwInstance,DWORD ÂdwParam1,DWORD dwParam2,DWORD dwParam3)
{

    switch (dwMessage)
        {
        case LINE_CALLSTATE:    // review the call state messages
            {
                switch( dwParam1 )
                {
                    case LINECALLSTATE_IDLE:    // went idle
                    {
                        LONG retcode;
                        LINECALLINFO LineCallInfo;
                        //
                        // load call info into structure
                        memset( &LineCallInfo, 0, sizeof( LINECALLINFO ) );
                        LineCallInfo.dwTotalSize = sizeof( LINECALLINFO );
                        lineGetCallInfo( (HCALL)hDevice, &LineCallInfo );
                        //
                        // deallocate the call
                        retcode = lineDeallocateCall( (HCALL)hDevice );
                        //
                        // post message to main dialog
                        PostMessage((HWND)dwInstance, WM_COMMAND, ÂTAPI_LINECALLSTATE_IDLE, (LPARAM)(HLINE)LineCallInfo.hLine );
                        break;
                    }
                    case LINECALLSTATE_CONNECTED:    // hey, we got through!
                    {
                        //
                        // local vars for processing
                        LPVARSTRING lpVarStringStruct = NULL;
                        size_t sizeofVarStringStruct = sizeof( VARSTRING ) + 1024;
                        HANDLE CommFile = NULL;
                        long lreturn;
                        // get the comm handle.  Be sure to drop this handle when
                        // the call is done or you'll get device unavailable errors
                        // and have to REBOOT!
                        lpVarStringStruct = LocalAlloc( 0, sizeofVarStringStruct );
                        do
                        {
                            memset( lpVarStringStruct, 0, sizeofVarStringStruct );
                            lpVarStringStruct->dwTotalSize = Â(DWORD)sizeofVarStringStruct;
                            lreturn = lineGetID( 0, 0, (HCALL)hDevice, ÂLINECALLSELECT_CALL, lpVarStringStruct, "comm/datamodem" );
                        } while( lreturn != 0 );
                        //
                        // get comm device handle and save it to properties area
                        CommFile = *( (LPHANDLE )( ( LPBYTE )lpVarStringStruct + ÂlpVarStringStruct->dwStringOffset ) );
                        SetProp( (HWND)dwInstance, "HCOMM", CommFile );
                        SetVarProps( (HWND)dwInstance, hDevice );
                        //
                        // tell main dialog we got through
                        PostMessage( (HWND)dwInstance, WM_COMMAND, ÂTAPI_LINECALLSTATE_CONNECTED, (LPARAM)(HANDLE)CommFile );
                        LocalFree( lpVarStringStruct ); // drop mem space
                        break;
                                          &nbs p;                              }

Notice that the LINE_CALLSTATE_CONNECTED routine contains code that retrieves the comm handle and gets the lineID. You'll need these values in subsequent calls to TAPI services. The LINE_CALLSTATE_IDLE routine simply de-allocates the device handle to free up resources for the next call.

The second segment of the callback routine passes messages to the main dialog box for display. These are added here to show you how the messages work and how call progress can be reported. You may not need to add these messages to your production applications. Listing 24.10 shows the portion of lineCallBackProc that posts messages to the MainDialog routine.


Listing 24.10. Passing messages from lineCallBack to MainDialog.
                case LINECALLSTATE_AccEPTED:    // just pass message on...
                    {
                        SetVarProps( (HWND)dwInstance, hDevice );
                        PostMessage( (HWND)dwInstance, WM_COMMAND, ÂTAPI_LINECALLSTATE_AccEPTED,(LPARAM)(HCALL)hDevice );
                         break;
                    }

                    case LINECALLSTATE_PROCEEDING:    // post progress message
                    {
                        SetVarProps( (HWND)dwInstance, hDevice );
                        PostMessage( (HWND)dwInstance, WM_COMMAND, ÂTAPI_LINECALLSTATE_PROCEEDING,(LPARAM)(HCALL)hDevice );
                        break;
                    }

                    case LINECALLSTATE_OFFERING:    // pass it on
                    {
                        SetVarProps( (HWND)dwInstance, hDevice );
                        PostMessage( (HWND)dwInstance, WM_COMMAND, ÂTAPI_LINECALLSTATE_OFFERING, (LPARAM)(HCALL)hDevice );
                        break;
                    }

                    case LINECALLSTATE_DIALTONE:    // pass it on
                    {
                        SetVarProps( (HWND)dwInstance, hDevice );
                        PostMessage( (HWND)dwInstance, WM_COMMAND, ÂTAPI_LINECALLSTATE_DIALTONE, (LPARAM)(HCALL)hDevice );
                        break;
                    }

                    case LINECALLSTATE_DIALING:        // pas it on...
                    {
                        SetVarProps( (HWND)dwInstance, hDevice );
                        PostMessage( (HWND)dwInstance, WM_COMMAND, ÂTAPI_LINECALLSTATE_DIALING, (LPARAM)(HCALL)hDevice );
                        break;
                    }

                    case LINECALLSTATE_BUSY:        // pass it on...
                    {
                        SetVarProps( (HWND)dwInstance, hDevice );
                        PostMessage( (HWND)dwInstance, WM_COMMAND, ÂTAPI_LINECALLSTATE_BUSY, 0 );
                        break;
                    }

                    case LINECALLSTATE_DISCONNECTED:    // pass it on...
                    {
                        SetVarProps( (HWND)dwInstance, hDevice );
                        PostMessage( (HWND)dwInstance, WM_COMMAND, ÂTAPI_LINECALLSTATE_DISCONNECTED, (LPARAM)(HCALL)hDevice );
                        break;
                    }
                }
                break;
            }

In the final section of the lineCallBack routine, several other potential messages are listed, but no code is added. This is just to give you an idea of the other messages that can be received by lineCallBackProc. Listing 24.11 shows the final section of the lineCallBackProc routine.


Listing 24.11. The last section of lineCallBackProc.
        case LINE_LINEDEVSTATE:    // we'll ignore these for now...
            switch (dwParam1)
            {
                case LINEDEVSTATE_REINIT:
                    break;

                case LINEDEVSTATE_RINGING:
                    break;
            }
            break;

        case LINE_CLOSE: // the line has been closed!
            {
            break;
            }
        case LINE_REPLY:    // pass on TAPI_REPLY messages
        {
            PostMessage( (HWND)dwInstance, WM_COMMAND, TAPI_LINE_REPLY, 0 );
            break;
        }

        //
        // other messages that we'll ignore here
        //
        case LINE_REQUEST:
        case LINE_ADDRESSSTATE:
            break;
        case LINE_CALLINFO:
            break;
        case LINE_DEVSPECIFIC:
            break;
        case LINE_DEVSPECIFICFEATURE:
            break;
        case LINE_GATHERDIGITS:
            break;
        case LINE_GENERATE:
            break;
        case LINE_MONITORDIGITS:
            break;
        case LINE_MONITORMEDIA:
            break;
        case LINE_MONITORTONE:
            break;
        } /* switch */

} /* LineCallBackProc */

That concludes the code review for the TAPIOUT project. In the next section, you'll get to test the TAPIOUT project.

Testing the TAPIOut Project

If you have a C compiler that can handle the TAPIOUT code, compile it now. If you do not have a compiler, you can load the TAPIOUT.EXE from the CD-ROM that ships with the book.

When you first load TAPIOUT you'll see a simple dialog box. Enter a valid phone number in the input box and press PlaceCall. You'll see several messages appear in the status box as TAPI attempts to complete your call (see Figure 24.2).

Figure 24.2 : Running the TAPIOUT project

In a production application you would add code that responded to the LINE_CALLSTATE_CONNECTED message by notifying the user to pick up a connected handset (for voice calls), beginning a data send (for fax or data calls), or possibly starting a recorded message (for automated voice calls).

Summary

In this chapter you learned how to use the Basic Telephony API functions to write a short dialer program in C. The techniques you learned here will be used throughout all the TAPI projects in this book. Even the projects written in Visual Basic 4.0 will use the same API calls in the same order.

In the next chapter, you'll learn about the details of hardware configurations including the limitations and advantages of standard modem cards, voice-modem cards, and telephony hardware.