While it might seem like a good idea to always ask for the latest and the greatest, doing so can introduce some practical problems. As TAPI has evolved, many of its data structures have been modified. Also, some new TAPI messages have been introduced while others have been retired or subtly changed in their meaning. Writing an application that will work with multiple versions of TAPI is not impossible, but it's a complex task that should be discouraged.
There are two factors that determine which version of TAPI an application should use: feature set and platform. If an application needs a particular feature that's only available on a certain TAPI version, use the version that supports that capability. This may limit the platforms on which the application can run. On the other hand, if an application must support as many platforms as possible, you'll have to sacrifice features that are not available on early versions of TAPI.
The Platform SDK reference contains a section called "What's New" that describes the differences between TAPI 1.4 and TAPI 2.0. The 16-bit TAPI SDK outlines TAPI 1.3. Unfortunately, there is no single document that clearly outlines the differences between all versions of TAPI.
Life Cycle of a Call
So far I've been discussing TAPI in abstract terms. In the following section, you'll look at TAPI from the API perspective by following the life of a call. The discussion will follow roughly the accompanying sample code (see Figure 6), which was written for TAPI 2.0 using event-based notification.
The sample program is a simple console mode application that takes a few parameters. In outbound mode, it calls a specified destination. In incoming mode, it answers an incoming call in the specified media mode. Once the call is connected, pressing Ctrl-C will hang up the call and exit the application. Disconnection from the target address will also terminate the application.
Before jumping into the details, I'll discuss some programming concepts that may be unfamiliar to developers who have not yet worked with TAPI.
VARSTRING and Variable Length Structures
This TAPI concept makes many programmers cry. Much of the information that is passed in TAPI programs is provided by the TAPI service provider. Often this data is amorphous in nature and variable in length. To efficiently pass this data around, TAPI uses variable length "structures" that are really headers. Variable length structures consist of conventional structures along with offset and size information for additional data. The additional information is appended to the structure itself.
Variable length structures always starts with three DWORD members: dwTotalSize, dwNeededSize, and dwUsedSize. When allocating and initializing a structure, dwTotalSize must be initialized with a value that reflects the total amount of allocated memory. Upon return from an API call, the caller should check the value of dwNeededSize against dwTotalSize. If dwNeededSize is larger than dwTotalSize, then the memory initially allocated was insufficient. You will need to reallocate the memory and try the call again.
There is no guarantee that the amount of memory needed will be the same on the second call. The contents of the structure are dynamic so the size can change. This is not a problem with relatively static TSPs like UNIMODEM, but it's an important consideration in high-volume TSPs such as PBX switches. It's a good idea to keep checking the dwNeededSize until the memory requirement is satisfied.
It's an acceptable shortcut to arbitrarily allocate extra buffer space in anticipation of extra memory requirements. The sample code demonstrates this approach by allocating 1K buffers. It's still necessary to check afterward that what you
anticipated was indeed enough. Continue looping until you have confirmed that the buffer size
is enough to hold the necessary information.
VARSTRING is the most extreme example of the variable length structure. Its header is minimal and its sole purpose is to hold opaque and amorphous data. Most commonly, it's used to pass data between TSPs and running applications that TAPI doesn't touch.
Asynchronous Calls and LINE_REPLY
TAPI calls are frequently asynchronous. When an API function requests that a certain action be performed, the request is passed to TAPI, TAPI passes it down to the TSP, the request is queued, and the API function returns. Later when the operation is completed, the application is notified.
It is important to realize that the successful return of the API call doesn't mean the requested operation was successful. It simply means that the TSP has acknowledged the request. The true result of the operation won't be available until some later point when the operation is actually completed. If the operation is asynchronous, then when the API call returns the application will be given a request identifier for the pending operation. When TAPI advises the application of request completion through LINE_REPLY, it uses this ID to identify which request has completed.
In the Beginning
Typically, the first TAPI call an application makes is lineInitialize or lineInitializeEx. A TAPI 1.3 application uses lineInitialize, and a TAPI 2.x application uses lineInitializeEx. These calls will load TAPI.DLL into the application's memory space. TAPI will also start TAPISRV (or TAPIEXE, in the case of Windows 95) and load the installed TSPs. If this is the first TAPI session, TAPI will call TSPI_
providerEnumDevices for each TSP installed. Each TSP will respond with the number of line and phone devices it's exposing, and TAPI will assign a device ID for each of them. TAPI also calls TSPI_providerInit to initialize each provider. Once complete, the API call will return with the number of total enumerated line devices in a zero-based index.
TAPI also returns the LineApp Handle, which is like a ticket stub at the movies. The LineApp Handle identifies the TAPI session your application is using. Keep this somewhere safe as you'll need to present your ticket stub later.
When calling the initialization API function, the application must select and provide TAPI with an event notification method. Telephony applications are strongly event-driven. Telephony events reported to applications by TAPI include changes in existing calls, new incoming calls, connections, and disconnections.
In TAPI 1.x, callback functions are the only method
of event notification. TAPI uses a hidden window to drive this callback mechanism. This means that TAPI 1.x applications must meet the following conditions: the application must dispatch messages, and the thread that calls lineInitialize must handle the callbacks. When writing a console-based application, the first requirement is often problematic.
TAPI 2.0 added two additional notification mechanisms: event-based and completion-port-based. These are useful in writing Windows NT service or console-based applications because they provide more flexible implementation options. While they can be used interchangeably, some applications benefit more from one than the other. Events are relatively simple to use and are good for small applications or services. The completion ports can be queued and queried, and are the logical choice for higher volume applications where many lines are opened and many activities take place concurrently. Completion ports are not implemented on Windows 95, even with the TAPI 2.1 update.
Selecting a Line
Users frequently have more than one telephony line device installed on a machine. Some of them are obvious devices like modems and telephone lines. Others are less obvious. For example, Direct Cable Connection on Windows 95 installs a telephony line for each port it can use. It's also common to have more telephony devices than physical hardware. For example, more than one modem can be installed on a single port, each with a different device ID. An ISDN adapter TSP may model its various modes (voice, data, bonded data, and so on) as different logical devices. In traditional programming before TAPI, you chose communication devices by choosing the underlying hardware. For example, you'd choose COM1 to use the fax/modem on that port. TAPI's device IDs have nothing to do with the underlying hardware.
With TAPI, instead of picking devices according to where they are, you pick them according to what they can do. The TAPI API function lineGetDevCaps lets you query each device for its capabilities. The function populates the LINEDEVCAPS structure, which includes useful information such as the supported media modes, the TSP name, and line features. An application can use the information returned by the function to choose the line device with the desired capabilities.
Once the desired device is selected, the API version must be negotiated using the lineNegotiateAPIVersion function, which allows the caller to specify low and high versions separately. In theory, you can select a range of API versions. In practice, as discussed earlier, it's a good idea to specify the API version for which the application was written.
Opening the Line
Now that you have selected a line device, you can open the line. But first you must make a few decisions. The Platform SDK documentation does a good job of explaining what each parameter means and what it does. If UNIMODEM is the TSP for the selected line, you must watch out for a few additional details:
- UNIMODEM will only open the port if LINECALLPRIVILEGE_OWNER is set.
- UNIMODEM will accept LINEMEDIAMODE_INTERACTIVEVOICE even though the modem device itself is data only. This allows dialer applications to dial outbound voice calls.
If a line is open with LINECALLPRIVILEGE_MONITOR, no call state information will be available unless the line is opened with LINECALLPRIVILEGE_OWNER, either by another process or by another call to lineOpen.
Optionally, an application can call lineOpen with a value passed in the dwCallbackInstance parameter. TAPI doesn't touch this variable; it simply saves it internally. When TAPI notifies the application with a line event, it will pass back this value. The dwCallbackInstance parameter can be used for any purpose, but is especially useful as a "token" that identifies the line in a multiline application.
A successful call to lineOpen yields a line handle that
can be used to address the line in subsequent line device API calls.
Creating a Call
Outbound Call
You can initiate outbound calls by calling the lineMakeCall API function. As described in the previous section, the line must already be open since this call requires a valid line handle. You also will need to specify the destination address, a fancy way of describing the "phone number" you are dialing. You may simply feed in the number to be dialed as needed, or have TAPI prepare one for you by "translating" a canonically formatted number.
Translation
What is "translation"? You may recall that when you first installed a modem on a TAPI-supported PC, you filled out some dialog boxes asking for your area code, country, whether to dial 9 to get an outside line, and so on. That information is used for the translation process. TAPI provides the API function lineTranslateAddress to achieve this. You must pass the destination address in the canonical format, which is defined as follows: +Country Code (Area Code) Exchange-Station. For example, the phone number for Microsoft would be represented as +1 (425) 882-8080. The spaces and parentheses are important. TAPI will perform the translation based on the location profile and translation options, and populate the structure LINETRANSLATEOUTPUT with the translation results.
LINETRANSLATEOUTPUT contains, among other things, two types of translated destination addresses: the dialable string and the displayable string. The dialable string is the string that should be passed to lineMakeCall. It stores the digits in exactly the way they should be dialed, including calling card numbers in plain text. The displayable string contains a "safe" string that hides the card information. The displayable string always should be used for user interface displays to avoid inadvertently disclosing a user's calling card information.
Incomplete Dialing
In most cases, simply passing the translated dialable string to lineMakeCall will satisfy your dialing needs. There are, however, some specific instances where it is desirable to break up the dialing into multiple parts. In particular, you must use this method when it's necessary to insert delays in dialing between sets of digits. A semicolon appended to a dialable string tells TAPI that dialing is incomplete and more digits are to follow. Subsequent digits can be dialed using the lineDial API call. The dialing stays in an incomplete state as long as a semicolon is suffixed to the dialable string. You can complete the dialing by either omitting the semicolon or calling the lineDial function with a null destination address.
Call progress
Once dialing is completed, TAPI will start reporting the call state using LINE_CALLSTATE messages. Typically, the call state first will transition to LINECALLSTATE_PROCEEDING, indicating that the call is being routed to the destination. If the call is answered, it will shift to the LINECALLSTATE_CONNECTED state, indicating that the call is established. Depending on the capability of the service provider and the underlying hardware, it is common to encounter several other call states before reaching the state LINECALLSTATE_CONNECTED. The exact meanings of each state are documented in the Platform SDK documentation.
Inbound Call
Incoming call handling is simpler than outgoing call handling. For an application to respond to an incoming call, it must open the line with OWNER privilege. Ownership can be requested on a per media mode basis, allowing multiple applications to have ownership of different media modes over the same line. If the TSP can detect media modes for incoming calls, you can use this ability to efficiently share lines between multiple applications. For example, UNIMODEM/V uses the distinctive ring capability of POTS to determine the media modes predefined by the user.
In the POTS + UNIMODEM scenario, however, it is most common to request all supported media modes plus UNKNOWN. This is because POTS and UNIMODEM are normally incapable of detecting the media mode. Applications answer each incoming call, determine its media mode, then hand it off to the application that owns that media mode. A good example of an application of this is the Operator application that ships with UNIMODEM/V.
Once the line is successfully opened, the application simply waits for a call. When a call arrives that matches the media modes requested, the application receives the call state message LINECALLSTATE_OFFERING, which indicates that a call is being offered to the application. Depending on what messages were masked by the lineSetStatusMessages call, the application may also receive a LINEDEVSTATE_RINGING notification as the call rings. If this is the case, dwParam2 indicates the ring count for this call.
While it makes intuitive sense to rely on LINEDEVSTATE_RINGING to determine the presence of an incoming call rather than LINECALLSTATE_OFFERING, it's bad programming practice for several reasons. LINEDEVSTATE_RINGING simply means that the line is in a ringing state (the station is being alerted of an incoming call), and does not mean the application necessarily owns that particular call. For example, the call could be for a media mode that the application doesn't support. Also, since you are likely to get more than one ring, it's very difficult in a high-volume situation to distinguish how many distinctive calls have come in by looking at the LINEDEVSTATE_RINGING notification.
Caller ID
In the United States using POTS, caller ID information is sent by TelCo between the first and second ring. If the TSP supports it, caller ID information is reported by a LINE_CALLINFO notification when the information becomes available. When an application receives this notification, it can call lineGetCallInfo to obtain the actual data.
Answering Offered Calls
When an application receives a call offering, it can answer it by issuing a lineAnswer call. As with outbound calls, after a successful API function return, the call state should transition to LINECALLSTATE_CONNECTED, indicating that the call is established.
When a Connection is Made
The sample code really doesn't do much once the call is connected. It simply reports status changes if they occur. Here are some hints on where to go from here. In most cases, once the connection is made it is no longer in TAPI's domain until something happens on the line that requires call control action. The media stream itself is handled by the APIs that are appropriate for the media of interest.
Data Call
If the application needs to make a data connection, then you will need to obtain a 32-bit communications handle. A call to the API function lineGetID with "comm/datamodem" as the device class will give the application the Win32 communications handle it needs. This handle is very similar to what you would get by opening the COM port using the CreateFile API call. By default, the handle is opened for overlapped I/O operation.
UNIMODEM duplicates the communications handle it owns when an application calls lineGetID. When the line is closed, UNIMODEM closes the original communications handle, but doesn't close the handles owned by the application. If an application obtained a communications handle by calling lineGetID, the application is responsible for closing the handle once it's done with it. Failure to do so will cause the handle to remain opened for the duration of the process instance, and UNIMODEM will not be able to make any subsequent calls or receive any notifications.
Voice Calls
On LINEMEDIAMODE_AUTOMATEDVOICE calls, the wave API is used to play back or record voice signals on the phone line. Applications must call lineGetID using the device class wave/in or wave/out to obtain a handle to a wave audio device. Many sound drivers (including the SERWAVE driver that ships with UNIMODEM/V) are semi-duplex, so the same device cannot be opened simultaneously for both input and output.
DTMF and Tones
While TAPI doesn't provide much in terms of media control, it does provide methods to monitor telephony-specific media activities. DTMF (Dual Tone Multi Frequency) and tone functions fall in this category. These functions, if supported by the underlying hardware and TSP, allow TAPI applications to generate DTMF digits and tones. The term "DTMF digits" refers to specific tones generated by the digit buttons on touch-tone telephones. "Tones" here refers to specific sounds indicating line conditions, such as a dial tone or busy signal. Similarly, applications can monitor tones or gather digits if the underlying hardware and TSP is capable.
Ending a Call
A call can be dropped in one of two ways: by the remote end or by the application via an API call. This is a fancy way of saying either you hang up or I hang up. TAPI applications hang up by calling lineDrop. As with lineMakeCall, calling lineDrop doesn't mean that the call is immediately hung up. The call to lineDrop simply initiates the process. Depending on the TSP and how the call is set up, the call might go through several call states before settling down to LINECALLSTATE_IDLE, which indicates that the call no longer exists on the line. If a call is dropped by the other side or by divine intervention, the TSP will signal LINECALLSTATE_IDLE when the call no longer exists on the line.
LINECALLSTATE_IDLE is a terminal call state; it cannot transition to any other. Consider it the "corpse" of a call. Once this state is reached, call control API functions will not work on the call, but information pertaining to the call will be available until the call is deallocated.
Cleanup
In general, it's good practice to clean up when handling the LINECALLSTATE_IDLE notification. This is a convenient place for cleanup because the notification will be received no matter how the call was dropped. It is also a safe place for cleanup since no action can be taken on the call that may change the call state. Whenever possible, avoid doing cleanup when calling lineDrop. Cleanup at this stage may cause deallocation of the call, or the closing of handles that are in use. If no more calls are to be placed on the line, the application may elect to close the line.
After all the telephony resources are deallocated, the TAPI session can be terminated by a call to lineShutdown. As a general guideline, an application should keep its
calls to lineInitializeEx and lineShutdown to a minimum. These are costly API calls. On each call to lineInitializeEx, every TSP is loaded and initialized. Depending on the
TSP, this can be a lengthy process. Thus, it's a good idea to retain the TAPI session until the application is completely done with it.
What Next?
That wasn't too bad, was it? The good news is that I have covered most of the basic programming concepts in TAPI. It's true that TAPI programming is quite a bit different from conventional Win32 communication. These models, however, are functional and efficient, and do an excellent job of providing consistent and flexible programming methods. The telecommunications industry is notoriously diverse and proprietary, with few accepted programming standards. While TAPI is far from perfect, it provides what it set out to provide: a device-independent programming environment for both ISVs and IHVs.
The bad news is that I've only scratched the surface of a very rich API set. I have only touched on phone devices, and I didn't mention any of the more advanced control functions like conferencing and handoff. TAPI 2.0 introduced a call agent and proxy functionality that I have yet to mention. Then there is the topic of TSP development, which I haven't discussed at all. No need to worry, though. The hard part is over. All of these advanced features are built on top of the same basic concepts I have already examined.
In closing, I would like to mention some resources and publications that are helpful in developing TAPI applications or TSPs. The Platform SDK is an indispensable resource. While Developer Studio and Microsoft Visual C++® ship with headers and libraries that are pulled from the SDK, they lack some extremely useful samples and utilities. The Platform SDK is available from the Microsoft Web site or through an MSDN subscription. The Platform SDK is updated more frequently than Microsoft's developer products. New information and fixes are first included on the SDK, and then propagated to developer products.
Platform SDK Resources
In the Platform SDK documentation, TAPI is now grouped under "Networking and Distributed Services" rather than "Files and I/O Systems" where it was previously. This reflects TAPI's new positioning as a strategic networking component in future Microsoft operating systems. The documentation includes a fairly detailed overview of each component. If you are new to TAPI, it is worth going through each section.
The SDK ships with two development tools that are extremely useful in working with and diagnosing TAPI applications. TB (TAPI Browser) lets you issue individual API calls without actually having to write code. API calls are presented in a list format, and parameters can be specified individually. The return codes and event notifications are presented in another window. TB is useful in "prototyping" an application because it can simulate how TAPI functions will work. It can also be used as a monitoring application to aid debugging. ESP (Economical Service Provider) is the TSP counterpart of TB, and simulates a TSP. You can create calls and various line events using ESP.
Samples
The SDK ships with several sample programs. If you are new to TAPI, I recommend taking a look at these and playing with them. TAPICOMM demonstrates the creation of an outbound datamodem call. It's similar in functionality to the TTY sample, using TAPI to create the outbound call and emulating TTY once connected. Dialer is similar to the Phone Dialer applet that comes with Windows. Dialer creates outbound voice calls and logs line activity if requested. ATSP32 is a skeleton TSP sample program that demonstrates the minimum requirements for a functional TSP. This is a good starting point for writing a TAPI 2.0 service provider. ACD demonstrates the new call agent capability of TAPI 2.0.
From the April 1998 issue of Microsoft Systems Journal..
|