February 20, 2019
Hot Topics:

Managing Calls and Transferring Data with TAPI

  • April 29, 2005
  • By Alex Gusev
  • Send Email »
  • More Articles »

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:

DWORD dwSize = 0;
LPTSTR lpszTranslatedNumber = NULL;

   lpLineTranslateOutput = (LPLINETRANSLATEOUTPUT) new BYTE[dwSize];
   lpLineTranslateOutput->dwTotalSize = dwSize;
   lReturn = ::lineTranslateAddress(g_hLineApp, dwDeviceID, dwAPIVersion,
                                    lpszPhone, 0, 0,
   if ( 0 != lReturn && LINEERR_STRUCTURETOOSMALL != lReturn )
      TRACE(L"Error: %X\n",lReturn);
      delete lpLineTranslateOutput;
      lpLineTranslateOutput = NULL;

      bOK = FALSE;

   if ( lpLineTranslateOutput->dwNeededSize <=
        lpLineTranslateOutput->dwTotalSize )

      RACE(L"Reallocating to new size = %lu\n",lpLineDevCaps->

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

if ( bOK )
   lpszTranslatedNumber = (LPTSTR)((LPBYTE)lpLineTranslateOutput
                        + lpLineTranslateOutput->
   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;

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

   dwRet = ::lineGetID(g_hLine, 0, 0, LINECALLSELECT_LINE, lpVarString,
   if ( dwRet == 0 )
      LPHANDLE lpHandle = (LPHANDLE)((LPBYTE)lpVarString +
      HANDLE hCommPort = *lpHandle;
      LPTSTR lpCommPortName = (LPTSTR)((LPBYTE)lpVarString +
                                       lpVarString->dwStringOffset +
      dwSize = lpVarString->dwNeededSize;
      delete lpVarString;
      lpVarString = NULL;
      // handle errors
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!


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.

Page 2 of 2

Comment and Contribute


(Maximum characters: 1200). You have characters left.



Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Thanks for your registration, follow us on our social networks to keep up-to-date