The Microsoft Telephony API, or TAPI, provides telephony-related services for Win32 applications.
TAPI is currently supported on the Windows 3.1 and Windows 95 platforms. In this chapter, we discuss TAPI 1.4, the version that is provided with Windows 95. TAPI is used extensively by those Windows 95 applications that use a modem; for example, the FAX driver and the CompuServe driver in Microsoft Exchange, the Microsoft Network software, or the Windows 95 Phone Dialer and Hyperterminal applications.
First, an important note. When most of us hear the term telephony in the context of computer communications, we think of data or FAX modems and voice grade telephone linesand very little else. TAPI goes far beyond these simple concepts and provides a consistent programming interface for a variety of devices operating on voice grade lines, ISDN lines, and private branch exchanges. The devices include modems, FAX modems, voice capable modems, computer-controlled telephone sets, and more.
TAPI provides services for placing outgoing calls, accepting incoming calls, and managing calls and devices. What TAPI does not do is handle the media stream; that is, the data that is exchanged during a call. For example, when TAPI is used to place a voice call, it is not TAPI but you, the human operator, who talks; similarly, when TAPI is used to place a data call, it is the communication application that takes over the device and performs I/O operations using standard Win32 file functions.
Before we delve deeper into the TAPI architecture, take a look at the simple program in Listing 42.1. This is about as simple as a TAPI application can get. This program takes a single command-line argument, a telephone number, and dials that number for a voice call.
#include <windows.h> #include <stdio.h> #include <tapi.h> void main(int argc, char *argv[]) { if (argc != 2) printf("Usage: %s telephone-number\n", argv[0]); else { printf("Dialing %s...", argv[1]); tapiRequestMakeCall(argv[1], NULL, NULL, NULL); } }
The actual dialing is performed on behalf of the application by the default call control application. An example for a call control application is the Phone Dialer that is provided as part of Windows 95.
The tapiRequestMakeCall function that is used in this program is ideally suited for use in scripts. For example, a call to this function can be included as an external DLL call in Visual Basic for Applications.
To compile this application from the command line, type cl dial.c tapi32.lib.
The tapiRequestMakeCall function is part of TAPI's Assisted Telephony features. In the current version of TAPI, there is only one other Assisted Telephony function: the tapiGetLocationInfo function can be used to obtain the country code and city code for the user's current location.
TAPI provides a series of personal telephony services. Telephony, in this context, refers to technology in general that connects computers with the telephone network.
TAPI services provide for all aspects of usage of the telephone network. This includes connecting to the network, placing and accepting calls, call management features (such as transferring calls, setting up conference calls), use of calling number identification (Caller ID) for identifying incoming calls, and more.
TAPI services are divided into basic, supplementary, and extended services. Basic services are generally supported by all devices; supplementary services may only be available on special devices. Extended services are provider-specific.
For example, TAPI can place a call on all telephone lines; however, call management functions, such as transferring a call, may only be available on devices that specifically support such a feature, and thus is considered a supplementary service.
TAPI is not restricted to what is whimsically referred to by the acronym POTS: Plain Old Telephone Service. POTS is analog service on the local loop (the wire connecting the telephone set with the nearest switching office). POTS supports voice calls with a 3.1 kHz bandwidth, or data calls at speeds up to 28.8 kbps using V.34 modems.
In contrast, ISDN (Integrated Services Digital Network) supports up to 128 kbps with its Basic Rate Interface (BRI-ISDN); the speed on PRI-ISDN (Primary Rate Interface) is much higher. TAPI supports ISDN as well as other connection types, such as switched 56, or T1/E1. TAPI can also utilize CENTREX features and the features of Private Branch Exchanges (PBXs).
TAPI makes a distinction between line devices and phone devices. A line device is the abstract representation of a physical device that connects your computer to the telephone network. Examples of line devices include modems, FAX modems, or ISDN cards.
A phone device is the abstract representation of a device with the capabilities of a telephone set. A phone device may have a speaker, a microphone, lamps, a display, buttons, and so on. A phone device is not necessarily a physical device; a software emulation that uses the computer's speaker, microphone, sound card hardware, and a voice-capable modem and displays a telephone-like interface on the screen can also act as a phone device.
Figure 42.1 shows a basic configuration consisting of a line device (a data modem) and a phone device (a programmable telephone under TAPI control). Note that the presence of a telephone set does not imply the existence of a TAPI phone device. For example, if you have a plain telephone set that is used in conjunction with a dialer that responds to the Hayes AT command set (or a modem used as a dialer) this configuration is represented by a line device. A phone device is used when TAPI has control over some of the features of the telephone set such as its display, switchhook, ringer, or buttons.
Line devices all provide a basic set of functions (Basic Telephony). In contrast, all phone device functions are part of Supplementary Telephony; this is because there is no minimum set of functions that a phone device is expected to provide.
TAPI distinguishes between the concepts of a line and that of an address. The line is the physical entity; the address is, for example, a telephone number assigned to the line.
Although most POTS lines are associated with a single telephone number, this is not always the case. For example, an ordinary telephone line may be configured with more than one telephone number using the telephone company's distinctive ringing service. On digital lines, the use of more than one address on a single line is more common.
A unique feature of telephone numbers is that their actual format is relative to the originating location. Take, for example, a number here in Ottawa, such as 613-555-1234. When I dial this number locally, all I need to dial is the seven digits of the local number, 555-1234. If I call this number from New York City, I need to dial 1-613-555-1234. Calling the same number from Budapest, Hungary, requires dialing 00w1-613-555-1234, where the letter w represents waiting for a second dial tone. Calling the same number from a telephone set attached to the PBX of a local company may require dialing the digits 8-555-1234. These differences become especially relevant on portable computers.
TAPI does an excellent job translating telephone numbers. At the heart of its capability is the canonical address format. The syntax of a canonical address is as follows:
+CountryCode Space [(AreaCode) Space] SubscriberNumber [| SubAddress] [^ Name] CRLF
For example, the canonical format of the Ottawa number 555-1234 is as follows:
+1 (613) 555-1234
The canonical address differs from a dialable address. Dialable addresses are those that do not begin with the character +; these numbers are presumed to be dialable on the given line without modification. A canonical address can be translated into a dialable address by the lineTranslateAddress TAPI function. The syntax for a dialable number is as follows:
DialableNumber [| SubAddress] [^ Name] CRLF
Dialable addresses can contain, in addition to digits, the DTMF symbols A-D, # (DTMF "gate"), and * (DTMF "star"), and any of a variety of dial modifier characters. Dial modifier characters are based on the Hayes AT command set and include the characters shown in Table 42.1.
Dial Modifier Character |
Description |
! (exclamation mark) |
flash switchhook |
P or p |
pulse dial for subsequent digits |
T or t |
tone dial for subsequent digits |
, (comma) |
pause |
w or W |
wait for dial tone |
@ |
wait for quiet answer |
$ |
wait for billing signal |
; |
indicates incomplete dialable number |
At the heart of TAPI is the TAPI DLL, the dynamic link library that offers TAPI services to applications. This DLL serves as a layer between telephony applications and TAPI service providers. One such service provider is the UNIMODEM driver; this Universal Modem driver is supplied with Windows 95 and provides TAPI services for modems compatible with the Hayes AT command set.
This basic TAPI architecture is shown in Figure 42.2. In addition to the TAPI DLL and the telephony service providers (drivers), another important, albeit invisible, component of TAPI is the executable program tapiexe.exe. This program plays an important role when TAPI sends notifications to the calling application via callback functions.
Figure 42.2. The TAPI software architecture.
Many TAPI operations are synchronous; that is, when the TAPI function returns, the operation is either completed or failed, in which case an error code is returned. However, some TAPI operations are asynchronous; the TAPI function returns indicating whether the TAPI operation has been successfully initiated, but the operation is completed in another thread, and the application is notified via a callback function. The callback function is registered with TAPI when the TAPI library is initialized.
The actual callback mechanism deserves a closer examination, especially because it has some consequences as to how TAPI functions operate.
When a service provider wishes to place a notification, it calls the TAPI DLL. In effect, it requests that the DLL notify all concerned applications that a specific event has taken place. This first call to the TAPI DLL takes place in the execution context of the service provider.
The TAPI DLL in turn sends a message to tapiexe.exe. This executable program calls the TAPI DLL itself, this time in its own execution context. This call instructs the TAPI DLL to post a Windows message to the applications that need to be modified.
When the application receives and processes the message in its message loop, the message is dispatched to the TAPI DLL again, this time in the application's execution context. The TAPI DLL may in turn call the application's registered TAPI callback function to notify the application of a TAPI event.
The TAPI notification mechanism is shown in Figure 42.3.
Figure 42.3. Processing of TAPI events.
This mechanism has important implications for the architecture of TAPI applications. For one thing, the scenario described here makes it clear that TAPI applications must have a message loop in order to process notifications correctly. Although the use of a callback function may imply that a message loop is unnecessary, this is not the case; the callback function is only called after the application receives a Windows message that the TAPI DLL processes.
Another consequence concerns the use of multiple threads. It is important to realize that in order for TAPI to operate as expected, threads that call asynchronous TAPI functions must have a message loop. The callback function is called in the context of the thread making the asynchronous call; this cannot happen unless the thread processes Windows messages.
While the need for a message loop does not completely rule out the use of TAPI with console applications, it places certain restrictions on them. The console application must have a message loop that processes and dispatches Windows messages. The example presented later in this chapter (Listing 42.2) demonstrates the use of this technique.
Everywhere throughout TAPI, variable-length structures are frequently used. These structures represent data that is variable in length, such as optional fields or strings.
All TAPI variable-length structures make use of structure members dwTotalSize, dwNeededSize, and dwUsedSize. When a TAPI function is called that is expected to return data in such a structure, your first task is to allocate the structure and fill its dwTotalSize member prior to making the call.
The TAPI documentation refers to structures of this kind as flattened. Instead of referred to through pointers, supplementary data fields and variable-length fields are simply appended to the end of the structure. Variable-length fields are referred to in the structure by an offset and a length parameter; the offset specifies the starting position of the field, in bytes, relative to the start of the structure; the length represents the length of the field in bytes.
Suppose a TAPI function called tapiStrangeFunc returns variable-length data in a VARSTRUCT structure. This structure is declared as follows:
typedef struct { DWORD dwTotalSize; DWORD dwNeededSize; DWORD dwUsedSize; // other fixed-length elements here DWORD dwVarItem1Size; DWORD dwVarItem1Offset; // more fixed-length elements here DWORD dwVarItem2Size; DWORD dwVarItem2Offset; } VARSTRUCT, FAR *LPVARSTRUCT;
Before tapiStrangeFunc is called, you must allocate a VARSTRUCT structure. Although you can allocate it as an automatic variable, doing so is not recommended. Instead, use the following mechanism:
LPVARSTRUCT pVarStruct; pVarStruct = (LPVARSTRUCT)malloc(sizeof(pVarStruct)); pVarStruct->dwTotalSize = sizeof(VARSTRUCT); tapiStrangeFunc(pVarStruct);
The description of tapiStrangeFunc may tell you that this function appends an extra DWORD member to this structure. Clearly, this extra member requires additional memoryand so do the two variable-length items identified by the members dwVarItem1Size/dwVarItem1Offset, and dwVarItem2Size/dwVarItem2Offset.
When tapiStrangeFunc returns, this fact is indicated by the value of the dwNeededSize structure member. This member will indicate that additional memory is needed to return all values. A possible response to this would be a reallocation of the structure, and another call to tapiStrangeFunc:
pVarStruct = (LPVARSTRUCT)malloc((LPVOID)pVarStruct, pVarStruct->dwNeededSize); pVarStruct->dwTotalSize = pVarStruct->dwNeededSize; tapiStrangeFunc(pVarStruct);
When this call to tapiStrangeFunc returns, pVarStruct points to a structure in memory as shown in Figure 42.4.
Figure 42.4. An example for a TAPI variable-length structure.
The dwUsedSize field is somewhat of a lesser significance; it comes into play when TAPI could not fill in all the structure members (dwTotalSize was less than dwNeededSize). In this case, rather than truncating a variable-length field, TAPI simply leaves that field empty.
In addition to the Assisted TAPI services that we have seen already, TAPI provides services that fall into three categories: Basic Telephony, Supplementary Telephony, and Extended Telephony.
Basic Telephony includes all functions that a POTS line can be expected to provide. This minimal set of functions must be supported by all service providers.
Supplementary Telephony includes all standard TAPI services that are not in the Basic Telephony set of functions. These include supplementary services found on most PBXs, such as hold, call transfer, conference calls, and so on. An application can query the set of supplementary services supported by a particular line device or phone device by calling lineGetDevCaps, lineGetAddressCaps, or phoneGetDevCaps.
Extended TAPI services are provider-specific. These include all device-specific TAPI extensions. TAPI provides the necessary mechanisms for extending services through variable-length structures, and functions through which service providers can inform applications about the extended services they support.
The basic TAPI programming model for line devices is illustrated in Figure 42.5.
Figure 42.5. The TAPI programming model: calls in a typical TAPI application.
All TAPI applications that utilize line devices begin by a call to lineInitialize. This call initializes TAPI for use with line devices. Note that this call should not be made unless the application actually intends to utilize TAPI services; making this call unnecessarily may use up valuable TAPI resources and cause other applications to fail.
One parameter to lineInitialize is the address of a callback function. It is through this function that the application is informed of the completion of asynchronous function requests and other TAPI events.
Before an application can open a specific line device, it must negotiate a TAPI version number by calling lineNegotiateAPIVersion. Through this call, the application and the service provider handling the specific device can agree on a version number they can both support. Note that the current TAPI version number is 1.4; only this and an earlier 16-bit version, 1.3, are presently in existence.
The line device is opened by calling lineOpen. Afterwards, applications can call a variety of functions that use the open device. One example is the lineMakeCall function that is used to place a call on the line device. This function is also an example for an asynchronous function; it returns immediately after the call request has been successfully placed. The application is notified of the completion of the call request through its callback function.
After the call has been placed, an application can do a variety of things with the line depending on its intended function. A number of additional functions can be used to obtain information about the line and the call, configure the line, and manipulate addresses. Supplementary functions can be used to transfer the call, place it on hold, set up conference calls, and so on.
A specific feature offered by TAPI assists data communication applications in particular. Such applications may use the TAPI lineGetID function to obtain a handle to the communication device. This handle is opened by TAPI for overlapped I/O and can be used by the application for exchanging data with a remote host.
Using lineMakeCall is not the only way to establish a call. Applications may also obtain a call handle by accepting incoming calls. Applications that are set up to accept incoming calls are notified of such calls through their callback function.
When the application wishes to terminate the call, it can use the lineDrop function. A call may also be terminated by the remote end; in this case, the application is notified through its callback function.
When the application is finished using the line device, it should call the lineClose function to close the device. The lineShutdown function can be used to terminate the application's session with TAPI.
The programming model used for phone devices is similar. The key steps of initializing TAPI, negotiating a version number, and opening the device are present. There is no equivalent to placing a call on a phone device; phone device functions exist to manipulate the various components of a telephone, such as its switchhook, display, or buttons.
Applications that wish to use provider-specific Extended Telephony services must call the lineNegotiateExtVersion or phoneNegotiateExtVersion functions to negotiate the extended version number. Device-specific functions can be executed by calling the escape functions lineDevSpecific, lineDevSpecificFeature (for switch functions), and phoneDevSpecific.
TAPI provides two concepts that specify the quality of service supported by a line and the type of a call.
The bearer mode specifies the quality of service. For example, the voice bearer mode (LINEBEARERMODE_VOICE) indicates a POTS line with a 3.1 kHz analog bandwidth and no provisions for data integrity. Other bearer modes describe ISDN or other data lines.
The media mode determines the type of the call. For example, on a voice line it is possible to make voice or data calls; these correspond to the media modes LINEMEDIAMODE_INTERACTIVEVOICE or LINEMEDIAMODE_DATAMODEM.
The TAPI architecture enables multiple applications to coexist. This is a very important feature. This makes it possible, for example, for a TAPI FAX application to monitor a line for incoming FAX transmissions while at the same time enabling another TAPI application, such as a data communication application, to use the same line for outgoing calls.
At the heart of this capability is the concept of call ownership. Initially, ownership of a call is assigned to one application; it is either the application that originated the call or the application that receives the incoming call. An application can pass ownership of a call to another application through the lineHandoff function. The original application also continues owning the call. It can then choose to remain a co-owner of the call (although doing so is not recommended), deallocate the call handle indicating that it is no longer interested in the call, or use lineSetCallPrivilege to become a call monitor. A call monitor is an application cannot control the call's existence, but it can record facts about the call (logging).
When handling incoming calls, applications may perform probing to determine the nature of the call. Probing can be used, for example, to determine whether an incoming call is a data, FAX, or voice call. Probing is usually done by applications, although some service providers can be configured to auto-answer a call and hand it off to the appropriate application. Note that TAPI does not launch applications to handle specific call types.
Applications can learn about in-progress calls at startup by calling lineGetNewCalls. Through this function, an application can obtain handles with monitoring privilege for all calls that are currently in progress.
Applications can communicate with each other by using the lineSetAppSpecific function. Through this function, they can set the dwAppSpecific field of the LINECALLINFO structure. Other applications that own or monitor the call are informed by receiving a LINE_CALLINFO message.
I decided to put TAPI into practice by modifying a simple console application I wrote earlier. This simple communication application opens a communication port and uses overlapped I/O operations to perform input and output.
In its original version, the application simply opened the port without making any attempt at placing a call. It was up to the user to use the appropriate AT commands to place a call. The communication port was hardcoded in the application.
In the TAPI version presented in Listing 42.2, the call is placed through the TAPI function lineMakeCall. Before that happens, the user is given the opportunity to choose a TAPI device.
#include <windows.h> #include <tapi.h> #include <stdio.h> volatile BOOL bConnected = FALSE; VOID FAR PASCAL lineCallback(DWORD hDevice, DWORD dwMsg, DWORD dwCallbackInstance, DWORD dwParam1, DWORD dwParam2, DWORD dwParam3) { if (dwMsg == LINE_CALLSTATE && dwParam1 == LINECALLSTATE_CONNECTED) bConnected = TRUE; } LINEDEVCAPS *GetDevCaps(HLINEAPP hLineApp, DWORD dwDeviceID, LPDWORD lpdwAPIVersion) { LINEDEVCAPS *pLineDevCaps; LINEEXTENSIONID extensionID; lineNegotiateAPIVersion(hLineApp, dwDeviceID, 0x10004, 0x10004, lpdwAPIVersion, &extensionID); pLineDevCaps = malloc(sizeof(LINEDEVCAPS)); pLineDevCaps->dwTotalSize = sizeof(LINEDEVCAPS); lineGetDevCaps(hLineApp, dwDeviceID, *lpdwAPIVersion, 0, pLineDevCaps); if (pLineDevCaps->dwNeededSize > pLineDevCaps->dwTotalSize) { pLineDevCaps = realloc(pLineDevCaps, pLineDevCaps->dwNeededSize); pLineDevCaps->dwTotalSize = pLineDevCaps->dwNeededSize; lineGetDevCaps(hLineApp, dwDeviceID, *lpdwAPIVersion, 0, pLineDevCaps); } return pLineDevCaps; } HANDLE SelectTAPIDevice(HLINEAPP hLineApp, DWORD dwNumDevs, LPHLINE lphLine, LPHCALL lphCall) { LINEDEVCAPS *pLineDevCaps; DWORD dwDeviceID; DWORD dwAPIVersion; DWORD i; LINECALLPARAMS lineCallParams; LPVARSTRING lpDeviceID; MSG msg; char szNumber[81]; for (i = 0; i < dwNumDevs; i++) { pLineDevCaps = GetDevCaps(hLineApp, i, &dwAPIVersion); if (pLineDevCaps->dwMediaModes & LINEMEDIAMODE_DATAMODEM) printf("%d: %s\n", i, (char*)pLineDevCaps + pLineDevCaps->dwLineNameOffset); free(pLineDevCaps); } dwDeviceID = -1; while (dwDeviceID < 0 || dwDeviceID >= dwNumDevs) { printf("Select device: "); scanf("%d", &dwDeviceID); if (dwDeviceID < 0 || dwDeviceID >= dwNumDevs) continue; pLineDevCaps = GetDevCaps(hLineApp, dwDeviceID, &dwAPIVersion); if(!(pLineDevCaps->dwMediaModes & LINEMEDIAMODE_DATAMODEM)) { dwDeviceID = -1; free(pLineDevCaps); } } printf("Enter telephone number: "); scanf("%s", szNumber); printf("Dialing %s on %s...", szNumber, (char *)pLineDevCaps + pLineDevCaps->dwLineNameOffset); free(pLineDevCaps); lineOpen(hLineApp, dwDeviceID, lphLine, dwAPIVersion, 0, 0, LINECALLPRIVILEGE_NONE, LINEMEDIAMODE_DATAMODEM, NULL); memset(&lineCallParams, 0, sizeof(LINECALLPARAMS)); lineCallParams.dwTotalSize = sizeof(LINECALLPARAMS); lineCallParams.dwMinRate = 2400; lineCallParams.dwMaxRate = 57600; lineCallParams.dwMediaMode = LINEMEDIAMODE_DATAMODEM; lineMakeCall(*lphLine, lphCall, szNumber, 0, &lineCallParams); while (!bConnected) if (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg); putchar('\n'); lpDeviceID = malloc(sizeof(VARSTRING)); lpDeviceID->dwTotalSize = sizeof(VARSTRING); lineGetID(0, 0, *lphCall, LINECALLSELECT_CALL, lpDeviceID, "comm/datamodem"); if (lpDeviceID->dwNeededSize > lpDeviceID->dwTotalSize) { lpDeviceID = realloc(lpDeviceID, lpDeviceID->dwNeededSize); lpDeviceID->dwTotalSize = lpDeviceID->dwNeededSize; lineGetID(0, 0, *lphCall, LINECALLSELECT_CALL, lpDeviceID, "comm/datamodem"); } return *((LPHANDLE)((char *)lpDeviceID + sizeof(VARSTRING))); } void main(void) { HLINEAPP hLineApp; HLINE hLine; HCALL hCall; DWORD dwNumDevs; HANDLE hConIn, hConOut, hCommPort; HANDLE hEvents[2]; DWORD dwCount; DWORD dwWait; COMMTIMEOUTS ctmoCommPort; DCB dcbCommPort; OVERLAPPED ov; INPUT_RECORD irBuffer; BOOL fInRead; char c; int i; lineInitialize(&hLineApp, GetModuleHandle(NULL), lineCallback, "Test TAPI Application", &dwNumDevs); hCommPort = SelectTAPIDevice(hLineApp, dwNumDevs, &hLine, &hCall); hConIn = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); SetConsoleMode(hConIn, 0); hConOut = CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); ctmoCommPort.ReadIntervalTimeout = MAXDWORD; ctmoCommPort.ReadTotalTimeoutMultiplier = MAXDWORD; ctmoCommPort.ReadTotalTimeoutConstant = MAXDWORD; ctmoCommPort.WriteTotalTimeoutMultiplier = 0; ctmoCommPort.WriteTotalTimeoutConstant = 0; SetCommTimeouts(hCommPort, &ctmoCommPort); dcbCommPort.DCBlength = sizeof(DCB); GetCommState(hCommPort, &dcbCommPort); SetCommState(hCommPort, &dcbCommPort); SetCommMask(hCommPort, EV_RXCHAR); ov.Offset = 0; ov.OffsetHigh = 0; ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); hEvents[0] = ov.hEvent; hEvents[1] = hConIn; fInRead = FALSE; while (1) { if (!fInRead) while (ReadFile(hCommPort, &c, 1, &dwCount, &ov)) if (dwCount == 1) WriteFile(hConOut, &c, 1, &dwCount, NULL); fInRead = TRUE; dwWait = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE); switch (dwWait) { case WAIT_OBJECT_0: if (GetOverlappedResult(hCommPort, &ov, &dwCount, FALSE)) if (dwCount == 1) WriteFile(hConOut, &c, 1, &dwCount, NULL); fInRead = FALSE; break; case WAIT_OBJECT_0 + 1: ReadConsoleInput(hConIn, &irBuffer, 1, &dwCount); if (dwCount == 1 && irBuffer.EventType == KEY_EVENT && irBuffer.Event.KeyEvent.bKeyDown) for (i = 0; i < irBuffer.Event.KeyEvent.wRepeatCount; i++) { if (irBuffer.Event.KeyEvent.uChar.AsciiChar) { WriteFile(hCommPort, &irBuffer.Event.KeyEvent.uChar.AsciiChar, 1, &dwCount, NULL); if (irBuffer.Event.KeyEvent.uChar.AsciiChar == 24) goto EndLoop; } } } } EndLoop: CloseHandle(ov.hEvent); CloseHandle(hConIn); CloseHandle(hConOut); CloseHandle(hCommPort); lineDrop(hCall, NULL, 0); lineClose(hLine); lineShutdown(hLineApp); }
The fact that this is a console application represented special challenges. In particular, it was necessary to use a message loop at one point to enable the TAPI callback mechanism to work. Although this approach may be somewhat unorthodox, it demonstrates the TAPI programming model and its traps and pitfalls surprisingly well.
The first call in the applications main function is to the TAPI function lineInitialize. Next, main calls the function SelectTAPIDevice; this high-level function queries the user for a TAPI device and a telephone number, opens the device, places the call, and returns a Win32 handle that the application can use in subsequent I/O calls.
The SelectTAPIDevice function first queries all TAPI line devices for the line name. These line names are presented to the user in the form of a numbered list, and the user is requested to choose one of them. When determining the line name, SelectTAPIDevice utilizes another function, GetDevCaps. Note the duplicate calls to the TAPI function lineGetDevCaps in GetDevCaps; the first call is used to determine the size of the structure lineGetDevCaps would return. The second call is made after a sufficiently large block of memory has been allocated.
After a line device has been selected in SelectTAPIDevice, the user is requested to enter a telephone number. This number is then used, after the line has been opened and the appropriate structure initialized, in a call to lineMakeCall. Since lineMakeCall is an asynchronously executing TAPI function, the return of this function does not indicate completion of the request. In particular, the call handle pointed to by lphCall is not yet valid. The application must wait until its callback function is called indicating that the call has been set up; furthermore, it must ensure that the callback mechanism operates as expected by executing a message loop.
The callback function, lineCallback, is extremely simplistic; it simply waits for a LINE_CALLSTATE message that indicates that the call has been connected. The rest of the application is notified of call completion when the global variable bConnected is set to TRUE by the callback function. In particular, this change causes the message loop in SelectTAPIDevice to terminate.
When SelectTAPIDevice is notified of successful call completion through this mechanism, it uses the lineGetID function to retrieve a handle to the communication port. Note how lineGetID is called twice, first to determine the size of the data structure it is about to return. Note also how an extra structure member of type HANDLE is retrieved.
When SelectTAPIDevice returns, it passes the communication device handle to main. In main, this handle is used to configure the communication device and the console for I/O and handle bidirectional data transfer. The application is terminated when the user hits the Control+ X key combination. At this time, the application closes all handles, terminates the TAPI session, and exits.
To compile this application from the command line, type cl tty.c tapi32.lib user32.lib. The USER library is required because of the references to the Windows functions GetMessage and DispatchMessage.
I ran this application on my main desktop computer that has two modems attached to it. An internal FAX modem connects my desktop computer to my data line; an old external pocket modem connects it to my voice line. I mostly use this modem simply as a dialer; however, it comes in handy when I need to test communication applications like this one. Also connected to my data line through its own FAX modem is another computer running Linux (this is my server for Internet mail and TCP/IP connections). This server also accepts incoming data calls, so I can utilize the modem on my voice line to make calls to it.
A sample session using this configuration looked like the following:
C:\TTY>tty 0: SupraFAXModem 144i 1: Practical Peripherals 2400 Select device: 1 Enter telephone number: 555-1234 Dialing 555-1234 on Practical Peripherals 2400... You have reached a private computer system. Calls to this system are logged using calling party identification (caller ID). Unauthorized calls violate my privacy, not to mention the law! If you have not been specifically authorized by me to access this system, now would be a great time to terminate your connection. Viktor Welcome to Linux 1.1.37. vtt1!login: vttoth Password: Last login: Tue Oct 17 01:57:20 on ttyS0 Linux 1.1.37. (Posix). vtt1:~$ ^X C:\TTY>
TAPI, the Microsoft Telephony API, provides personal telephony services for Windows applications. TAPI provides abstractions for line devices that connect a computer to a telephone line, and phone devices, which are telephone sets with a variety of components such as a switchhook, display, buttons, or ring, that can be manipulated programmatically. (Note that most commonly used telephone sets cannot be manipulated this way.) A TAPI line device always represents a physical device; in contrast, a phone device can be a software representation of a telephone that uses the computer's display, keyboard, and sound hardware to provide the services of a telephone set.
TAPI line devices are not restricted to represent only devices attached to plain old telephone service (POTS) lines. Line devices can represent hardware connected to ISDN lines, T1/E1 data lines, switched 56 data lines, and other lines.
TAPI provides services to place outgoing calls and accept incoming calls. It is the responsibility of applications to manage the media stream, the actual flow of data during a call. The media stream is a generic term that represents voice, data, FAX images, or other information that flows through a telephone line.
The TAPI DLL represents a layer between applications and device-specific service providers (drivers). Another TAPI component is the tapiexe.exe system application that is used in TAPI messaging.
TAPI functions can execute synchronously and asynchronously. Synchronous functions return immediately with a success or failure result. Asynchronous functions, on the other hand, return only to indicate whether a request has been placed successfully; applications are notified of request completion through a callback function. In order for the callback function mechanism to operate, applications must maintain a Windows message loop.
The TAPI interface is broken down into Assisted Telephony, Basic Telephony, Supplementary Telephony, and Extended Telephony.
Assisted Telephony provides a set of simple functions for placing calls. These functions are ideally suited for use in script languages that can call external DLLs.
Basic Telephony consists of those line device functions that all service providers must implement. These include functions to place and accept calls and monitor calls in progress.
Supplementary Telephony consists of functions that require special hardware. For example, special hardware is required for call transfer, conference call, and other call management functions to work. Applications cannot expect that a supplementary function is available; they must query the service provider about the availability of a supplementary function.
Extended Telephony represents device- and provider-specific functionality. Extended functions are accessed through special escape functions that TAPI provides. Before using extended functions, applications must determine their availability by querying the service provider.