Microsoft TAPI (Telephony API) in Windows provides telephony functionality for the application, it is a bit confusing for programmers. I have many friends asking me the same question how to connect and communicate through modem. Finally, I came up with this sample tool to do the coaching. I tried to keep this tool source bare minimum to get an easy hold of TAPI programming even though I got to do some juggling. Here, I have used TAPI 2.0. TAPI 3.0 and above is much more simplified and easy to use.
Microsoft TAPI supports a wide variety of devices operating on voice grade lines, ISDN lines, and private branch exchanges. It provides services for placing outgoing calls, accepting incoming calls and managing calls, devices, etc. It simplifies the complexity to program telephony applications. You can ignore the need to know about modem initialization string, AT command and all other strenuous stuffs. TAPI is an abstraction layer built on top of TSPs (Telephony Service Providers). TSPs are the components that provide hardware or service-specific functionality. TSPs are specific to hardware. When an application requests that a telephony device perform an action, TAPI figures out which TSP services that device and makes a call to it. Then TSP will do the rest. Writing TSPs are the next level in telephony development. Rest of this article, leaving the TSPs, I’m explaining only TAPI programming.
TAPI is classified into five parts. Those are
Phone. These parts in TAPI are depicted in the picture below:
LineApp is created when
lineInitializeEx is called, and destroyed when
lineShutdown is called. A process can create multiple
LineApps if necessary, but need at least one to access TAPI. Each
LineApp represents a distinct TAPI session.
Lines are created by an application's call to
lineOpen and are represented by their
Line handles. In the end, these
Line handles are closed by
Line devices are owned by the
LineApp, and multiple
Lines can be owned concurrently by a single
Lines are most commonly used for call control purposes.
Call: Application creates a
call by making a call using
lineMakeCall or answering an incoming call using
lineAnswer. An application can destroy the
call by calling
Call is identified by a
Calls are created on a
Line, and so are always associated with
Line. Though a
Call is always owned by a
Line, the relationship is not necessarily one-to-one. A single
Line can own more than one
call. Call waiting and conferencing are other examples.
PhoneApp is created when
phoneInitializeEx is called, and destroyed when
phoneShutdown is called. A process can create multiple
PhoneApps if necessary but need at least one to access
phone specific TAPIs. Each
PhoenApp represents a distinct TAPI session.
Phone: Phone is created by calling
phoneOpen TAPI and destroyed by calling
Phone is a logical representation of the terminal equipment. As in the physical telephony world,
Phone can be used without calls. For example, you can use a telephone as an interface to a voice mail, SMS, etc.
In this example, totally excluding the
Phone sections, I concentrated on the first three connection-oriented parts. Take a look at the
CTapiLine class; I feel it is comprehensive enough to teach call making and answering procedures.
This class is not a complete wrapper for TAPI as I said earlier. It posses a minimal set to make or answer a call. All the methods in this class return zero for success and non-zero for failure (other than the
Open method is the initial method to call. Inside this, it initializes the
LineApp and opens the
Line. It can open the
line in any one of the two modes. First mode opens in the data/fax mode and the other opens in voice mode. To make your PC as an answering machine, you should open in the second mode. The first mode only connects or answers a data call. On successful open, it starts a thread to monitor the line message. Successful connection is notified through one of these messages.
MakeOutgoingCall: This method takes number string as the parameter and dials. It doesn't retrieve or prompt any other sophisticated dialing details. This method should be modified if it needs to dial country code, area code, etc. The simple code in this method is as follows:
lpCallParams = (LPLINECALLPARAMS)malloc(sizeof(LINECALLPARAMS)+1024);
lpCallParams->dwTotalSize = sizeof(LINECALLPARAMS)+1024;
lpCallParams->dwBearerMode = LINEBEARERMODE_VOICE;
lpCallParams->dwMediaMode = LINEMEDIAMODE_DATAMODEM;
lpCallParams->dwCallParamFlags = LINECALLPARAMFLAGS_IDLE;
lpCallParams->dwAddressMode = LINEADDRESSMODE_ADDRESSID;
lpCallParams->dwAddressID = 0;
lpCallParams->dwDisplayableAddressOffset = sizeof(LINECALLPARAMS);
lpCallParams->dwDisplayableAddressSize = strlen(szAddress);
lRet = lineMakeCall(m_hLine, &m_hCall, szAddress, 0, lpCallParams);
GetIncomingCall: This is a blocking method. Until a call comes, it blocks. After a call, it tries to answer and if it is successful, it returns zero. Success does not mean connected. The code, which answers, is...
if(m_dwLineMsg == LINECALLSTATE_OFFERING)
lRet = lineAnswer(m_hCall, NULL, 0);
lRet = (lRet>0)?0:lRet;
Message monitoring thread will indicate through an event when a call comes. Then this method answers by calling
GetHandle: This returns the
HANDLE of the requested class only after successful connection. This handle is very similar to what you would get by opening the COM port using the
CreateFile API. By default, the handle is opened for overlapped I/O operation. After use, retrieved process should close this handle. Check the code below, it retrieves the
HANDLE for serial communication:
VARSTRING *pvarStrDevID = (VARSTRING *)malloc(sizeof(VARSTRING)+255);
pvarStrDevID->dwTotalSize = sizeof(VARSTRING)+255;
long lRet = lineGetID(m_hLine,0,m_hCall,
*lError = lRet;
*lError = 0;
return *((LPHANDLE)((char *)pvarStrDevID + pvarStrDevID->dwStringOffset));
VARSTRING, which is a variable structure. On successful return, end of this structure will contain 4 byte
HANDLE corresponding to the class requested. Next to this
HANDLE, a null terminated modem string name (from the driver) will appear, which can be ignored. Here in this tool, specified hard-coded class name is "
Close closes the entire operation. Inside, it closes the
Line and shuts down the
LineApp. Also terminates the message monitoring thread.
The following picture shows the typical TAPI use, using
CTAPILine class methods.
One of the complications in TAPI programming is, most of the TAPIs use variable structure. I.e., when you code for successful operation, you should always check for
LINEERR_STRUCTURETOOSMALL error code from the TAPI and reallocate the structure constantly to fit. To simplify, I haven't coded this class in that way. Here, I scrupulously allocated all of the variable structure with huge memory, it prevents the failure in ordinary execution.
TAPISample is the perfect sample tool which uses the
CTapiLine class. TAPISample tool's picture on top was captured when making a dialup connection to server in Internet. Those who have dialup Internet access can replicate this. To start, click 'Open' and type the number to dial. There is no feature in this tool to indicate the connected status. You can find the connected sign by the modem sound. Once connected, modem stops squeaking. Now get the handle by clicking "Get Handle" and start the reading by clicking "Read". Now you can see some bytes coming on "Data from stream" edit box. When you click "Write", the text you typed on "Data to write" will be sent.
Here in this tool, serial communication handle is retrieved. It is Overlapped serial communication handle. This is used on the "Serial communication" section on the dialog, same like a handle created by the
CreateFile with Overlapped flag.
More on TAPI
Here, I have demonstrated only serial communication using TAPI. You can use this to record and playback Wave files on the phone line only if you have voice capable modem. Then you can use your PC as an answering machine.
To record or playback first, you must open with
LINEMEDIAMODE_AUTOMATEDVOICE media mode (first mode in
Open) and with
LINECALLPRIVILEGE_OWNER privilege. Then request the handles for "wave/in" and "wave/out" and use these handles in Wave API
waveOutWrite to record and playback.
I have performed very little test on this class and the tool, since I don't have the luxury of resources like extra PCs with modem, ISDN modem, data/fax/voice modem, etc. So I guarantee you this class has some serious bugs lurking. If you find any such thing, please update me. Please appreciate if you find this useful and don't curse me if you find it difficult. Definitely, this is a bit obsolete tutorial after TAPI 3.0 and later release. If you are a beginner, I bet it will provide you a good start. Thanks.