Understanding Windows CE Telephone API (TAPI): Introduction

TAPI Overview

Telephone API gives to programmer a control over manipulating calls over various telephonic hardware. It can be an intertnal modem, GSM, and so forth. TAPI provides a powerful mechanism for operating on those devices and hides all low-level details from the application developer. You no longer need to work with AT commands. TAPI will translate all required items calling to the telephone service and provides through a Telephone Service Provider Interface (TSPI). Hence, if your target device was equipped with some telephone hardware, it definitely has all necessary drivers installed. Thus, you are perfectly set up to implement your specific task using TAPI.

Dealing with TAPI, you will face two major types of devices:

  • line devices
  • phone devices

A notion of a Line device is used to represent a device-independent abstraction of phone line. It can contain one or more communication channels. In turn, Phone device is a representation of a phone handset; in other words, microphone, volume controls, and so forth. Under Windows CE, in most of the cases, Line and Phone devices are associated one-to-one. Nevertheless, it is possible that one Line device supports several Phone devices; for example, Integrated Services Digital Network (IDSN).

Having said these general words, I'll move forward and start exploring TAPI capabilities. Actually, you can use TAPI not only for making calls but also for retrieveing various types of information about TAPI devices. This data may be useful in other areas, such as RAS or Connection Manager API calls and so forth.

Line Initialization and TAPI Version Negotiating

I will start your tour of TAPI with very basical stuff. No matter what you will do afterwards, the first thing you must carry out with TAPI is line initialization:

LONG WINAPI lineInitialize(
     LPHLINEAPP          lphLineApp,
     HINSTANCE           hInstance,
     LINECALLBACK        lpfnCallback,
     LPCWSTR             lpszAppName,
     LPDWORD             lpdwNumDevs
    );

This function takes three incoming parameters: application or DLL instance handle, callback function to return asynchronous notifications, and application name to indicate who is calling TAPI. The outgoing lphLineApp handle will be used throughout the rest of the TAPI functions calls. Please note that, with all its simplicity, lineInitialize gives you important information on exit: how many TAPI devices are available for application. All this is illustarted below:

VOID PASCAL LineCallbackFunc(
  DWORD hDevice,
  DWORD dwMsg,
  DWORD dwCallbackInstance,
  DWORD dwParam1,
  DWORD dwParam2,
  DWORD dwParam3)
{
}


HLINEAPP g_hLineApp;
...
DWORD dwNumDevices = 0;
DWORD dwRet = ::lineInitialize(&g_hLineApp,g_hInst,
                               (LINECALLBACK)LineCallbackFunc,
                               _T("Developer.com Test"),
                               &dwNumDevices);

if ( dwRet != 0 )
{
// Report error
}

For now, leave LineCallbackFunc untouched; you'll see it a bit later in this tutorial.

The second TAPI call you will usually execute is lineNegitiateAPIVersion:

LONG lineNegotiateAPIVersion(
     HLINEAPP hLineApp,
     DWORD dwDeviceID,
     DWORD dwAPILowVersion,
     DWORD dwAPIHighVersion,
     LPDWORD lpdwAPIVersion,
     LPLINEEXTENSIONID lpExtensionID
);

Version negotiation is used to reach some kind of agreement between all communication parties: TAPI, the service provider, and your application. You can call it for any device in range from 0 to dwNumDevices - 1. This function takes the low and high boundaries of the TAPI version required by the application and at exit returns its negotiated value.

The last parameter is a pointer to a structure of type LINEEXTENSIONID. For Windows CE 3.0, you have had to set this parameter to NULL. Latest OS versions allow a service provider supplying provider-specific extensions for the specified dwDeviceID parameter. In such a case, upon a successful negotiation, lpExtensionID will be filled with the extension identifier of these extensions. Otherwise, you will get zeros here. Your application may freely ignore this structure if it does not need any extensions. Hence, the typical code you will write for TAPI version negotiation may look like the following:

#define EARLY_TAPI_VERSION 0x00010003    // Early TAPI version

DWORD dwNumDevs    = 0;
LONG lReturn       = 0;
DWORD dwAPIVersion = 0;
LINEEXTENSIONID lineExtID;

// Get number of available TAPI devices
lReturn = ::lineInitialize(&g_hLineApp,
                           AfxGetInstanceHandle(),
                           lineCallbackFunc,
                           _T("Developer.com Test"),
                           &dwNumDevs);

for (DWORD dwDeviceID = 0; dwDeviceID < dwNumDevs; dwDeviceID ++)
{
   lReturn = ::lineNegotiateAPIVersion(g_hLineApp, dwDeviceID,
            EARLY_TAPI_VERSION, TAPI_CURRENT_VERSION,
            &dwAPIVersion, &lineExtID);
   ...........
}

You eventually can carry out above two tasks in one shot, calling

LONG WINAPI lineInitializeEx(
     LPHLINEAPP lphLineApp,
     HINSTANCE hInstance,
     LINECALLBACK lpfnCallback,
     LPCWSTR lpszFriendlyAppName,
     LPDWORD lpdwNumDevs,
     LPDWORD lpdwAPIVersion,
     LPLINEINITIALIZEEXPARAMS lpLineInitializeExParams
     );

This function has minor differences in paramaters from the pair lineInialize/lineNegitiateAPIVersion, but finally results in the same outcome. What is important to notice regarding lineInitializeEx is that you can select one of two mechanisms by which TAPI will notify the application of telephony events: Hidden Window or Event Handle.

Understanding Windows CE Telephone API (TAPI): Introduction

The Hidden Window mechanism is selected by one of two ways:

  • when you call lineInialize
  • by specifying lpLineInitializeExParams->dwOptions = LINEINITIALIZEEXOPTION_USEHIDDENWINDOW

As a result, TAPI creates a hiden window that receives messages from TAPI when some events occur. Subclassing this window, TAPI finally calls lineCallbackFunc.

The Event Handle mechanism is selected when you set lpLineInitializeExParams->dwOptions = LINEINITIALIZEEXOPTION_USEEVENT. In this case, TAPI creates an event object on behalf of the application, and returns a handle to the object in the lpLineInitializeExParams->hEvent. Your application mustn't try to call any functions to change the state of this event (as in SetEvent, ResetEvent, or CloseHandle). Otherwise, the result of TAPI operations will be unpredicted. All your application can do is to wait on this event using functions such as WaitForSingleObject or MsgWaitForMultipleObjects. TAPI will fire this event every time some telephony event notification is pending for the application. After the event is signalled, the application should call lineGetMessage to obtain the message contents. When lineShutdown is called, the event handle is closed and the event object destroyed by TAPI. You can choose between waiting on created event handle or simply calling lineGetMessage and having it blocked until a message will be queued. Just decide which method is more suitable for your needs.

Implementing the Line Callback Function

As you have seen above, the Line Callback function is used by TAPI to inform your application about various events that happen during the TAPI session. First of all, the good news is that you can leave the callback function empty in case you do not intend to use any TAPI handles during session lifetime. The simplest example of it is device enumeration, when you want to obtain some information about available TAPI devices only.

The sample implementation of a callback function will be as simple as possible. Again, place its definition here:

VOID PASCAL LineCallbackFunc(
     DWORD hDevice,
     DWORD dwMsg,
     DWORD dwCallbackInstance,
     DWORD dwParam1,
     DWORD dwParam2,
     DWORD dwParam3)
{
...
}

The first parameter is a handle either of a line device or a call on which TAPI is informing your application. More interesting parameters are dwMsg and dwParamN. They describe fired message and its specific data. You can find a complete list of TAPI messages in the SDK documentation with a detailed description of each message parameter. To avoid odd duplication of existing documentation, I would simply like to redirect you to a well-commented SDK sample called CEDialer. It contains a sample implementation of lineCallbackFunc with gorgeous explanations of many TAPI messages. Below is a short excerpt from this sample:

VOID CALLBACK lineCallbackFunc(
     DWORD hDevice,
     DWORD dwMsg,
     DWORD dwCallbackInstance,
     DWORD dwParam1,
     DWORD dwParam2,
     DWORD dwParam3)
{
...
      switch (dwMsg)
      {
         case LINE_CALLSTATE:    // Sent after change of call state

         // dwParam1 is the specific CALLSTATE change that is occurring.
         switch (dwParam1)
         {
         case LINECALLSTATE_DIALTONE:
            lpszStatus = TEXT("Dial tone");
            break;

         case LINECALLSTATE_DIALING:
            lpszStatus = TEXT("Dialing...");
            break;

         case LINECALLSTATE_PROCEEDING:
            lpszStatus = TEXT("Dialing completed, call proceeding.");
            break;

         case LINECALLSTATE_RINGBACK:
            lpszStatus = TEXT("Ring back");
            break;

         case LINECALLSTATE_CONNECTED:
            lpszStatus = TEXT("Connected");
            break;

         case LINECALLSTATE_BUSY:
            lpszStatus = TEXT("Line busy, shutting down.");
            bCloseLine = TRUE;
            break;

         case LINECALLSTATE_IDLE:
            lpszStatus = TEXT("Line is idle");
            break;

         case LINECALLSTATE_SPECIALINFO:
            lpszStatus =TEXT("Special Information, couldn't dial number");
            bCloseLine = TRUE;
            break;

         case LINECALLSTATE_DISCONNECTED:
            {
               LPTSTR lpszDisconnected;
               lpszDisconnected = TEXT(" ");

               switch (dwParam2)
               {
               case LINEDISCONNECTMODE_NORMAL:
                  lpszDisconnected = TEXT("Remote party disconnected");
                  break;

               case LINEDISCONNECTMODE_UNKNOWN:
                  lpszDisconnected = TEXT("Disconnected: Unknown reason");
                  break;

               case LINEDISCONNECTMODE_REJECT:
                  lpszDisconnected = TEXT("Remote Party rejected call");
                  break;

               case LINEDISCONNECTMODE_PICKUP:
                  lpszDisconnected =
                     TEXT("Disconnected: Local phone picked up");
                  break;

               case LINEDISCONNECTMODE_FORWARDED:
                  lpszDisconnected = TEXT("Disconnected: Forwarded");
                  break;

               case LINEDISCONNECTMODE_BUSY:
                  lpszDisconnected = TEXT("Disconnected: Busy");
                  break;

               case LINEDISCONNECTMODE_NOANSWER:
                  lpszDisconnected = TEXT("Disconnected: No Answer");
                  break;

               case LINEDISCONNECTMODE_BADADDRESS:
                  lpszDisconnected = TEXT("Disconnected: Bad address");
                  break;

               case LINEDISCONNECTMODE_UNREACHABLE:
                  lpszDisconnected = TEXT("Disconnected: Unreachable");
                  break;

               case LINEDISCONNECTMODE_CONGESTION:
                  lpszDisconnected = TEXT("Disconnected: Congestion");
                  break;

               case LINEDISCONNECTMODE_INCOMPATIBLE:
                  lpszDisconnected = TEXT("Disconnected: Incompatible");
                  break;

               case LINEDISCONNECTMODE_UNAVAIL:
                  lpszDisconnected = TEXT("Disconnected: Unavailable");
                  break;

               case LINEDISCONNECTMODE_NODIALTONE:
                  lpszDisconnected = TEXT("Disconnected: No dial tone");
                  break;

               default:
                  lpszDisconnected = TEXT("Disconnected: Unknown reason");
                  break;
               }    // end switch (dwParam2)

               bCloseLine = TRUE;
               wcscpy(lpszStatus,lpszDisconnected);
               break;
            }       // end case LINECALLSTATE_DISCONNECTED:
         }          // end switch (dwParam1)

         if (g_hwndDial)
            SendDlgItemMessage(g_hwndDial, IDC_STATUSMESSAGE, WM_SETTEXT,
                               0,(LPARAM)lpszStatus);

         if (bCloseLine)
         {
            CurrentLineClose ();
            if (g_hwndDial)
               SendMessage (g_hwndDial, WM_COMMAND, MAKEWPARAM(IDOK,0), 0);
         }
         break;
         ....
      }

As you can see, the callback gets a lot of info on each change in TAPI-related stuff. Your applications should just respond properly.

Line Shutdown

At the very moment you do not need TAPI anymore, you should release unnecessary resources. You will learn about most of them later, but here I'll put in the simplest one. You have to shut down the opened line by the following call:

lReturn = ::lineShutdown(m_hLineApp);
As you see, this is the easiest stuff.

Where to go

In this tutorial, you have started to explore a wide area of TAPI programming. You will continue on this way in the few next articles. Neverthless, know that you can use the discussed features to obtain useful information that TAPI easily provides you.

About the Author

Alex Gusev started to play with mainframes at the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers' lives in the mobile jungles a little bit simpler.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Live Event Date: October 23, 2014 @ 12:00 p.m. ET / 9:00 a.m. PT Despite the current "virtualize everything" mentality, there are advantages to utilizing physical hardware for certain tasks. This is especially true for backups. In many cases, it is clearly in an organization's best interest to make use of physical, purpose-built backup appliances rather than relying on virtual backup software (VBA - Virtual Backup Appliances). Join us for this eSeminar to learn why physical appliances are preferable to …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds