MobileManaging Calls and Transferring Data with TAPI

Managing Calls and Transferring Data with TAPI

TAPI Devices Enumeration

In the previous article, “Understanding Windows CE Telephone API (TAPI): Introduction,” you learned about the simplest TAPI functions. Based, on them, you are now ready to conduct an enumeration of available TAPI devices. For this purpose, you will use the lineGetDevCaps function. As you saw earlier, you have to initialize the line first. Then, after TAPI version negotiation, lineGetDevCaps can be called to obtain the required data. Below is is a tiny code snippet to illustrate the schema:

#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);

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

All lineGetDevCaps parameters are intuitively understandable, but the last one, of the LPLINEDEVCAPS type, requires some explanation. Just to feel what is it, please refer to its definition below:

typedef struct linedevcaps_tag {
DWORD dwTotalSize;
DWORD dwNeededSize;
DWORD dwUsedSize;

DWORD dwProviderInfoSize;
DWORD dwProviderInfoOffset;

DWORD dwSwitchInfoSize;
DWORD dwSwitchInfoOffset;

DWORD dwPermanentLineID;
DWORD dwLineNameSize;
DWORD dwLineNameOffset;
DWORD dwStringFormat;
DWORD dwAddressModes;
DWORD dwNumAddresses;
DWORD dwBearerModes;
DWORD dwMaxRate;
DWORD dwMediaModes;

DWORD dwGenerateToneModes;
DWORD dwGenerateToneMaxNumFreq;
DWORD dwGenerateDigitModes;
DWORD dwMonitorToneMaxNumFreq;
DWORD dwMonitorToneMaxNumEntries;
DWORD dwMonitorDigitModes;
DWORD dwGatherDigitsMinTimeout;
DWORD dwGatherDigitsMaxTimeout;

DWORD dwMedCtlDigitMaxListSize;
DWORD dwMedCtlMediaMaxListSize;
DWORD dwMedCtlToneMaxListSize;
DWORD dwMedCtlCallStateMaxListSize;

DWORD dwDevCapFlags;
DWORD dwMaxNumActiveCalls;
DWORD dwAnswerMode;
DWORD dwRingModes;
DWORD dwLineStates;

DWORD dwUUIAcceptSize;
DWORD dwUUIAnswerSize;
DWORD dwUUIMakeCallSize;
DWORD dwUUIDropSize;
DWORD dwUUISendUserUserInfoSize;
DWORD dwUUICallInfoSize;

LINEDIALPARAMS MinDialParams;
LINEDIALPARAMS MaxDialParams;
LINEDIALPARAMS DefaultDialParams;

DWORD dwNumTerminals;
DWORD dwTerminalCapsSize;
DWORD dwTerminalCapsOffset;
DWORD dwTerminalTextEntrySize;
DWORD dwTerminalTextSize;
DWORD dwTerminalTextOffset;

DWORD dwDevSpecificSize;
DWORD dwDevSpecificOffset

DWORD dwLineFeatures;

DWORD dwSettableDevStatus;
DWORD dwDeviceClassesSize;
DWORD dwDeviceClassesOffset;
} LINEDEVCAPS, *LPLINEDEVCAPS;

The LINEDEVCAPS struct is mostly built from Size/Offset pairs. Actually, it follows the common Windows phylosophy for returning this kind of variable-length data. The reason I note it here is pretty simple. Your application is responsible for providing TAPI with the correct memory chuck. Then, you obviously will want to use returned information. Hence, you start to dig in into the code.

The first thing to be said is that this process is cyclic. This means that you should allocate a minimal memory chunk. lineGetDevCaps will return LINEERR_STRUCTURETOOSMALL in the case when the supplied buffer is too small. In this case, lpLineDevCaps->dwNeededSize will contain the required buffer size. All you need to do is to reallocate your result buffer and call lineGetDevCaps once again. The next sample shows how all this theory can be implemented:

LPLINEDEVCAPS lpLineDevCaps = NULL;
DWORD dwSize = 0;
BOOL bOK = TRUE;
LPTSTR lpszDevName = NULL;
for (DWORD dwDeviceID = 0; dwDeviceID < dwNumDevs; dwDeviceID ++)
{
   lReturn = ::lineNegotiateAPIVersion(g_hLineApp, dwDeviceID,
                                       EARLY_TAPI_VERSION,
                                       TAPI_CURRENT_VERSION,
                                       &dwAPIVersion, &lineExtID);

   bOK = TRUE;
   dwSize = sizeof(LINEDEVCAPS);
   do
   {
      lpLineDevCaps = (LPLINEDEVCAPS) new BYTE[dwSize];
      lpLineDevCaps->dwTotalSize = dwSize;
      lReturn = ::lineGetDevCaps(g_hLineApp, dwDeviceID, dwAPIVersion,
                                 0, lpLineDevCaps);
      if ( 0 != lReturn && LINEERR_STRUCTURETOOSMALL != lReturn )
      {
         TRACE(L"Error: %Xn",lReturn);
         delete lpLineDevCaps;
         lpLineDevCaps = NULL;

         bOK = FALSE;
         break;
      }

      if ( lpLineDevCaps->dwNeededSize <=
           lpLineDevCaps->dwTotalSize )
      {
         break;
      }

      TRACE(L"Reallocating to new size = %lun",lpLineDevCaps->
            dwNeededSize);

      dwSize = lpLineDevCaps->dwNeededSize;
      delete lpLineDevCaps;
      lpLineDevCaps = NULL;
   }
   while (1);

   if ( bOK )
   {
      lpszDevName = (LPTSTR)((LPBYTE)lpLineDevCaps + lpLineDevCaps->
                             dwLineNameOffset);
      m_DevList.AddString(lpszDevName);
      delete lpLineDevCaps;
      lpLineDevCaps = NULL;
   }
}

This code runs through all available TAPI devices and tries to obtain a device name for each one. As you have seen before, most of the data is addressed by offsets in the lpLineDevCaps structure. The LINEDEVCAPS struct will guide you to which data is available. Here, I will only note a few of the struct members. DeviceClass info (pointed to by dwDeviceClassesSize/dwDeviceClassesOffset members) will be used later to make calls over TAPI devices. dwBearerModes describes the call types this device can make. dwMediaModes indicates the supported transmission modes. dwMaxRate is maximum transmission rate (in Bps). You may be required to fetch some other info.

Common Session Scenario

To create some basis for the next few sections, I’ll place a common flow of the usual TAPI session here:

  • lineOpen is used to open a device line
  • lineTranslateAddress is used to transtalate the phone number to canonical form
  • lineMakeCall is doing the actual job for you, communicating to your application via provided callback function
  • Finally, lineDrop, lineDeallocateCall, and lineClose will be called to make all required cleanup

In the following few sections, you will learn about all these steps in more detail.

Opening the Line

Prior to proceeding with any operation over TAPI, you have to open a line device. The following TAPI call suits this purpose:

LONG lineOpen(
   HLINEAPP hLineApp,
   DWORD dwDeviceID,
   LPHLINE lphLine,
   DWORD dwAPIVersion,
   DWORD dwExtVersion,
   DWORD dwCallbackInstance,
   DWORD dwPrivileges,
   DWORD dwMediaModes,
   LPLINECALLPARAMS const lpCallParams
);

Most of the parameters are already familiar to you. Let me drop a few words regarding the new ones.

lphLine is a handle of the opened line through which you’re going to make a call. dwPrivileges defines which privileges your application is interested in obtaining. Possible values are LINECALLPRIVILEGE_NONE, LINECALLPRIVILEGE_MONITOR, and LINECALLPRIVILEGE_OWNER. If you do not need any stuff on incoming calls, simply put LINECALLPRIVILEGE_NONE there. In case you really want to deal with incoming/outgoing calls, specify the appropriate privilege level. The dwMediaModes parameter is used only if LINECALLPRIVILEGE_OWNER is set. It declares which media modes are of interest for your application. For a complete list of available modes, please refer to SDK documentation.

Finally, you can pass call parameters via the lpCallParams structure. It had to be set to NULL for earlier versions of Windows CE (up to 3.0). This is the same parameter you will pass to the lineMakeCall function. Depending on when you will specify call parameters, they partially can be ignored during lineMakeCall. Usually, you will set this parameter to NULL.

Translating a Phone Mumber

A common practice is that phone numbers are stored in canonical format, which includes international the dialing code, area code, and so forth. An actual call may use a modified form of this number depending on current location, local or long distance call, and so forth. Therefore, the initial phone number should be translated into a dialable form. To proceed with this task, you need to call lineTranslateAddress. Similar to lineGetDevCaps, you might be required to call this function several times until a correctly sized memory block is passed to keep its output. The following code conducts the required translation:

LPLINETRANSLATEOUTPUT lpLineTranslateOutput = NULL;
DWORD dwSize = 0;
BOOL bOK = TRUE;
LPTSTR lpszTranslatedNumber = NULL;

bOK = TRUE;
dwSize = sizeof(LINETRANSLATEOUTPUT);
do
{
   lpLineTranslateOutput = (LPLINETRANSLATEOUTPUT) new BYTE[dwSize];
   lpLineTranslateOutput->dwTotalSize = dwSize;
   lReturn = ::lineTranslateAddress(g_hLineApp, dwDeviceID, dwAPIVersion,
                                    lpszPhone, 0, 0,
                                    lpLineTranslateOutput);
   if ( 0 != lReturn && LINEERR_STRUCTURETOOSMALL != lReturn )
   {
      TRACE(L"Error: %Xn",lReturn);
      delete lpLineTranslateOutput;
      lpLineTranslateOutput = NULL;

      bOK = FALSE;
      break;
   }

   if ( lpLineTranslateOutput->dwNeededSize <=
        lpLineTranslateOutput->dwTotalSize )
   {
      break;
   }

      RACE(L"Reallocating to new size = %lun",lpLineDevCaps->
           dwNeededSize);

   dwSize = llpLineTranslateOutput->dwNeededSize;
   delete lpLineTranslateOutput;
   lpLineTranslateOutput = NULL;
}
while (1);

if ( bOK )
{
   lpszTranslatedNumber = (LPTSTR)((LPBYTE)lpLineTranslateOutput
                        + lpLineTranslateOutput->
                          dwDialableStringOffset);
   ...
   delete lpLineTranslateOutput;
   lpLineTranslateOutput = NULL;
}

Making a Call

After the line is opened and the phone number is translated to a dialable form, you are ready to make a call. This is a relatively easy task, after all that things you have passed already. To place a call on the targeted line, call lineMakeCall:

LONG lineMakeCall(
   HLINE hLine,
   LPHCALL lphCall,
   LPCWSTR lpszDestAddress,
   DWORD dwCountryCode,
   LPLINECALLPARAMS const lpCallParams
);

If you need to set up additional parameters rather than the default ones, you can pass them via lpCallParams.

lineMakeCall operates asynchronously; in other words, it will return control before dialing is completed. The application will receive notifications on different stages of this process via supported a callback function. Once again, you can take a look at the CeDialer sample in the SDK for more details.

Sending and Receiving Data over Opened Lines

Once a call has been made successfully, your application can communicate over it. I will discuss the simplest case, when the application obtains a communication port handle from TAPI, so it can transfer data using regular ReadFile or WriteFile APIs. In general, the actual API to be used depends on the selected device class. It can be WAV, COM, MIDI, and so forth.

So, to perform the first step you should call the lineGetID function. It returns the required info for given the media format for the open line, line addressm or call handle. Note that you can obtain such info for an open line, before you make an actual call. Keeping this in mind, take a look at how to implement it:

DWORD dwSize = sizeof(VARSTRING) + 2048;
DWORD dwRet = 0;

do
{
   LPVARSTRING lpVarString = (LPVARSTRING) new BYTE[dwSize];
   lpVarString->dwTotalSize = dwSize;

   dwRet = ::lineGetID(g_hLine, 0, 0, LINECALLSELECT_LINE, lpVarString,
                       lpszDeviceClass);
   if ( dwRet == 0 )
   {
      LPHANDLE lpHandle = (LPHANDLE)((LPBYTE)lpVarString +
                                     lpVarString->dwStringOffset);
      HANDLE hCommPort = *lpHandle;
      LPTSTR lpCommPortName = (LPTSTR)((LPBYTE)lpVarString +
                                       lpVarString->dwStringOffset +
                                       sizeof(HANDLE));
      break;
   }
   else if ( dwRet == LINEERR_STRUCTURETOOSMALL )
   {
      dwSize = lpVarString->dwNeededSize;
      delete lpVarString;
      lpVarString = NULL;
      continue;
   }
   else
   {
      // handle errors
      break;
   }
}
while (1);

VARSTRING allows you to pass a variable amount of data needed for the given device type. All operations on it are similar to the previously discussed structures.

Once you have a handle, you can use it with ReadFile/WriteFile functions. It is quite a trivial thing, so I will omit it here. Only notice that you probably won’t need to call the CloseHandle function because it will terminate the call.

Closing the Line

Finally, after all requested operations are performed on the line device, you should close it and release all allocated resources. You also should clean up the resources in response to errors that occurred. If you have made a call, you should use the following two APIs:

LONG lineDrop( HCALL hCall, LPCTSTR lpsUserUserInfo, DWORD dwSize );
LONG lineDeallocateCall( HCALL hCall );

to terminate the specified call and release all related resources. You can specify in the lpsUserUserInfo parameter that some data will be sent to a remote target as a part of the disconnection process. Next, lineClose will close the opened line. lineShutdown finishes the game. Bingo!

Conclusion

You have seen several basic TAPI functions that allow you to perform various communications tasks over TAPI. And still, they are a small part of the whole TAPI. You will see some more advanced features in the next article.

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.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories