Introduction
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.
About TAPI
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 LineApp
, Line
, Call
, PhoneApp
and Phone
. These parts in TAPI are depicted in the picture below:
LineApp
: A LineApp
is created when lineInitializeEx
is called, and destroyed when lineShutdown
is called. A process can create multiple LineApp
s if necessary, but need at least one to access TAPI. Each LineApp
represents a distinct TAPI session.
Line
: Line
s 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 lineClose
. Line
devices are owned by the LineApp
, and multiple Line
s can be owned concurrently by a single LineApp
. Line
s 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 lineDrop
. Each Call
is identified by a Call
handle. Call
s 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
: A PhoneApp
is created when phoneInitializeEx
is called, and destroyed when phoneShutdown
is called. A process can create multiple PhoneApp
s 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 phoneClose
TAPI. 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 PhoneApp
and 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.
CTapiLine class
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 HANDLE
return).
Open
: 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:
LPLINECALLPARAMS lpCallParams;
lpCallParams = (LPLINECALLPARAMS)malloc(sizeof(LINECALLPARAMS)+1024);
memset(lpCallParams,0,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);
strcpy((LPSTR)lpCallParams+sizeof(LINECALLPARAMS), 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...
switch(WaitForSingleObject(m_hEventFromThread, INFINITE))
{
case WAIT_OBJECT_0:
if(m_dwLineMsg == LINECALLSTATE_OFFERING)
{
lRet = lineAnswer(m_hCall, NULL, 0);
lRet = (lRet>0)?0:lRet;
if(lRet)
Close();
return lRet;
}
break;
case WAIT_TIMEOUT:
return ERROR_TIMEOUT;
};
Message monitoring thread will indicate through an event when a call comes. Then this method answers by calling lineAnswer
TAPI.
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);
memset(pvarStrDevID,0,sizeof(VARSTRING)+255);
pvarStrDevID->dwTotalSize = sizeof(VARSTRING)+255;
long lRet = lineGetID(m_hLine,0,m_hCall,
LINECALLSELECT_LINE,pvarStrDevID,"comm/datamodem");
if(lRet)
{
*lError = lRet;
return NULL;
}
*lError = 0;
return *((LPHANDLE)((char *)pvarStrDevID + pvarStrDevID->dwStringOffset));
lineGetID
returns 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 "comm/datamodem
".
Close
: Close
closes the entire operation. Inside, it closes the Line
and shuts down the LineApp
. Also terminates the message monitoring thread.
Class Usage
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.
Tool Demo
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 waveInStart
and waveOutWrite
to record and playback.
Conclusion
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.