VAStGoodies.com
Your VA Smalltalk OSS repository. Powered by
with
.
Configuration Maps
|
Projects
|
Tags
|
Developers
|
Statistics
|
RSS
|
Disclaimer
|
About
|
Help
Configuration Maps Browser
Configuration Map Names
Abt Image Startup
AbtOleEdit
AbxProcessPeek
Announcements Core
Announcements Demos Seaside
Announcements Tests
Announcements Tools VA
Cloudfork
Cloudfork ActiveItem
CloudforkAWS
CloudforkAWSWithTests
CloudforkSSO
CloudforkSSOWithTests
CodeManagement
ComputerTelephony
Compuware SplitterWidget
CounterMap
DhbNumerics
DhbNumericsWithTests
Flotr Core
FogBugz Support
GemStoneCodeManagement
GemStoneCodeManagement Mods
Glorp
GlorpTest
GlorpVAPortMap
Goodies - UML Designer
GreaseVASTExtensions
Highcharts Seaside Addon
HQA Automated Build Support
HQA Code Snapshot Tool
InstallService
JNIPort
JNIPort Tests
JokXTEAEncryption
JQuery-plugin Autocomplete
JQuery-plugin Bind
JQuery-plugin QTip
JQuery-plugin Radiobutton and Checkbox
JQuery-plugin Table Sorter
JQuery-plugin Table Sorter Example
JQuery/JQueryUI
JQuery/JQueryUI Tests
JQueryMobile
JQueryMobile Tests
JQueryWidgetBox Core
JQueryWidgetBox Dynatree
JQueryWidgetBox Examples
JQueryWidgetBox Portability Extentions
JQueryWidgetBox Tests
JSON
KscCase
KscDynamicSortBlock
KscEMail
KscInspectWindow
KscLibUsb
KscMZZipExtensions
KscShellNotifyIcon
KscViewExtensions
KscWeather
KscX10Base
KscX10LibUSB
KscX10LibUsbGui
LibGit
Magritte
Magritte Seaside
Magritte Tests
MagritteMagic
Mastering ENVY/Developer Refactoring Browser Extensions
Mastering ENVY/Developer Script Manager
MessagePack
MessagePack Tests
MetaTest Browser
MethodPragmas
MethodWrappers
MethodWrappers Tests
MethodWrappersBase
MFNLSManagement
MiniSMock
MinneStoreDB
Modelling
Monticello Importer - Beta
MQTT-Paho
MQTT-Paho Tests
MSKAdditionalColorSupport
MSKCLDTPrimitivesExtension
MSKCLDTPrimitivesExtensionWithTestsAndResources
MSKClick
MSKCouchDB
MSKCouchDBWithTests
MSKCurlInterface
MSKDyBase
MSKDyBaseWithTests
MSKECLWrapper
MSKECLWrapperTestsAndResources
MSKFFLLInterface
MSKFischertechnikInterface
MSKFreeImageUI
MSKFreeImageWrapper
MSKFreeImageWrapperWithTests
MSKGeneralDatabase
MSKGhostscriptInterface
MSKGLibWrapper
MSKGLibWrapperWithTests
MSKICU
MSKICUAndTests
MSKICXDLibHaruDM
MSKICXDTNetStringsDM
MSKICXDTNetStringsTests
MSKJSONWrapper
MSKJSONWrapperWithTests
MSKLibHaruInterface
MSKLogging
MSKLua
MSKLuaWithTests
MSKMdlPlainRuntime
MSKMonoInterface
MSKMSAgentWrapper
MSKOleAdditions
MSKOOBaseDLLWrapper
MSKOOoAutoGenerated
MSKOOoBaseLibrary
MSKOOoCodeGenerator
MSKOOoExamplesAndIDE
MSKOOoSmalltalkExtensions
MSKOpenCLSupport
MSKOpenGLSupport
MSKOpenGLSupport Core
MSKOpenGLSupport Examples
MSKOpenGLSupport Examples FreeImage
MSKOpenGLSupport GUI
MSKOpenGLSupport Runtime Loading
MSKOpenGLSupport Structures
MSKPlatformExtension
MSKPostgreSQL
MSKPostgreSQLAbtDBMLayer
MSKPostgreSQLAbtDBMLayerWithTests
MSKPostgreSQLWithTests
MSKProcessViewer
MSKRaphaelExtensions
MSKRemoteCommandTools
MSKResourceManagement
MSKResourceManagementRuntime
MSKRexxSupport
MSKRexxSupportWithTests
MSKScintillaWrapper
MSKSDL
MSKSDLWithTests
MSKSeasideNLSExample
MSKSeasideNLSExtension
MSKSeasideSimpleREST
MSKSnarlInterface
MSKSQLite
MSKSQLiteAbtDBMLayer
MSKSQLiteGlorpSupport
MSKSQLiteWithTests
MSKSyslog
MSKSystemExtension
MSKSystemGraphicsExtension
MSKTestModel
MSKTreBinding
MSKTreBindingWithTests
MSKUDPMulticast
MSKUREWrapper
MSKVectorExtension
MSKVectorExtensionsWithTests
MSKZLibWrapper
MSKZLibWrapperWithTests
Mustache
NeoCSV
NeoJSON
Obdobion, EMan - Developer
Obdobion, EMan - Distribution
Obdobion, EMan - Examples
Obdobion, EMan - Runtime
OGLogger
Philemon Event Support
Philemon Melissa
Philemon TaggedData
Philemon Tools
Pier
Pier Add-ons
Pier Seaside
Pier Seaside Tests
Pier SIXX Persistence
Pier Tests
PUMRESTRuntime
QuotedPrintableCoderRun
QuotedPrintableCoderWithTests
Raphaël - JavaScript Library
Raphael - JavaScript Library Examples
RaspberryHardwareInterfaceCore
RaspberryHardwareInterfaceTest
RaspberryHardwareInterfaceViaDaemonTest
Reef
Refactoring Browser
Refactoring Browser Model
RefactoryTesting
Regex
Roassal
Roassal-Core
Seaside Core
Seaside GoogleChart
Seaside REST
Seaside REST Tests
SeasideGoogleMapsV3
SeasideGoogleMapsV3Examples
SimGeohash
Simple GBS Temperature Sensor Example
Simple Improvements
SIXX
SIXX Tests
SpsPdfLib
SpsPdfLib Development
StOMP
StOMP Tests
STON
StsBrowsersWin w/Code Completion
SUnit
SUnit Browser
SUnit Browser - Packagable
SUnit Tests
SWFObject2 Seaside Addon
SWFObject2 Seaside Addon Demo
Test Browser
Toothpick
TwitterBoostrapForSeaside
TwitterBootstrapForSeasideExamples
TwitterBootstrapForSeasideTests
USAstronomicalApi
VA Assist Pro for TrailBlazer
VAStGoodies.com Tools
VBRegex
WinCrypt
WinCryptWithTests
WinHttpClient
WinHttpClientWebServiceSupport
WinHttpClientWebServiceSupportWithTests
WinHttpClientWithTests
z.ST: Database, PostgreSQL-Base
z.ST: SUnit
z.ST: SUnit Testing
Zstandard
Zstandard Tests
Versions
Download
1.3.1
Applications
ComputerTelephony 1.3
Config. Expressions
Required Maps
Find
Notes
The code delivered here is not the original code. I changed the original 3.0 based code to work within VA 4.0x and upwards. Following are some texts from Guy Marentette, the author of this library wrapper. I got this code from the Smalltalk archive in the US ... School of Computer Science Carleton University Honours Project Title: Computer Telephony Using Smalltalk Student: Guy Marentette Supervisor: <name suppressed>, Assistant Professor, School of Computer Science, Carleton University November, 1997 Copyright © 1997, 1998 Guy Marentette INTRODUCTION 1 COMPUTER TELEPHONY - AN OVERVIEW 1 SOME IMPORTANT COMPUTER TELEPHONY APPLICATIONS 1 INTERACTIVE VOICE RESPONSE (IVR) 1 CALL CENTER 1 TELEMARKETING 1 CREDIT/DEBIT CARD TRANSACTIONS 1 INTERNET (IP) TELEPHONY 1 UN-PBX 1 UNIFIED MESSAGING SYSTEM 1 CHOOSING AN APPLICATION PROGRAMMING INTERFACE (API) 1 SOME KEY CHARACTERISTICS OF TAPI 1 REQUIRES DEVICE DRIVERS 1 SUPPORT FOR MULTIPLE VERSIONS 1 MULTIPLE SUPPORT LEVELS 1 CALL MEDIA SUPPORT 1 LARGE SIZE 1 MORE INFORMATION 1 INTERFACING VISUALAGE SMALLTALK TO THE TELEPHONY APIS 1 CALLING EXTERNAL FUNCTIONS 1 FUNCTION DEFINITIONS 1 CALLING THE FUNCTIONS 1 PROCESSING THE RETURN CODE 1 PASSING PARAMETERS 1 STORING AND ACCESSING CONSTANT DEFINITIONS 1 DEFINING AND USING STRUCTURES 1 ALLOCATING, REFERENCING, AND FREEING A STRUCTURE 1 ACCOMMODATING VARIABLE-LENGTH STRUCTURES 1 ACCOMMODATING STRUCTURE DEFINITIONS FOR MULTIPLE API VERSIONS 1 RETRIEVING AND SETTING STRUCTURE ELEMENT VALUES 1 INTEGERS 1 STRINGS 1 STRUCTURES 1 UNIONS 1 AVOIDING MEMORY LEAKS 1 FINALIZATION 1 THE APPLICATION INTERFACE LAYER 1 TAPI SUPPORT 1 CALLING API FUNCTIONS - GETTING THE ORDER RIGHT 1 HANDLING CALLBACKS 1 ACCESSING STRUCTURE DATA 1 HANDLING WAVE MEDIA 1 FAX SUPPORT 1 DEMONSTRATION APPLICATION 1 TEST RESULTS 1 CONCLUSION 1 Introduction This project is a learning exercise for me in several ways. I have used Smalltalk for many years now for academic assignments and a couple of custom applications developed for my current employer. But, for me, recent opportunities to use Smalltalk have been few and far between, and the dialect I had used, Digitalk's Smalltalk V for Windows (now known as Visual Smalltalk), has been effectively abandoned by its vendor. I selected IBM's VisualAge Smalltalk because it offered me the opportunity to learn this new dialect that appears to be taking over as the commercial Smalltalk application development tool of choice. I still consider Smalltalk to be the most powerful development tool I have ever used, and given a free choice, would not hesitate to use it for most custom application development. In my experience, it is particularly productive whan used on large, complex applications. I have never been employed in the telecommunications industry, so choosing Computer Telephony as my project topic has forced me to gain at least a rudimentary knowledge of the technology and terminology of telephones. For example, it gave me a better appreciation of the complex chain of events involved in the seemingly simple task of picking up the phone and placing a call. Computer Telephony combines general purpose computers and purpose-built telephone equipment to provide new and/or improved communication services. It generally consists of interface hardware installed in the computer to connect to the telephone service and accompanying software to allow an application to communicate with the hardware so that it can provide some telephone service in a user-friendly and cost-effective way. Most often, the support software for telephony devices is device-specific and low-level, i.e. aimed at C language programmers. This presents a barrier for most Smalltalk developers who wish to implement a telephony-enabled application. Most Smalltalk development environments provide interfaces for low-level C-type functions, but Smalltalk programmers generally prefer to work at a higher level. My main goal in this project is to fill that gap by developing Smalltalk classes which provide a relatively simple interface to telephony services. To the best of my knowledge, there is no commercial or freeware Computer Telephony interface available for any dialect of Smalltalk. The second section of this document is an overview of Computer Telephony (CT) aimed at readers who are not involved in the communications industry. It defines some of the common terms used in this industry which are relevant to CT and provides several examples of applications which benefit from the union of computers and telephony. As explained earlier, a computer telephony application generally communicates with telephone equipment via a software interface. This interface is usually referred to as an application program interface, or API. Some equipment also provides control functions using commands embedded in the information stream. Application developers can always interface with the equipment using the API mechanism or embedded commands supported by the individual hardware vendors. But some computer platforms offer the alternative of one or more general-purpose APIs which offers the potential of device-independent computer telephony. For this project, I chose to use the Telephony API (TAPI) available in Microsoft Windows 95 and Windows NT 4.0. The third section discusses the options I considered and the reasons for my choice. It then describes some of the more important characteristics of TAPI. I chose to do this project using IBM VisualAge Smalltalk. This development tool provides comprehensive support for communicating with APIs, and the documentation does go into some detail about these facilities. But completing this project also required knowledge about C programming and how it relates to the API interface classes. It also requires careful reading of the specification available from Microsoft. Much of the design is consistent, but occasionally one encounters a variation that must be treated as a special case. The fourth section will describe the low level implementation TAPI and MAPI. It is hoped that this will help others wanting to extend this interface or develop an interface to another API. Smalltalk development is most effective when the developer can interact with high-level abstractions rather than low-level API wrappers. Accordingly, my project does provide some high-level classes that mask the underlying complexity of TAPI and MAPI from the typical application developer. Due to time and resource constraints, this area will need some additional development if someone wishes to use this CT interface in a serious application. But the limited implementation provided in the code and documented in the fifth section should provide a model for future extensions. Finally, a good way of demonstrating how to use an interface class is to build sample applications. These applications also test the interface and can drive the design process for the high-level portion of the interface. The sixth section of this document describes the sample applications developed for the CT interface. Computer Telephony - An Overview The telephone network has used computer technology in the form of digital switches in central offices (COs) and digital PBXs (Private Branch Exchanges) in large-to-medium size companies. But this is not what the telecommunication industry means when referring to Computer Telephony (CT) and Computer Telephony Integration (CTI). Digital switches and PBXs are now relatively mature, having been around for over two decades, and are purpose built to perform their functions. CT/CTI, on the other hand, uses general-purpose computers equipped with plug-in hardware and software to work with or replace existing telephone equipment, providing better service and more efficient use of resources (equipment and people). The deployment of CT/CTI is very much tied to the availability of general purpose computers. Until desktop computers became commonplace recently, CT was generally restricted to telephony-intensive applications such as Call Centers and Interactive Voice Response (IVR) systems (see below for descriptions). Now, CT is also becoming available and cost-effective for casual telephone users in the form of client-server and workstation hardware and software. In the research literature for this project, some authors use the terms Computer Telephony and Computer Telephony Integration interchangeably while others are careful to make a distinction. However, I have not yet come across definitions which clearly differentiate these two terms. But based on my readings, I believe the following might be reasonably close: Computer Telephony Integration uses general-purpose computers to interact with existing telephone equipment and services Computer Telephony replaces purpose-built telephone equipment with general-purpose computers to provide equivalent or better service CT, as defined above, offers the potential benefits of lower cost as the price of computers continues to plummet and scalability in the sense that additional capacity can be added through addition of interface boards and/or processor upgrades. Both CT and CTI can offer easier operation and administration through the use of graphic user interfaces (GUIs). But the above is mainly from a telephony perspective. There is the other side of the picture, i.e. how the availability of CT equipment and software with the ability to digitize voice, touch-tone entries and faxes allows developers to telephony-enable applications. For example, phone conversations and faxes can be stored and retrieved just like any other database data. Microsoft's decision to incorporate CT support in their latest operating systems (Windows 95 and Windows NT 4 -- more on this later in this document) is, I believe, a clear signal from them that the time has come for applications developers to think of the telephone as just another extension of the PC. If you would believe the editorialists, columnists and contributing authors to such periodicals as Computer Telephony Magazine , CT is more than technology. It is a booming industry with almost unlimited growth potential. This might be a somewhat biased opinion on their part, but a typical monthly issue of this periodical has literally hundreds of ads and product announcements for CT-related hardware (predominantly) and software (to a much lesser degree). So even if CT is not yet in the "mainstream", there definitely is some basis for excitement about its future. The following section is by no means an exhaustive survey of CT/CTI applications. For those who are interested in this aspect, I recommend the following books at least as a starting point: CTI in Action Rob Walters et al John Wiley & Sons Ltd. Secrets of Windows Telephony Edited by Edwin Margulies Flatiron Publishing Inc. ISBN 1-57820-002-4 Some Important Computer Telephony Applications Interactive Voice Response (IVR) An IVR system allows a user to set up voice menus which allow callers to choose services by typically responding with touch-tone entries on a telephone keypad. Advanced IVR systems can offer voice-recognition as an alternative entry mechanism for callers who cannot respond with touch-tone. An example of an IVR system familiar to Carleton University students is the touch-tone course registration system. IVR lends itself to applications where the choice of services are finite and limited and where the information exchange is moderate. They can range from the previously mentioned course registration system to bank pay-by-phone and transit system schedule request systems etc. Given that IVR is what I term telephone intensive, it has been economically viable for many years now. Until recently, IVR systems were generally turn-key and implemented on dedicated computers. According to Margulies et al , IVR is moving away from proprietary systems to PC-based telephony servers. It should be obvious that CT does not just enhance IVR; it is the key enabling technology. In many applications, caller requests require database lookups and some caller data is used to update database information. Call Center The Call Center is generally a collection of human agents gathered in a room (or rooms or even buildings for large call centers) to respond to incoming calls from customers or clients. A call center may be set up to handle such things as sales orders or customer queries, e.g. "How can I include a pie-chart in my Word document ...". For some businesses such as direct-marketers, the Call Center is their life-blood. These businesses were served reasonably well by PBXs, but many have been early adapters of CTI technology because of the additional functionality it provides. For example, IVR could be used to initially screen the call, allowing the caller to be routed to the appropriate agent. Also, the name or phone number from caller-id or ANI can be used to retrieve the record(s) for a repeat customer and display it on a computer screen even before the agent picks up the call. Telemarketing Telemarketing is the mirror-image of Call Centers. Major telemarketing firms usually employ rooms full of agents who place unsolicited calls to "sales prospects", generally chosen at random from the phone book. Anyone who owns a phone is all too familiar with the calls that usually interrupt supper. Technically advanced telemarketers use CT/CTI to select call candidates and to place the call. A relatively new addition is predictive dialing where the computer system monitors the average length of each call. Using this average and the number of agents on duty, the system predicts when the next agent will be free. It initiates a call in advance of this time with the expectation that an agent will be available when the "prospect" answers. If an agent is not available, the system tries to keep the prospect on the line with a recorded message. A well-engineered predictive dialer can also screen out undesirable calls such as busy-signals, no-answers and answering machines (which can be detected by the cadence of the answer). As one might imagine, predictive dialers make more efficient use of the agents. Their time is not spent waiting for answers, especially those which never happen. Credit/Debit Card Transactions Even smaller merchants who accept payment via credit or debit cards have become equipped with phone terminal devices which are used to enter transactions using key pads and magnetic strip readers. The total automation of this process on the financial institutions side means significant staff savings when compared to the old validation call centers, not to mention the reduction in paper movement and processing with the old multi-carbon-copy system. Internet (IP) Telephony The Internet, as it is now, can be used to interchange data over long distances at essentially no cost to the user. At most, the customer may need an Internet Service Provider (ISP) who, for a nominal connection charge, will provide dial-up connection to the Internet. Hardware and software is now available to exploit this situation by allowing long distance phone calls without paying LD fees to the phone company or long distance service provider. IP Telephony digitizes voice or fax information into packets which can be sent over the Internet just like e-mail and news articles. The cost saving potential of IP telephony is certainly attractive for heavy LD user who can recover the capital outlay for the equipment and software in a short time. But another potential benefit is avoiding the telecom industry bureaucracy. As David Isenberg notes, every new feature and service offered by traditional dial-tone providers only comes after extensive cost-benefit analysis. Also, changing central switch programs to accommodate these new services is a lengthy and painful process given the typical goal of less than one failure in 10 years or some such. Users will have a wider choice of software and hardware for IP telephony, and this competition will spur development of innovative features and services. Un-PBX To make IP Telephony as convenient as traditional telephone usage, there should be a server which provides the switchless equivalent to the switching services of a traditional PBX. Such servers have been dubbed Un-PBXs by industry expert Ed Margulies. But the use of this term has been extended to encompass general-purpose computers equipped with hardware and software which allows them to replace purpose-built PBXs to interface with the Public Switched Telephone Network (PSTN). The latter type of Un-PBX is now becoming available from both new start-ups and well-established traditional PBX suppliers such as Mitel. Mitel have just recently announced the rollout of MediaPath which is an Un-PBX running on a DEC Alpha or Intel x86 server running Windows NT server. Other vendors include Altigen with their AltiServ server, Interactive Intelligence who offer a product they call Enterprise Interaction Center (EIC), and Nexpath who make Nexpath Telephony Server. A big advantage of purchasing an Un-PBX is being able to shop around for the best value for the hardware and software platforms vs. the very limited choices offered with a PBX. Un-PBXs also offer better potential for scaling up as additional capacity is required. PBXs generally require a "fork-lift upgrade" i.e. a total replacement with a larger model. Un-PBXs also offer both the customer and the vendor the opportunity to take advantage of cheaper components and new user interfaces as they become available. Purpose-built PBXs are generally "locked-in" to the technology available at the time of their initial design. Unified Messaging System Where I work, electronic mail messages can be retrieved from my computer by consulting the list of waiting messages and retrieving them in my preferred order. Provided I have available space on my local hard drive or server, I can choose to keep as many of the messages if I want to be able to go back later. On the other hand, notification of waiting voice mail is a blinking light on my telephone. Retrieving the messages requires calling a special access number and traversing a menu in the voice mail IVR. Even then, messages can only be retrieved in the order they arrived. I can save messages if I wish, but the storage space is limited and I can only retrieve stored messages in the order they were originally stored. These two factors tend to limit the usefulness of message storage. If I am expecting a fax, I must walk up one floor and look at the in-basket for the fax machine shared by 200 users. If the sender is slow in sending a fax, I might need to make several such trips before I get it. Unexpected faxes might sit in the in-basket for several days before I finally get to see them. Also, the faxes are on paper and must be scanned for electronic storage. Unified Messaging Systems provide a single application running on a desktop workstation which allows the user to access incoming e-mail, voice mail and faxes from a single list. They allow the user to decide the access order and provide better storage potential. An example of a Unified Messaging Service is Microsoft Exchange. Choosing an Application Programming Interface (API) To develop a telephony application, one's program must be able to communicate with and control the interface hardware connected to the telephone network. Among the programmatic alternatives to accomplish this are: 1. Communicating directly with the hardware using ports, DMA, hardware interrupts etc. This type of programming is not generally doable by typical application developers. 2. Using API calls to the operating system or driver software provided by the hardware vendor. This alternative is viable for turn-key systems but is slowly losing favor. 3. Using a general purpose API designed to interface with a general class of telephony equipment. Familiar examples of option 2 are communication programs for consumer-grade modems supporting the "standard" Hayes AT commands. These typically use OS API functions to establish communication with the modem. In-stream commands interspersed with the data control the operation of the modem. The main problem with this approach is that the "standard" AT command set is anything but standard. The command sets for modem models available from a wide choice of vendors have tended to diverge more and more with the introduction of each new feature. A shrink-wrap communications program supporting multiple modem models will rapidly become obsolete if upgrades are not made available periodically. Developers of word processing software for IBM PCs and compatibles were faced with a similar problem regarding printers until the advent of Microsoft Windows and its standard printer API. Veteran Word Perfect for DOS users will remember the hundreds of print drivers that shipped with WP, and the challenge of acquiring a driver for your printer if it wasn't included in the set. Windows word processors no longer need to supply print drivers. The printer's manufacturer is responsible for providing a general-purpose driver which can be installed on a Windows workstation or server and which works with any Windows application. After a bit of a shaky start, this concept of vendor-supplied drivers is working reasonably well, and has been extended to other peripherals such as video display boards, sound boards etc. With the bundling of the Telephony API (TAPI) in Windows 95 and Windows NT V4, Microsoft have extended the concept to Computer Telephony. But TAPI is not the only available general-purpose telephony API. Novell and AT&T (now Lucent) jointly developed the competing Telephony Services API (TSAPI). TSAPI runs as a service on a Novell NetWare server with applications typically accessing the services from desktop workstations. NetWare has, until recently, been the dominant PC network OS, and for NetWare users, TSAPI is a natural choice. I chose TAPI for my project. For me, the main reasons were availability, availability and availability: " TAPI comes bundled with Windows 95 and Windows NT V4, which are relatively easy and inexpensive to acquire. " The detailed specifications required to complete this project are readily available from several sources, some of which are free or nominally priced. " TAPI development can be done using consumer-grade equipment (e.g. voice modems) and affordable residential telephone services. Some Key Characteristics of TAPI Requires Device Drivers In order to use TAPI for a given Telephony hardware device, there must be driver software which maps the general-purpose TAPI functions to the hardware's API or control characters. Microsoft calls these drivers Service Provider Interfaces or SPIs. As is the case for most hardware driver software, Microsoft expects the telephony hardware vendor to develop the SPIs. They do at least provide an SPI software development kit to assist in this effort. Support for Multiple Versions Microsoft have released four versions of TAPI so far. Version 1.3 was available for download from Microsoft's FTP site, and was a 16-bit version intended for use with Windows 3.1. It provided basic client telephony functionality, but wasn't widely supported by telephony hardware vendors. Version 1.4 of TAPI was bundled with Windows 95 along with a universal modem SPI called unimodem. Unimodem supports data and fax operations for the vast majority of consumer grade modems, but requires a setup file from each modem vendor which maps each modem's AT command to the operations supported by Unimodem. Within a year of Windows 95's release, Microsoft developed and released an upgrade to Unimodem called Unimodem/V. Unimodem/V is available by free download from Microsoft, and adds support for newer modem features such as voice, caller ID and distinctive ring. Version 1.4 is basically an enhanced version of 1.3 in that it still only supports basic client functionality. Version 2.0, bundled with Windows NT 4.0 is a significant upgrade to TAPI, adding support for server telephony functionality. TAPI V2 can potentially be used to develop an Un-PBX given availability of suitable hardware and an associated SPI. Although TAPI V2.0 provides client and server telephony support, the application must rely on other mechanisms such as RPCs to allow a client workstation to access a telephony service on a remote server. TAPI version 2.1, which has become available via free download from Microsoft, is designed to run on both Windows 95 and Windows NT, and is reported to include support for direct intercommunication between telephony clients and servers. I have not had a chance to look at this new version to date, so cannot comment on this claim. Microsoft also claims that an application developed for an earlier version of TAPI should work with no change, not even a recompile, on a later version. This is another claim I have not been able to test as yet. They also strongly recommend that new TAPI applications should be developed to support older versions as well as the latest in order to give them a wider market appeal. The TAPI interface I developed for this project is designed to support TAPI V1.4 and TAPI V2.0. I will discuss just how this dual version support is provided in the next section of this document. When an application is running on a 32-bit platform such as Windows 95 or Windows NT, TAPI can support the full capabilities of that platform. This includes symmetrical multi-processing, multi-threading, preemptive multi-tasking, and Unicode . Multiple Support Levels TAPI provides four levels of support to applications and Service Providers: 1. Assisted Telephony consists of a small set of functions which support the establishment of calls, i.e. basically a phone dialer level of functionality. 2. Basic Telephony Services adds support for pickup of inbound calls. This is the minimum level that all TAPI SPIs must support, so an application developed to this level should work with any TAPI-compliant equipment or software. 3. Supplementary Telephony Services adds the bulk of TAPI functionality. They are optional in the sense that an SPI or application may choose to implement only part or even none of it. The list of facilities supported at this level includes: " Call hold, transfer, forward, and park " Conference call support " Call pickup and completion " Digit and tone monitoring and generation " Media monitoring and media stream routing and control 4. Extended Telephony Services allow a service provider to extend TAPI as needed to support hardware or software features not built into the three levels above. Call Media Support One would expect that support for Computer Telephony would extend to dealing with the content of the call, particularly if it is data related. But surprisingly, TAPI provides virtually no such support. Once a call connection is established, dealing with the call content requires going outside TAPI. Consider the simplest example. A call comes to the phone at your desk and you are there to answer it. TAPI might have been involved in getting the call to your desk, but once there, you just want to pick up the call yourself. TAPI involvement is no longer needed or wanted. Now consider a similar scenario, but you aren't at your desk to pick up the call. In this case, you want the computer to play a message to the caller and then record a response. Surely one would expect TAPI to support this, but in fact Microsoft specifies that playing and recording of digitized messages on telephone devices is virtually identical to playing sounds on a sound card. Accordingly, an application must use the multimedia extensions of the Windows operating system for this purpose. Similarly, a TAPI application is not expected to directly support sending and receiving of faxes. Microsoft has added extensions to MAPI to support faxes. In effect, they consider faxes to be akin to e-mail. Large Size Despite the lack of direct support for call media, TAPI is still quite large. For versions 1.4 and 2.0 combined, there are 144 unique functions and 58 C-type structures varying from small (2-4 elements) to large (>50 elements). Most of the structure content is fairly straightforward and consistent, but a few have complex designs and require special treatment to insert and extract data elements. The TAPI definition also includes several hundred constants which define control values passed to and from functions, error return codes from functions, and message identifiers for callback messages from TAPI to the application. These constant definitions must be transformed into the Smalltalk equivalent. More on this in the next section of this document. More Information A complete description of TAPI is beyond the scope of this paper. For more information, I would recommend the following: Secrets of Windows Telephony Edited by Edwin Margulies Flatiron Publishing Inc. 12 west 21 St. New York NY 10010 ISBN 1-57820-002-4 Microsoft Developers Network Software Developers Kit MSDN, including the TAPI application and SPI SDKs, is available in several levels on CD-ROM via subscription from Microsoft. The TAPI SDK documentation is also available for free on Microsoft's web site (www.microsoft.com). Communication Programming for Windows 95 Charles A. Mirho and Andre Terrisse Microsoft Press ISBN 1-55615-668-5 Interfacing VisualAge Smalltalk to the Telephony APIs The detailed specifications for Microsoft APIs assume the developer is using C, C++, or Visual Basic. The provided sample code is most often written in C. Microsoft systematically use what they term Hungarian Notation in the specifications and code fragments. I would strongly suggest that anyone attempting to develop an API interface to any Smalltalk dialect will need some C programming experience and should be familiar with the general architecture of Windows. The programmer must be comfortable with heap memory allocation, pointer definition, assignment and dereferencing, resource handles, and C structures and unions. Of course, the detailed specifications for the target API are also required. Specifications for all functions, structures and constants used in this project are available directly from Microsoft through their various Developer Network programs. An alternate recommended source is: Windows 95 Multimedia & ODBC API Bible Richard J. Simon et al Waite Group Press ISBN 1-57169-011-5 Because the detailed documentation for the APIs used in this project is quite lengthy, I have not reproduced sections of it that correspond to my Smalltalk code examples. I expect that the reader of this document will be able to reference one of the sources above. Chapter 11 of the IBM Smalltalk Programmer's Reference Manual, provided as part of VisualAge Version 4.02, includes a fairly complete and detailed definition of the supporting features provided to interface with an API. But it didn't clearly cover every situation I encountered in completing this project, and doesn't provide many comprehensive examples (although examples can be found in the base classes using the code browser). I hope that the following will address at least some of those deficiencies. Calling External Functions As can be seen in the VisualAge Programmer's Reference sections on external function calls, discussion of this topic can become complicated if one wishes to support such exotica as multiple threads and non-blocking calls. Fortunately, this project could be done using standard platform calls. There is no need to use such options as call futures etc., although developers of more challenging CT applications such as Un-PBXs may find these beneficial. Function Definitions An external function can be called in one of two ways: 1. You can define an instance of PlatformFunction and then call that instance using a callWith:with:... message. See the dosBeep example on pages 286-287 of the Programmer's Reference. 2. Use an inline function call using special syntax. This is described on page 288 of the Programmer's Reference, but generally looks like this: <callingConvention: returnType 'library':function paramType paramType...> When adding new functions, I chose to use the second option because it closely resembled the syntax used by other Smalltalk dialects, most notably Visual Smalltalk and Dolphin Smalltalk. Although VisualAge Smalltalk doesn't require I do so, I also created containing classes for the function calls with one instance method per function as per the requirements of Visual and Dolphin STs. This should simplify porting my interface to those other dialects. None of the TAPI or Simple MAPI functions were defined in VisualAge Smalltalk as provided by IBM. Accordingly, I added these definitions in classes TapiLibrary and MapiLibrary respectively. The functions required to read and process multimedia data (digital audio, specifically), on the other hand, were already included. VisualAge does not use the technique I chose. Instead, they define functions as inline call entries in a pool dictionary called PlatformFunctions. Finding out which functions were already defined was done by inspecting this dictionary. Calling the Functions To call one of the TAPI or MAPI functions I added, you must first create an instance of the library class as the following instance method, taken from CtiSession, demonstrates: library library isNil ifTrue:[library:=TapiLibrary new]. ^library I chose to instantiate the library object only once and store it in an instance variable rather than creating a new instance for each function call. This code also demonstrates the use of the Lazy Instantiation pattern as defined by Kent Beck , a technique which I use quite extensively throughout the code for this project. Then you invoke the method corresponding to the function you need to call. Here is an example calling the TAPI function lineShutdown to terminate a TAPI session: shutdown self treatError:(self library lineShutdown:self tapiHandle asParameter) This method is invoking the following TapiLibrary method: lineShutdown:lineApp <pascal: int32 'tapi32':lineShutdown uint32> ^self primitiveFailed and passing a single parameter which is the handle to the session to be closed. Calling a function already defined in VisualAge is different from the above and varies depending on whatever additional support is provided for the function. Here are a couple of examples taken from the WaveController class I developed: openPlayDevice:device |waveFormat devCaps devHand callback returnCode flags| devCaps:=self getPlayDeviceCaps:device. waveFormat:=self createWaveFormatStruct. devHand:=OSHwaveout calloc. callback:=EsEntryPoint receiver:self selector:#callback:msg:param1:param2: callingConvention:'c' arrayBased:false parameterTypes:#(uint32 uint32 uint32 uint32) returnType:#int32. flags:=CallbackFunction. device=WaveMapper ifTrue:[flags:=flags | WaveMapped]. returnCode:=OSCall new waveOutOpen: devHand address uDeviceID: device pwfx: waveFormat address dwCallback: callback address dwInstance: nil fdwOpen: flags. self deviceHandle:devHand. waveFormat free The bolded portion invokes a method in the class OSCall which calls the multimedia API function WaveOutOpen. This function, among other things, returns a handle to the device which the above method stores in an instance of OSHwaveout. Once this device handle is available to the application, all further function calls directed at that device are done by invoking instance methods defined in OSHwaveout. The following method from WaveController illustrates this: play (self playHeaderAt:bufferIndex) dwBufferLength=0 ifTrue:[^self stopPlay]. self deviceHandle waveOutWrite:(self playHeaderAt:bufferIndex) cbwh:OSWavehdr fixedSize The documentation for the WaveOutWrite function call shows that the call requires the device handle as the first parameter. Defining the call in a method of the wave handle instance makes passing this parameter from the application unnecessary. If we look at the waveOutWrite instance method definition in OSHwaveout: waveOutWrite: pwh cbwh: cbwh "\C equivalent: MMRESULT waveOutWrite(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh)." ^WaveOutWrite callWith: self with: pwh with: cbwh we see that the device handle parameter passed to the API function is the handle instance itself. Finding out which class supports a given function call is simply a matter of highlighting the specific function definition in the pool dictionary inspector and then selecting Browse Variable References in the Variables menu. Processing the Return Code All the API functions used in my project use the return code to signal the success or failure of the function call. The general convention is that a successful call returns a zero and a failed call returns a non-zero value which can be negative. Robust system design requires that all return codes be checked for error. Since my code is a development work in progress, my error treatment is to display a message on the system transcript. Passing Parameters As the above examples illustrate, function calls generally require passing of parameters. The number and type of parameters required depends on the function called. It is most important that the parameter values be passed from Smalltalk in the format expected by the function. It is also important that a value passed to a function or a buffer set aside to receive a result are in fixed storage which cannot be touched by Smalltalk's garbage collector. The following code example demonstrate how to pass various parameter types using a call to lineInitialize of TAPI: openForApp:app |callback handle numDevices| self callingApplication:app. "1" numDevices:=OSUInt32 calloc. "2" handle:=OSHandle calloc. "3" callback:=EsEntryPoint "4" receiver:self selector:#callback:msg:data:param1:param2:param3: callingConvention:'c' arrayBased:false parameterTypes:#(uint32 uint32 uint32 uint32 uint32 uint32) returnType:#int32. self treatError:(self library lineInitialize:handle address "5" instance:OSObject getInstanceHandle "6" callback:callback address "7" appName:self appName asParameter "8" numDevs:numDevices address). "9" self numLines:(numDevices uint32At:0). "10" self tapiHandle:(handle uint32At:0). numDevices free. handle free. According to the TAPI documentation, the first parameter required by lineInitialize is a pointer to a location where the function can deposit the handle for the TAPI session. This location is allocated in non-Smalltalk memory, what C programmers call heap space, in line "3". Line "5" gets the address of the location and passes it to the function. The second parameter required by lineInitialize is the instance handle of the client application. It took considerable detective work to find that the code in line "6" is required to retrieve that value. The third parameter required is the address of a callback function which TAPI can call to notify the application of an event. For example, when a telephone call is received, TAPI detects the call and then must inform the application so that the application may react appropriately. The EsEntryPoint instance created starting in line "4" defines the required callback function, and line "7" passes the address to lineInitialize. There will be more later in this document on dealing with the callbacks themselves. The fourth parameter must be a pointer to a null-terminated string containing the name of the application. Most Smalltalk dialects require sending the message asParameter to a standard Smalltalk string object when using it as a parameter. This transforms the string object into a null-terminated C-style string. Thus line "8'. The final parameter expected is a pointer to a location large enough to hold an unsigned integer value. lineInitialize detects the number of telephony devices installed in the system and deposits that number in the location referenced by the pointer. Line "2" reserves the space, line "9" passes the address to the function, and line "10" extracts the returned value from memory into a smalltalk object. A look at a call to lineGetDevCaps shows more parameter types: getDevCaps |caps| caps:=(CtiStructure struct:#LineDevCaps tapiVersion:self tapiVersion) allocate. "1" self library lineGetDevCaps:self tapiHandle "2" deviceId:self devID "3" apiVersion:self tapiVersion "4" extVersion:0 "5" lineDevCaps:caps address. "6" caps:=caps adjustSize. self treatError:(self session library lineGetDevCaps:self tapiHandle deviceId:self devID apiVersion:self tapiVersion extVersion:0 lineDevCaps:caps address). self devCaps:caps asObject. The first parameter required is the handle of the TAPI session obtained when the lineInitialize function was called. This handle was stored as an instance variable in the session object, and line "2" uses a get method to retrieve it and pass it to the function. Parameters two through four are 32-bit unsigned integer values which can be passed to the function using Smalltalk integers. Lines "3", "4" and "5" pass integers to the function. The fifth parameter requires a pointer to a location in memory sufficient for the function to deposit a structure of the type LineDevCaps. This location is reserved by the code in line "1", and line "6" passes the address to the function. I will have much more to say about structures later in this document. The above two examples cover the most common requirements for parameter passing. In general, you need to pass a value directly, pass a pointer to a value, or pass a pointer to a space reserved by the application so the function can pass values back. Storing and Accessing Constant Definitions The API documentation makes frequent references to constant values for such things as error return codes from function calls and status flag values which are sent to or returned from function calls. For example, the documentation for lineInitialize states that possible error return values include LINEERR_INVALAPPNAME, LINEERR_OPERATIONFAILED etc. Each of these constants has a corresponding integer value which can be obtained from the C header files provided in the SDKs and used directly in the application. But a better way is to translate the constant definitions into Smalltalk pool dictionaries. The constant definitions for the functions I added are in pool dictionaries called TapiPool and MapiPool. I kept the constant names identical to those in TAPI.H and MAPI.H. Where the integer value in the header file was specified as a hex value, say 0x00010000, I substituted the Smalltalk equivalent of 16r00010000. Negative error codes defined using hex notation, e.g. 0x80000052, required "casting" to a signed integer value before being assigned to the pool dictionary. Thus the negative error code 0x80000052 was stored as 16r80000052 asInt32. Constants for API functions already defined in VisualAge are stored in a pool dictionary called PlatformConstants. The developers of VA decided to use a "cleaned-up" version of constant names rather than the "raw" version from the specifications. Thus, where the waveOutOpen specification refers to a flag value of CALLBACK_FUNCTION, you must use CallbackFunction in your application to reference the constant value in the pool dictionary. Defining and Using Structures A C structure is a set of data elements laid out in contiguous storage. A normal Smalltalk object may also contain a set of data elements, but their location in memory is not controllable by the programmer and normally is not contiguous. Thus a Smalltalk object cannot be used directly as a C structure. The Smalltalk base class set must include facilities for defining C structures as part of its support for external function calls. VisualAge provides this support in the form of the OSStructure and OSVariableStructure classes. New structure definitions are added as subclasses to OSStructure, if they are fixed size, or OSVariableStructure for structures which have a variable-sized extension. These classes provide class and instance methods to do the following: " Allocate the memory space for the structure and free it when no longer required. " Supply the memory space's address to pass to a function " Map a structure definition to a memory location allocated by a function " Insert values into or extract values from a structure. These may be Smalltalk objects or non-smalltalk values copied from non-smalltalk (heap) memory Allocating, Referencing, and Freeing a Structure The OSStructure class defines a class variable called fixedSize which each subclass is expected to initialize to the size of the instance structures. OSVariableStructure adds a variableSize class variable. When the class message calloc is sent to a structure class, the values in these variables determine the amount of non-smalltalk memory reserved for the structure instance. The starting address of the allocated memory can be obtained by sending the address message to the structure instance. Freeing the allocated structure memory is done by sending the free message to the structure object. As discussed later in Avoiding Memory Leaks on page 1, freeing this memory is critical to assuring reliable operation of an application. Accommodating Variable-Length Structures Strings are often variable and indeterminate in length and so cannot be easily inserted in the middle of a structure without displacing other fixed-location elements. The most common solution is to put the strings somewhere else in memory and then store a reference to the location (a pointer) in the structure. The developers of TAPI decided that the best place to put variable strings is at the end of the structure. But this poses a problem when an application must allocate space for a structure and then call a function which needs more space than provided in order to return appended strings. The solution for TAPI's designers was to provide three control values at the front of such structures. Those values are: 1. dwTotalSize which should be set by the application to the actual memory allocation 2. dwNeededSize which TAPI sets to the amount of memory it needs for all the return values 3. dwUsedSize is set by TAPI to indicate the actual number of bytes of the data structure containing useful information So an application can either allocate a generous amount of extra memory and hope it is enough, or make multiple calls to the function until dwTotalSize and dwNeededSize are equal. I use the latter technique. The subset of TAPI structures that require size adjustment are defined as subclasses of CtiVariableStructure. As the following method code shows, the lineGetDevCaps function is called twice, and the structure allocation is adjusted between the calls by the highlighted code: getDevCaps |caps| caps:=(CtiStructure struct:#LineDevCaps tapiVersion:self tapiVersion) allocate. self library lineGetDevCaps:self tapiHandle deviceId:self devID apiVersion:self tapiVersion extVersion:0 lineDevCaps:caps address. caps:=caps adjustSize. self treatError:(self session library lineGetDevCaps:self tapiHandle deviceId:self devID apiVersion:self tapiVersion extVersion:0 lineDevCaps:caps address). self devCaps:caps asObject. The adjustSize instance method is implement in CtiVariableStructure and inherited by all its subclasses. It returns a new instance of the structure with the allocation area adjusted to the needed size. Accommodating Structure Definitions for Multiple API Versions TAPI presents another challenge to the application developer wishing to support more than one version. Some of the structures passed into or returned from TAPI have additional elements for Ver. 2.0 compared to Ver. 1.4. For example, the LineDevCaps structure does not return elements dwSettableDevStatus, dwDeviceClassesSize and dwDeviceClassesOffset when the application is running under TAPI 1.4, but will include them when running under TAPI 2.0. My TAPI interface accommodates this requirement by using inheritance in the structure definitions and a selection class method defined in CtiStructure to get the proper class name for the current version. LineDevCaps has two structure class definitions, namely CtiLineDevCaps and CtiLineDevCaps20, for use by TAPI 1.4 and TAPI 2.0 respectively. The current TAPI version can be obtained by calling the lineNegotiateAPIVersion function with the lowest and highest TAPI versions you are prepared to support. The function returns the version that TAPI can support on the system currently running the application. This version number along with a generic name for the structure will return the actual structure class name. The following example is taken from the getDevCaps instance method of CtiLineDevice: caps:=(CtiStructure struct:#LineDevCaps tapiVersion:self tapiVersion) allocate. My interface only supports two versions, but this technique could be used to extend support for additional versions when needed. Retrieving and Setting Structure Element Values Integers Integers are extracted from a structure using methods such as uint32At:. An example taken from CtiLineCallParams illustrates this: dwAddressID ^self uint32At:28 This method extracts a 32-bit unsigned integer value from the structure location starting at position 28 (relative to the starting location of zero) and returns it as a Smalltalk integer object. There are a whole set of integer extraction methods which cover 8, 16, and 32-bit signed and unsigned integers. Integers are inserted into a structure using methods such as uint32At:put:. Here is the set method corresponding to the get method above: dwAddressID:aValue self uint32At:28 put:aValue Because we are dealing with an integer, the parameter referenced by aValue must be a Smalltalk integer object. Of course, the value of the integer must be representable in 32 bits. Strings If the API documentation does not explicitly state that the string is null-terminated, and if the starting location and length of a string is available in the structure, the string can be copied into a Smalltalk string object by using the memcopyStringFrom:to: structure method. But I found a problem with this. Most of the strings extracted with this method had a trailing null character even if the documentation did not specify this. I remove trailing null characters, if present, in an overriding memcopyStringFrom:to: method defined in CtiStructure. Copying a Smalltalk string into a structure memory location and adding a null terminator did not appear to be supported by the VisualAge structure methods. I added an instance method string:to: to CtiStructure to do this operation. When the structure contains a pointer to a null-terminated string, for example the fileName entry in MapiFileDesc, use the address: class method of OSStringZ to create an instance with a copy of the null-terminated string, and then send this object the asString message to convert it to a String object. See the fileName method in MapiFileDesc for an example. Assigning a pointer to a null-terminated string to a structure element first requires copying a null-terminated version of a string object into non-smalltalk storage and then inserting the address into the structure at the proper location. Look at the instance methods filename: and lpszFileName in MapiFileDesc to see how to do this. Occasionally, the TAPI documentation specifies that null-terminated strings are embedded inside a structure. An example is the four elements of LineReqMakeCall. Since I was not able to find a structure method to address this case, I implemented the instance method stringFrom: in CtiStructure. There is one unusual case in LineAddressCaps, specifically the DeviceClasses element, where the specifications indicate that this string is actually a series of null-terminated device class identifiers, with the last identifier being followed by two nulls. The stringsFrom:length: instance method in CtiStructure deals with this case, returning a collection of Smalltalk strings. Structures There are a few cases in TAPI where a structure has one or more embedded structures. The simplest case is like the single DialParams structure inside LineCallInfo. Extracting this structure into a Smalltalk object can be done using the struct:at: method already defined in VisualAge. This creates a copy of the structure's content as a new structure object. Alternatively, one can use the address: class method to map an OSStructure object onto the memory area inside the containing structure. This avoids redundant storage of the structure data, but the containing structure must be retained until the embedded structure is no longer needed. See the create:beginningAt:source: method in CtiStructure for an example of this technique. Copying a structure object into another structure is done using structAt:put:. See the dialParams: instance method in CtiLineCallInfo for an example of this. A few TAPI structures have a variable number of embedded structures appended to the end of the structure in a manner similar to string entries described earlier. An example of this is the LineForward structure entries appended to the LineAddressStatus structure. To access these structures, I added the instance method create:beginningAt:source: to CtiStructure. Note that this method returns an array of structure objects mapped to the storage as opposed to creating a new copy of the structure content. Unions A union is a C language facility which allows multiple structure definitions to be overlaid on a single area of storage. Only one of the definitions is applicable at a given time, and the alternative is generally indicated by a value outside the union. TAPI uses a union in the structure LineProxyRequest and uses the variable dwRequestType to indicate which alternative definition applies. The CtiLineProxyRequest20 get and set methods for the union members test the dwRequestType value as specified in the documentation before extracting and returning a structure object. Look at instance methods agentActivity: and agentActivityList for examples. Avoiding Memory Leaks A memory leak occurs when memory allocated for temporary use is not released after it is no longer needed. After a while, the program overruns real (RAM) memory and starts to use virtual memory, usually set up on a hard drive. The application slows down noticeably at this point, and the hard drive begins to chatter. Eventually, virtual memory is used up and the application halts. Experienced C and C++ developers are all too familiar with memory leaks. All dialects of Smalltalk have automatic garbage collectors which normally makes the topic of memory leaks moot. But the garbage collectors do not extend their control into non-smalltalk memory. In VisualAge, any object allocated in non-smalltalk memory must be explicitly freed using the free message. This message must be sent to the object before the object itself is removed by the garbage collector. Otherwise, the non-smalltalk memory is left unreferenced but still allocated. If the allocated memory is only used for a brief section of code, it is easy to explicitly free the memory directly in the program code. The following example is taken from CtiLineDevice: open |handle| handle:=OSHandle calloc. self treatError:(self library lineOpen:self tapiHandle deviceId: self devID line:handle address apiVersion:self tapiVersion extVersion:0 callbackInstance:0 privileges:LINECALLPRIVILEGE_OWNER mediaModes:LINEMEDIAMODE_INTERACTIVEVOICE | LINEMEDIAMODE_DATAMODEM callParams:nil). self devHandle:(handle uint32At:0). handle free. Finalization But some non-smalltalk objects may be referenced in many places, and it might not be possible to clearly determine exactly when the memory can be freed. For this case, most Smalltalks offer finalization. With this technique, when the Smalltalk garbage collector is about to remove an object, a method with a name such as finalize is invoked. This gives the application the opportunity to perform clean-up operations such as freeing non-smalltalk memory. In VisualAge, using finalization requires that an object be sent either the addToBeFinalized or onFinalizeDo: message. It also requires an instance method (by default called finalize but other names can be specified) to perform the finalization actions. I use finalization for freeing the non-smalltalk memory for TAPI and MAPI structures. This means that allocating TAPI or MAPI structures should be done using the allocate class method rather than calloc. See the allocate class methods and the finalize instance methods in CtiStructure and MapiStructure for examples of finalization. If you choose to use finalization, you must be careful to always pass references to the non-smalltalk memory objects between Smalltalk objects. If you only pass the address of the non-smalltalk memory, you risk what C programmers refer to as dangling pointers, i.e. a pointer to a memory location which has been freed. The only place you should be passing an address is into an API function. The Application Interface Layer The preceding section described the API "wrapper" layer which translates the C-type API functions and structures into Smalltalk objects. An application developer could conceivably use this wrapper layer directly. But most Smalltalk development environments and commercial API interface extensions provide a higher-level application interface which provides an abstract view of the API functionality. This shields the developer from the complexity of the underlying technology. It also helps in making applications portable. An example of this is the Motif abstraction layer developed for VisualAge Smalltalk. User Interfaces developed using the VisualAge Motif abstraction can be ported to a wide variety of platforms (Microsoft Windows, OS/2, AIX, or Solaris) without change. Because TAPI is only available on Windows 95 and Windows NT 4.0 at present, portability is not a goal of my middle application layer interface. Its only purpose is to make Computer Telephony easier for application developers. It is intended to provide telephone services using methods such as placeVoiceCallTo:'1-613-555-1212' rather than having to set up the needed structures and then calling the API function(s) directly to select an appropriate device and dial the call. Because TAPI is so complex and because additional support is required for handling the call media, this layer requires several supporting classes designed to work together. A proper design of these classes would provide extensibility so that applications could exploit the complete functionality supported by TAPI, allowing development of both client workstation and telephony server applications (e.g. an Un-PBX). It would also support upgrades as new versions of TAPI are released by Microsoft. My design fails to deliver this. I did not have sufficient time to develop a robust, elegant, extensible application interface layer. I fully expect that developing such an interface will be a large undertaking, possibly requiring more effort than I have already put into this project to date. The following sections describe what I was able to come up with in the limited time available. TAPI Support The set of classes developed to support TAPI were mostly taken directly from TAPI itself. TAPI specifications refer to devices which have addresses upon which calls are placed or received. The corresponding classes are CtiDevice, CtiAddress and CtiCall and its subclasses. I added CtiSession to look after the establishment of the TAPI environment and to serve as the communication channel between TAPI and the application. Computer Telephony is a very dynamic application requiring extensive intercommunication between the application and TAPI. The application must be able to pass on user requests for telephone services to TAPI, and TAPI must be able to notify the application of events it has detected such as an incoming call. The following diagram depicts the intercommunication among the TAPI classes: This shows that all communication between the application and the TAPI interface is done through an instance of CtiSession and that the application never interfaces directly with the TAPI wrapper classes. It also shows that, at the middle level, communication channels are bidirectional. The dashed lines show ephemeral connections made by the application to the currently selected device, address and call objects. What the above diagram does not show is the cardinality. If TAPI detects multiple TAPI devices, the CtiSession object instantiates a CtiDevice instance for each device. Similarly, if a device supports multiple addresses, it instantiates a CtiAddress instance for each address detected. An address instance will contain CtiCall instances only when a call is present. Once the call ends, the call object is destroyed. My CtiAddress object support multiple calls, but typical POTS telephone devices only support one call per address. The above framework puts the CtiSession clearly in control of the TAPI-related messages, but at the price of making it complex. But this design is somewhat forced by the fact that callbacks from TAPI all arrive at one place selected by the developer. I chose to use CtiSession to take on that role. An alternative design might include a class specifically designed for that purpose. This design does offer alternatives to accomplish tasks requiring cooperation among objects. For example, when a call comes in on a line having caller ID, the CtiSession instance is first sent a message by TAPI notifying it about the new call. This callback contains the device, address and call handles. The session uses this to create a new CtiCall instance and stores it in the proper address and device objects. When the caller ID information subsequently arrives from the telco , TAPI sends another message informing the session that the call's information has changed. This message contains the call's handle and a flag indicating the nature of the change. When this happens, the following must be done by the middle layer: 1. The proper call object must be notified so that it can update its call information 2. The application might require notification so it can update any call information it is displaying, do a database search using the caller ID info. etc. But the exact mechanism to do the above can be implemented in many different ways. For example, I chose to broadcast the caller ID message to all call objects and let the individual calls decide whether to react based on whether their call handle matched the message's handle or not. An alternative design would have the session requesting its devices to find and return the call object (this request would, of course, be passed up the line to the addresses) and then sending the message directly to the call object. Also, once the call has updated its information, who is responsible for notifying the application? Should the call send the message back to the session through the chain of objects and then on to the application, or should the session notify the application directly right after it notifies the call? Either alternative will work, but I chose the latter method. The application sets up a communication link with a call object when it is interested in it (e.g. when displaying the call info) and severs the link when it no longer needs the call's info. In my current middle-level design, the notification messages are sent to the application using specific methods. This is workable for the limited functionality of my demo application, but would not be suitable for a general-purpose design. This design requires the application to implement methods for all possible messages, even those it is not interested in. A better design would allow the application to only implement methods it chooses to handle without risking the fatal does not understand error message. Calling API Functions - Getting the Order Right With a complex system like TAPI, several operations require multiple function calls which must be done in a certain order and which require specific parameter information. Getting this right is mainly the responsibility of this middle layer. For TAPI, the introductory section of the MSDN SDK documentation provides some of this information in overview form. But real code examples sometimes help to crystallize the information. I relied quite heavily on the book Communications Programming for Windows 95, cited in the previous chapter, in developing this layer. Handling Callbacks The callback messages for line devices from TAPI are initially send to the callback:msg:data:param1:param2:param3: instance method defined in CtiLineSession. From there, they are dispatched using their message identifier to more specific methods also defined in CtiLineSession. These latter methods are then responsible for sending the messages on their way to the final destination(s). When the application is running, the actual sequence and content of callback messages from TAPI appears, from the perspective of my limited experience, to be a function of the specific SPI in use. For example, when using Unimodem/V, a new call arrival does not raise a LINE_APPNEWCALL message as one would expect. Instead, it sends a LINE_CALLSTATE message with a state of LINECALLSTATE_OFFERING. This was determined by actually monitoring the TAPI messages. As a development aid, I have left monitoring code in the callback methods which display callback information on the Transcript. Accessing Structure Data This application interface layer could access and modify the structure data using the get and set methods defined in the various CtiStructure subclasses. But because the data values contained in these structures reside in non-Smalltalk storage, the Smalltalk inspector cannot be used during development to look at and modify the structure data. As an aid to the developer, I added a second set of structure definitions which parallel the structures defined under CtiStructure. These are what I term structure objects, and they are defined as subclasses of CtiObject. Sending an instance of a structure such as CtiLineDevCaps the asObject message returns an instance of CtioLineDevCaps with the contents of the structure assigned to instance variables. This structure object is then used by the application interface level and can be inspected by the developer. Going the other way, i.e. from a structure object to a structure in non-smalltalk memory, is done using the fromObject: class message defined in CtiStructure. Many of the structure values returned by TAPI inside the structures are flags. A typical flag value contains an integer representing multiple flag settings. For a flag value such as dwMediaModes in CtiLineDevCaps, it is difficult for the developer to see that flags 1, 4, 6, and 8 are set when the value is 169. To make the developer's job a little easier, flag variables with more than 5 possible flags are provided in both the raw form and as an array of radix 16 values which can be checked directly against the flag values defined in TAPI.H. See, for example, mediaModes and mediaModesArray in CtioLineDevCaps. Handling Wave Media As mentioned earlier in this document, an application wishing to play and record voice messages digitally must use the OS kernel multimedia functions to do so. Let's assume for a moment that a call comes in and the application wants to pick it up and play a message recorded on a wave file. It would be trivial if the kernel included a function such as playFile(filePathName, DeviceID). There is, in fact, a function which comes close, namely PlayFile. It allows playing a wave file by supplying the path name. But it, unfortunately, does not provide a means of specifying the device identifier. It chooses what it considers to be the best playback device itself using the format of the wave file. This is not acceptable for a telephony application which must play the message on the same device that the call came in on. The alternative is to use the low-level wave functions that require opening and reading the wave file and then playing the file contents on the proper device, but only after determining if the telephone device is capable of playing wave data. VisualAge Smalltalk contains low-level API interface support for wave devices and reading/writing wave files. But there is no middle level support to allow an application to send a message such as playFile:onDevice. I added the class WaveController in an attempt to provide this level of support. The WaveController class uses the low-level wave I/O function methods, defined in the classes OSCall, OSHwavein and OSHwaveout, to perform the wave device initialization, wave data buffer preparation, and wave play functions. It also uses the multimedia file I/O functions defined in class OSHmmio to read the wave data. Note that wave data files cannot be read or written using Smalltalk file streams. You must use the multimedia I/O kernel functions. The code I provided for the wave and multimedia classes above was translated from a C telephone-answering program published in the August, 1996 issue of The Microsoft Journal. Unfortunately, I was not able to complete the development of this facility in time to hand in with my project. The code as it is right now can read the format information from a wave file, but any attempt to open the modem wave device returns an error code to the effect that the wave format is not supported by the device. Once it is working properly, this WaveController class will have potential applications outside Computer Telephony. Fax Support Microsoft recommends that applications use MAPI to send faxes rather than attempting to develop faxing functionality in a TAPI application. First, a MAPI-compliant program such as Microsoft Fax must be installed in the system. Then the application can call MAPI functions to send faxes. Similarly, if an application wishes to use MAPI to send and receive e-mail, the system must be equipped with an e-mail application such as Microsoft Mail or Lotus ccMail. VisualAge Smalltalk has no support for MAPI in any form, so I added low-level support for a variant called Simple MAPI. Applications can access these MAPI services using the class MapiSession. Again, as was the case for Wave support, time ran out before I was able to complete this aspect. Additional debugging is required to complete this facility. A complete implementation of Simple MAPI would allow an application to send and receive both e-mail and faxes. I only attempted to include middle level support for sending faxes. But all low-level function and structure definitions are included so that the middle level could be extended with only a moderate amount of development effort. Simple MAPI is much smaller than TAPI, so designing an elegant middle level should not be too challenging. Demonstration Application The demonstration application implemented in the class CtiDemo1 serves both as a debugging aid and as a tutorial for application developers who might want to use this CT interface in a telephony-enabled application. This demonstration can be run from inside VisualAge by evaluating the expression CtiDemo1 new in a text window. The application displays a single window, an example of which is shown below: When the program starts, it establishes a new TAPI session and then commands the session to find all the telephone devices and all the addresses for each device. The Device combo box allows the user to choose to view the description (Sportster 56000 Voice Internal in the above screen shot) and addresses for a detected device. Similarly, the Address combo box allows the user to select an address and see how many calls are present at a given moment. If a call should come into the system, the session will be notified via a message sent from TAPI. This message identifies the device and address for the new call. The session dispatches this message to the appropriate address object. It also notifies the application so that it may update the display if applicable. If the call is on the currently selected device and address, the application will update the No. Calls value. The user can then select the call using the Call combo box to look at the call information. In the above case, the call came in on a line which didn't have caller ID service, so the name and number show as Unknown. When the displayed call terminates for some reason, e.g. the caller hanging up, the session is again notified via a callback from TAPI. Again, the session passes this message both to the address object so it can delete the call object and to the application so that it can update the display, if necessary. The demonstration application also allows the user to place a call by entering the number and, optionally, the name in the caller entry fields and then clicking the Call button. The call is dialed using the Assisted Telephony facilities of TAPI and the CtiAssistedTelephony middle-level support class. As an aside, I used the VisualAge Motif-inspired user interface widgets for this application. They did get the job done, but are too primitive to consider for real development work. I would recommend, instead, using a GUI builder such as VisualAge parts or WindowBuilder Pro. Test Results I tested the demonstration application on a system equipped with a US Robotics 56K Voice modem and running Microsoft Windows 95. The latest Unimodem/V SPI was downloaded from Microsoft's web site (get file unimodv.exe from the download area accessed from www.microsoft.com) and installed along with the control files (they generally have .inf filename extension) supplied by U. S. Robotics. I added the caller ID service to my original phone line and added a second phone line without caller ID. Using the above setup, my testing uncovered problems with Caller ID service. When the modem was connected to the line having Caller ID, an incoming call would, more often than not, be dropped as soon as it was detected . When the call wasn't dropped, only the name portion, as a rule, would be available from the call object. It is interesting to note that, in one isolated test case, the calling number was sent. However, in that case, the name was missing. When the modem is connected to the line without Caller ID service, I observed no cases where a call was dropped on its own. In all cases, the calls remained until I hung up the calling phone. The unreliable operation of Caller ID on my system can probably be explained by my use of a consumer-grade modem and the Unimodem/V SPI which Microsoft essentially gives away. My experience with caller ID phones has assured me that the caller ID service itself is reasonably reliable. And the general recommendation from the various contributors to the book Secrets of Windows Telephony and to the microsoft.public.win32.programmer.tapi Usenet news group is to use commercial-grade telephone interface equipment and SPIs for serious CT applications. My experience would tend to back this up. When the Caller ID information is made available, it does not come with the original call notification. Instead, TAPI sends a second message to advise the application that a call's information has changed. This behavior is a consequence of Caller ID on POTS in that the information is transmitted by the switch between the first and second rings. An application wishing to use Caller ID information must wait at least two rings before picking up the call. The demo application starts up reliably on my primary development system, correctly identifying the single modem as the only telephony device installed. I also attempted to run the demo application on a second system, but with different results. The second system also had a single modem, but TAPI detected four devices. These included the modem plus two COM ports and one LPT port. The application then caused the mouse cursor to freeze. On this second system, the mouse is plugged into COM port 1 -- My primary system has a PS/2 mouse port. The above test did generate three INVALMEDIAMODE error messages, presumably for the COM and LPT devices. But, given that my program is a work-in-progress, my standard error treatment is to display a message on the Transcript and carry on. It would be a simple matter to trap those errors. But this serves to demonstrate the need for proper error treatment and also supports the contention that any attempt to develop reliable shrink-wrap TAPI applications will require extensive testing on a wide range of systems. Conclusion Computer Telephony is in the process of changing from a niche technology into a mainstream one. It currently doesn't have the same high-level public visibility as, say, the Internet, but Microsoft's endorsement of CT with the inclusion of TAPI in their latest Windows releases will, I believe, result in increased support from independent commercial and custom software developers. Unfortunately, the form of application support for CT on Windows, namely TAPI, MAPI and the multimedia APIs, can be a barrier for typical Smalltalk developer who are not used to working with interfaces aimed at C programmers. Visual Basic developers, in contrast, have a choice of several commercial control libraries to choose from. My project's goal has been to provide Smalltalk developers with equivalent CT support. An Honours Project such as this is expected to be equivalent, effort-wise, to a typical Computer Science course which lasts a single term. Given the size of TAPI, I was forced to choose where to concentrate my effort. It is impossible to do the total job in the limited time available. I chose to spend most of my time developing a complete implementation of the lowest level API and Structure wrapper classes. I could have used incremental development throughout the project. In fact, I used that technique on the middle application interface layer and the demonstration application. I expect this would have allowed me to develop a more impressive demo, including support for Wave play/record and faxing. But there is a downside to incremental development, particularly for complex systems such as TAPI. It tends to defer, sometimes forever, the development of a solid design framework in favor of generating quick-and-dirty results that look spectacular. I believe that my use of incremental development produced a middle layer which works for the moment but which will not be extensible without major redesign. The low-level wrapper classes are, in my opinion, better designed to cope with extensions to TAPI and should be able to accommodate a wide range of applications. Perhaps another CS student might choose to develop a better middle layer as their Honours Project. They might try to implement it as both classes and VisualAge Parts. The interactive nature of Smalltalk development did, I believe, enhance my productivity, particularly for the middle layer and demo application. The browsers allowed me to easily find the existing support for multimedia in VisualAge. The object oriented paradigm supported by Smalltalk provided what I consider to be elegant solutions to some of the challenges posed by TAPI. Here are just a few examples: " Inheritance proved very useful in supporting the different structure definitions for different versions of TAPI " Inheritance also provided a simple way of dealing with the variable-length structures, i.e. those defined as subclasses of CtiVariableStructure " The parallel structure objects, subclasses of CtiObject, depend on inheritance and polymorphism for their instantiation and setup. They also rely on the fact that, in Smalltalk, classes are objects too " Message passing is useful in a dynamic environment such as CT where events affecting a wide range of objects occur spontaneously Adding the parallel structure objects, i.e. the subclasses of CtiObject, was a tedious and time-consuming exercise. But this feature of my interface proved to be very beneficial during the development of the middle layer and the demo application. I would recommend adding this debugging aid to anyone building Smalltalk API wrappers. I strongly recommend that anyone attempting to extend this interface or build an application with it equip themselves better than I did. In my experience, albeit limited, consumer grade modems supported by Unimodem/V do not work reliably with TAPI. Finally, developing API wrappers in Smalltalk requires working "close to the metal". It often produces General Protection Faults which cause the Smalltalk development environment to terminate, i.e. to crash without the opportunity to save the image. So one needs to save the image and file-out the code frequently.
Comment
The license is unknown - the code was written somewher around 1997/1998 from Guy Marentette at the Carleton University
Developer:
Marten Feldtmann
Use the
VAStGoodies.com Tools
to submit your contributions.