MAPI, SAPI, and TAPI Developer's Guide
MAPI, SAPI, and TAPI Developer's Guide
SAPI, and
TAPI Developer's Guide
by Michael C. Amundsen
C O N T E N T S
● What Is WOSA?
● The WOSA Model
❍ The Client API Makes Requests
● WOSA Services
❍ Common Application Services
❍ Communication Services
● Benefits of WOSA
❍Isolated Development
❍ Multivendor Support
❍ Upgrade Protection
● Summary
❍ Consistency
❍ Portability
● Messages
❍ Text Messages
❍ Control Messages
● MAPI Applications
❍ Electronic Mail Clients
❍ Message-Aware Applications
❍ Message-Enabled Applications
● Summary
❍ Storage Folders
❍ Addresses
❍ Message Stores
❍ Address Books
● Introduction
● What Is the Microsoft Exchange Forms Designer?
❍ EFD Design Wizards
● Summary
● Summary
❍ Additional Features
● Summary
● Introduction
● The Session Object
❍ The Session Object Methods
● Summary
Chapter 9 Creating a MAPI Mailing List Manager with the OLE Messaging
Library
● Introduction
❍ Laying Out the MLM Form
❍ Dropping Subscribers
❍ Listing Archives
● Summary
● Summary
❍ Property Extensions
❍ Registering Extensions
● Summary
● Speech Recognition
❍ Word Separation
❍ Speaker Dependence
❍ Word Matching
❍ Vocabulary
❍ Text-to-Speech
❍ Voice Quality
❍ Phonemes
❍ TTS Synthesis
● Grammar Rules
❍ Context-Free Grammars
❍ Dictation Grammars
● Summary
● Introduction
● High-Level SAPI
❍ Voice Command
❍ Voice Text
● Low-Level SAPI
❍ Speech Recognition
❍ Text-to-Speech
● Summary
● SAPI Hardware
❍ General Hardware Requirements
● Technology Issues
❍ SR Techniques
❍ SR Limits
❍ TTS Techniques
❍ TTS Limits
❍ Using the Enable Property to Start and Stop the TTS Engine
● Summary
● Control Tags
❍ The Voice Character Control Tags
● Grammar Rules
❍ General Rules for the SAPI Context-Free Grammar
❍ Phones
❍ pc-Based Configurations
❍ Multiline Configurations
❍ Digital T1 Lines
● Summary
● Summary
● Summary
❍ Testing TAPIANS
● Summary
● Summary
● Testing TAPIFONE
● Summary
● Microsoft Phone
❍ Adding Announcement, Message, and AutoFax Mailboxes
● Summary
● Introduction
● General Considerations
❍ Rules of Complexity
● Introduction
● Project Resources
❍ The VBVoice Controls
● Project Resources
● Coding the Library Modules
❍ The AssistedTAPI Module
● Design Considerations
❍ Project Forms and Resources
❍ Coding tmView
❍ Coding tmNew
❍ Coding tmRead
● Design Issues
● The FaxBack Application
● The Voice Phone Application
● The Talk Mail Project
● Some Final Remarks
● Books
● Web Links
● Other Online Resources
● Books
● Web Links
● Other Online Resources
● Software and Hardware Resources
● Books
● Web Links
● Other Online Resources
● Software and Hardware Resources
This book is dedicated to Joe and Ida LaSala in thanks for their love, support, and
generosity over the last twenty years.
FIRST EDITION
All rights reserved. No part of this book shall be reproduced, stored in a retrieval system, or transmitted by any means,
electronic, mechanical, photocopying, recording, or otherwise, without written permission from the publisher. No patent
liability is assumed with respect to the use of the information contained herein. Although every precaution has been
taken in the preparation of this book, the publisher and author assume no responsibility for errors or omissions. Neither
is any liability assumed for damages resulting from the use of the information contained herein. For information, address
Sams Publishing, 201 W. 103rd St., Indianapolis, IN 46290.
HTML conversion by :
M/s. LeafWriters (India) Pvt. Ltd.
Website : http://leaf.stpn.soft.net
e-mail : leafwriters@leaf.stpn.soft.net
Trademarks
All terms mentioned in this book that are known to be trademarks or service marks have been appropriately capitalized.
Sams Publishing cannot attest to the accuracy of this information. Use of a term in this book should not be regarded as
affecting the validity of any trademark or service mark.
Acknowledgments
Putting this book together took lots of help from many talented people. Although I can't list them all, I want to take a
moment to single out a few of the individuals who made this work possible.
First, I want to thank Jefferson Schuler and Bill Zembrodt of Pioneer Solutions. They accepted my challenge to build a
simple TAPI OCX tool that would allow Visual Basic programmers virtually the same access to Microsoft's Telephony
API services as C++ programmers. The result is the TAPILINE.OCX that is included on the CD-ROM that
accompanies this book. They spent several long days and late nights developing this handy tool and I thank them for all
their work and assistance.
Next, I must thank all those in cyberspace who answered my queries about telephony, speech systems, and electronic
mail. Many of the concepts that appear in this book were hashed out in extensive messages over the Internet, and I thank
all those who assisted me in my efforts. I could name many who helped, but I will refrain from doing so lest they be
blamed for any of my mistakes within these pages.
I also want to thank the people at Sams Publishing. It takes a great number of talented individuals to get a book from the
idea stage to the store shelves, and I consider it a privilege to be able to work with the folks at Sams. Completing this
book took more time and effort than any of us originally suspected and more than once it seemed like the book would
never be done. I am especially indebted to Sharon Cox for her continued help and support. I doubt this book would be in
your hands today were it not for her assistance.
Finally, I need to acknowledge the special contributions made by my family. Without their support, patience, and
understanding, I could not have completed this book. (And now that I have completed it, I have a long list of promises
that I must live up to!)
About the Author
Mike Amundsen works as an IS consulting and training specialist for Design-Synergy Corporation, a consulting and
project management firm specializing in information technology services. He has earned Microsoft certifications for
Windows operating systems, Visual Basic, SQL Server, and Microsoft Exchange Server. Mike's work takes him to
various locations in the U.S. and Europe where he teaches Windows programming and helps companies develop and
manage Windows-based client/server solutions.
He is co-author of Teach Yourself Database Programming with Visual Basic 4 in 21 Days, published by Sams, and was a
contributing author for Visual Basic 4 Unleashed and Visual Basic 4 Developer's Guide from Sams Publishing. Mike is
the contributing editor for Cobb's "Inside Visual Basic for Windows" newsletter, and his work has been published in
"Visual Basic Programmer's Journal" magazine, "VB Tech" magazine, and "Access Developer's Journal."
When he's not busy writing or traveling to client sites, Mike spends time with his family at his home in Kentucky. You
may write to Mike at his CompuServe address 102461,1267, at MikeAmundsen@msn.com on the Internet, or you
can visit his Web site at www.iac.net/~mamund/.
As a reader, you are the most important critic and commentator of our books. We value your opinion and want to know
what we're doing right, what we could do better, what areas you'd like to see us publish in, and any other words of
wisdom you're willing to pass our way. You can help us make strong books that meet your needs and give you the
computer guidance you require.
Do you have access to CompuServe or the World Wide Web? Then check out our CompuServe forum by typing GO
SAMS at any prompt. If you prefer the World Wide Web, check out our site at http://www.mcp.com.
Note
If you have a technical question about this book, call the technical support
line at (800) 571-5840, ext. 3668.
As the team leader of the group that created this book, I welcome your comments. You can fax, e-mail, or write me
directly to let me know what you did or didn't like about this book-as well as what we can do to make our books
stronger. Here's the information:
FAX: 317/581-4669
E-mail: programming_mgr@sams.mcp.com
Mail: Greg Wiegand
Comments Department
Sams Publishing
201 W. 103rd Street
Indianapolis, IN 46290
Introduction to MAPI, SAPI, and TAPI Developer's Guide
This book covers the three most exciting programming services available on the Microsoft Windows platform-messaging
(MAPI), speech (SAPI), and telephony (TAPI). Each of these APIs provides a specialized set of services that expand the
reach of the Windows operating system in a way that makes it easier to write programs that work without having to deal
with the differences between hardware provided from third parties.
The addition of these services as part of the basic operating system not only is a boon to programmers-it is of great
interest to users, too. Computers that can handle messages and telephones, and that can generate and understand simple
speech, are computers that, ultimately, are easier to use. Learning how you add these vital features to your applications
will give your software a greater reach and appeal that can make a real difference to your target audience.
● Part I-Introduction covers some preliminary issues regarding the Windows Open Services Architecture
(WOSA) upon which all three of the API sets are based.
● Part II-The Messaging API (MAPI) contains chapters that describe the MAPI service model, review existing
client and server software that implements the MAPI model, and show you how to use common developer tools
for building MAPI-compliant applications. There are also several chapters devoted to creating commonly used
MAPI-based programs, including e-mail clients, a broadcast mailing list manager, an e-mail-based discussion
forum tool, and an e-mail agent. You'll also learn how to use the Microsoft Exchange Forms designer and
discover how you can use C++ to create built-in extensions to the Windows Messaging client interface.
● Part III-The Speech API (SAPI) covers the Microsoft Voice product available for Windows 95. You'll learn
the details of the API model and how you can use it to create applications that use Text-to-Speech (TTS) and
Speech Recognition (SR) engines to add a voice to your pc applications. You'll use both C++ and Visual Basic
to build programs that respond to voice commands and read printed text back to users.
● Part IV-The Telephony API (TAPI) outlines the API set that allows Windows programmers to add inbound
and outbound telephony features to their applications. You'll learn about the telephony object model, and how
to build simple dialing applications and basic inbound call handlers. Along the way you'll learn how to select
telephony hardware and third-party TAPI development tools that will make it easier to build and maintain TAPI-
compliant applications.
● Part V-Creating Integrated Applications covers design issues you need to keep in mind when designing
Windows applications that combine messaging, telephony, and speech services. You'll learn how to build a
FaxBack service using MAPI and TAPI; an integrated voice response system that uses TAPI to allow users to
call in and request data from the computer and have the results spoken over the phone; and an application that
combines all three extension services to create an integrated voice and telephony application that uses voice
commands to place outbound telephone calls.
● Part VI-appendixes contains lists of third-party vendors for each of the three API sets and pointers to printed
and online documentation sources, along with a handful of e-mail and Web addresses that you can use to keep
current on these three technologies.
I encourage you to contact me via the Internet or through my Web site. I hope you enjoy this book, and I look forward to
hearing from you soon.
Mike Amundsen
MikeAmundsen@msn.com
www.iac.net/~mamund/
Chapter 1
CONTENTS
This developer's guide is designed to show you how to design, develop, and deploy applications that use messaging,
telephony, and speech services available within the Windows 95 operating system. The primary focus is on developing
client-side applications. Several chapters in this book cover the server-side aspects of these APIs, too. However, the
emphasis is on the client or desktop.
Note
The primary focus of this book is Windows 95, and all examples are
therefore in 32 bits. The same examples will work for Windows NT. The
ideas can be applied to 16-bit Win31, but no 16-bit examples are given in
this book.
One book cannot begin to cover all the possible development tools and API sets that provide message, telephony, and
speech services for desktop pcs. The approach taken here is to concentrate on the Microsoft API sets. This offers several
advantages: First, by using all Microsoft API models, the programmer gets to work with a consistent set of interfaces
that work well together. Second, there is no denying that Microsoft's presence in the marketplace adds strength to any
service model they develop. The time you invest in learning a set of API calls to provide advanced Windows services is
usually time well spent. Using Microsoft API models assures you that your knowledge will not be useless in a year or so
when some other API model is abandoned due to lack of use. And third, the Microsoft approach to service models is
designed to allow third-party vendors to contribute to the growth and development of the service model. This open
approach means you're really learning how to create programs that work with a variety of hardware/software
combinations and not tying your efforts to a single hardware or software system.
The messaging system explored in this book is the Microsoft Messaging API (MAPI). The MAPI system is one of the
most widely used message models available for the pc environment. While there are other message models in use today,
they are not covered here just because one book can't cover everything. If you are familiar with other message models,
your knowledge will help you gain a greater understanding of the MAPI system.
The telephony model covered here is the Microsoft Telephony API (TAPI). Just as with message services, there are
competing telephony service models, too. The primary advantage of TAPI to the programmer is that Microsoft has
committed to providing the TAPI model as part of the basic operating system for all future versions of Windows. For
example, TAPI is shipped as part of every Windows 95 operating system. Version 4.0 of Windows NT is also designed
to contain TAPI services as a fundamental part of the operating system.
Another major advantage of the TAPI model is that it is designed to work with multiple hardware products.
Programmers who use TAPI can be assured that their programs will work the same from one installation to the next as
long as TAPI-compliant hardware is used at all locations.
The speech service discussed here is Microsoft's Speech API (SAPI). There are several vendor-specific speech APIs. As
in the TAPI model, the real advantage of Microsoft's SAPI is that it is designed to support multiple third-party vendor
products. By writing to the SAPI model, you can potentially increase the reach of your speech-enabled software to all
SAPI-compliant hardware.
Some of the examples in this book also use the Microsoft Multimedia Communications Interface (MCI) to add audio and
video services. This is a well-established interface that is supported by almost all existing audio and video hardware.
The programming examples in this book move from simple, high-level examples and progress to more complex, full-
featured applications. Each section of the book has four basic parts:
● General theory
● Creating simple service-aware applications
● Building full-featured service implementation examples
● Designing applications that use the service in unique ways
Each section starts with the general theory of the API service. This includes coverage of the general design of the service
model and its objects, methods, and properties. You'll also get information about the various developer tools available
for building programs that use the target service.
You'll then learn how to create service-aware applications. This usually involves adding the target service to existing
applications, such as adding Send, Dial, or Speak buttons to a form. Service-aware applications use the target service
as an added feature instead of as a basic part of the program. You'll see how you can add service features using high-
level code routines.
The next level of programming examples shows you how to build applications that take advantage of the target service
as a basic part of the program design. Examples would be an e-mail message reader, an online address book that can dial
the selected address at the push of a button, or a program that allows users to drag and drop text documents onto a palette
and then reads a selected document aloud.
Lastly, you'll learn how to create applications that use the service in unique ways. For example, you could use the
message service to send data between two programs running on different machines on the same network, or use TAPI
services to create an answering machine application.
The final section of the book contains examples of applications that provide integrated services: using MAPI and TAPI
to build a dial-up FAX-back application; building a program that allows users to dial in, request information from a
database, and hear the results spoken to them over the phone; and creating a single application that combines messaging,
telephony, and speech into a single voice-mail system.
While the examples in this book don't cover every possible use of the target APIs, you'll find enough examples here to
give you the techniques and skills you'll need to build your own Windows applications to exploit the features of the
MAPI, TAPI, and SAPI services.
Development Tools
In the past, if you wanted to access advanced Windows services like MAPI, SAPI, and TAPI, you had to use C or C++
developer tools. But now, with the introduction of OLE libraries and the Microsoft OCX add-in control specification,
Microsoft has made it possible to access these advanced services from high-level developer tools like Visual Basic 4.0
and Microsoft FoxPro. You can even complete a number of the programming examples included in this book using the
VBA-compatible Microsoft Office tools like Excel, Access, Word, and others.
Most of the programming examples presented in this book are done in the 32-bit version of Microsoft Visual Basic 4.0
Professional Edition. Several examples are done using Microsoft's Excel 95 and Microsoft Access 95. However, a few of
the services are only available via C++ programming. You'll therefore see some short programming examples here using
Microsoft's Visual C++ 4.0. If you do not own a copy of Visual C++, you can still get a lot out of the C++ examples
simply by reading them to get the general concepts. None of the major projects in this book require coding in C++. In the
cases where C++ code is needed to gain access to a specific feature in the Windows service, you'll find compiled OLE
libraries and/or OCX tools included on the accompanying CD-ROM. You can use these compiled tools as part of your
high-level language projects.
All of the programming concepts described here are applicable to non-Microsoft programming environments, too. If you
are using products such as Borland's Delphi, dBASE, Borland C++, and so on, you can still get a lot out of this book.
You'll need to re-create the detail of the code examples in code usable in your programming environment, but the
Windows API calls will be exactly the same.
The programming examples in this book cover a wide range of hardware and software requirements, and it's no small
matter to equip your workstation with all the tools necessary to access all the Windows services described within these
pages. The following list summarizes the tools used in developing the programs presented here:
● Windows 95 operating system-Most of the examples require Windows 95. Some of the examples will run under
Windows 3.1 and most will run without problems under Windows NT (WinNT). However, all of the
programming examples in this book were developed under Windows 95. You'll avoid lots of difficulty by
sticking with Windows 95.
● Messaging API tools-MAPI services are accessed via the MAPI OCX controls, the OLE Message Library, and
the MAPI 1.0 API set. The MAPI OCXs ship with the Professional Edition of Visual Basic 4.0. The OLE
Message Library and the MAPI 1.0 API developer tools can be found on the Microsoft Developer's Network
(MSDN) Level II CD-ROMs. You'll need access to MSDN CD-ROMs to complete the advanced examples in
this book.
● Telephony API tools-The examples in this book were written using TAPI v1.4 for Windows 95. The v1.4 run-
time files are shipped as part of the Windows 95 operating system. TAPI services are accessed via a custom
OCX that comes on the CD-ROM shipped with this book and via calls directly through the API set. TAPI
developer tools are found on the MSDN Professional Level CD-ROMs, too. It is important to note that all the
examples in this book were developed as 32-bit applications for Windows 95. There is a 16-bit version of TAPI
(v1.3), and a new version of TAPI (v2.0) for WinNT will be released with NT v4.0. To keep frustration to a
minimum, stick with Win95 and TAPI 1.4.
● Speech API tools-The release version of SAPI developer tools and run-time modules are available from
Microsoft and are included on the CD-ROM that ships with this book. However, a major piece of the SAPI kit
is not included automatically as part of the SAPI developer kit. You'll need a text-to-speech engine supplied by
a third-party vendor in order to run the SAPI examples in this book. Check the accompanying CD-ROM for
demo versions of Text-to-Speech (TTS) engines. You'll also find a list of vendors of SAPI tools in appendix B,
"SAPI Resources."
● Microsoft Exchange E-Mail-Almost all the examples in this book that involve e-mail clients were created using
the Microsoft Exchange client for Windows 95. Some were created using the Microsoft Exchange Server or
Microsoft Mail Server clients. MAPI services are pretty much the same regardless of the client installed on your
workstation. Where the differences are important, they are noted in the text of the book.
● Sound cards, speakers, and microphones-Some of the examples here require the use of a pc audio system. Just
about any WAV-compatible audio card will work fine. Some examples use voice input and playback. You can
accomplish this with an attached microphone and speakers. In some cases, you can even use an attached
telephone handset, too.
● Data modems-You'll need at least a basic data/fax modem to run the examples in this book. Both the TAPI and
the MAPI examples require a data modem. For some of the TAPI examples, you can perform all the functions
using a simple data modem. For others, you'll need a data modem that supports the Unimodem/V
communications driver. Not all modems support Unimodem/V even though they offer voice-mail or telephony
features. Consult the appendix at the back of the book and the accompanying CD-ROM for a list of vendors
who offer data modems that support TAPI and the Unimodem/V standard.
● Telephony cards-While you can get through almost all the TAPI examples in this book without a telephony card
in your workstation, you'll get a lot more out of the TAPI section if you have access to one. Telephony cards
provide a much more advanced set of telephony features than do TAPI-compliant data modems. The CD-ROM
that comes with this book contains some demo tools that can be used to mimic the presence of a telephony card.
These demo tools can be used during development and testing, but they will not work in a production setting.
Consult appendix C, "TAPI Resources," and the CD-ROM for information on third-party developers who offer
full-featured TAPI-compliant telephony hardware. When shopping for telephony cards, be sure to confirm that
they are TAPI-compliant and that there are TAPI drivers (service providers) available for your telephony card.
● Access to live phone service-You'll need access to a live phone line to run most of the programs in this book.
All the examples will work with a simple analog phone line like the line available to most U.S. households. The
nature of the TAPI services makes it very easy to use digital lines such as T1 and ISDN to run these examples,
too. However, if you use digital lines, be sure your hardware (data modem and/or telephony card) is compatible
with the type of phone service you're using.
Even though this book covers a lot of territory, it leaves a lot out, too. The main focus of this book is the client desktop.
However, there's another set of APIs designed for building server-side applications for Windows. Microsoft provides
developer kits for the creation of MAPI message servers like transport providers, address books, and message storage
systems. Every TAPI client depends on a server-side interface that talks directly to the vendor hardware to translate the
requests from the client into something the hardware understands. Microsoft publishes the Telephony Service Provider
Interface (TSPI) as a set of API calls for handling the server-side aspects of Windows telephony. Finally, the Speech API
is designed to allow third-party vendors to create separate text-to-speech and speech recognition engines that work
within the SAPI system.
Covering the details of each of these server-side models could fill an entire book. For our purposes, you'll learn the
general facts about how the clients and servers relate to each other, but you'll need to look elsewhere if you want to learn
how to build server-side components.
It is also important to keep in mind that all the material here was built for the Windows 95 platform. Even though many
of the examples will run on other Windows platforms, not much will be said about Windows 3.1 or even Windows NT.
This is mainly to keep the book clear of confusing exceptions and special notations for cases where the platforms behave
in different ways. If you're unlucky enough to have the responsibility of deploying advanced Windows services on
multiple platforms, you'll need to supplement this text with copious experimenting and tweaking.
Since this book is divided into very clear sections, you can use it in a number of ways:
● Start from the beginning-The chapters of the book are arranged to start with relatively simple issues and
progress through more complex topics toward the end of the book. If you're new to programming with WOSA
and APIs, you'll get the most out of the book by following along in chapter order.
● Focus on a target service-If you have a good understanding of WOSA and the use of Windows services, you
can jump right into the API sections that interest you most. The three service sections (MAPI, SAPI, and TAPI)
are each self-contained. You can read these sections in any order without missing any vital material.
● Focus on integrating services-If you already know one or more of the APIs and are mainly interested in
building advanced integration applications, you can skip the API sections you have had previous experience
with and move directly to the section on building integrated applications. If you take this approach, you may
run into areas of the book that refer to previous programming examples or concepts discussed in previous
sections of the book. Be prepared to do a bit of skipping around at times to follow some of these threads back to
their source. In this way, you can use this book as more of a reference guide than a tutorial.
No matter how you use this book, by the time you complete the examples and absorb the basic concepts explained here,
you'll have a solid understanding of some of the most advanced Windows extension services available.
CONTENTS
● What Is WOSA?
● The WOSA Model
❍ The Client API Makes Requests
● WOSA Services
❍ Common Application Services
❍ Communication Services
● Benefits of WOSA
❍ Isolated Development
❍ Multivendor Support
❍ Upgrade Protection
● Summary
Before we jump into the details of the three API sets covered in this book, it is a good idea to spend some time reviewing
the overall architecture upon which these APIs are built. Although these API sets are only slightly related in their
function, they are all closely related in their construction and implementation. It is the construction and implementation
of the APIs that is the topic of this chapter.
All of the API sets covered in this book are part of the Windows Open Services Architecture (WOSA). This chapter
offers a brief overview of the WOSA model and describes how it works and the benefits of the WOSA model for
developers. Once you understand the general notions behind the creation and deployment of API services via the WOSA
model, you will also have a good understanding of how to build well-designed end-user applications that use WOSA-
based API services. The WOSA model APIs offer clear benefits to developers who understand the model. Those who
fail to grasp the basic theories behind WOSA may end up using the APIs incorrectly, thus ruining their chances of taking
advantage of the real benefits of the WOSA model.
What Is WOSA?
The Windows Open Services Architecture was developed by Microsoft to "...provide a single, open-ended interface to
enterprise computing environments." The concept of WOSA is to design a way to access extended services from the
Windows operating system that require having only a minimum amount of information about the services. For example,
the MAPI (Message API) model is designed to allow programmers to develop applications that use the message services
without having to understand the complexities of the hardware and software routines that implement messaging on
various Windows platforms. The same is true for the Speech (SAPI) and Telephony (TAPI) services we will discuss in
this book.
The WOSA model goes beyond the idea of exposing services in a uniform way across Windows operating systems.
WOSA is also designed to work in a mixed operating system environment. For example, the Microsoft Rpc (Remote
Procedure Call) interface is a WOSA service that is designed to work with the Open Software Foundation's DCE
(Distributed Computing Environment) Rpc model. The design of Microsoft Rpc allows programmers to design software
that will safely interface with any product that uses the DCE model, regardless of the operating system with which the
software must interact.
In order to attain this flexibility, the WOSA model defines two distinct interfaces-the Client API and the Server SPI.
These interfaces are linked by a single interface module that can talk to both API and SPI applications. As a result, all
client applications need to do is conform to the API rules and then to the universal interface. All server applications need
to do is conform to the SPI rules and then to the universal interface. No matter what changes are made to the client or
server applications, both software modules (client and server) will be compatible as long as they both continue to
conform to the API/SPI model and use the universal interface.
The next two sections of this chapter outline the WOSA model in detail and give several examples of WOSA services
currently available.
The WOSA model consists of three distinct pieces. Each of these pieces plays an important, and independent, role in
providing programming services to your applications. The three WOSA components are
● The Client API-the application programming interface used by the program requesting the service.
● The Server SPI-the service provider interface used by the program that provides the extended service (for
example, e-mail, telephony, speech services, and so on).
● The API/SPI Interface-the single module that links the API and SPI calls. This is usually implemented as a
separate DLL in the Windows environment.
Figure 2.1 shows the relationship between the three WOSA components.
Each of the components has an important job to do. Even though they perform their tasks independently, the components
work together to complete the service interface. This is the key to the success of the WOSA model-distinct, independent
roles that together provide the whole interface.
The Client API is the interface for the application requesting the service. API sets are usually implemented at the
Windows desktop. The Message API (MAPI) is a good example of a WOSA client API. Each client API defines a stable
set of routines for accessing services from the back-end service provider. For example, the operations of logging into an
e-mail server, creating an e-mail message, addressing it, and sending it to another e-mail client are all defined in the
MAPI set. These services are requested by the client. The actual services are provided by the server-side application.
The key point is that the client application interface allows programs to request services from the server-side service
provider but does not allow the client software to access the underlying services directly. In fact, the request is not even
sent directly to the server-side application. It is sent to the DLL interface that sits between the API and SPI (see Figure
2.2).
The Server SPI (Service Provider Interface) accepts requests for services and acts upon those requests. The SPI is not
designed to interface directly with a client application. Most SPI programs are implemented on network servers or as
independent services running on desktop pcs. Users rarely interact with these service providers, except through the client
API requests.
A good example of an SPI implementation is the Open Database Connectivity (ODBC) interface. Even though
programmers use API calls (or some other method of requesting ODBC services) in their programs, these calls merely
request services from an external program. ODBC calls to Microsoft's SQL Server, for example, are simply requests to
SQL Server to perform certain database operations and to return the results to the client application. When making an
ODBC request to SQL Server, the client actually performs very few (if any) database operations. It is SQL Server that
performs the real database work.
As mentioned earlier, service providers rarely display interfaces directly to client applications. Their job is to respond to
requests. These requests do not even come directly from the client program. In the WOSA model, all requests come
directly from the interface DLL. The SPI talks only to the interface DLL. Any information that the SPI needs to supply
as a part of the response to the request is sent directly to the interface DLL. It is the DLL's job to send the information on
to the client that made the initial request.
Another important point should be highlighted here. The service provider portion of the WOSA model allows for
multiple clients to request services (see Figure 2.3).
The DLL interface tells the SPI which client is making the request. It is the SPI's responsibility to keep the various
clients straight. SPIs must have the ability to handle multiple requests from the same client and from multiple clients.
Since a key design aspect of WOSA is the isolation of the client API and the server SPI, a single interface between the
two components is required. This single interface is usually implemented as a Windows dynamic link library (DLL) that
allows programs to link to existing services at run-time instead of at compile time. The advantage of using DLLs is that
programs need not know everything about an interface at compile time. Thus, programmers can upgrade DLL modules
without having to recompile the applications that access the interface.
The primary role of the DLL is to broker the requests of the client API and the responses of the server SPI. The DLL
does not actually perform any real services for the client and makes no requests to the SPI.
Note
Actually, the interface DLL may request basic information from the SPI at
startup about the underlying SPI services, their availability, and other
information that may be needed to support requests from clients.
The interface DLL is the only application in the Windows environment that actually "speaks" both API and SPI. It is the
DLL's job to act as translator between the client and server applications.
In the past (before WOSA), these DLLs were written as translators from the client API directly to a specific back-end
product. In other words, each DLL interface understood how to talk to only one back-end version of the service. For
example, early implementations of the MAPI interface involved different DLLs for each type of MAPI service provider.
If a program needed to talk to a Microsoft MAPI service provider, the MAPI.DLL was used as an interface between the
client and server. If, however, the program needed to talk to another message server [such as Lotus Vendor Independent
Messaging (VIM) or the universal Common Message Calls (CMC) service provider], another DLL had to be used to link
the client request with the back-end provider.
In the WOSA world, interface DLLs can speak to any service provider that understands the SPI call set. This is an
important concept. Now, a single interface DLL can be used for each distinct service. This single DLL is capable of
linking the client application with any vendor's version of the service provider. This is possible because the service
provider speaks SPI rather than some proprietary interface language (see Figure 2.4).
WOSA Services
Microsoft has been championing the WOSA model for several years and promotes three types of WOSA services:
Each type has its own purpose and its own core of services. The following sections describe the WOSA service types
and give examples of services currently available for each.
Common Application Services allow applications to access services provided by more than one vendor. This
implementation of WOSA focuses on providing a uniform interface for all Windows applications while allowing
programmers and/or users to select the vendor that provides the best service option for the requirement. In this way,
Microsoft can encourage multiple (even competing) vendors to provide their own versions of key service components for
the Windows operating system.
By defining a single set of APIs to access the service, all third-party vendors are assured equal access to the Windows
operating environments. Since the interface is stable, vendors can concentrate on building service providers that expose
the services that customers request most often. These vendors can also be confident that, as Windows operating systems
change and evolve, the basic model for service access (the WOSA model) will not change.
The list of Common Application Services available for Windows operating systems is constantly growing and changing.
Here is a list of some of the services provided under the WOSA model:
● License Service Application Program Interface (LSAPI) provides access to software license management
services.
● Messaging Application Program Interface (MAPI) provides access to e-mail and other message services.
● Open Database Connectivity (ODBC) provides access to database services.
● Speech Application Program Interface (SAPI) provides access to speech and speech recognition services.
● Telephony Application Program Interface (TAPI) provides access to telephone services.
Communication Services
Communication Services provide access to network services. This set of WOSA services focuses on gaining uniform
access to the underlying network on which the Windows pc is running. The Communications Services also provide
uniform access to all the network resources exposed by the underlying network. By defining a universal interface
between the pc and the network, Windows applications are able to interact with any network operating system that
conforms to the WOSA model.
The following list shows some examples of WOSA implementations of Communication Services:
● Windows SNA Application Program Interface provides access to IBM SNA services.
● Windows Sockets provide access to network services across multiple protocols, including TCP/IP, IPX/SPX,
and AppleTalk.
● Microsoft Rpc provides access to a common set of remote procedure call services. The Microsoft Rpc set is
compatible with the Open Software Foundation Distributed Computing Environment (DCE) model.
The last category of WOSA services defined by Microsoft is Vertical Market Services. These are sets of API/SPI calls
that define an interface for commonly used resources in a particular vertical market. By defining the interfaces in this
way, Microsoft is able to work with selected vertical markets (banking, health care, and so on) to develop a standard
method for providing the services and functions most commonly used by a market segment. In effect, this allows users
and programmers to invent Windows-based solutions for an entire market segment without having to know the particular
requirements of back-end service provider applications.
As of this writing, Microsoft has defined two Vertical Market Services under the WOSA umbrella:
● WOSA Extensions for Financial Services provide access to common services used in the banking industry.
● WOSA Extensions for Real-Time Market Data provide access to live stock, bond, and commodity tracking data
for Windows applications.
Benefits of WOSA
There are several benefits for both users and programmers in the WOSA model. The three key benefits worth
mentioning here are
● Isolated development
● Multivendor support
● Upgrade protection
The next three sections describe the details of the benefits of the WOSA model as it relates to both client and server
programmers and application users.
Isolated Development
In one way or another, all WOSA benefits are a direct result of the model's ability to separate the details of service
providers from the application running on users' desktops. By keeping the details of hardware and software interface
locked away in the SPI-side, programmers can concentrate on providing a consistent interface to the services, rather than
concerning themselves with the low-level coding needed to supply the actual services.
The isolation of services from user applications has several other benefits. With WOSA services, developers can limit
their investment in understanding the details of a service technology, where appropriate. Those focused on developing
client-side applications can leave the details of server-side development to others. They can concentrate their efforts on
developing quality client-side software knowing that, as long as the service providers maintain WOSA compatibility, the
client software will be able to take advantage of new services as they become available.
Of course, this works the same for developers of server-side software. They can concentrate their efforts on providing
the most efficient and effective means for exposing and supporting requested services and leave the client interface
details to others. Service provider developers are assured equal access to all Windows clients because the WOSA model
ensures that all links to the services are the same, regardless of the client software used.
Multivendor Support
In addition to allowing programmers to focus on the client-side of the equation instead of the server-side, WOSA
implementations provide benefits to application programmers. With a common interface for the service, application
programmers can build software solutions that are independent of vendor-specific implementations. The WOSA model
allows programmers to build programs that interact with any vendor's service implementation as long as that vendor
adheres to the WOSA model.
This is a key benefit for both client-side and provider-side developers. Now service provider vendors can be assured that,
as client-side applications change, the interface to their services will remain the same. At the same time, client-side
developers can be assured that as service providers upgrade their software, client applications need not be rewritten
except to take advantage of new services. This feature allows client-side and service-side development to go forward
independently. The result is greater freedom to advance the technology on both sides of the interface.
Upgrade Protection
Another benefit of WOSA-compliant systems is the protection it provides during service or application upgrades or
platform migrations. Users can more easily plan and implement software and hardware upgrades when the service access
to uniform calls is isolated to a single DLL. Since the WOSA model ensures that service provision and access is
standardized across vendors and platforms, changing software and hardware has a minimal effect on users who access
services from WOSA-compliant applications.
Thus, when users decide to move their primary database services from an IBM VSAM system to a DB2 or SQL Server
environment, the client applications see minimal change as long as the WOSA model was implemented for database
access. This protects users' software investment and allows greater flexibility when selecting client and server software.
At the same time, this approach provides protection for commercial software providers because a single application can
be designed to work with multiple service providers. Developers can focus on creating full-featured applications without
tying their software to a single service provider. As the market grows and changes, and service providers come and go,
client-side applications can remain the same when the WOSA model is used as the route for service access.
Now that you understand the concepts behind the WOSA model, you can use this information in your own development
efforts. For example, when accessing WOSA services from your client applications, isolate those calls in your code. This
will make it easier to modify and enhance your application's WOSA interface in the future. As the services available
change and grow, you'll need only to make changes in a limited set of your code.
Also, when designing your application, plan for the future from the start. Assume that the various program services will
be provided via WOSA-compliant tools-even if they are not currently available as true WOSA components. This
approach will make it much easier to add true WOSA components to your client application once they become available
and will increase the life and flexibility of your software.
The same holds true for back-end service provider developers. If there is a WOSA SPI defined for the services you are
providing, use it. This will make your software client-ready for virtually all Windows desktops. If no WOSA interface is
yet defined for your service, code as if there is one. Limit the code that talks directly to hardware to a single area in your
program. If you are using vendor-specific calls, isolate these, too. This way, when a WOSA model is defined for your
service, you'll have an easier time converting your software to comply with the WOSA model.
As you work through the book, you learn about the specific API calls that are used to access extension services. You'll
see a consistent pattern throughout all three extensions. Each of the extension services is divided into layers. The first
layer provides a simple interface-sometimes as simple as a single function call-to the target service. The second level
provides a more extensive access to a complete feature set. Finally, you'll find a third level of service that allows
sophisticated programmers access to all the nifty little details of the extension service. When you're coding your own
applications, it is a good idea to use this same approach. Where possible, give users a single function or subroutine that
provides access to the extension service. For more advanced applications, create a function library or class object that
encapsulates all the extension service calls. By doing this, you'll make it easier to make modifications to your program
logic without touching the extension routines. It will also be easier to update the extension routines in the future without
damaging your local program logic.
Summary
In this chapter, you learned that the WOSA model was developed by Microsoft to "... provide a single, open-ended
interface to enterprise computing environments." You learned that the WOSA model has three key components:
● The Client API usually resides on the desktop workstation and makes service requests.
● The Server SPI usually resides on a network server and receives and responds to service requests made by
clients.
● The interface DLL acts as a broker between the client API and the server SPI. The API and SPI never talk
directly to each other. They both talk only to the interface DLL.
You learned that there are three types of WOSA implementations. Each of these implementations focuses on a different
aspect of service implementation. The three WOSA types are
● Common Application Services provide common access to system-level services, independent of vendor
implementations. Examples of WOSA Common Application Services are ODBC, MAPI, SAPI, TAPI, and
LSAPI.
● Communication Services provide standard access to network services, independent of the underlying network
available to the workstation. Examples of Communication Services are Windows SNA API, Windows Sockets
API, and the Microsoft Rpc interface.
● Vertical Market Services provide access to common services used in a particular vertical market, such as
banking, health care, and so on. Examples of this type of WOSA service are the WOSA Extensions for
Financial Services and the WOSA Extensions for Real-Time Market Data.
We also covered some of the key benefits of using WOSA-defined services in developing Windows applications. The
benefits mentioned are
● Isolated development-Using WOSA-compliant services isolates access to external services. As these services
change, the impact on your application is limited.
● Multivendor support-WOSA-compliant services are vendor neutral. If your application uses WOSA-defined
service calls, they can be provided by any vendor's service provider if it is also WOSA-compliant.
● Upgrade protection-When you use WOSA components, you can easily plan and implement software and
hardware upgrades because the WOSA model is consistent across platforms and operating systems.
Finally, you learned how you will use the concepts behind the WOSA model in your own software development to
improve compatibility and ensure easy future upgrades.
Chapter 3
What is MAPI
CONTENTS
❍ Consistency
❍ Portability
● Messages
❍ Text Messages
❍ Control Messages
● MAPI Applications
❍ Electronic Mail Clients
❍ Message-Aware Applications
❍ Message-Enabled Applications
● Summary
Before getting into the details of how the Messaging Application Programming Interface (MAPI) works and how to
write MAPI applications, we'll take a moment to review the general architecture of Microsoft's messaging API and how
this set of message services fits into the overall Windows operating system. As you will see a bit later in this chapter,
MAPI is more than a handful of e-mail APIs-it is a defined set of message services available to all programs that run in
the Windows operating environment.
We'll also discuss the various kinds of applications and message types commonly used under MAPI services. In this
chapter, you'll learn about the three general types of MAPI messages: text messages, formatted documents and binary
files, and control messages. Each of these message types has a distinct set of properties and uses within the MAPI
framework. This chapter describes each of the message types and shows how you can use them within the MAPI
architecture.
There are also three common types of MAPI applications: electronic mail clients, message-aware applications, and
message-enabled applications. Each of these application types is defined and illustrated in this chapter. You'll also learn
the relative strengths of each type of MAPI application.
The MAPI service set is more than a set of API commands and functions that you can use to send e-mail from point to
point. The MAPI interface is actually a carefully defined set of messaging services available to all Windows programs.
This pre-defined set has three key attributes:
● Flexibility
● Consistency
● Portability
Because the MAPI service set contains these three characteristics, it has become the de facto messaging interface
standard for Windows applications.
Access to MAPI services is the same for all versions of the Windows operating system. But even though your Windows
programs use the same methods for accessing MAPI services, MAPI services can vary from system to system. Also,
MAPI architecture allows software designers to create their own service providers (SPs) to support the MAPI service set.
These services are also available within all existing flavors of the Windows operating system. Even more important, the
methods of access to these message services is the same, regardless of the version of Windows you are working with.
This means that programs using MAPI services that were written under Windows 3.1 will still be able to access those
same MAPI services under Windows 95.
Flexibility
Probably the most important aspect of the MAPI service architecture is its flexibility. Microsoft has implemented MAPI
within the Windows Open Systems Architecture (WOSA). This architecture is designed to allow customization at both
the client (user) side and the server (provider) side. In other words, you can use MAPI not only to create your own end-
user software to read, write, create, and send messages, but also to construct custom server-side software to store and
transport those same messages. As part of the WOSA model, MAPI services are implemented in a tiered format (see
Figure 3.1).
The first layer is the client layer. This is what the end-user most often sees. At this level a set of well-defined services are
available. These services are accessed when the client layer makes a service request to the second layer-the MAPI DLL.
The MAPI DLL takes the service request from the client and forwards it on to the third layer in the tier-the service
provider. The service provider is responsible for fulfilling the client request and sending the results of that request back
to the DLL where it is then forwarded to the client that made the initial request. Throughout the process, the DLL layer
acts as a broker between the client side and the server side.
The primary advantage of this layered implementation is the ease with which users can interchange client and server
components. Since the only constant required in the mix is the DLL layer, any client can be matched with any server
component to provide a working message service. It is very common to switch client-side components in a messaging
service. Each e-mail reader is a MAPI client application. Any application that sports a send button is actually a MAPI
client. Any specialized program written to manipulate messages at the end-user level is a MAPI client.
While interchanging MAPI clients is rather commonplace, interchanging MAPI service providers is not. In a network
environment, a single service provider will usually be designated as the default provider of MAPI services. It is no
longer rare to have several MAPI service providers available at the same time, however. In fact, the Microsoft Mail
Exchange client that ships with Windows 95 is specifically designed to be able to access more than one service provider.
It is possible to use the Windows Exchange client to access messages via Microsoft Mail Server, Microsoft FAX, or
through the Microsoft Network (MSN). You can even install third-party service providers into the Exchange client (such
as the one provided by CompuServe) to access messages stored in other mail systems (see Figure 3.2).
Consistency
The MAPI service set contains a set of services that encompasses all the basic messaging tasks:
● Message service logon and logoff
● Reading, creating, and deleting text messages
● Adding and deleting message binary file attachments
● Addressing and transporting the completed messages
The exact behavior and properties of each of these services are defined as part of MAPI. All vendors who supply a
MAPI-compliant set of tools provide these services in the exact same manner. This way, any program that uses MAPI
services can be assured that there are no special API variations to deal with when moving from one vendor's MAPI
product to another. This means the programs you write today using your current implementation of MAPI services will
function under other implementations of the MAPI service set (see Figure 3.3).
Portability
This leads to the second strength of Microsoft's MAPI service set-portability. Microsoft supports MAPI services on all
versions of its Windows operating systems. If you write a program for the Windows 3.1 version of MAPI services, that
same program can still access the MAPI services under any other version of Windows that supports your executable
program. This is a key issue when you consider how many versions of Windows are currently in use and how quickly
new versions of the operating system are developed and deployed.
Not only will you be able to move your MAPI-related programs to various Windows platforms, you can also allow
programs to access MAPI services from more than one platform at once. In other words, users of more than one version
of Windows can all be accessing MAPI services from a central location at the same time (see Figure 3.4).
Microsoft has announced plans to move several of its service sets (MAPI included) beyond the Windows operating
environment, too. As this happens, Microsoft has pledged that the same set of functions and routines used under the
Windows environment will be available in other operating systems.
Messages
The primary role of the MAPI service is to transport and store messages. This section identifies three common message
types supported by MAPI services:
● Text messages
● Formatted documents or binary files
● Control messages
The most basic message form is the text message, commonly thought of as e-mail. Most electronic message systems also
support the second type of message-formatted documents or binary files. These are usually included as attachments to a
text message.
The third message type is a control message. Control messages are usually used by the operating system to pass vital
information such as system faults, potential failure conditions, or some other type of status information. Control
messages can also be passed between programs in order to implement a level of distributed processing in a computer
network.
The text message is the most common MAPI message. In fact, all MAPI messages have a default text message
component. A text message contains the letters and words composed by users to communicate with other message
system users.
All MAPI service providers must supply a simple text message editor as part of their MAPI implementation. All MAPI
message providers support plain ASCII text characters as a message body. Many also support rich-text messages that
contain formatting characters such as font and color. The Microsoft Mail client supplied for Windows 3.11 and Windows
for Workgroups supports plain ASCII text. The Microsoft Mail Exchange client supplied for Windows 95 supports rich-
text formatting.
The second MAPI message type is the formatted document or binary file, which is usually a file containing non-printable
characters such as a spreadsheet, a word-processing file, graphics, or even an executable program. Binary files are
supported by MAPI services as attachments to text messages. The MAPI service set allows multiple attachments to a
single text message. This means you can send several binary files to the same e-mail address using a single message
body.
All MAPI service providers support the use of binary attachments to a message body. However, the transport of binary
attachments across multiple message servers may not be supported. For example, if you compose a message that
contains attached binary files, address it to an associate at a distant location, and attempt to send the message using a
service provider that supports only Simple Mail Transfer Protocol (SMTP) format, your attached binary files will not be
successfully transported to the recipient.
Control Messages
The third type of MAPI message is the control message. Control messages are usually used by the operating system to
deliver system status information, such as a component failure or other system-related problem. These messages are
usually addressed directly to the system administrator.
Control messages can also be used to pass data or other control information between programs. Control messages of this
type can contain requests for information that is to be collected by one program and returned to another for further
processing. Or the control message can contain actual data to be manipulated by another program. Since MAPI services
can stretch across the LAN or across the globe, control messages can be passed to systems halfway around the globe as
easily as to systems across the room.
It is possible to designate one or more workstations on a network as batch job computers. These machines wait for
control messages that direct them to perform time-consuming tasks, such as extended database searches or generating
long reports, thus freeing up users' workstations for more immediate business. Once the task is complete, the batch job
machine can send a completion notice via e-mail to the user who sent the original request. While it is true that OLE
Automation servers are beginning to replace batch job computers that are controlled by MAPI messages, MAPI services
are still a very powerful alternative.
MAPI Applications
Just as there are three types of MAPI messages, there are three general types of MAPI applications:
Electronic mail (e-mail) clients are the most common form of MAPI application. An e-mail client allows end-users direct
access to the MAPI services supported by the back-end service provider. Figure 3.5 shows the Microsoft Exchange
electronic mail client.
Electronic mail clients can also provide additional services to make it easy to manipulate, store, and retrieve text
messages and binary file attachments. Electronic mail clients may also have additional features for addressing and
transporting messages, including the use of defined mailing lists and the capability to address messages as cc (courtesy
copies) or Bcc (blind courtesy copies).
Message-Aware Applications
Message-aware applications are non-MAPI programs that allow users access to MAPI services. Typically, this access is
implemented through the addition of a send option in a menu or button bar. Figure 3.6 shows the Microsoft Word 95
main menu with the Send menu item highlighted.
Message-aware applications usually treat e-mail services just like any other storage or output location, such as disk
drives, printers, or modems. In these cases, the ability to send the standard output as an electronic mail message is an
added feature for the application. As MAPI services become a standard part of the Windows operating system, message-
aware applications will become the norm instead of the exception.
Message-Enabled Applications
The last category of MAPI applications is message-enabled applications. Message-enabled applications are programs
that offer message services as a fundamental feature. While message-aware applications provide message services as an
additional feature and can operate well without them, message-enabled applications are specifically designed to use
message services and most will not run properly unless message services are available on the workstation.
● Computerized service dispatch-Customer calls are handled by representatives at pc workstations where they fill
out data entry forms outlining the repair needs and the location of the service call. When the data entry is
complete, the program analyzes the information and, based on the parts needed and the service location, routes
an instant electronic message containing the service request and a list of needed parts to the repair office nearest
to the customer.
● Online software registration-When a user installs a new software package, part of the installation process
includes an online registration form that already contains the unique software registration code along with a
data entry form for the user to complete. Once the form is completed, the results are placed in the user's e-mail
outbox to be sent directly to the software company to confirm the user's software registration.
● End-user support services-When network end-users have a question about a software package or need to report
a problem with their workstation or the network, they call up a data entry form prompting them to state the
nature of the problem. This program will also automatically load the user's system control files and add them as
attachments to the incident report. Once the form is complete, it is sent (along with the attachments) to the
appropriate network administrator for prompt action.
It is important to note that, in some cases, users of message-enabled applications may not even be aware that they are
using the e-mail system as part of their application. MAPI services define properties and methods for logging users in
and out of the message server without using on-screen prompts. MAPI also provides options for addressing and sending
messages without the use of on-screen prompts or user confirmation. By using these features of MAPI services, you can
design a program that starts a message session, reads mail, composes replies, addresses the new mail, and sends it to the
addressee without ever asking the user for input.
There are two more types of message-enabled applications that deserve comment here. These two application types are
Electronic forms applications display a data entry screen that contains one or more data fields for the user to complete.
These data fields act as full-fledged windows controls and can support all the events normally supported by Windows
data entry forms. Once the form is completed, the data, along with additional control information, is sent to the addressee
through MAPI services. When the addressee opens the new mail, the same formatted data entry form appears with the
fields filled in (see Figure 3.7).
The message-driven application looks for data contained in a message and acts based on the data it finds. Message-
driven applications can use any aspect of the message as control information for taking action. Message-driven
applications can inspect the message body or subject line for important words or phrases, check the sender's name or the
date and time the message was sent, or even scan attachments for important data. These applications can then use the
data to forward messages to another person automatically, to set alerts to notify the user of important messages, or to
start other programs or processes at the workstation.
● Message filtering agent-Users can enter a list of important keywords into a list box. This list is used to scan all
incoming text messages automatically. If the message contains one or more of the keywords, the user is notified
immediately that an important message has arrived. Users could also set values to scan for the message sender's
name. For example, if the message came from the user's boss, an alert could sound to warn the user that an
urgent message has arrived. The same technique can be used to automatically forward specific messages when
the user is away on a trip.
● File transfer database update application-This program could be used by outlying sales offices to update a
central database automatically. Each day the remote offices would enter sales figures in a database, then attach
the binary database file to an e-mail message, and send the message to the corporate headquarters. There, a
special workstation (logged in as the addressee for all sales database updates) would receive the message and
automatically run a program that takes the binary database file and merges it into the central database. This
same program could then provide summary data back to the remote offices to keep them up to date on their
progress.
● Electronic database search tool-Many companies have large libraries of information on clients, products,
company regulations, policies and procedures, and so on. Often users would like to run a search of the
information but don't have time to physically visit the library and pour through thousands of pages in search of
related items. If the information is kept in online databases, users at any location around the world could
formulate a set of search criteria to apply against the databases and then submit these queries, via MAPI
messages, to one or more workstations dedicated to performing searches. After the search is completed, the
resulting data set could be returned to the user who requested the data.
Filtering agents, remote update routines, and long-distance search tools are all examples of how MAPI services can be
used to extend the reach of the local workstation to resources at far-away locations. The Windows MAPI services
provide excellent tools for building programs that enable users to collect and/or disseminate data over long distances or
to multiple locations. The next several chapters will explore the details of MAPI architecture, teach you how to
incorporate MAPI services into your programs, and show you examples of real-life programs that take advantage of
Windows MAPI services.
Summary
In this chapter, you learned that the Messaging Application Programming Interface (MAPI) is a part of the Windows
Open Systems Architecture (WOSA) model. MAPI is designed to offer three key benefits over other messaging services:
● Flexibility-Since MAPI is implemented within the WOSA model, there are three distinct layers:
● The client layer (the end-user software)
● The MAPI DLL layer (the MAPI service broker)
● The service layer (the actual message service provider)
● Because the MAPI DLL layer acts as the service request broker between the MAPI client and the MAPI server,
you can interchange servers and clients without having to modify your MAPI software modules.
● Consistency-MAPI services and the methods for accessing them are the same no matter what vendor you are
using to provide the message services.
● Portability-MAPI services are available on all supported versions of Windows (Win3.11, WFW, WinNT, and
Win95). As Microsoft moves WOSA services to non-Windows platforms, MAPI services will be available
within those operating systems, too.
● Text messages-These are the standard plain ASCII text messages commonly known as e-mail. Some MAPI
service providers support the use of rich-text formatted messages (for example, the Microsoft Exchange Mail
client).
● Formatted documents and binary files-These are word-processing documents, graphics files, databases, and so
on. MAPI enables you to send these binary files as attachments to the body of a text message.
● Control messages-These are used by operating systems and specialized batch programs to send information
about the operating system, or send commands to tell remote machines how to process attached data or run
special jobs.
Finally, you learned about the various types of MAPI applications. These application types are
● Electronic mail clients-The sole purpose of these programs is to give users direct access to the available MAPI
services (for example, the Microsoft Mail Exchange client that ships with Windows 95).
● Message-aware applications-These are programs that provide MAPI services as an added feature. These
programs usually offer users a send button or menu option. The standard output of the program can then be
routed to another location through MAPI. The Send menu option of Microsoft Word 95 is an example of a
message-aware application.
● Message-enabled applications-These programs offer MAPI services as a basic part of their functionality.
Usually, message-enabled applications will not operate properly unless MAPI services are available to the
workstation. Examples of message-enabled applications include data entry forms that collect data and
automatically route the information to the appropriate e-mail address, sometimes without asking the user for
MAPI logons or addresses.
Now that you know the common types of MAPI messages and applications, it's time to review the details of MAPI
architecture. That is the subject of the next chapter.
Chapter 4
MAPI Architecture
CONTENTS
❍ Storage Folders
❍ Addresses
❍ Message Stores
❍ Address Books
In Chapter 3, "What Is MAPI?," you learned the general outline of the messaging API and how MAPI fits into
Microsoft's Windows Open Systems Architecture (WOSA) scheme. You also learned about the three most common
types of MAPI messages (ASCII text, formatted documents or binary files, and control messages). Lastly, Chapter 3
described the three major categories of MAPI applications (e-mail, message-aware, and message-enabled).
In this chapter, you'll learn about the basic conceptual components of MAPI-the MAPI
Client and the MAPI Server. These two components work together to create, transport, and store messages within the
MAPI system.
Each of these objects is represented slightly differently in the various versions of MAPI implementations you'll learn
about in this book. For example, the MAPI OCX tools that ship with Visual Basic allow only limited access to folders
and address information. The Messaging OLE layer (provided through the MAPI 1.0 SDK) provides increased access to
folders and addresses, but only the full MAPI 1.0 functions allow programmers to add, edit, and delete folders and
addresses at the client level. This chapter will cover all three objects in a general sense. Later chapters will provide
detailed programming examples.
● Message transport
● Message stores
● Addresses books
As you can see, the server side of MAPI is quite similar to the client side (see Figure 4.1).
Where the client is concerned with creating and manipulating messages, the server component is concerned with the
transporting of those same messages. Where the client side is accessing storage folders, the server side is dealing with
message storage, and both client and server must deal with message addresses. However, the MAPI server has the
responsibility of managing the transport, storage, and addressing of messages from any number of client applications.
In addition to maintaining the message base for all local clients, MAPI servers also have the task of moving messages to
and from remote servers and clients. The final section of this chapter talks briefly about a special MAPI component that
handles this task-the MAPI Spooler.
When you complete this chapter, you'll understand the fundamental tasks that must be performed by all MAPI clients
and MAPI servers and the basic objects maintained and manipulated by both client and server applications. Once you
have a grasp of the general operations of a MAPI system, you'll be ready to explore MAPI functions from a Windows
programming perspective in the coming chapters.
The MAPI Client is the application that runs on the user's workstation. This is the application that requests services from
the MAPI Server. As mentioned in the preceding chapter, client applications can be generic e-mail tools such as
Microsoft's Microsoft Mail or Exchange Mail Client for Windows 95. Client applications can also be message-aware
applications like the Microsoft Office Suite of applications. Each of these applications provides access to the message
server via a send menu option or command button. Lastly, message-enabled applications, ones that use MAPI services as
a primary part of their functionality, can be built to meet a specific need. These programs usually hide the MAPI services
behind data entry screens that request message-related information and then format and send messages using the
available message server.
All MAPI clients must, at some level, deal with three basic objects:
Depending on the type of client application, one or more of these MAPI objects may be hidden from the client interface.
However, even though they may not be visible to the application user, all three objects are present as part of the MAPI
architecture. The next three sections describe each of the MAPI client objects and their functions in the MAPI model.
The MAPI system exists in order to move messages from one location to another. Therefore, the heart of the system is
the MAPI message object. All message objects must have two components-a message header and a message body. The
message header contains information used by MAPI to route and track the movement of the message object. The
message body contains the actual text message portion of the message object. Even though every message object must
have a message body, the body can be left blank. In addition to the two required components, message objects can also
have one or more attachments. Attachments can be any valid operating system file such as an ASCII text file, a binary
image, or an executable program.
Note
The item names given in Table 4.1 do not necessarily correspond to a valid
property or variable name in programming code. The actual property names
for each item can vary from one code set to another. Also, the order in
which these properties appear differs greatly for each MAPI
implementation. The chapters following this one focus on different code
sets (CMC API, MAPI Controls, OLE Messaging) and detail the exact
keywords and property names used to access the items described here.
Along with this basic set, additional data items may appear or be available to the programmer. These additional items
add functionality to the MAPI interface, but because they are not part of the core set, you cannot expect them to be
available to you when you write your programs. Table 4.2 contains a list of additional header data items.
Table 4.2. Optional items that may be found in the message header.
Property Name Type Description
Flag that indicates the sender asked for a return
receipt message upon either delivery of the message
DeliveryReceipt Boolean
to the recipient or the reading of the message by the
recipient.
A value that indicates the relative importance of the
Importance Long message. Currently, Microsoft Mail clients recognize
three priorities: High, Medium, and Low.
Flag that indicates the item has been sent to the
Submitted Boolean
recipient.
Sent Boolean Read/write.
Signed Boolean Read/write.
Value that identifies this message as one of a class of
messages. Currently, Microsoft Mail systems
recognize the IPM (Interpersonal Message) type for
Type String sending messages read by persons. Microsoft has
defined the Ipc (Interprocess Communication) type
for use between program processes. Other types can
be defined and used by other programs.
Flag that indicates whether the message has been
Unread Boolean
received and/or read by the recipient.
The message body contains the text data sent to the recipient from the sender. For most systems, this is a pure ASCII text
message. However, some service providers can handle rich-text format message bodies, which allows for additional
information such as font, color, and format codes to be included in the message body (see Figure 4.2).
The availability and support of rich-text message bodies vary between service providers. Some service providers allow
you to create rich-text message bodies but translate the information into simple ASCII upon delivery. For example,
Microsoft Exchange allows users to build rich-text message bodies but translates that message into simple ASCII text
when using the SMTP service provider. All messages received by the Microsoft Mail system are converted into simple
text as well. This behavior ensures that the message will be delivered but may result in surprise or even unreadable
material at the recipient's end (see Figure 4.3).
Figure 4.3 : Receiving a rich-text message that has been changed to simple ASCII.
Other transport providers may transmit the rich-text and allow the receiving message provider to handle the translation to
simple ASCII, if needed. This option allows for the most flexibility but can result in undeliverable messages. For
example, Microsoft Exchange users have the option of sending rich-text messages through the CompuServe message
transport. The CompuServe transport supports rich-text messages. Rich-text messages sent from one Windows
Messaging Client to another by way of the CompuServe transport retain their original layout and look. However,
recipients using something other than Microsoft Exchange may see something different. For example, the WinCIM
client provided by CompuServe treats rich-text messages as binary attachments to a simple ASCII text message.
WinCIM clients would receive the message shown in Figure 4.2 as an attachment to view with Microsoft Word or some
other viewer.
If, however, the CompuServe recipient is defined as an Internet user through the CompuServe address (for example,
102461.1267@compuserve.com), the CompuServe message transport reports the message as undeliverable when it is
discovered that the message body contains anything other than simple ASCII text (see Figure 4.4).
Message Attachments
Message attachments are supported by all forms of Microsoft MAPI. A MAPI attachment can be any data file, of any
type (text, binary programs, graphics, and so on). These attachments are sent to the recipient along with the message
header and body. Upon receipt of the message, the recipient can, depending on the features of the message client
software, view, manipulate, and store the attachments on the local workstation. The MAPI system keeps track of
attachments with a set of properties. Table 4.3 lists an example set of attachment properties. The actual properties and
their names can differ between program code sets.
Table 4.3. Example MAPI attachment properties.
Property Name Type Description
Each message object can contain more than one
Index Long
attachment. Attachments are numbered starting with 0.
The name to display in a list box or in the client message
Name String
area (for example, "June Sales Report").
A value that indicates where in the message body the
attachment is to be displayed. Microsoft Mail and
Windows Messaging clients display an icon representing
Position Long
the attachment within the message body. Other clients
may ignore this information, show an icon, or show ASCII
text that represents the attachment.
The exact filename used by the operating system to locate
Source String and identify the attachment (for example, "\\Server1\Data
\Accounting\JuneSales.xls").
A value that indicates the type of attachment. Microsoft
defines three attachment types:
Type Long Data-A direct file attachment
Embedded OLE-An embedded OLE object
Static OLE-A static OLE object
Attachments can be handled differently by the message transport provider. Microsoft Mail and Windows Messaging
clients display attachments within the message body and transport the attachments as part of the message body, too.
Microsoft Mail and Microsoft Exchange recipients see the attachment within the message body and can use the mouse to
select, view, and save the attachment when desired. Other transports may handle the attachment differently.
SMTP transports will report a message that contains attachments as undeliverable unless the transport supports MIME or
some other binary transfer protocol. The Internet transport that ships with the Windows Messaging Client does support
MIME protocol.
The CompuServe service provider for Microsoft Exchange will transport the attachments as additional messages
addressed to the same recipient. For example, a single ASCII text message with one attachment would be received as
two messages when sent via the CompuServe transport.
Note
The CompuServe message transport for Microsoft Exchange supports only
attach-ments for native, or CompuServe, addresses. You cannot use the
CompuServe transport to send attachments to an SMTP-defined Internet
account (such as 102461.1267@compuserve.com).
Storage Folders
MAPI messages can be saved in storage folders. The MAPI model defines several storage folders. Figure 4.5 shows a set
of folders as viewed from the Windows Messaging Client.
Figure 4.5 : Viewing the folders from the Windows Messaging Client.
Not all implementations of MAPI support all of the folders listed above. For example, the Simple MAPI interface allows
access to the Inbox (by way of the .Fetch method of the MAPISession Control) and the Outbox (by way of the .
Send method of the MAPISession Control). Simple MAPI allows no other folder access. Programmers cannot inspect
the contents of the Sent folder or move messages from the Inbox to other user-defined storage folders.
The OLE Messaging library offers a more complete access to the MAPI storage system. The Inbox and Outbox
folders are accessible by name (that is, Session.Inbox and Session.OutBox). The Sent and Deleted folders
are available. The OLE MAPI implementation allows access to all folders through the InfoStore object. An
Infostore object contains all folder and message objects. All MAPI service providers can implement their own
message stores, and a single client can have access to more than one message store at the same time. Therefore, a single
MAPI session can involve attaching to several message stores and presenting their contents to the user for handling.
Note
The OLE Messaging library does not allow programmers to create or delete
folders from the InfoStore collection. Only the MAPI 1.0 API set
available from C++ allows programmers to create and delete folders from
the storage set.
The MAPI storage folders are defined as a hierarchy (as seen in Figure 4.5). The MAPI model allows for the creation of
multiple levels of the hierarchy at any time. The MAPI model allows for the creation of subfolders at any level and on
any folder. For example, the Inbox folder can have several subfolders such as UnRead, Read, Urgent, and Unknown.
The addition of subfolders allows users to organize message stores based on use and preference.
The MAPI model defines a handful of properties for storage folders. These properties are available through the OLE
Messaging library and C++. Table 4.4 lists some of the more commonly used properties of the storage folder.
Physical organization of the storage folders can differ greatly depending on the service provider. For example, the
Microsoft Mail Server storage arrangement involves a single directory on the disk that identifies the post office and
subsequent directories underneath that correspond to an individual user's InfoStores (and subfolders within the
storage). However, the physical organization of storage folders for the Windows Messaging Client that ships with
Windows 95 involves a single data file (usually found in \Exchange\mailbox.pst). This single file is used to store
all the personal messages maintained on the user's workstation. It is up to the service provider to establish the storage
details and to support access to the physical storage using the pre-defined InfoStore, Folder, and Message
objects.
Addresses
Addresses are the last class of objects dealt with at the client level. Every electronic message has at least two address
objects: the sender object and the recipient object. MAPI allows you to add several recipient address objects to the same
message. Each address object has several properties. Table 4.5 shows a set of sample properties for the MAPI Address
object.
MAPI address objects are a part of every MAPI message and are stored in the MAPI address book. You'll learn more
about the address book in the following section on MAPI Server objects.
The MAPI Server
The MAPI Server handles all the message traffic generated by MAPI clients. The MAPI Server usually runs on a
standalone workstation connected to the network, but this is not a requirement. There are versions of user-level MAPI
servers that can be used to handle message services.
The Microsoft Mail Server runs standalone on both Intel pcs or Apple workstations. It provides direct MAPI services for
all connected MAPI users and also provides gateway MAPI services for remote users. The Microsoft Mail Server has,
until recently, been Microsoft's primary electronic mail server. Even though Microsoft is stressing the early adoption of
the new Microsoft Exchange Server for NT, the Microsoft Mail Server will continue to be the primary mail server for
thousands of users. All MAPI Clients can share information with connected Microsoft Mail Servers regardless of the
client platform (Win31, WinNT, or Win95).
The Microsoft Exchange Server runs as a service on an NT Server workstation. It provides MAPI services to all MAPI
users. Unlike the Microsoft Mail Server, which distinguishes between local and remote users, the Microsoft Exchange
Server treats all MAPI users as remote users. This simplifies several aspects of MAPI administration. Unlike the
Microsoft Mail Server, which only supports Microsoft Mail format messages, the Microsoft Exchange Server supports
multiple message formats and services, including Microsoft Mail. This also means that the administration of gateways
and remote transports is quite different for Microsoft Exchange.
Microsoft also supports two peer-to-peer message servers. These servers run on the user's workstation, usually as a part
of the MAPI client software. The two client-level MAPI servers provided by Microsoft are
The WorkGroup Post Office runs as a limited version of the Microsoft Mail Server. Clients that use Microsoft's peer-to-
peer networking are able to establish a post office on a single workstation and then share the post office directories with
other peers on the network. The design and operation are very much like the Microsoft Mail Server system, but it runs on
an individual pc. The primary advantage of the peer-to-peer version is that a single user can set up a WorkGroup Post
Office and use that as a base for adding remote mail connections and fax support. The main disadvantage of the peer-to-
peer client is that users were not able to attach to both the WorkGroup Post Office and an existing Microsoft Mail Server
post office.
With the introduction of Windows 95, Microsoft introduced a client version of Microsoft Exchange that provides the
same features as the Microsoft Exchange Server version. Users are able to install and share a WorkGroup Post Office
and are also able to attach to existing Microsoft Mail post offices. In addition, users can connect using other mail
transports as they become available.
Regardless of the actual server application used, the same basic processes must occur for all MAPI server systems. The
three main tasks of all MAPI servers are
Message Transport
Message Transport is the process of moving messages from one place to another. Under the MAPI model, message
transport is a distinct, and often separate, process. MAPI 1.0 allows for the use of external message transports. In other
words, programmers can write software that knows how to handle a particular type or types of message formats and
register this transport mechanism as part of the MAPI system. This allows third-party vendors to create format-specific
transports that can be seamlessly integrated into the MAPI system.
It is the message transport that knows just how to format and, if necessary, pre-process messages for a particular
messaging format. The message transport knows exactly what information must be supplied as part of the message
header and how it needs to be arranged. The message transport also knows what types of message bodies are supported.
For example, SMTP format allows only text message bodies. However, the Microsoft Network message format allows
rich-text message bodies. It is the job of the message transport to keep track of these differences, modify the message
where appropriate, or reject the message if modification or pre-processing is not possible.
One of the key features of the MAPI model is the provision for multiple message transports within the MAPI system.
Once message transports are installed (or registered) with a MAPI client application, they are called into action
whenever the pre-defined message type is received by the MAPI client software. Since MAPI is designed to accept the
registration of multiple transports, the MAPI Client is potentially capable of handling an unlimited number of vendor-
specific message formats.
Note
Message types are stored as part of the address. These types were discussed
earlier in this chapter in the "Addresses" section.
Figure 4.6 shows how the multiple message transports are used when messages are received by the MAPI Client.
Under the MAPI system, message transports provide another vital service. It is the responsibility of the message
transport to enforce any security features required by the message format. For example, the MSN mail transport is
responsible for prompting the user for a username and password before attempting to link with the MSN mail system.
It is important to note that the message transport is not responsible for storing the messages that have been received. The
transport is only in charge of moving messages from one location to another. Message storage is discussed in the next
section.
Message Stores
Message stores are responsible for providing the filing system for the messages received via the message transport. The
MAPI model dictates that the message store must be in a hierarchical format that allows multilevel storage. In other
words, the system must allow users to create folders to hold messages, and these folders must also be able to hold other
folders that hold messages. Under the MAPI model, there is no limit to the number of folder levels that can be defined
for a message store (see Figure 4.7).
Under the MAPI model, storage folders can have properties that control how they are used and how they behave. For
example, storage folders can be public or private. Folders can have properties that make the contained messages read-
only to prevent modification. The options available depend on the implementation of the message store. In other words,
the programmer who designs the message store can establish the scope of storage options and the MAPI Client will
comply with those rules.
As in the case with message transports, MAPI clients can access more than one message store. Figure 4.8 shows the
Windows Messaging Client that is currently accessing two different message stores. You can see that each store has its
own set of folders and its own set of messages.
The Windows Messaging Client that ships with Microsoft Exchange Server also allows you to create folder column,
grouping, sort, and filter rules for personal and public folders. By doing this, you can create storage views that reflect the
course of an ongoing discussion and allow for easy search and retrieval of data kept in the message store (see Figure
4.9).
Address Books
The last of the main MAPI server objects is the address book. The MAPI address book contains all the directory
information about a particular addressee. The book can contain data for individual users or groups of users (a distribution
list). The minimum data stored in the address book is the user's display name, the transport type, and the user's e-mail
address.
Additional information such as mailing address, telephone number, and other data may be available depending on the
design of the address book.
Address books, like the other objects described in this chapter, work independently under the MAPI model. Also, the
MAPI client can access more than one address book at a time. This means that several address books of various formats
can all be viewed (and used) at the same time when composing messages (see Figure 4.10).
Along with storing addresses, the address book interface also acts to resolve display names used in the MAPI interface
with the actual e-mail addresses and transport types for those display names. To do this, MAPI offers a ResolveName
service that performs lookups upon request. The ResolveName service is able to look at all address books (regardless of
their storage format) in order to locate the proper e-mail address.
Users are also able to designate one of the address books as the default or personal address book. This is the first address
book in which new addresses are stored and the first address book that is checked when resolving a display name. The
Windows Messaging Client and the Microsoft Mail client both ship with default personal address books. The Windows
Messaging Client allows users to add new address books and designate their own personal address book container.
The MAPI Spooler is a special process that interacts with both message stores and message transports. It is the spooler's
job to route messages from the client to the proper transport and from the transport to the client. The spooler is the direct
link between the client and the transport. All messages go through the MAPI Spooler.
Note
Actually there are some cases in which a message moves directly from the
message store to the message transport. This occurs when service providers
offer both message store and message transport. E-mail service providers
that offer these features are known as tightly coupled service providers.
Figure 4.11 illustrates the different paths messages can take when the MAPI Spooler is used.
Figure 4.11 : The MAPI Spooler passes messages between the message store and the message transport.
As each message is moved from the message store (the "outbox") to the transport, the MAPI Spooler checks the address
type to see which transport should be used. Once this is determined, the spooler notifies the transport and attempts to
pass the message from the message store to the message transport. If the transport is not currently available, the MAPI
Spooler holds onto the message until the transport is free to accept messages. This allows transport providers to act as
remote connections without any additional programming or addressing on the client side.
Note
In fact, the implementation used in the Microsoft Exchange version of
MAPI treats all connections as if they were remote-even when the message
is moved from one user's Microsoft Exchange outbox to another Microsoft
Exchange user's inbox on the same network.
In the case of messages that move along a constantly connected transport (that is, between two addresses on the same
Microsoft Exchange Server), the spooler notifies the transport (Microsoft Exchange) and the transport accepts the
message almost immediately. Often the user is not aware of any delay in the handling of the message.
In the case of messages that move from the Windows Messaging Client by way of an SMTP transport through a dial-up
connection to the Internet, the MAPI Spooler holds onto the message until the user connects to the Internet account
through Win95 Dial-Up Networking. Once the connection is made, the MAPI Spooler sends all local messages on to the
Internet mail server, retrieves any waiting mail from the mail server, and passes these new messages to the appropriate
message store.
The MAPI Spooler is also able to move a single message to several recipients when some of those recipients are not
using the same message transport. For example, users can build distribution lists that contain names of users on the local
Microsoft Exchange Server, users who have addresses on a local Microsoft Mail Server, and users who can only be
contacted through a fax address. When the message is sent, it moves from the message store to the spooler, which then
sorts out all the transports needed and passes the messages on to the correct transports at the first available moment.
The MAPI Spooler is also responsible for marking messages as read or unread, notifying the sender when a message has
been successfully passed to the transport, and, when requested, notifying the sender when the recipient has received (or
read) the message. The MAPI Spooler also reports when messages cannot be sent due to unavailable transports or other
problems.
Summary
In this chapter, you learned about the general architecture of the MAPI system. You learned that there are two main
components to the system:
You learned that the MAPI Client resides on the user's desktop and handles three main MAPI objects:
This chapter also reviewed the basic properties and features of MAPI messages, including message headers, folders, and
address objects.
You learned that the MAPI Server usually resides on a standalone workstation connected to the network (although not
always). Like the MAPI Client, the MAPI Server handles three main objects:
● Message transports
● Message stores
● Address books
You learned that the MAPI model allows users to use multiple versions of message transports (such as Microsoft
Exchange Server messages and SMTP Internet messages), message storage, and address books. You also learned about
the MAPI Spooler. The MAPI Spooler is the process that moves items from the message store to the appropriate
provider.
Now that you know the basic architecture of the MAPI system, it's time to build some applications that use the system.
In the next chapter, you'll learn how to use the Microsoft Exchange Forms Designer to build MAPI-enabled applications
that work within the Microsoft Exchange interface.
Chapter 8
CONTENTS
● Introduction
● The Session Object
❍ The Session Object Methods
● Summary
Introduction
One of the new features of Microsoft Exchange is the creation of the OLE Messaging library. This set of OLE objects,
properties, and methods allows any VBA-compliant development tool to gain access to MAPI services and incorporate
them into desktop applications. This chapter shows you how the OLE Messaging library works and how you can use the
OLE objects to create MAPI-enabled programs.
This chapter provides an overview of all the OLE Messaging library objects and gives examples of their use. You'll learn
about the following objects:
You'll also learn how these objects interact with each other and how to use them to perform several advanced MAPI
tasks, including:
When you complete this chapter, you'll understand the OLE Messaging library objects and how to use them to build
MAPI-compliant e-mail applications with any VBA-compatible development tool.
The Session object of the OLE Messaging library is the top-most object in the hierarchy. You must create an instance
of a MAPI Session object before you can gain access to any other aspects of the MAPI system. The Session object
has several properties (including subordinate objects) and a handful of methods that you can invoke.
Table 8.1 shows the Session Object Methods along with a list of parameters and short descriptions.
The most used of these methods are the Logon and Logoff methods. You use these to start and end MAPI sessions.
You will also use the AddressBook and GetInfoStore methods frequently in your programs.
If you have not done so yet, load Visual Basic 4.0 and start a new project. Place a single button on the form. Set its index
property to 0 and its caption to MAPI &Start. Copy and paste a second command button onto the form and set its
caption property to MAPI &End. Now add the code in Listing 8.1 to the Command1_Click event of the buttons.
The code in Listing 8.1 calls two subroutines-MAPIStart and MAPIEnd. They each use a form-level variable called
objSession. Add this variable to the general declarations section of the form.
Option Explicit
'
Dim objSession As Object ' for mapi session
Now create a new subroutine called MAPIStart and add the code shown in Listing 8.2. This routine initializes the
session object and calls the MAPI logon dialog box.
It is important that you formally end each MAPI session you begin. This will ensure that you do not have any stray
sessions running in the background.
Warning
By default, the MAPI Logon method will attempt to connect you to the
first available active session currently running on your workstation. For
example, if you have started a MAPI session with your e-mail client and
you then run this sample code, MAPI will attach this program to the same
MAPI session started by the MAPI client. This could give you unexpected
results. That is another reason why you should always close your MAPI
sessions when you are exiting your programs.
Now add a new subroutine to the project called MAPIEnd and add the code shown in Listing 8.3. Notice that this routine
sets the objSession variable to Nothing. This is done to clear Visual Basic's memory storage and conserve RAM
space.
Save the form as OML.FRM and the project as OML.VBP. You can now run the project and log into and out of a MAPI
session. You won't see much, but it works!
Add another command button to the array (Edit | Copy, Edit | Paste) and modify the Command1_Click event
to match the code in Listing 8.4. This will call up the MAPI address book.
Listing 8.4. Modifying the Command1_Click event to call the address book.
Now add the MAPIAddrBook routine to the form and enter the code in Listing 8.5.
Save and run the project. After clicking the MAPI Start button, press the Address Book button. Your screen
should look like the one in Figure 8.1.
You can set several parameters when you call the Address Book method. For example, you can set the title of the
address book using the Title property. You can also control the number and caption of the recipient selection buttons
that appear on the address book. You can even set the address book dialog so that the user can review the addresses but
cannot select one.
Listing 8.6 shows you how to modify the code in the MAPIAddrBook routine to set the title and remove all recipient
selection buttons.
When you run the project, your address book will look something like the one in Figure 8.2.
You can also control the Logon method behavior by passing selected parameters. The most common use for this is
automatically logging a user into MAPI without the use of the Logon dialog box. To do this you need to pass the user
profile and password and set the ShowDialog flag to false.
Listing 8.7 shows you how to modify the MAPIStart routine to perform an automatic logon. You should change the
ProfileName parameter to match your personal Microsoft Exchange logon.
Now let's look at some of the Session object properties. After reviewing the properties, you can add another button to
the form to display the properties of your MAPI session. Table 8.2 has a list of the Session object properties, their
type, and a short description.
The Session object has several properties, many of them objects themselves. Using these object properties allows you
to gain access to more complex data than standard strings or numbers. You'll inspect the object properties later in the
chapter.
Now add another button to the control array (use Edit | Copy, Edit | Paste), set its Caption to "MapiProps" and
modify the code in the Command1_Click event to look like the code in Listing 8.9.
Now create a new subroutine called SessionProps and enter the code that appears in Listing 8.10. This routine
creates a message box that displays several of the Session object's properties.
Save the project again and run it. After clicking on the MAPI Start button, click on the MapiProps button. Your
screen should look like the one in Figure 8.3.
Figure 8.3 : Displaying the Session properties.
Now it's time to start inspecting the subordinate objects in the OLE Messaging library.
The first-level subordinate object of the Session object is the InfoStore object. Each InfoStore object
represents a separate message store. The MAPI model allows clients to access more than one message storage system at
the same time. For example, the Microsoft Exchange shared folders are a separate message store (represented by a
separate InfoStore object). The Microsoft Exchange Personal Folders are another separate message store. Users can
have any number of message stores connected to their MAPI client.
The InfoStores collection object is an OLE Messaging library object that allows you to view all the connected
message stores for the logged-in user. You can use the InfoStores object to locate a particular message store and
then access that message store using the InfoStore object.
Note
Be sure not to confuse the InfoStore object with the InfoStores
object. The InfoStore object is the OLE Messaging library object that
you use to gain access to the contents of a single message store. The
InfoStores object is the OLE Messaging library object you use to gain
access to the collection of message stores. In Microsoft OLE naming rules,
collection objects are plural (InfoStores) and single objects are not
(InfoStore).
The InfoStores collection has only a few properties and no methods at all. You cannot add, modify, or delete
InfoStore objects using the OLE Messaging library interface. Table 8.3 shows the InfoStores properties with
their type and a short description.
To test the InfoStores object, add another button to the control array on the form with the caption of
InfoStoreColl and modify the Command1_Click event as shown in Listing 8.11.
Listing 8.11. Updated Command1_Click event.
Next you'll need to add two new form-level variables to the general declaration section of the form. Your general
declaration section should now look like this:
Option Explicit
'
Dim objSession As Object ' for mapi session
Dim objInfoStoreColl As Object ' collection of stores
Dim objInfoStore As Object ' single info store
Now add the SessionInfoStoreColl routine shown in Listing 8.12. This routine gets the InfoStores
properties and displays the names of all the message stores in the collection.
Warning
The OLE Messaging library does not require message stores to have unique
names. If you are using the InfoStores collection object to locate a
particular message store, you must be sure that there is not more than one
store with that name in the collection! InfoStore objects are assigned a
unique unchanging ID value. Once you know the ID value of an
InfoStore object, you can locate it using the GetInfoStore method
of the Session object.
Save and run this project. After logging in, press the InfoStoreColl button. Your screen should look similar to the
one in Figure 8.4.
Now that you know how to review the InfoStore collection, it's time to learn more about the individual InfoStore
objects.
The InfoStore object contains all the folders and messages defined for a single message store. InfoStore objects
have several properties and no methods. You cannot use the OLE Messaging library to add, modify, or delete
InfoStore objects. Table 8.4 shows the important InfoStore object properties, their types, and their descriptions.
Now add some code to view the properties of an InfoStore object. First, add another command button to the control
array and set its caption to InfoStore. Then modify the Command1_Click routine so that it matches the one in
Listing 8.13.
Now add the InfoStoreProps subroutine and enter the code shown in Listing 8.14.
Note that the InfoStore object is part of what is called a small collection. OLE object collections that are considered
to have a limited number of members are called small collections. All small collection objects have an Index property
and a Count property. Most of them also have an Item property. Small collection objects support the use of the For
Each...Next programming construct.
Save and run the project. After clicking the InfoStore button, you should see a series of dialog boxes showing the
properties of each message store available to your client (see Figure 8.5).
The next object to review is the Folder object and the Folders collection object.
One of the first level objects below the InfoStore object is the Folder object. The Folder object can hold
messages and other folders. Each InfoStore object also has a Folders collection object that contains a list of all the
Folder objects in the message store.
There is no limit to the number of messages or folders a Folder object can have. For this reason it is called a large
collection object. large collection objects do not have an Index property or a Count property. The only
way you can locate all the folders in a message store is to "walk through" the store using a set of methods to get each
item. All large collection objects support the use of the GetFirst, GetNext, GetPrevious, and
GetLast methods to provide a way to navigate through the collection. You'll use these methods in the next few
examples.
The Folders collection object has only a few properties and methods. Table 8.5 shows the important Folders
collection object properties and Table 8.6 shows the Folders collection object methods.
To test the Folders object, add two new variables to the general declaration area of the form. Your form-level variable
list should now look like this:
Option Explicit
'
Dim objSession As Object ' for mapi session
Dim objInfoStoreColl As Object ' collection of stores
Dim objInfoStore As Object ' single info store
Dim objFolderColl As Object ' collection of folders
Dim objFolder As Object ' single folder
Now add another command button to the control array and set its Caption property to FolderColl. Then modify the
Command1_Click event so that it matches the code in Listing 8.15.
Now add the new subroutine called FoldersColl and enter the code from Listing 8.16.
The FoldersColl routine shows the application and class property of the object and then lists all the folder names in
the collection. Note that you cannot determine the folder hierarchy from the list returned by the Get methods. The Get
methods traverse the folder collection in the order the folders were created, not in the order they are displayed or
arranged within the MAPI client.
Save and run the program. Clicking the FoldersColl button should give you a display similar to the one in Figure
8.6.
The Folder object has several properties and one method. The OLE Messaging library allows you to modify the Name
property of a Folder object, but you cannot add or delete a Folder object from the message store. Table 8.7 contains
the list of important Folder object properties.
Add a new button to the command array and set its Caption property to Fo&lder Object. Then modify the
Command1_Click event to match the one in Listing 8.17.
Save and run the project. Then click the Folder Object button and compare your results with those shown in Figure
8.7.
The only method available for Folder objects is the Update method. You can use this method to save changes made
to the Folder object properties. The only property you can modify is the Name property. If you wish to change the
name of an existing folder you can use the following line of code:
Note
The MAPI system will not let you modify the name of the Inbox,
Outbox, Sent Items, or Deleted Items folders. Attempting to do
this will cause MAPI to return an error to your program.
There are two folders that are used for almost every MAPI message transaction:
Note
The InfoStore object was discussed in the earlier section of this chapter
titled "The InfoStore Objects and Collections."
You can modify the FolderProp routine to access the properties of the Inbox by changing the line that sets the
objFolder object.
'
'Set objFolder = objSession.InfoStores.Item(1).RootFolder
Set objFolder = objSession.Inbox
'
Now when you click the Folder Object button, you'll get data on the Inbox folder in message store #1. This works
the same way for the Outbox.
The Message object and Messages collection object are the heart of the OLE Messaging library. These objects hold
the actual messages composed and received by the MAPI client. You will use the Message objects to read, modify,
create, and delete messages from the message store.
The Messages collection object has very few properties and a number of methods. Because the Message collection is
a large collection (that is, it has an unlimited number of members), you must use the GetFirst, GetNext,
GetPrevious, and GetLast methods to retrieve messages from the collection. You can also add and delete
messages within the collection.
Table 8.8 shows the properties for the Messages collection object.
Table 8.9 shows the list of methods for the Messages collection object.
To test the Messages collection object, you first need to add two new variables to the general declaration area of the
form. Make sure your variables match the ones in Listing 8.19.
Option Explicit
'
Dim objSession As Object ' for mapi session
Dim objInfoStoreColl As Object ' collection of stores
Dim objInfoStore As Object ' single info store
Dim objFolderColl As Object ' collection of folders
Dim objFolder As Object ' single folder
Dim objMessageColl As Object ' messages collection
Dim objMessage As Object ' single message
Next add a new button to the control array and set its caption to M&essageColl. Then modify the
Command1_Click event to match the code shown in Listing 8.20.
Then add the new MessagesColl subroutine to the project and enter the code from Listing 8.22.
Save and run the project. Your screen should look similar to the one in Figure 8.8.
You can sort the messages in the collection using the Sort method. Setting the sort order affects how the Get methods
access the messages in the collection. The sort order is based on the TimeReceived property of the messages. The sort
method can be set for None(0), Ascending(1), or Descending(2). The default SortOrder is None(0). If
there is no sort order specified, the Get methods access the messages in the order in which they were added to the
message collection.
Tip
Because the sort order affects the way in which the Get methods access the
file, setting the SortOrder to Ascending(1) and performing the
GetFirst method on the messages collection will return the same
message as setting the SortOrder to Descending(2) and performing
the GetLast method on the messages collection.
Modify the MessageColl routine to ask you for a sort order. Add the lines of code to the routine as indicated in
Listing 8.23.
Now when you press the MessageColl button you'll be asked to supply a sort order and then be presented with a
different view of the message collection list.
You can delete all the messages in a message collection by invoking the Delete method followed by the Update
method on the parent object to save the change. The code below shows how this is done.
Set objMessageColl = objSession.Inbox.Messages ' get inbox
messages
objMessageColl.Delete ' dump all messages in the inbox
objSession.Inbox.Update ' make the modification permanent
You'll learn about the Add method in the section on the Message object.
The Message object is the richest object in the OLE Messaging library system. It has only a few methods, but they are
all quite powerful. They allow you to delete the message from the collection (Delete), display a message option dialog
box (Options), display a send dialog box (Send), and save the modified message properties (Update). These
properties range from handling attachments to flagging a message as read or unread. Table 8.10 shows the Message
object methods with their parameters and a short description.
The Message object properties are extensive and powerful. The properties range from simple Boolean flags to indicate
the importance of a message, to the actual text body of the message, to the list of binary attachments to the message.
Table 8.11 shows the important Message object properties, their type, and a short description.
IPM-Interpersonal messages.
These are recognized by the
MAPI client and displayed in
Type String
the Inbox when received.
Ipc-Interprocess
communications. These are
not recognized by the MAPI
client, but can be processed
behind the scenes.
Flag indicating whether the
Unread Boolean
message has been read.
Now add another button to the control array and set its caption to MessageProps. Then modify the
Command1_Click event as shown in Listing 8.24.
Next add a new subroutine called MessageProps to the form and enter the code from Listing 8.25.
The MessageProps routine first selects the session's Inbox message collection and then selects the first message in
the collection.
Warning
This routine crashes with an error if no message is present in the Inbox. If
you do not currently have at least one message in the Inbox, fire up an e-
mail client and place one there before running this routine. In production
applications, you should make sure you trap for such an error to prevent
needless (and annoying) crashes.
Notice the addition of the On Error Resume Next just before the series of statements that retrieve the message
properties. This is done in case one or more properties are missing in the selected message. Since MAPI supports
messages from several different providers, it is quite likely that some of the properties will contain garbage or nothing at
all. You should keep this in mind when writing routines that read Message object properties.
Save and run the project. After starting MAPI and pressing the MessageProps button, you'll see something like the
data shown in Figure 8.9. Of course, the actual values of the properties will vary depending on the message you are
reading.
It's easy to add new messages to the message collection. The simplest method is to use the Send method of the
Message object to invoke the MAPI Send dialog box. From this dialog box, the user can compose, address, and send
the message without any additional coding.
Add a new button to the control array and set its caption property to MessageAdd. Then update the
Command1_Click event so that it matches the one in Listing 8.26.
Listing 8.26. Updating the Command1_Click event.
Now add the MessageAdd subroutine and enter the code from Listing 8.27. This will create a new Message object in
the message collection and then bring up the MAPI Send dialog box.
After adding this routine, add a new button to the array and set its caption property to MessageMake. Also add another
line to the Select Case structure to handle the button selection (see Listing 8.29).
You can also set several message options by calling the Options method to bring up the Options dialog box. Modify
the code in the MessageMake routine to call the Options method before the Send method. Insert the line shown
below into the code just before the objMessage.Send line.
When you save and run the project, press the MessageMake button. You'll see the Options dialog box before you see
the Send dialog box. The exact layout and contents of the Options dialog box depend on the type of message you are
working with and the available message transports at your workstation (see Figure 8.10).
You can also compose a complete message, address it, and send it without the use of dialog boxes. However, to do that
you need to create at least one recipient object. You learn how to do that in the next section.
The OLE Messaging library defines two address objects-the Recipient object and the AddressEntry object. The
two objects are related, but serve different purposes. The purpose of the Recipient object is to provide valid
addressing information for the message. The purpose of the AddressEntry object is to gain access to individual
records in the MAPI address book.
The next several sections outline the properties and methods of both the Recipient and AddressEntry objects.
The Recipients collection object holds a list of all recipients for a message. Every message has a recipients
collection-even if the message has only one recipient in the collection. There are three methods for the collection and a
handful of properties. Because the Recipients collection is a small collection, it has Count and Item properties.
This also means that the Recipient object supports an Index property.
Table 8.12 shows the Recipients object methods and Table 8.13 shows the object's properties.
To test the properties of the Recipients collection object, add a new button to the control array and set its caption to
RecipColl. Then add a new line in the Command1_Click event to match the code in Listing 8.30.
Before coding the collection routine, you must first add three new variables to the general declaration area of the form.
Modify the declaration list to match the code in Listing 8.31.
Option Explicit
'
Dim objSession As Object ' for mapi session
Dim objInfoStoreColl As Object ' collection of stores
Dim objInfoStore As Object ' single info store
Dim objFolderColl As Object ' collection of folders
Dim objFolder As Object ' single folder
Dim objMessageColl As Object ' messages collection
Dim objMessage As Object ' single message
Dim objRecipColl As Object ' recipient collection
Dim objRecipient As Object ' single recipient
Dim objAddrEntry As Object ' addressentry object
Next add the RecipColl subroutine to the project and enter the code in Listing 8.32.
The RecipColl routine locates the first message in the Inbox collection and then displays the Recipients
collection object properties. Notice that the routine calls the Name property of the Item object in the collection. The
Item(1) property points to the first recipient object in the collection.
In Figure 8.11 you can see the results of a message with three recipients. Your exact results will vary based on the
message you select.
Using the Delete method will remove all the Recipient objects from the collection. After calling the Delete
method, you must call the Update method on the Message object to make the changes permanent. Once the recipients
are deleted, they cannot be recovered. The following code sample illustrates how to remove all recipients from a
collection:
objMessage.Recipients.Delete
objMessage.Update
You can use the Add method to add a new recipient to a message, as explained in the next section.
The Recipient object holds all the information needed to address a message to its destination. Along with the usual
properties, the Recipient object has an AddressEntry property. This property points to a valid AddressEntry
object. You'll learn more about the AddressEntry object in the next section. The methods for the Recipient object
allow you to delete the recipient from the collection and validate the address before attempting to send the message.
Table 8.14 shows the Recipient object methods, and Table 8.15 shows the Recipient object properties.
TransportType:
EmailAddress. The
Address String TransportType is taken from
the Type property of the child
AddressEntry object. The
EmailAddress is taken from the
Address property of the
AddressEntry object.
The underlying object that contains
detailed information on the
AddressEntry AddressEntry object recipient, including the exact
address and message transport
type.
Name of the library. Always set to
Application String
OLE/Messaging.
Internal identifying code for all
Class Long MAPI objects. Always set to 4 for
Recipient objects.
A value that indicates the type of
recipient. This is used to control
how the message is displayed by
client applications. Valid display
types are
Local User
Distribution List
Shared Folder
Agent
DisplayType Long
Organization
Private Distribution
List
Remote User
To view the properties of a Recipient object, add another button to the control array and set its caption property to
RecipProps. Then modify the Command1_Click event as shown in Listing 8.33.
You can use the Add method of the Recipients collection object to add a valid recipient to a message. To do this you
must have a Message object, access the recipients collection for that message, execute the Add method, and then
populate the new Recipient object properties. Once that is done, you must execute the Update method of the
message to save all changes.
Add a new button to the control array and set its caption property to RecipAdd. Be sure to modify the
Command1_Click event to match the code in Listing 8.35.
Now add the RecipAdd subroutine to the form and enter the code from Listing 8.36.
The RecipAdd routine first creates a new message in the outbox and then adds a new recipient to the message. Finally,
after validating the Recipient object, the routine updates all the changes and calls the Send dialog box (see Figure
8.12).
This routine could easily be modified to complete the send operation without presenting any dialog boxes. If you
change the ShowDialog parameter to False, the OLE Messaging library will post the message without interfacing
with the user.
The AddressEntry object is a child object of the Recipient object. The AddressEntry object contains all the
valid addressing information for a message system. The AddressEntry object is also the object that represents an
entry in the MAPI address book. In this way, the AddressEntry object provides a link between the MAPI address
book and MAPI messages.
The OLE Messaging library interface allows you to modify or delete AddressEntry objects from the MAPI address
book. However, there is no Add method for the AddressEntry object. You cannot use the OLE Messaging library to
create new entries in the MAPI address book. Table 8.16 shows the AddressEntry object methods, their parameters,
and brief descriptions.
The AddressEntry object has a handful of properties. These properties identify the message transport used to send
messages to this location, the actual address, and other internal properties. Table 8.17 shows the properties of the
AddressEntry object.
Table 8.17. The AddressEntry object properties.
Property name Type Description
The electronic mail address of the
Address String
record.
Name of the library. Always set to
Application String
OLE/Messaging.
Internal identifying code for all
Class Long MAPI objects. Always set to 8 for
AddressEntry objects.
A value that indicates the type of
recipient. This is used to control
how the message is displayed by
client applications. Valid display
types are
Local User
Distribution List
Shared Folder
Agent
DisplayType Long
Organization
Private Distribution
List
Remote User
Add a new button to the control array and set its caption to AddrProps. Then modify the Command1_Click event to
match the code in Listing 8.37.
Listing 8.37. Modifying the Command1_Click event.
Now add the AddrProps subroutine and enter the code in Listing 8.38.
When you save and run the project, then click AddrProps, you'll see a set of property information for the message
recipient. Although the details will vary, your screen should look like the one in Figure 8.13.
You can use the Details method to invoke a Details dialog box for an AddressEntry object. This is handy for
looking up additional information on a known AddressEntry object. Add another button to the control array and set
its caption to AddrDetails. Modify the Command1_Click event as shown in Listing 8.39.
'
End Sub
Now add the AddrDetails subroutine and enter the code from Listing 8.40.
After accessing the first message in the inbox and getting the AddressEntry of the first Recipient object of the
message, this routine invokes the Details method to show a dialog box that allows users to modify the fields of the
address book entry (see Figure 8.14).
Figure 8.14 : Viewing the Details dialog box for the AddressEntry object.
You can modify AddressEntry properties without invoking the Details method. For example, you can change the
Name property of an existing AddressEntry object. The following code fragment shows how this is done:
You can also delete an entry from the MAPI address book by using the Delete method. The following code example
shows how this can be done:
Warning
Once a Delete/Update method pair is invoked on an AddressEntry
object, it is permanently removed from the address book and cannot be
recovered. Use this method sparingly. And when you do provide delete
services in your programs, be sure to add a confirmation option dialog box
before permanently deleting the object.
The Attachment objects contain non-text data that is to be sent along with a standard message. Often this is a binary
graphic or formatted document file that is shipped from one user to another. The Attachment object methods and
properties allow you to read and write attachments to your messages.
There can be multiple attachments for each message. For this reason, the message object has an Attachments
collection object associated with it. All attachment objects are part of the Attachments collection.
The Attachments collection object is a child of the Message object and holds all the associated attachments for that
message. The Attachments collection is a small collection, so it supports the Count and Item properties and the
Attachment object supports the Index property.
Table 8.18 shows the Attachments collection object methods, and Table 8.19 shows the Attachments collection
object properties.
To test the Attachments collection object, add a new command button to the control array and set its caption to
AttachColl. Then modify the Command1_Click event to match the code in Listing 8.41.
Next you need to add two new variables to the general declaration section of the form. Modify your declarations to
match the code in Listing 8.42.
Option Explicit
'
Dim objSession As Object ' for mapi session
Dim objInfoStoreColl As Object ' collection of stores
Dim objInfoStore As Object ' single info store
Dim objFolderColl As Object ' collection of folders
Dim objFolder As Object ' single folder
Dim objMessageColl As Object ' messages collection
Dim objMessage As Object ' single message
Dim objRecipColl As Object ' recipient collection
Dim objRecipient As Object ' single recipient
Dim objAddrEntry As Object ' addressentry object
Dim objAttachColl As Object ' attachment collection
Dim objAttachment As Object ' single attachment
Now add the AttachColl subroutine to the form and enter the code that appears in Listing 8.43.
Warning
The AttachColl routine will crash if there are no attachments on the first
message in the inbox. Be sure to add a single message in the inbox with at
least one attachment before you run this routine. You can use your MAPI
client to create a new message with attachments and send it to yourself.
The AttachColl routine accesses the attachments collection of the first message in the inbox and displays its
properties. Your output may be different, but it should look something like Figure 8.15.
You can delete all the attachments in the collection by invoking the Delete method on the Attachments collection
object. The delete is not complete until you invoke the Update method on the parent object. And once the delete is
complete, you cannot recover the data.
You'll learn about the Add method in the next section on Attachment objects.
To review the attachment properties, add a new button to the control array and set its caption to AttachProps. Then
modify the Command1_Click event to match the code in Listing 8.44.
Now add the AttachProps subroutine and enter the code from Listing 8.45.
Save and run this routine. When you click on the AttachProps button, you will see the properties of the attachment.
Refer to Figure 8.16 for an example of the output.
You can use Visual Basic code to add an attachment directly to a message. This involves setting three properties and
invoking the ReadFromFile method. First, add another button to the control array and set its caption to AttachAdd.
Then modify the Command1_Click event to match the code in Listing 8.46.
Next, add a new subroutine called AttachAdd and enter the code shown in Listing 8.47.
Warning
Be sure to address the message to a valid person in your MAPI address
book. It is a good idea to address this message to yourself since you will be
reading and saving an attachment in the next section.
The AttachAdd routine creates a new message, adds the WIN.INI file as an attachment (at the first position in the
file), and then addresses the message and sends it. Your screen should look something like the one in Figure 8.17.
Tip
You could easily send the message without ever seeing the dialog box by
just setting the ShowDialog parameter to False.
Now you can read the message you just sent yourself. Add one more button to the command array and set its caption to
AttachSave. Then modify the Command1_Click event to match the code in Listing 8.48.
Now add the AttachSave subroutine and enter the code from Listing 8.49.
The AttachSave routine reads the last message added to the inbox collection (the one you sent just a moment ago)
and retrieves the attachment from the message's attachment collection. The attachment is then saved in the local disk
folder as SAVED.TXT (see Figure 8.18).
In production applications, you should add code to the routine to prompt the user for a folder and filename for storing the
attachment.
Summary
In this chapter, you learned how to use the OLE Messaging library to access features of MAPI. You learned all the major
objects, their methods, and their properties.
You also wrote several code examples that inspected and modified objects and their properties. You learned to use the
OLE Messaging library to
The OLE Messaging library is a rich and powerful set of commands. In the next several chapters, you'll use the OLE
Messaging library to build powerful desktop applications that use the advanced features of MAPI 1.0.
Chapter 7
CONTENTS
❍ Additional Features
● Summary
In this chapter, you'll learn how to use the MAPI controls from Visual Basic to create a simple program that can read and
reply to all e-mail sent to the logon ID. You'll also learn how to write routines to access all the electronic mail services
available to a basic MAPI client application on a desktop workstation. This includes creating new e-mail messages,
checking the inbox for new mail, and accessing and updating the e-mail address book. When you complete this chapter,
you will have a fully functional e-mail client application.
You'll also learn the details of using the MAPISession and MAPIMessage controls with Visual Basic 4.0. The Visual
Basic MAPI controls offer a quick way to build Simple MAPI applications. The MAPI controls allow you full access to
the Inbox object, to the MAPI Compose, Fetch, Read, Delete, and Send services, and limited access to the
address book.
Note
Simple MAPI is sometimes called MAPI 0 to indicate that it precedes the
MAPI 1.0 release that matches Microsoft Exchange Server. Throughout this
book, you'll see Simple MAPI instead of MAPI 0.
When you complete the coding examples in this chapter, you will understand the basics of Simple MAPI, and you'll
know how to use Visual Basic and the MAPI controls to read, compose, address, send, and delete MAPI messages.
Visual Basic 4.0 Professional Edition ships with two OCX controls that provide access to all the MAPI services you'll
need to create fully functional electronic mail applications using Visual Basic. The MAPISession control provides
access to everything you'll need to sign on and sign off any MAPI-compliant server. The MAPIMessage control
provides access to the MAPI routines that allow you to read, compose, address, and send messages using the session
established with the MAPISession control. This section of the chapter will review the MAPI-related properties and
methods of each of these controls.
The MAPISession control is used to establish a link between your Visual Basic program (the mail client) and the
electronic mail server. The MAPI-compliant mail server must be installed and available to the Visual Basic client
program. If your program will run on a traditional network server, the mail server may be Microsoft Mail installed on a
network workstation. If you are running Windows for Workgroups or Windows 95, Microsoft Mail or Microsoft
Exchange can act as a mail server. There are other mail servers available for both file server and peer-to-peer networks.
There are two methods and seven properties for the MAPISession control that are directly MAPI related. The two
following sections identify these components of the MAPISession control, and describe their meaning and their use in
Visual Basic programs.
There are only two MAPISession methods: SignOn and SignOff. The SignOn method is used to begin a MAPI
session. By default, the SignOn method provides a logon dialog box that prompts the user for valid logon information.
The exact nature of the logon dialog box depends on the mail server. The Microsoft Mail logon dialog box prompts the
user for a valid username and password (see Figure 7.1).
The default logon dialog box for the Microsoft Exchange Mail product simply asks the user to select the desired mail
services profile (see Figure 7.2).
Figure 7.2 : Default logon dialog box for Microsoft Exchange Mail.
If a valid username and password are supplied by way of the SignOn method, the MAPISession control returns a
unique value in the SessionID property. This unique value is used in all message transactions to identify the link
between the client application and the mail server. We'll talk more about the SessionID property in the next section of
this chapter.
The SignOff method of the MAPISession control does just what you'd expect-it safely ends your link to the mail
server. There is no dialog box associated with the SignOff method.
In order to use the MAPISession control methods, you must first place the MAPISession control on a Visual Basic
form. The form is invisible at run-time and is only used to provide the methods and properties needed to establish a
MAPI session between your Visual Basic program and the mail server.
As an example of the MAPISession control, start a Visual Basic project. Use the Tools | Custom Controls
menu item to add the Microsoft MAPI controls to your project. Place a MAPISession control on the form and add the
three lines of code in Listing 7.1 to the Form_Load event.
Save the form as CDG0701.FRM and the project as CDG0701.VBP, and run the project. You'll see the default logon
dialog box provided by your mail server. Once you sign in, the Visual Basic program immediately signs you out and
ends. You'll add more features as we go along.
The MAPISession control has seven MAPI-related properties. These properties all deal with options that you can set
to control the behavior of the control at logon time.
The Action property can be used to invoke the sign-on or sign-off methods. This property was present in Visual Basic
3.0 and has been replaced by the SignOn and SignOff methods discussed earlier.
The DownloadMail property is used to control whether new messages are downloaded from the mail server at logon
time. By default, all new messages are forced into the user's mailbox at logon time. You can set this property to False
to prevent this from happening. In the code example in Listing 7.2, the DownloadMail property has be set to False
before invoking the SignOn method.
You can use the NewSession property to force the creation of a new MAPI session, even if a MAPI session already
exists for this workstation. By default, the SignOn method will attempt to locate any current MAPI interface sessions
before attempting to log on to the mail server. When you set the NewSession parameter to True, the SignOn
method will create a second MAPI session link with the mail server. The code in Listing 7.3 shows how this is done.
The Password and UserName properties should be set to valid values if you want to bypass the default logon screen.
If you supply a UserName but leave the Password property blank, the SignOn method will force the logon dialog
box to appear and prompt for the missing information. If, however, the LogonUI property is set to False, no dialog
box will appear, and an error will be returned.
If you are using the Microsoft Exchange Mail Client, you only need to provide a valid Profile Name. Microsoft
Exchange Mail will ignore any value in the Password property. If you are using the Microsoft Mail client (network or
workgroup version), you will need to supply both the UserName and the Password properties if you want to bypass
the default logon dialog box. Refer to Listing 7.4 for an example.
The SessionID property is a read-only property available only at run-time that contains the unique session ID number
of a completed link between your Visual Basic program and the mail server. This session ID value must be used in all
transactions to the mail server. Its value is used to set the SessionID of the MAPIMessage control before attempting
to access the message services provided by the mail server. The code in Listing 7.5 displays the value of the
SessionID after a successful logon of the user.
Modify the UserName and, if needed, the Password properties to contain valid logon data for your mail server. Then
save and run the project. You will see a message box that shows the unique session handle for the MAPI session (see
Figure 7.3).
The MAPIMessage control gives your Visual Basic program access to all the message services available from the mail
server with which your program has established a session. The MAPIMessage control provides services for reading,
composing, addressing, and sending mail messages. You can perform all major mail operations by requesting the service
from the server.
There are 11 methods and over 30 properties of the MAPIMessage control that are MAPI-related. We'll review these
methods and properties briefly. You can find additional documentation on each of the MAPIMessage properties in the
Visual Basic online help files.
Methods
● To create and send messages, the control uses the Compose, Copy, Forward, Reply, ReplyAll, and
Send methods.
● To read messages, the control uses the Fetch method.
● To manage old messages, the control uses the Save and Delete methods.
● To maintain the address list, the control uses the Show and ResolveName methods.
The message-creation methods account for six of the eleven methods. Of these six, you only need to know two before
you can create a functional e-mail application. The Compose method clears out the edit buffer area and prepares a clean
slate for the creation of a new message. The Send method attempts to send the new message to the address supplied. If
an address or message is not supplied, the Send method forces the mail server to present the default message compose
form to the user.
In other words, you can create a complete e-mail composer by adding only two lines to the code we started earlier in this
chapter. The code shown in the following listing is all you need to create a Visual Basic application that can compose
and send e-mail messages. Start a new Visual Basic project and add the MAPISession and the MAPIMessage
controls to the form. Then add the code shown in Listing 7.6 to the Form_Load event.
Now save the form as CDG0702.FRM and the project as CDG0702.VBP, and run the program. You'll be prompted for
a logon, and then you'll see the default compose form. Figure 7.4 shows what comes up if you are using Microsoft
Exchange Mail.
Notice that you have a highly functional e-mail program with very little coding. Use this form to send yourself a
message. We'll use other Visual Basic code to read the message later in this chapter.
You can use the other message creation methods (Copy, Forward, Reply, and ReplyAll) to load the compose
buffer with messages that have been sent to you by others. You can then use the default compose dialog box to edit the
old message and send it just as you would a new message you created from scratch. The Forward, Reply, and
ReplyAll methods also alter the subject line to reflect the action. For example, when you use the Forward method,
the mail server will add "FW:" to the beginning of the subject line. The Reply methods place "RE:" at the start of the
subject line.
The code example in Listing 7.7 takes the first message in the user's inbox and copies it to the compose buffer with
"FW:" on the subject line.
You can use the Fetch method to tell the mail server to send your Visual Basic program all the messages that are
currently on file in the inbox for the logged-on user. The code example shown in Listing 7.8 fetches all the messages for
the user and then uses the MsgCount property to find out how many messages are actually on file.
Finally, you can tell the mail server to show you a list of all the mail server addresses on file by invoking the Show
method. The Show method can also be used to display details of the current message recipient (if this is supported by the
MAPI mail server). The example code shown in Listing 7.10 will display the current mail server address list.
Properties
The MAPIMessage control has more than thirty MAPI-related properties. We will quickly review them here. You can
find more detailed information on each of the MAPIMessage control properties by consulting the topic
"MAPIMessage Control" in the Visual Basic online help file.
The MAPIMessage control properties can be divided into several groups. Each group contains a set of properties that
all deal with the same aspect of the message services provided by the mail server. Table 7.1 shows the MAPI-related
properties, divided by service group.
You can use these properties to modify the behavior of the MAPI dialog boxes supplied by the mail server. For example,
you can change the caption of the address list, change the number of buttons that appear on the list, even change the
caption of the To: button. You can also use these properties to add increased functionality to your Visual Basic mail
applications by honoring attached documents, allowing the use of blind courtesy copy recipients, and so on.
While there are a number of things you can do with the numerous properties of the MAPIMessage control, one of the
most useful is the ability to create e-mail attachments. That is the subject of the next section.
Creating e-mail attachments is very useful and very easy to do with the MAPIMessage control and Visual Basic.
Attachments can be simple text files, word processing documents, or even databases. For the next example, you'll attach
a system file from a user's workstation and prepare a message to send to the help desk at a large corporation.
There are four property values you need to set in order to successfully create an e-mail attachment:
● AttachmentPathName-This contains the complete filename as it is used by the operating system (for
example, c:\config.sys or \\server\directory1\file.ext).
● AttachmentName-This is a text string that appears underneath the attachment icon in the message.
● AttachmentType-This flag tells the server what type of attachment you are using. Visual Basic constants for
this property are mapData (a data file), mapEOLE (an embedded OLE object), and mapSOLE (a static OLE
object).
● AttachmentPosition-This is an integer value that contains the exact character position where the
attachment icon is to appear in the message text.
Note
If you plan on adding multiple attachments to the message, you'll also need
to use the AttachmentCount and AttachmentIndex properties to
fetch the attachments when you read the message.
You can add an attachment to any valid message in the compose buffer. The code example below creates a new message
and adds the workstation's CONFIG.SYS file as an attachment. Modify the Form_Load event of CDG0702.FRM to
match the code in Listing 7.11.
Now save and run the CDG0702.VBP project. After logging onto the mail server, you'll see a form appear with the C:
\CONFIG.SYS file already attached to the message (see Figure 7.5).
The rest of this chapter covers the creation of a complete e-mail client for Simple MAPI access. This example uses all of
the default MAPI services supplied by the installed MAPI service provider. The look and feel of the sign-in dialog box,
the compose form, and the address book are completely controlled by the underlying MAPI engine running on the
workstation. As you saw earlier in the chapter, each of these forms looks different depending on whether you are running
Microsoft Mail, Microsoft Exchange, or any other MAPI-compliant messaging provider (such as Perfect Office). By
using the services already available on the workstation, you can reduce your coding and maintain a familiar look and feel
for your users' e-mail applications.
You will notice that one of the services unavailable within this client is the ability to store and retrieve old messages
within the existing private and public mail folders. Simple MAPI services do not include access to the message store.
You can only read information from the inbox using Simple MAPI. The Send method automatically writes messages to
the outbox, but the Simple MAPI client cannot view messages in the outbox once they are placed there.
Note
The ability to access mail folders is available through MAPI OLE
Messaging. You'll learn how to use OLE Messaging in Chapter 8, "The
OLE Messaging Library."
The simple mail client project requires two forms. The first form (MAPIMAIN.FRM) is the main form in the application.
This form will consist of a single list box that shows all the messages in the user's e-mail inbox and a set of command
buttons that can be used to perform e-mail activities such as creating new messages, reading e-mail, deleting old
messages, saving messages to text files, and replying to existing messages. This form will also hold the MAPI controls
and a common dialog box control that will be used to save e-mail messages as text files. Refer to Figure 7.6 when laying
out the form.
Listing 7.12 shows the Visual Basic form code for MAPIMAIN.FRM. When you build the MAPIMAIN.FRM, be sure to
add the picture control first; then add a single command button to the form by selecting the command button from the
toolbox and drawing it onto the picture control rather than double-clicking the command button from the toolbox. By
drawing it on the picture control, you establish the command button as a child control of the picture box. Now, whenever
you move the picture box, the command button will move with it.
VERSION 4.00
Begin VB.Form MapiMain
Caption = "Form1"
ClientHeight = 1710
ClientLeft = 1875
ClientTop = 1725
ClientWidth = 6345
Height = 2115
Left = 1815
LinkTopic = "Form1"
ScaleHeight = 1710
ScaleWidth = 6345
Top = 1380
Width = 6465
Begin VB.ListBox List1
BeginProperty Font
name = "Courier"
charset = 0
weight = 400
size = 9.75
underline = 0 'False
italic = 0 'False
strikethrough = 0 'False
EndProperty
Height = 840
Left = 120
TabIndex = 1
Top = 540
Width = 4695
End
Begin VB.PictureBox Picture1
Align = 1 'Align Top
Height = 375
Left = 0
ScaleHeight = 315
ScaleWidth = 6285
TabIndex = 0
Top = 0
Width = 6345
Begin VB.CommandButton Command1
Caption = "Command1"
Height = 315
Index = 9
Left = 900
TabIndex = 11
Top = 0
Width = 375
End
Begin VB.CommandButton Command1
Caption = "Command1"
Height = 315
Index = 8
Left = 1380
TabIndex = 10
Top = 60
Width = 375
End
Begin VB.CommandButton Command1
Caption = "Command1"
Height = 315
Index = 7
Left = 1800
TabIndex = 9
Top = 60
Width = 375
End
Begin VB.CommandButton Command1
Caption = "Command1"
Height = 315
Index = 6
Left = 2280
TabIndex = 8
Top = 0
Width = 375
End
Begin VB.CommandButton Command1
Caption = "Command1"
Height = 315
Index = 5
Left = 2820
TabIndex = 7
Top = 0
Width = 375
End
Begin VB.CommandButton Command1
Caption = "Command1"
Height = 315
Index = 4
Left = 540
TabIndex = 6
Top = 0
Width = 375
End
Listing 7.12. continued
Begin VB.CommandButton Command1
Caption = "Command1"
Height = 315
Index = 3
Left = 3240
TabIndex = 5
Top = 0
Width = 375
End
Begin VB.CommandButton Command1
Caption = "Command1"
Height = 315
Index = 2
Left = 3720
TabIndex = 4
Top = 0
Width = 375
End
Begin VB.CommandButton Command1
Caption = "Command1"
Height = 315
Index = 1
Left = 4200
TabIndex = 3
Top = 0
Width = 375
End
Begin VB.CommandButton Command1
Caption = "Command1"
Height = 315
Index = 0
Left = 120
TabIndex = 2
Top = 0
Width = 375
End
End
Begin MSComDlg.CommonDialog CommonDialog1
Left = 5280
Top = 900
_Version = 65536
_ExtentX = 847
_ExtentY = 847
_StockProps = 0
End
Begin MSMAPI.MAPISession MAPISession1
Left = 5880
Top = 540
_Version = 65536
_ExtentX = 741
_ExtentY = 741
_StockProps = 0
End
Begin MSMAPI.MAPIMessages MAPIMessages1
Left = 5100
Top = 480
_Version = 65536
_ExtentX = 741
_ExtentY = 741
_StockProps = 0
End
End
Also, you'll notice that the command buttons are in the form of a control array. Control arrays are easier to work with
than a set of individually named controls. Control arrays also consume fewer Windows resources than a set of
individually named controls. You create the array by clicking on the first command button and setting its Index
property to 0. Then highlight the control and select Edit | Copy from the Visual Basic main menu. Select Edit |
Paste from the same menu. You'll see a dialog box asking you to confirm that you want to create a control array-click
Yes. Visual Basic will then place a copy of the control on the form. Continue cutting and pasting until you have ten
command buttons in the picture control.
The second form (MAPIREAD.FRM) will be used to display messages sent to you by other users. This form will have a
label control to display the e-mail header information, a text box control to display the actual body of the message, and a
single command button to close the form. Use the Visual Basic form code in Listing 7.13 and Figure 7.6 as a reference
when building the MAPIREAD.FRM.
There are four support routines that you need to add to the MAPIMAIN.FRM form. The first routine, AdjustForm (see
Listing 7.14), will do all the work to set the proper size and location for all the controls on the form, including the ten
command buttons.
This routine is called at the very start of the program and also each time the user resizes the form during run-time. When
you add this routine to the Form_Resize event, all controls will be adjusted automatically each time the user changes
the size of the form.
The next support routine you need to add is the MAPIStart routine (see Listing 7.15). This is the routine that attempts
to log the user onto the mail server. Calling the MAPISession1.Signon method forces the mail server to display the
default logon screen for the e-mail system.
Tip
Notice the use of the error-handling routine. You'll see these throughout the
application. Adding error handlers is always a good idea. Error handlers are
even more critical whenever your programs are calling for system services
like e-mail, since unexpected errors in the e-mail system can affect your
Visual Basic program, too.
Once your user has successfully logged onto the mail server, you need to check for any messages in the user's inbox. The
MAPIFetch routine (see Listing 7.16) does this. The MAPIMessages1.Fetch method brings all messages from the
inbox into the MAPI read buffer. You can then use a For...Next loop to "walk" through this buffer and read each
message. This program pulls the messages into the read buffer and then copies the message header information (message
subject, the sender's name, and the date and time the message was received) into a list box on the main form.
The last support routine you need to add to the MAPIMAIN.FRM form is MAPISave (see Listing 7.17). This routine is
used to save selected messages to your local workstation for later use. The common dialog box control is used to provide
the standard save file dialog box. Once a valid name is given for the file, the routine writes out the message header
information followed by the actual message text. This is saved as an ASCII file that can be loaded by Notepad or any
standard text editor.
Now that you have coded the support routines, you need to add code for four Visual Basic events on the MAPIMAIN.
FRM form.
Coding the MAPIMAIN.FRM Events
The MAPIMAIN.FRM form has only five events that require code. Four of them are described here. The last event is
described in the next section. The four events covered here are
● Form_Load
● Form_Unload
● Form_Resize
● List1_DblClick
The Form_Load event first sets the form size and location and then calls the support routines AdjustForm,
MAPIStart, and MAPIFetch. If any of these routines returns an error, the program is halted. The Form_Resize
event calls the AdjustForm routine. This automatically resizes all controls each time the user changes the size of the
form during run-time. The Form_Unload event invokes the MAPISession1.SignOff method to end the e-mail
session and log the user out of the message server. The List1_DblClick event gets the currently selected message
and calls the MAPIREAD.FRM form to display the e-mail note. You'll code the MAPIREAD.FRM form later on.
The last code routine needed for the MAPIMAIN.FRM form is the code used for the Command1_Click event (see
Listing 7.19). Since you built a command button array, all button clicks are sent to the same routine. The program will be
able to tell which button was pressed by checking the Index parameter passed to the Click routine.
Each time a user presses one of the command buttons, this routine will execute the desired function. For most routines,
the user must first select one of the messages in the inbox. If no message is selected, a dialog box pops up. Notice also
that the only function that will require much code is the message-reading routine. You need to provide your own MAPI
message reader (you'll use the MAPIREAD.FRM for that). All other e-mail services (New, Delete, Forward, Reply,
ReplyAll, and AddressBook) are all provided by the message server your user logged onto at the start of the
program.
Note
There is, of course, a downside to this "easy-code" access to MAPI services.
Simple MAPI does not allow you to read, write, or search the MAPI address
book via code. You can only gain access to the address book by starting the
address book dialog box with the MAPIMessages.Show method. Also,
you cannot see any existing MAPI storage folders except the inbox. This
means you cannot save messages to an existing folder, and you cannot read
old messages stored in other folders in the past. These limitations make the
Simple MAPI interface less than ideal for building robust MAPI clients.
However, Simple MAPI is very good for general access to MAPI messages
and for creating very simple e-mail interfaces.
You need to code the MAPIREAD.FRM form to complete the project. This form has two support routines and four events
that require code. The first support routine, AdjustReader, handles the resizing and locating of controls on the reader
form. The second support routine, LoadReader, loads the message from the read buffer onto the form for display. This
routine also creates a message header to display in the label control of the form. The code for these two support routines
is shown in Listing 7.20.
The Form_Load event contains code to set the form size and then call the AdjustReader and LoadReader
routines. The Form_Resize event calls the AdjustReader routine to resize the controls. The cmdClose_Click
event unloads the MAPIREAD.FRM form, and the Text1_KeyPress event contains a single line of code that tells
Visual Basic to ignore any key presses that occur within the text box control. This is a simple way to make a text box
control read-only. Each time a user presses a key, the KeyPress event will zero out the ASCII key value before it gets
typed to the screen. Listing 7.21 contains the code for the MAPIREAD.FRM form events.
After you add all the code, save the project (MAPICLN.VBP) and run it. When the program first starts, you'll see a
prompt to log onto your e-mail server. After you log on, you'll see the main form of the application (see Figure 7.7).
This form shows all the e-mail messages that are currently in your inbox. Notice that you can resize the form, and all the
controls will adjust to the new form dimensions. If you double-click an item in the list box or select an item with a single
click and press the Read button, you'll see the MAPIREAD.FRM form with the message header and selected e-mail
message (see Figure 7.8).
If you press the New button, you'll see the default message compose dialog box. This box will look different depending
on the e-mail system you are using. Figure 7.9 shows how the Microsoft Exchange compose dialog box looks in
Windows 95.
You can get direct access to the e-mail address book by clicking the Addr command button on the MAPIMAIN.FRM
form. Figure 7.10 shows what the Microsoft Exchange address book looks like in Windows 95.
You can test the application by sending yourself e-mail and then using the various command buttons to forward, reply,
save, and delete e-mail messages. You now have a completed e-mail client application!
Additional Features
Even though this project is fairly complete, there are a few features you might consider adding to improve the program.
Some possible enhancements are
● Adding a print button to print the selected message. This involves adding a print button to the control button
array and setting up the common dialog box properties to access the printer.
● Adding the ability to read and save e-mail attachments. This involves adding the ability to collect and list
attachments to the MAPIREAD.FRM form. The Visual Basic text box will not allow you to render an icon
within the message body as is done by the Microsoft Mail and Windows Messaging clients. However, VB4-
32bit does contain a rich-text edit box that will allow you to render icons. If you wish, you can collect the
attachment properties and add them to a list box at the end of the MAPIREAD form, or to a list on another form
launched from MAPIREAD.FRM. After viewing the attachment list, you'll need to offer the user the chance to
save the attachments via the file options of the Visual Basic common dialog box control.
● Replacing the command buttons with icon buttons using the Sheridan 3-D command button that ships with the
Professional Edition of Visual Basic. If you are working in the 32-bit version of Visual Basic, use the new
Toolbar control instead.
● Adding a set of menu items to the forms to match the functions of the command buttons.
Summary
You learned the properties and methods of the two Visual Basic MAPI controls. The MAPISession control is used to
gain access to MAPI service providers through the SignOn and SignOff methods. The MAPIMessages control is
used to read, create, delete, address, and send MAPI messages.
The Simple MAPI client detailed in this chapter showed you how to build a complete MAPI-enabled application with a
minimum of coding. This sample application shows how you can use the Visual Basic MAPI controls to create a fully
functional e-mail client that can read and delete incoming messages, compose new messages, address them, and send
them to their recipients.
You also learned that Simple MAPI services allow only limited access to the MAPI address book. You can search and
edit the address book only through the pre-defined MAPI.Show method. You cannot directly search for existing
addresses or add, edit, or delete addresses without using the address dialog box supplied by the MAPI service provider.
You also learned that Simple MAPI does not allow you to access any of the existing mail folders. You can only see the
inbox folder and its contents.
Now that you know how to use the Visual Basic MAPI controls to create a simple e-mail client, you're ready to tackle
OLE messaging. In the next chapter, you'll learn how to build a mailing list server application that is capable of
managing large mailing lists.
Chapter 6
CONTENTS
● Summary
The most basic MAPI service is the ability to provide a send feature to a program. Almost all Windows programs have a
print feature to send output to a print device. The send feature works basically the same way. It provides the user a way
to send output to some other e-mail address. Adding this most basic form of MAPI service to your Windows applications
makes it "MAPI-aware." MAPI-aware applications do not have e-mail services as a basic part of their functionality (that
is, an e-mail client) but provide it as an added feature. For example, most office suite applications (word processing,
spreadsheet, and so on) provide a send feature on the main menu of all their programs. Basically, whatever documents
you can create with the program can be sent to other locations using the mail services available on the network.
In this chapter, you'll learn how to make your programs MAPI-aware using the Simple MAPI API call set. This API set
provides very easy, very quick access to the most needed MAPI services. Another advantage of using the API set is that
it is available to any program that supports DLL calls. This means it is quite easy to add MAPI services to most any
Windows application.
In the first half of this chapter, you'll get a quick overview of the API calls themselves. You'll learn about the eleven API
calls and three user-defined types that comprise the Simple MAPI interface, and you'll build a set of examples that
illustrate the use of Simple MAPI services.
In the second half of the chapter, you'll create some real-world examples of Windows applications that have MAPI-
aware features. You'll create a quick spreadsheet routine that can send its results using MAPI. You'll also modify an
existing Visual Basic 4.0 program to add MAPI capabilities.
When you complete this chapter, you'll have a good understanding of the Microsoft MAPI API calls. You will also be
able to design and build applications that can use the MAPI interface to provide mail services from within your Visual
Basic programs.
The Simple MAPI (SMAPI) calls allow you to add MAPI services to virtually any Windows program. While these calls
offer only the smallest set of MAPI services, they are still quite useful. In the next few sections, you'll learn about the
three user-defined structures needed to provide MAPI via the API calls, and you'll experiment with each of the API calls
themselves.
Note
All the SMAPI services for Visual Basic and VBA applications are
provided through the dynamic link library (DLL) called VBAMAP32.DLL
(or VBAMAPI.DLL for 16-bit plat-forms). If you do not already have this
DLL on your system, you can find it on the CD-ROM that ships with this
book.
Before you begin this part of the chapter, start Visual Basic 4.0 and begin a new project. Locate the VBAMAP32.BAS
module in the Chap06 folder and add that module to your project. This has all the API calls and structures defined
along with several constants and a helper error function.
Note
Throughout this chapter, you'll use the 32-bit version of the SMAPI set. A
16-bit version of the SMAPI calls can also be used. You'll find the 16-bit
version of the API calls in the VBAMAP16.BAS module on the CD-ROM.
● MAPIMessage-Contains all the information about a message packet, including originator, subject, text,
recipients, and attachments.
● MAPIRecip-Contains all the information about a message recipient, including name, address type, full address,
and unique entry ID.
● MAPIFile-Contains all the information about an attached file, including display name, operating system name,
and position in the message packet.
MAPIMessage
The MAPIMessage structure is used to hold all the vital information about a message packet. You will use this
structure to pass message data from your programs to the DLL and back. Table 6.1 shows the structure of the
MAPIMessage along with short descriptions for each element.
Listing 6.1 shows how the MAPIMessage structure is built using Visual Basic 4.0.
Note
The user-defined types and API calls are all contained in the VBAMAP32.
BAS and VBAMAPI16.BAS modules on the CD-ROM. You do not have to
type these structures and API calls into your projects.
'***************************************************
' MAPI Message holds information about a message
'***************************************************
Type MapiMessage
Reserved As Long
Subject As String
NoteText As String
MessageType As String
DateReceived As String
ConversationID As String
Flags As Long
RecipCount As Long
FileCount As Long
End Type
MAPIRecip
The MAPIRecip structure holds all the important data related to a message recipient. Table 6.2 describes the structure,
and Listing 6.2 shows how it looks in VBA code.
'************************************************
' MAPIRecip holds information about a message
' originator or recipient
'************************************************
Type MapiRecip
Reserved As Long
RecipClass As Long
Name As String
Address As String
EIDSize As Long
EntryID As String
End Type
MAPIFile
The last structure used by SMAPI is the MAPIFile structure. This user-defined type holds all the information about a
message attachment. Table 6.3 describes the structure, and Listing 6.3 shows the UDT definition.
'******************************************************
' MapiFile holds information about file attachments
'******************************************************
Type MapiFile
Reserved As Long
Flags As Long
Position As Long
PathName As String
FileName As String
FileType As Long
End Type
These are the only three structures needed to establish MAPI services with the VBAMAPI DLLs. The next section
describes each of the API calls and constants and shows you examples of how to use them.
There are eleven SMAPI API calls. This set of calls provides access to the core MAPI services including
The next several sections describe each of the API calls and provide Visual Basic 4.0 examples of how to use them.
If you haven't already done so, start Visual Basic 4.0 and load the VBAMAP32.BAS module into your project. Listing
6.4 shows the complete set of API calls for SMAPI. You do not have to type this information into your project. You can
find this module in the Chap06 directory that was created when you installed the source code from the CD-ROM.
'***************************
' FUncTION Declarations
'***************************
There are also a number of CONSTANT declarations needed to make the API calls easier to work with. Listing 6.5 shows
the error constants and flag declarations used for SMAPI.
'**************************
' CONSTANT Declarations
'**************************
'
These are all the basic tools needed to begin to write MAPI applications. The next section reviews each API call in
greater depth, including at least one coding example for each call.
Warning
In order for the 32-bit API calls to work, you must have the VBAMAP32.
DLL in your WINDOWS\SYSTEM folder. If you are using Visual Basic 4.0
on a 16-bit platform, you can load the VBAMAPI.BAS module and make
sure that the VBAMAPI.DLL is in your WINDOWS\SYSTEM folder.
All SMAPI calls return a status code (either SUccESS or some error). You should always check this value before
continuing on with your program. In order to make it easier to work with the SMAPI calls, you can add a helper function
that returns meaningful error messages for the established MAPI errors. Add a BAS module to your Visual Basic project
called MAPIERR.BAS and enter the code in Listing 6.6.
Listing 6.6. Adding the MAPIErr function.
The MAPILogon and MAPILogOff functions are used to start and stop MAPI sessions.
Note
It is always a good idea to log off a session when you no longer need MAPI
services. Leaving an unused session open can slow your program and, if it's
left open after you exit, can lead to unexpected problems.
Table 6.4 shows the MAPILogon parameters along with their type and description.
Table 6.5 shows the MAPILogOff parameters along with their type and description.
Add a new command button to your Visual Basic form. Set its Caption property to LogOn. Use Edit|Copy, Edit|
Paste to add a second command button as part of a control array. Set the second command button's caption to
LogOff. Then add the code in Listing 6.7 to the Command1_Click event.
Next add the following form-level declarations to the general declaration area of your form. You'll use these throughout
the project.
Option Explicit
'
Dim lReturn As Long ' return flag
Dim lSession As Long ' session handle
Dim udtMessage As MapiMessage ' message object
Dim udtRecip As MapiRecip ' recipient object
Dim udtRecips() As MapiRecip ' recipient collection
Dim udtFile As MapiFile ' attachment object
Dim udtFiles() As MapiFile ' attachment collection
Now add a new subroutine called SMAPIStart to the project, and enter the code shown in Listing 6.8.
Finally, add the SMAPIEnd routine, and enter the code shown in Listing 6.9.
Now save the form as SMAPI.FRM and the project as SMAPI.VBP. When you run the project, click the LogOn button
to bring up the logon dialog box (see Figure 6.1).
MAPIAddress
The MAPIAddress call produces the address book dialog box and allows users to add, edit, and delete records from the
address book. There are several flags you can use to control the address book dialog box behavior. Table 6.6 shows the
MAPIAddress call parameters and their type and description.
Now add a new button to the control array and set its caption to AddressBook. Then modify the Command1_Click
event to match the code in Listing 6.10.
Next add the AddressBook subroutine and enter the code shown in Listing 6.11.
The AddressBook routine calls up the MAPI address book, passing a new dialog title, and enabling all possible
recipient class buttons (TO:, cc:, and Bcc:). The AddressBook function returns a collection of recipients.
Save and run the project. After clicking the AddressBook, you should see something like the screen in Figure 6.2.
MAPIResolveName
The MAPIResolveName function is used to look up and/or validate a recipient object. You can use this to retrieve the
complete e-mail address of a recipient and to present a dialog box to the user to resolve any unknown or ambiguous
names. The MAPIResolveName parameters are described in Table 6.7.
Table 6.7. The MAPIResolveName parameters.
To test MAPIResolve, add a new button to the control array and set its caption to ResolveName. Then modify the
Command1_Click event to match the one in Listing 6.12.
Now add the new ResolveName subroutine and enter the code in Listing 6.13.
Listing 6.13. Adding the ResolveName routine.
If you run this routine and supply an ambiguous name, MAPI returns a dialog box asking you to resolve the differences
(see Figure 6.3).
You can avoid viewing the dialog box (and just trap any error) by setting the Flag value to 0.
MAPIDetails
The MAPIDetails function returns a special dialog box that allows users to inspect and edit information about a single
recipient. You can use this in your programs to give users direct access to an address entry edit form. Table 6.8 shows
the MAPIDetails parameter list.
Add a new button to the control array and set its caption to MAPIDetails. Then modify the Command1_Click
event to match the code in Listing 6.14.
Now add the AddrDetails subroutine and fill it in with the code from Listing 6.15.
When you save and run the project, you will see an address entry dialog box appear when you press the AddrDetails
button (see Figure 6.4).
MAPISendDocuments
The MAPISendDocuments function is unique. You do not need to log in to MAPI before you call this function. As an
option, you can fill simple test strings with information for attachments and pass them in the call, too. When the call is
used, it brings up a full-featured compose dialog box that you can use to create and send an e-mail message. Table 6.9
shows the parameters for MAPISendDocuments.
Add another button to the control array and set its caption to SendDocs. Make sure your Command1_Click event
matches the one in Listing 6.16.
Now add the SendDocs subroutine and enter the code in Listing 6.17.
Note
The code in Listing 6.17 refers to documents in the C:\WINDOWS
directory. If your Windows directory is not found at C:\WINDOWS, make
the needed changes to the code example.
Save and run the project. Remember that you do not have to press LogOn before you press the SendDocs button. The
MAPISendDocuments API will log you in automatically. Your screen should look similar to the one in Figure 6.5.
MAPISendMail
The MAPISendMail function is similar to MAPISendDocuments. The difference is that MAPISendMail uses the
MAPIRecip and MAPIFile structures to pass data to MAPI. The MAPISendMail function also allows you to
compose, address, and send a message without the use of any dialog boxes. Table 6.10 shows the MAPISendMail
parameters.
Add another button to the control array and set its caption to SendDialog. Update the Command1_Click event to
match the code in Listing 6.18.
Now add the SendDialog subroutine and enter the code from Listing 6.19.
Save and run the project and press SendMail. You should see the Send Note dialog box on your screen, already filled
out and ready to go (see Figure 6.6).
You can use MAPISendMail to send a message without invoking the Send Note dialog box. To do this, modify the
MAPISendMail line by removing the MAPI_DIALOG constant and replacing it with 0.
The MAPIFindNext and MAPIReadMail functions are used to read messages that have been placed in the user's
InBox. The MAPIFindNext function is used to point to the next (or first) unread message in the InBox. The
MAPIReadMail function takes information received during the MAPIFindNext operation and retrieves the message
for viewing or other processing. Table 6.11 contains a list of MAPIFindNext parameters.
The MAPIReadMail function reads a message from the MAPI InBox and places it into the MAPIMessage structure.
You use MAPIReadMail to retrieve messages from the Inbox for review and subsequent action. Table 6.12 shows the
MAPIReadMail parameters.
Now add another button to the control array and set its caption to ReadMail. Update the Command1_click event to
match Listing 6.20.
Now add the ReadMail subroutine and enter the code from Listing 6.21.
Listing 6.21. Adding the ReadMail routine.
Save and run the project. After you press the ReadMail button, you'll see a message box that shows the subject and
sender name (see Figure 6.7).
Even though MAPI services provide a built-in compose form (see SendMail), there is no built-in read form. You must
provide that through Visual Basic code.
The most basic form of MAPI applications is the mail-aware application. This is a program that offers mail services as
an added feature. A good example of this is the send option in Word, Excel, Access, and the other Microsoft Office
programs.
Making your programs mail-aware is about the same as making them aware of a printer. Usually, you can add a send
option to the main menu and treat mail output the same way you treat printer output. It is possible that you may have to
create an interim ASCII text file that you can then import into the message text using the clipboard or a few lines of
Visual Basic code. All in all, it's quite easy.
In this section, you'll develop a send feature for an Excel spreadsheet and then modify a Visual Basic project to add
MAPI-aware features to its menu.
One of the quickest ways to add MAPI services to existing applications is through the use of the
MAPISendDocuments API call. This API requires no user-defined types and does not even require that you perform a
MAPI logon before attempting the send operation. All you need to do is add a MAPISendDocuments API declaration
and write a short routine to handle the selection of files for the send.
All the Microsoft Office applications allow you to build this kind of MAPI service into your spreadsheets, input forms,
and other projects. As an illustration, let's build a quick Excel spreadsheet that allows users to select from a friendly list
of accounting reports and then route those reports to someone else in the company.
Note
This example uses Excel 95 and requires the VBAMAP32.DLL be present in
the WINDOWS\SYSTEM folder. If you are running a 16-bit version of
Excel, you need to have the VBAMAPI.DLL installed in your WINDOWS
\SYSTEM folder, and you need to change the code referenced DLL to
match your version.
First, bring up Excel and start a new worksheet. Insert a code module (Insert | Macro | Module) and enter the
code shown in Listing 6.22.
'
' declare API call for MAPI service
Declare Function MAPISendDocuments Lib "VBAMAP32.DLL" Alias
"BMAPISendDocuments"
(ByVal UIParam&, ByVal DelimStr$, ByVal FilePaths$, ByVal FileNames
$, ByVal Reserved&) As Long
'
' send file in active cell
'
Sub MAPIAware()
'
' send the selected file as attachments
'
Dim x As Long ' for return
Dim cFile As String
Dim cName As String
'
If ActiveCell = "" Then
MsgBox "Select a Report to Send"
Exit Sub
End If
'
cName = ActiveCell & ";"
cFile = ActiveCell.Offset(0, 1) & ";"
'
x = MAPISendDocuments(0, ";", cFile, cName, 0)
'
If x <> 0 Then
MsgBox "SendDocuments Error [" & Str(x) & "]"
End If
'
End Sub
Now select a new worksheet page and, using Figure 6.8 as a guide, enter the columns of information shown in Table
6.13.
Add a button to the worksheet (selected from the Forms Toolbar) and connect the button to the MAPIAware routine you
built earlier. Set its caption to Send Report. Now hide column B so that users cannot see the exact operating system
filenames. To do this, click the column header, click the right mouse button, and select Hide. Finally, add a title to the
worksheet and save it as QIKSEND.XLS.
Now press Send Report. You'll be asked to log into the MAPI service; then MAPI will collect the file you selected
and present you with the Send Note dialog box with the attachment already in place (see Figure 6.9).
That's all there is to it! You can create much more sophisticated forms and options, though-including building the entire
message, attaching the selected file, even routing directly to the person who is authorized to see the report. And all this
can be done without ever asking the user to do more than select the report and click Send!
For this example, you'll borrow the code from a sample program that ships with Visual Basic 4.0 Standard Edition: the
MDI sample application. This can be found in the SAMPLES\MDI folder within the main Visual Basic folder. If you do
not have this application or if you want to leave your copy alone, you can find another copy of it on the CD-ROM
shipped with this book.
This MDI application is a simple project that creates a multidocument editor that can save documents as ASCII files. To
make this system mail-aware will require a few lines of code behind a Send... menu item in one form.
Note
This project uses the MAPI controls that ship with Visual Basic 4.0. You'll
cover the MAPI controls in detail in the next chapter. For now, just
remember that the MAPI controls offer the same level of access to MAPI
services that the SMAPI API offers.
Load the MDI project and open the NOTEPAD form. First, add the MAPI Session and MAPI Message controls to
the bottom of the form. Next, add a separator line and a Send... menu item just after the Save As... menu item
(see Figure 6.10).
Finally, add the code in Listing 6.23 to the mnuFileSend_Click event. This is all the code you need to make this
application mail-aware.
Now save and run the project. Begin to edit a new document (see Figure 6.11).
When you are done editing the text, select the Send... menu item to send the document out. You'll see the default
compose form appear with the text and subject already supplied (see Figure 6.12).
There is a way to "bury" the mail features even deeper into this application. You really only need a way to tack on an
address to this document. Listing 6.24 shows a modified routine that calls only the address dialog box and then sends the
document out.
Note
In Listing 6.24, it is possible for the user to call the address book and exit it
without selecting a valid address. This will be reported as an error when the
Send method is invoked. To prevent the error, you could check the
RecipAddress property before invoking the Send method.
Save and run this project. When you select the Send... menu option, you now will see only the address dialog box
before the program sends your document out to the server.
As you can see, it's not at all difficult to add mail features to your existing applications. This technique of adding a send
option to the menu will work with just about any Windows application.
Summary
In this chapter, you learned how to make your programs MAPI-aware using the Simple MAPI API call set. This API set
provides very easy, very quick access to the most-needed MAPI services.
You learned that there are three user-defined types required to provide full SMAPI services:
● MAPIMessage-Contains all the information about a message packet, including originator, subject, text,
recipients, and attachments.
● MAPIRecip-Contains all the information about a message recipient, including name, address type, full address,
and unique entry ID.
● MAPIFile-Contains all the information about an attached file, including display name, operating system name,
and position in the message packet.
You also learned that there are eleven API calls in the SMAPI set. This set of calls provides access to the core MAPI
services, including
You also discovered that the MAPISendDocuments API call is the only MAPI call that requires no use of user-
defined types to pass data via MAPI. This API call is very useful for adding quick MAPI support to existing
applications.
In the second half of the chapter, you used SMAPI to add send features to an Excel worksheet (using the
MAPISendDocuments API). You also modified an existing Visual Basic 4.0 project by adding a Send... menu
option to the form.
In the next chapter, you'll get an in-depth look at the Visual Basic MAPI controls, and in the process you'll build a fully
functional e-mail client application that you can use to read and write all your MAPI messages.
Chapter 5
CONTENTS
● Introduction
● What Is the Microsoft Exchange Forms Designer?
❍ EFD Design Wizards
● Summary
Introduction
One of the quickest ways to develop MAPI applications is to use the Microsoft Exchange Forms Designer kit. This tool
ships with the Microsoft Exchange Server and includes a GUI form designer tool, sample templates, design wizards, and
an installation wizard. The Microsoft Exchange Forms Designer (called the EFD) generates Visual Basic 4.0 code. Once
the forms are generated, you can also use Visual Basic 4.0 to modify and enhance the forms.
Note
For those who do not own a copy of Visual Basic 4.0, the Microsoft
Exchange Forms Designer includes a version of the Visual Basic 4.0 16-bit
compiler.
To get the most out of this chapter, you should have access to a copy of the Microsoft Exchange Forms Designer on your
machine. You do not have to be linked to a Microsoft Exchange Server to complete the project in this chapter. If you do
not have a copy of the Microsoft Exchange Forms Designer, you can still get a lot out of this chapter. The concepts and
techniques discussed here apply to any project that uses Microsoft Exchange as a message platform. The last section of
the chapter focuses on folder views. You do not need the Microsoft Exchange Forms Designer to complete the exercises
in that section of the chapter.
You can use the EFD to develop two different types of forms:
● Send forms-These are forms used to send information from one location to the next. This is, in effect, a
formatted e-mail message.
● Post forms-These are forms used to place information into a particular folder. This is an application designed to
control the content of bulletin board messages to be viewed by several people.
You can also use the EFD to design folder views. Folder views are rules that control just how a folder appears to the
users. By setting values such as Sort Order, Message Grouping, and Message Filtering, you can present
folder contents in ways that reflect users' needs and highlight the most important aspects of the message collection.
When you complete this chapter, you'll know how to design, code, test, and install customized forms and folders using
the Microsoft Exchange Forms Designer. You'll learn how to use the EFD to create a Send form and a Post form. You'll
also create several new folders with custom views. Finally, you'll learn how to link customer forms to folders.
The Microsoft Exchange Forms Designer is a development tool that is a part of Microsoft Exchange Server. The EFD is
a complete design tool for the creation and management of customized electronic message applications. You can design
forms that perform various tasks, including forms that
The Microsoft Exchange Forms Designer uses the Visual Basic development environment. If you are familiar with
Visual Basic or Microsoft Access, you'll have no trouble learning to use the Microsoft Exchange Forms Designer. Even
if you have not had a lot of experience with Visual Basic or Access, you'll find the EFD environment easy to work with.
Most of the form design work involves drag-and-drop operations to add fields to a form. When you use the EFD wizards,
toolbars, and menus, most of the basic message fields (To, Cc, Subject, and so on) are automatically added to your
forms. You can add custom controls such as labels, text boxes, list and combo boxes, check boxes and radio buttons, and
even tabs, frames, and picture boxes. One of the controls available with the Microsoft Exchange Forms Designer is a 16-
bit version of the rich-text control. This allows users to select fonts, type sizes, and colors within an editable text box.
Another very handy feature of the Microsoft Exchange Forms Designer is the ability to add field-level and form-level
help to the project without having to create a WinHelp file. The EFD's QuickHelp allows you to enter help information
for each control on the form and for the form itself. You can create a message that appears on the status bar at the bottom
of the form. You can also create a message window that acts as context-sensitive help whenever the user presses the f1
key. And if you are really serious, the EFD allows you to enter help context IDs that link to standard WinHelp files.
Although it is possible to use Visual Basic alone to design and implement Microsoft Exchange forms, the EFD provides
several advantages over "pure" Visual Basic. With the Microsoft Exchange Forms Designer, you get a tool that handles
most of the drudgery of linking message fields to form controls. The EFD helps you establish a consistent look and feel
for all your forms. The EFD also walks you through the installation process, which involves creating a custom message
type, registering that message type with Microsoft Exchange, and creating a configuration file to link the form to
Microsoft Exchange.
In this section, you'll use the Microsoft Exchange Forms Designer to create a job request form to initiate requests to have
maintenance jobs completed in a workplace. This will be a single-window Send form (addressed to a user). After you
build the form, you'll install it into your personal forms library for use at any time.
The easiest way to start developing forms with the Microsoft Exchange Forms Designer is to use the Forms Designer
wizard. The wizard will take you through the initial steps in creating an electronic form. Once you answer all the
wizard's questions, you'll see the EFD build a basic form for your use. You can then use the EFD to modify the project
before saving and installing the new application.
If you haven't already done so, start up the Microsoft Exchange Forms Designer. You can do this from the Microsoft
Exchange program group. To do this, press the Start button on the Windows 95 task bar. Then select Programs |
Microsoft Exchange | Microsoft Exchange Forms Designer. You can also start the EFD directly
from Microsoft Exchange. To do this, start Microsoft Exchange and log in to your e-mail system. Then select Tools
| Application Design | Forms Designer... (see Figure 5.1).
Figure 5.1 : Selecting the EFD from the Windows Messaging client.
Tip
Starting the EFD from the Microsoft Exchange menu takes more memory.
On some systems, it may seem a bit slower than starting EFD from the
program menu. However, when you're developing an EFD form, it's really
handy to have Microsoft Exchange up and running at the same time. That
way you can easily switch between design and test mode while you debug
your EFD application.
The first screen you'll see is the Forms Designer Wizard. It asks whether you want to begin a new project using the
wizard, load a template form, or open an existing form (see Figure 5.2).
For now, select the Form Template Wizard option and press Next to continue.
The wizard asks whether you are designing a Send form or a Post form (see Figure 5.3).
Send forms are used to send messages directly to one or more users. Send forms have a field on the form for the "To"
and "Cc" fields of a message. Post messages are sent to a folder, not a person, and therefore do not have a "To" or a "Cc"
field on them. For our example job request form, you want to use a Send form. This will make sure that the form is sent
directly to a person. Select Send and press Next.
The wizard next asks whether you are creating a form to send information to someone or a form to respond to an existing
EFD form (see Figure 5.4).
The first option allows you to create an "initiating" form. The second option allows you to create a response form. For
now, select the Send option and press the Next button.
Warning
Don't confuse the Send option on this screen with the previous screen
where you were asked if you wanted to create a Send or a Post form. This
screen is really asking you to describe the action of your form-send or
respond. The previous screen asked about the destination of your form-a
person or a group.
The wizard now asks whether you want your form to have one or two windows (see
Figure 5.5).
You use single-window forms when you want to allow the reader to be able to edit the same fields filled out by the
sender. A good example would be a form that you send to people with information for their editing and final approval.
You use the two-window form when you do not want to allow readers to alter the data on the form that they read. When
you select a two-window option, the EFD creates a compose form and a read form. The compose form appears when a
user first creates a form to send to someone. When the recipient opens the two-window form, only the read form appears.
This form has the same controls as the compose form, but the fields are all read-only.
For this example, select the one-window option and then press Next.
Finally, the Forms Designer Wizard asks you to supply the name and general description of your form (see Figure 5.6).
The name you enter appears in list boxes (and in some cases on menus) within the Windows Messaging client. The
description you enter appears in information dialog boxes that users can view when selecting Microsoft Exchange forms
from their client interface. For now, enter the information contained in Table 5.1.
After you supply the form name and description, press Next. The wizard displays a final screen telling you that you
have completed the wizard steps (see Figure 5.7).
At this point you could press the Back button to return to previous screens and make any changes needed. When you are
sure that all screens have been completed properly, select Finish to close the wizard.
You now have a basic electronic form ready for final modification and use. You can see that the top part of the form has
been filled in with the Date and From fields. These will be filled in automatically when you first execute the completed
form. You'll also see the To, Cc, and Subject fields. These fields will be filled in by the person executing the form.
The rest of the form has been left blank. It will contain application-specific controls and information. In the next few
sections, you'll add labels, input boxes, list controls, and a picture control to the form.
Now that you have the basic electronic form built, it's time to add the fields needed to collect and display specific data.
To do this, you add controls for new fields and set the field properties. After adding all the needed fields, you set a few
form-level properties, add some help, and you're ready to install and test your form.
It's very easy to add fields to the form. All you do is click once on the toolbox object you want to use, then move your
mouse to the desired location on the form, and click once again to drop the object onto the form.
Warning
If you're used to Visual Basic, you'll discover that the method for dragging
and dropping form objects is slightly different here. You do not paint the
objects onto the EFD form as you do in Visual Basic. Here you just click,
point, and click.
As a test, select the Entry Field object (called a text box in Visual Basic) and drop it onto the body of the form.
Note
You'll notice that you cannot use the EFD to place standard form objects in
the header area of the form. You can, however, place one of the MAPI
fields (From, Date, To, Cc, Bcc, and Subject) on the header.
Notice that the control is placed on the form along with an associated label control (see
Figure 5.9).
You can click on the label in design mode to edit the contents. You can also use the anchors on the object to move or
resize it as needed. It is also possible to "unlink" the caption and input control by selecting the large square on the upper
left of the input control and moving it independently of the caption.
Tip
Deleting the caption from the input form will also delete the input control
itself. You can use the General tab of the Field Properties dialog box
(double-click the control) to remove the caption. Locate the Position
drop-down list and set its value to None.
Now that you've added a field to the form, you need to adjust several of the field's properties. In the next section, you'll
learn how to do this using the Field Properties dialog box.
The Field Properties dialog box gives you access to several properties of the field object (see Figure 5.10).
● General-Use this tab to set the name, caption, and position of the control on the form. You can also use the tab
to establish the control's locked, hidden, and required status and to enter the field-level help.
● Format-Use this tab to set the font, size, color, alignment, and other formatting properties of the control. Note
that you must use this same tab to set properties for both input control and the caption.
● Initial Value-Use this tab to enter the default value for this control. The contents of this tab depend on the type
of control. List and combo boxes allow you to enter multiple items, text boxes allow you to enter straight text,
picture boxes allow you to load a graphic image from the disk, and so on.
Use Table 5.2 as a guide in setting the field properties of the text box field on your job request form.
After setting the properties on the three tabs, select Close to save the information.
You need to add several fields to the job request form before it is complete. Now that you have an idea of how to add
fields and set their properties, use the information in Table 5.3 and Figure 5.11 to add, size, and locate the remaining
fields on the form.
Tip
The area of the EFD form where you place your controls is scrollable. It is
very easy to lose one or more fields due to unexpected scrolling when you
place a control on the form. To make it easy to see where things are on the
form, you need to turn on the scroll bars. Select View | Show Scroll
Bars from the main menu. If you do not want users to see these at runtime,
turn them off before you install the form.
Note
Be sure to use a complete path/directory name for the Picture Field control
in Table 5.3. You can find the Chap05 directory under the main directory
created when you installed the source code from the CD-ROM that ships
with the book.
After adding all the fields and setting their properties, save the project (JOBREQ.EFP) before continuing with the next
step.
There are several form- and window-level properties that you can set for Microsoft Exchange forms. These settings
affect the way your form looks and behaves once it's up and running under Microsoft Exchange.
First, select View | Window Properties from the main menu. Then set the properties using the information in
Table 5.4.
Table 5.4. Setting the window properties of the job request form.
Dialog Tab Property Setting
General Window Name JobRequestWindow
Window Caption Job Request Form
Fields in Tab Order MAPI_To
MAPI_Subject
ContactPhone
JobType
Priority_1
Priority_2
Priority_3
Department
AffectsProduction
Description
Format Maximize Button (off)
Minimize Button (off)
ToolBar (off)
Formatting Toolbar (off)
Status Bar (on)
Window Sizing Options Fixed Size
Next you need to set the form-level properties. Select View | Form Properties from the main menu. Refer to
Table 5.5 for the proper settings.
Table 5.5. Setting the form properties of the job request form.
Tab Property Setting
General Form Display Name Job Request Form
Version 1
Number 1
Item Type IPM.JobRequest
You will note that the first time you bring up a new form, the Item Type property is set to a long string of letters and
numbers. This is a GUID (guaranteed unique ID). Microsoft Exchange uses this ID value internally to identify the form.
The value you enter here does not have to be this cryptic. It is a good idea to enter a value that will mean something to
you and others in your organization. It is, however, important that you keep this name unique.
Save this project again (JOBREQ.EFP) before you go on to your last development step-adding help.
It is very easy to add online help to your electronic forms. The Microsoft Exchange Forms Designer has a built-in
QuickHelp feature that lets you build tooltips and pop-up help boxes at the field, window, and form levels. You can even
add notes to the design-time version of the form for tracking development issues.
First, let's add a few notes to the design-time form. Select Help | Designer Notes... from the main menu to
bring up the Designer Notes screen (see Figure 5.12).
Enter a short comment about the form, the date, and the author. The information you enter here is stored with the project
and will be available each time you load the project into the Microsoft Exchange Forms Designer. Notice that this is a
rich-text box. You can set the font type, size, and color at any time.
Notice that you can select No Help, QuickHelp, or enter a context ID for a standard WinHelp file. For now, enter a
short comment into the QuickHelp pop-up box and press Close to save the form. You can also set the Windows
Caption by moving the cursor up into the title bar of the sample help window and typing a caption.
You can enter help at the window level, too. This is most useful when you have a project with multiple windows. For
now, select the Window Properties dialog box (View | Window Properties | General) and press the
Window Help... button. Your screen will look like the one in Figure 5.14.
Notice that you have an additional control on this dialog box. If you have multiple windows in your project, you can use
the drop-down list control to select each window and enter unique help information.
Finally, you can also enter help information at the field level. Double-click a field object or select View | Field
Properties | General Tab to bring up the Field Properties dialog box. Then press the Field Help... button
to view the help dialog box (see Figure 5.15).
Figure 5.15 : Viewing the Field Help for Users dialog box.
Notice that there are now two controls at the top of the help dialog box. The drop-down list can be used to select the field
for which you want to create a help topic. The Status Bar control lets you enter a short help line that will appear at the
bottom of the form as you select each field. Of course, the QuickHelp box contains the help information that will appear
in a pop-up box if you press f1 at run-time while a field is in focus.
Enter QuickHelp information for several fields and then save the project. Save your project as JOBREQ.EFP before
continuing with the last step-installing and testing your new form.
After you have completed the development phase of your Microsoft Exchange form, you need to run the Install
routine from the Microsoft Exchange Forms Designer. This routine
This entire process may take awhile, depending on the size of your project and your hardware configuration. If you do
not have the project loaded now, open the Microsoft Exchange Forms Designer and load the JOBREQ.EFP project.
Select File | Install... from the main menu. You'll see a small dialog box telling you that the Microsoft
Exchange Forms Designer is generating Visual Basic code. Then you'll see Visual Basic 4.0 load and compile the
project.
After Visual Basic finishes, you'll see a dialog box that asks you where you want to install the form (see Figure 5.16).
Select Personal Forms Library for now. This will install the form on your workstation. Once you have tested it
thoroughly, you can re-install the form on a network location to allow others to use the form.
After selecting a forms library, you'll be asked to fill in a few more questions about the form (see Figure 5.17). The
information in these fields is used by Microsoft Exchange to categorize your form. Forms are sorted and grouped to
make them easier to locate and use.
For now, enter your initials for the first category and Help for the second category. Enter your initials again as the
contact person. Notice that several of the fields you set in design mode appear here, too.
Your new Microsoft Exchange form is now installed. Exit the Microsoft Exchange Forms Designer and switch to your
Windows Messaging client so that you can start testing your new form.
Once you have installed the form, you can switch to Microsoft Exchange and run it. In the previous step, you installed
the form in your personal forms library. In order to start an instance of the form, you need to launch your Windows
Messaging client and select Compose | New Forms... from the main menu. You'll see a dialog box that lists all
the forms you have in your personal library (see Figure 5.18).
Select the job request form from the list to launch an instance of the form. You'll see the form appear with several fields
already filled in with suggested entries (see Figure 5.19).
Complete the form and send it to yourself. Then check your inbox for the arrival of the message.
Tip
If your server is slow in returning your form to you, select Tools |
Deliver Now Using... | Microsoft Exchange or Tools |
Deliver Now Using... | Microsoft Mail if you are running a
standalone version of Microsoft Exchange for Windows 95.
When you open the message, you'll see that it appears in the same electronic form that it was sent in. If you select
Compose | Reply from the main menu of the form, you'll see your form automatically convert the data on the
application into a text message (see Figure 5.20).
You can now fill out a response to the request and return it to the person who sent you the note (in this case, yourself!).
You can use the Microsoft Exchange Forms Designer to create a response form that reads information from the job
request form and includes that data automatically on the response form. You can also create Post forms that are not
addressed to users, but to folders. These Post forms help you control discussion groups and other public data sharing in
your organization.
The Microsoft Exchange Forms Designer ships with several example projects that illustrate using multiwindow forms
and using several forms together to create a set of send/respond forms. Check out the Templates folder and the Samples
folder for more examples of Microsoft Exchange forms development.
In the next section, you'll learn how to create folder views and then how to install an electronic form in a folder.
Another very easy and powerful way to create custom MAPI interfaces is to use the Windows Messaging client's
capabilities to create and control folder views. Folder views are an excellent way to set up customized views of the
message base. You can create folder views that show and group messages according to their subject. You can also create
views that show only selected items in the folder based on subject, sender, or several other criteria.
In effect, you can use folder views to narrow the scope of your incoming messages. This is especially handy in large
organizations where you get a lot of information and must focus on the most important messages first.
In this section, you'll learn how to create a new discussion folder and establish its view properties. You'll then write
several messages to test the folder view. Finally, you'll install a form in the folder. This way every time someone wants
to post a message to the folder, he or she can use the custom form.
Note
You need to have access to the Windows Messaging client that ships with
the Microsoft Exchange Server. That version has the capability to create
folder views. You do not, however, have to be connected to the Microsoft
Exchange Server to create folders and views. This example uses personal
folders and views.
Creating folder views is the easiest way to build custom discussion applications using Microsoft Exchange. You can
create a specialized view, test it on your personal system, and then publish it for others to use. You can even use the
Microsoft Exchange Forms Designer to create custom posting forms for use in the discussion forum. These forms can be
installed in the folder itself and will be available to anyone who enters the forum.
There are just a few steps to creating a custom folder and view:
● Select a message store and add a new folder.
● Create a folder view by setting the Sort, Group, and Filter options.
● Test the view by sending/posting messages to the folder.
● Install a new or existing form in the folder.
● Test the form by using it to send messages.
● Share the folder and place the form in the folder library.
The first step is to create a new folder. If you haven't done so yet, start the Windows Messaging client and select your
personal message store. Point to the top level of the message store and add a new folder called "MAPI Discussion
Group" (see Figure 5.21).
Once the folder is created, it is a good idea to set its Description property. This description will help everyone know
what kind of information is supposed to be in the folder. It is also a good idea to add the name of the person who created
the folder and the date it was first created. To set the Description property of a new folder, select the folder, and
then select File | Properties from the main menu. Enter a general description of the folder along with a creation
date and the author's initials (see Figure 5.22).
Once you fill in the description, press the Apply button or the OK button to update the folder properties.
The folder view controls just which messages are seen by the user, along with the order in which they are seen and what
message columns appear in the summary listing. There are four main steps to setting a folder view:
Once you have set all the view properties, you can test the folder view by posting messages to the folder.
First, highlight the MAPI Discussion Group folder again and select File | Properties to bring up the Folder
Properties page. This time select the View tab. Be sure the Folder Views radio button is selected and then press the
New button to create a new view.
The first step is to name the folder view. Enter "Group By Conversation Topic."
Tip
It is a good habit to name views based on grouping and sorting criteria. That
way, as you build up a library of folder views, it is easy to remember how
the view affects the message displays.
The next step is to select the columns to be displayed in the list window. Press the Columns button to bring up the
Columns dialog box (see Figure 5.23).
Locate and select the Conversation Topic column from the list box on the left. Add this column to the very top of the list
box on the right. Now delete the From column from the list box on the right. You can save this selection by pressing OK.
Tip
You may have noticed that there is a small input box at the lower right-hand
side of the dialog box. This input box allows you to set the display width of
each column in the list. The Conversation Topic column defaults to one
character. You do not need to change this. When messages are grouped
together, their topic will appear as a header within the listing. Adding the
conversation topic to the listing would only clutter the display.
Next you need to set the grouping value. Press the Group By button to bring up the Group By dialog box (see Figure
5.24).
In the topmost combo box, select Conversation Topic and select the Ascending radio button. Notice that you
can set the sort order by activating the combo box at the bottom of the Group By dialog box. It should be set to
Received, Ascending. If it is not, set it now, and then press OK to update the grouping and sorting properties.
Now select the Filter button from the Views dialog box. This calls up the first of two filter dialogs boxes (see Figure
5.25).
Through this dialog box, you can set criteria for limiting the display of messages:
● From-Use this to limit the view to only those messages that are from a specified user or distribution list
(defined in the MAPI Address Book). Notice that you can list more than one address on this line.
● Sent To-Use this to limit the view to only those messages that you sent to a specified user or distribution list.
You can include more than one address on this line.
● Sent directly to me-Check this box if you want to see only messages addressed directly to you.
● Copied (Cc) to me-Check this box if you want to see only messages that have you on the Cc: line.
● Subject-When you enter text here, the Windows Messaging client displays only messages that have that text in
the Subject line. You cannot use wildcards.
● Message body-When you enter text here, the Windows Messaging client displays only messages that have that
text somewhere in the message body.
You can also set additional filter criteria by pressing the Advanced button on the Filter dialog box (see Figure 5.26).
A second form appears through which you can set filters based on file size, date ranges, read/unread flags, and level of
importance. You can also set filters based on forms ("show me only Job Request Forms") or document statistics ("show
me only Word documents").
For now, select Cancel from the Advanced Filter dialog box and select Clear All at the main Filter dialog box to
turn off all filtering.
Warning
It is important to remember that setting message filters affects only the
display of message folders, not their content. If you have a folder that filters
all but a few messages, you should keep in mind that there may actually be
hundreds of messages in the folder, it's just that you can see only a few.
Message filtering will not remove messages from a folder; it just hides
them.
Under the Views tab of the Folder Properties dialog box, press Apply to update the properties and then select OK to
exit the dialog box. You have created a custom view for your folder. Now it's time to test the view.
To test the new folder view you just created, you need to add a few messages. For now, you can add these messages and
replies yourself. Once you are sure the view is working properly, you can place this view in a new or existing public
folder and share it with other users.
Since this view was built as a discussion forum, you'll use a Post form instead of the standard Send form. To create a
new post in the MAPI Discussion Group, highlight the folder and select Compose | New Post in this
Folder... from the main menu. This will bring up the default posting form (see Figure 5.27).
After filling out the form and posting it, check the folder to see how the view works. You'll see that a conversation topic
has been started and that your first message appears underneath the topic. You can click on the topic to expand the
message listing and then select the message to read it. When you create a reply to the message, it is added to the folder,
under the same topic. Figure 5.28 shows you how an extended set of messages appears in a discussion folder.
Note
Although the folder we created is a discussion folder, you are not restricted
to using Post messages while you are in the folder. If you wish, you can use
Send forms to reply directly to a user's inbox. This is a way to start private
conversations with people you first meet in a public discussion group.
Although there's a lot more to creating and managing folders and views, you should now have a pretty good idea of the
possibilities. If you are interested in learning more about Microsoft Exchange folders and views, check out the
documentation that ships with the Microsoft Exchange Server.
The final step in this chapter is to install a custom form in a folder. You can install forms in personal forms libraries or
folder forms libraries. The advantage of installing forms in the personal forms library is that it is available to the users no
matter what other folder they are looking in. All they need to do is select Compose | New Forms... to locate the
form installed on the local workstation.
The advantage of installing forms in a folder form library is that each time the user enters the folder, that custom form
appears on the Compose menu. This makes it easier to find and more likely that it will be used.
For this example, you'll install the job request form in the MAPI Discussion Group folder.
Note
It actually doesn't matter what folder you use for this exercise.
First, start up the Windows Messaging client and select the target folder (the MAPI Discussion Group folder). Then
select File | Properties and select the Forms tab. This brings up the Forms page. Press the Manage button to
display the Forms Manager dialog box, and press the Set button above the list box on the left to bring up the Set
Library To dialog box (see Figure 5.29).
You've seen this dialog box before. It's the same one you used when you used the Microsoft Exchange Forms Designer
to install the job request form. Select the Personal Forms Library radio button; then press OK to return to the
Forms Manager.
You'll now see one or more topics and forms in the list box on the left side of the form (see Figure 5.30).
Locate and select the job request form in the list box on the left. Then press Copy to copy the form to the list box on the
right. You have just associated the job request form with the MAPI Discussion Group folder. Now it's time to install the
form in the folder's library.
When you press Install..., Microsoft Exchange asks you for the location of the configuration file (.CFG) for the
form. This is stored in the subfolder in which you created the job request form. Locate the folder where you saved the
JOBREQ.EFP file. You'll see a subfolder called JOBREQ.VB. This is the folder that has the Visual Basic source code
and the JOBREQ.CFG file (see Figure 5.31).
After locating and selecting the JOBREQ.CFG file, click OK to load the configuration file. Microsoft Exchange will then
show you the Form Properties dialog box from the Microsoft Exchange Forms Designer. Select a category and a
subcategory, and enter your initials as the contact name for the form (see Figure 5.32).
Now you can press Close on the Forms Manager dialog box and press OK on the Form Properties tab page. You have
just installed your form into the MAPI Discussion Group folder.
Select Compose from the main menu, and you'll see an item at the bottom of the menu list called "New Job Request
Form" (see Figure 5.33).
Figure 5.33 : Selecting the new job request form the menu.
You can start from here and run the job request form just like any other form. If you move to another folder, however,
you will not see the form on the menu. It appears on a folder's menu only if it has been installed in that folder's form
library.
Summary
In this chapter, you learned how to use the Microsoft Exchange Forms Designer kit that ships with Microsoft Exchange
Server. You learned how to design, code, test, and install custom message forms for use at individual workstations or
over a large network.
You also learned how to set up Microsoft Exchange folders for use with custom forms,
including
In the next chapter, you'll learn how to use the Messaging API to create MAPI-aware windows applications that can read
and write MAPI messages from outside Windows Messaging clients.
Chapter 9
Creating a MAPI Mailing List Manager with the OLE Messaging Library
CONTENTS
● Introduction
❍ Laying Out the MLM Form
❍ Dropping Subscribers
❍ Listing Archives
● Summary
Introduction
After reviewing the OLE Messaging Library objects in Chapter 8, "The OLE Messaging Library," you're now ready to
build a MAPI application for Win95 and Visual Basic 4.0 that uses these objects.
The Mailing List Manager application lets users define and manage automated mailing lists from the client desktop.
Messages can be distributed within a single server or across the Internet (depending on the available transports at the
desktop). All access to MAPI services will be performed through the OLE Message objects.
The MLM application allows individuals to create a set of text files to be distributed to a
controlled list of users at specified times. This project has only one simple form and several support routines. All
application rules are stored in a set of ASCII control files similar to INI/registry settings. These control files can be
changed by the list manager to determine how the mailing list operates and what features are available to subscribers.
Once you complete this application, you'll be able to establish and manage one or more one-way mailing lists from your
own desktop. These mailing lists can be limited to your current attached server or cross over any transport out onto the
Internet (depending on the transports installed on your desktop).
The MLM application has only one form. Since the primary purpose of the application is to manage automated lists,
there is very little needed in the way of a GUI interface. MLM has a set of command buttons to initiate specific tasks and
a single scrollable text box to show progress as the application processes incoming and outgoing mail.
Start a new Visual Basic project and lay out the MLM form. Refer to Table 9.1 and Figure 9.1 for details on the size and
position of the controls on the form.
Note that the layout table calls for a control array of command buttons. Add a single button to the form, set its properties
(including the Index property), and then use the Edit | Copy, Edit | Paste menu options to make the additional
copies of the button. You can then edit the Caption properties as needed.
After you lay out the form, you need to add a handful of variables to the general declaration area, a few routines to
handle the standard form events, and one routine to respond to the command-button actions. Listing 9.1 shows the code
that declares the form-level variables for this project. Add this code to the general declaration area of the form.
Option Explicit
Next, add the code in Listing 9.2 to the Form_Load event. This code centers the form and then stores its current width
and height. This information will be used to prevent users from resizing the form at run-time.
You'll also notice that the Form_Load event checks for a parameter passed on the command line at startup. This will be
used to determine what set of control files will be used for each run of the MLM application (you'll see more about this
later).
Next, add the code in Listing 9.3 to the Form_Resize event. This code uses the values established in the Form_Load
event to keep forcing the form back to its original size whenever a user tries to adjust the form size. Note, however, that
this routine will allow users to minimize the form.
You also need to add code behind the command-button control array. Listing 9.4 contains the code that should be placed
in the Command1_Click event. This routine just calls a set of custom subroutines that you'll add a bit later in the
chapter.
One more line of code is needed to complete this section. The text box control should be a read-only form object. By
adding the following line of code to the Text1_KeyPress event, you can trick Visual Basic into ignoring any
keyboard input performed within the text box control.
That's the code needed to support form events and controls. Save this form as MLM.FRM and save the project as MLM.
VBP. In the next section you'll add a series of simple support routines to the project.
Now you'll add a few support routines that are called frequently from other, high-level routines in the project. You'll add
all these routines to the general declaration section of the form.
First, add a new subroutine called Status, and add the code shown in Listing 9.5.
The code in the Status routine places a new line in the text box. This will be used to pass progress information to the
text box control as the MLM is processing subscriber lists and the Microsoft Exchange inbox.
The MLM project gets its primary instructions from a set of ASCII text control files. The next routine you'll build in this
section is the one that reads the master control file. Add a new subroutine called ControlsLoad to the project, and
enter the code shown in Listing 9.6.
Notice that the ControlsLoad routine reads each line of the ASCII text file, and if it is not a comment line (that is, it
starts with a ";"), it parses the line into a control name array and a control value array. You'll use these values throughout
your project.
Now that the control values are stored in a local array, you need a routine to retrieve a particular control value. Add a
new function (not a subroutine) to the project called ControlSetting, and add the code shown in Listing 9.7.
The ControlSetting function accepts a single parameter (the name of the control value you are requesting) and
returns a single value (the value of the control setting you named). This routine accomplishes its task by simply reading
through the array of control names until the name is found.
That's all for the general support routines. Save this form and project again before continuing.
This next set of routines allows users to edit the various control files required to manage the project. You'll use a call to
the NOTEPAD.EXE applet to edit the control files. This is much easier than spending the time to write your own text file
editor. Also, the first time you call these routines you'll be prompted to create the new files.
Add a new subroutine called ControlsEdit to the form, and enter the code shown in Listing 9.8.
This routine first attempts to load the master control values, then launches the default editor to allow users to modify
those values. You can also see the use of the Status routine to update the form's text box. Go back to the
Command1_Click routine (see Listing 9.4) and remove the comment from in front of the ControlsLoad command.
Then save this project.
Before you can run this routine, you need to create the default control file. Start NOTEPAD.EXE and enter the
information shown in Listing 9.9. Once you complete the entry, save the file in the same folder as the MLM project and
call it MLM.TXT.
Tip
If you get errors attempting to launch the editor from these routines, you can
include the drive and path qualifiers in the Editor control value.
; ===================================================
; Mailing List Control values for MLM
; ===================================================
;
; read by MLM.EXE
;
; ===================================================
;
MAPIUserName=MCA
MAPIPassword=
SearchKey=MLM
ListName=MLM Mailing List
NewSub=SUB
NewSubMsg=MLMHello.txt
UnSubMsg=MLMBye.txt
UnSub=UNSUB
GetArchive=GET
ListArchive=LIST
ArchiveFile=MLMArch.txt
ListSchedule=MLMSked.txt
ListSubs=MLMSubs.txt
Editor=notepad.exe
Tip
If you don't want to spend time entering this control file information, you
can find it in the MLM folder that was created when you installed the
source code from the CD-ROM.
There are several entries in this control file. For now, make sure that the control names and values are entered correctly.
You'll learn more about how each one works as you go along. Once you get the hang of the control file, you can modify
it to suit your own mailing-list needs.
Now add a new subroutine, called SubEdit, to allow the editing of the subscriber list. Enter the code in Listing 9.10
into the routine.
Normally you will not need to pre-build the subscriber file. It will be created as you add new subscribers to your mailing
list via e-mail requests. However, for testing purposes, open up Notepad and enter the values shown in Listing 9.11.
When you are done, save the file as MLMSUBS.TXT in the same folder as the Visual Basic project.
Listing 9.11. Creating the test MLMSUBS.TXT file.
; ====================================================
; Mailing List Subscriber File
; ====================================================
;
; Read by MLM.EXE
;
; format:name^address^transport
;
; where:name = display name
; address = e-mail address
; transport = MAPI transport
;
; example:Mike Amundsen^mamund@iac.net^SMTP
;
; ====================================================
;
Michael C. Amundsen^mamund@iac.net^SMTP
Mike Amundsen^102461,1267^COMPUSERVE
The addresses in the file may not be valid e-mail addresses on your system, but they illustrate the format of the file. Each
address entry has three parts:
As users request to be on your mailing list, their mailing information is added to this file. Later in the chapter, you'll add
yourself to this list by sending yourself an e-mail request.
The next control file needed for the MLM application is the schedule file. This control file contains information on the
display name, complete filename, and scheduled delivery date of messages to be sent by MLM. Create a new routine
called SkedEdit, and add the code in Listing 9.12.
You'll need to create a default schedule file for this project. Listing 9.13 shows the schedule file format. Use NOTEPAD.
EXE to build this file and save it in the project directory as MLMSKED.TXT.
; ==================================================
; Mailing List Schedule file
; ==================================================
;
; read by MLM.EXE
;
; format: YYMMDD,uafn,title
;
; where: YYMMDD = Year, Month, Day
; uafn = unambiguous file name
; title = descriptive title
;
; example: 960225,MLMFAQ.txt,MLM FAQ Document
;
; ==================================================
You can see from the sample file that there are three control values for each entry:
● YYMMDD-The year, month, and day that the message should be sent.
● UAFN-An unambiguous filename. The contents of this text file will be placed in the body of the message to be
sent.
● Title-This is the title of the message. This value will be placed on the subject line of the message that is sent.
As you build your mailing list message base, you can add lines to this control file.
The last edit routine to add to the project is the one used to edit the archive list. Add a new subroutine called ArchEdit
to the project, and enter the code shown in Listing 9.14.
Again, you'll need to create an initial archive listing file before you first run your project. Use NOTEPAD.EXE to build a
file called MLMARch.TXT and enter the data shown in Listing 9.15. Save this file in the project directory.
; ==================================================
; Mailing List Archive File
; ==================================================
;
; read by MLM.EXE
;
; format: YYMMDD,uafn,title
;
; where: YYMMDD = Year, Month, Day
; uafn = unambiguous file name
; title = descriptive name
;
; example: 960225,MLMFAQ.txt,MLM FAQ Document
;
; ==================================================
This file format is identical to the one used in the MLMSKED.TXT file. The contents of this file can be requested by
subscribers when they want to retrieve an old message in the database. By passing a GET YYMMDD line in the message
subject, subscribers can get a copy of the archive file sent to them automatically.
This is the last of the edit routines for the project. Be sure to save this project before you continue.
Coding the MAPIStart and MAPIEnd routines
Before you can start processing messages, you need to build the routines that will start and end your MAPI sessions. Add
a new subroutine called MAPIStart to the project, and enter the code that appears in Listing 9.16.
Note the use of the OLE Messaging Library as the means of access into the MAPI system. Now add the MAPIEnd
subroutine to your project and enter the code from Listing 9.17.
These two routines are the start and end of the ReadMail and SendMail routines you'll add in the next two sections.
Create a new routine called SendMail and add the code shown in Listing 9.18.
The SendMail routine first clears the status box and loads the master control file. Then the MAPIStart routine is
called. Once the MAPI session is established, the routine calls ProcessSubList to handle all processing of the
subscriber list. After the list is processed, the MAPIEnd routine is called and the status box is updated along with
message to the user announcing the completion of the processing.
Next add the ProcessSubList subroutine, and enter the code shown in Listing 9.19.
The main job of the ProcessSubList routine is to open the schedule file, and see if there is a message to send for
today's date. If one is found, the routine opens the subscriber control file and calls the ProcessSubListMsg routine
to compose and send the message.
Finally, add the ProcessSubListMsg routine and enter the code that appears in Listing 9.20.
The most important part of the ProcessSubListMsg routine is the last section of code that composes and addresses
the message. There are two main processes in this part of the routine. The first process is the creation of a new
Message object:
The second process is the creation of a new Recipient object and the addressing of the message:
Notice that addressing is handled a bit differently for MS-type messages. Messages with the address type of MS are
addresses within the Microsoft addressing scheme-they're local addresses. To handle these items, you only need to load
the Name property, set the recipient type (To:), and then call the MAPI Resolve method to force MAPI to look up the
name in the address book(s). When the name is found, MAPI loads the Address property with the complete transport
and e-mail address for routing. This is how most MAPI messages are usually sent.
However, for messages of type other than MS, it is likely that they are not in the locally available address books. These
messages can still be sent if you load the Address property of the message with both the transport type and the user's e-
mail address. This is the way to handle processing for messages that were sent to you from someone who is not in your
address book. This is known as one-off addressing. One-off addressing ignores the Name property and uses the
Address property to route the message.
That is all the code you need to send out daily messages to your subscriber list. The next set of routines will allow your
application to scan incoming messages for mailing list-related items and process them as requested.
Tip
It is a good idea to save your project as you go along. The next set of
routines are a bit longer and you may want to take a break before continuing.
The next set of routines handles the process of scanning the subject line of incoming messages for mailing-list
commands. These commands are then processed and subscribers are added or dropped from the list and archive items are
sent to subscribers as requested.
The Inbox processing can recognize four different commands on the subject line. These commands are:
● SUB-When this command appears in the subject line of a message, the sender's name and address are added to
the subscriber control file.
● UNSUB-When this command appears in the subject line of a message, the sender's name and address are
removed from the subscriber control file.
● LIST-When this command appears in the subject line of a message, MLM will compose and send a message
that contains a list of all the archived messages that the subscriber can request.
● GET YYMMDD-When this command appears in the subject line of a message, MLM will retrieve the archived
message (indicated by the YYMMDD portion of the command) and send it to the subscriber.
Tip
The exact word used for each of these four commands is determined by
settings in the master control file. See Listing 9.9 for an example of the
master control file. If you want to change the values for these commands,
you can do so in the control file. This is especially useful if you plan to
manage more than one list from the same e-mail address. Adding prefixes to
the commands will help MLM distinguish which command should be
respected and which commands are for some other mailing list.
The first three routines for handling the inbox are rather simple. The first routine clears the progress box, loads the
controls, calls the ProcessInbox routine, and performs cleanup functions upon return. Add the ReadInbox
subroutine and add the code shown in Listing 9.21.
Next, add a new subroutine called ProcessInbox, and add the code that appears in Listing 9.22.
This routine performs three main tasks. The first is to open the messages stored in the Inbox folder. Every message
store has an Inbox folder. All new messages are sent to the Inbox folder upon receipt.
The second step is to create a collection of all the messages in the Inbox folder. You access messages as a collection of
objects in the folder.
The third process in this routine is to inspect each message in the collection to see if its subject line contains the search
key word from the master control file. If found, the message is passed to the ProcessInboxMsg routine for further
handling.
Now add the ProcessInboxMsg subroutine. This routine checks the content of the message for the occurrence of
MLM command words (SUB, UNSUB, LIST, GET). If one is found, the appropriate routine is called to handle the
request. Enter the code shown in Listing 9.23.
Listing 9.23. Adding the ProcessInboxMsg routine.
In the next few sections you'll add supporting code to handle all the MLM subject-line commands. It's a good idea to
save the project at this point before you continue.
When a person sends you an e-mail message with the words MLM SUB in the subject line, the MLM application adds
that person's e-mail address to the subscriber list. The next set of routines handles all the processing needed to complete
that task. This version of the program will also automatically send the new subscriber a greeting message (one that you
designate in the control file).
First, add a new subroutine called ProcessInboxMsgNewSub to the project, and enter the code shown in Listing
9.24.
This routine first checks to see if the name already exists in the subscriber control file. If not, it is added and the new
subscriber is sent a friendly greeting message.
Create a new function called SubFind, and enter the code from Listing 9.25.
The SubFind routine accepts one parameter (the name to look up), and returns True if the name is found and False
if the name is not in the subscriber list.
Next, add the SubWrite subroutine to the project. The code for this routine is in Listing 9.26.
Notice that the SubWrite routine copies the Name, Address, and Type properties from the AddressEntry object
into the subscriber control file. Each value is separated by a caret (^). This separator character was chosen somewhat
arbitrarily. You can change it if you wish.
Warning
If you change the separator value, be sure you don't use a character that could be part of a
valid e-mail address. E-mail addresses today can have a comma (,), colon (:), semicolon (;),
slashes (/ or \), and other characters. You'll need to be careful when you choose your separator
character!
Next add the SubGreet routine. This composes and sends a friendly greeting message to all new subscribers. Add the
code from Listing 9.27 to your project.
The SubGreet routine looks similar to the routine used to send daily messages to subscribers. The message sent as the
greeting pointed to the NewSubMsg parameter in the master control file.
Save your work before adding the code to drop subscribers from the list.
Dropping Subscribers
The routines needed to drop subscribers from the mailing list are very similar to the code needed to add them. You'll
create a routine to respond to the request and two supporting routines-one to delete the name from the list, and one to
send a goodbye message to the requester.
First add the ProcessInboxMsgUnSub subroutine to the project and enter the code from Listing 9.28.
Listing 9.28. Adding the ProcessInboxMsgUnSub routine.
This routine checks to make sure the name is in the subscriber list. If it is, then the name is dropped and a goodbye
message is sent. Add the SubDelete routine to the project by copying the code from Listing 9.29.
This routine accomplishes the delete process by copying all the valid names to a temporary file, and then erasing the old
file and renaming the temporary file as the new master subscriber list. While this may seem a bit convoluted, it is the
quickest and simplest way to handle deletes in a sequential ASCII text file.
Note
In a more sophisticated project, you could build the subscriber list in a
database and use database INSERT and DELETE operations to manage the
list.
Next add the SubBye routine. This sends a goodbye message to the subscriber that was just dropped from the list. The
greeting message is kept in the text file pointed to by the value of the UnSubMsg control parameter in the master control
file.
The next code routines will handle subscriber requests for the list of retrievable archive
messages.
Listing Archives
One of the added features of MLM is to allow subscribers to send requests for copies of old, archived messages. Users
can also request a list of messages that are in the archive. You need two routines to handle the LIST command-the main
caller, and the one to actually assemble and send the list.
Add the new subroutine ProcessInboxMsgArcList to the project and enter the code shown in Listing 9.31.
Now create the WriteArcList subroutine and add the code shown in Listing 9.32.
The WriteArcList routine reads the MLMARch.TXT control file and creates a message body that has a brief set of
instructions and lists all available archived messages. Once this is done, the message is addressed and sent.
Once subscribers have received a list of available archives, they can send a MLM GET YYMMDD command on the
subject line of a message to ask for a specific message to be sent to them. You need three routines to handle this
processing:
First add the ProcessInboxMsgArcGet subroutine to your project and enter the code in Listing 9.33.
Finally, add the WriteArcGet routine to the project and enter the code shown in Listing 9.35.
The WriteArcGet routine picks the archive name out of the subject line and, if it is found, reads the archived
message, composes a new message, and sends it to the requestor.
That is all the code for this project. The next step is to test the various MLM functions. Be sure to save the project before
you begin testing. Once testing is complete, you can make an executable version of the project for installation on any
workstation that has the MAPI OLE Messaging Library installed.
In a production setting, you can set up an e-mail account that is dedicated to processing MLM requests. All e-mail
messages regarding that list can be addressed to this dedicated account. You (or someone else) can run the MLM once a
day and it can automatically log onto the dedicated account and perform the list processing. Also, since MLM accepts a
command-line parameter, you can build control files for several different mailing lists and run them all from the same
workstation. You just need to keep in mind that in order to process the messages, you need to start the MLM application
and run it at least once a day.
Tip
If you have the Microsoft Plus! pack installed, you can use the System
Agent to schedule MLM to run at off-peak times. If you decide to use the
System Agent, you need to add additional code to the project to allow you
to select the Read & Send button automatically. Just add an additional
parameter to the command line that will execute the Command1_Click
event for Read & Send.
For testing purposes, set the MLM control file to log onto your own e-mail account. You can then send messages to
yourself and test the features of MLM to make sure they are working properly.
Before you start testing you need to make sure you have valid ASCII files for the new subscribe greeting (MLMHELLO.
TXT) and the unsubscribe departing message (MLMBYE.TXT). Refer to Listings 9.36 and 9.37 for examples of each of
these files.
Tip
You can also find examples in the source code directory Chap08\MLM
created when you installed the CD-ROM.
Welcome!
MCA
BYE - MCA
Use NOTEPAD.EXE to create these two files and save them in the project directory.
You may also need to modify the MLMSKED.TXT file to match today's date. Change the first entry from 960225 to make
sure that any registered subscriber gets a message when you run the SendMail routine.
Once the request is sent, load MLM and select the ReadMail button. This will scan all the messages in your inbox, and
(if all goes right!) find and process your request to be added to the list. Figure 9.3 shows how the MLM screen looks as it
is processing.
After MLM is done, start up your MAPI client again. You should see a greeting message in your inbox confirming that
you have been added to the MLM mailing list (see Figure 9.4).
Next, try sending a message that requests a list of available archives. Use your MAPI client to compose a message with
MLM LIST in the subject line. After sending the message, run the MLM ReadMail option again, and then check your
inbox with the MAPI client. You should see a message similar to the one in Figure 9.5.
Once you get the list, you can send MLM a command to retrieve a specific message in the archive. Use your MAPI
client to send a message that asks for one of the messages. After sending the message, run MLM ReadMail, and then
recheck your MAPI client for the results. You should get a message like the one in Figure 9.6.
Tip
You have probably noticed that messages that have already been read and
processed are being processed again each time you run MLM. You can
prevent this from happening by deleting the messages from the inbox by
hand, or by adding code to the project to delete each message after you are
finished processing it.
You can test the SendMail option by simply starting MLM and pressing the SendMail button. This will search for a
message file tagged with today's date and send it to all subscribers in the list. To test this, modify one of the entries in the
MLMSKED.TXT file so that it has the current date as its key number. Figure 9.7 shows what the MLM progress screen
looks like as it is working.
Finally, you can test the unsubscribe feature of MLM by sending a message with MLM UNSUB in the subject. When
MLM receives this message, it will drop your name from the subscriber list and send you a departing message.
Summary
In this chapter you built a mailing list manager using Microsoft's OLE Messaging Library. This application lets you
define a mailing list, allow others to become list subscribers, publish messages automatically (based on date), and allow
others to query and retrieve old messages from the list archives. This program can accept members from within a single
network, or from around the world through Internet (or other) transports.
In the next chapter you'll use OLE to build a MAPI application that allows distant users to query databases and other
collections of information by way of e-mail.
Chapter 10
CONTENTS
In this chapter you'll learn how to use MAPI services to create a threaded discussion client. Threaded discussions clients
are used quite frequently in groupware settings and on the Internet. The biggest difference between a discussion tool and
an e-mail client is that the e-mail client is designed primarily for conducting one-to-one conversations. The discussion
tool, however, is designed to support multiple parties, all participating in the same discussion. Even though MAPI
services were originally deployed as only an e-mail solution, Microsoft has added several features to MAPI that now
make it an excellent platform for building discussion applications.
Before getting into the details of building the tool, you'll learn a bit more about the differences between e-mail and
discussion communications. You'll also learn how to take advantage of little-known MAPI properties and new features
of the OLE Messaging library that make it easy to put together a standalone discussion tool that can be deployed from
within a Local Area Network, over a Wide Area Network, or even across the Internet. You'll learn
As an added bonus, after you've completed the programming example in this chapter, you'll have a set of library
functions that allow you to easily add threaded discussion capabilities to new or existing Visual Basic applications.
There are a number of other advantages to using discussion groups instead of e-mail. Often, it is easier to find answers to
technical problems if you can go to a place where all the "techies" hang out. Discussion forums can work the same way.
Instead of trying to e-mail several people in attempts to solve your problem, you can often find a discussion forum where
you can send a single request that will reach hundreds (possibly thousands) of people who may be able to help you. So
discussion forums can increase your ability to get your problems solved sooner.
Another good way to highlight the differences between e-mail and discussion groups is by comparing several face-to-
face meetings (e-mail messages) with a single staff meeting where everyone shows up at the same place at the same time
(the discussion forum). Using forum communications can reduce the amount of time you need to spend communicating.
You can say it once and reach lots of people instead of having to repeat your message in lots of single e-mail messages.
Companies use forums as a way to communicate information of general interest as well as a way to take advantage of
specialized expertise within an organization. Where corporate e-mail can lead to isolated individuals communicating in a
vacuum, discussion groups can foster increased interaction with others and a sense of belonging to a special group-even
if one or more of the team members is half-way around the world.
Now that you have a general idea of what discussion groups are and how you can use them in your organization, you're
ready to review the three key concepts that make discussion systems different from e-mail systems.
Folders as Destinations
First, and most important, users participating in an online discussion forum do not send messages to individuals. Instead
they send their messaging to a single location where all messages are stored. In the MAPI model, the storage location is
called a folder. When you create MAPI-based forum tools, you'll select a folder (or folders) to hold all the
correspondence from others participating in the forum.
Note
There are times when forum participants will initiate private e-mail
correspondence in order to cover a topic in more depth or to delve deeper
into an aspect of the discussion that would not be interesting to most of the
other members.
Then, after each message has been composed, you just post the message to the discussion folder for others to see. They,
if appropriate, respond to your message with their own comments. The key point to remember is that messages are
addressed to locations, not people.
Another key aspect of discussion groups is the ability to track topics in a thread that leads from the beginning of the
discussion to the end. As each participant replies to an existing message, a relationship is established between the source
message and the reply. Of course, it is perfectly correct (and quite common) to generate a "reply to a reply." This can
continue on indefinitely. And in the process an intricate web of ideas and comments all collect in the storage folder that
houses the discussion group.
Some discussion tools simply list the messages in the chronological order in which they were received. These are
referred to as non-threaded or flat-table discussion tools (see Figure 10.1).
Non-threaded tools (often called readers) can be handy, but most people prefer the discussion tools that provide threaded
discussion tracking. With threaded tools, each conversation that is initiated is a branch. Each branch can have its own
subsequent conversation thread. And those threads can be combined with others, too. Viewing discussion messages as
related threads is really handy (see Figure 10.2).
MAPI uses two special properties of the Message object to keep track of each branch and sub-branch of a threaded
discussion. These two properties are the ConversationTopic and the ConversationIndex properties. The
ConversationTopic is usually a readable string. This is much like the subject line of a mail message. Conversation
topics for a discussion forum for a typical computer firm might be
● General questions
● Sales
● Technical support
● Press releases
Note
Some forums have fixed topics and only allow users to select from a
predetermined list when posting messages. Other forums allow users to
enter free-form text into the topic field.
MAPI uses the ConversationIndex property to keep track of conversation threads within the topic group. This is
usually done using some type of numbering scheme. Microsoft recommends that programmers use the same method that
is employed by Microsoft Exchange when populating the ConversationIndex field. Microsoft Exchange uses the
CoCreateGuid API call to generate a unique number to place in the ConversationIndex field. New messages
that respond to an existing message should inherit the ConversationIndex of the previous message and append
their own index value to the right. This way, each new branch of the conversation has a longer ConversationIndex.
You'll get a better look at this technique when you build the sample Visual Basic project later in this chapter.
Note
The CoCreateGuid API generates a unique value based on the system
date (among other things). You can use any method you wish to keep track
of conversation threads, but it is highly recommended that you use the
method described here. It is quick, effective, and requires very few
computing resources.
The last major concept to deal with when building discussion tools is the use of the MAPI Update method instead of
the Send method. In all the examples covered in the book so far, when you complete a new message and want to place it
into the MAPI message pool, you use the Send method (or some type of Send API call) to release the message to
MAPI for delivery. This works when you have a person to whom you are addressing the message. However, because
discussion messages are not addressed to persons but to folders instead, you cannot "send" new messages. Instead you
use the Update method to update the contents of a MAPI folder. In fact, attempting to use the Send method to deliver
forum messages will result in a MAPI error.
Tip
The key idea here is to think of updating the contents of a MAPI folder as
opposed to sending a message to a person.
The actual process of placing a message in a MAPI folder includes adding a blank message to the target folder, setting
that message's subject and text body (just like any mail message) and then setting several other message properties, too.
Each of the properties shown in Table 10.1 must be set properly before you invoke the Update method to place a
message in a MAPI folder.
Note
You'll get to see the details of creating a forum message when you build the
forum tool later in this chapter. The main thing to keep in mind now is that
you do not use the Send method to place messages in folders.
So there are the three key points to remember about the differences between e-mail and discussion messages:
Now it's time to use Visual Basic to build the example forum tool.
For the rest of the chapter, you'll build a complete discussion tool that can be used to track ongoing messages in a target
folder. You'll be able to use the program to read messages, generate replies to existing messages, and start new
discussion threads. You'll also be able to select the target folder for the discussion. This way, you can use the same tool
to monitor more than one discussion.
Note
The forum tool described in this chapter is a very basic project. Not much
time will be spent on the user interface. This is done to keep focus on the
general issues of creating a discussion tool for MAPI systems. If you plan to
put a version of this project into production, you'll want to add some bells
and whistles to make this a more friendly product.
You'll use the OLE Messaging library (OML) to access MAPI services for this project. The OML gives you all the
features you need to be able to manipulate message and folder properties within the MAPI file system. Although you can
use the MAPI.OCX to read most of the discussion-related properties, you cannot use the OCX to place messages in a
target MAPI folder. Also, you cannot use the simple MAPI API declarations to gain access to target folders or to
manipulate message properties.
Tip
If you are planning to build any software that manipulates folders, you'll
need to use the OLE Messaging library. No other Microsoft programming
interface (besides the C MAPI interface) allows you to gain access to the
folders collection.
There is one main code module and several forms in the Discuss project you'll build here.
● MAPIPost-This code module holds all the routines for posting new threads, replying to threads, and collecting
information on the available information stores, folders, and messages available to the workstation. You can use
the code here in other Visual Basic discussion projects.
● modDiscuss-This code module contains a few constants and global variables used for the Discuss project.
● mdiDiscuss-This is the main form. This acts as a wrapper for the note form and the message list form.
● frmNote-This form is used to read, compose, and reply to existing forum messages.
● frmMsgs-This form is used to display a list of discussion messages within the target folder. The messages are
shown in the threaded discussion form.
● frmOptions-This form allows users to change the target folder for the discussion, set the default MAPI logon
profile, and control whether the message listing is shown in threaded or flat-table format.
● frmAbout-This is the standard About box for the project.
Note
Before you start coding the Discuss project, make sure you have added the
OLE Messaging type library. Do this by selecting Tools | References
from the main menu of Visual Basic. You will need to install the OLE
Messaging library from the MSDN CD-ROM before you can locate it using
the "Tools | References" menu option.
The MAPIPost Code Library contains several very important routines. These routines will be used throughout the
project to gain access to folder collections and message collections within the folders. This also holds routines for
posting new messages and generating replies to existing messages. There are also several routines for performing folder
and message searches. You'll be able to use the routines in this module in other MAPI-related projects.
First, start a new Visual Basic project and add a BAS module (Insert | Module). Set its Name property to
MAPIPost and save the file as MAPIPOST.BAS. Now add the code shown in Listing 10.1 to the declaration section of
the form.
Option Explicit
'
' OLE message objects
Public objSession As Object
Public objMsgColl As Object
Public objMsg As Object
Public objRecipColl As Object
Public objRecip As Object
Public objAttachColl As Object
Public objAttach As Object
Public objAddrEntry As Object
Public objUserEntry As Object
Public objFolderColl As Object
Public objFolder As Object
Public objInfoStoreColl As Object
Public objInfoStore As Object
Public gnIndentlevel as Integer
Public cStoreID As String
'
' UDT for store/folder pairs
Type FolderType
Name As String
FolderID As String
StoreID As String
End Type
'
Public FolderRec() As FolderType ' members
Public iFldrCnt As Integer ' pointer
'
' UDT for message/conversation pairs
Type MsgType
Subject As String
Topic As String
ConvIndex As String
MsgID As String
Date As Date
End Type
'
Public MsgRec() As MsgType ' members
Public iMsgCnt As Integer ' pointer
'
' type for creating Exchange-compliant timestamp
Type GUID
guid1 As Long
guid2 As Long
Listing 10.1. continued
guid3 As Long
guid4 As Long
End Type
'
Declare Function CoCreateGuid Lib "OLE32.DLL" (pGuid As GUID) As Long
Public Const S_OK = 0
You'll notice the usual OLE Messaging library object declarations along with two user-defined types that will be used to
keep track of message and folder collections. This will come in quite handy as you'll see later on. There is also the
CoCreateGuid API call. You'll use this to generate unique ConversationIndex values for your message threads.
Next you need to add code that starts and ends MAPI services. Listing 10.2 shows the OLEMAPIStart and
OLEMAPIEnd routines. Add them to your project.
The next routines are used to build a collection of all the folders in all the message stores available to the workstation.
You'll remember that MAPI 1.0 allows more than one message store for each workstation. Typically, users will have a
personal message store, a server-based collection of messages, and possibly a message store related to an outside
messaging service (such as Sprint, CompuServe, and so on). Each one of these stores has its own set of folders, too. The
routine in Listing 10.3 shows you how you can enumerate all the folders in all the message stores and build a local user-
defined type that you can use to locate and manipulate MAPI folders. Add the code shown in Listing 10.3 to your
project.
You'll see that the routine in Listing 10.3 walks through all the attached message stores and calls the LoadFolders
routine to actually collect all the folders in a message store. This routine also allows you to pass an option list control
(list box, combo box, or outline control). The routine will use this control to build an onscreen pick list of the available
folders.
Now add the LoadFolders routine from Listing 10.4 to your project.
Notice that this routine is called recursively in order to collect all the folders that might be found within a folder. Since
MAPI places no restrictions on how many levels of folders may be defined, you need to use a recursive routine to locate
all the available folders.
Once you have built the folder collection, you'll need a method for pulling information out of the collection. Listing 10.5
shows a function that will take the friendly name of a folder and return the unique folder ID and store ID. Add this to
your project.
Warning
This routine will return the folder record of the first folder with the name
you request. Because MAPI allows users to define two folders with the
same name, this routine may not always return the results expected. This
works fine for most projects, but you should keep it in mind when
developing MAPI search tools.
You also need some tools for collecting and accessing all the discussion messages in a folder. Listing 10.6 shows the
routine that you can use to collect all messages into a local user-defined type. Add the routine to your project.
You'll need three different routines to access messages from the user-defined array. First, you need a routine that allows
you to pass in a pointer to the sorted list and that returns the ConversationIndex of a message. Listing 10.7 shows
how this is done.
Next you need a routine that takes the conversation index and returns the complete internal message structure. Listing
10.8 shows you how to do this step.
'
' start w blank records
GetMsgRec.MsgID = ""
GetMsgRec.ConvIndex = ""
GetMsgRec.Subject = ""
GetMsgRec.Topic = ""
'
' now try to find it
If iPointer < 0 Or iPointer > UBound(MsgRec) Then
MsgBox "Invalid Message pointer!", vbExclamation, "GetMsgRec"
Exit Function
Else
GetMsgRec = MsgRec(iPointer + 1)
End If
'
End Function
While you're coding the message routines, add the FillOutline subroutine shown in Listing 10.10. This routine
loads an outline control from the sorted list. The outline can then be displayed to the user.
The FillOutline routine also makes sure threaded messages are indented properly and expands the entire message
tree for users to see the various branches.
One more handy routine is the MakeTimeStamp function. This will be used to generate the ConversationIndex
values. Add the code from Listing 10.11 to your project.
LocalErr:
MsgBox "Error " & Str(Err) & ": " & Error$(Err)
MakeTimeStamp = "00000000"
Exit Function
'
End Function
Only two routines remain: OLEMAPIPostMsg and OLEMAPIReplyMsg. The OLEMAPIPostMsg routine builds and
posts a new message thread to the target folder. Add the code from Listing 10.12 to your project.
There are quite a few things going on in this routine. First, it locates the folder and message store where the message will
be posted. Then a new message object is created, populated with the appropriate values, and posted (using the Update
method) to the target folder.
The OLEMAPIReplyMsg is quite similar, but this method carries information forward from the source message to
make sure that the conversation thread is maintained. Add the code from Listing 10.13 to your project.
That's all there is to this module. Save the module (MAPIPOST.BAS) and the project (DISCUSS.VBP) before moving
on to the next section.
The two main forms for the Discuss project are the MDI Discuss form and the Msgs form. The MDI form presents a
button array and hosts all the other forms. The Msgs form is used to display the threaded discussion list.
You'll also need to add a few values to a short BAS module. These are project-level values that are used throughout the
project. Add a BAS module, set its Name property to ModDiscuss and save it as MODDISCUSS.BAS. Now add the
code shown in Listing 10.14 to the general declaration section of the form.
Option Explicit
'
' constants
Public Const dscRead = 0
Public Const dscNewPost = 1
Public Const dscReply = 2
'
' variables
Public cGroup As String
Public cProfile As String
Public bThreaded As Boolean
That's all you need to add to this form. Save it (MODDISCUSS.BAS) and close it now.
After you complete the form layout, save the form as MDIDISCUSS.FRM before you begin coding.
Listing 10.15 shows the MDIForm_Load event. Add this to your project.
'
lblStatus = "Logging into Discussion Group [" & cGroup & "]..."
'
OLEMAPIStart cProfile
CollectFolders
OLEMAPIGetMsgs cGroup, frmMsgs.list1
FillOutline frmMsgs.list1, frmMsgs.Outline1
frmMsgs.Show
lblStatus = ""
'
End Sub
Listing 10.16 shows the code for the Activate and Resize events of the form. Add these two routines to your
project.
Finally, add the code from Listing 10.17 to the MDIForm_Unload event.
'
' write to registry
SaveSetting App.EXEName, "Options", "Profile", cProfile
SaveSetting App.EXEName, "Options", "Group", cGroup
SaveSetting App.EXEName, "Options", "Threaded", bThreaded
'
'drop all loaded forms
'
Dim x As Integer
Dim y As Integer
'
x = Forms.Count - 1
For y = x To 0 Step -1
Unload Forms(y)
Next
End
'
End Sub
Next, you need to add code to the cmdMain_Click event to handle user selections from the button array. Listing
10.18 shows the code to handle this.
You can see that the cmdMain routine calls two other routines: LoadMsgRec and NewMsgRec. You need to add these
routines to your project. First, add the NewMsgRec subroutine to the form. Add the code from Listing 10.19.
The code in Listing 10.19 initializes values on the note form and then calls the form for the user.
The code for the LoadMsgRec routine is a bit more complicated. This routine must first locate the selected message,
load it into the form, and then call the note form. Add the code in Listing 10.20 to your form.
Listing 10.20. Adding the LoadMsgRec routine.
The Msgs form is used to show the threaded discussion list. Refer to Figure 10.4 and Table 10.3 for details on laying out
the form.
Note
You'll notice that the form contains both an outline control and a list box
control. The list box control is not visible at run time. It is used to
automatically sort the messages by conversation index.
There is very little code to the form. Listing 10.21 shows all the code you need to add to the Msgs form.
Save this form as DSCMSGS.FRM and update the project before going to the next step.
Building the Other Forms
There are three other forms you need to add to the project. The Note form will be used to read and reply to messages; the
Options form allows the user to set and store some program options; and the About dialog box contains typical program
information.
Add a new form to the project and set its Name property to frmNote. Refer to Table 10.4 and Figure 10.5 in laying out
the note form.
After laying out the form, save it (DSCNOTE.FRM) before you add the code.
Along with the typical form-related events, you need to add code to handle user button selections and a custom routine to
establish the mode of the form (read, reply, or new message).
First, Listing 10.22 shows all the code for the Form_Load, Form_Activate, and Form_Resize events. Add this
to your project.
Listing 10.22. Adding the Form_Load, Form_Activate, and Form_Resize code.
Worth mentioning here is the Form_Resize code. This code will adjust controls on the form to fill out as much (or as
little) screen area as is allowed.
Another routine that deals with form controls is the StatusUpdate routine (Listing 10.23). This routine toggles the
various controls based on the mode of the form (read, reply, or new message). Add the code from Listing 10.23 to your
form.
The last routine you need to add to the form is the code for the cmdBtn_Click event. This also uses the mode of the
form to determine just what the program will do when the user presses the OK button. Add the code from Listing 10.24
to your form.
After you complete the code, save the form (DSCNOTE.FRM) and project (DISCUSS.VBP) before you go to the next
section.
The Options form allows the user to select the folder for the discussion group, enter a MAPI profile, and turn the
message display from flat to threaded. Refer to Figure 10.6 and Table 10.5 for laying out the Options form.
It's a good idea to save this form (DSCOPTIONS.FRM) before you start coding.
There is not much code for the Options form. Listing 10.25 shows the code for the Form_Load event.
CollectFolders lstGroup
'
Me.Left = (Screen.Width - Me.Width) / 2
Me.Top = (Screen.Height - Me.Height) / 2
'
End Sub
The next code to add is for the cmdBtn_Click event. This routine will save the user's choices in the system registry
for later recall. Add the code from Listing 10.26 to your form.
'
' save to registry
SaveSetting App.EXEName, "Options", "Profile", Trim
(cProfile)
SaveSetting App.EXEName, "Options", "Group", Trim
(cGroup)
SaveSetting App.EXEName, "Options", "Threaded", IIf
(chkThreaded.Value = ➂1, 1, 0)
'
' update list & main form
OLEMAPIGetMsgs cGroup, frmMsgs.list1
FillOutline frmMsgs.list1, frmMsgs.Outline1
mdiDiscuss.Caption = "Discuss [" & cGroup & "]"
'
MousePointer = vbNormal
'
End Select
'
Unload Me
'
End Sub
'
lblSelected = Trim(lstGroup.List(lstGroup.ListIndex))
'
End Sub
Now save the form (DSCOPTIONS.FRM) and update the project. Only one more form to go!
The About dialog box contains basic information about the project. Refer to Figure 10.7 and Table 10.6 for laying out
the form.
You only need to add code to the Form_Load event and the cmdOK_click event. Listing 10.27 shows all the code
for the About form.
That's the last of the code for the Discuss project. Save this form (DSCABOUT.FRM) and update the project before you
begin testing.
Note
The discussion tool will not work in a Remote Mail setup. You need to have
direct access to at least one message store.
When you first start Discuss, you'll be asked to log into MAPI. One of the first things you should do is open the Options
page and select a target folder and set up your default user profile (see Figure 10.8).
You can select any folder as the discussion target. However, since the idea is to carry on discussions with other people,
you'll typically select a public folder as the discussion target.
Note
If you are working on a standalone machine, you can select any personal
folder for this demonstration. Just remember that no one but you can see the
results!
The program will list only messages that have their Type property set to IPM.Discuss. This means the first time you
select a target folder, you won't see any messages. So the next thing you need to do is add a message!
Press New Post to add a new thread to the folder (see Figure 10.9).
You can also highlight any message in the discussion and double-click it to read it. When you press the Reply button at
the bottom of a read form, it automatically turns into a reply form. Even the subject and message body are updated to
show that this is a reply (see Figure 10.10).
Summary
In this chapter you learned how to use the OLE Messaging library to create online, threaded discussion tools using MAPI
services. You learned that there are three ways in which discussion messages differ from mail messages:
You also learned how to build routines that collect all the message stores available to a workstation, all the folders in
each of those stores, and all the messages within a folder. You now know how to compose a message for Update
(rather than Send) and how to use the CoCreateGuid API to generate unique conversation index values used in
threaded discussion tools.
Best of all, most of the heart of this example program can be used to add discussion capabilities to other Visual Basic
projects.
Chapter 11
CONTENTS
● Summary
In this chapter you'll learn how to use the OLE Messaging library to create a stand-alone e-mail agent. This agent can
scan your incoming mail and, based on rules you establish, automatically handle messages for you. All actions are based
on rules you establish in a control file.
The first part of the chapter will discuss the concept of e-mail agents and cover the overall design of the program. The
next sections will detail the coding of the forms and support routines needed to complete the project. In the final section,
you'll install and test the MAPI Email Agent program.
Note
The Microsoft Exchange Server clients allow users to establish and code
their own mail agent (called the Inbox Assistant). If you have the Microsoft
Exchange Server client, now is a good time to review the Inbox
Assistant to get an idea of its features. The programming project
covered in this chapter works independently of the Inbox Assistant
and does not require that users have Microsoft Exchange Server installed on
their system.
When you complete the programming project in this chapter, you'll have a fully functional MAPI Email Agent that can
be installed on any workstation that has access to a MAPI-compliant e-mail system. Also, the techniques used to build
this project can be incorporated into other Windows programming projects to add message processing capabilities.
Before starting to code the project, it's a good idea to discuss the general features and functions of an e-mail agent. Once
you have a good idea of what e-mail agents can do, then you can lay out the basic design features of the MAPI Email
Agent programming project covered in this chapter.
Basically, an e-mail agent is a program that "acts for you." It is a program that reviews your messages and, based on
information you have supplied, processes the messages for you. Typically, e-mail agents process messages in the user's
inbox. Users can set up rules that tell the agent how to handle each new message. These rules can tell the e-mail agent to
check various parts of the incoming message and then take a specific action.
For example, the agent can be instructed to look for all messages that are received from your boss and then place those
messages in a special folder called "Urgent." Or the agent could be told that any message with the word "SALES" in the
subject line should be immediately forwarded to another user's inbox and then erased from your inbox without any
comment. You might also tell the agent to automatically reply to all senders that you are out on vacation and will return
next week.
For this chapter, you'll use Visual Basic 4.0 and the OLE Messaging library to build a stand-alone MAPI Email Agent
that has the following features:
● Timed scanning of your inbox-Users can start the program and allow it to run unattended. It scans the inbox for
new messages every N minutes. Users can also set configuration values that start the program as an icon on the
task bar or as a dialog box.
● Message notification-Users can establish a rule that causes a dialog box to pop up each time a specific message
is received. This notification can be based on sender ID, subject content, or level of importance.
● Automatic forwarding-Users can create rules that automatically forward messages to other addresses. Users can
also determine whether the original should be kept or discarded.
● Automatic replies-Users can create rules that generate automated replies to senders based on sender ID, subject
content, or level of importance.
● Automatic message copying or moving-Users can create rules that copy or move incoming messages to other
folders in the user's message store. This feature can be used to sort incoming message by sender ID, subject
content, or level of importance.
Note
The features described here are just the start of what can be accomplished
with an e-mail agent. The number of options has been limited to keep this
chapter focused on design and coding issues instead of content. As you
build your own agent, you can add many other capabilities.
To accomplish this, the MAPI Email Agent will keep track of rules created by the user. These rules will have three parts:
tests, comparisons, and actions. The test portion of the rule performs a simple scan of the designated portion of the
message, searching for requested content. The MAPI Email Agent described in this chapter is capable of inspecting three
message parts:
For example, the test SUBJECT MAPI tells the agent to check the message subject for the word "MAPI." The phrase
SENDER Boss tells the agent to check for messages sent to the user from the e-mail ID "boss."
All tests must use a logical condition as part of the processing. The MAPI Email Agent uses comparisons to do this. The
program can check for the following four logical conditions:
You'll notice that the last value is able to check the selected message part for the occurrence of a word or phrase. Note
that all the comparisons are case-insensitive. It is important to note that the LT and GT can be used with character data,
too.
The last of the three portions of a rule is the action. This is the part of the rule that tells the agent what action to take if
the test criteria have been met. The MAPI Email Agent can perform the following actions on a message:
The agent allows users to determine whether the forwarded and reply messages are retained or removed once the
forward/reply is generated.
The MAPI Email Agent allows users to build tests and actions, and then use them to create rules. All this information is
stored in a text file similar to an INI file. This file also contains general control information, such as the scan interval,
whether the agent should create a log file, the default log on profiles, and so on. Listing 11.1 shows a sample rule file.
Listing 11.1. Sample rule file for the MAPI Email Agent.
; ********************************************************
; MAPI Email Agent Control File
; ********************************************************
;
[General]
Editor=notepad.exe
ScanInterval=2
LogFile=mea.log
LogFlag=1
RuleCount=3
ActionCount=4
TestCount=4
Profile=MCA
DeleteForwardFlag=0
NotifyDialog=1
DeleteReplyFlag=0
MinimzeOnStart=0
AutoStart=0
LastUpdated=04/29/96 9:27:30 PM
[Actions]
Action0=MOVE MAPI
Action1=MOVE Urgent
Action2=FORWARD mamund@iac.net
Action3=COPY SavedMail
[Tests]
Test0=SENDER MCA
Test1=SENDER Boss
Test2=SUBJECT SAPI
Test3=SUBJECT MAPI
[Rules]
RuleName0=Boss's Mail
RuleTest0=SENDER Boss
RuleAction0=Move Urgent
RuleCompare0=EQ
RuleName1=Send To ISP
RuleTest1=SENDER MCA
RuleAction1=FORWARD mamund@iac.net
RuleCompare1=EQ
RuleName2=MAPI Mail
RuleTest2=SUBJECT MAPI
RuleAction2=MOVE MAPI
RuleCompare2=CI
The next sections show you how to code the MAPI Email Agent forms and support routines that will create and process
the rules described here.
You'll use Visual Basic 4.0 to create the three forms of the MAPI Email Agent. These forms will allow you to start and
stop message processing; add or delete new tests, actions, and rules; and modify default configuration settings for the
MAPI Email Agent.
The next three sections of this chapter outline the steps needed to layout and code these three forms. If you haven't done
so yet, start Visual Basic 4.0 now and create a new project.
The Main form of the MAPI Email Agent shows the current list of rules, tests, and actions. You can also launch the
message scan routine from this screen, access the setup dialog box, and inspect the MAPI Email Agent log file. Figure
11.1 shows an example of the Main form in run-time mode.
The Main form has a set of command button control arrays to handle the user selections. The first control array covers
the top row of buttons. These buttons handle the main processing steps:
● Start Timer starts the timer to count down to the next message scan.
● End Timer disables the timer.
● Setup calls the configuration dialog box.
● View Log displays the contents of the MAPI Email Agent log file.
● Refresh refreshes the list boxes from the control file.
● Exit ends the program.
The second command button control array handles the adding and deleting of tests, actions, and rules. To keep things
simple for this project, the system is capable only of adding or deleting rules. Existing rules cannot be edited and saved
again. Also, only basic input editing is performed by this program. In a production environment, this program should be
enhanced to add an improved user interface with additional input checking and recovery.
Table 11.1 contains a list of all the controls used on the MAPI Email Agent main form along with their property settings.
Use this table along with Figure 11.1 to build the MAPI Email Agent Main form.
Table 11.1. Controls for the MAPI Email Agent Main form.
Control Property Setting
VB.Form Name frmMEA
Caption "MAPI Email Agent"
ClientHeight 6585
ClientLeft 975
ClientTop 1575
ClientWidth 8175
Height 6990
Left 915
LinkTopic "Form1"
MaxButton 0 'False
ScaleHeight 6585
ScaleWidth 8175
Top 1230
Width 8295
VB.CommandButton Name Command1
Caption "E&xit Program"
Height 495
Index 5
Left 6780
TabIndex 18
Top 120
Width 1200
VB.CommandButton Name Command1
Caption "Re&fresh"
Height 495
Index 4
Left 5460
TabIndex 16
Top 120
Width 1200
VB.CommandButton Name Command2
Caption "Delete R&ule"
Height 495
Index 5
Left 6900
TabIndex 15
Top 3000
Width 1100
VB.CommandButton Name Command2
Caption "New &Rule"
Height 495
Index 4
Left 5700
TabIndex 14
Top 3000
Width 1100
VB.CommandButton Name Command1
Caption "View L&og"
Height 495
Index 3
Left 4140
TabIndex 13
Top 120
Width 1200
VB.CommandButton Name Command2
Caption "De&lete Action"
Height 495
Index 3
Left 6960
TabIndex 12
Top 5700
Width 1100
VB.CommandButton Name Command2
Caption "Ne&w Action"
Height 495
Index 2
Left 5760
TabIndex 11
Top 5700
Width 1100
VB.CommandButton Name Command1
Caption "SetU&p"
Height 495
Index 2
Left 2820
TabIndex 10
Top 120
Width 1200
VB.CommandButton Name Command1
Caption "E&nd Timer"
Height 495
Index 1
Left 1500
TabIndex 9
Top 120
Width 1200
VB.CommandButton Name Command2
Caption "&Delete Test"
Height 495
Index 1
Left 2820
TabIndex 8
Top 5700
Width 1100
VB.CommandButton Name Command2
Caption "&New Test"
Height 495
Index 0
Left 1620
TabIndex 7
Top 5700
Width 1100
VB.ListBox Name lstActions
Font
name="Courier"
charset=0
weight=400
size=9.75
underline=0 'False
italic=0 'False
strikethrough=0 'False
Height 1815
Left 4200
TabIndex 3
Top 3720
Width 3855
VB.ListBox Name lstTests
Font
name="Courier"
charset=0
weight=400
size=9.75
underline=0 'False
italic=0 'False
strikethrough=0 'False
Height 1815
Left 120
TabIndex 2
Top 3720
Width 3795
VB.ListBox Name lstRules
Font
name="Courier"
charset=0
weight=400
size=9.75
underline=0 'False
italic=0 'False
strikethrough=0 'False
Height 1815
Left 120
TabIndex 1
Top 1020
Width 7875
VB.CommandButton Name Command1
Caption "&Start Timer"
Height 495
Index 0
Left 180
TabIndex 0
Top 120
Width 1200
VB.Timer Name Timer1
Left 4860
Top 3000
VB.Label Name lblStatus
BorderStyle 1 'Fixed Single
Caption "Label5"
Height 255
Left 0
TabIndex 17
Top 6300
Width 8115
VB.Label Name Label3
AutoSize -1 'True
Caption "Actions:"
Font
name="Courier"
charset=0
weight=400
size=9.75
underline=0 'False
italic=0 'False
strikethrough=0 'False
Height 195
Left 4260
TabIndex 6
Top 3480
Width 960
VB.Label Name Label2
AutoSize -1 'True
Caption "Tests:"
Font
name="Courier"
charset=0
weight=400
size=9.75
underline=0 'False
italic=0 'False
strikethrough=0 'False
Height 195
Left 180
TabIndex 5
Top 3420
Width 720
VB.Label Name Label1
AutoSize -1 'True
Caption "Current Rules:"
Font
name="Courier"
charset=0
weight=400
size=9.75
underline=0 'False
italic=0 'False
strikethrough=0 'False
Height 195
Left 180
TabIndex 4
Top 780
Width 1680
Notice that the font size and type used for this form is slightly different. By using a fixed-width font for the text boxes, it
is very easy to get consistent alignment. This is a quick way to present a grid-like look to your list boxes. After
completing the form layout, save the form as MEA.FRM and save the project as MEA.VBP.
Next you need to add some code to the form. This code covers several key events for the form including the command
button selections. The first thing to do is add two form-level variables (see Listing 11.2).
Option Explicit
These variables are used to retain the original size and shape of the form.
Next add code to the Form_Load event. This code executes some one-time initializations and prepares the form for the
user (see Listing 11.3).
The next section of code goes in the Timer1_Timer event (see Listing 11.5).
This event fires off every minute (see Listing 11.3 where the interval was set to 60000). Each time the event fires, the
lCounter variable is decremented. When it hits zero, the StartProcess routine is called. This is the routine that
actually scans the messages. Notice also that the routine is designed to report timer progress on the form title bar. This
same information appears as part of the task bar when the form is minimized.
Now it's time to add the code behind the first set of command buttons. This first set of buttons calls all the main
operations of the program. Add the code in Listing 11.6 to the Command1_Click event.
Warning
Most of the code in Listings 11.6 and 11.7 refer to routines and forms that
have not been built yet. If you attempt to run the project before all your
routines are complete, you'll get error messages from Visual Basic. If you
want to test your code by running the project, you'll need to build
subroutines to cover the ones that are missing or comment out the lines that
call for other routines.
The last code you need to add to the MAPI Email Agent main form is the code for the Command2_Click event. This
code handles the calls for adding and deleting rules, actions, and tests. Add the code in Listing 11.7 to the
Command2_Click event.
That's all the coding for the MAPI Email Agent main form. Save this form (MEA.FRM) and this project (MEA.VBP)
before continuing.
The rule form is used to compose new rules for the MAPI Email Agent. This form is actually quite simple. It has three
list boxes that allow the user to select a test, a compare value, and an action. By combining these three items, the user
creates a valid MAPI e-mail agent rule. Once the rule is given a name, it can be saved. All saved rules are acted upon
each time MAPI Email Agent scans the incoming messages.
Note
This version of MAPI Email Agent does not let you turn rules on or off. If a
rule is in the database, it will be processed each time the messages are
scanned. The only way to tell the MAPI Email Agent to not process a rule is
to permanently remove the rule from the database. Toggling rules on and
off would make a nice enhancement for future versions of MAPI Email
Agent.
Refer to Figure 11.2 and Table 11.2 when laying out the new MAPI Email Agent Rule form.
Figure 11.2 : Laying out the MAPI Email Agent Rule form.
Table 11.2. Controls for the MAPI Email Agent Rule form.
Control Property Setting
VB.Form Name frmMEARule
BorderStyle 3 'Fixed Dialog
Caption "Create Rule"
ClientHeight 3990
ClientLeft 1140
ClientTop 1515
ClientWidth 6165
Height 4395
Left 1080
LinkTopic "Form1"
MaxButton 0 'False
MinButton 0 'False
ScaleHeight 3990
ScaleWidth 6165
ShowInTaskbar 0 'False
Top 1170
Width 6285
VB.TextBox Name Text1
Height 315
Left 1260
TabIndex 6
Text "Text1"
Top 120
Width 4755
VB.CommandButton Name Command1
Caption "&OK"
Height 495
Index 1
Left 4800
TabIndex 5
Top 3360
Width 1215
VB.CommandButton Name Command1
Caption "&Cancel"
Height 495
Index 0
Left 3420
TabIndex 4
Top 3360
Width 1215
VB.ListBox Name List3
Height 1815
Left 2820
TabIndex 2
Top 1380
Width 495
VB.ListBox Name List2
Height 1815
Left 3420
TabIndex 1
Top 1380
Width 2595
VB.ListBox Name List1
Height 1815
Left 120
TabIndex 0
Top 1380
Width 2595
VB.Label Name Label2
Caption "Rule Name:"
Height 315
Left 180
TabIndex 7
Top 120
Width 975
VB.Label Name Label1
BorderStyle 1 'Fixed Single
Caption "Label1"
Height 675
Left 120
TabIndex 3
Top 540
Width 5895
There are only a few events in the MAPI Email Agent Rule form that need coding. Listing 11.8 shows the code for the
Form_Load event.
Listing 11.8. Adding the code for the MAPI Email Agent Rule form Form_Load event.
The Form_Load event first fills the local list boxes, then initializes the other local controls, centers the form, and sets
the form's icon and caption.
The code in Listing 11.9 handles the user selections on the command button control array. Add this code to the
Command1_Click event.
Finally, you need to add code to the DblClick events of the three list boxes. By double-clicking each box, the user can
create a new rule to add to the database. Refer to Listing 11.10 for the code lines to add to each list box.
That is all you need to do for the MAPI Email Agent Rule form. Save this form as MEARULE.FRM and save the project
(MEA.VBP) before continuing.
The last form you need to add to the project is the MAPI Email Agent Setup form. This form allows users to modify the
default configuration settings for the MAPI Email Agent. Add a new form to your project called MEASETUP.FRM.
Refer to Figure 11.3 and Table 11.3 as you lay out the form.
Figure 11.3 : Laying out the MAPI Email Agent Setup form.
Table 11.3. Controls for the MAPI Email Agent Setup form.
Control Property Setting
VB.Form Name frmMEASetUp
BorderStyle 3 'Fixed Dialog
Caption "Form2"
ClientHeight 5940
ClientLeft 2175
ClientTop 1605
ClientWidth 4005
Height 6345
Left 2115
LinkTopic "Form2"
MaxButton 0 'False
MinButton 0 'False
ScaleHeight 5940
ScaleWidth 4005
ShowInTaskbar 0 'False
Top 1260
Width 4125
VB.CommandButton Name Command1
Caption "&OK"
Height 495
Index 1
Left 2640
TabIndex 23
Top 5280
Width 1215
VB.CommandButton Name Command1
Caption "&Cancel"
Height 495
Index 0
Left 1320
TabIndex 22
Top 5280
Width 1215
VB.CheckBox Name Check6
Alignment 1 'Right Justify
Caption "Log Activity to File:"
Height 495
Left 2040
TabIndex 13
Top 2940
Width 1800
VB.CheckBox Name Check5
Alignment 1 'Right Justify
Caption "Use PopUp Dialog on Notify:"
Height 495
Left 2040
TabIndex 12
Top 2400
Width 1800
VB.CheckBox Name Check4
Alignment 1 'Right Justify
Caption "Start Scan at Load:"
Height 495
Left 2040
TabIndex 11
Top 1860
Width 1800
VB.CheckBox Name Check3
Alignment 1 'Right Justify
Caption "Minimize On Startup"
Height 495
Left 120
TabIndex 10
Top 2940
Width 1800
VB.CheckBox Name Check2
Alignment 1 'Right Justify
Caption "Delete Replied Messages:"
Height 495
Left 120
TabIndex 9
Top 2400
Width 1800
VB.CheckBox Name Check1
Alignment 1 'Right Justify
Caption "Delete Forwarded Messages:"
Height 495
Left 120
TabIndex 8
Top 1860
Width 1800
VB.TextBox Name Text4
Height 315
Left 1440
TabIndex 7
Text "Text1"
Top 1380
Width 2400
VB.TextBox Name Text3
Height 315
Left 1440
TabIndex 6
Text "Text1"
Top 960
Width 2400
VB.TextBox Name Text2
Height 315
Left 1440
TabIndex 5
Text "Text1"
Top 540
Width 2400
VB.TextBox Name Text1
Height 315
Left 1440
TabIndex 1
Text "Text1"
Top 120
Width 2400
VB.Label Name Label12
BorderStyle 1 'Fixed Single
Height 300
Left 1440
TabIndex 21
Top 3600
Width 1200
VB.Label Name Label11
BorderStyle 1 'Fixed Single
Height 300
Left 1440
TabIndex 20
Top 4860
Width 2415
VB.Label Name Label10
BorderStyle 1 'Fixed Single
Height 300
Left 1440
TabIndex 19
Top 4440
Width 1200
VB.Label Name Label9
BorderStyle 1 'Fixed Single
Height 300
Left 1440
TabIndex 18
Top 4080
Width 1200
VB.Label Name Label5
BorderStyle 1 'Fixed Single
Caption "Rule Count:"
Height 300
Left 120
TabIndex 17
Top 3600
Width 1200
VB.Label Name Label8
BorderStyle 1 'Fixed Single
Caption "Last Updated:"
Height 300
Left 120
TabIndex 16
Top 4860
Width 1200
VB.Label Name Label7
BorderStyle 1 'Fixed Single
Caption "Action Count:"
Height 300
Left 120
TabIndex 15
Top 4440
Width 1200
VB.Label Name Label6
BorderStyle 1 'Fixed Single
Caption "Test Count:"
Height 300
Left 120
TabIndex 14
Top 4020
Width 1200
VB.Label Name Label4
BorderStyle 1 'Fixed Single
Caption "Profile:"
Height 300
Left 120
TabIndex 4
Top 1380
Width 1200
VB.Label Name Label3
BorderStyle 1 'Fixed Single
Caption "Log File:"
Height 300
Left 120
TabIndex 3
Top 960
Width 1200
VB.Label Name Label2
AutoSize -1 'True
BorderStyle 1 'Fixed Single
Caption "Scan Interval:"
Height 300
Left 120
TabIndex 2
Top 540
Width 1200
VB.Label Name Label1
AutoSize -1 'True
BorderStyle 1 'Fixed Single
Caption "Editor:"
Height 300
Left 120
TabIndex 0
Top 120
Width 1200
The MAPI Email Agent Setup form needs code for two events and two custom routines. The Custom routines are needed
to load the forms controls with the configuration values for editing and then to save them after the values have been
modified.
Add a new subroutine called SetupPageLoad to the MAPI Email Agent Setup form and enter the code shown in
Listing 11.11.
Now add another new subroutine called SetupPageSave and enter the code shown in Listing 11.12.
Only two events need coding-the Form_Load event and the Command1_Click event. Listing 11.13 shows the code
for the Form_Load event.
Listing 11.13. Adding code to the MAPI Email Agent Setup Form_Load event.
Warning
In the second-to-last line, this code refers to an icon in the local folder. The
icon can be found on the CD-ROM that ships with this book, and is copied
to your machine when you install the source code from the CD-ROM. If
you get an error message on this line, you may need to locate the icon or use
another icon for your project.
The last event you need to code for the form is the Command1_Click event. This event handles the saving (or
canceling) of a new rule. Add the code shown in Listing 11.14 to the Command1_Click event.
That is the end of the coding for the MAPI Email Agent Setup form. Save this form as MEASETUP.FRM and save the
project (MEA.VBP) before you continue to the next section.
In the next section, you'll add the support routines that are needed to make the MAPI Email Agent really work.
The real heart of the MAPI Email Agent program is the support routines. There are three main sets of routines in the
program:
● Initialization routines
● List-handling routines
● Message-processing routines
The next three sections of this chapter walk you through the process of building the support routines for the MAPI Email
Agent program.
All the code for the support routines will be added to a BAS module. Before going on to the next sections, add a BAS
module called LIBMEA.BAS to the MAPI Email Agent project.
The initialization routines declare the global variables and set them to their initial values. There are also routines to
handle the reading and writing of configuration values and the storing and retrieving of the test, action, and rule records.
Note
All of the control information is kept in a single ASCII text file (MEA.RUL)
in the same folder as the program. This format was chosen for simplicity. In
a production environment, you will want to consider a more sophisticated
storage system including database formats.
Since we will be using an ASCII control file similar to the 16-bit INI files, we need to declare two API calls to handle
the reading and writing of those values. Listing 11.15 shows the two API calls needed for this project. Add this code to
the general declarations section of the BAS module.
Option Explicit
'
' APIs for read/write of shared INI settings
Declare Function GetPrivateProfileString Lib "kernel32" Alias
"GetPrivateProfileStringA" (ByVal lpApplicationName As String, ByVal
lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString
As String, ByVal nSize As Long, ByVal lpFileName As String) As Long
Declare Function WritePrivateProfileString Lib "kernel32" Alias
"WritePrivateProfileStringA" (ByVal lpApplicationName As String,
ByVal lpKeyName As Any, ByVal lpString As Any, ByVal lpFileName As
String) As Long
The next code to add declares the global variables. Listing 11.16 shows all the declarations needed for the project. Add
these values to the general declarations section of the module.
Next add the Main subroutine to the project. This Visual Basic project launches from a Main() subroutine instead of a
form. The Main routine first calls a routine that performs the initialization routines, then calls the main form. Once the
form is closed by the user, a short cleanup routine is called. Add the code in Listing 11.17 to the project.
Now add the InitStuff subroutine to the project and enter the code in Listing 11.18.
The routine in Listing 11.18 calls four other routines. The first one is LoadStrings. This routine initializes the local
variables at startup. Add the LoadStrings subroutine and enter the code in Listing 11.19.
Tip
Most of the strings in the LoadString routine are the names of control
file keys ("Editor," "ScanInterval," "LogFile," and so on). By storing the
names of the keys in this way, you can easily localize the project for other
languages-because the MAPI Email Agent project checks only variable
names, you can change the values in this section to match other languages
without having to re-code most of the program.
The next few routines all deal with data transfers to and from the ASCII control file. Listing 11.20 shows two new
routines-LoadValues and SaveValues. Add these routines to your module and enter the code shown in Listing
11.20.
You can see that both routines call the same subroutines using different parameters for the save and load events. Now
add the INIGeneral subroutine to your module and enter the code shown in Listing 11.21.
You'll notice that the INIRegSetting function called in each line of Listing 11.21 is very similar to the
SaveSetting/GetSetting functions built into Visual Basic 4.0. However, unlike the built-in Visual Basic
functions, this one routine can be used to both read and write values. Also, this custom version always writes to a disk
file. The Visual Basic 4.0 SaveSetting function saves values to the Registry under Windows NT and Windows 95.
Add the INIRegSetting function to your project and enter the code shown in Listing 11.22.
The INIRegSetting routine makes one last call down to a pair of custom routines. These custom routines
(LocalGetSetting and LocalSaveSetting) are the wrapper functions for the API declarations you added at the
start of this module. Add the two new functions (LocalGetSetting and LocalSaveSetting) and enter the code
shown in Listing 11.23.
Next you need to add the routines to load the rules, actions, and tests from the control file into memory variables. Listing
11.24 contains the code for the INIRules routine.
Listing 11.25 shows the code for the INITests and INIActions routines. Add these to your project.
The last routines needed for the initialization section are the ones that start and end your MAPI sessions (you remember
MAPI, right?), and the routine that handles program exit cleanup.
First add the CloseDown cleanup routine to your project and enter the code shown in Listing 11.26.
Now add the StartMAPI and MAPIEnd routines and enter the code from Listing 11.27.
That is the end of the support routines for initialization. Save this module (MEA.BAS) and project (MEA.VBP) before
continuing to the next section.
The next set of routines handle the addition and deletion of records from the rules, tests, and actions lists. There are also
two routines that handle the populating of the list controls on the MAPI Email Agent forms.
First, add a new subroutine called LoadLists to the module and enter the code shown in Listing 11.28. This routine
simply calls the low-level function that actually fills the list box control.
Next, add the low-level routine that is called by LoadLists. Add the subroutine FillList to the project and enter
the code shown in Listing 11.29.
Notice the use of column-like spacing for the rules list. By computing length and setting spacing evenly, you can give
the effect of a grid format while still using a list box control.
Tip
This technique works well only if you set your list box control font type to a
fixed-width font such as Courier or System.
The next two routines deal with the addition and deletion of tests. First, add the new routine AddTest to the project and
enter the code shown in Listing 11.30.
This routine works by adding an item to the Test() array. First, the routine attempts to find an open slot in the existing
list. If none is found, the routine will expand the list and add the new item at the bottom of the list. As a final step,
AddTest saves the new data to the control file and then reloads the control data to refresh the local variables.
The DeleteTest function is quite simple. It just removes the selected item from the array by blanking it out. This is
simple, but not the most desirable. When you run the program you'll see that each deletion leaves a hole in the list. As
new items are added, these holes are filled. The holes do not adversely affect processing, but they are a bit unsightly in
the list controls. In a production application, more time can be spent on the user interface. For now, just keep in mind
you have a routine that works-you can add the bells and whistles later.
Add the DeleteTest subroutine to your project and enter the code in Listing 11.31.
The AddAction and DeleteAction routines are almost identical to the AddTest and DeleteTest routines. Add
these two new subroutines to your project and enter the code from Listings 11.32 and 11.33.
Listing 11.32. Adding the AddAction routine.
● AddRule
● MakeRule
● DeleteRule
First, add the new AddRule subroutine and enter the code shown in Listing 11.34.
Now add the MakeRule subroutine to your project. This is the routine that actually saves the results of data entry on the
MAPI Email Agent Rule form. Enter the code from Listing 11.35.
The last list-handling routine you need to add is the DeleteRule subroutine. After adding the routine, enter the code
you see in Listing 11.36.
Listing 11.36. Adding the DeleteRule routine.
There is one more support routine needed before you are done with this section. You need a routine to write out log
messages when requested. Add a new subroutine called LogWrite to your project and enter the code shown in Listing
11.37. This routine writes lines to a text file along with the date and time the line was written. This routine also sends the
same message to the status line of the MAPI Email Agent main form.
Now save the module (MEA.BAS) and the project (MEA.VBP) before doing any test runs. After you are satisfied the
routines are working properly, you can go on to the next section for the last bit of coding-the message processing
routines.
This last set of routines is where the MAPI services are finally used. The goal of the message processing routines is to
inspect each message in the user's inbox and check the messages against the rules that have been established for the
MAPI Email Agent.
The top-level routines are StartProcess and ScanMsgs. The StartProcess routine is called by the
Timer1_Timer event or by pressing the Start button on the main form. StartProcess checks to see if there are
any messages in the user's inbox. If there are, then the ScanMsg routine is called to process each message.
If you already added the StartProcess routine, locate it now. Otherwise, add the new routine and enter the code
shown in Listing 11.38.
The StartProcess routine attempts to create a message collection object based on the Session inbox. If that is
successful, the ScanMsgs routine can be called. Now add the ScanMsgs subroutine and enter the code in Listing
11.39.
Listing 11.39. Adding the ScanMsgs routine.
The ScanMsgs routine selects each message in the collection and submits it to the CheckRule routine for processing.
Now add the CheckRule subroutine and type in the code shown in Listing 11.40.
Several things are going on in this routine. First, the first "word" on the line is removed using the ParseWord()
function. This word is then used to determine the type of test to perform on the message. The appropriate check
subroutine is called (CheckSender, CheckSubject, CheckPriority) and, if the return is positive, the
DoAction routine is called to handle the action portion of the rule.
Now add the ParseWord function and enter the code shown in Listing 11.41.
The ParseWord() function accepts a string and returns the first full word found in the string. For example
ParseWord("SENDER Smith") would return SENDER. This is used to pull the command portion of a test or action
record.
The CheckRule routine you entered earlier (in Listing 11.40) uses ParseWord() to get the message portion
command of a rule (SENDER, SUBJECT, PRIORITY). This value is then used to call the three message-part-specific
check routines (CheckSender, CheckSubject, and CheckPriority). You need to add these routines next.
First add the CheckSender function, and enter the code shown in Listing 11.42.
The CheckSubject and CheckPriority functions work the same way. The only difference is that the
CheckPriority function does not test for CI ("is contained in"). Add the CheckSubject function and enter the
code from Listing 11.43.
If the Checknnn routine returns TRUE, an action must take place. The DoAction routine is used to execute the
appropriate e-mail action. DoAction accepts the index to the rule as its only parameter. Like the CheckRule routine,
DoAction uses a SELECT CASE structure to act on each command word (NOTIFY, COPY, MOVE, FORWARD, and
REPLY).
Add the DoAction subroutine to the project and enter the code shown in Listing 11.45.
There are only three routines used to act on all five commands. This is because the FORWARD and REPLY commands act
on messages, and the COPY and MOVE commands act on folders. Only one routine is needed for each (with slight
behavior changes within each routine).
The NOTIFY option is the easiest to handle. All that is needed is a pop-up dialog box when the message arrives. Add the
MsgNotify routine and type in the code from Listing 11.46.
The routine needed for forwarding and replying to messages involves making a new message (with the contents of the
original) and sending it to a new address. Since this is actually a messaging operation, you'll use the .Send function to
force the message into the transport for delivery. Add the MsgFwdReply subroutine and enter the code in Listing
11.47.
There are a few things to keep in mind about this routine. First, a good REPLY routine should allow users to attach, or
append, a text message to the original. Here, that code is left out for brevity. You'll also notice that only one recipient is
added to the note. In some cases, it is possible that more than one person should receive the forwarded message. This can
be handled by using distribution lists. Finally, there is no code here to handle any attachments to the original note. This
should be added in a production environment.
The other action to be handled by the MAPI Email Agent is copying or moving messages to other folders. This is
accomplished using the .Update method. Moving messages is similar to posting them. For this reason you do not want
to attempt to "send" the message. Add the MsgMoveCopy subroutine and enter the code shown in Listing 11.48.
Warning
Since traversing the folder tree can be time-consuming, you might be
tempted to build a tree at startup and use that throughout your program. Be
careful! Since the Microsoft Exchange message stores can be shared among
users, the tree can change rather rapidly. Not only could you encounter new
folders or discover that old folders have been deleted, it is also possible that
your target folder has been moved. It is a good idea to traverse the folder
tree each time you attempt to find a folder.
Notice that the process of moving a message really involves creating a copy in the new folder. The code here copies the
most commonly used items, but does not copy any attachments. Keep in mind that some properties may not exist for
some messages. You'll need error trapping to prevent program crashes.
The last routine you need for the MAPI Email Agent application is the FindFolder function. This routine accepts a
folder name and returns its unique MAPI ID value. Add the FindFolder function and enter the code shown in Listing
11.49.
You'll notice that the MAPI Email Agent inspects only the top-level folder collection of the Session.InBox. This is
done to keep the code brief. If you wanted to search all the folders, you'd need to start at the Session.RootFolder
and, after collecting the folders, inspect each of them for the existence of folders, and so on and so forth. For now, the
MAPI Email Agent program only works with folders in the inbox. You can add code to expand the search capabilities of
the Findfolder routine in future MAPI projects.
Save this module (MEA.BAS) and the project (MEA.VBP) before you start testing the MAPI Email Agent.
After you have completed the MAPI Email Agent project, you can install it on any workstation that has access to MAPI
services. You do not have to have Microsoft Exchange or Microsoft Mail clients running in order to use the MAPI Email
Agent.
In order to set up MAPI Email Agent to process your incoming mail you need to build test, action, and rule records. You
also need to use your Microsoft Exchange or Microsoft Mail client to build any new target folders you want to include as
parts of COPY or MOVE actions.
Once your folders are built and your rules are entered, you can launch the MAPI Email Agent at startup each day and let
the program do its thing!
The next few sections talk you through a simple example of running the MAPI Email Agent on your workstation.
First, start up your MAPI e-mail client and make sure you have the following folders within your inbox:
● Urgent
● MAPI
● Sales
After adding these folders to your inbox, it should look something like the one in Figure 11.4.
You'll use these folders as destinations when you add rules to your MAPI Email Agent database.
Next you need to add test, action, and rule records to the MAPI Email Agent database. Start the MAPI Email Agent
program and enter the tests and actions shown in Table 11.4.
Table 11.4. MAPI Email Agent tests and actions.
Record Type Contents
Test SENDER Boss
SUBJECT MAPI
SUBJECT SALES
PRIORITY 0
SENDER Assistant
Action FORWARD Boss
MOVE Urgent
COPY MAPI
REPLY Sales
NOTIFY Me
Figure 11.5 shows how the MAPI Email Agent looks after the tests and actions have been entered.
Now you're ready to create some rules for the MAPI Email Agent. Enter the rules shown in Table 11.5.
Figure 11.6 shows what your screen should look like as you build your MAPI Email Agent rules.
You can easily test the MAPI Email Agent by sending yourself some messages that meet the entered criteria. Send
yourself a note that has MAPI in the subject or send a note that is marked high priority. Once the messages are sent, click
the Start Timer button on the MAPI Email Agent to scan your inbox. You'll see messages along the status bar as the
program scans; when the program finishes, check the log file by clicking the View Log button.
Summary
In this chapter you learned how to use the OLE Messaging library to create a stand-alone
e-mail agent. This agent can scan your incoming mail and, based on rules you establish, automatically handle messages
for you. All actions are based on rules you establish in a control file.
You also learned that the process of sending messages is handled differently than posting messages (.Send versus .
Update), and you learned how to traverse the folder tree to locate a desired MAPI storage folder.
Chapter 12
CONTENTS
❍ Property Extensions
❍ Registering Extensions
● Summary
Up to this point, you've learned how to create standalone MAPI applications and how to use the Exchange Forms
Designer to create MAPI-enabled forms that run under the Windows Messaging client interface. In this chapter, you'll
learn how to use C++ to create direct extensions to the Microsoft Exchange interface. You'll see how easy it is to add
new property pages to the Microsoft Exchange menus. You'll also see how you can build programs that execute at
selected times throughout the life of a Microsoft Exchange session.
In the first part of the chapter, you'll get a review of the Microsoft Exchange extension interface. You'll learn about the
various extension types and how you can use context mapping to determine when your client extension is called by
Microsoft Exchange.
In the second part of the chapter, you'll use C++ to create a program that adds a property sheet and several message event
extensions to the Windows Messaging client. This program will act as a message checksum verifier. You'll be able to
turn this new message extension on and off by selecting Tools | Options from the main Microsoft Exchange menu
to find your extension property page.
When you're finished with this chapter, you'll understand the theory behind Windows Messaging client extensions and
have experience creating your own working extension.
Note
The project in this chapter was built using Microsoft Visual C++ 4.1. You
may need to make slight changes to the code if you plan to compile it using
another version of C++. If you do not have a C++ compiler, you can still get
a lot out of this chapter. The complete source code and a compiled version
of the project can be found on the CD-ROM. You can review the source
code as you read the chapter and install the compiled extension as instructed.
Before we jump into the task of creating a Windows Messaging client extension, it's worthwhile spending some time
reviewing the theory behind Windows Messaging client extensions and how they can be used to create programs that
work within the Windows Messaging client environment.
In the next section, you'll build a working Windows Messaging client extension using VC++.
The heart of the Windows Messaging client extension model is to expose all major process events of the Windows
Messaging client to make them available for other programs to monitor and, when appropriate, modify or replace
standard Windows Messaging client actions. For example, you can write an extension that scans the incoming message
for selected words and then immediately moves that message to another folder or even forwards the message directly to
another user. You can write extensions that automatically add additional text at the bottom of the message body when the
user saves the message or submits it for delivery. You can even write extension code that executes when a user attempts
to resolve recipient names to the installed address books. You can also write an extension that will be executed when a
user accesses an attachment (for example, to check for viruses).
There are additional extension sets that allow you to add property pages to the Microsoft Exchange main menu (Property
extensions); extension sets to add entirely new menu commands to Microsoft Exchange (Command extensions); new
features for message searching (Advanced Criteria extensions) and others.
To accomplish all this, Microsoft has created a large set of API calls and callback functions that can be used to create
Dynamic Link Libraries (DLLs) that intercept Windows Messaging client messages and then, when appropriate, jump in
and perform any desired task before returning control to the Windows Messaging client. In effect, your extension
program becomes part of the Windows Messaging client (see Figure 12.1).
Figure 12.1 : Windows Messaging client extensions become part of the Windows Messaging client.
Microsoft Exchange is designed to allow several extension DLLs to be loaded at one time. In fact, many of the Windows
Messaging client features that appear on the menu are simple extension DLLs designed and shipped by Microsoft. For
example, if you have installed the Internet Mail features of Microsoft Exchange, you have really installed an extension
DLL. There are several Microsoft Exchange extension DLLs currently available, and many more will be available in the
future.
There are some real advantages to creating Windows Messaging client extensions instead of creating standalone MAPI
applications. First, if you want to add just one or two features to the client interface, it's a lot easier to code those few
additional features instead of doing the work to code a complete MAPI client to replace the Windows Messaging client.
Second, by using client extensions, you can customize the Microsoft Exchange interface to meet your needs without
losing the power of Microsoft Exchange.
An added benefit is that not only can you install your own special Microsoft Exchange features, you can also take
advantage of other extension DLLs by other vendors. And, because the interface is well defined and open to all
programmers, it is not too likely that you'll design an extension that is incompatible with other DLLs you'll install later.
Finally, by using the DLL extension to add features to Microsoft Exchange, users can use a familiar interface while
taking advantage of the unique add-ins you've developed. Also, Microsoft has pledged to make sure that any extensions
built for Microsoft Exchange will run under all versions of the Windows Messaging client. This means you can write
routines that can potentially be installed on NT, Windows 95, Windows for Workgroups, and Windows 3.1 Windows
Messaging client software.
In order to allow DLLs to monitor Windows Messaging client processes, a set of context messages has been developed
to inform any DLLs of the current Windows Messaging client process in progress. These context messages make up
what is called the Context Map. This map lists all the general process events that can be monitored by an installed
extension DLL. Table 12.1 shows the list of contexts along with a short description.
As you can see from Table 12.1, there are quite a few different context messages that you can use to monitor Windows
Messaging client processing. Also, more than one of these contexts can be active at the same time. For example, the
TASK context is always active when the Windows Messaging client is loaded. At the same time, as soon as the user logs
onto a MAPI session, the SESSION context is active, too. As users perform finds, read messages, post notes, and so on,
each of the contexts becomes active. And as each process ends, the context becomes inactive. This can be a bit confusing
at first glance. However, you needn't worry about knowing what contexts are active at any given moment. You'll write
your extension to become active when a certain context becomes active. When that happens, your code executes.
Along with the Microsoft Exchange contexts, Microsoft has developed a set of Component Object Model (COM)
interfaces to react to the various events associated with a Windows Messaging client process. Most of the Microsoft
Exchange COM interfaces relate directly to a Microsoft Exchange context. Table 12.2 shows a list of the Microsoft
Exchange COM interfaces and gives short descriptions of them.
Most of the COM interfaces are self-explanatory. However, a few deserve some extra attention. The IExchExt:
IUnknown interface is used mostly by the IExchExtCallBack interface. Although it is possible to build complete
context interfaces using IExchExt:IUnknown, it's not very practical-especially when you have all the other COM
objects to work with.
Also, the IExchExtUserEvents interface returns general information about user actions in the Windows Messaging
client. Whenever the focus changes from one Microsoft Exchange object to another (that is, the selected folder or
message), you can have your application execute some code.
Finally, there are two other interfaces not listed in Table 12.2. The IExchExtModeless:IUnknown and
IExchExtModelessCallBack interfaces can be used to develop non-modal processes that run alongside the
Windows Messaging client and intercept messages normally addressed to the client.
Note
Creating modeless add-ins is beyond the scope of this book. If you're
interested in creating a modeless extension for Microsoft Exchange, you can
find additional information in the Microsoft MAPI SDK documentation.
In order to write your program to become active for the various contexts, you need to use one (or more) of the
Component Object Model interfaces that match up to the Microsoft Exchange contexts. In other words, you "map" your
program code to contexts using the COM interfaces. When you register your program with one or more of the COM
interfaces, your program receives alerts from Microsoft Exchange that fire off methods within the COM interface. You
can place code within these methods to make sure that your program performs the desired actions at the right time.
For example, if you want your program to execute whenever the SESSION context occurs, you'll need to use the
IExchExtSession COM interface object in your program. Some of the contexts can be accessed using more than
one COM interface. For example, The IExchExtMessage COM interface is called from several of the Microsoft
Exchange contexts (SENDNOTEMESSAGE, READNOTEMESSAGE, SENDPOSTMESSAGE, READPOSTMESSAGE,
READREPORTMESSAGE, and SENDRESENDMESSAGE). To make it a bit more complex, a single context can alert more
than one COM interface. For example, the ADDRBOOK context notifies the IExchExtCommands,
IExchExtUserEvents, and IExchExtPropertySheets interfaces.
Table 12.3 shows the Microsoft Exchange contexts along with the COM interfaces that can be used to monitor those
contexts.
Table 12.3. Mapping Microsoft Exchange contexts to Microsoft Exchange COM interfaces.
Context Name Interfaces Called
None. Only IexchExt::Install and
TASK IUnknown::Release are called from this
context.
SESSION IExchExtSessionEvents
VIEWER, REMOTEVIEWER, IExchExtCommands, IExchExtUserEvents,
SEARchVIEWER IExchExtPropertySheets
IExchExtCommands, IExchExtUserEvents,
ADDRBOOK
IExchExtPropertySheets
SENDNOTEMESSAGE, IExchExtCommands, IExchExtUserEvents,
READNOTEMESSAGE, IExchExtMessageEvents,
READREPORTMESSAGE, IExchExtAttachedFileEvents,
SENDRESENDMESSAGE,
READPOSTMESSAGE, IExchExtPropertySheets
SENDPOSTMESSAGE
PROPERTYSHEETS IExchExtPropertySheets
ADVAncEDCRITERIA IExchExtAdvancedCriteria
Now that you have a good idea of how the Windows Messaging client extensions and the Windows Messaging client
contexts work together, it's time to focus on two of the COM interfaces that you'll use in the Message Signing project for
this chapter:
These message event extensions can be used to monitor the Windows Messaging client actions on incoming and
outgoing messages. When your application registers the IExchExtMessageEvents interface, you can receive
notifications whenever the user attempts to read, write, address, or submit a MAPI message. Table 12.4 shows the
methods associated with the IExchExtMessageEvents interface along with a short description.
You should notice that all eight of the message event methods can be sorted into four groups of two:
● OnCheckNames/OnCheckNamesComplete
● OnRead/OnReadComplete
● OnSubmit/OnSubmitComplete
● OnWrite/OnWriteComplete
The first method of the pair (for example, OnWrite) is called when the user attempts the suggested operation within the
Windows Messaging client. The second method of the pair (for example, OnWriteComplete) is executed
immediately after the first method. This second event can be used to check results from code execution under the first
method, and to perform any error recovery or cleanup that might be needed.
For example, if you decide to add an extension that encrypts messages upon submission for delivery, you could write
code in the OnSubmit method of the Message Event interface that would copy the message body to a temporary
file and convert it into its encrypted form. You might then write code in the OnSubmitComplete method that would
check the results of the encryption to make sure all went well. If not, you could then inform the user of the problem and
restore the original text. If all went well, the OnSubmitComplete method could copy the encrypted message over the
original and then erase the temporary file.
In the example you'll build later in this chapter, you'll use the OnWrite and OnRead method pairs to create and check
message checksums before saving and reading MAPI messages.
Property Extensions
The Windows Messaging client also has an extension for managing the creation of custom property pages. You can
create a simple dialog box and then register it as one of the tabbed property pages of a Windows Messaging client object.
The Windows Messaging client displays property pages for four different objects. Table 12.5 shows the four objects
along with short descriptions of them.
Table 12.5. The Microsoft Exchange objects that display property pages.
Property Page Object Description
This shows the properties of the selected MAPI
Information Store Properties
Information Store object.
This shows the properties of the selected MAPI Folder
Folder Properties
object.
This shows the properties of the selected MAPI Message
Message Properties
object.
This shows the properties page from the Tools |
Tools Options Menu Options menu selection of the Windows Messaging
client.
There are three methods to the IExchExtPropertySheets COM interface. These methods allow you to determine
the number of pages that are currently registered for display, allow you to define and add a new property page, and allow
you to release the property page you added to the object. Table 12.6 describes the three methods.
In the project shown later in this chapter, you'll use these three methods to add a property page to the Tools |
Options menu of the Windows Messaging client.
Note
The other COM interfaces mentioned here also have associated methods.
These methods are not covered in this chapter. This was done to focus on
the creation of a complete Windows Messaging client extension application.
You can learn more about all the Microsoft Exchange COM interfaces and
their methods by reviewing the MAPI SDK documentation.
Registering Extensions
Once you have successfully created a Windows Messaging client extension, you must inform the Windows Messaging
client that the extension exists before it will be able to use it. This is done by making entries in the system registry
database. All Windows Messaging client extensions must be registered. On 32-bit Windows systems, the client extension
entries are placed in the HKEY_LOCAL_MAchINE\Software\Microsoft\Exchange\Client\Extensions
section of the registry. On 16-bit systems, the registration is stored in the [Extensions] section of the EXchNG.INI file.
Figure 12.2 shows the system registry editor open to the [Extensions] section.
Note
In network environments, Windows Messaging client extensions can be
shared among several clients. Shared Microsoft Exchange DLLs are
registered in the SHARED32.INI file on 32-bit systems and in the
SHARED.INI file on 16-bit systems.
The registry entry tells the Windows Messaging client several important things about the extension DLL. The syntax of
an extension entry is as follows:
Tag=Version;<ExtsDir>DllName;[Ordinal];[ContextMap];[InterfaceMap];[Provider]
Table 12.7 shows each of the parts of the registration entry and explains its meaning and use.
The ContextMap and InterfaceMap entries need some clarification. By placing "1" or "0" values in these strings,
you are telling Microsoft Exchange which context events and COM interfaces you want your client to participate in.
Table 12.8 shows the meaning of each position in the ContextMap parameter.
For example, if your DLL extension should execute each time one of the message events occurred, you would create the
following ContextMap parameter:
"00000111111000"
In addition to the ContextMap parameter, there is a parameter to inform Microsoft Exchange which COM interfaces
your extension will use. Table 12.9 shows the list of COM interfaces and their positions in the InterfaceMap
parameter.
For example, if your DLL extension used the property sheet and message interfaces, you'd create an InterfaceMap
parameter that looks like the following:
"0001010"
Later in this chapter, you'll make a registry entry to match the Message Signing Extension example described in this
chapter.
Warning
It is acceptable to leave out trailing bits in the interface and context map
parameters and allow Microsoft Exchange to assume these missing entries
are zero ("0"). However, it is not recommended. It is much better to
construct a complete string, even if most of them are set to zero.
Now that you know the theory behind creating Windows Messaging client extensions and how to register them for use,
you're ready to build the sample project.
The sample Windows Messaging client extension described in this chapter creates a checksum of each message body
and stores that value before it is sent to the recipient. The same extension also checks the stored value against the
computed checksum each time the user attempts to read a message. If, upon reading the message, the stored checksum is
not equal to the computed checksum, it is likely that the message has been altered in some way since it was submitted for
delivery by the original author. In this way, you can implement a rather simple message signature process that will give a
high degree of confidence that the messages users receive have not been tampered with before they arrive at their
destination.
There are four main steps to creating the Message Signing extension DLL:
● Building the initial header file-This file will contain global declarations along with prototypes for the Microsoft
Exchange class objects and their associated methods.
● Coding the main DLL routines-These are the actual routines that register the DLL for callbacks to the COM
interfaces and establish code behavior for the necessary COM interface methods.
● Laying out the Property Sheet dialog box-This dialog box will be registered and accessed from the Tools |
Options menu of the Windows Messaging client.
● Coding the Property Sheet dialog box events-This is the code to handle user interaction with the registered
property page.
Note
If you own a copy of Microsoft Visual C++, you can load the CDGEXT32
project found on the CD-ROM that ships with this book. If you do not have
a copy of C++, you can still load the source code files and review them as
you read along in this chapter.
The initial header file (CDGEXT32.H) contains basic defines, includes, and global declarations. It also contains the
prototypes for the Microsoft Exchange class objects and their associated methods. Listing 12.1 shows the first part of the
CDGEXT32.H file.
// ========================================================================
// CDGEXT32.H
// ========================================================================
#ifndef __CDGEXT32_H__
#define __CDGEXT32_H__
//
// include files
//
#include <WINDOWS.H>
#include <COMMCTRL.H>
#include <MAPIX.H>
#include <MAPIUTIL.H>
#include <MAPIFORM.H>
#include <EXchEXT.H>
#include "RESOURCE.H"
//
// function prototypes
//
extern "C"
{
LPEXchEXT CALLBACK ExchEntryPoint(void);
}
// global declarations
extern BOOL bSignatureOn;
//
// class declarations
//
class CDGExt;
class CDGExtPropSheets;
class CDGExtMsgEvents;
The code in Listing 12.1 first lists all the include files needed for the project (these are part of the MAPI SDK that can be
found on MSDN Professional Level CD-ROMs and above). Next is the declaration of the initial entry point for the
Microsoft Exchange DLL extension, along with three custom functions used in the project, along with a single variable
declaration. Finally, the three COM objects that will be used in the project are declared.
Listing 12.2 shows the CDGEXT32.H code that defines the methods and properties of the high-level IExchExt
interface object. This object will be used to install the extension and register the DLL for property sheet and message
events.
//
// overall exchange extension class
//
class CDGExt : public IExchExt
{
public:
CDGExt();
STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj);
inline STDMETHODIMP_(ULONG) AddRef() { ++m_cRef; return
m_cRef; };
STDMETHODIMP_(ULONG) Release();
STDMETHODIMP Install (LPEXchEXTCALLBACK pmecb, ULONG mecontext,
ULONG ulFlags);
private:
ULONG m_cRef;
UINT m_context;
CDGExtPropSheets * m_pExchExtPropertySheets;
CDGExtMsgEvents * m_pExchExtMessageEvents;
};
Next, Listing 12.3 shows the definition of the Property Sheets object.
//
// property sheet extension class
//
class CDGExtPropSheets : public IExchExtPropertySheets
{
public:
CDGExtPropSheets (LPUNKNOWN pParentInterface) {
m_pExchExt = pParentInterface;
m_cRef = 0;
};
private:
ULONG m_cRef;
LPUNKNOWN m_pExchExt;
};
Finally, Listing 12.4 shows the code that defines the Message Event COM interface.
//
// message event extension class
//
class CDGExtMsgEvents : public IExchExtMessageEvents
{
public:
CDGExtMsgEvents (LPUNKNOWN pParentInterface) {
m_pExchExt = pParentInterface;
m_cRef = 0;
m_bInSubmitState = FALSE;
};
private:
ULONG m_cRef;
HRESULT m_hrOnReadComplete;
BOOL m_bInSubmitState;
LPUNKNOWN m_pExchExt;
};
This is all there is to the CDGEXT32.H header file. Next, you'll review the code for the main extension DLL.
The CDGEXT32.CPP file contains the main body of code for the extension. The code file can be divided into five parts:
Listing 12.5 shows the initialization and main entry code for CDGEXT32.CPP. This code handles some basic
housekeeping and then establishes the DLL entry point (DLLMain) and the callback address for the IExchExt object
(ExchEntryPoint).
Listing 12.5. The initialization and main entry code for CDGEXT32.CPP.
// ==================================================================
// CDGEXT32.CPP
// ==================================================================
//
// module-level defines
//
#define INITGUID
#define USES_IID_IExchExt
#define USES_IID_IExchExtAdvancedCriteria
#define USES_IID_IExchExtAttachedFileEvents
#define USES_IID_IExchExtCommands
#define USES_IID_IExchExtMessageEvents
#define USES_IID_IExchExtPropertySheets
#define USES_IID_IExchExtSessionEvents
#define USES_IID_IExchExtUserEvents
#define USES_IID_IMessage
#define USES_PS_MAPI
//
// include files
//
#include "CDGEXT32.H"
#include <INITGUID.H>
#include <MAPIGUID.H>
//
// local declarations
//
MAPINAMEID NamedID[1]; // for new property
BOOL bSignatureOn = TRUE; // assume it's on
static HINSTAncE ghInstDLL = NULL; // DLL handle
// ----------------------------------------------------------------
// Main Body of routines
//
// These two routines are the initial entry point for the DLL and
// the code that registers the extension DLL.
// ----------------------------------------------------------------
//
// to start things off as a DLL
//
BOOL WINAPI DllMain(
HINSTAncE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved)
{
if (DLL_PROCESS_ATTAch == fdwReason)
{
ghInstDLL = hinstDLL;
}
return TRUE;
}
//
// register the extension
//
LPEXchEXT CALLBACK ExchEntryPoint(void)
{
return new CDGExt;
}
Next is the code for the methods and properties of the IExchExt object. Along with the initial object constructor code,
three methods must be coded:
// ------------------------------------------
// Handle the creation of the CDGExt object
// ------------------------------------------
//
// These routines establish the initial
// extension interface.
//
// CDGExt::CDGExt() - constructor
// CDGExt::Release() - frees up resources
// CDGExt::QueryInterface() - main entry
// CDGExt::Install() - installs extensions
// --------------------------------------------
//
// constructor for the propertysheet and message event extension
//
CDGExt::CDGExt()
{
m_cRef = 1;
m_pExchExtPropertySheets = new CDGExtPropSheets(this);
m_pExchExtMessageEvents = new CDGExtMsgEvents(this);
};
//
// frees up resources when done
//
STDMETHODIMP_(ULONG) CDGExt::Release()
{
ULONG ulCount = -m_cRef;
if (!ulCount)
{
delete this;
}
return ulCount;
//
// initial interface query
// for both propertysheets and message events
//
STDMETHODIMP CDGExt::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{
HRESULT hResult = S_OK;
*ppvObj = NULL;
if (NULL != *ppvObj)
((LPUNKNOWN)*ppvObj)->AddRef();
return hResult;
}
//
// actually installs the extension
//
STDMETHODIMP CDGExt::Install(LPEXchEXTCALLBACK peecb, ULONG
eecontext, ULONG ➂ulFlags)
{
ULONG ulBuildVersion;
HRESULT hr;
m_context = eecontext;
// compare versions
peecb->GetVersion(&ulBuildVersion, EECBGV_GETBUILDVERSION);
if (EECBGV_BUILDVERSION_MAJOR != (ulBuildVersion &
EECBGV_BUILDVERSION_MAJOR_MA
SK))
return S_FALSE; // oops!
switch (eecontext)
{
case EECONTEXT_PROPERTYSHEETS:
case EECONTEXT_SENDNOTEMESSAGE:
case EECONTEXT_SENDPOSTMESSAGE:
case EECONTEXT_SENDRESENDMESSAGE:
case EECONTEXT_READNOTEMESSAGE:
case EECONTEXT_READPOSTMESSAGE:
case EECONTEXT_READREPORTMESSAGE:
hr = S_OK;
break;
default:
hr = S_FALSE;
break;
}
return hr;
}
//
// end of CDGExt Object
// ----------------------------------------------------------------
The next section of code in the CDGEXT32.CPP file is the code that implements the methods of the
IExchExtMessageEvents interface. Listing 12.7 shows the initial QueryInterface function for the object.
Listing 12.7. The initial QueryInterface function for the MessageEvents object.
// ----------------------------------------------------------------
// Handle creation of Messge Event object
// ----------------------------------------------------------------
//
// Routines for the Message Event extension
//
// CDGExtMsgEvents::QueryInterface() - [not used]
// CDGExtMsgEvents::OnRead() - at start of read msg
// CDGExtMsgEvents::OnReadComplete() - at end of read
// CDGExtMsgEvents::OnWrite() - ad start of write msg
// CDGExtMsgEvents::OnWriteComplete() - at end of write
// CDGExtMsgEvents::OnSubmit() - at start of msg submit
// CDGExtMsgEvents::OnSubmitComplete() - end of submit
// CDGExtMsgEvents::OnCheckNames() - start of resolve
// CDGExtMsgEvents::OnCheckNamesComplete() - resolve end
// ------------------------------------------------------------------
//
// sample queryinterface for message events
// not used now
//
STDMETHODIMP CDGExtMsgEvents::QueryInterface(REFIID riid, LPVOID FAR
* ppvObj)
{
*ppvObj = NULL;
if (riid == IID_IExchExtMessageEvents)
{
*ppvObj = (LPVOID)this;
AddRef(); // using one more!
return S_OK;
}
if (riid == IID_IUnknown)
{
*ppvObj = (LPVOID)m_pExchExt; // return parent interface
m_pExchExt->AddRef();
return S_OK;
}
return E_NOINTERFACE;
);
Tip
The Windows Messaging client does not call the QueryInterface()
methods directly. The code is added here in anticipation of changes to the
client that might result in calls to this method. It is also a good rule to code
the QueryInterface method for all COM objects.
Listing 12.8 shows the code for the OnRead and OnReadComplete methods. These methods check for the existence
of the CheckSum property and, if found, compare the stored value to a freshly computed value. If the results do not
match, the user is warned that the message may have been altered.
//
// fires when user attempts to Read a message
//
HRESULT CDGExtMsgEvents::OnRead(LPEXchEXTCALLBACK lpeecb)
{
HRESULT hr;
LPMESSAGE pMsg;
LPMDB pMDB;
ULONG ulCheckSum;
LPSPropTagArray pNamedPropTags;
LPSPropValue pPropValues;
ULONG ulcValues;
HWND hWnd;
HCURSOR hOldCursor;
pMsg = NULL;
pMDB = NULL;
pPropValues = NULL;
pNamedPropTags = NULL;
// turned OFF?
if (!bSignatureOn)
{
goto error_return;
}
//
// got a value back
//
if (pPropValues[0].Value.ul == ulCheckSum)
{
MessageBox(hWnd,
"Signed Message Verified.",
"CDG Message Signing Extension",
MB_OK);
}
else
{
int nRet = MessageBox(hWnd,
"Signed Message was altered.\n"
"Do you wish to view the message anyway?",
"CDG MEssage Signing Extension",
MB_YESNO);
if (nRet == IDNO)
// tell OnReadComplete to not display the message
m_hrOnReadComplete = MAPI_E_CALL_FAILED;
}
error_return:
hr = S_FALSE;
//
// free up resources
//
if (pMDB != NULL)
pMDB->Release();
if (pMsg != NULL)
pMsg->Release();
if (pNamedPropTags != NULL)
MAPIFreeBuffer(pNamedPropTags);
if (pPropValues != NULL)
MAPIFreeBuffer(pPropValues);
SetCursor(hOldCursor);
return hr;
}
//
// fires after the read is complete
//
HRESULT CDGExtMsgEvents::OnReadComplete(LPEXchEXTCALLBACK lpeecb,
ULONG ulFlags)
{
return m_hrOnReadComplete;
}
Listing 12.9 shows the code for the OnWrite and OnWriteComplete methods. These methods are called when the
user attempts to write a message back to the message store. This is the routine that computes a checksum signature and
places it in a new message field before sending it to the recipient(s).
//
// fires when user starts to write message
//
HRESULT CDGExtMsgEvents::OnWrite(LPEXchEXTCALLBACK lpeecb)
{
HRESULT hr;
hr = S_FALSE;
return hr;
}
//
// fires after message has been written
//
HRESULT CDGExtMsgEvents::OnWriteComplete(LPEXchEXTCALLBACK lpeecb,
ULONG ulFlags)
{
HRESULT hr;
LPMESSAGE pMsg;
LPMDB pMDB;
ULONG ulCheckSum;
SPropValue pChksumProp[1];
LPSPropTagArray pNamedPropTags;
LPSPropProblemArray pPropProblems;
HWND hWnd;
HCURSOR hOldCursor;
// turned OFF?
if (!bSignatureOn)
{
return S_FALSE;
}
pMsg = NULL;
pMDB = NULL;
pPropProblems = NULL;
pNamedPropTags = NULL;
hr = CheckMsgSignature(pMsg, &ulCheckSum);
if (FAILED(hr))
{
lpeecb->GetWindow(&hWnd);
error_return:
//
// free up resources
//
if (pMDB != NULL)
pMDB->Release();
if (pMsg != NULL)
pMsg->Release();
if (pNamedPropTags != NULL)
MAPIFreeBuffer(pNamedPropTags);
if (pPropProblems != NULL)
MAPIFreeBuffer(pPropProblems);
SetCursor(hOldCursor);
return hr;
}
The last bit of code for the Message Events interface is found in Listing 12.10. Not much is happening here. This
code is included for completeness.
//
// fires when user attempts to submit a msg to the outbox
//
HRESULT CDGExtMsgEvents::OnSubmit(LPEXchEXTCALLBACK lpeecb)
{
HRESULT hr;
hr = S_FALSE;
m_bInSubmitState = TRUE; // submit is called
return hr;
}
//
// fires after message was submitted
//
VOID CDGExtMsgEvents::OnSubmitComplete(LPEXchEXTCALLBACK lpeecb,
ULONG ulFlags)
{
//
// fires when user attempts to resolve addreses
//
HRESULT CDGExtMsgEvents::OnCheckNames(LPEXchEXTCALLBACK lpeecb)
{
return S_FALSE; // not used
}
//
// fires after resolve is done
//
HRESULT CDGExtMsgEvents::OnCheckNamesComplete(LPEXchEXTCALLBACK
lpeecb, ULONG ➂ulFlags)
{
return S_FALSE; // not used
}
The last section of code that deals with objects is the code for the IExchExtPropertySheets interface. Listing
12.11 shows the code for implementing custom property sheets for the Windows Messaging client.
// ----------------------------------------------------------------
// Handle creation CDGExtPropSheets object
// ----------------------------------------------------------------
//
// Routines for the propertysheet extension
//
// CDGExtPropSheets::QueryInterface() - [not used]
// CDGExtPropSheets::GetMaxPageCount() - count pages
// CDGExtPropSheets::GetPages() - add a new page
// CDGExtPropSheets::FreePages() - free up resources
// --------------------------------------------------------------
//
// sample queryinterface for propertysheets
// not used right now
//
STDMETHODIMP CDGExtPropSheets::QueryInterface(REFIID riid, LPVOID
FAR * ppvObj)
{
*ppvObj = NULL;
if (riid == IID_IExchExtPropertySheets)
{
*ppvObj = (LPVOID)this;
// Increase usage count of this object
AddRef();
return S_OK;
}
if (riid == IID_IUnknown)
{
*ppvObj = (LPVOID)m_pExchExt; // return parent interface
m_pExchExt->AddRef();
return S_OK;
}
return E_NOINTERFACE;
//
// get property page count
//
ULONG CDGExtPropSheets::GetMaxPageCount(ULONG ulFlags)
{
ULONG ulNumExtSheets;
switch (ulFlags)
{
// ignore these objects.
case EEPS_FOLDER:
case EEPS_STORE:
case EEPS_MESSAGE:
ulNumExtSheets = 0;
break;
default:
ulNumExtSheets = 0;
break;
}
return ulNumExtSheets;
}
//
// to add a new page
//
STDMETHODIMP CDGExtPropSheets::GetPages(LPEXchEXTCALLBACK peecb,
ULONG ulFlags, LPPROPSHEETPAGE ppsp, ULONG FAR
* pcpsp)
{
LPMDB pMDB = NULL;
LPMESSAGE pItem = NULL;
*pcpsp = 0;
*pcpsp = 1;
return S_OK;
}
//
// free up any resources
//
VOID CDGExtPropSheets::FreePages(LPPROPSHEETPAGE ppsp, ULONG
ulFlags, ULONG cpsp)
{
// not used
}
//
// end of CDGExtPropSheets object
// ----------------------------------------------------------------
Only the code for the local helper functions remains. This code section contains two routines: an error message handler
and the code that actually computes the signature checksum. Listing 12.12 shows both these routines.
// ------------------------------------------------------------------
// Local Helper routines
// ------------------------------------------------------------------
//
// ErrMsgBox() - used to handle error msgs
// CheckMsgSignature() - calculates checksum
// ------------------------------------------------------------------
//
// ErrMsgBox()
//
// Params:
// hWnd - parent window
// hr - HRESULT value (0 to suppress)
// szFunction - function name in which the error occurred (NULL
to suppress)
// szMessage - error message (required)
//
void ErrMsgBox(HWND hWnd, HRESULT hr, LPSTR szFunction, LPSTR
szMessage)
{
static char szError[256];
if (szMessage == NULL)
{
MessageBox(hWnd,
"An unknown error occured in\nextension",
"CDG Message Signing Extension", MB_ICONEXCLAMATION |
MB_OK);
return;
}
if (szFunction != NULL)
{
wsprintf(szError, "Error %08X in %s\n%s", hr, szFunction,
szMessage);
MessageBox(hWnd, szError, "CDG Message Signing Extension Error",
➂MB_ICONEXCLAMATION | MB_OK);
}
//
// CheckMsgSignature(pMsg, &ulCheckSum)()
//
// Params:
// pMsg - points to message object
// *pulCheckSum - points to checksum
//
HRESULT CheckMsgSignature(LPMESSAGE pMsg, ULONG *pulCheckSum)
{
HRESULT hr;
LPSTREAM pStreamBody;
ULONG ulValue;
ULONG ulRead;
LARGE_INTEGER LgInt;
ULARGE_INTEGER uLgInt;
//
// make sure you have a valid msg body
//
if ( (pMsg == NULL) ||(pulCheckSum == NULL) )
{
hr = MAPI_E_INVALID_PARAMETER;
goto error_return;
}
//
// access the message body
//
pStreamBody = NULL;
hr = pMsg->OpenProperty(PR_BODY, &IID_IStream, STGM_DIRECT |
STGM_READ, 0, ➂(LPUNKNOWN *) &pStreamBody);
if (FAILED(hr))
{
goto error_return;
}
//
// point to starting position
//
LgInt.LowPart = 0;
LgInt.HighPart = 0;
pStreamBody->Seek(LgInt, STREAM_SEEK_SET, &uLgInt);
//
// add up ascii values
//
(*pulCheckSum) = 0;
ulValue = 0;
while ( (S_OK == (hr = pStreamBody->Read((LPVOID)&ulValue, 4,
&ulRead))) &&
(ulRead > 0) )
{
(*pulCheckSum) += ulValue;
ulValue = 0;
}
//
// clean up
//
error_return:
if (pStreamBody != NULL)
pStreamBody->Release();
return hr;
}
//
// End of Helper routines
// ------------------------------------------------------------------
That is the end of the code for the CDGEXT32.CPP file. In the next section, you'll create the dialog box that will be
added to the collection of property pages for the Windows Messaging client.
The next step is to design the dialog page that will appear on the Tools | Options tabbed dialog box of the
Windows Messaging client. Although you'll build this as if it were a standalone dialog box, Microsoft Exchange will use
the information to create an additional tab in the property pages displayed by the Windows Messaging client.
The dialog box consists of a frame control (IDC_STATIC), a check box (IDC_ENABLESGN), and a label control
(IDC_STATIC). Figure 12.3 shows the layout of the dialog box.
Once the dialog box is designed and saved into the CDGEXT32.RC, you need to create a code module to handle the user
events on the property sheet. Listing 12.13 shows the code for the CDGPRP32.CPP file that handles all the dialog
messages.
// ================================================================
// CDGPRP32.CPP
// ================================================================
//
#include "CDGEXT32.H"
// ----------------------------------------------------------------
// MsgSigningDlgProc
//
// Params:
// hDlg - handle to modeless dialog, the property page
// uMsg - message
// wParam - wParam of wndproc
// lParam - lParam of wndproc, points to NMHDR for notifications
//
// Handles events for the custom property page
// ----------------------------------------------------------------
switch (uMsg)
{
case WM_INITDIALOG:
{
LOGBRUSH lb;
GrayColor = (COLORREF)GetSysColor(COLOR_BTNFACE);
memset(&lb, 0, sizeof(LOGBRUSH));
lb.lbStyle = BS_SOLID;
lb.lbColor = GrayColor;
hBrush = CreateBrushIndirect(&lb);
return TRUE;
}
break;
case WM_CTLCOLORDLG:
case WM_CTLCOLORBTN:
case WM_CTLCOLORSTATIC:
if (hBrush != NULL)
{
SetBkColor((HDC)wParam, GrayColor);
return (BOOL)hBrush;
}
break;
case WM_DESTROY:
{
if (hBrush != NULL)
DeleteObject(hBrush);
return TRUE;
}
case WM_COMMAND:
{
if (LOWORD(wParam) == IDC_ENABLESGN)
{
SendMessage(GetParent(hDlg), PSM_chANGED, (WPARAM)hDlg, 0L);
bSignatureOn = SendDlgItemMessage(hDlg, IDC_ENABLESGN,
BM_GETchECK, 0, 0L);
}
}
break;
case WM_NOTIFY:
{
pnmhdr = ((LPNMHDR) lParam);
switch ( pnmhdr->code)
{
case PSN_KILLACTIVE:
bMsgResult = FALSE; // allow this page to receive PSN_APPLY
break;
case PSN_SETACTIVE:
// initialize controls
if (bSignatureOn)
SendDlgItemMessage(hDlg, IDC_ENABLESGN, BM_SETchECK, 1,
0L);
else
SendDlgItemMessage(hDlg, IDC_ENABLESGN, BM_SETchECK, 0,
0L);
bMsgResult = FALSE;
break;
case PSN_APPLY:
bMsgResult = PSNRET_NOERROR;
break;
case PSN_HELP:
MessageBox( pnmhdr->hwndFrom,
"CDG Message Signing Extension\n"
"(c)1996 MCA/SAMS Publishing",
"About",
MB_OK);
bMsgResult = TRUE;
break;
default:
bMsgResult = FALSE;
break;
} // switch
} // case WM_NOTIFY
default:
bMsgResult = FALSE;
break;
} // switch
return bMsgResult;
}
This is the end of the code for the project. If you have Microsoft Visual C++ (or some other compatible C compiler), you
can compile this code to create the DLL extension that will be installed into the Windows Messaging client.
Those who cannot (or choose not to) compile the source code shown here can copy the CDGEXT32.DLL from the CD-
ROM that ships with the book to your local hard drive instead. You can then install this version of the extension.
There are two main steps to installing the compiled version of the Message Signing Extension. First, you should compile
the project or produce the CDGEXT32.DLL and copy that to a folder on your local hard drive.
Note
If you are not compiling your own version of the source code, copy the
CDGEXT32.DLL from the CD-ROM to a local directory on your machine.
Next, you need to add an entry to your system registry database to inform Microsoft Exchange that a new extension is
available. Listing 12.14 shows the exact registry entry you need to add to the HKEY_LOCAL_MAchINE\Software
\Microsoft\Exchange\Client\Extensions section of your registry database.
CDGEXT32 = 4.0;d:\sams\cdg\chap12\cdgext32\CDGEXT32.
dll;1;00000111111100
Note
The exact drive and directory should reflect the correct location of the
CDGEXT32.DLL on your system.
Once you've added the entry, close the registry to update it and then start the Windows Messaging client to force it to
install the new extension. Now you're ready to try the new Windows Messaging client feature!
When you start up the Windows Messaging client and select Tools | Options from the main menu, you should see a
new property page for the message signing feature (see Figure 12.4).
While you have the property page up, make sure that Message Signing is enabled (that is, the check mark is on).
Next, send yourself a new message. When the new message is written, the CDGEXT32.DLL kicks in and creates a
checksum signature for the message. When you receive this message and open it, you should see a dialog box telling you
the message was verified (see Figure 12.5).
And that's all there is to it! You now have a working Windows Messaging client extension that can perform simple
security checks on incoming messages. And you can turn the verification feature on and off using the new property page
you created.
Summary
In this chapter, you learned the theory behind the Windows Messaging client extension model and the advantages of
using Microsoft Exchange extensions instead of building your own standalone MAPI client. You also learned the
following key concepts:
● The Microsoft Exchange Client has fourteen separate contexts that can be monitored by an extension DLL.
● The Microsoft Exchange Client has seven COM interfaces that can be used by C++ programmers to create
programs that respond to Windows Messaging client events.
● Programmers need to map Microsoft Exchange contexts to Microsoft Exchange COM interfaces.
You also learned how to register Microsoft Exchange extension DLLs in the system registry (for 32-bit systems) or the
EXchNG.INI files for 16-bit systems. You also learned that you can install an extension as a shared resource on the
network by making the correct entry in the SHARED32.INI file (32-bit systems) or the SHARED.INI file (16-bit
systems).
Finally, you learned the details about the methods and properties of the Message Events and Property Sheets
COM interfaces, and used that knowledge to build a working Message Signing extension.
Chapter 13
CONTENTS
In this section of the book you learned the details of the Messaging API (MAPI) services interface and how to use the
various programming tools to access MAPI services. Throughout the section you used the programming techniques and
tools to build several useful desktop applications including:
You learned that there are two versions of MAPI currently active-Simple MAPI (or MAPI 0) and MAPI 1.0. Simple
MAPI is available through the MAPI controls that ship with Visual Basic and through a library of Simple MAPI API
calls. Most of the MAPI 1.0 features are available via the OLE Messaging Library that is shipped with the MSDN
Professional Level CDs. The only MAPI services you cannot perform with the OLE Messaging Library are adding new
entries to the address book, and creating and deleting folders from the message store. For this you need the power of C+
+ and access to the full MAPI 1.0 API set.
This chapter explained that the Messaging Application Programming Interface (MAPI) is a part of the Windows Open
Systems Architecture (WOSA) model. MAPI is designed to offer three key benefits over other messaging services:
● Flexibility-Since MAPI is implemented within the WOSA model, there are three distinct layers:
● The client layer (the end-user software)
● The MAPI DLL layer (the MAPI service broker)
● The service layer (the actual message service provider)
● Because the MAPI DLL layer acts as the service request broker between the MAPI client and the MAPI server,
you can interchange servers and clients without having to modify your MAPI software modules.
● Consistency-MAPI services and the methods for accessing them are the same no matter what vendor you use to
provide the message services.
● Portability-MAPI services are available on all supported versions of Windows (Win3.11, WFW, WinNT, and
Win95). As Microsoft moves WOSA services to
non-Windows platforms, MAPI services will be available within those operating systems, too.
● Text messages-These are the standard plain ASCII text messages commonly known as e-mail. Some MAPI
service providers support the use of rich-text formatted messages (for example, the Microsoft Exchange Mail
client).
● Formatted documents and binary files-These are word processing documents, graphics files, databases, and so
on. MAPI allows you to send these binary files as attachments to the body of text messages.
● Control messages-These messages are used by operating systems and specialized batch programs to relay
information about the operating system, or to send commands that tell remote machines how to process attached
data or run special jobs.
● Electronic mail clients-The sole purpose of these programs is to give users direct access to the available MAPI
services (for example, the Microsoft Mail Exchange client that ships with Windows 95).
● Message-aware applications-These are programs that offer MAPI services as an added feature. Usually these
programs offer users a send button or menu option. The standard output of the program can then be routed to
another location through MAPI. The Send... menu option of Microsoft Word95 is an example of a message-
aware application.
● Message-enabled applications-These programs offer MAPI services as a basic part of their functionality.
Message-enabled applications usually will not operate properly unless MAPI services are available to the
workstation. Examples of message-enabled applications are data entry forms that collect data and automatically
route it to the appropriate e-mail address, sometimes without asking the user for MAPI logons or addresses.
● Electronic forms applications-These programs are fully functional data entry forms that are MAPI-enabled.
Users can treat the form like any Windows program. Once data entry is completed and the message is sent, the
addressee can open the message and see the same data form.
● Message-driven applications-These are programs that can inspect portions of a message (body, header,
attachments) and perform requested actions based on the contents of the message parts. Examples of message-
driven applications include e-mail filtering agents, file transfer and update routines, and long-distance data
search and retrieval programs.
In this chapter you learned about the general architecture of the MAPI system. You learned that there are two main
components to the system:
The MAPI Client resides on the user's desktop and handles three main MAPI objects:
● Messages and attachments
● Storage folders
● MAPI addresses
Chapter 4 also reviewed the basic properties and features of MAPI messages, including message headers, folders, and
address objects.
You learned that the MAPI Server usually resides on a stand-alone workstation connected to the network (although not
always). Like the MAPI Client, the MAPI Server handles three main objects:
● Message transports
● Message stores
● Address books
The MAPI model allows users to use multiple versions of message transports (such as Microsoft Exchange Server
messages and SMTP Internet messages), message storage, and address books. You also learned about the MAPI Spooler,
which moves items from the message store to the appropriate provider.
In this chapter you learned how to use the Microsoft Exchange Forms Designer kit that ships with Microsoft Exchange
Server. You learned how to design, code, test, and install custom message forms for use at individual workstations or
over a large network.
You also learned how to set up Microsoft Exchange folders for use with custom forms. Related topics included:
In Chapter 6 you learned how to use low-level MAPI function calls to add e-mail capabilities to existing Windows
applications.
You learned that MAPI-aware applications are programs that provide electronic send and/or receive services as a part of
their basic feature set. Mail-aware software is able to use the available mail services in much the same way that
programs use available printers, modems, or storage media (disk drives, CD-ROMs, and so on). For example, most
office suite applications (word processors, spreadsheets, for example) provide a send feature on the main menu of all
their programs. Basically, whatever documents you can create with the program can be sent to other locations using the
mail services available on the network.
You learned to add MAPI-aware features to an Excel spreadsheet by adding a few API calls and some code to package
up sections of the spreadsheet to be forwarded to another e-mail user.
You also modified a text editor project built with Visual Basic 4.0 by adding a Send... option to the main menu. In
this way you added MAPI features to the application with a minimum of additional coding.
Part of this chapter described the details of the MAPI 0 API call set. This API set gives you the same abilities as the
Visual Basic 4.0 MAPI controls with a few advantages:
● Crash protection-Using the Visual Basic 4.0 MAPI controls will cause your program to crash if MAPI services
are not available at the workstation. Using the API set means you can trap for missing MAPI services and
modify your program accordingly.
● Portability-You can use the API call set with any VBA-compliant system, including Access, Excel, and Word.
You cannot use the Visual Basic MAPI controls with these applications.
You learned the properties and methods of the two Visual Basic MAPI controls. The MAPISession control is used to
gain access to MAPI service providers through the SignOn and SignOff methods. The MAPIMessages control is
used to read, create, delete, address, and send MAPI messages.
The Simple MAPI client described in this chapter showed you how to build a complete MAPI-enabled application with a
minimum of coding. This example application showed how you can use the Visual Basic MAPI controls to create a fully-
functional e-mail client that can read and delete incoming messages, compose new messages, address them, and send
them to their recipients.
You also learned that Simple MAPI services allow only limited access to the MAPI address book. You can search and
edit the address book only through the pre-defined MAPI.Show method. You cannot directly search for existing
addresses or add, edit, or delete addresses without using the address dialog box supplied by the MAPI service provider.
Simple MAPI does not allow you access to any of the existing mail folders. You can only see the inbox folder and its
contents.
In Chapter 8 you learned how to use the OLE Messaging Library (OML) to access features of MAPI. You learned all the
major objects, their methods, and their properties.
You also wrote several code examples that inspected and modified objects and their properties. You learned to use the
OLE Messaging Library to:
In Chapter 9 you built a Mailing List Manager (MLM) using Microsoft's OLE Messaging Library. This application let
users define a mailing list, allowed others to become list subscribers, published messages automatically (based on date),
and allowed other users to query and retrieve old messages from the list archives. The program accepted members from
within a single network, or from around the world via Internet (or other) transports.
The MLM application allowed individuals to create a set of text files to be distributed to a controlled list of users at
specified times. This project had only one simple form and several support routines. All application rules were stored in
a set of ASCII control files similar to INI/registry settings. These control values can be changed by the list manager to
determine how the mailing list operates, and what features are available to subscribers.
In Chapter 10 you learned how to use the OLE Messaging Library to build a Visual Basic 4.0 application that can track
discussion threads in the shared Microsoft Exchange folder. You learned how to use the ConversationIndex and
ConversationTopic properties of message objects in order to track and link messages in a series of related threads.
You also used the Visual Basic 4.0 Win95 controls to create a Forum Client reader that graphically represents the
progress of a discussion and allows users to read threads and add new items to a thread. You added discussion manager
features that control the expiration, deletion, and archiving of threads and allow users to set memberships and security
levels for discussion groups.
In addition to building your own Forum Tool with Visual Basic 4.0, you learned how to use the Folder Views options of
Microsoft Exchange Client to create both personal and public views of message folders. This gives you the ability to
create discussion forums within Microsoft Exchange without having to install additional MAPI software.
In this chapter you learned how to use the OLE Messaging Library and some API calls to create a stand-alone e-mail
agent. This agent scanned your incoming mail and, based on rules you established, automatically handled messages for
you. It also archived and purged old messages. All actions were based on rules you established in a control file.
Features of the e-mail agent included:
In Chapter 12 you learned how to add custom routines to your existing MAPI clients. You learned how to do this using
the MAPI 0 extension interface and by using the Microsoft Exchange client extension.
You learned that there are four types of Microsoft Exchange client extensions:
● Command extensions
● Event extensions
● Property sheet extensions
● Advanced criteria extensions
You also learned that there are several possible Microsoft Exchange extension message events that you can use to build
special processing routines into the Microsoft Exchange client (see Table 13.1).
You designed a simple extension that computes and reports the size of the message at the OnRead event. You also
added a simple property page to the Microsoft Exchange client to turn the size checker on and off.
You learned how to register MAPI clients by modifying the [Register] section of the local INI file. Also, you learned that
there are several places where extension registration can be found, depending on the Windows platform:
What Is SAPI
CONTENTS
● Speech Recognition
❍ Word Separation
❍ Speaker Dependence
❍ Word Matching
❍ Vocabulary
❍ Text-to-Speech
❍ Voice Quality
❍ Phonemes
❍ TTS Synthesis
● Grammar Rules
❍ Context-Free Grammars
❍ Dictation Grammars
● Summary
One of the newest extensions for the Windows 95 operating system is the Speech Application Programming Interface
(SAPI). This Windows extension gives workstations the ability to recognize human speech as input, and create human-
like audio output from printed text. This ability adds a new dimension to human/pc interaction. Speech recognition
services can be used to extend the use of pcs to those who find typing too difficult or too time-consuming. Text-to-
speech services can be used to provide aural representations of text documents to those who cannot see typical display
screens because of physical limitations or due to the nature of their work.
Like the other Windows services described in this book, SAPI is part of the Windows Open Services Architecture
(WOSA) model. Speech recognition (SR) and text-to-speech (TTS) services are actually provided by separate modules
called engines. Users can select the speech engine they prefer to use as long as it conforms to the SAPI interface.
In this chapter you'll learn the basic concepts behind designing and implementing a speech recognition and text-to-
speech engine using the SAPI design model. You'll also learn about creating grammar definitions for speech recognition.
Speech Recognition
Any speech system has, at its heart, a process for recognizing human speech and turning it into something the computer
understands. In effect, the computer needs a translator. Research into effective speech recognition algorithms and
processing models has been going on almost ever since the computer was invented. And a great deal of mathematics and
linguistics go into the design and implementation of a speech recognition system. A detailed discussion of speech
recognition algorithms is beyond the scope of this book, but it is important to have a good idea of the commonly used
techniques for turning human speech into something a computer understands.
Every speech recognition system uses four key operations to listen to and understand human speech. They are:
● Word separation-This is the process of creating discreet portions of human speech. Each portion can be as large
as a phrase or as small as a single syllable or word part.
● Vocabulary-This is the list of speech items that the speech engine can identify.
● Word matching-This is the method that the speech system uses to look up a speech part in the system's
vocabulary-the search engine portion of the system.
● Speaker dependence-This is the degree to which the speech engine is dependent on the vocal tones and
speaking patterns of individuals.
These four aspects of the speech system are closely interrelated. If you want to develop a speech system with a rich
vocabulary, you'll need a sophisticated word matching system to quickly search the vocabulary. Also, as the vocabulary
gets larger, more items in the list could sound similar (for example, yes and yet). In order to successfully identify these
speech parts, the word separation portion of the system must be able to determine smaller and smaller differences
between speech items.
Finally, the speech engine must balance all of these factors against the aspect of speaker dependence. As the speech
system learns smaller and smaller differences between words, the system becomes more and more dependent on the
speaking habits of a single user. Individual accents and speech patterns can confuse speech engines. In other words, as
the system becomes more responsive to a single user, that same system becomes less able to translate the speech of other
users.
The next few sections describe each of the four aspects of a speech engine in a bit more detail.
Word Separation
The first task of the speech engine is to accept words as input. Speech engines use a process called word separation to
gather human speech. Just as the keyboard is used as an input device to accept physical keystrokes for translation into
readable characters, the process of word separation accepts the sound of human speech for translation by the computer.
There are three basic methods of word separation. In ascending order of complexity they are:
● Discrete speech
● Word spotting
● Continuous speech
Systems that use the discrete speech method of word separation require the user to place a short pause between each
spoken word. This slight bit of silence allows the speech system to recognize the beginning and ending of each word.
The silences separate the words much like the space bar does when you type. The advantage of the discrete speech
method is that it requires the least amount of computational resources. The disadvantage of this method is that it is not
very user-friendly. Discrete speech systems can easily become confused if a person does not pause between words.
Systems that use word spotting avoid the need for users to pause in between each word by listening only for key words
or phrases. Word spotting systems, in effect, ignore the items they do not know or care about and act only on the words
they can match in their vocabulary. For example, suppose the speech system can recognize the word help, and knows to
load the Windows Help engine whenever it hears the word. Under word spotting, the following phrases will all result in
the speech engine invoking Windows Help:
Continuous speech systems recognize and process every word spoken. This gives the greatest degree of accuracy when
attempting to understand a speaker's request. However, it also requires the greatest amount of computing power. First,
the speech system must determine the start and end of each word without the use of silence. This is much like
readingtextthathasnospacesinit (see!). Once the words have been separated, the system must look them up in the
vocabulary and identify them. This, too, can take precious computing time. The primary advantage of continuous speech
systems is that they offer the greatest level of sophistication in recognizing human speech. The primary disadvantage is
the amount of computing resources they require.
Speaker Dependence
Speaker dependence is a key factor in the design and implementation of a speech recognition system. In theory, you
would like a system that has very little speaker dependence. This would mean that the same workstation could be spoken
to by several people with the same positive results. People often speak quite differently from one another, however, and
this can cause problems.
First, there is the case of accents. Just using the United States as an example, you can identify several regional sounds.
Add to these the possibility that speakers may also have accents that come from outside the U.S. due to the influence of
other languages (Spanish, German, Japanese), and you have a wide range of pronunciation for even the simplest of
sentences. Speaker speed and pitch inflection can also vary widely, which can pose problems for speech systems that
need to determine whether a spoken phrase is a statement or a question.
Speech systems fall into three categories in terms of their speaker dependence. They can be:
● Speaker independent
● Speaker dependent
● Speaker adaptive
Speaker-independent systems require the most resources. They must be able to accurately translate human speech across
as many dialects and accents as possible. Speaker-dependent systems require the least amount of computing resources.
These systems require that the user "train" the system before it is able to accurately convert human speech. A
compromise between the two approaches is the speaker-adaptive method. Speaker-adaptive systems are prepared to
work without training, but increase their accuracy after working with the same speaker for a period of time.
The additional training required by speaker-dependent systems can be frustrating to users. Usually training can take
several hours, but some systems can reach 90 percent accuracy or better after just five minutes of training. Users with
physical disabilities, or those who find typing highly inefficient, will be most likely to accept using speaker-dependent
systems.
Systems that will be used by many different people need the power of speaker independence. This is especially true for
systems that will have short encounters with many different people, such as greeting kiosks at an airport. In such
situations, training is unlikely to occur, and a high degree of accuracy is expected right away.
For systems where multiple people will access the same workstation over a longer period of time, the speaker-adaptive
system will work fine. A good example would be a workstation used by several employees to query information from a
database. The initial investment spent training the speech system will pay off over time as the same staff uses the system.
Word Matching
Word matching is the process of performing look-ups into the speech database. As each word is gathered (using the word
separation techniques described earlier), it must be matched against some item in the speech engine's database. It is the
process of word matching that connects the audio input signal to a meaningful item in the speech engine database.
● Whole-word matching
● Phoneme matching
Under whole-word matching, the speech engine searches the database for a word that matches the audio input. Whole-
word matching requires less search capability than phoneme matching. But, whole-word matching requires a greater
amount of storage capacity. Under the whole-word matching model, the system must store a word template that
represents each possible word that the engine can recognize. While quick retrieval makes whole-word matching
attractive, the fact that all words must be known ahead of time limits the application of whole-word matching systems.
Phoneme matching systems keep a dictionary of language phonemes. Phonemes are the smallest unique sound part of a
language, and can be numerous. For example, while the English language has 26 individual letters, these letters do not
represent the total list of possible phonemes. Also, phonemes are not restricted by spelling conventions.
Consider the words Philip and fill up. These words have the same phonemes: f, eh, ul, ah, and pah. However, they have
entirely different meanings. Under the whole-word matching model, these words could represent multiple entries in the
database. Under the phoneme matching model, the same five phonemes can be used to represent both words.
As you may expect, phoneme matching systems require more computational resources, but less storage space.
Vocabulary
The final element of a speech recognition system is the vocabulary. There are two competing issues regarding
vocabulary: size and accuracy. As the vocabulary size increases, recognition improves. With large vocabularies, it is
easy for speech systems to locate a word that matches the one identified in the word separation phase. However, one of
the reasons it is easy to find a match is that more than one entry in the vocabulary may match the given input. For
example, the words no and go are very similar to most speech engines. Therefore, as vocabulary size grows, the accuracy
of speech recognition can decrease.
Contrary to what you might assume, a speech engine's vocabulary does not represent the total number of words it
understands. Instead, the vocabulary of a speech engine represents the number of words that it can recognize in a current
state or moment in time. In effect, this is the total number of "unidentified" words that the system can resolve at any
moment.
For example, let's assume you have registered the following word phrases with your speech engine: "Start running
Exchange" and "Start running Word." Before you say anything, the current state of the speech engine has four words:
start, running, Exchange, and Word. Once you say "Start running" there are only two words in the current state:
Exchange and Word. The system's ability to keep track of the possible next word is determined by the size of its
vocabulary.
Small vocabulary systems (100 words or less) work well in situations where most of the speech recognition is devoted to
processing commands. However, you need a large vocabulary to handle dictation systems. Dictation vocabularies can
reach into tens of thousands of words. This is one of the reasons that dictation systems are so difficult to implement. Not
only does the vocabulary need to be large, the resolutions must be made quite quickly.
Text-to-Speech
A second type of speech service provides the ability to convert written text into spoken words. This is called text-to-
speech (or TTS) technology. Just as there are a number of factors to consider when developing speech recognition
engines (SR), there are a few issues that must be addressed when creating and implementing rules for TTS engines.
The four common issues that must be addressed when creating a TTS engine are as follows:
● Phonemes
● Voice quality
● TTS synthesis
● TTS diphone concatenation
The first two factors deal with the creation of audio tones that are recognizable as human speech. The last two items are
competing methods for interpreting text that is to be converted into audio.
Voice Quality
The quality of a computerized voice is directly related to the sophistication of the rules that identify and convert text into
an audio signal. It is not too difficult to build a TTS engine that can create recognizable speech. However, it is extremely
difficult to create a TTS engine that does not sound like a computer. Three factors in human speech are very difficult to
produce with computers:
● Prosody
● Emotion
● Pronunciation anomalies
Human speech has a special rhythm or prosody-a pattern of pauses, inflections, and emphasis that is an integral part of
the language. While computers can do a good job of pronouncing individual words, it is difficult to get them to
accurately mimic the tonal and rhythmic in-flections of human speech. For this reason, it is always quite easy to
differentiate computer-generated speech from a computer playing back a recording of a human voice.
Another factor of human speech that computers have difficulty rendering is emotion. While TTS engines are capable of
distinguishing declarative statements from questions or exclamations, computers are still not able to convey believable
emotive qualities when rendering text into speech.
Lastly, every language has its own pronunciation anomalies. These are words that do not "play by the rules" when it
comes to converting text into speech. Some common examples in English are dough and tough or comb and home. More
troublesome are words such as read which must be understood in context in order to figure out their exact pronunciation.
For example, the pronunciations are different in "He read the paper" or "She will now read to the class." Even more
likely to cause problems is the interjection of technobabble such as "SQL," "MAPI," and "SAPI." All these factors make
the development of a truly human-sounding computer-generated voice extremely difficult.
Speech systems usually offer some way to correct for these types of problems. One typical solution is to include the
ability to enter the phonetic spelling of a word and relate that spelling to the text version. Another common adjustment is
to allow users to enter control tags in the text to instruct the speech engine to add emphasis or inflection, or alter the
speed or pitch of the audio output. Much of this type of adjustment information is based on phonemes, as described in
the next section.
Phonemes
As we've discussed, phonemes are the sound parts that make up words. Linguists use phonemes to accurately record the
vocal sounds uttered by humans when speaking. These same phonemes also can be used to generate computerized
speech. TTS engines use their knowledge of grammar rules and phonemes to scan printed text and generate audio output.
Note
If you are interested in learning more about phonemes and how they are
used to analyze speech, refer to the Phonetic Symbol Guide by Pullum and
Ladusaw (Chicago University Press, 1996).
The SAPI design model recognizes and allows for the incorporation of phonemes as a method for creating speech output.
Microsoft has developed an expression of the International Phonetic Alphabet (IPA) in the form of Unicode strings.
Programmers can use these strings to improve the pronunciation skills of the TTS engine, or to add entirely new words
to the vocabulary.
Note
If you wish to use direct Unicode to alter the behavior of your TTS engine,
you'll have to program using Unicode. SAPI does not support the direct use
of phonemes in ANSI format.
As mentioned in the previous section on voice quality, most TTS engines provide several methods for improving the
pronunciation of words. Unless you are involved in the development of a text-to-speech engine, you probably will not
use phonemes very often.
TTS Synthesis
Once the TTS knows what phonemes to use to reproduce a word, there are two possible methods for creating the audio
output: synthesis or diphone concatenation.
The synthesis method uses calculations of a person's lip and tongue position, the force of breath, and other factors to
synthesize human speech. This method is usually not as accurate as the diphone method. However, if the TTS uses the
synthesis method for generating output, it is very easy to modify a few parameters and then create a new "voice."
Synthesis-based TTS engines require less overall computational resources, and less storage capacity. Synthesis-based
systems are a bit more difficult to understand at first, but usually offer users the ability to adjust the tone, speed, and
inflection of the voice rather easily.
The diphone concatenation method of generating speech uses pairs of phonemes (di meaning two) to produce each
sound. These diphones represent the start and end of each individual speech part. For example, the word pig contains the
diphones silence-p, p-i, i-g, and g-silence. Diphone TTS systems scan the word and then piece together the correct
phoneme pairs to pronounce the word.
These phoneme pairs are produced not by computer synthesis, but from actual recordings of human voices that have
been broken down to their smallest elements and categorized into the various diphone pairs. Since TTS systems that use
diphones are using elements of actual human speech, they can produce much more human-like output. However, since
diphone pairs are very language-specific, diphone TTS systems are usually dedicated to producing a single language.
Because of this, diphone systems do not do well in environments where numerous foreign words may be present, or
where the TTS might be required to produce output in more than one language.
Grammar Rules
The final elements of a speech engine are the grammar rules. Grammar rules are used by speech recognition (SR)
software to analyze human speech input and, in the process, attempt to understand what a person is saying. Most of us
suffered through a series of lessons in grade school where our teachers attempted to show us just how grammar rules
affect our everyday speech patterns. And most of us probably don't remember a great deal from those lessons, but we all
use grammar rules every day without thinking about them, to express ourselves and make sense of what others say to us.
Without an understanding of and appreciation for the importance of grammars, computer speech recognition systems
would not be possible.
There can be any number of grammars, each composed of a set of rules of speech. Just as humans must learn to share a
common grammar in order to be understood, computers must also share a common grammar with the speaker in order to
convert audio information into text.
Grammars can be divided in to three types, each with its own strengths and weaknesses. The types are:
● Context-free grammars
● Dictation grammars
● Limited domain grammars
Context-free grammars offer the greatest degree of flexibility when interpreting human speech. Dictation grammars offer
the greatest degree of accuracy when converting spoken words into printed text. Limited domain grammars offer a
compromise between the highly flexible context-free grammar and the restrictive dictation grammar.
Context-Free Grammars
Context-free grammars work on the principle of following established rules to determine the most likely candidates for
the next word in a sentence. Context-free grammars do not work on the idea that each word should be understood within
a context. Rather, they evaluate the relationship of each word and word phrase to a known set of rules about what words
are possible at any given moment.
Context-free grammars are good for systems that have to deal with a wide variety of input. Context-free systems are also
able to handle variable vocabularies. This is because most of the rule-building done for context-free grammars revolves
around declaring lists and groups of words that fit into common patterns or rules. Once the SR engine understands the
rules, it is very easy to expand the vocabulary by expanding the lists of possible members of a group.
For example, rules in a context-free grammar might look something like this:
<NameRule>=ALT("Mike","Curt","Sharon","Angelique")
The construction of quality context-free grammars can be a challenge, however. Systems that only need to do a few
things (such as load and run programs, execute simple directives, and so on) are easily expressed using context-free
grammars. However, in order to perform more complex tasks or a wider range of chores, additional rules are needed. As
the number of rules and the length of lists increases, the computational load rises dramatically. Also, since context-free
grammars base their predictions on predefined rules, they are not good for tasks like dictation, where a large vocabulary
is most important.
Dictation Grammars
Unlike context-free grammars that operate using rules, dictation grammars base their evaluations on vocabulary. The
primary function of a dictation grammar is to convert human speech into text as accurately as possible. In order to do
this, dictation grammars need not only a rich vocabulary to work from, but also a sample output to use as a model when
analyzing speech input. Rules of speech are not important to a system that must simply convert human input into printed
text.
The success of a dictation grammar depends on the quality of the vocabulary. The more items on the list, the greater the
chance of the SR engine mistaking one item for another. However, the more limited the vocabulary, the greater the
number of "unknown" words that will occur during the course of the dictation. The most successful dictation systems
balance vocabulary depth and the uniqueness of the words in the database. For this reason, dictation systems are usually
tuned for one topic, such as legal or medical dictation. By limiting the vocabulary to the words most likely to occur in
the course of dictation, translation accuracy is increased.
Limited domain grammars offer a compromise between the flexibility of context-free grammars and the accuracy of
dictation grammars. Limited domain grammars have the following elements:
● Words-This is the list of specialized words that are likely to occur during a session.
● Group-This is a set of related words that could occur during the session. The grammar can contain multiple
word groups. A single phrase would be expected to include one of the words in the group.
● Sample-A sample of text that shows the writing style of the speaker or general format of the dictation. This text
is used to aid the SR engine in analyzing the speech input.
Limited domain grammars are useful in situations where the vocabulary of the system need not be very large. Examples
include systems that use natural language to accept command statement, such as "How can I set the margins?" or
"Replace all instances of 'New York' with 'Los Angeles.'" Limited domain grammars also work well for filling in forms
or for simple text entry.
Summary
In this chapter you learned about the key factors behind creating and implementing a complete speech system for pcs.
You learned the three major parts to speech systems:
● Speech recognition-Converts audio input into printed text or directly into computer commands.
● Text-to-speech-Converts printed text into audible speech.
● Grammar rules-Used by speech recognition systems to analyze audio input and convert it into commands or
text.
In the next chapter, you'll learn the specifics behind the Microsoft speech recognition engine.
Chapter 15
SAPI Architecture
CONTENTS
● Introduction
● High-Level SAPI
❍ Voice Command
❍ Voice Text
● Low-Level SAPI
❍ Speech Recognition
❍ Text-to-Speech
● Summary
Introduction
The Speech API is implemented as a series of Component Object Model (COM) interfaces. This chapter identifies the
top-level objects, their child objects, and their methods.
● High-level SAPI-This level provides basic speech services in the form of command-and-control speech
recognition and simple text-to-speech output.
● Low-level SAPI-This level provides detailed access to all speech services, including direct interfaces to control
dialogs and manipulation of both speech recognition (SR) and text-to-speech (TTS) behavior attributes.
Each of the two levels of SAPI services has its own set of objects and methods.
Along with the two sets of COM interfaces, Microsoft has also published an OLE Automation type library for the high-
level SAPI objects. This set of OLE objects is discussed at the end of the chapter.
When you complete this chapter you'll understand the basic architecture of the SAPI model, including all the SAPI
objects and their uses. Detailed information about the object's methods and parameters will be covered in the next
chapter-"SAPI Basics."
Note
Most of the Microsoft Speech API is accessible only through C++ code. For
this reason, many of the examples shown in this chapter are expressed in
Microsoft Visual C++ code. You do not need to be able to code in C++ in
order to understand the information discussed here. At the end of this
chapter, the OLE Automation objects available through Visual Basic are
also discussed.
High-Level SAPI
The high-level SAPI services provide access to basic forms of speech recognition and text-to-speech services. This is
ideal for providing voice-activated menus, command buttons, and so on. It is also sufficient for basic rendering of text
into speech.
The high-level SAPI interface has two top-level objects-one for voice command services (speech recognition), and one
for voice text services (text-to-speech). The following two sections describe each of these top-level objects, their child
objects, and the interfaces available through each object.
Voice Command
The Voice Command object is used to provide speech recognition services. It is useful for providing simple command-
and-control speech services such as implementing menu options, activating command buttons, and issuing other simple
operating system commands.
The Voice Command object has one child object and one collection object. The child object is the Voice Menu
object and the collection object is a collection of enumerated menu objects (see Figure 15.1).
The Voice Command interface is used to enumerate, create, and delete voice menu objects. This interface is also used
to register an application to use the SR engine. An application must successfully complete the registration before the SR
engine can be used. An additional method defined for the Voice Command interface is the Mimic method. This is
used to play back a voice command to the engine; it can be used to "speak" voice commands directly to the SR engine.
This is similar to playing keystroke or mouse-action macros back to the operating system.
The Attributes interface is used to set and retrieve a number of basic parameters that control the behavior of the
voice command system. You can enable or disable voice commands, adjust input gain, establish the SR mode, and
control the input device (microphone or telephone).
The Dialogs interface gives you access to a series of dialog boxes that can be used as a standard set of input screens
for setting and displaying SR engine information. The SAPI model identifies five different dialog boxes that should be
available through the Dialogs interface. The exact layout and content of these dialog boxes is not dictated by
Microsoft, but is determined by the developer of the speech recognition engine. However, Microsoft has established
general guidelines for the contents of the SR engine dialog boxes. Table 15.1 lists each of the five defined dialog boxes
along with short descriptions of their suggested contents.
The Voice Menu object is the only child object of the Voice Command object. It is used to allow applications to
define, add, and delete voice commands in a menu. You can also use the Voice Menu object to activate and deactivate
menus and, optionally, to provide a training dialog box for the menu.
The voice menu collection object contains a set of all menu objects defined in the voice command database. Microsoft
SAPI defines functions to select and copy menu collections for use by the voice command speech engine.
In the process of registering the application to use a voice command object, a notification callback (or sink) is
established. This callback receives messages regarding the SR engine activity. Typical messages sent out by the SR
engine can include notifications that the engine has detected commands being spoken, that some attribute of the engine
has been changed, or that spoken commands have been heard but not recognized.
Note
Notification callbacks require a pointer to the function that will receive all
related messages. Callbacks cannot be registered using Visual Basic; you
need C or C++. However, the voice command OLE Automation type library
that ships with the Speech SDK has a notification callback built into it.
Voice Text
The SAPI model defines a basic text-to-speech service called voice text. This service has only one object-the Voice
Text object. The Voice Text object supports three interfaces:
The Voice Text interface is the primary interface of the TTS portion of the high-level SAPI model. The Voice
Text interface provides a set method to start, pause, resume, fast forward, rewind, and stop the TTS engine while it is
speaking text. This mirrors the VCR-type controls commonly employed for pc video and audio playback.
The Voice Text interface is also used to register the application that will request TTS services. An application must
successfully complete the registration before the TTS engine can be used. This registration function can optionally pass a
pointer to a callback function to be used to capture voice text messages. This establishes a notification callback with
several methods, which are triggered by messages sent from the underlying TTS engine.
Note
Notification callbacks require a pointer to the function that will receive all
related messages. Callbacks cannot be registered using Visual Basic; you
need C or C++. However, the voice text OLE Automation type library that
ships with the Speech SDK has a notification callback built into it.
The Attribute interface provides access to settings that control the basic behavior of the TTS engine. For example,
you can use the Attributes interface to set the audio device to be used, set the playback speed (in words per minute),
and turn the speech services on and off. If the TTS engine supports it, you can also use the Attributes interface to
select the TTS speaking mode. The TTS speaking mode usually refers to a predefined set of voices, each having its own
character or style (for example, male, female, child, adult, and so on).
The Dialogs interface can be used to allow users the ability to set and retrieve information regarding the TTS engine.
The exact contents and layout of the dialog boxes are not determined by Microsoft but by the TTS engine developer.
Microsoft does, however, suggest the possible contents of each dialog box. Table 15.2 shows the four voice text dialogs
defined by the SAPI model, along with short descriptions of their suggested contents.
Low-Level SAPI
The low-level SAPI services provide access to a much greater level of control of Windows speech recognition and text-
to-speech services. This level is best for implementing advanced SR and TTS services, including the creation of dictation
systems.
Just as there are two basic service types for high-level SAPI, there are two primary COM interfaces defined for low-level
SAPI-one for speech recognition and one for text-to-speech services. The rest of this chapter outlines each of the objects
and their interfaces.
Note
This section of the chapter covers the low-level SAPI services. These
services are available only from C or C++ programs-not Visual Basic.
However, even if you do not program in C, you can still learn a lot from this
section of the chapter. The material in this section can give you a good
understanding of the details behind the SAPI OLE automation objects, and
may also give you some ideas on how you can use the VB-level SAPI
services in your programs.
Speech Recognition
The Speech Recognition object has several child objects and collections. There are two top-level objects in the SR
system: the SR Engine Enumerator object and the SR Sharing object. These two objects are created using their
unique CLSID (class ID) values. The purpose of both objects is to give an application information about the available
speech recognition engines and allow the application to register with the appropriate engine. Once the engine is selected,
one or more grammar objects can be created, and as each phrase is heard, an SR Results object is created for each
phrase. This object is a temporary object that contains details about the phrase that was captured by the speech
recognition engine. Figure 15.2 shows how the different objects relate to each other, and how they are created.
When an SR engine is created, a link to a valid audio input device is also created. While it is possible to create a custom
audio input device, it is not required. The default audio input device is an attached microphone, but can also be set to
point to a telephone device.
The rest of this section details the low-level SAPI SR objects and their interfaces.
The role of the SR Enumerator and Engine Enumerator objects is to locate and select an appropriate SR engine
for the requesting application. The Enumerator object lists all available speech recognition modes and their associated
installed engines. This information is supplied by the child object of the Enumerator object: the Engine
Enumerator object. The result of this search is a pointer to the SR engine interface that best meets the service request.
The Enumerator and Engine Enumerator objects support only two interfaces:
Note
The SR Enumerator and Engine Enumerator objects are used only
to locate and select an engine object. Once that is done, these two objects
can be discarded.
The SR Sharing object is a possible replacement for the SR Enumerator and Engine Enumerator objects. The
SR Sharing object uses only one interface, the ISRSharing interface, to locate and select an engine object that will
be shared with other applications on the pc. In essence, this allows for the registration of a requesting application with an
out-of-process memory SR server object. While often slower than creating an instance of a private SR object, using the
Sharing object can reduce strain on memory resources.
The SR Sharing interface is an optional feature of speech engines and may not be available depending on the design
of the engine itself.
The SR Engine Object is the heart of the speech recognition system. This object represents the actual speech engine
and it supports several interfaces for the monitoring of speech activity. The SR Engine is created using the Select
method of the ISREnum interface of the SR Enumerator object described earlier. Table 15.3 lists the interfaces
supported by the SR Engine object along with a short description of their uses.
The SR Engine object also provides a notification callback interface (ISRNotifySink) to capture messages sent by
the engine. These messages can be used to check on the performance status of the engine, and can provide feedback to
the application (or speaker) that can be used to improve performance.
The Grammar object is a child object of the SR Engine object. It is used to load parsing grammars for use by the
speech engine in analyzing audio input. The Grammar object contains all the rules, words, lists, and other parameters
that control how the SR engine interprets human speech. Each phrase detected by the SR engine is processed using the
loaded grammars.
● ISRGramCFG-This interface is used to handle grammar functions specific to context-free grammars, including
the management of lists and rules.
● ISRGramDictation-This interface is used to handle grammar functions specific to dictation grammars,
including words, word groups, and sample text.
● IRSGramCommon-This interface is use to handle tasks common to both dictation and context-free grammars.
This includes loading and unloading grammars, activating or deactivating a loaded grammar, training the
engine, and possibly storing SR results objects.
The Grammar object also supports a notification callback to handle messages regarding grammar events. Optionally, the
grammar object can create an SR Results object. This object is discussed fully in the next section.
The SR Results object contains detailed information about the most recent speech recognition event. This could
include a recorded representation of the speech, the interpreted phrase constructed by the engine, the name of the
speaker, performance statistics, and so on.
Note
The SR Results object is optional and is not supported by all engines.
Table 15.4 shows the interfaces defined for the SR Results object, along with descriptions of their use. Only the first
interface in the table is required (the ISRResBasic interface).
Text-to-Speech
The low-level text-to-speech services are provided by one primary object-the TTS Engine object. Like the SR object
set, the TTS object set has an Enumerator object and an Engine Enumerator object. These objects are used to
locate and select a valid TTS Engine object and are then discarded (see Figure 15.3).
The TTS services also use an audio output object. The default object for output is the pc speakers, but this can be set to
the telephone device. Applications can also create their own output devices, including the creation of a WAV format
recording device as the output for TTS engine activity.
The rest of this section discusses the details of the low-level SAPI TTS objects.
The TTS Enumerator and Engine Enumerator objects are used to obtain a list of the available TTS engines and
their speaking modes. They both support two interfaces:
Once the objects have provided a valid address to a TTS engine object, the TTS Enumerator and Engine
Enumerator objects can be discarded.
The TTS Engine object is the primary object of low-level SAPI TTS services. The Engine object supports several
interfaces. Table 15.5 lists the interfaces used for the translations of text into audible speech.
In addition to the interfaces described in Table 15.5, the TTS Engine object supports two notification callbacks:
● ITTSNotifySink-Used to send the application messages regarding the playback of text as audio output,
including start and stop of playback and other events.
● ITTSBufNotifysink-Used to send messages regarding the status of text in the playback buffer. If the
content of the buffer changes, messages are sent to the application using the TTS engine.
Microsoft supplies an OLE Automation type library with the Speech SDK. This type library can be used with any VBA-
compliant software, including Visual Basic, Access, Excel, and others. The OLE Automation set provides high-level
SAPI services only. The objects, properties, and methods are quite similar to the objects and interfaces provided by the
high-level SAPI services described at the beginning of this chapter.
There are two type library files in the Microsoft Speech SDK:
You can load these libraries into a Visual Basic project by way of the Tools | References menu item (see Figure
15.4).
Figure 15.4 : Loading the Voice Command and Voice Text type libraries.
The OLE Automation speech recognition services are implemented using two objects:
The OLE Voice Command object has three properties and two methods. Table 15.6 shows the Voice Command
object's properties and methods, along with their parameters and short descriptions.
Table 15.6. The properties and methods of the OLE Voice Command object.
Property/Method Name Parameters Description
This method is used to
register the application with
Register method the SR engine. It must be
called before any speech
recognition will occur.
Visual Basic 4.0 programs
can use this property to
identify an existing class
CallBack property Project.Class as string module that has two special
methods defined. (See the
following section, "Using the
Voice Command Callback.")
Use this property to turn on
Awake property TRUE/FALSE or off speech recognition for
the application.
Use this property to
determine which command
was heard by the SR engine.
VB4 applications do not need
to use this property if they
have installed the callback
CommandSpoken property cmdNum as integer
routines described earlier. All
other programming
environments must poll this
value (using a timer) to
determine the command that
has been spoken.
Use this method to create a
appName as String,
new menu object. Menu
state as String,
objects are used to add new
MenuCreate method langID as Integer,
items to the list of valid
dialect as String,
commands to be recognized
flags as Long
by the SR engine.
The Voice Command type library provides a unique and very efficient method for registering callbacks using a Visual
Basic 4.0 class module. In order to establish an automatic notification from the SR engine, all you need to do is add a
VB4 class module to your application. This class module must have two functions created:
● CommandRecognize-This event is fired each time the SR engine recognizes a command that belongs to your
application's list.
● CommandOther-This event is fired each time the SR engine receives spoken input it cannot understand.
Listing 15.1 shows how these two routines look in a class module.
Listing 15.1. Creating the notification routines for the Voice Command object.
End Function
Note
You'll learn more about how to use the Voice Command object in
Chapter 19, "Creating SAPI Applications with C++."
The OLE Voice Menu object is used to add new commands to the list of valid items that can be recognized by the SR
engine. The Voice Menu object has two properties and three methods. Table 15.7 shows the Voice Menu object's
methods and properties, along with parameters and short descriptions.
Table 15.7. The properties and methods of the OLE Voice Menu object.
Property/Method Parameters Description
Sets the window handle for a voice
menu. Whenever this window is the
foreground window, the voice menu
hWndMenu property hWnd as long
is automatically activated; otherwise,
it is deactivated. If this property is set
to NULL, the menu is global.
Use this to turn the menu on or off. If
this is set to TRUE, the menu is
Active property TRUE/FALSE active. The menu must be active
before its commands will be
recognized by the SR engine.
Adds a new menu to the list of
recognizable menus. The command
id as Long, parameter contains the actual menu
command as String, item the SR engine will listen for.
Add method
category as String, The id parameter will be returned
description as String when the SR engine recognizes that
the command has been spoken. The
other parameters are optional.
Removes an item from the menu list.
The id parameter is the same value
Remove method id as Long
used to create the menu in the Add
method.
Add a list of possible entries for use
with a command (see "Using
Command Lists with the Voice
Name as String, Menu Object" later in this chapter).
ListSet method Elements as Long, Name is the name of the list referred
Data as String to in a command. Elements is the
total number of elements in this list.
Data is the set of elements,
separated by a chr(0).
The Voice Menu object allows you to define a command that refers to a list. You can then load this list into the
grammar using the ListSet method. For example, you can use the Add method to create a command to send e-mail
messages. Then you can use the ListSet method to create a list of people to receive e-mail (see Listing 15.2).
Listing 15.2. Using the Add and ListSet methods of the Voice Menu object.
Dim Names
Dim szNULL as String
szNULL = Chr(0)
You can gain access to the OLE Automation TTS services using only one object-the Voice Text object. The Voice
Text object has four properties and seven methods. Table 15.8 shows the properties and methods, along with their
parameters and short descriptions.
Table 15.8. The properties and methods of the Voice Text object.
Property/Method Parameters Description
Used to register the application with
Register method AppName as string the TTS engine. This must be called
before any other methods are called.
This property is used to establish a
callback interface between the
Project.Class as Voice Text object and your
Callback property
string program. See the "Using the Voice
Text Callback" section later in this
chapter.
Use this property to turn the TTS
service on or off. This must be set to
Enabled property TRUE/FALSE
TRUE for the Voice Text object to
speak text.
Setting this value controls the speed
(in words per minute) at which text is
Speed property lSpeed as Long spoken. Setting the value to 0 sets the
slowest speed. Setting the value to -1
sets the fastest speed.
Indicates whether the TTS engine is
currently speaking text. You can poll
this read-only property to determine
IsSpeaking TRUE/FALSE when the TTS engine is busy or idle.
Note that VB4 programmers should
use the Callback property instead
of this property.
Use this method to get the TTS engine
cText as string, to speak text. The lFlags parameter
Speak method
lFlags as Long can contain a value to indicate this is a
statement, question, and so on.
Use this method to force the TTS
StopSpeaking method (none) engine to stop speaking the current
text.
Use this method to pause all TTS
AudioPause (none) activity. This affects all applications
using TTS services at this site (pc).
Use this method to resume TTS
activity after calling AudioPause.
AudioResume (none)
This affects all applications using TTS
services at this site (pc).
Use this method to back up the TTS
AudioRewind (none) playback approximately one phrase or
sentence.
Use this method to advance the TTS
AudioFastForward (none) engine approximately one phrase or
sentence.
The Voice Text type library provides a unique and very efficient method for registering callbacks using a Visual Basic
4.0 class module. In order to establish an automatic notification from the TTS engine, all you need to do is add a VB4
class module to your application. This class module must have two functions created:
● SpeakingStarted-This event is fired each time the TTS engine begins speaking text.
● SpeakingDone-This event is fired each time the TTS engine stops speaking text.
Listing 15.3. Creating the notification routines for a Voice Text object.
Function SpeakingDone()
VtintrForm.StatusMsg.Text = "Speaking Done notification" & Chr
(13) & Chr(10) & ➂VtintrForm.StatusMsg.Text
End Function
Function SpeakingStarted()
VtintrForm.StatusMsg.Text = "Speaking Started notification" & Chr
(13) & Chr(10) ➂& VtintrForm.StatusMsg.Text
End Function
Only VB4 applications can use this method of establishing callbacks through class modules. If you are using the TTS
objects with other VBA-compatible languages, you need to set up a routine, using a timer, that will regularly poll the
IsSpeaking property. The IsSpeaking property is set to TRUE while the TTS engine is speaking text.
Summary
In this chapter you learned the details of the SR and TTS interfaces defined by the Microsoft SAPI model. You learned
that the SAPI model is based on the Component Object Model (COM) interface and that Microsoft has defined two
distinct levels of SAPI services:
● High-level SAPI-This provides a command-and-control level of service. This is good for detecting menu and
system-level commands and for speaking simple text.
● Low-level SAPI-This provides a much more flexible interface and allows programmers access to extended SR
and TTS services.
You learned that the two levels of SAPI service each contain several COM interfaces that allow C programmers access
to speech services. These interfaces include the ability to set and get engine attributes, turn the services on or off, display
dialog boxes for user interaction, and perform direct TTS and SR functions.
Since the SAPI model is based on the COM interface, high-level languages such as Visual Basic cannot directly call
functions using the standard API calls. Instead, Microsoft has developed OLE Automation type libraries for use with
Visual Basic and other VBA-compliant systems. The two type libraries are:
You now have a good understanding of the types of speech recognition and text-to-speech services that are available
with the Microsoft SAPI model. In the next chapter, you'll learn about details surrounding the design and implementation
of SAPI applications, including typical hardware required, technology limits, and design considerations when building
SAPI applications.
Chapter 19
CONTENTS
Most of the things you'll want to add to Windows applications can be handled using the OLE automation libraries for
speech recognition (VCMDAUTO.TLB) and text-to-speech (VTXTAUTO.TLB). These libraries can be called from Visual
Basic or any VBA-compliant application such as Excel, Access, Word, and so on. However, there are some things that you
can only do using C or C++ languages. This includes building permanently stored grammars, calling SAPI dialog boxes,
and other low-level functions. For this reason, it is a good idea to know how to perform some basic SAPI functions in C.
Even if you do not regularly program in C or C++, you can still learn a lot by reading through the code in these examples.
In this chapter, you'll get a quick tour of a TTS demonstration application and an SR demonstration application. The
applications reviewed here are part of the Microsoft Speech SDK. If you have the Speech SDK installed on your
workstation, you can follow along with the code and compile and test the resulting application. If you do not have the SDK
installed or do not have a copy of C++ with which to compile the application, you can still follow along with the review of
the various functions used in the C++ programs.
When you are finished with this chapter, you will know how to build simple TTS and SR programs using C++ and the
Microsoft Speech SDK.
Note
All the examples in this chapter are shipped with the Microsoft Speech SDK.
The compiler used in this chapter is Microsoft Visual C++ 4.1 (VC++). If you
are using another compiler, you may need to modify some of the code in order
for it to work for you.
Creating a TTS application with C++ involves just a few basic steps. To see how this is done, you can look at the
TTSDEMO.CPP source code that ships with the Microsoft Speech SDK. You can find this file in the \SPEEchSDK
\SAMPLES\TTSDEMO folder that was created when you installed the Microsoft Speech SDK.
Note
If you do not have the Microsoft Speech SDK installed or do not have Visual
C++, you can still follow along with the code examples shown in this chapter.
The rest of this section reviews the contents of the TTSDEMO.MAK project. There are two components to review: the
DEMO.CPP source code file and the DEMO.RC file. The DEMO.CPP source code file contains all the code needed to
implement TTS services for Windows using C++. The DEMO.RC file contains a simple dialog box that you can use to
accept text input from the user and then send that text to the TTS engine for playback.
Before you code the various routines for the TTS demo, there are a handful of include statements and a couple of global
declarations that must be added. Listing 19.1 shows the include statements needed to implement TTS services in VC++.
Listing 19.1. The include statements and global declarations for DEMO.CPP.
/
***********************************************************************
Demo.Cpp - Code to demo tts.
*/
#include <windows.h>
#include <string.h>
#include <stdio.h>
#include <mmsystem.h>
#include <initguid.h>
#include <objbase.h>
#include <objerror.h>
#include <ole2ver.h>
#include <speech.h>
#include "resource.h"
/
*************************************************************************
Globals */
Most of the include files are part of VC++. The speech.h header file is shipped as part of the Microsoft Speech SDK.
And the resource.h header file is created when you build the dialog box for the project.
Since the SAPI model is implemented using the Component Object Model (COM) interface, you need to begin and end an
OLE session as part of normal processing. After starting the OLE session, you need to use the TTSCentral interface to
locate and initialize an available TTS engine. Once you have a session started with a valid TTS engine, you can use a
simple dialog box to accept text input and send that text to the TTS engine using the TextData method of the
TTSCentral interface. After exiting the dialog box, you need to release the connection to TTSCentral and then end
the OLE session.
The code in Listing 19.2 shows the WinMain procedure for the TTSDEMO.CPP file.
/
*************************************************************************
winmain - Windows main code.
*/
if (!BeginOLE())
{
MessageBox (NULL, "Can't create OLE.", NULL, MB_OK);
return 1;
}
if (!EndOLE())
MessageBox (NULL, "Can't shut down OLE.", NULL, MB_OK);
return 0;
}
You can see the basic steps mentioned earlier: start the OLE session, get a TTS object, and start the dialog box. Once the
dialog is completed, you need to release the TTS object and end the OLE session. The rest of the code all supports the code
in WinMain.
The code needed to start and end the OLE session is pretty basic. The code in Listing 19.3 shows both the BeginOLE and
EndOLE procedures.
Listing 19.3. The BeginOLE and EndOLE procedures.
/
*************************************************************************
BeginOLE - This begins the OLE.
inputs
none
returns
BOOL - TRUE if is succedes
*/
// Initialize OLE
SetMessageQueue(96);
dwVer = CoBuildVersion();
return TRUE;
}
/
*************************************************************************
EndOLE - This closes up the OLE.
inputs
none
returns
BOOL - TRUE if succede
*/
CoUninitialize ();
return TRUE;
}
The code in Listing 19.3 shows three steps in the BeginOLE routine. The first line creates a message queue that can hold
up to 96 messages. The next two lines check the OLE version, and the last line actually initializes the OLE session. The
EndOLE session simply releases the OLE session you created in BeginOLE.
/
*************************************************************************
FindAndSelect - This finds and selects according to the specific
TTSMODEINFOW.
inputs
PTTSMODEINFOW pTTSInfo - desired mode
returns
PITTSCENTRAL - ISRCentral interface to TTS engine
sets:
*/
Next, you need to create an instance of the TTSFind object. This will be used to select an available TTS engine. Listing
19.5 shows how this is done.
if (hRes)
{
pITTSFind->Release();
return NULL; // error
}
The next step is to locate and select an audio output object. This will be used by the TTS engine for playback of the text.
Listing 19.6 shows the code needed to select an available audio device.
The code in Listing 19.6 uses the DeviceNumSet method of the MMAudioDest interface to find the available WAVE
output device for the pc.
Once you have successfully created the TSFind object and the MMAudioDest object, you're ready to use the Select
method of TTSFind to return a handle to a valid TTS engine object. After getting the handle, you can release the
TTSFind object because it was needed only to locate a valid TTS engine object. Listing 19.7 shows how this is done.
if (hRes) {
pITTSFind->Release();
return NULL;
};
pITTSFind->Release();
return pITTSCentral;
}
After getting a valid TTS engine and a valid audio output, you can start sending text to the TTS engine using the
TextData method of the TTSCentral interface.
Sending Text to the TTS Engine
The TTSDEMO project uses a simple dialog box to accept text from the user and send it to the TTS engine. Figure 19.1
shows the dialog box in design mode.
This dialog box has a single text window and two command buttons-Speak and Exit. When the user presses the OK
button, the text typed into the window is sent to the TTS engine for playback. The code in Listing 19.8 shows the
CallBack routine that handles the dialog box events.
/
*************************************************************************
DialogProc
*/
BOOL CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM
lParam)
{
switch (uMsg) {
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
{
char szSpeak[1024];
WchAR wszSpeak[1024];
SDATA data;
// Speak
GetDlgItemText (hWnd, IDC_EDIT, szSpeak, sizeof
(szSpeak));
data.dwSize = (DWORD)
MultiByteToWideChar(CP_ACP, 0, szSpeak, -1,
wszSpeak,
sizeof(wszSpeak) / sizeof(WchAR)) * sizeof(WchAR);
data.pData = wszSpeak;
gpITTSCentral->TextData (chARSET_TEXT, 0,
data, NULL,
IID_ITTSBufNotifySinkW);
}
return TRUE;
case IDCAncEL:
EndDialog (hWnd, IDCAncEL);
return TRUE;
}
break;
};
That's all there is to it. You can test the application by compiling it yourself or by loading the TTSDEMO.EXE application
from the CD-ROM that ships with this book.
Building SR applications in C++ is not much different. The biggest change is that you need to create a menu object and
load it with commands that will be spoken to the SR engine. In fact, the process of loading the commands and then
checking the spoken phrase against the command list is the largest part of the code.
The C++ example reviewed here is part of the Microsoft Speech SDK. The project VCMDDEMO.MAK is installed when you
install the SDK. If you have the SDK installed and own a copy of C++, you can load the project and review the source code
while you read this chapter.
Note
If you do not have a copy of the SDK or C++, you can still learn a lot by
reviewing the code shown here.
There are two main parts to the project. The first is the DEMO.CPP source code. This file contains the main C++ code for
the project. The second part of the project is the VCMDDEMO.RC resource file. This file contains the definitions of two
dialog boxes used in the project.
The first thing that must be done is to add the include and declaration statements to the source code. These will be used
throughout the project. Most of the include files are a part of the VC++ system. However, the last two items (speech.h
and resource.h) are unique. The speech.h file ships with the SDK. The resource.h file contains information
about the two dialog boxes used in the project. The define statements establish some constant values that will be used to
track timer events later in the project. Listing 19.9 shows the header and include code for the project.
Listing 19.9. The include and header code for the VCMDDEMO project.
/
***********************************************************************
Demo.Cpp - Code to quickly demo voice commands.
*/
#include <windows.h>
#include <string.h>
#include <stdio.h>
#include <mmsystem.h>
#include <initguid.h>
#include <objbase.h>
#include <objerror.h>
#include <ole2ver.h>
#include <speech.h>
#include "resource.h"
The VCMDDEMO project uses notification callbacks to receive messages from the SR engine. Since the SAPI system is
based on the Component Object Model (COM), you'll need to include some code that creates the needed class object and
associated methods. Listing 19.10 shows the code needed to declare the class and methods of the event notification routine.
public:
CIVCmdNotifySink(void);
~CIVCmdNotifySink(void);
// IVCmdNotifySink members
STDMETHODIMP CommandRecognize (DWORD, PVCMDNAME, DWORD, DWORD,
PVOID,
DWORD,PSTR, PSTR);
STDMETHODIMP CommandOther (PVCMDNAME, PSTR);
STDMETHODIMP MenuActivate (PVCMDNAME, BOOL);
STDMETHODIMP UtteranceBegin (void);
STDMETHODIMP UtteranceEnd (void);
STDMETHODIMP CommandStart (void);
STDMETHODIMP VUMeter (WORD);
STDMETHODIMP AttribChanged (DWORD);
STDMETHODIMP Interference (DWORD);
};
typedef CIVCmdNotifySink * pcIVCmdNotifySink;
There is one more step needed as part of the initial declarations. The VCMDDEMO project must declare a list of commands
to be loaded into the menu. These are the commands that the SR engine will be able to recognize when a user speaks. The
project also uses a handful of other global-level declarations in the project. Listing 19.11 shows the final set of declarations
for the project.
/
*************************************************************************
Globals */
The main routine of the project is quite simple. First, the project performs basic initialization by starting the OLE session,
initializing the SR engine, and making sure it is up and running. After the OLE routines are done, the default list of
commands is loaded, and the first dialog box is presented. This first dialog box simply displays a list of possible commands
and listens to see if the user utters any of them (see Figure 19.2).
Once the user exits the top dialog box, string resources are freed and the OLE objects are released. Listing 19.12 shows the
complete WinMain procedure.
/
*************************************************************************
winmain - Windows main code.
*/
if (!BeginOLE())
{
if(!bNonFatalShutDown)
MessageBox (NULL, "Can't open. OLE or a VoiceCommands call
failed", NULL, ➂MB_OK);
return 1;
}
if (gpszCommands)
free (gpszCommands);
return 0;
}
Starting and Ending the OLE Session
All C++ programs need to use the OLE services of Windows to access SAPI services. The BeginOLE routine of the
VCMDDEMO project covers a lot of ground. This one routine handles OLE initialization, the registration of SAPI services,
creation of several SR objects, checking the registry for SAPI-related entries, and checking the status of the SR engine on
the workstation.
The first step is to establish the start of OLE services. Listing 19.13 shows this part of the BeginOLE routine.
/
*************************************************************************
BeginOLE - This begins the OLE and creates the voice commands object,
registers with it, and creates a temporary menu.
inputs
none
returns
BOOL - TRUE if is succedes
*/
gpIVoiceCommand = NULL;
gpIVCmdDialogs = NULL;
gpIVCmdMenu = NULL;
// Initialize OLE
SetMessageQueue(96);
dwVer = CoBuildVersion();
The next step is to create a Voice Command object, get a pointer to one of the training dialog boxes provided by SAPI,
and register the VCMDDEMO application to receive notifications when the SR engine recognizes a command. Listing 19.14
shows this part of the BeginOLE routine.
Listing 19.14. Creating the Voice Command object and registering the application.
// Create the voice commands object
if (CoCreateInstance(CLSID_VCmd,
NULL,
CLSCTX_LOCAL_SERVER,
IID_IVoiceCmd,
(LPVOID *)&gpIVoiceCommand) != S_OK)
goto fail;
// Register
hRes = gpIVoiceCommand->Register("", &gVCmdNotifySink,
IID_IVCmdNotifySink, VCMDRF_ALLMESSAGES, NULL);
if (hRes)
goto fail;
If all that goes well, the next section of code creates a link to a command object attribute interface and checks the status of
SAPI services on the workstation. The CheckNavigator routine checks the registry to see if speech services are present.
If they are, the VCMDState routine is used to return a value indicating the status of SAPI services. Based on the return
value, several different messages are displayed in dialog boxes for the user to review. These two routines are reviewed later
in this chapter. Listing 19.15 shows the next part of the BeginOLE routine.
hRes = gpIVoiceCommand->QueryInterface(
IID_IVCmdAttributes, (LPVOID FAR *)&pIVCmdAttributes);
if (pIVCmdAttributes)
{
int iRes;
DWORD dwRes;
iRes = CheckNavigator();
if(iMBRes == IDCAncEL)
{
pIVCmdAttributes->Release();
bNonFatalShutDown = TRUE;
goto fail;
}
else if(iMBRes == IDOK)
{
pIVCmdAttributes->EnabledSet( TRUE );
pIVCmdAttributes->AwakeStateSet( TRUE );
}
}
pIVCmdAttributes->Release();
};
Finally, the routine creates a menu object to hold the command list. The final part of the routine contains code that is
invoked in case of errors. This code releases any collected resources. Listing 19.16 shows the final portion of the
BeginOLE routine.
// else failed
fail:
if (gpIVoiceCommand)
gpIVoiceCommand->Release();
if (gpIVCmdDialogs)
gpIVCmdDialogs->Release();
if (gpIVCmdMenu)
gpIVCmdMenu->Release();
gpIVoiceCommand = NULL;
gpIVCmdDialogs = NULL;
gpIVCmdMenu = NULL;
return FALSE;
}
After the user exits the main dialog box, the WinMain routine calls the EndOLE procedure. This procedure releases SAPI
resources and closes out the OLE session. Listing 19.17 shows the code for the EndOLE procedure.
/
*************************************************************************
EndOLE - This closes up the OLE and frees everything else.
inputs
none
returns
BOOL - TRUE if succeed
*/
CoUninitialize ();
return TRUE;
}
The VCMDDEMO project contains two routines that check the status of speech services on the workstation. The first routine
(CheckNavigator) checks the Windows registry to see if speech services have been installed on the machine. Listing
19.18 shows the CheckNavigator procedure.
/
****************************************************************************
* CheckNavigator:
*
* Checks the registry entries to see if a navigator application
* has been installed on the machine. If the Navigator is installed
* CheckNavigator returns its state(0 [not running], 1 [running])
else if no
* navigator is found it returns -1.
****************************************************************************/
int CheckNavigator(void)
{
HKEY hKey;
DWORD dwType=REG_DWORD, dwSize=sizeof(DWORD), dwVal;
RegCloseKey (hKey);
return (int)dwVal;
}
The second routine in VCMDDEMO that checks the status of speech services is the VCMDState procedure. This routine
uses the EnabledGet method of the Attributes interface to see whether the SR engine is already listening for audio
input. Listing 19.19 shows the code for the VCMDState routine.
Listing 19.19. The VCMDState procedure.
/
****************************************************************************
* VCMDState:
*
* Determines what listening state Voice Commands is in. Returns an
int
* specifying a state( 0 [not listening state], 1 [sleep state], 2
[listening
* state]) or in case of error returns 3.
****************************************************************************/
dwAwake = dwEnabled = 0;
if((FAILED(pIVCmdAttributes->EnabledGet(&dwEnabled))) || ➂(FAILED
(pIVCmdAttributes->AwakeStateGet(&dwAwake))))
return 3;// function failed
else
{
if(dwEnabled == 0)
return 0; //not listening state
else if(dwEnabled == 1 && dwAwake == 0)
return 1; //sleep state
else
return 2; //listening state
}
}
Once the main dialog box starts, there are four possible events to handle. First, upon initiation of the dialog box, the
commands are loaded into the menu object, and the timer is activated. The next possible event is the user pressing one of
the three command buttons on the form. Here, if Cancel is selected, the program is ended. If the Train button is
selected, a general dialog box (supplied by the engine) is called. Finally, if the Change button is pressed, the secondary
dialog box is presented.
While waiting for the user to press a button, the timer event fires every two seconds. Each time the timer event occurs, the
program displays a new command on the main form and waits for the user to speak the phrase. Finally, upon exiting the
dialog box, the timer is canceled and the routine is exited. Listing 19.20 shows the code for this procedure.
/
*************************************************************************
DialogProc
*/
BOOL CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM
lParam)
{
switch (uMsg) {
case WM_INITDIALOG:
ghwndResultsDisplay = GetDlgItem (hWnd, IDC_HEARD);
ghwndDialog = hWnd;
The secondary dialog box allows the user to create a new, customized menu. When the OK button is pressed, the new
command list is copied to the menu object using the UseCommands routine. Listing 19.21 contains the code for the
ChangeProc procedure.
/
*************************************************************************
ChangeProc
*/
BOOL CALLBACK ChangeProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM
lParam)
{
switch (uMsg) {
case WM_INITDIALOG:
SetDlgItemText (hWnd, IDC_EDIT, gpszCommands);
return FALSE;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
{
char *pszNew;
DWORD dwSize;
// Throw out the old buffer & copy the
// new one in. Then set us to use it
pszNew = (char*) malloc (dwSize =
GetWindowTextLength(GetDlgItem(hWnd, IDC_EDIT)) +
1);
if (pszNew) {
GetDlgItemText (hWnd, IDC_EDIT, pszNew, dwSize);
free (gpszCommands);
gpszCommands = pszNew;
gpszCurCommand = pszNew;
if (UseCommands (gpszCommands, gpIVCmdMenu))
return 1; // error
};
EndDialog (hWnd, IDOK);
}
return TRUE;
case IDCAncEL:
EndDialog (hWnd, IDCAncEL);
return TRUE;
}
break;
};
The VCMDDEMO project uses three routines to handle the process of loading commands into the voice menu object and
responding to recognized spoken commands. The UseCommands procedure is the high-level routine that loads the voice
menu object. There are five steps to complete for loading the menu. First, the current menu is deactivated. Then the number
of commands in the menu is updated (pMenu->Num). Next, all the commands in the existing menu are removed using the
pMenu->Remove method.
Once all commands are removed, the GetCommands procedure is called to collect all the new commands into a single
data block. This block is then used as the source for adding the new menus (pMenu->Add). Notice that the C++ Add
method allows you to add all menus at once by telling the SR engine to total the number of commands in the data block.
After the data is loaded, the memory is freed and the menu is reactivated using the pMenu->Activate method. Listing
19.22 shows how this looks in the VCMDDEMO code.
/
************************************************************************
UseCommands - This accepts a NULL-terminated string with commands
separated by new-lines and loads them into the voice-menu object,
replacing any old commands.
inputs
char *pszCommands - String.
PIVCMDMENU pMenu - Menu
returns
HRESULT - error
*/
HRESULT UseCommands (char *pszCommands, PIVCMDMENU pMenu)
{
HRESULT hRes;
SDATA data;
DWORD dwNum, dwStart;
if (dwNum)
hRes = pMenu->Remove (1, dwNum, VCMD_BY_POSITION);
if (hRes) return hRes;
// free memory
free (data.pData);
The GetCommands procedure converts the text strings stored in the memory block into the menu commands structure
understood by the SAPI system. The first step is a call to NextCommand to get a command line to load. Then, after
computing the total length of the command, a series of steps is executed to build a valid menu structure. This continues in a
loop until the NextCommand procedure reports that all command strings have been converted. Listing 19.23 shows the
source code for the GetCommands procedure.
/*****************************************************************
GetCommands - Takes a block of memory containing command strings and
converts it into a list of VCMDCOMMAND structures.
inputs
char *pszMemory - NULL terminated string. Commands are
separated by \n or \r.
PSDATA pData - This is filled in with a pointer to memory and
size for the vcmdcommand structure. The memory
must be freed by the caller with free().
DWORD *pdwNumCommands - Filled with the number of commands
*/
BOOL GetCommands(char *pszMemory, PSDATA pData, DWORD
*pdwNumCommands)
{
PSTR pTemp;
DWORD dwTotal, dwSize, dwSizeDesc, dwSizeCat;
DWORD dwSizeCmd;
PVCMDCOMMAND pCmd, pCmdNew;
chAR *pszBegin;
DWORD dwCmdSize;
DWORD dwCmds = 0; // Current count
DWORD dwCount = 1; // Command number
char szCat[] = "Main";
dwTotal = dwSize = 0;
pTemp = (PSTR)malloc(0);
if (!pTemp)
return FALSE;
pCmd = (PVCMDCOMMAND)pTemp;
for( ;; ) {
pszMemory = NextCommand (pszMemory, &pszBegin, &dwCmdSize);
if (!pszMemory)
break; // no more
// size of header
dwSize = sizeof(VCMDCOMMAND);
// doubleword align
dwSizeDesc += 3;
dwSizeDesc &= (~3);
dwSize += dwSizeDesc;
// doubleword align
dwSizeCat += 3;
dwSizeCat &= (~3);
dwSize += dwSizeCat;
// action indicator
dwSize += sizeof(DWORD);
pCmdNew->dwSize = dwSize;
pCmdNew->dwFlags = 0;
pCmdNew->dwAction = (DWORD)(pCmdNew->abData-(PBYTE)pTemp);
pCmdNew->dwActionSize = sizeof(DWORD);
pCmdNew->dwCommandText = NULL;
// action index
*(DWORD *)pTemp = dwCount++;
pTemp += sizeof(DWORD);
// command
pCmdNew->dwCommand = (DWORD)((PBYTE)pTemp - (PBYTE)pCmdNew);
strncpy(pTemp, pszBegin, dwCmdSize);
pTemp += dwSizeCmd;
// description
pCmdNew->dwDescription = (DWORD)((PBYTE)pTemp - (PBYTE)
pCmdNew);
strncpy(pTemp, pszBegin, dwCmdSize);
pTemp += dwSizeDesc;
// category
pCmdNew->dwCategory = (DWORD)((PBYTE)pTemp - (PBYTE)pCmdNew);
strcpy(pTemp, szCat);
pData->pData = (PVOID)pCmd;
pData->dwSize = dwTotal;
*pdwNumCommands = dwCmds;
return TRUE;
}
The final routine that handles the processing of menu commands is the NextCommand procedure. This routine searches
the command list data block for characters until a newline character is found. The resulting string is assumed to be a valid
command. This command is returned, and the starting position is updated for the next call to this routine. Listing 19.24
shows the code for the NextCommand routine.
/****************************************************************
NextCommand - This looks in the memory and finds the next command.
inputs
chAR *pszMemory - Memory to start looking at
PchAR *pBegin - Filled in with a pointer to the
beginning of the command string.
DWORD *pdwSize - Filled in with the number of bytes in
the string (excluding any NULL termination)
returns
chAR * - The next place that NextCommand should be called from,
or NULL if no command string was found.
*/
chAR * NextCommand (chAR *pszMemory, PchAR *pBegin,
DWORD *pdwSize)
{
DWORD i;
for( ;; ) {
// try to find a non-newline
while ((*pszMemory == '\n') || (*pszMemory == '\r')) {
if (*pszMemory == '\0')
return NULL;
pszMemory++;
};
The final code section to review is the code that handles the various notification events of the voice command menu object.
In this program, most of the events are ignored. However, the two most important events-CommandRecognized and
CommandOther-contain code that will display the command spoken. The standard COM methods (QueryInterface,
AddRef, and Release) are also coded. Listing 19.25 contains the code for the notification events.
/
**************************************************************************
* Voice Command notification objects
**************************************************************************/
CIVCmdNotifySink::CIVCmdNotifySink (void)
{
m_dwMsgCnt = 0;
}
CIVCmdNotifySink::~CIVCmdNotifySink (void)
{
// this space intentionally left blank
}
if (!ghwndResultsDisplay)
return NOERROR;
SetWindowText (ghwndResultsDisplay,
pszCommand ? pszCommand : "[Unrecognized]");
return NOERROR;
}
if (!ghwndResultsDisplay)
return NOERROR;
SetWindowText (ghwndResultsDisplay,
pszCommand ? pszCommand : "[Unrecognized]");
return NOERROR;
}
return NOERROR;
}
STDMETHODIMP CIVCmdNotifySink::CommandStart(void)
{
// Called when SR starts processing a command. We don't care.
return NOERROR;
}
STDMETHODIMP CIVCmdNotifySink::UtteranceBegin(void)
{
// Called when an utterance begins. We don't care.
return NOERROR;
}
STDMETHODIMP CIVCmdNotifySink::UtteranceEnd()
{
// Called when an utterance finishes. We don't care.
return NOERROR;
}
That completes the review of the VCMDDEMO project. You can test this project by compiling the project or just by running
the VCMDDEMO.EXE program. You'll find this in the SPEEch\BIN directory that was created when you installed the
Microsoft Speech SDK. You can also find this program on the CD-ROM that ships with this book.
Summary
In this chapter, you learned how to write simple TTS and SR applications using C++. You reviewed (and hopefully were
able to build) a simple TTS program that you can use to cut and paste any text for playback. You also reviewed (built and
tested) a simple SR interface to illustrate the techniques required to add SR services to existing applications.
In the next chapter, you'll build a complete program in Visual Basic 4.0 that uses both SR and TTS services to implement a
voice-activated text reader.
Chapter 18
CONTENTS
● Control Tags
❍ The Voice Character Control Tags
● Grammar Rules
❍ General Rules for the SAPI Context-Free Grammar
In this chapter, you'll learn about three aspects of the SAPI system that are not often used in the course of normal speech
services operations:
● Control tags are used to modify the audio output of TTS engines.
● Grammar rules are used to tell SR engines how to analyze audio input.
● The International Phonetic Alphabet is used by Unicode systems to gain additional control over both TTS
output and SR input.
You'll learn how to add control tags to your TTS text input in order to change the speed, pitch, volume, mood, gender,
and other characteristics of TTS audio output. You'll learn how to use the 15 control tags to improve the sound of your
TTS engine.
You'll also learn how grammar rules are used by the SR engine to analyze spoken input. You'll learn how to design your
own grammars for specialized uses. You'll also learn how to code and compile your own grammars using tools from the
Microsoft Speech SDK. Finally, you'll load your custom grammar into a test program and test the results of your newly
designed grammar rules.
The last topic in this chapter is the International Phonetic Alphabet (IPA). The IPA is a
standard system for documenting the various sounds of human speech. The IPA is an implementation option for SAPI
speech services under Unicode. For this reason, the IPA can be implemented only on WinNT systems. In this chapter,
you'll learn how the IPA can be used to improve both TTS playback and SR recognition performance.
Control Tags
One of the most difficult tasks for a TTS system is the rendering of complete sentences. Most TTS systems do quite well
when converting a single word into speech. However, when TTS systems begin to string words together into sentences,
they do not perform as well because human speech has a set of inflections, pitches, and rhythms. These characteristics of
human speech are called prosody.
There are several reasons that TTS engines are unsuccessful in matching the prosody of human speech. First, very little
of it is written down in the text. Punctuation marks can be used to estimate some prosody information, but not all. Much
of the inflection of a sentence is tied to subtle differences in the speech of individuals when they speak to each other-
interjections, racing to complete a thought, putting in a little added emphasis to make a point. These are all aspects of
human prosody that are rarely found in written text.
When you consider the complexity involved in rendering a complete thought or sentence, the current level of technology
in TTS engines is quite remarkable. Although the average output of TTS engines still sounds like a poor imitation of
Darth Vader, it is amazingly close to human speech.
One of the ways that the SAPI model attempts to provide added control to TTS engines is the inclusion of what are
called control tags in text that is to be spoken. These tags can be used to adjust the speed, pitch, and character of the
voice used to render the text. By using control tags, you can greatly improve the perceived performance of the TTS
engine.
The SAPI model defines 15 different control tags that can be used to modify the output of TTS engines. Microsoft
defined these tags but does not determine how the TTS engine will respond to them. It is acceptable for TTS engines that
comply with the TAPI model to ignore any and all control tags it does not understand. It is possible that the TTS engine
you install on your system will not respond to some or all of these tags. It is also possible that the TTS engine will
attempt to interpret them as part of the text instead of ignoring them. You will need to experiment with your TTS engine
to determine its level of compliance with the SAPI model.
Note
All of the examples in this section were created using the Microsoft Voice
TTS engine that ships with Microsoft Phone.
The voice character tags can be used to set high-level general characteristics of the voice. The SAPI model allows users
to select gender, dialect, accent, message context types, speaker's age, even the general mood of the speaker.
The phrase modification tags can be used to adjust the pronunciation at a word-by-word or phrase-by-phrase level. Users
can control the word emphasis, pauses, pitch, speed, and volume of the playback.
The low-level TTS tags deal with attributes of the TTS engine itself. Users can add comments to the text, control the
pronunciation of a word, turn prosody rules on and off, reset the engine to default settings, or even call a control tag
based on its own GUID (guaranteed unique identifier).
You add control tags to the text sent to the TTS engine by surrounding them with the backslash (\) character. For
example, to adjust the speed of the text playback from 150 to 200 words per minute, you would enter the \Spd=\
control tag. The text below shows how this looks:
Note
The following examples all use the TTSTEST.EXE program that is
installed in the BIN\ANSI.API or the BIN\UNICODE.API folder of the
SPEEchSDK folder. These folders were created when you installed the
Microsoft Speech SDK.
Before continuing, load the TTSTEST.EXE program from the SPEEchSDK\BIN\ANSI.API directory (Win95) or
the SPEEchSDK\BIN\UNICODE.API directory (WinNT). This program will be used to illustrate examples
throughout the chapter. After loading your program, press the Register button to start the TTS engine on your
workstation. Then press the Add Mode button to select a voice for playback. Finally, make sure
TTSDATAFLAG_TAGGED is checked. This informs the application that you will be sending control tags with your text.
Your screen should now look something like the one in Figure 18.1.
Note
Even if you do not have a copy of the software, you can still learn a lot by
reviewing the material covered in this section.
There are three control tags that allow you to alter the general character of the speaking voice. Microsoft has identified
several characteristics of playback voices that can be altered using control tags. However, your TTS engine may not
recognize all of them. The three control tags in this group are
The Chr tag allows you to set the general character of the voice. The syntax for the Chr tag is
\Chr=string[[,string...]]\
More than one characteristic can be applied at the same time. The default value is Normal. Others that are recognized
by the Microsoft Voice TTS engine are Monotone and Whisper. Additional characteristics suggested by Microsoft
are
To test the Chr tag, enter the text shown in Listing 18.1 into the input box of TTSTEST.EXE.
\chr="monotone"\
How are you today?
\chr="whisper"\
I am fine.
\chr="normal"\
Good to hear.
Each sentence will be spoken using a different characteristic. After entering the text, press the TextData button to hear
the results.
Another valuable control tag is Ctx, the context tag. You can use this tag to tell the TTS engine the context of the
message you are asking it to render. Like the Chr tag, the Ctx tag takes string as a parameter. Microsoft has defined
the strings in Table 18.1 for the context tag.
Setting the context helps the TTS engine better interpret the text. To test this, enter the text shown in Listing 18.2 into
the text box.
\ctx="Address"\
1204 W. 7th Street
Oak Ridge, TN
\ctx="E-Mail"\
BillGates@msn.com
\ctx="Unknown"\
129 W. First Avenue
When you press the TextData button to hear the results, you'll notice that the TTS engine automatically converts the
"W." to "West" when given the \Ctx="Address"\ tag but fails to do so when the \Ctx="Unknown"\ tag is used.
You'll also notice that the e-mail address is spoken using the phrase "Bill Gates at msn dot com" when the \Ctx="E-
Mail"\ tag is used.
The last voice character control tag is the Vce tag. This tag can be used to set several aspects of a voice in a single
control tag. The exact syntax of the Vce tag is
\Vce=chartype=string[[,chartype=string...]]\
Several character types can be set in a single call. Microsoft has defined six different character type classes. These
classes, along with their possible settings and brief descriptions, are shown in Table 18.2.
To test the Vce control tag, enter the text shown in Listing 18.3 and press TextData to hear the results.
\Vce=Speaker="Sidney"\
Hello there Peter.
\Vce=Speaker="Peter"\
Hi Sid. How are you?
\Vce=Speaker="Sidney"\
Not good really. Bad head cold.
You can use the Vce control tag to program the TTS engine to carry on a multiperson dialog.
The second set of control tags-the phrase modification tags-can be used to modify words or phrases within the message
stream. Phrase modification tags give you added control over TTS output. There are five phrase modification control
tags:
You can insert the \Emp\ tag before a word to force the TTS engine to give it added emphasis. Enter the text shown in
Listing 18.4 and press TextData to hear the results.
You can use the Pau tag to add pauses to the playback. The pause is measured in milliseconds. Here's an example of the
Pau tag syntax:
\Pau=1000\
To test the Pau tag, add two tags to the speech you entered from the previous example. Your text should now look like
the text in Listing 18.5.
Using the Pit Control Tag to Modify the Pitch of the Voice
The Pit control tag can be used to modify the base pitch of the voice. This base pitch is used to set the normal speaking
pitch level. The actual pitch hovers above and below this value as the TTS engine mimics human speech prosody. The
pitch is measured in hertz. There is a minimum and maximum pitch: the minimum pitch is 50 hertz and the maximum is
400 hertz.
Listing 18.6 shows modifications to the previous text adding \Pit\ control tags to the text.
\Pit=100\
I \Emp\told you never to go running in the street.
\pau=1000\ \Pit=200\
Didn't you \Emp\hear me?
\pau=2000\ \Pit=400\
You must listen to me when I tell you something \Emp\important.
\Pit=50\
Tip
Notice that the last line of Listing 18.6 shows a pitch tag setting the pitch
back to normal (\Pit=50\). This is done because the pitch setting does
not automatically revert to the default level after a message has been
spoken. If you want to return the pitch to its original level, you must do so
using the Pit control tag.
You can modify the playback speed of the TTS engine using the \Spd\ control tag. The speed is measured in words per
minute (wpm). The minimum value is 50wpm and the maximum is 250wpm. Setting Spd to 0 sets the slowest possible
speed. Setting Spd to -1 sets the fastest possible speed. Listing 18.7 shows additional modifications to the previous text.
Enter this text and press the TextData button to hear the results.
\Spd=150\
I \Emp\told you never to go running in the street.
\pau=1000\ \Spd=75\
Didn't you \Emp\hear me?
\pau=2000\ \Spd=200\
You must listen to me when I tell you something \Emp\important.
\Spd=150\
The Vol control tag can be used to adjust the base line volume of the TTS playback. The value can range from 0 (the
quietest) to 65535 (the loudest). The actual pitch hovers above and below the value set by Vol. Make the changes to the
text shown in Listing 18.8 and press TextData to hear the results.
\Spd=150\ \Vol=30000\
I \Emp\told you never to go running in the street.
Of the event low-level TTS control tags, only one is used frequently-the \Rst\ tag. This tag resets the control values to
those that existed at the start of the current session.
Note
With the exception of the \Rst\ tag, none of the other tags produced
noticeable results using the TTS engine that ships with Microsoft Voice. For
this reason, there are no examples for these control tags.
Now that you know how to modify the way the TTS engine processes input, you are ready to learn how to use grammar
rules to control the way SR engines behave.
Grammar Rules
The grammar of the SR engine controls how the SR engine interprets audio input. The grammar defines the objects for
which the engine will listen and the rules used to analyze the objects. SR engines require that one or more grammars be
loaded and activated before an engine can successfully interpret the audio stream.
As mentioned in earlier chapters, the SAPI model defines three types of SR grammars:
● Context-free grammars-Words are analyzed based on syntax rules instead of content rules. Interpretation is
based on the placement and order of the objects, not their meaning or context.
● Dictation grammars-Words are compared against a large vocabulary, a predefined topic (or context), and an
expected speaking style.
● Limited-domain grammars-This grammar is a cross between rule-based context-free and word-based dictation
grammars.
The context-free grammar format is the most commonly used format. It is especially good at interpreting command and
control statements from the user. Context-free grammars also allow a great deal of flexibility since the creation of a set
of rules is much easier than building and analyzing large vocabularies, as is done in dictation grammars. By defining a
small set of general rules, the SR engine can successfully respond to hundreds (or even thousands) of valid commands-
without having to actually build each command into the SR lexicon. The rest of this section deals with the design,
compilation, and testing of context-free grammars for the SAPI SR engine model.
The SAPI Context-Free Grammar (CFG) operates on a limited set of rules. These rules are used to analyze all audio
input. In addition to rules, CFGs also allow for the definition of individual words. These words become part of the
grammar and can be recognized by themselves or as part of a defined rule.
Note
Throughout the rest of this section, you will be using NOTEPAD.EXE (or
some other ASCII editor) to create grammar files that will be compiled
using the GRAMCOMP.EXE grammar compiler that ships with the Microsoft
Speech SDK. You will also need the SRTEST.EXE application that ships
with the Speech SDK to test your compiled grammars. Even if you do not
have the Microsoft Speech SDK, however, you can still learn a lot from this
material.
In SAPI CFGs, each defined word is assigned a unique ID number. This is done by listing each word, followed by a
number. Listing 18.9 shows an example.
Listing 18.9. Defining words for a CFG file.
//
// defining names
//
Lee = 101 ;
Shannon = 102 ;
Jesse = 103 ;
Scott = 104 ;
Michelle = 105 ;
Sue = 106 ;
Notice that there are spaces between each item on the line. The Microsoft GRAMCOMP.EXE program requires that each
item be separated by white space. Also note that a semicolon (;) must appear at the end of each definition.
Tip
If you are using the GRAMCOMP.EXE compiler, you are not required to
define each word and give it a number. The GRAMCOMP.EXE program
automatically assigns a number to each new word for you. However, it is a
good idea to predefine words to prevent any potential conflicts at compile
time.
The list of words can be as short or as long as you require. Keep in mind that the SR engine can only recognize words
that appear in the vocabulary. If you fail to define the word "Stop," you can holler Stop! to the engine as long as you like,
but it will have no idea what you are saying! Also, the longer the list, the more likely it is that the engine will confuse
one word for another. As the list increases in size, the accuracy of the engine decreases. Try to keep your lists as short as
possible.
Along with words, CFGs require rules to interpret the audio stream. Each rule consists of two parts-the rule name and the
series of operations that define the rule:
There are several possible operations within a rule. You can call another rule, list a set of recognizable words, or refer to
an external list of words. There are also several special functions defined for CFGs. These functions define interpretation
options for the input stream. There are four CFG functions recognized by the GRAMCOMP.EXE compiler:
When building a rule definition, you can tell the SR engine that only one of the items in the list is expected. Listing
18.10 shows how this is done.
<Names> = alt(
Scott
Wayne
Curt
)alt ;
The <Names> rule in Listing 18.10 defines three alternative names for the rule. This tells the SR engine that only one of
the names will be spoken at a single occurrence.
You can also define a rule that indicates the sequence in which words will be spoken. Listing 18.11 shows how you can
modify the <Names> rule to also include last names as part of the rule.
<Names> = alt(
Scott
seq( Scott Ivey )seq
Wayne
seq( Wayne Ivey )seq
Curt
seq( Curt Smith )seq
)alt ;
The <Names> rule now lists six alternatives. Three of them include two-word phrases that must be spoken in the proper
order to be recognized. For example, users could say Scott or Scott Ivey, and the SR engine would recognize the input.
However, if the user said Ivey Scott, the system would not understand the input.
You can define rules that show that some of the input is optional-that it may or may not occur in the input stream. The
opt() function can simplify rules while still giving them a great deal of flexibility. Listing 18.12 shows how to apply
the opt() function to the <Names> rule.
Listing 18.12. An example of the opt() rule function.
<Names> = alt(
seq( Scott opt( Ivey )opt )seq
seq( Wayne opt( Ivey )opt )seq
seq( Curt opt( Smith )opt )seq
)alt ;
The <Names> rule now has only three alternative inputs again. This time, each input has an optional last name to match
the first name.
The rep() rule function can be used to tell the SR engine to expect more than one of the objects within the context of
the rule. A good example would be the creation of a phone-dialing rule. First, you can define a rule that dials each phone
number (see Listing 18.13).
<Dial> = alt(
3215002
4975501
3336363
)alt ;
Listing 18.13 meets all the requirements of a well-formed rule, but it has some problems. First, SR engines are not very
good at recognizing objects such as "3336363" as individual words. Second, this list can easily grow to tens, even
hundreds, of entries. As the list grows, accuracy will decrease, especially since it is likely that several phone numbers
will sound alike.
Instead of defining a rule that contains all the phone numbers, you can define a rule using the rep() function that tells
the engine to listen for a set of numbers. Listing 18.14 is an improved version of the <Dial> rule.
<Dial> = alt(
seq( Dial rep( <Numbers> )rep )seq
)alt ;
<Numbers> = alt(
zero
one
two
three
four
five
six
seven
eight
nine
)alt ;
Now the <Dial> rule knows to wait for a series of numbers. This allows it to be used for any possible combination of
digits that can be used to dial a telephone number.
Tip
The <Dial> rule described here is still not very good. The SR engine has a
hard time interpreting long sets of numbers. It is better to define words that
will aid in the dialing of phone numbers. For example, Dial New York
Office is more likely to be understood than Dial 1-800-555-1212.
You can define a rule that uses the contents of a list built at run-time. This allows the SR engine to collect information
about the workstation (loadable applications, available Word documents, and so on) while the system is up and running
rather than having to build everything into the grammar itself. The list name is added to the rule surrounded by braces
({}). At run-time, programmers can use the SetList method of the Voice Menu object in the OLE library to create
and populate the list. Listing 18.15 shows how to build a rule that refers to a run-time list.
<RunProgram> allows the user to say "Run name" where name is one of the program names that was loaded into the
list at run-time.
Now that you know the basic building blocks used to create CFGs, it is time to build and compile actual grammar rules
using NOTEPAD.EXE and the GRAMCOMP.EXE grammar compiler that ships with the Microsoft Speech SDK. The first
step in the process is to define the general scope and function of the grammar. For example, you might want a grammar
that can handle typical customer requests for directions in a shopping center.
Once you define the scope and function of a grammar, you need to identify the words and rules needed to populate the
CFG. Since an SR engine can only recognize words it already knows, you must be sure to include all the words needed
to complete operations.
Tip
You do not, however, need to include all the possible words users may utter
to the SR engine. The software package that is using the SR engine should
have some type of error response in cases where the audio input cannot be
interpreted.
To use the shopping center example, you'd need a list of all the locations that users might request. This would include all
the stores, restaurants, major landmarks within the building, public services such as restrooms, exits, drinking fountains,
security office, and so on. Then you need to collect a set of typical phrases that you expect users to utter. Examples
might be "Where is the ....?" or "Show me ... on the map," or "How can I locate the ....?" After you have collected all this
material, you are ready to create the grammar.
Let's assume you have the job of building a workstation that will allow shoppers to ask directions in order to locate their
favorite shops within the mall. Listing 18.16 shows a list of the store names and some other major landmarks in the mall.
Load NOTEPAD.EXE and enter this information into a file called MALL.TXT.
// ********************************************************
// MALL GRAMMAR RULES
// ********************************************************
//
// Title: MALL.TXT
// Version: 1.0 - 05/16/96 (MCA)
//
// Site: Win95 SAPI
// Compiler: GRAMCOMP.EXE
//
// Desc: Used to direct customers to their favorite
// shops in the mall.
//
// ********************************************************
//
// define words
//
J = 9900 ;
C = 9901 ;
Penneys = 9902 ;
Sears = 9903 ;
Bobs = 9904 ;
Bagels = 9905 ;
Michelles = 9906 ;
Supplies = 9906 ;
The = 9907 ;
Sports = 9908 ;
Barn = 9909 ;
Security = 9910 ;
Office = 9911 ;
Main = 9912 ;
Food = 9913 ;
Court = 9914 ;
Shops = 9915 ;
Specialty = 9916 ;
Exits = 9917 ;
Restroom = 9918 ;
Fountain = 9919 ;
Next, you need to define a top-level rule that calls all other rules. This one rule should be relatively simple and provide
for branches to other more complicated rules. By creating branches, you can limit SR errors since the possible words or
phrases are limited to those defined in a branch. In other words, by creating branches to other rules, you limit the scope
of words and rules that must be analyzed by the SR engine at any one moment. This improves accuracy.
Listing 18.17 shows a top-level rule that calls several other possible rules. Add this to your MALL.TXT grammar file.
Listing 18.17. Adding the top-level rule to the MALL grammar file.
// **************************************
// Define starting rule
//
// This rule calls any one of the other
// internal rules.
//
<Start> = alt(
<_Locations>
<_TellMeWhere>
<_HowCanIFind>
<_ShowMe>
<_WhereIs>
)alt ;
Notice that each of the rules called by <Start> begins with an underscore (_). This underscore tells the compiler that
this is an internal rule and should not be exported to the user. The more exported rules the SR engine has to review, the
greater the chance of failure. It is a good idea to limit the number of exported rules to a bare minimum.
The first internal rule on the list is called <_Locations>. This rule contains a list of all the locations that customers
may ask about. Notice the use of seq() and opt() in the rule. This allows customers to ask for the same locations in
several different ways without having to add many items to the vocabulary. Enter the data shown in Listing 18.18 into
the MALL.TXT grammar file.
Listing 18.18. Adding the Locations rule.
// *************************************
// Define Locations rule
//
// This rule lists all possible locations
//
<_Locations> = alt(
// JC Penneys, Penneys
seq( opt( seq( J C )seq )opt Penneys )seq
// sears
Sears
// Main Office
seq( Main Office )seq
// Specialty Shops
seq( Specialty Shops )seq
// Exits
Exits
// Restroom
Restroom
// Fountain
Fountain
)alt ;
The last step is to build a set of query rules. These are rules that contain the questions customers will commonly ask of
the workstation. Each of these questions is really a short phrase followed by a store or location name. Listing 18.19
shows how you can implement the query rules defined in the <Start> rule.
Listing 18.19. Adding the query rules to the MALL.TXT grammar file.
// *************************************
// Define simple queries
//
// These rules respond to customer
// queries
//
<_TellMeWhere> = seq( Tell me where <_Locations> is )seq ;
<_HowCanIFind> = seq( How can I find opt( the )opt <_Locations> )
seq ;
<_ShowMe> = seq( Show me opt( where )opt <_Locations> opt( is )opt )
seq ;
<_WhereIs> = seq( Where is <_Locations> )seq ;
//
// eof
//
Notice the use of the opt() functions to widen the scope of the rules. These add flexibility to the grammar without
adding extra rules.
Note
Be sure to save the file as MALL.TXT before you continue on to the next
step.
After you have constructed the grammar file, you are ready to compile it into a binary form understood by the SAPI SR
engine.
Note
To do this, you need to use the GRAMCOMP.EXE program that ships with
the Speech SDK. You can find this program in the SPEEchSDK\BIN
folder that was created when you installed the Speech SDK.
The GRAMCOMP.EXE program is a command-line application with no defined window. To run the program, open an
MS-DOS window and move to the directory that contains the GRAMCOMP.EXE file. Then type the following on the
command line:
Warning
If you are using WinNT, do not include the /ansi portion of the
command. This is needed only for Win95 workstations that do not support
the default Unicode compilation mode.
You may need to include the directory path to locate the MALL.TXT file. Once the compiler is running, it will read in
the MALL.TXT file and compile it into binary format and save it as MALL.GRM. Your screen should look something like
the one in Figure 18.2.
You should get a message telling you that one rule has been exported (Start). If you receive error messages, return to
the MALL.TXT file to fix them and recompile. Once you complete compilation successfully, you are ready to test your
grammar using SRTEST.EXE.
You can test your new grammar by loading it into the SRTEST.EXE application that ships with the Speech SDK. The
ANSI version of the program can be found in the SPEEchSDK\BIN\ANSI.API. You can find the Unicode version in
\SPEEchSDK\BIN\UNICODE.API.
Once you load the grammar, you can use the same software to test the SR engine's response to your spoken queries.
When you first start the program, press the Add Mode button to select an engine mode. You should then see one or
more modes available. It does not matter which one you pick as long as it supports the same language you used to build
the grammar (see Figure 18.3).
After selecting the SR mode, you need to load the new MALL.GRM file. To do this, press the Rescan Files button
and enter the directory path that contains the MALL.GRM grammar file (see Figure 18.4).
You will see the MALL.GRM file appear in the list of available grammars. Double-click the name to load it into the SR
engine.
Next, you need to activate the MALL grammar. To do this, select the Grammar option button on the left side of the
screen and bring up the ISRGramCom tab of the form. Set the Rule combo box to Start and the Window combo box
to MainWnd and press Activate. The MALL.GRM grammar should activate, and your screen should look like the one
in Figure 18.5.
The status box at the bottom of the form should contain messages like the ones in Listing 18.20.
You are now ready to test the grammar by speaking to your system. The responses will appear in the status box at the
lower left of the form.
For example, ask your system the following question: How can I find Sears? You should see the application flash a few
status messages across the bottom of the screen and then return with the selected response. The message in the lower
portion of the screen should look like the one in Figure 18.6.
You can experiment with the grammar by speaking phrases and watching the response. You can also make changes to
your MALL.TXT file and recompile it to add new features or refine the grammar to meet your needs.
The Unicode versions of the SAPI model can support the use of the International Phonetic Alphabet (IPA) to aid in the
analysis and pronunciation of words. The IPA is a standardized set of symbols for documenting phonemes.
On IPA-supported TTS systems, the IPA values can be used to adjust the pronunciation of troublesome words. This is
done by associating the Unicode strings with the word in the dictionary. Some TTS engines will store this set of IPA
codes as a permanent part of the dictionary. In this way, the TTS system can be refined over time to handle difficult
words.
Note
Since the IPA system is only supported through Unicode, Win95 systems do
not support the IPA. You can check for IPA support on WinNT-based
speech systems by inspecting the TTSMODEINFO structure of TTS engines
and the ILexPronounce interface of the SR engine.
SR systems that support IPA will allow users to enter IPA codes into the SR lexicon as a way of teaching the system
how some words will sound. These IPA values are then used to match audio input to words in the SR engine's
vocabulary, thereby improving recognition performance.
The IPA defines a common set of English consonants and vowels along with a more complex set of phoneme sets that
are used to further describe English language sounds. Table 18.4 shows the list of IPA consonants and vowels with their
associated Unicode values.
The IPA system also defines a set of phonemes that describe the various complex sounds of a language. There are
several general categories of sounds. Each has its own set of Unicode characters associated with it. The basic sound
categories are
● Pulmonic obstruents-These are "stop" sounds in which the breath is obstructed in some way. Examples are
words like pig, fork, and chip.
● Pulmonic resonants-These are "stop" sounds in which the breath is not obstructed. Examples are words such as
mouse, look, rock, and with.
● Rounded vowels-These are sounds that are produced when the lips are rounded. Examples are boot and coat.
● Unrounded vowels-These are sounds that are produced without rounding the lips. Examples of unrounded
vowels are beef, father, and how.
Additional information on the IPA and its use can be found in the appendix section of the Microsoft Speech SDK
documentation.
Summary
In this chapter, you learned about three aspects of the SAPI interface that are usually not seen by the average user. You
learned the following:
You learned that Microsoft has defined 15 control tags and that they fall into three general categories:
● Voice character control tags-These are used to control the general character of the voice including gender,
mood, age, and so on.
● Phrase modification control tags-These are used to control the playback speed, volume, pitch, and word
emphasis.
● Low-level TTS control tags-These are used to inform the TTS engine of the correct pronunciation of words,
identify the part of speech, call special commands, and so on.
You learned that the SAPI model supports three types of grammar: context-free, dictation, and limited-domain. You
learned how to create your own context-free grammar with defined words and rules. You compiled and tested that
grammar, too.
You learned that the context-free grammar compiler supplied by Microsoft supports the definition of words, rules, and
external lists that can be filled at run-time. You also learned that there are four compiler functions:
You designed, coded, compiled, and tested a grammar that could be used to support a voice-activated help kiosk at a
shopping center.
Finally, you learned about the International Phonetic Alphabet (IPA) and how Unicode-based speech systems can use
IPA to improve TTS and SR engine performance.
Chapter 17
CONTENTS
❍ Using the Enable Property to Start and Stop the TTS Engine
● Summary
The Microsoft Speech SDK contains a set of OLE library files for implementing SAPI services using Visual Basic and
other VBA-compatible languages. In this chapter, you'll learn how to use the objects, methods, and properties in the OLE
library to add speech recognition (SR) and text-to-speech (TTS) services to your Windows applications.
Note
The Microsoft SDK supplies these files in the REDIST folder that is created
when you install the SDK. You should copy these files from the REDIST
folder into the WINDOWS\SYSTEM folder before you begin the examples in
this book.
You will first learn to use the Voice Text object to add TTS services to Visual Basic applications. Once you know
how to use the Voice Text object, you'll learn how to use the Voice Command and Voice Menu objects to add
SR services to Visual Basic applications.
If you have not yet done so, start Visual Basic 4.0 and begin a new project. You'll use Visual Basic to complete all the
examples in this section.
The Voice Text object is used to provide access to the text-to-speech services of the installed TTS engine. Using the
Voice Text object involves only a few simple steps. First, you must register your application with the SAPI services
on the workstation. Then you can send text to the Voice Text object for playback. You can use several other methods
to rewind, fast-forward, pause, and restart audio playback of the text message.
When using Visual Basic, you can use the Tools | References menu option to load the object library into the
Visual Basic design-time environment (see Figure 17.1).
Figure 17.1 : Loading the OLE Voice Text library into Visual Basic at design time.
The advantage of this method is that you can use the Visual Basic Object browser to view all the objects, methods, and
properties of the library at design time (see Figure 17.2).
Figure 17.2 : Using the Visual Basic Object Browser to inspect the Voice Text library.
Note
Other VBA-compliant languages (Excel, Word, Access, and so on) cannot
pre-load the object library. You can still use the SAPI OLE objects as
described here, but you will not be able to use the object browser to view
the object details at run-time.
Start a new Visual Basic project and add the following code to the general declarations section:
You'll use this variable throughout the project. This is the top-level object for all TTS services. Next you need to add
code to the Form_Load event to initialize the TTS object. Also, you need to add code to the Form_Unload event to
destroy the TTS object upon exit. Add the code shown in Listing 17.1 to your project.
Tip
It's always a good idea to destroy the object variable once you no longer
need it. Setting the object equal to Nothing clears memory of any
remaining references to the unused objects.
Once you create a valid TTS object, you must use the Register method to register your application with the TTS
engine. There are two parameters that you can use with the Register method. The first parameter allows you to select
the output device that the TTS engine will use for playback. You can leave this parameter empty in order to use the
default device. You can also enter the name of any other valid line device such as an attached telephone handset. The
second parameter is the name of the application that is requesting TTS services. This second parameter cannot be left
blank.
To test the Register method, add a new command button to the project, and set its Name property to cmdReg and its
caption to &Register. Then add the code shown in Listing 17.2 to the CmdReg_Click event.
Using the Enable Property to Start and Stop the TTS Engine
Once you have registered your application with the TTS engine, you must set the Enabled property to TRUE before
you can actually send text to the engine for output.
Add two new buttons to the form. Set the Name property and Caption property of one button to cmdEnable and
&Enable, respectively. Set the Name and Caption properties of the next button to cmdDisable and &Disable.
Then add the code shown in Listing 17.3.
Note
The Enabled property affects all applications using TTS services at your
workstation. Use this setting with caution. Changing the Enabled property
to FALSE will disable all TTS services registered on your pc. For this
reason, it is a good idea to check the Enabled property each time you
attempt to speak text.
Once you have registered your application with the TTS engine and set the Enabled property to TRUE, you can send
text to the engine for playback. The Speak method takes two parameters. The first parameter is the text to play back,
and the second parameter is a flag parameter that can be used to establish the statement type and queue priority of the
text being sent to the TTS engine.
Every text message sent to the TTS engine must be declared as one of seven types. The OLE Voice Text library has a set
of pre-defined constants that can be used to set the statement type of the Speak method option flag. These types are
shown in Table 17.1.
Warning
The flag parameter of the Speak method is not optional, but how the value
is used depends on the installed TTS engine. It is possible that setting this
flag to various values will not result in different inflections of voice during
playback.
The TTS engine is responsible for playing back messages sent in from all speech-enabled applications on your pc. Each
message sent to the TTS engine is played back in the order in which it was received. You can use the flag parameter to
control the priority level of the text message you send to the TTS engine. There are three priority levels for a message.
The OLE library has a set of pre-defined constants to match the priority values. Table 17.2 shows each of the priority
levels, their values, and a short description.
To test the Speak method, you need to add a command button to the form along with a text box and several option
buttons with two frame controls. Refer to Figure 17.3 and Table 17.3 as you arrange the controls on the form.
Figure 17.3 : Laying out the controls for adding the Speak method to the project.
Table 17.3. Control table for the Voice Text Demo form.
Control Property Setting
VB.Frame Name fraPriority
Caption "Priority"
Height 615
Left 3120
TabIndex 13
Top 3120
Width 3975
VB.OptionButton Name OptPriority
Caption "&Very High"
Height 255
Index 2
Left 2760
TabIndex 16
Top 240
Width 1095
VB.OptionButton Name OptPriority
Caption "&High"
Height 255
Index 1
Left 1440
TabIndex 15
Top 240
Width 1215
VB.OptionButton Name OptPriority
Caption "&Normal"
Height 255
Index 0
Left 240
TabIndex 14
Top 240
Value -1 'True
Width 1215
VB.Frame Name fraType
Caption "Statement Types"
Height 1095
Left 3120
TabIndex 5
Top 1920
Width 3975
VB.OptionButton Name optType
Caption "Warning"
Height 255
Index 6
Left 2760
TabIndex 12
Top 240
Width 975
VB.OptionButton Name optType
Caption "Statement"
Height 255
Index 5
Left 1440
TabIndex 11
Top 720
Value -1 'True
Width 1095
VB.OptionButton Name optType
Caption "Spreadsheet"
Height 255
Index 4
Left 1440
TabIndex 10
Top 480
Width 1215
VB.OptionButton Name optType
Caption "Reading"
Height 255
Index 3
Left 1440
TabIndex 9
Top 240
Width 975
VB.OptionButton Name optType
Caption "Question"
Height 255
Index 2
Left 240
TabIndex 8
Top 720
Width 1095
VB.OptionButton Name optType
Caption "&Numbers"
Height 255
Index 1
Left 240
TabIndex 7
Top 480
Width 1095
VB.OptionButton Name optType
Caption "Command"
Height 255
Index 0
Left 240
TabIndex 6
Top 240
Width 1095
VB.TextBox Name txtSpeak
Height 1575
Left 3120
MultiLine -1 'True
ScrollBars 2 'Vertical
TabIndex 4
Top 240
Width 3975
VB.CommandButton Name cmdSpeak
Caption "&Speak"
Height 495
Left 1680
TabIndex 3
Top 240
Width 1215
Note that the option buttons are built as members of control arrays. You'll use this array when you add the code behind
the Speak command button. Now add the code from Listing 17.4 to the cmdSpeak_Click event.
This is the minimal set of commands needed to implement speech services. You can provide speech services by simply
registering your application, enabling the engine, and executing the Speak method. However, there are additional
properties and methods that you can use to control the behavior of the TTS engine.
You can adjust the speed of audio playback using the Speed property. The speed of audio playback is measured in
words per minute (wpm). The default speed of playback is 150wpm. Setting the Speed property to 0 causes the engine
to speak at the lowest possible speed. Setting it to -1 results in the highest possible speaking speed.
You can use the Speed property to both read and write the playback value. Add a single command button to the form.
Set its Name property to cmdSpeed and its Caption property to S&peed. Then add the code shown in Listing 17.5 to
the cmdSpeed_click event.
When you save and run the project, press the Speed button (be sure to press Register and Enabled first). You'll
see a message box showing the speed limits of your TTS engine. Your message box should look like the one in Figure
17.4.
To test the effects of changing playback speed, add two buttons, one label control, and one text box control to the form.
Use Table 17.4 and Figure 17.5 as guides when adding these controls.
You can also add the ability to pause, rewind, fast-forward, and restart TTS playback. The TTS fast-forward and rewind
methods cause the engine to move the playback pointer approximately one sentence. Refer to Table 17.5 and Figure 17.5
to add the playback button controls to the form.
After adding the command button array and the frame control to the form, add the code shown in Listing 17.6 behind the
cmdAudio_Click event.
Listing 17.6. Adding code behind the cmdAudio_Click event.
You can use the IsSpeaking property to check the status of the TTS engine. The IsSpeaking property returns
TRUE if the engine is currently speaking a message, FALSE when the engine is idle. You must check the property on a
regular basis in order to get up-to-date status reports. The best way to do this is in a timed loop. In Visual Basic, this can
be done using the timer control.
Add three new controls to the form: a new frame control (Name=fraIsSp, Caption=Is Speaking), a new label
control (Name=lblIsSpeaking, Caption=False), and a timer control. Refer to Figure 17.6 for the position and
size of the controls.
After you place the controls on the form, add the code shown in Listing 17.7 to the Timer1_Timer event and the
cmdEnabled_Click event.
After saving and running the program, you can register and enable the TTS services and enter text to be spoken. When
you press the Speak button, you can watch the Is Speaking label change from False to True and back again to
False.
If you are using Visual Basic 4.0 as the platform for the OLE Voice Text library, you can use the Callback property to
establish an asynchronous callback from the TTS engine to your Visual Basic program. This link is established by
adding a class module to your Visual Basic program. This class module must contain two methods:
SpeakingStarted and SpeakingDone. These methods will be automatically invoked by the TTS engine at the
appropriate time.
To establish a TTS callback in Visual Basic 4.0, first add a class module. Set its Name property to clsCallBack, its
Instance property to MultiUse, Creatable, and its Public property to TRUE. Then add the code in Listing
17.8.
The code in Listing 17.8 creates the class and methods required to link the TTS engine with your Visual Basic
application. The next step is to initialize the Callback property. In this step, you are telling the TTS engine the name
of the project and the name of the class module that has the two required methods.
To do this, add another command button, frame control, and label control to the form. Use Figure 17.7 and Table 17.6 to
place and size the controls.
After adding the controls, place the code shown in Listing 17.9 behind the CallBack_Click event.
Listing 17.9. Adding the code to the CallBack_Click event.
This code sets the Callback property and informs the TTS engine of the application and class module to use for
sending notification messages.
Warning
Be sure to include the proper application name (found by inspecting the
ProjectName value on the Tools | Project command dialog box)
and the proper class name (check the Name property of the class module).
Failure to do this will result in OLE Automation error 440.
After adding the callback controls, you can save the form (VTEXT.FRM) and the project (VTEXT.VBP) and run the
application. After registering and enabling the application, establish the callback by pressing the CallBack button.
Then, when you have the TTS engine speak text in the text box, you'll see the TTS engine status messages appear in the
Callback frame.
You now know how to use all the methods and properties of the Voice Text object. Next, you'll learn how to use the
Voice Command objects to make Visual Basic applications listen to a human voice and translate the audio input into
computer commands from a menu.
The Microsoft SR engine is implemented in an OLE object library called VCAUTO.TLB. This file can be added to the
Visual Basic 4.0 design-time library through the Tools | References menu, and then you can view the library's
objects, methods, and properties using the View | Object Browser option on the main menu.
● The Voice Command object-This is the top-level object used to initialize and control the SR engine.
● The Voice Menu object-This is the only child object of the Voice Command object. It is used to add and
delete voice menu items. Each menu item represents a command recognized by the SR engine.
Note
In order to use these examples, you need to have the Microsoft Speech SDK
installed on your machine and have the files from the REDIST folder
(created when you install the Speech SDK) copied to your WINDOWS
\SYSTEM folder. You also need to have an SR engine installed and
running. All the examples here were created using the Microsoft Voice SR/
TTS engine that ships with Microsoft Phone.
The first step in the process is the creation and initialization of a programming object variable. This variable will act as
the top-level object for accessing the SR engine.
Load Visual Basic and start a new project. You need to create two form-level variables that will be used throughout the
program. You also need to add code to the Form_Load and Form_Unload events. Listing 17.10 shows the code for
these events and the declaration section.
Option Explicit
………
After creating the Voice Command object, you can use the Register method to connect the object to the installed
SR engine. Add a command button to the form. Set its Name property to cmdReg and its Caption property to
&Register. Then add the code shown in Listing 17.11.
Listing 17.11. Adding code to the cmdReg_Click event.
The Register method of the Voice Command object takes only one parameter: the device identifier. Passing an
empty string results in registering the default input device (usually an attached microphone).
Before the SR engine will attempt to interpret audio input, you must "wake it up" by setting the Awake property to
TRUE. You can tell the SR engine to stop listening for commands by setting the Awake property to FALSE.
Add two buttons to the form, named CmdAwake and CmdSleep. Set their Caption properties to &Awake and
&Sleep, respectively. Finally, add a label control named lblAwake. This label will contain a status message telling
you when the SR engine is listening to you. Then add the code shown in Listing 17.12.
Each time you press the Awake button or the Sleep button, the contents of the lblAwake control are updated.
Creating the Menu Object
After establishing the TTS connection and enabling it, you must create a menu object and fill it with menu commands.
Then you can activate the menu, and it will respond to your voice.
First, add two new buttons to the form. Set their Name properties to cmdCreate and cmdAdd. Set their Caption
properties to &Create Menu and &Add Menu. Next, add the code shown in Listing 17.13 behind the
cmdCreate_Click event.
Creating a menu involves setting several parameters. First, you need to tell the SR engine the name of the application
that is requesting services. This must be a unique name, since all responses will be directed by the SR engine, based on
this published application name. Next, you need to initialize the MenuState parameter. The menu state is much like
the level of a menu. It groups menu items together. All the menus in the same group are considered at the same time
when the SR engine is analyzing audio input.
The lMenuLangID helps the engine interpret user input. Most SR engines support only one language. Setting this
parameter tells the SR engine what language you plan to speak. You can set the values using the pre-defined Visual
Basic 4.0 constants, or by looking up the values in the Visual Basic help files under "Collating Order." Table 17.7 shows
the possible language constants in Visual Basic.
Note
If you are using Visual Basic 4.0, you can find these constants using the
Object Browser. Once you find them, make sure you use only the language
portion of the constant. SAPI needs only the language number, not the
country code and other items stored in the collating-order constants.
The next value is the Dialect parameter. This value can control the character of the spoken word. It is also possible
that the SR engine does not use this parameter. A NULL string will use the default dialect built into the speech engine.
Lastly, you need to indicate the type of menu you wish to work with. The SAPI model defines three ways to create new
menus and ways to open existing menus. Table 17.8 shows the various versions of the MenuType parameter.
Creating the empty menu is only half of the process. Creating the menu object does not populate that menu with
commands. You must add voice command items to the new menu using the Add method of the Voice Menu object.
Once you have created the Voice Menu object, you can start adding actual commands to the menu. This is done with
the Add method of the Voice Menu object. The Add method takes the four parameters shown in Table 17.9.
The identifier parameter must be a unique number. This value is returned when the SR engine recognizes the
spoken command in the command parameter. The category parameter is used to organize lists of commands. This is
similar to using "File," "Edit," or "Help"-type menu categories. The description parameter is used only for
comment purposes and does not affect the performance of the SR engine at all.
To add some commands to the menu you created earlier, add a new button to the project. Set its Name property to
cmdAdd and its Caption property to Add Items. Then add the code shown in Listing 17.14.
Your form should look something like the one in Figure 17.8.
There are two other steps to completing the process of adding commands to a menu. First, all commands added to a
menu are global unless they are assigned a window handle. Global menus can be called at any time during a Windows
session, regardless of what window is active or what programs are loaded. If a menu command is assigned to a particular
window, it will only be available when the window is loaded and has focus.
Tip
It is a good idea to limit the use of global menu commands. The more global
commands you have registered, the more analysis will be required before
the SR engine can identify your command. Additional commands also
increase the error rate of the SR engine.
After entering the code from Listing 17.14, save and run the project. Press Register and Awake; then press Create
Menu and Add Items. This will add the four items to the list of available voice commands. Figure 17.9 shows a list of
the available voice commands. You can see the new commands added by the VCMD application at the end of the
command list.
Note
Figure 17.9 shows the output of the "What Can I Say?" voice command for
Microsoft Voice. If you are using another SR/TTS engine, you may have a
different method for viewing the active voice commands.
At this point, you have created a new voice command menu and added new commands that can be recognized by the
engine. However, you still need to add additional code to get the application to respond to the menu commands
themselves.
Once you have registered commands with the SAPI system, you need to have a way to detect when commands have
been spoken and to react to them accordingly. To do this, you need to check the CommandSpoken property of the
Voice Command object.
The CommandSpoken property will contain the identifier number of the menu command the SR engine has recognized.
You can then use this number to execute program code.
You need to regularly check the CommandSpoken property using a timer loop. To do this, you need to add one timer
control and three label controls to the form. After placing the timer control on the form, add the three label controls.
Name the first one lblResults and set its BorderStyle property to Fixed Single and its Alignment
property to Centered. Set the name of the second label control to lblStatus and give it the same properties as the
lblResults control. Finally, set the name of the third label control to lblPolling with the Caption set to
Polling Results. Refer to Figure 17.10 for sizing and placement of these three controls.
Figure 17.10 : Adding the controls for polling the CommandSpoken property.
Now add two new lines of code to the Form_Load event. This code will start the timer cycle (see Listing 17.15).
Finally, you need to add code to the Timer1_Timer event. This code will fire each time the timer interval loop
reaches zero. Here you can test the value of CommandSpoken and respond according to the number returned. Add the
code shown in Listing 17.16.
As soon as you get a non-zero value, you can check this value against a list of known identifier numbers and then fire off
your own code based on the returned value. In this case, you will update the lblStatus box to show the command
value returned, and the lblResults box will be updated to show the action to be taken.
You can test this by saving and running the project. After pressing Register, Awake, Create Menu, and Add
Items, you will be able to speak any of the four registered commands (Line 1 through Line 4), and you'll see the
lblResults label reflect the spoken command. In a real program, this code could fire off new forms, load files, save
data to disk, and so on.
In Visual Basic 4.0, you establish a callback notification using a class module. This is a better method for receiving
information about the spoken commands than polling the CommandSpoken property. To create a callback link between
SAPI and your Visual Basic program, you must first add a class module to the project and then add two subroutines:
To add these elements, add a new class module to your project (Insert | Class Module) and set its Name
property to clsCallBack. Also set its Instancing property to Creatable, Multiuse and its Public
property to TRUE. Then add the code shown in Listing 17.17.
End Function
Notice that the CommandRecognize function returns two parameters. The first one is the actual command string
spoken by the user. The second is the identifier number of the command. The single line of code added to the routine
will update a label control with the results of the spoken command.
The CommandOther function allows you to trap commands that were meant for other applications. In this way, you
can monitor all voice commands from a single location. For now, you do not need to add any code to this routine.
Next, you need to add two more label controls and one button control to the project. Add a label control and set its Name
property to lblCallBack, its Alignment property to Centered, and its BorderStyle property to Fixed
Single. Set the name of the second label control to lblCBResults and its Caption to CallBack Results.
Now add a command button to the project with its Name property set to cmdCallBack and its Caption set to
CallBack.
Finally, you need to add code behind the cmdCallBack_Click event to actually register the callback notification.
Listing 17.18 shows you how to do this.
Now save and run the project. You'll see the lblCallBack label show the exact command spoken along with the
identifier number. This will match the information shown in the lblResults box.
The Voice Menu object allows you to create a list of items to be applied to a single command. For example, you could
create a command that displays a customer address (such as Show Smith and Sons). But what if you wanted to show
every customer address? Instead of creating a menu command for each address, you can create a list of customers and
then apply that list to a special command string that has the list name as a part of the command (for example, Show
<CustomerName>).
You can create lists using the ListSet method of the Voice Menu object. The ListSet method accepts two
parameters. The first is the name of the list. The second parameter is the list of items, each separated by Chr(0).
To test this, add a new command button to the form. Set its Name property to cmdList and its Caption to Make
List. Then add the code shown in Listing 17.19.
Notice that you must first create a valid menu command that refers to the list. Also, it is a good idea to set the Active
property to FALSE while you edit a menu and set it back to TRUE when you are done.
Since you have added a new command to the menu, you also need to update the Timer1_Timer event to look for the
new command. Modify the code in Timer1_Timer to match the code in Listing 17.20.
Now save and run the project. After starting the SR engine (press Register, Awake, Create Menu, Add Items,
and CallBack), press the Make List button to add the new commands to the list. At this point, you should be able to
view the command list from Microsoft Voice by invoking the "What Can I Say?" command. Your screen should look
something like the one in Figure 17.11.
Now when you speak the command Show Lee, you'll see the command appear in the lblCallBack window (see
Figure 17.12).
Notice that the callback window shows the complete spoken command (that is, both the command and the list item), but
the CommandSpoken window just displays the word Show. The CommandSpoken property can return only the
identifier of the command recognized by the SR engine, not the actual command itself. This means that if you plan to use
lists as part of your voice commands, you will have to use the Callback property and class module to return the
complete spoken command.
Tip
Since you must use the Callback property to retrieve the complete results
of spoken list commands, you will not be able to use lists in other VBA-
compatible languages that do not allow the creation of class modules. As of
this writing, only Visual Basic 4.0 allows the creation of class modules.
You can remove commands from the Voice Menu object using the Remove method. It is a good idea to remove any
commands that are no longer needed. The fewer commands the SR engine has to deal with, the faster and more accurate
the engine will be.
Add one more button to the form. Set its Name property to cmdRemove and its Caption property to Remove. Now
add the code shown in Listing 17.21 to the cmdRemove_Click event.
Listing 17.21. Adding code to the cmdRemove_Click event.
Tip
In this code example, you are creating a temporary menu. This menu will be
removed automatically when the program exits. It is still a good practice to
remove old menu items, because these items take up storage space and can
cause slower recognition.
Summary
In this chapter, you learned how to use the SAPI OLE objects to create real, working TTS and SR Windows programs
using Visual Basic 4.0. You learned that there are two OLE libraries for use with Visual Basic 4.0 and all other VBA-
compatible languages:
The Voice Text library provides TTS services and the Voice Command library provides SR services.
You learned how to use the Voice Text object to register and enable the TTS engine and how to send text to the
engine for playback. You also learned how to adjust the speed of playback and how to add rewind, pause, fast-forward,
and resume options to TTS playback. Finally, you learned how to use the IsSpeaking property in a timer loop and the
Callback property to get notification on the status of the TTS engine.
You learned how to use the Voice Command object and the Voice Menu object to register and awaken the SR
engine. You also learned how to create and populate command menus and how to use the CommandSpoken and
Callback properties to return the identifier of the command spoken and to respond by executing code associated with
the command. You also learned how to create command lists to allow users to speak a single command with replaceable
parameters.
In the next chapter, you'll learn more about command lists and parameters used by the grammar engine that underlies the
Speech system. You'll also learn how to use control tags to improve TTS engine playback and about the International
Phonetic Alphabet and how it is used to improve both SR and TTS services.
Chapter 16
SAPI Basics
CONTENTS
● SAPI Hardware
❍ General Hardware Requirements
● Technology Issues
❍ SR Techniques
❍ SR Limits
❍ TTS Techniques
❍ TTS Limits
This chapter covers a handful of issues that must be addressed when designing and installing SR/TTS applications,
including hardware requirements, and the state of current SR/TTS technology and its limits. The chapter also includes
some tips for designing your SR/TTS applications.
SR/TTS applications can be resource hogs. The section on hardware shows you the minimal, recommended, and
preferred processor and RAM requirements for the most common SR/TTS applications. Of course, speech applications
also need special hardware, including audio cards, microphones, and speakers. In this chapter, you'll find a general list of
compatible devices, along with tips on what other options you have and how to use them.
You'll also learn about the general state of SR/TTS technology and its limits. This will help you design applications that
do not place unrealistic demands on the software or raise users' expectations beyond the capabilities of your application.
Finally, this chapter contains a set of tips and suggestions for designing and implementing SR/TTS services. You'll learn
how to design SR and TTS interfaces that reduce the chance of engine errors, and increase the usability of your
programs.
When you complete this chapter, you'll know just what hardware is needed for speech systems and how to design
programs that can successfully implement SR/TTS services that really work.
SAPI Hardware
Speech systems can be resource intensive. It is especially important that SR engines have enough RAM and disk space to
respond quickly to user requests. Failure to respond quickly results in additional commands spoken into the system. This
has the effect of creating a spiraling degradation in performance. The worse things get, the worse things get. It doesn't
take too much of this before users decide your software is more trouble than it's worth!
Text-to-speech engines can also tax the system. While TTS engines do not always require a great deal of memory to
operate, insufficient processor speed can result in halting or unintelligible playback of text.
For these reasons, it is important to establish clear hardware and software requirements when designing and
implementing your speech-aware and speech-enabled applications. Not all pcs will have the memory, disk space, and
hardware needed to properly implement SR and TTS services. There are three general categories of workstation
resources that should be reviewed:
The following three sections provide some general guidelines to follow when establishing minimal resource
requirements for your applications.
Speech systems can tax processor and RAM resources. SR services require varying levels of resources depending on the
type of SR engine installed and the level of services implemented. TTS engine requirements are rather stable, but also
depend on the TTS engine installed.
The SR and TTS engines currently available for SAPI systems usually can be successfully implemented using as little as
a 486/33 processor chip and an additional 1MB of RAM. However, overall pc performance with this configuration is
pretty poor and is not recommended. A good suggested processor is a Pentium processor (P60 or better) with at least
16MB of total RAM. Systems that will be supporting dictation SR services require the most computational power. It is
not unreasonable to expect the workstation to use 32MB of RAM and a P100 or higher processor. Obviously, the more
resources, the better the performance.
In general, SR systems that implement command and control services will only need an additional 1MB of RAM (not
counting the application's RAM requirement). Dictation services should get at least another 8MB of RAM-preferably
more. The type of speech sampling, analysis, and size of recognition vocabulary all affect the minimal resource
requirements. Table 16.1 shows published minimal processor and RAM requirements of speech recognition services.
TTS engines do not place as much of a demand on workstation resources as SR engines. Usually TTS services only
require a 486/33 processor and only 1MB of additional RAM. TTS programs themselves are rather small-about 150K.
However, the grammar and prosody rules can demand as much as another 1MB depending on the complexity of the
language being spoken. It is interesting to note that probably the most complex and demanding language for TTS
processing is English. This is primarily due to the irregular spelling patterns of the language.
Most TTS engines use speech synthesis to produce the audio output. However, advanced systems can use diphone
concatenation. Since diphone-based systems rely on a set of actual voice samples for reproducing written text, these
systems can require an additional 1MB of RAM. To be safe, it is a good idea to suggest a requirement of 2MB of
additional RAM, with a recommendation of 4MB for advanced TTS systems.
The general software requirements are rather simple. The Microsoft Speech API can only be implemented on Windows
32-bit operating systems. This means you'll need Windows 95 or Windows NT 3.5 or greater on the workstation.
Note
All the testing and programming examples covered in this book have been
performed using Windows 95. It is assumed that Windows NT systems will
not require any additional modifications.
The most important software requirements for implementing speech services are the SR and TTS engines. An SR/TTS
engine is the back-end processing module in the SAPI model. Your application is the front end, and the SPEEch.DLL
acts as the broker between the two processes.
The new wave of multimedia pcs usually has SR/TTS engines as part of their initial software package. For existing pcs,
most sound cards now ship with SR/TTS engines.
Microsoft's Speech SDK does not include a set of SR/TTS engines. However, Microsoft does have an engine on the
market. Their Microsoft Phone software system (available as part of modem/sound card packages) includes the
Microsoft Voice SR/TTS engine. You can also purchase engines directly from third-party vendors.
Note
Refer to appendix B, "SAPI Resources," for a list of vendors that support
the Speech API. You can also check the CD-ROM that ships with this book
for the most recent list of SAPI vendors. Finally, the Microsoft Speech SDK
contains a list of SAPI engine providers in the ENGINE.DOC file.
Just about any sound card can support SR/TTS engines. Any of the major vendors' cards are acceptable, including Sound
Blaster and its compatibles, Media Vision, ESS technology, and others. Any card that is compatible with Microsoft's
Windows Sound System is also acceptable.
Many vendors are now offering multifunction cards that provide speech, data, FAX, and telephony services all in one
card. You can usually purchase one of these cards for about $250-$500. By installing one of these new cards, you can
upgrade a workstation and reduce the number of hardware slots in use at the same time.
A few speech-recognition engines still need a DSP (digital signal processor) card. While it may be preferable to work
with newer cards that do not require DSP handling, there are advantages to using DSP technology. DSP cards handle
some of the computational work of interpreting speech input. This can actually reduce the resource requirements for
providing SR services. In systems where speech is a vital source of process input, DSP cards can noticeably boost
performance.
SR engines require the use of a microphone for audio input. This is usually handled by a directional microphone
mounted on the pc base. Other options include the use of a lavaliere microphone draped around the neck, or a headset
microphone that includes headphones. Depending on the audio card installed, you may also be able to use a telephone
handset for input.
Most multimedia systems ship with a suitable microphone built into the pc or as an external device that plugs into the
sound card. It is also possible to purchase high-grade unidirectional microphones from audio retailers. Depending on the
microphone and the sound card used, you may need an amplifier to boost the input to levels usable by the SR engine.
The quality of the audio input is one of the most important factors in successful implementation of speech services on a
pc. If the system will be used in a noisy environment, close-talk microphones should be used. This will reduce
extraneous noise and improve the recognition capabilities of the SR engine.
Speakers or headphones are needed to play back TTS output. In private office spaces, free-standing speakers provide the
best sound reproduction and fewest dangers of ear damage through high-levels of playback. However, in larger offices,
or in areas where the playback can disturb others, headphones are preferred.
Tip
As mentioned earlier in this chapter, some systems can also provide audio
playback through a telephone handset. Conversely, the use of free-standing
speakers and a microphone can be used successfully as a speaker-phone
system.
Technology Issues
As advanced as SR/TTS technology is, it still has its limits. This section covers the general technology issues for SR and
TTS engines along with a quick summary of some of the limits of the process and how this can affect perceived
performance and system design.
SR Techniques
Speech recognition technology can be measured by three factors:
● Word selection
● Speaker dependence
● Word analysis
Word selection deals with the process of actually perceiving "word items" as input. Any speech engine must have some
method for listening to the input stream and deciding when a word item has been uttered. There are three different
methods for selecting words from the input stream. They are:
● Discrete speech
● Word spotting
● Continuous speech
Discrete speech is the simplest form of word selection. Under discrete speech, the engine requires a slight pause between
each word. This pause marks the beginning and end of each word item. Discrete speech requires the least amount of
computational resources. However, discrete speech is not very natural for users. With a discrete speech system, users
must speak in a halting voice. This may be adequate for short interactions with the speech system, but rather annoying
for extended periods.
A much more preferred method of handling speech input is word spotting. Under word spotting, the speech engine
listens for a list of key words along the input stream. This method allows users to use continuous speech. Since the
system is "listening" for key words, users do not need to use unnatural pauses while they speak. The advantage of word
spotting is that it gives users the perception that the system is actually listening to every word while limiting the amount
of resources required by the engine itself. The disadvantage of word spotting is that the system can easily misinterpret
input. For example, if the engine recognizes the word run, it will interpret the phrases "Run Excel" and "Run Access" as
the same phrase. For this reason, it is important to design vocabularies for word-spotting systems that limit the
possibility of confusion.
The most advanced form of word selection is the continuous speech method. Under continuous speech, the SR engine
attempts to recognize each word that is uttered in real time. This is the most resource-intensive of the word selection
methods. For this reason, continuous speech is best reserved for dictation systems that require complete and accurate
perception of every word.
The process of word selection can be affected by the speaker. Speaker dependence refers to the engine's ability to deal
with different speakers. Systems can be speaker dependent, speaker independent, or speaker adaptive. The disadvantage
of speaker-dependent systems is that they require extensive training by a single user before they become very accurate.
This training can last as much as one hour before the system has an accuracy rate of over 90 percent. Another drawback
to speaker-dependent systems is that each new user must re-train the system to reduce confusion and improve
performance. However, speaker-dependent systems provide the greatest degree of accuracy while using the least amount
of computing resources.
Speaker-adaptive systems are designed to perform adequately without training, but they improve with use. The
advantage of speaker-adaptive systems is that users experience success without tedious training. Disadvantages include
additional computing resource requirements and possible reduced performance on systems that must serve different
people.
Speaker-independent systems provide the greatest degree of accuracy without performance. Speaker-independent
systems are a must for installations where multiple speakers need to use the same station. The drawback of speaker-
independent systems is that they require the greatest degree of computing resources.
Once a word item has been selected, it must be analyzed. Word analysis techniques involve matching the word item to a
list of known words in the engine's vocabulary. There are two methods for handling word analysis: whole-word matching
or sub-word matching. Under whole-word matching, the SR engine matches the word item against a vocabulary of
complete word templates. The advantage of this method is that the engine is able to make an accurate match very
quickly, without the need for a great deal of computing power. The disadvantage of whole-word matching is that it
requires extremely large vocabularies-into the tens of thousands of entries. Also, these words must be stored as spoken
templates. Each word can require as much as 512 bytes of storage.
An alternate word-matching method involves the use of sub-words called phonemes. Each language has a fixed set of
phonemes that are used to build all words. By informing the SR engine of the phonemes and their representations it is
much easier to recognize a wider range of words. Under sub-word matching, the engine does not require an extensive
vocabulary. An additional advantage of sub-word systems is that the pronunciation of a word can be determined from
printed text. Phoneme storage requires only 5 to 20 bytes per phoneme. The disadvantage of sub-word matching is that is
requires more processing resources to analyze input.
SR Limits
It is important to understand the limits of current SR technology and how these limits affect system performance. Three
of the most vital limitations of current SR technology are:
● Speaker identification
● Input recognition
● Recognition accuracy
The first hurdle for SR engines is determining when the speaker is addressing the engine and when the words are
directed to someone else in the room. This skill is beyond the SR systems currently on the market. Your program must
allow users to inform the computer that you are addressing the engine. Also, SR engines cannot distinguish between
multiple speakers. With speaker-independent systems, this is not a big problem. However, speaker-dependent systems
cannot deal well in situations where multiple users may be addressing the same system.
Even speaker-independent systems can have a hard time when multiple speakers are involved. For example, a dictation
system designed to transcribe a meeting will not be able to differentiate between speakers. Also, SR systems fail when
two people are speaking at the same time.
SR engines also have limits regarding the processing of identified words. First, SR engines have no ability to process
natural language. They can only recognize words in the existing vocabulary and process them based on known grammar
rules. Thus, despite any perceived "friendliness" of speech-enabled systems, they do not really understand the speaker at
all.
SR engines also are unable to hear a new word and derive its meaning from previously spoken words. The system is
incapable of spelling or rendering words that are not already in its vocabulary.
Finally, SR engines are not able to deal with wide variations in pronunciation of the same word. For example, words
such as either (ee-ther or I-ther) and potato (po-tay-toe or po-tah-toe) can easily confuse the system. Wide variations in
pronunciation can greatly reduce the accuracy of SR systems.
Recognition accuracy can be affected by regional dialects, quality of the microphone, and the ambient noise level during
a speech session. Much like the problem with pronunciation, dialect variations can hamper SR engine performance. If
your software is implemented in a location where the common speech contains local slang or other region-specific
words, these words may be misinterpreted or not recognized at all.
Poor microphones or noisy office spaces also affect accuracy. A system that works fine in a quiet, well-equipped office
may be unusable in a noisy facility. In a noisy environment, the SR engine is more likely to confuse similar-sounding
words such as out and pout, or in and when. For this reason it is important to emphasize the value of a good microphone
and a quiet environment when performing SR activities.
TTS Techniques
TTS engines use two different techniques for turning text input into audio output-synthesis or diphone concatenation.
Synthesis involves the creation of human speech through the use of stored phonemes. This method results in audio output
that is understandable, but not very human-like. The advantage of synthesis systems is that they do not require a great
deal of storage space to implement and that they allow for the modification of voice quality through the adjustment of
only a few parameters.
Diphone-based systems produce output that is much closer to human speech. This is because the system stores actual
human speech phoneme sets and plays them back. The disadvantage of this method is that it requires more computing
and storage capacity. However, if your application is used to provide long sessions of audio output, diphone systems
produce a speech quality much easier to understand.
TTS Limits
TTS engines are limited in their ability to re-create the details of spoken language, including the rhythm, accent, and
pitch inflection. This combination of properties is call the prosody of speech. TTS engines are not very good at adding
prosody. For this reason, listening to TTS output can be difficult, especially for long periods of time. Most TTS engines
allow users to edit text files with embedded control information that adds prosody to the ASCII text. This is useful for
systems that are used to "read" text that is edited and stored for later retrieval.
TTS systems have their limits when it comes to producing individualized voices. Synthesis-based engines are relatively
easy to modify to create new voice types. This modification involves the adjustment of general pitch and speed to
produce new vocal personalities such as "old man," "child," "female," "male," and so on. However, these voices still use
the same prosody and grammar rules.
Creating new voices for diphone-based systems is much more costly than for synthesis-based systems. Since each new
vocal personality must be assembled from pre-recorded human speech, it can take quite a bit of time and effort to alter an
existing voice set or to produce a new one. Diphone concatenation is costly for systems that must support multiple
languages or need to provide flexibility in voice personalities.
There are a number of general issues to keep in mind when designing SR interfaces to your applications.
First, if you provide speech services within your application, you'll need to make sure you let the user know the services
are available. This can be done by adding a graphic image to the display, telling the user that the computer is "listening,"
or you can add caption or status items that indicate the current state of the SR engine.
It is also a good idea to make speech services an optional feature whenever possible. Some installations may not have the
hardware or RAM required to implement speech services. Even if the workstation has adequate resources, the user may
experience performance degradation with the speech services active. It is a good idea to have a menu option or some
other method that allows users to turn off speech services entirely.
When you add speech services to your programs, it is important to make sure you give users realistic expectations
regarding the capabilities of the installation. This is best done through user documentation. You needn't go into great
length, but you should give users general information about the state of SR technology, and make sure users do not
expect to carry on extensive conversations with their new "talking electronic pal."
Along with indications that speech services are active, it is a good idea to provide users with a single speech command
that displays a list of recognized speech inputs, and some general online help regarding the use and capabilities of the SR
services of your program. Since the total number of commands might be quite large, you may want to provide a type of
voice-activated help system that allows users to query the current command set and then ask additional questions to learn
more about the various speech commands they can use.
It is also a good idea to add confirmations to especially dangerous or ambiguous speech commands. For example, if you
have a voice command for "Delete," you should ask the user to confirm this option before continuing. This is especially
important if you have other commands that may sound similar-if you have both "Delete" and "Repeat" in the command
list you will want to make sure the system is quite sure which command was requested.
In general, it is a good idea to display the status of all speech processing. If the system does not understand a command,
it is important to tell users rather than making them sit idle while your program waits for understandable input. If the
system cannot identify a command, display a message telling the user to repeat the command, or bring up a dialog box
that lists likely possibilities from which the user can select the requested command.
In some situations, background noise can hamper the performance of the SR engine. It is advisable to allow users to turn
off speech services and only turn them back on when they are needed. This can be handled through a single button press
or menu selection. In this way, stray noise will not be misinterpreted as speech input.
There are a few things to avoid when adding voice commands to an application. SR systems are not very successful
when processing long series of numbers or single letters. "M" and "N" sound quite alike, and long lists of digits can
confuse most SR systems. Also, although SR systems are capable of handling requests such as "move mouse left,"
"move mouse right," and so on, this is not a good use of voice technology. Using voice commands to handle a pointer
device is a bit like using the keyboard to play musical notes. It is possible, but not desirable.
The key to designing good command menus is to make sure they are complete, consistent, and that they contain unique
commands within the set. Good command menus also contain more than just the list of items displayed on the physical
menu. It is a good idea to think of voice commands as you would keyboard shortcuts.
Useful voice command menus will provide access to all the common operations that might be performed by the user. For
example, the standard menu might offer a top-level menu option of Help. Under the Help menu might be an About
item to display the basic information about the loaded application. It makes sense to add a voice command that provides
direct access to the About box with a Help About command.
These shortcut commands may span several menu levels or even stand independent of any existing menu. For example,
in an application that is used to monitor the status of manufacturing operations within a plant, you might add a command
such as Display Statistics that would gather data from several locations and present a graph onscreen.
When designing menus, be sure to include commands for all dialog boxes. It is not a good idea to provide voice
commands for only some dialog boxes and not for others.
Tip
You do not have to create menu commands for Windows-supplied dialog
boxes (the Common Dialogs, the Message Box, and so on). Windows
automatically supplies voice commands for these dialogs.
Be sure to include voice commands for the list and combo boxes within a dialog box, as well as the command buttons,
check boxes, and option buttons.
In addition to creating menus for all the dialog boxes of your applications, you should consider creating a "global" menu
that is active as long as the application is running. This would allow users to execute common operations such as Get
New Mail or Display Status Log without having to first bring the application into the foreground.
Tip
It is advisable to limit this use of speech services to only a few vital and
unique commands since any other applications that have speech services
may also activate global commands.
It is also important to include common alternate wordings for commonly used operations, such as Get New Mail and
Check for New Mail, and so on. Although you may not be able to include all possible alternatives, adding a few will
greatly improve the accessibility of your speech interface.
Use consistent word order in your menu design. For example, for action commands you should use the verb-noun
construct, as in Save File or Check E-Mail. For questions, use a consistent preface such as How do I… or Help Me…, as
in How do I check e-mail? or Help me change font. It is also important to be consistent with the use of singular and
plural. In the above example, you must be sure to use Font or Fonts throughout the application.
Since the effectiveness of the SR engine is determined by its ability to identify your voice input against a list of valid
words, you can increase the accuracy of the SR engine by keeping the command lists relatively short. When a command
is spoken, the engine will scan the list of valid inputs in this state and select the most likely candidate. The more words
on the list, the greater the chance the engine will select the wrong command. By limiting the list, you can increase the
odds of a correct "hit."
Finally, you can greatly increase the accuracy of the SR engine by avoiding similar-sounding words in commands. For
example, repeat and delete are dangerously similar. Other words that are easily confused are go and no, and even on and
off. You can still use these words in your application if you use them in separate states. In other words, do not use repeat
in the same set of menu options as delete.
There are a few things to keep in mind when adding text-to-speech services to your applications. First, make sure you
design your application to offer TTS as an option, not as a required service. Your application may be installed on a
workstation that does not have the required resources, or the user may decided to turn off TTS services to improve
overall performance. For this reason, it is also important to provide visual as well as aural feedback for all major
operations. For example, when processing is complete, it is a good idea to inform the user with a dialog box as well as a
spoken message.
Because TTS engines typically produce a voice that is less than human-like, extended sessions of listening to TTS output
can be tiring to users. It is a good idea to limit TTS output to short phrases. For example, if your application gathers
status data on several production operations on the shop floor, it is better to have the program announce the completion
of the process (for example, Status report complete) instead of announcing the details of the findings. Alternatively, your
TTS application could announce a short summary of the data (for example, All operations on time and within
specifications).
If your application must provide extended TTS sessions you should consider using pre-recorded WAV files for output.
For example, if your application should allow users aural access to company regulations or documentation, it is better to
record a person reading the documents, and then play back these recordings to users upon request. Also, if your
application provides a limited set of vocal responses to the user, it is advisable to use WAV recordings instead of TTS
output. A good example of this would be telephony applications that ask users questions and respond with fixed answers.
Finally, it is not advisable to mix WAV output and TTS output in the same session. This highlights the differences
between the quality of recorded voice and computer-generated speech. Switching between WAV and TTS can also make
it harder for users to understand the TTS voice since they may be expecting a familiar recorded voice and hear computer-
generated TTS instead.
Summary
The Microsoft Speech SDK only works on 32-bit operating systems. This means you will need Windows 95 or Windows
NT version 3.5 or greater in order to run SAPI applications.
The minimum, recommended, and preferred processor and RAM requirements for SAPI applications vary depending on
the level of services your application provides. The minimum SAPI-enabled system may need as little as 1MB of
additional RAM and be able to run on a 486/33 processor. However, it is a good idea to require at least Pentium 60
processor and an additional 8MB RAM. This will give your applications the additional computational power needed for
the most typical SAPI implementations.
SAPI systems can use just about any of the current sound cards on the market today. Any card that is compatible with
the Windows Sound System or with Sound Blaster systems will work fine. You should use a close-talk, unidirectional
microphone, and use either external speakers or headphones for monitoring audio output.
You learned that SR technology uses three basic processes for interpreting audio input:
● Word selection
● Word analysis
● Speaker dependence
You also learned that SR systems have their limits. SR engines cannot automatically distinguish between multiple
speakers, cannot learn new words, guess at spelling, or handle wide variations in word pronunciation (for example, to-
may-toe or to-mah-toe).
TTS engine technology is based on two different types of implementation. Synthesis systems create audio output by
generating audio tones using algorithms. This results in unmistakably computer-like speech. Diphone concatenation is an
alternate method for generating speech. Diphones are a set of phoneme pairs collected from actual human speech
samples. The TTS engine is able to convert text into phoneme pairs and match them to diphones in the TTS engine
database. TTS engines are not able to mimic human speech patterns and rhythms (called prosody) and are not very good
at communicating emotions. Also, most TTS engines experience difficulty with unusual words. This can result in odd-
sounding phrases.
Finally, you learned some tips on designing and implementing speech services. Some of the tips covered here were:
In the next chapter, you'll use the information you learned here to start creating SAPI-enabled applications.
Chapter 20
CONTENTS
❍ Adding SR Services
In this chapter, you'll build a complete Visual Basic 4.0 application that uses both speech recognition and text-to-speech
services. You'll use the MDINOTE project that ships with Visual Basic 4.0 as a starting framework for creating a voice-
activated text reader. This application will allow users to use voice commands to load text documents and then tell the
workstation to play back the loaded documents using TTS services.
You'll learn how to declare your own custom voice commands and how to use the voice commands automatically built
by Windows each time you load an application. You'll also learn how to add menu options and toolbar buttons that
provide TTS services for your application. When you are done with the example project in this chapter, you'll know how
to build SAPI-enabled applications using Visual Basic 4.0 or any other VBA-compliant language.
The first stage in the process of building a SAPI-enabled Windows application is designing the SAPI interface. Adding
SAPI services to an application is much like adding other Windows extensions (such as messaging, telephony, and so
on). Usually, you can start from an existing application framework and add SAPI services where appropriate.
In order for you to focus on the process of incorporating SAPI services into an application, the project in this chapter
starts from a working Visual Basic 4.0 application-the MDINOTE.VBP project. This application allows users to create,
edit, and save text files.
Note
This project ships with Visual Basic 4.0 and can be found in the SAMPLES
\MDI folder of the Visual Basic home directory. You can also find it on the
CD-ROM that ships with this book.
For the example in this chapter, you'll add both speech recognition and text-to-speech services to this application. When
you complete the project outlined here, you'll have a fully functional text editor that provides both SR and TTS services
to users.
Adding TTS services to the MDINOTE.VBP project is really quite simple. You will need to provide an option that
allows users to tell the TTS engine to read the selected text. You should also allow users to fast-forward the reader,
rewind it, and stop it as needed. The easiest way to do this is to provide a set of menu options that correspond to the
SAPI TTS Speak, FastForward, Rewind, and StopSpeaking services. In addition to adding menu options,
you'll also add command buttons to appear on the toolbar.
Adding TTS services also requires some initialization code to declare and initialize the Voice Text object.
Adding SR Services
Adding speech recognition services to Windows 95 applications is very easy. First, as soon as the SAPI services are
installed and activated, all the menu options of a Windows application are valid voice commands. In other words, even if
you have done no coding at all, your Windows 95 applications are ready to receive and respond to voice commands.
Tip
As soon as you load a Windows application, the menu options are activated
as valid voice commands. You can view the valid voice commands at any
moment by saying What Can I Say? or by selecting the alternate mouse
button while pointing to the Microsoft Voice balloon icon in the system
tray.
In addition to the default menu option commands, you'll also add a set of custom commands for the MDINOTE.VBP
project. These are one-word commands that the user can speak. They correspond to the set of toolbar buttons that appear
on the form while the project is running.
Along with the code that declares the menu commands and their objects, you'll also write code to check the
CommandSpoken property and respond to the voice commands as needed.
The first job in adding speech services to a project is declaring global variables and adding the initialization routines for
starting and ending the application. If you are adding TTS services, you need to add code that will respond to the various
TTS engine requests (Speak, FastForward, Rewind, and Stop). If you are enabling custom SR voice commands,
you'll need to add code that registers the new commands.
Note
You'll also need code that checks the CommandSpoken property of the
Voice Command object to see if one of your custom commands was
uttered by the user. You'll add that code to the main form later in this
chapter.
For the project described in this chapter, you'll need to add both SR and TTS services. First, load the MDINOTE project
and add a new BAS module. Set its Name property to modSpeech and save it as MDISP.BAS. Next add the code
shown in Listing 20.1 to the general declaration section of the module.
Option Explicit
'
' *********************************************
' This module adds SAPI support to the program.
' *********************************************
'
' VCmd speech stuff
Global objVCmd As Object ' SR Command object
Global objVMenu As Object ' SR Menu object
Global cVCmdMenu() As String ' SR command strings
Global lSRCmd As Long ' SR command ID
'
' VTxt Speech stuff
Global objVText As Object ' TTS object
Global bVText As Boolean ' TTS flag
Global Const vTxtSpeak = 0
Global Const vTxtForward = 1
Global Const vTxtRewind = 2
Global Const vTxtStop = 3
The first several code lines declare the variables and objects needed to provide SR services. The SAPI voice command
object library requires two different objects: the Voice Command object and the Voice Menu object. You'll also
need an array to hold the commands to be added to the menu and a variable to hold the menu ID returned in the
CommandSpoken property.
The TTS service requires only one object. You'll use a Boolean flag to hold the activity status of the TTS engine returned
by the IsSpeaking property. The last four constants are used by Visual Basic to keep track of menu and button
options selected by the user.
Save the code module as MDISP.BAS and the project as MDINOTE.VBP before continuing on to the next section.
This routine first initializes the Voice Command objects, registers the application, and starts the SR engine. The call to
the InitVoice routine adds the custom voice commands to the declared menu. You'll see that code in the next section.
After handling the registration of SR services, the routine adds TTS services to the project. After initializing the Voice
Text object, registering the application, and enabling TTS, the InitVText routine is called to build the menu and
button options on the forms. You'll see this code later.
The next set of code to add is the UnInitSAPI routine. This routine will be called at the end of the program. The
UnInitSAPI routine deactivates all SAPI objects and releases all links to SAPI services. Add a new subroutine to the
modSpeech module and enter the code shown in Listing 20.3.
The InitVoice routine adds all the new custom commands to the menu object declared in the InitSAPI routine.
After it adds the new commands, a timer object is initialized and enabled. The timer will be used to check for a valid
voice command. Add a new subroutine called InitVoice and enter the code shown in Listing 20.4.
As you can see, adding custom menu options involves adding the command strings to the menu object and then
activating the menu object. The code that sets the timer is needed to poll the SpokenCommand property every half
second.
Tip
You do not have to add custom commands to SAPI-enabled applications
that have a declared menu. All menu items are automatically registered as
voice commands by the operating system. The commands added here are
really shortcuts to existing menu options.
When you add TTS services to the application, you need only to initialize the TTS object and then "turn on" the menu
and/or command buttons that allow users to gain access to the TTS engine. In this application a set of buttons for the
toolbar needs initialization. The code in Listing 20.5 shows how this is done.
You'll notice a call to the VTextAction routine. This routine handles the TTS service requests (Speak,
FastForward, Rewind, and StopSpeaking). A timer is also enabled in order to track the active status of the TTS
engine.
The VTextAction routine is the code that handles the various TTS service requests made by the user. This one set of
code can handle all of the playback options for the program. It also handles the enabling of the toolbar buttons and the
menu options. Listing 20.6 shows the code needed for the VTextAction subroutine.
There are three parts to this one routine. The first part of the code handles the actual TTS request. Only the first option
(vTxtSpeak) involves any coding. Since the MDINOTE project uses a single text box for user input, this text box is
automatically used as the source for all TTS playback.
Note
You'll notice that an index value is used to power this routine. This index
value comes from an array of command buttons or from a menu array.
You'll build these later. Control and menu arrays are excellent ways to build
compact code in Visual Basic.
The second part of the code handles the enabling and disabling of the menu items on the edit form. When the edit form is
opened, a single menu item is activated (Read). Once the TTS engine starts reading, the other options (Rewind,
Forward, and Stop) are made visible. The third and last part of this routine makes sure the proper buttons appear on
the toolbar. This works the same as the menu array. You'll build the menu and control array in the next section.
This is the end of the code module for the project. Save the module as MDISP.BAS and the project as MDINOTE.VBP
before continuing.
There are two forms in the MDINOTE project that must be modified: the frmMDI form and the frmNotePad form.
The frmMDI form holds the button array and calls the InitSAPI and UnInitSAPI routines. It also has code to
handle the timer controls. The frmNotePad form holds the menu array and has code to control the starting and
stopping of the TTS engine.
Modifying the MDI Form
The first task in modifying the frmMDI form is adding the button array to the toolbar. You'll use image controls to hold
the buttons views. You'll also need to add code to the Form_Load and Form_Unload events. Finally, you'll add code
for the two timer events.
First, you need to open the frmMDI form and add an array of four image controls and then add two timers. Table 20.1
shows the controls that need to be added to the form. Refer to Figure 20.1 and Table 20.1 for the placement of the image
controls on the form.
Be sure to paint the image controls and the timers onto the toolbar. Also note that the imgVText control is a control
array of four controls. You'll use the index value returned by this array to tell the VTextAction routine what service
was requested by the user.
Note
You don't need to set the picture property of the image controls at design
time. This is handled by the InitText routine you wrote earlier.
After adding these controls, save the form (MDI.FRM) and the project (MDINOTE.VBP) before continuing.
After adding the controls, you need to add some code to the Form_Load and Form_UnLoad events. This code will be
executed only once at the start or end of the program. Listing 20.7 shows the complete Form_Load code with the single
line that calls the InitSAPI routine added at the end. Modify the Form_Load event code to look like the code in
Listing 20.7.
You also need to make a similar modification to the Form_Unload event code. Listing 20.8 shows the entire code
listing for the Form_Unload event with the call to UnInitSAPI at the start of the routine. Modify the
Form_Unload code to match the code in Listing 20.8.
The final code you need to add to the frmMDI form is the code to handle the button array and the code to handle the
timer events. First, add the code in Listing 20.9 to the ImgVText_Click event. This will pass the array index to the
VTextAction routine to handle the TTS service request.
The code for the SRTimer_Timer event is more involved. The SRTimer must check the value returned by the
CommandSpoken property to see if it is a valid custom command. If the value is part of the Select Case structure,
the corresponding program routine is called. Add the code shown in Listing 20.11 to the SRTimer_Timer event.
Warning
You'll notice that the CommandSpoken property is loaded into a local
variable and then set to zero. This is an important step. Failure to clear the
CommandSpoken property can result in locking your program into a loop
that keeps executing the last requested command. To prevent getting caught
in a loop, be sure to clear the CommandSpoken property as soon as you
read it.
Those are all the modifications needed to the frmMDI form. Save the form and the project before continuing.
There are only two main modifications to the frmNotePad form. First, you need to add the TTS menu options to the
form. Next, you need to add a few lines of code to the Form_Load and Form_Unload events. Once these are done,
you are ready to test your finished application.
You need to add four menu options to the File menu. These four options correspond to the TTS engine service options:
Speak, FastForward, Rewind, and StopSpeaking. Refer to Table 20.2 and Figure 20.2 to see how to add these
menu options to the frmNotePad form.
Figure 20.2 : Using the Menu Editor to add the TTS engine options.
Note that all but the first menu option (Read) have their Visible property set to FALSE. You'll only show the other
options after the TTS engine starts speaking some text.
After adding the menu object, you need to add some code to the mnuVText_Click event to handle menu selections.
Since this is a menu array, you'll only need one line of code to pass the selected service request to the VTextAction
routine. Listing 20.12 shows the code you should add to the mnuVText_Click event.
You need to add only one line to the Form_Load and Form_Unload events. This line forces the TTS engine to stop
speaking any code already in progress. That prevents the engine from attempting to speak two sets of text at once.
Listing 20.13 shows the complete Form_Load event with the SAPI-related line at the end. Modify the code in
Form_Load to match the code in Listing 20.13.
mnuFontName(0).Caption = Screen.Fonts(0)
For i = 1 To Screen.FontCount - 1
Load mnuFontName(i)
mnuFontName(0).Caption = Screen.Fonts(i)
Next
'
VTextAction vTxtStop ' <<< force stop all speaking >>>
'
End Sub
The code modification to the Form_Unload event is also quite easy. Listing 20.14 shows the whole Form_Unload
event code with the SAPI-related line at the end. Modify the Form_Unload event code to match that shown in Listing
20.14.
That is the end of the code modification for the MDINOTE.VBP project. Save this form (MDINOTE.FRM) and the
project (MDINOTE.VBP) before beginning to test your new SAPI-enabled version of the MDINOTE project.
Once you compile the MDINOTE project, you can view the new voice commands that were added and also test the TTS
playback of text documents.
Warning
Be sure your workstation has speech services installed and activated before
you start this project. If not, you may encounter an error and may have to
reboot your system.
First, start the MDINOTE application and ask your workstation to tell you what commands are available (ask What can I
say?). You should see two additional sections in the voice menu. The first one ("MDI NotePad voice commands") was
added by the operating system when you first loaded the program. The Windows operating system will automatically
create a set of voice commands that matches the menus of the program. Figure 20.3 shows the list of commands
automatically built by Windows under the "MDI NotePad..." heading.
You'll also see the custom commands added to the command list under the "MDINOTE" heading. These were added by
the InitVoice routine in the project.
You can test the SR options of the program by speaking any of the menu commands or custom voice commands. When
you say New, you should see a new blank page appear, ready for text input. You can test the TTS services by loading a
text document and selecting Read from the menu or speaking the Read voice command. You'll see a set of buttons
appear on the toolbar and an expanded list on the File menu (see Figure 20.4).
Figure 20.4 : Viewing the expanded file menu during a Read command.
Summary
In this chapter, you learned how to add SR and TTS services to Visual Basic 4.0 applications using the OLE Voice
Command and Voice Text libraries. You modified the MDINOTE project that ships with Visual Basic 4.0 to add
options to speak command words and have the TTS engine read loaded text documents back to you.
You also learned that you do not need to add any code to Windows programs in order to make them SR-capable. Every
menu option that is declared in a Windows program is automatically loaded as a valid SAPI voice command by the
Windows operating system (as long as SAPI services are active). You also learned how to add custom voice commands
to speed access to key menu items.
The next chapter is a summary of the SAPI section. The next section of the book describes the Windows telephony
application programming interface and how you can use it to control incoming and outgoing voice and data telephone
calls.
Chapter 21
CONTENTS
Chapter 14 covered the key factors in creating and implementing a complete speech system for pcs. You learned the
three major parts of speech systems:
● Speech recognition converts audio input into printed text or directly into computer commands.
● Text-to-speech converts printed text into audible speech.
● Grammar rules are used by speech recognition systems to analyze audio input and convert it into commands or
text.
In Chapter 15 you learned the details of the SR and TTS interfaces defined by the Microsoft SAPI model. You also
learned that the SAPI model is based on the Component Object Model (COM) interface and that Microsoft has defined
two distinct levels of SAPI services:
● High-level SAPI provides a "command-and-control" level of service. This is good for detecting menu and
system-level commands and for speaking simple text.
● Low-level SAPI provides a much more flexible interface and allows programmers access to extended SR and
TTS services.
You learned that the two levels of SAPI service each contain several COM interfaces that allow C programmers access
to speech services. These interfaces include the ability to set and get engine attributes, turn the services on or off, display
dialog boxes for user interaction, and perform direct TTS and SR functions.
Since the SAPI model is based on the COM interface, high-level languages such as Visual Basic cannot call functions
directly using the standard API calls. Instead, Microsoft has developed OLE automation type libraries for use with
Visual Basic and other VBA-compliant systems. The two type libraries are:
● Voice Command Objects-This library provides access to speech recognition services.
● Voice Text Objects-This library provides access to text-to-speech services.
Chapter 16 focused on the hardware and software requirements of SAPI systems, the general technology and limits of
SAPI services, and some design tips for creating successful SAPI implementations.
The Microsoft Speech SDK only works on 32-bit operating systems. This means you need Windows 95 or Windows NT
Version 3.5 or greater in order to run SAPI applications.
The minimum, recommended, and preferred processor and RAM requirements for SAPI applications vary depending on
the level of services your application provides. The minimum SAPI-enabled system may need as little as 1MB of
additional RAM and be able to run on a 486/33 processor. However, it is a good idea to require at least a Pentium 60
processor and an additional 8MB RAM. This will give your applications the additional computational power needed for
the most typical SAPI implementations.
SAPI systems can use just about any of the current sound cards on the market today. Any card that is compatible with
the Windows Sound System or with Sound Blaster systems will work fine. You should use a close-talk, unidirectional
microphone, and you can use either external speakers or headphones for monitoring audio output.
You learned that SR technology uses three basic processes for interpreting audio input:
● Word selection
● Word analysis
● Speaker dependence
You also learned that SR systems have their limits. SR engines cannot automatically distinguish between multiple
speakers, cannot learn new words, guess at spelling, or handle wide variations in word pronunciation (for example, "toe-
may- toe" versus "toe- mah- toe").
TTS engine technology is based on two different types of implementations. Synthesis systems create audio output by
generating audio-tones using algorithms. This results in unmistakably computer-like speech. Diphone concatenation is
an alternate method for generating speech. Diphones are sets of phoneme pairs collected from actual human speech
samples. The TTS engine is able to convert text into phoneme pairs and match them to diphones in the TTS engine
database. TTS engines are not able to mimic human speech patterns and rhythms (called prosody), and are not very good
at communicating emotions. Also, most TTS engines experience difficulty with unusual words. This can result in odd-
sounding phrases.
Finally, you learned some tips for designing and implementing speech services, including:
Chapter 17, "SAPI Tools-Using SAPI Objects with Visual Basic 4.0"
In Chapter 17 you learned that the Microsoft Speech SDK contains a set of OLE library files for implementing SAPI
services using Visual Basic and other VBA-compatible languages. There is an OLE Automation Library for TTS
services (VTXTAUTO.TLB), and one for SR services (VMCDAUTO.TLB). Chapter 17 showed you how to use the
objects, methods, and properties in the OLE library to add SR and TTS services to your Windows applications.
You learned how to register and enable TTS services using the Voice Text object. You also learned how to adjust the
speed and how to control the playback, rewind, fast forward, and pause methods of TTS output. Finally, you learned how
to use a special Callback method to register a notification sink using a Visual Basic Class module.
You also learned how to register and enable SRT services using the Voice Command and Voice Menu objects. You
learned how to build temporary and permanent menu commands and how to link them to program operations. You also
learned how to build commands that accept a list of possible choices and how to use that list in a program. Finally, you
learned how to use the Callback property to register a notification sink using the Visual Basic Class module.
In Chapter 18 you learned how the speech system uses grammar rules, control tags, and the International Phonetic
Alphabet to perform its key operations.
You built simple grammars and tested them using the tools that ship with the Speech SDK. You also learned how to load
and enable those grammars for use in your SAPI applications.
You added control tag information to your TTS input to improve the prosody and overall performance of TTS interfaces.
You used Speech SDK tools to create and play back text with control tags, and you learned how to edit the stored
lexicon to maintain improved TTS performance over time.
Finally, you learned how the International Phonetic Alphabet is used to store and reproduce common speech patterns.
The IPA can be used by SR and TTS engines as a source for analysis and playback.
In Chapter 19 you learned how to write simple TTS and SR applications using C++. Since many of the SAPI features are
available only through C++ coding, this chapter gave you a quick review of how to use C++ to implement SAPI
services.
You built a simple TTS program that you can use to cut and paste any text for playback. You also built and tested a
simple SR interface to illustrate the techniques required to add SRT services to existing applications.
In Chapter 20 you used all the information gathered from previous chapters to build a complete application that
implements both TTS and SR services. The Voice-Activated Text Reader allows users to select text files to load, loads
them into the editor page, and then reads them back to the user on command. All major operations can be performed
using speech commands.
You also learned how to add SR services to other existing applications using a set of library modules that you can add to
any Visual Basic project.
With the creation of the generalized interfaces defined by Microsoft in the SAPI model, it will not be long before new
versions of the TTS and SR engine appear on the market ready to take advantage of the larger base of Windows
operating systems already installed. With each new release of Windows, and new versions of the SAPI interface, speech
services are bound to become more powerful and more user-friendly.
Although we have not yet arrived at the level of voice interaction depicted in Star Trek and other futuristic tales, the
release of SAPI for Windows puts us more than one step closer to that reality!
Chapter 22
What Is TAPI?
CONTENTS
❍ Phones
❍ pc-Based Configurations
❍ Multiline Configurations
❍ Digital T1 Lines
● Summary
The Telephony Application Programming Interface (TAPI) is one of the most significant API sets to be released by
Microsoft. The telephony API is a single set of function calls that allows programmers to manage and manipulate any
type of communications link between the pc and the telephone line(s). While telephony models for the pc have been
around for several years, the telephony API establishes a uniform set of calls that can be applied to any type of hardware
that supplies a TAPI-compliant service provider interface (SPI).
This chapter provides a general overview of the Telephony API and how it fits into the WOSA (Windows Open Services
Architecture) model. You'll learn about the two main devices defined within the TAPI model:
● Line devices
● Phone devices
You'll also learn about the typical physical configurations used in a TAPI model, which can be:
● Phone-based
● pc-based
● Shared or unified line
● Multiline
You'll also learn about the different types of telephone service lines used to provide media transport services for TAPI
applications, including:
● POTS
● Digital T1
● ISDN service
● PBX service
When you complete this chapter you should have a good understanding of the TAPI model, including the meaning and
use of line and phone devices. You'll also know the most common TAPI hardware configurations, their advantages, and
their drawbacks. Finally, you'll understand the various physical telephone services available for use in TAPI
applications.
The telephony API model is designed to provide an abstracted layer for access to telephone services on all Windows
platforms. In other words, the telephony API is a single set of functions that can be used to access all aspects of
telephony services within the Windows operating system.
This is a huge undertaking. The aim of TAPI is to allow programmers to write applications that work regardless of the
physical telephone medium available to the pc. Applications written using TAPI to gain direct access to telephone-line
services work the same on analog or digital phone lines. Applications that use TAPI can generate a full set of dialing
tones and flash-hook functions (like that of the simple analog handset found in most homes), and can also communicate
with sophisticated multiline digital desktop terminals used in high-tech offices.
The TAPI design model is divided into two areas, each with its own set of API calls. Each API set focuses on what TAPI
refers to as a device. The two TAPI devices are:
● Line devices to model the physical telephony lines used to send and receive voice and data between locations.
● Phone devices to model the desktop handset used to place and receive calls.
Lines
The line device is used to model the physical telephone line. It is important to understand that, in TAPI, the line device is
not really a physical line; it's just a model or object representing a physical line. In TAPI applications, a program could
keep track of several line devices, each of which is connected to a physical line. That same TAPI application could also
keep track of multiple line devices that number more than the total physical lines available to the pc.
For example, a single TAPI application could be designed to provide voice, fax, and data links for a user. The TAPI
application would identify three line devices. One for voice calls, one for fax transmission, and one for sending and
receiving data via an attached modem. If the pc has only one physical phone line attached, the TAPI application would
share the one line between the three defined line devices. This is called dynamic line mapping (see Figure 22.1).
Each time the TAPI application starts a line device, it requests the first available physical line that has the capabilities
needed (voice, fax, data, and so on). If a line is not available, a message to that effect is returned to the calling program.
In some cases, such as fax transmissions, the TAPI application may "queue up" the line request for processing at a later
time.
If two lines are available, the TAPI application uses them as they are needed. If a third line device becomes active, the
TAPI application knows that there are no other available open lines and notifies the user (or possibly queues up the
outbound call for later).
TAPI is also able to keep track of the types of lines available. For example, one of the two lines connected to the pc may
be a dedicated, high-speed data transmission line (such as a Switched 56Kbps line), and the other a basic voice-grade
line (such as a 3.1KHz line). If the TAPI application requested a high-speed data line, the SPI would return the handle of
the Switch 56Kbps line. If the application requested a voice-grade line, the SPI would return the handle of the voice line.
If, however, the second request was for another data line, the SPI would know that the voice-grade line was not
acceptable and would return a message telling the TAPI application that all data lines were busy.
Phones
The second type of device modeled by TAPI is the phone device. This model allows TAPI programmers to easily create
"virtual phones" within the pc workspace. For example, a standard pc with a sound card, speakers, and microphone can
emulate all the functions of a desktop phone. These virtual phones, like their line device counterparts, need not exist in a
one-to-one relationship to physical phones. A single pc could model several phone devices, each with their own unique
characteristics. When an actual call must be made, the user could select one of the phone devices, enter the desired
number and then the TAPI application would attach the phone device to an available line device. Note that the phone
devices link to line devices (which eventually link to physical telephone lines).
One of the primary uses of multiple phone devices would be the modeling of an office switchboard. The typical
switchboard has several trunk lines that terminate at a single multiline handset. Usually this handset has several flashing
lights and pushbuttons. The buttons are used to connect trunk lines to extension phones within the office. The extension
phones could be modeled as phone devices on a receptionist's screen. The incoming trunk lines could be modeled as line
devices. As calls come in, the receptionist would "pick up" a line, "drop" it onto the switchboard phone device,
determine to whom the call should be routed, and then pick up the call off the switchboard phone and drop it onto the
appropriate extension (see Figure 22.2).
Figure 22.2 : A virtual switchboard using TAPI phone and line devices.
TAPI is able to accomplish its task by dividing the job into two distinct layers: the client API and the SPI. Each interface
is a set of functions designed to complete generic telephony tasks such as opening a line, checking for a dial tone, dialing
a number, checking for a ring or a busy signal, and so on. The client API sends requests from the application to the SPI
for each task. It is the job of the SPI to complete the task and pass the results back to the calling program through the
client API. Does any of this sound familiar? It should. TAPI is a full member of the WOSA (Windows Open Services
Architecture) model.
By acting as part of the WOSA model, TAPI provides a complete telephony implementation for Windows operating
systems without forcing the programmer to learn vendor-specific APIs. Application developers can focus on delivering
the features needed most by users and leave the details of vendor-specific implementation to the hardware driver (SPI)
programmers. At the same time, hardware vendors can spend time implementing a single set of SPI calls that they can be
assured will work on all Windows platforms.
Typical Configurations
The TAPI model is designed to function in several different physical configurations, which each have advantages and
drawbacks. There are four general physical configurations:
● Phone-based-This configuration is best for voice-oriented call processing where the standard handset (or some
variation) is used most frequently.
● pc-based-This configuration is best for data-oriented call processing where the pc is used most frequently for
either voice or data processing.
● Shared or unified line-This is a compromise between phone-based and pc-based systems. It allows all devices to
operate as equals along the service line.
● Multiline-There are several variations of the multiline configuration. The primary difference between this
configuration and the others is that the pc acts as either a voice-server or a call switching center that connects
the outside phone lines to one or more pcs and telephone handsets. The primary advantage of multiline
configurations is that you do not need a direct one-to-one relationship between phone lines and end devices
(phones or pcs).
Phone-Based Configurations
In phone-based TAPI configurations, the standard telephone handset is connected to the telephone switch and the pc is
connected to the telephone (see Figure 22.3).
This configuration is most useful when the telephone handset is the primary device for accessing the telephone line.
Since the telephone rests between the pc and the switch, the pc may not be able to share in all the activity on the line. In
the example shown in Figure 22.3, the pc could not be included in a conference call if the handset originated the call
since no line can be "conferenced" with itself and the call originates "upstream" from the pc.
A phone-based configuration does not preclude the use of the pc to originate calls. As long as the pc is equipped with a
phone card that allows dialing, the pc can originate a call and then allow the handset to pick up on that call at any time.
In other words, even in a phone-based configuration, the pc can be used as a dialing tool, and then hand the voice calls
off to the upstream handset device.
pc-Based Configurations
pc-based TAPI configurations place the pc between the telephone switch and the standard handset (see Figure 22.4).
This configuration is most useful when the pc is the primary device for accessing the telephone line. In this
configuration, the pc most often originates phone calls. Typically, this is done via a phone card and software on the pc
that manages a list of phone numbers and handles the dialing of the phone. Depending on the exact media mode of the
call, the pc can be used to display digital data on screen while handling voice information, too.
The pc-based configuration illustrated in Figure 22.4 also allows for originating calls from the handset. In this case, calls
can be shared by the pc since the data stream is passing through the pc to the switch. Users could originate a voice call
through the handset and then switch to the pc to capture and display digital data sent over the same line.
Another major advantage of the pc-based configuration is that the pc can act as a call manager for the handset. This is
especially valuable in a mixed-mode environment where voice, data, and fax are all coming in to the same phone
address. For example, as a call comes in to the attached phone line, the pc can answer the call and determine the media
mode of the call. If it is a fax call, the pc can route the call directly to an attached fax machine (or to the fax driver on the
pc). Data calls can be handled directly by the pc and voice calls can be forwarded to the attached handset.
In a pc-based configuration, the pc can also be used for call screening and message handling. TAPI-compliant software
could record incoming messages for the user and place them in a queue for later review, or forward calls to another
address. With the addition of caller ID services from the local telephone company, the pc could also act as a call filter
agent, screening the calls as they arrive and allowing only designated callers access to the pc or the handset.
The shared or unified line configuration is a bit of a compromise between pc-based and phone-based configurations. The
shared line configuration involves a split along the line leading to the switch. Both the pc and the phone have equal (and
simultaneous) access to the line (see Figure 22.5).
The advantage of the shared-line configuration is that either device can act as the originator of a call. The primary
disadvantage is that both devices have equal access to incoming calls. In other words, as a call comes in, both devices
will ring. Depending on the software operating on the pc, it is possible that both devices would attempt to answer the
same incoming call. This situation is much like having two extension phones along the same access line.
The unified line configuration offers the combined benefits of the pc-based configuration and the shared-line
configuration. In the unified line configuration, the access line goes directly from the switch into a telephone card in the
pc. The pc also has handset equipment either attached to the phone card or integrated into the pc itself. All that is really
needed is a microphone for input and speakers for output, but some systems offer headphones or a keypad to simulate
the familiar telephone handset (see Figure 22.6).
With the unified line arrangement the pc can act as either a handset device or a pc data device. In fact, the unified line
arrangement is virtually the same as the pc-based configuration except that the phone is internal to the pc instead of
attached directly to the pc. As new calls come in to the device, software on the pc can determine the media mode of the
call (data, fax, voice, and so on) and route the call to the proper hardware on the pc. Also, with the unified arrangement,
users do not need to worry about two devices ringing at the same time when a call comes in.
Multiline Configurations
So far, all the configurations reviewed here have been single-line models. This is commonly referred to as first-party call
control. TAPI is also designed to support multiline configurations. In this arrangement, TAPI is used to provide third-
party call control.
Single lines act as the first (and only) party in a telephone call. In a multiline environment, a device can act as a third
party in a telephone call. The most common form of third-party call control is a central switchboard in an office. When a
call comes in, the switchboard (the third party) accepts the call, determines the final destination of the call, routes the call
to the correct extension, and then drops out of the call stream.
● Voice server-Used to provide voice mail and other services from a central location.
● PBX server-Used to provide call control for inbound and outbound trunk lines.
In a voice server configuration, the TAPI-enabled pc acts as a message storage device accessible from any other
telephone handset. Handsets can be configured to forward all "ring/no answer" calls to the voice server where inbound
callers are instructed to leave recorded messages. Later, users can dial into the voice server to retrieve their messages.
Alternatively, users could consult a universal in-box that contains voice-mail, faxes, and e-mail. Upon selecting the
voice-mail items, the pc would play the message over the pc speakers and allow the user to speak a reply using either the
phone handset or an attached microphone.
In a PBX server configuration, the TAPI-enabled pc acts as a sort of first line of defense for all incoming calls to a
multiline location, usually an office building. In this mode, TAPI functions are used to accept calls, present them to an
operator for review, and then forward them to the final destination. This is using the TAPI pc as a true third-party control
system.
While a Voice Server does its work behind a standard desktop phone, the PBX Server does its work in front of any
desktop phone. In other words, the Voice Server is designed to handle calls when the desktop phone is busy or in some
other way unable to accept calls. The PBX Server, on the other hand, is used to accept all calls coming into the office
and route those calls to the appropriate desktop phone. Many offices employ both PBX Server and Voice Server systems.
The PBX Server answers the incoming line and routes it to the desktop phone. If the desktop phone is unable to accept
the call, the Voice Server takes a message and stores it for later retrieval.
One of the primary aims of the TAPI model is to allow programmers to design systems that will work the same way
regardless of the physical characteristics of the telephone line. TAPI functions behave the same on analog, digital, and
cellular phone lines. TAPI is able to operate in single-line or multiline configurations. In fact, the real value of the TAPI
model for the programmer is that users can usually install TAPI-compliant software on systems with different physical
line types and still operate properly.
It is important to know that some physical line types offer options not available on other line types. For example, ISDN
lines offer simultaneous data and voice channels not available on POTS or T1 lines. TAPI cannot make a POTS line
offer the same services an ISDN line offers. TAPI does, however, provide a consistent interface to all services options
shared by line types (such as line open, dial, send data, close line, and so on).
● Analog lines
● Digital lines
● Private protocol lines
Analog lines are the kind of lines available in most homes. Digital lines are usually used by large organizations,
including local telephone service providers, to transfer large amounts of voice and data channels. T1 and ISDN lines are
typical types of digital lines. Private protocol lines are a special kind of digital line. These lines are used within private
branch exchanges (PBXs). PBX-type lines are used to transport voice, data, and special control information used by the
switching hardware to provide advanced telephony features such as call transfer, conferencing, and so on.
Regardless of the type of telephone line service used (POTS, T1, ISDN, PBX), signal transmission (voice or data) must
move from the source location (the call originator) to the desired destination. Along the way, a typical call can be
converted from analog to digital, move from physical wires, through fiber optical cables, and possibly even by way of
microwave satellite transmission before it finally arrives at the designated destination address. Figure 22.7 shows how an
overseas call might travel through the telephone network.
The rest of this section describes each of the telephone line service types in greater detail.
Plain Old Telephone Service (POTS) is the line service type provided to most homes in the United States. POTS is an
analog service that provides basic connection to the telephone company's central office by way of a single-line link.
Standard POTS users cannot perform advanced telephone operations such as call transfers, forwarding, or conferencing.
The analog POTS is designed to send voice signals, not data. For this reason, POTS users must employ a data modulator-
demodulator (or modem) to send digital information over POTS lines. The analog aspects of POTS lines limits the
amount of digital data that can be fed through the line. While 28.8Kbps service over analog dial-up lines is quite reliable,
data rates beyond that limit require special line conditioning. Available in some locations in the U.S. is a special dial-up
service known as Switched 56 service. Switched 56 service provides for data rates of up to 56Kbps over dial-up lines.
Recently, local telephone companies have begun offering additional services for POTS users. Caller ID, call forwarding,
and voice message services can be purchased from local switch operators for an additional charge. Also, a specialized set
of services, called Centrex, can be purchased from almost all local telephone companies. Centrex services allow POTS
lines to behave much like a line tied to a private branch exchange (PBX). Centrex services offer call waiting, call
forwarding, call transfers, and other services that provide the appearance of a multiline phone system while still using
POTS lines.
Digital T1 Lines
Digital T1 lines are designed to transport several conversations at once. T1 lines can send 24 multiple phone connections
at the same time. Since T1 lines are digital instead of analog, data rates can extend well beyond the limits of analog lines.
Data rates in the megabytes-per-minute are typical for dedicated T1 lines. T1 lines are typically used for dedicated data
transmission and for bulk transmission of multiple conversations from point to point.
Since T1 lines are digital, the analog voice signals originating on POTS lines at a residence must be converted into
digital signals at the central office switch before a voice conversation is transported over T1 lines. Once the signal
reaches its destination, it must often be converted back to an analog signal to reach the final address. Early in the
deployment of T1 lines, it was common to experience time delays or echoes during the signal conversion. The advance
of technology has all but eliminated this problem.
Note
European telephone lines have a similar format to the US T1 line digital
format, called E1. E1 lines can handle up to 30 simultaneous conversations
at one time. Although the digital format of E1 lines is different than the T1
format, central office switching handles all translations needed and TAPI
users need to do nothing special to handle calls over E1 lines.
The Integrated Services Digital Network (ISDN) was developed to handle voice, data, and video services over the same
line. Although developed more than 20 years ago, ISDN lines have only recently become available in major service
markets in the United States. In some major metropolitan areas, ISDN service is available at price levels approaching
that of analog dial-up lines. The increased expansion of data services (such as the Internet), along with the upcoming
deregulation of the U.S. telecommunications industry, will contribute to lowering the price and increasing the
availability of ISDN in the U.S.
The most common form of ISDN service, called Basic Rate Interface or BRI-ISDN, provides two 64Kbps channels and
one 16Kbps control or signal information channel. The advantage of ISDN is that the two 64Kbps B channels can be
configured to provide a single 128Kbps pipe for voice, video, or data, or the line can be configured to provide one
64Kbps data channel and a simultaneous 64Kbps digital voice channel. Thus ISDN service provides for transporting
more than one media mode at the same time. In addition to the two B channels, BRI-ISDN provides a 16Kbps control
channel, called the D channel, that can be used to send signaling information and additional control data. This control
data can contain information about the calling party (name, location, and so on) or other supplemental information
needed to complete the media transmission.
An advanced ISDN format, called Primary Rate Interface or PRI-ISDN, can provide up to 32 separate channels. The PRI-
ISDN format used in the US, Canada, and Japan provides 23 64Kbps B channels and one 64Kbps D channel. The PRI-
ISDN format used in Europe allows for 30 64Kbps B channels and two 64Kbps D channels.
BRI-ISDN offers high-speed data services at very competitive costs when compared to T1 or even Switched 56 services.
In fact, ISDN is arriving "at the curb" in some major metropolitan areas, and residential users are now able to purchase
BRI-ISDN services for Internet use at relatively low cost. PRI-ISDN, however, is a much more expensive service and is
used by businesses that need to quickly move large amounts of data.
The Private Branch Exchange (PBX) format is used in commercial switching equipment to handle multiline phone
systems in offices. Typically, the local telephone provider brings multiline service up to the PBX switch and then the
PBX handles all call control and line switching within the PBX network. PBX phones operate on proprietary formats and
are usually digital-only lines.
PBX format most often includes more than just the voice/data signal. Impulses to control flashing lights on desktop
phones, data strings that appear on display panels, and additional information to handle call conferencing, forward, and
call screening are all usually sent along the same line.
The exact data format of PBX lines varies between vendors. However, TAPI applications work on all PBX lines for
which the vendor has supplied a TAPI-compliant service provider.
Summary
In this chapter you were presented with a general outline of the TAPI model, including the definition and purpose of two
devices defined within the TAPI model:
You should also know the relative advantages and disadvantages of the four main types of TAPI hardware
configurations:
Finally, you should be able to identify the four main types of telephone service used to transmit the voice and data
signal:
● Analog POTS (Plain Old Telephone Service) for general voice-grade transmissions and for data transmission up
to 28.8Kbps speed.
● Digital T1 for dedicated high-speed voice or data services (56Kbps and above).
● ISDN (Integrated Services Digital Network) for high-speed multichannel simultaneous voice and data services.
● PBX (Private Branch Exchange) for use within proprietary switchboard hardware in an office setting.
Now that you have a general idea of the TAPI model, it's time to learn the specifics of the TAPI architecture. That is the
topic of the next chapter.
Chapter 23
TAPI Architecture
CONTENTS
In this chapter, you'll learn how the Telephony API is organized and how its various function calls are used to provide
TAPI services to Windows applications. You'll learn about the four different levels of TAPI services:
You'll also learn how these levels of service are implemented using API calls and how they work together to provide
complete TAPI services, from a simple Dial button, through handling inbound and outbound calls, to acting as a
switchboard in a multiline setting, and finally to providing access to vendor-specific features of telephony cards.
When you complete this chapter you'll understand how the Telephony API is organized and how you can use it to add
telephony services to your Windows applications.
The simplest form of TAPI service is Assisted Telephony. Under the Assisted Telephony interface, programmers can
place outbound calls and check the current dialing location of the workstation. This type of telephony service can be
used to provide a simple Dial button to existing applications or add dialing capabilities to new applications that will use
telephony as an added service.
In fact, the Assisted Telephony model only provides access for programs to request the placement of an outbound call.
The actual dialing of the call is handled by another Windows/TAPI application. The default application is DIALER.
EXE. This application ships with the Windows TAPI SDK and is part of the Windows 95 operating system.
There are two API calls used to provide Assisted TAPI services. Table 23.1 shows the two calls, their parameters, and
descriptions of what they do and how they can be used.
The TapiRequestMakeCall has four parameters. Only the DestAddess is required. The DestAddress is a
string of numbers that represents the telephone number to dial. For example, "999-555-1212" is a valid DestAddress
in the United States format. The AppName parameter is the name of the application that requested the TAPI service.
This would be the name of your application. The CalledParty is a string that represents the name of the person you
are calling. This information could be used by the DIALER.EXE application to log the person called. The Comment
parameter could contain a string of text describing the reason for the call.
The TapiGetLocation function returns two parameters: the CountryCode and CityCode of the current location
set by the Windows control panel TAPI applet. These two parameters are stored in the TELEPHON.INI file in the
Windows folder of the workstation. The country code is a value used to place out-of-country calls. The country code for
the United States is "1." The CityCode is known as the area code in the United States. The combination of the country
code and the city code is used to determine how the TAPI dialer will place the requested call.
For example, if the requested call contained "1-312-555-1212" and the current workstation location indicated a country
code of "1" and a city code of "312," then the TAPI DIALER.EXE program would attempt to place the call without
including the country or city codes: "555-1212." If, however, the requested call contained "43-80-12 33 45" then the
DIALER program would assume that the user was attempting to place an out-of-country call and would use the
appropriate dialing prefixes.
Note
You'll learn more about the dialing prefixes and how they are used in
Chapter 27, "TAPI Behind the Scenes-The TELEPHON.INI File." For now
it is only important to know that TAPI uses the location information stored
in TELEPHON.INI to determine how to place a requested call.
Basic Telephony is the next level up in the TAPI service model. Basic Telephony function calls allow programmers to
create applications that can provide basic in- and outbound voice and data calls over a single-line analog telephone. The
analog phone line most often used for this level of service is often referred to as a POTS or Plain Old Telephone Service
line. The Basic Telephony API set can also be used with more sophisticated lines such as T1, ISDN, or digital lines.
However, the added features of these advanced line devices (such as call forwarding, park, hold, conference, and so on)
are not available when using the Basic Telephony API set.
The Basic Telephony level of service focuses on the use of a line device as a means of transporting information from one
place to the next. A line device to TAPI can be a handset, a fax board, a data modem, a telephony card, or any other
physical device that can be attached to a telephone line, But it is treated as a virtual device, not a physical one.
Line devices are not associated directly with any physical telephone line. This way, TAPI can "see" multiple TAPI
devices on the same machine (data modem, handset, and fax board) while there is only one physical telephone line
attached to the workstation (see Figure 23.1).
Figure 23.1 : The relationship between TAPI line devices and physical phone lines
One of the primary functions of the TAPI interface is to handle multiple TAPI service requests from the workstation. It
is possible that several applications running on the workstation may request TAPI services at some time. The call control
application (DIALER.EXE) accepts each request and places them in a queue for processing in the requested order.
The Basic Telephony service model has several API calls for handling and fulfilling service requests. These calls can be
collected into logical groups:
● Basic line-handling calls handle the initialization and opening and closing of TAPI lines.
● Line settings and status calls handle the reading and writing of various parameter values that control the
behavior of the line device.
● Outbound and inbound functions handle the details of placing an outbound voice or data call and answering an
inbound voice or data call.
● Addressing functions handle the details of recognizing, translating, and/or building telephone "addresses" or
dialing strings.
● Miscellaneous features handle other TAPI-related functions, such as managing call-monitoring privileges and
manipulating call handles.
Table 23.2 shows the Basic Telephony API calls, sorted by functional group, along with a short description of their use.
You'll learn more about how these API calls can be used in a program in Chapter 30, "Creating TAPI-Enabled
Applications."
Along with the extensive API set for Basic Telephony, the TAPI model defines several data structures that are used to
pass information between TAPI and the requesting application. The layout of the structures contains variable as well as
fixed data. This allows the API set to contain information of indeterminate length without prior knowledge of the
contents of the structure.
In order to handle variable-length structures, the defined data structures contain fields that indicate the total size needed
to fill in all variable data (dwNeededSize) along with the total size used by TAPI when filling in the structure
(dwUsedSize). Listing 23.1 shows how this looks in the LINECALLLIST structure.
DWORD dwCallsNumEntries;
DWORD dwCallsSize;
DWORD dwCallsOffset;
} LINECALLLIST, FAR *LPLINECALLLIST;
The dwTotalSize field is first set by the calling application to tell TAPI how much memory has been allocated for the
structure. If TAPI cannot fill in all values without running out of allocated space, an error is returned and it is the job of
the requesting application to re-allocate space and make the call again.
Along with the total size and total needed fields, each variable-length structure has a fixed portion and a variable portion.
The fixed portion contains values that indicate the size of the variable-length field and the offset (from the start of the
structure) at which the field is located. Note the fields dwCallsSize and dwCallsOffset in the LINECALLLIST
structure shown in Listing 23.1.
Table 23.2 shows the list of data structures used by the Basic Telephony API set along with short descriptions of their
use.
Note
A detailed listing of all TAPI structures is included in the Microsoft Visual
C++ Win32 documentation. You can also find complete TAPI
documentation on the MSDN Professional Level CD-ROMs.
The Telephony API uses Windows messages to communicate with the requesting application. When the requesting
application first performs a LineInitialize function, a callback function address must be supplied. All messages
are then sent to this callback function.
Note
The fact that TAPI uses callbacks for messages means that any high-level
language such as Visual Basic must use either a DLL or OCX or establish
the callback link or use some other tool that can capture Windows
messages. A sample OCX is included on the CD-ROM that ships with this
book. This OCX is used throughout the book to show how you can link
Visual Basic and other VBA-compliant languages to TAPI services.
Each message returns the same set of parameters. The first is the relevant handle. Usually this is the call handle, but it
may also be a line handle. The second parameter is the callback instance value. This value will always be the instance
handle of the current running application. The next three values vary depending on the message. One or more of these
return values will contain non-zero data. Table 23.3 contains a list of the Basic Telephony messages, their parameters,
and short descriptions.
Note
Additional information about the TAPI line messages can be found in the
Win32 documentation that ships with Microsoft Visual C++ and in the
TAPI documentation that ships with the MSDN Professional Level CD-
ROM pack.
The Supplemental Telephony functions provide advanced line device handling (conference, park, hold, forward, and so
on). Access to these advanced services is dependent on the type of telephone line to which the workstation is connected.
In other words, even if you implement call forwarding functions within your TAPI application, these functions will only
work if call forwarding services are available on the telephone line provided by the local telephone company.
The Supplemental Telephony functions also allow programmers to handle service requests for multiple-line phones. You
can use Supplemental Telephony to mange a physical handset that has access to multiple physical lines (see Figure 23.2).
Figure 23.2 : Using Supplemental TAPI to manage a single handset linked to multiple physical lines.
You can also use the Supplemental Telephony functions to manage multiple handsets using one or more physical lines.
Because TAPI "virtualizes" both line and phone devices, there need not be a direct one-to-one correspondence between a
defined phone device and a defined line device. In this way you can use TAPI to create a switchboard application to
manage telephony services (see Figure 23.3).
Supplemental Telephony also provides access to defining and manipulating phone devices. To TAPI a phone device is
any device that can accept or place calls. In effect, you can register your workstation as a phone device. Then you can
use resources on your workstation to place or accept calls without the need of a handset or desktop phone. Of course, in
order to act successfully as a phone device, your workstation must have audio input and output hardware.
Note
You'll learn more about telephony hardware under the TAPI model in
Chapter 25, "TAPI Hardware Considerations."
The Supplemental API set for line devices adds advanced call control and other features to the API library. The set can
be divided into the following related groups of functions:
● Digit and tone handling functions allow programmers to detect and generate digits or tones along the phone
line. This capability is needed to allow some systems to perform advanced line operations such as forwarding,
call holds, and so on.
● Advanced line-handling functions provide call acceptance, rejection, redirection, and other operations. These
are most useful in an environment where the phone line is connected to a central switch instead of directly to
the external telephone service provider.
● Advanced call features functions provide Call Hold, Transfer, Park, Forward, and Pickup capabilities. These
functions only work if the telephone line supports the advanced call features.
● Miscellaneous advanced features functions provide added features specific to TAPI service requests, such as
monitoring lines and setting call parameters.
Table 23.4 shows all the Supplemental Telephony API functions for the advanced line device features.
Table 23.4. The Supplemental Telephony API set for line devices.
Function Group API Call Description
Enables or disables digit
Digit and tone handling lineMonitorDigits detection notification on
a specified call.
Performs the buffered
LineGatherDigits gathering of digits on a
call.
Specifies which tones to
LineMonitorTones
detect on a specified call.
Generates inband digits
LineGenerateDigits
on a call.
Generates a given set of
LineGenerateTone
tones inband on a call.
Accepts an offered call
and starts alerting both
Advanced call handling lineAccept
caller (ring-back) and
called party (ring).
Redirects an offering call
LineRedirect
to another address.
Secures an existing call
from interference by
LineSecureCall other events such as call-
waiting beeps on data
connections.
Places a call completion
LineCompleteCall
request.
Cancels a call completion
LineUncompleteCall
request.
Places the specified call
Call hold lineHold
on hard hold.
LineUnhold Retrieves a held call.
Prepares a specified call
Call transfer lineSetupTransfer for transfer to another
address.
Transfers a call that was
set up for transfer to
LineCompleteTransfer
another call, or enters a
three-way conference.
Transfers a call to
LineBlindTransfer
another party.
Swaps the active call
LineSwapHold with the call currently on
consultation hold.
Prepares a given call for
Call conference lineSetupConference the addition of another
party.
Prepares to add a party to
an existing conference
call by allocating a
consultation call that can
LinePrepareAddToConference
later be added to the
conference call that is
placed on conference
hold.
Adds a consultation call
LineAddToConference to an existing conference
call.
Removes a party from a
LineRemoveFromConference
conference call.
Parks a given call at
Call park linePark
another address.
LineUnpark Retrieves a parked call.
Sets or cancels call
Call forwarding lineForward
forwarding requests.
Picks up a call that is
alerting at another
number. Picks up a call
alerting at another
Call pickup linePickup destination address and
returns a call handle for
the picked up call
(linePickup can also
be used for call waiting).
Sends user-to-user
Miscellaneous advanced information to the remote
lineSendUserUserInfo
features party on the specified call
(ISDN only).
Specifies the terminal
device to which the
LineSetTerminal specified line, address
events, or call media
stream events are routed.
Requests a change in the
LineSetCallParams call parameters of an
existing call.
Enables or disables
LineMonitorMedia media mode notification
on a specified call.
Sets up a call's media
LineSetMediaControl
stream for media control.
Sets the media mode(s)
of the specified call in its
LineSetMediaMode
LINECALLINFO
structure.
The Supplemental Telephony API also provides function calls for the handling of phone devices. To TAPI, any device
that can place or accept calls can be a phone device. The phone device API set allows programmers to invent their own
phone devices in code. In effect, you can create a virtual handset using the TAPI phone device. This allows properly
equipped workstations to act as single- or multiple-line phones in an office environment. If your pc has appropriate audio
input and output hardware (speakers, sound card, microphone, and so on) and is connected to the telephone service, you
can create a "handset" using the phone device API set.
The Supplemental Telephony API set for phone devices can be divided into the following function groups:
● Basic phone-handling functions provide basic initialization and shutdown, opening and closing a phone device,
and ringing the open device.
● Phone settings and status functions allow programmers to read and write various settings of the phone device
such as volume, gain, hookswitch behavior, and so on.
● Physical display, data, button, and lamp functions can be used to read and write display information to desktop
units. Since TAPI can be used to support more than just pc workstations, these functions allow a central TAPI
program to monitor and update LCD displays, to flash lamps, to change buttons labels, and to store and retrieve
data from desktop terminals.
Table 23.5 shows all the Supplemental Telephony phone device API calls along with short descriptions of their use.
Just as the line device API set has a series of data structures, the phone device set also has related data structures. These
structures are used to pass information between the desktop program and the TAPI service provider.
The phone device structures most often used are the PHONECAPS and PHONESTATUS structures. Table 23.6 shows all
the phone device structures along with brief descriptions of their use.
The Supplemental Telephony phone device also uses a callback function to register a function address to receive
Windows messages. This callback address is established during the phoneInitialize API call.
Note
The fact that TAPI uses callbacks for messages means that any high-level
languages such as Visual Basic must use either a DLL or OCX or establish
the callback link or use some other tool that can capture windows messages.
A sample OCX is included on the CD-ROM that ships with this book. This
OCX is used throughout the book to show how you can link Visual Basic
and other VBA-compliant languages to TAPI services.
Each message returns the same set of parameters. The first is the handle of the phone device. The second parameter is
the callback instance value. This value will always be the instance handle of the current running application. The next
three values vary depending on the message. One or more of these return values will contain non-zero data. Table 23.7
contains a list of the Basic Telephony messages, their parameters, and short descriptions.
The last level of Telephony services is Extended Telephony. Extended Telephony service allows hardware vendors to
define their own device-specific functions and services and still operate under the TAPI service model. By adding a
small set of extended service API functions, Microsoft allows hardware vendors to continue to provide unique services
not previously defined by TAPI. The TAPI model defines both line and phone device API calls for Extended Telephony.
Table 23.8 shows the Extended Telephony API set along with short descriptions of their use.
The actual meaning and use of extended TAPI calls is defined by the service provider or hardware vendor. Extended
Telephony providers define the parameters of the calls and their meaning, and publish this information to the
programmer. The programmer can then check the version information with the service provider before attempting to
make an extended service call.
Summary
In this chapter you learned how the TAPI service model is implemented as a set of API calls. You learned there are four
levels of TAPI services:
You also learned that the TAPI function set defines two different devices to handle telephony services:
● A line device is used to control the connection between a data source and the physical telephone line.
● A phone device is used to control the connection between a desktop handset and a line device.
Finally, you reviewed the API calls, data structures, and Windows messages defined for each level of TAPI service.
In the next chapter, you'll learn the basic programming steps needed to create a TAPI application.
Chapter 27
CONTENTS
● Summary
In this chapter you'll learn how the TAPI system uses entries in the TELEPHON.INI file to determine what TAPI service providers are
installed, how to handle outbound dialing rules, the current dialing location, and which providers are to be used for TAPI service requests.
Almost all of this information is updated from the various configuration dialog boxes available through calls to TAPI functions. As part of
this chapter, you'll build a short program that calls the TAPI Line dialog boxes and allows you to inspect the resulting changes to the
TELEPHON.INI file.
Before getting into the details of how the TELEPHON.INI file is used for TAPI services, let's build a short Visual Basic program that gives
you easy access to the TAPI dialog boxes that affect the file, and a quick TELEPHON.INI viewer.
Note
If you do not have Visual Basic on your machine or you want to skip over this section, you
can find the completed program (TAPIDLG.EXE) on the CD-ROM that comes with this
book. You can use this compiled version of the program to follow along with the rest of the
chapter.
Creating the TAPI Dialog Utility project takes only a few controls and a small amount of code. The following two sections of the chapter will
walk you through the details of building this handy utility.
Start by loading Visual Basic 4.0 and creating a new project. Next, you need to lay out the form. Refer to Table 27.1 and Figure 27.1 as
guides. Be sure to load the TAPILINE.OCX into your toolbox before you start coding. Also, make sure you add the frame control before
you add the option buttons, and paint the option buttons within the frame control. That way they will be registered as child controls of the
frame.
Figure 27 1 : Laying out the TAPI Dialog Utility project
Tip
If you plan to do a lot of coding with the TAPILINE control, add it to your startup project.
Open AUTO32LD.VBP (or AUTO16LD.VBP) and add the TAPILINE control. Then save
the project. Now every new project you create will have the TAPILINE control in the
toolbox.
Once you complete the layout of the dialog box, save the form as TAPIDLG.FRM and the project as TAPIDLG.VBP. Now you're ready to
add code to the form.
There isn't much code to add here, just a few lines to initialize TAPI and then some code to respond to user selections on the option buttons,
command buttons, and the list control.
First, add a new subroutine called StartTAPI to the form. This will hold the code that initializes the TAPI session and confirms the
available devices. Listing 27.1 shows the code for the StartTAPI routine. Add this to your project.
As you can see, this routine just calls the lineInitialize method, gets the total number of devices, and then performs the
lineNegotiateAPIVersion method for all the available TAPI line devices. In the process, the line devices are loaded into the list box
for the user.
Next you need to add some code to the Form_Load event. This code is executed when the program first starts. Listing 27.2 shows the code
you need to add to your project.
The code in the Form_Load event sets the form caption, centers the form, and then calls the StartTAPI routine to initialize TAPI
services.
The really important code is in the cmdButton_Click event. This is the code that handles the user's dialog box selections. Listing 27.3
shows the code needed for the cmdButton_Click event. Add it to your project.
Listing 27.3. Adding the cmdButton_Click event code.
Tip
If you do not have a copy of NOTEPAD.EXE, or it is in some directory other than your
Windows directory, change the Shell call in Listing 27.3 to point to your favorite ASCII
editor.
The first few lines of code make sure one of the line devices in the list box has been selected. If not, it automatically selects the first one in
the list. Next, the SELECT CASE statements determine which button has been pressed.
If the Exit button was pressed, the program executes the lineClose method before unloading the form. This is not strictly needed, since
this project does not open any available line devices. However, it is a good practice to release any remaining TAPI resources before exiting.
If the user selects the Apply button, the program checks to see which option button was selected and then calls the requested dialog box.
Notice that the call to the Translate dialog box contains an optional dialing address. This will be displayed in the lower left of the dialog box
to show the user how the dialing rules will be applied to the selected phone number.
If the user selects the Telephon.ini button, the program launches a shell operation which calls NOTEPAD.EXE and loads the
TELEPHON.INI file for viewing. You can use this option to view the results of editing the various settings in the dialog boxes.
The last bit of code to add to the project is a single line behind the List1_DblClick event. This will automatically launch the selected
dialog box each time the user double-clicks the item in the list. Add the code shown in Listing 27.4 to your project.
That's all the code you need. Save this form (TAPIDLG.FRM) and project (TAPIDLG.VBP) and then test it out. When running the project,
you should see a list of available line devices in the list box and be able to call up each of the TAPI dialogs for the various devices (see
Figure 27.2).
Once you are sure the program is working correctly, compile it (TAPIDLG.EXE) to disk. You'll use the TAPIDLG.EXE program in the next
several sections of the chapter.
The TAPI system uses the TELEPHON.INI file to hold important control parameters. Even though the TAPI system is implemented on
Win95 and WinNT, the control values are not stored in the system registry. This is because TAPI was originally deployed as part of the
Windows 3.1 operating system. Also, as of this writing, all TAPI service providers (TSPs) are 16-bit applications. For these reasons, the old
16-bit INI file is still used by TAPI.
Most of the TELEPHON.INI file is updated through TAPI dialog boxes. In fact, it is not a good idea to modify the INI file directly, since
this may result in invalidating your TAPI installation. Still, you can call up the TELEPHON.INI file using NOTEPAD.EXE (or some other
ASCII editor) and view the contents. This is a good way to learn a lot about how TAPI services are managed by the Windows operating
system.
Warning
If you are working with the old TAPI 1.3 system on Windows 3.1 machines, your
TELEPHON.INI file will look quite different. In fact, you cannot use the TELEPHON.INI
file from TAPI 1.3 for TAPI 1.4 installations. Any attempt to do so will cause an error when
you try to start any TAPI service. All the material covered here is based on TAPI 1.4
TELEPHON.INI file layouts.
● The Service Provider section holds information on all the Telephony Service Providers (TSPs) installed on the workstation.
● The HandOff Priorities section holds information about which line devices can support which media modes and the order in which
the line devices should be called.
● The Location section holds information on the dialing location of the workstation. TAPI allows multiple locations to be defined for
a single workstation. That way a user with a laptop can set up several location definitions and easily switch from one to the other
when traveling.
● The Credit Card section holds dialing instructions for using telephone service credit cards to control billing. TAPI ships with a
number of standard credit card instructions, but you can also add your own, if needed.
The rest of this chapter reviews each of the four main sections and shows you examples of how to modify the information using the TAPI
dialog utility you built earlier in the chapter.
Tip
Now is a good time to load the TAPIDLG.EXE file you created earlier. If you did not enter
and compile the program described above, you can find a completed version of it on the CD-
ROM, which you can load instead.
The TAPI service provider information is actually stored in two separate sections of the TELEPHON.INI file. The [Providers] section
lists all the TSPs installed on the machine. This section gives each of them a unique ID value. Each TSP then has its own section in the
TELEPHON.INI file. That section is named based on the provider ID that appears in the [Providers] section. The provider-specific
section contains data about the services supported by the TSP and any other TSP-related data for that provider.
TAPI uses this information to initialize all available service providers when the lineInitialize method is first invoked on the
workstation.
The [Providers] section of the TELEPHON.INI file has three main parts:
Listing 27.5 shows how a typical [Providers] section of a TELEPHON.INI file looks.
[Providers]
NumProviders=2
NextProviderID=5
ProviderID0=1
ProviderFilename0=unimdm.tsp
ProviderID1=4
ProviderFileName1=esp.tsp
In the example shown in Listing 27.4, there are two TSPs active on the workstation, the UNIMDM.TSP and the ESP.TSP. They are given
the provider IDs "0" and "4" respectively. You can see that the next TSP added to the workstation will be given the ID "5." The values in this
section are altered by the TSPs themselves.
There is no dialog box from TAPI that makes changes to these settings. Changing these settings manually can cause your system to lock up
when you start TAPI services.
For each ProviderFileName, ProviderID pair in the [Providers] section, there is a separate section header that bears the
ProviderID value. This section contains entries used to describe the properties of the lines and phones supported by the service provider.
Listing 27.6 shows the set of provider sections that match the [Providers] section that appears in Listing 27.5.
Listing 27.6. Example provider-specific sections.
[Provider1]
NumLines=1
NumPhones=0
[Provider4]
NumLines=8
NumPhones=4
In Listing 27.6, Provider1 supports only one line device and no phone devices. Provider4 supports up to eight lines and four phones.
The entries shown in Listing 27.6 are the required minimum entries for each provider-specific section. TSPs can also add other values to their
sections if they wish. These values could identify individual lines and phones, provide additional configuration settings used by the TSP, and
so on. Microsoft suggests TSPs establish a system for identifying line or phone parameters similar to the one used for identifying providers.
For example, each line could have an ID and name value along with any special settings. Listing 27.7 shows how this might look in the
TELEPHON.INI file.
[Provider1]
NumLines=2
NumPhones=1
NextLineID=2
NextPhoneID=1
LineID0=1
LineName1=MCA's Data Line
LinePort1=COM1
LineInitString1=ATH0
LineID1=2
LineName2=Voice Line
LinePort2=COM2
LineInitString2=
Notice the use of the NextLineID and NextPhoneID settings. This will allow the TSP to add future lines or phones with unique values.
The additional settings here are related to communications ports and initialization strings, but they could be anything at all that the TSP needs
to manage the TAPI service on the line.
The main thing to keep in mind about the provider settings is that they are managed by the Telephony service provider application-not the
TAPI.DLL or your own desktop applications. All settings that appear here are the result of actions by the TSPs. You run the risk of trashing
your TAPI installation if you meddle with these values!
When there is more than one TAPI application registered on the workstation, the TAPI system must have some process for deciding which
service request is handled by which TAPI application. The [HandOffProrities] section of the TELEPHON.INI file helps TAPI
handle this situation. The [HandOffPriorities] section lists all the applications that can handle inbound and outbound calls. Also, the
applications are listed in preference order. The first items on the list should be called before the last items on the list.
Each entry in the [HandOffPriorities] section lists the type of TAPI service request followed by one or more registered programs
capable of handling the request. Listing 27.8 shows a typical [HandOffPriorities] section from the TELEPHON.INI file.
Listing 27.8. A typical [HandOffPriorities] section.
[HandoffPriorities]
datamodem=rasapi32.dll,
interactivevoice=AMENGINE.DLL,MSPHONE.EXE,
automatedvoice=AMENGINE.DLL,
RequestMakeCall=C:\PROGRA~1\MICROS~3\MSPHONE.EXE,C:\WINDOWS\DIALER.EXE,
Listing 27.8 shows that the workstation can support four TAPI service request types:
You'll notice that the last entry in the section (RequestMakeCall) shows that there are two different programs on the workstation that can
handle the TAPI service request. In this case, when a program makes a call to the RequestMakeCall TAPI function, TAPI will attempt to
hand the request to the MSPHONE.EXE application first. If that application does not respond (it is busy with another call or not working
properly), then TAPI will attempt to pass the service request to DIALER.EXE.
The [HandOffPriorities] section can contain several different entries, each corresponding to a media mode recognized by TAPI.
Listing 27.9 shows a prototype of the [HandOffPriorities] section with all the known media modes and a sample extended media
mode listed.
[HandoffPriorities]
RequestMakeCall=[<appname>[,<appname>]...]
RequestMediaCall=[<appname>[,<appname>]...]
unknown=[<appname>[,<appname>]...]
interactivevoice=[<appname>[,<appname>]...]
automatedvoice=[<appname>[,<appname>]...]
g3fax=[<appname>[,<appname>]...]
g4fax=[<appname>[,<appname>]...]
datamodem=[<appname>[,<appname>]...]
teletex=[<appname>[,<appname>]...]
videotex=[<appname>[,<appname>]...]
telex=[<appname>[,<appname>]...]
mixed=[<appname>[,<appname>]...]
tdd=[<appname>[,<appname>]...]
adsi=[<appname>[,<appname>]...]
digitaldata=[<appname>[,<appname>]...]
Note
TAPI also allows TSPs to define their own media modes and place them in the
[HandOffPriorities] section. These settings are important only to those developing
TSPs and are not covered in this book. For more on TSPs and extended media modes, you
can refer to the Win32 Extensions documentation that ships with Microsoft Visual C++, or
versions of the TAPI documentation that appear on the MSDN Professional Level CD-
ROMs.
Like the provider section, the values in this section are manipulated by the TAPI applications that are designed to fulfill TAPI service
requests. If you develop an application that is designed to process a particular type of media, you can place its name in this section. Most of
the examples covered in this book are designed to make TAPI requests, not respond to them. For this reason, you will not be making any
changes to the entries in the [HandOffPriorities] section of the TELEPHON.INI file.
The next important section is the [Locations] section. This section holds information on the current location from which the workstation
is placing calls. The values here are used to determine how to actually dial the telephone numbers provided to TAPI. For example, TAPI will
attempt to determine if the telephone number can be handled using local dialing rules, long distance rules, or international rules.
The TAPI system allows users to define more than one location entry for the workstation. This way, users can move their computer to
different locations and not have to reinstall or reinitialize the TAPI location each time. The best example of this is a laptop user who travels
to different locations, but still wants to use TAPI services to place and receive calls. When the user arrives at a new location, the TAPI
system need only be informed of the current location (and its dialing rules) and all applications will behave as normal, without any changing
of phone numbers or dialing rules.
There are three main entries in the [Locations] section of the TELEPHON.INI:
● Locations-This entry tells TAPI how many locations are defined in the [Locations] section.
● CurrentLocation-This entry tells TAPI which of the location entries is currently selected.
● Location-This entry contains information about the dialing parameters for the defined location. There is one of these entries for
each location defined in the [Locations] section.
[Locations]
CurrentLocation=5,3
Locations=4,6
Location0=0,"Default Location","","","606",1,0,0,1,"",0,""
Location1=1,"Office","","","513",1,0,0,0,"",0,""
Location2=3,"Oak Ridge, TN","","","423",1,0,0,0,"",0,""
Location3=5,"Sweden Office","9","8","013",46,1,0,0,"",0,""
The first entry in the section (CurrentLocation) tells TAPI that the current location is location index "5". The second parameter tells
TAPI apps that they can find location index "5" by looking at the third location item in the list. This speeds selection of the record.
The second entry in the section (Locations) tells TAPI how many locations are defined here (4) and what the index value of the next
location will be ("6"). This is used when adding new locations to the list.
The rest of the entries in the [Locations] section contain information about each defined location for this workstation. Listing 27.11
shows the prototype for all location entries in the TELEPHON.INI file.
Location<index>=<LocationID>,"<FriendlyName>","<LocalPrefix>","<LDPrefix>","<AreaCode>",
<CountryCode>,
➂<PreferredCardID>,<CardHint>,<InsertAreaCode>,"<TollPrefixes>",
<TonePulseDialing>,
➂<DisableCallWaiting>
Table 27.2 shows the list of parameters in the location line along with a short description of their use and meaning. The parameters are listed
in the order in which they appear in the Location entry.
You can adjust the values for location entries by using the lineTranslateDialog method of the TapiLine control. To test this out, run
the TAPIDLG.EXE program and select the Telephon.ini button to view the current settings for the [Location] section. Figure 27.3
shows what your screen might look like at this point.
Now close the instance of NOTEPAD.EXE (be sure not to save any incidental changes you might have made). Now select the Line
Translate Dialog option button and press Apply. This will bring up the Dialing Properties dialog box. You are ready to add a new
location entry into the TELEPHON.INI file.
Press the New... button and create a new location. Use the information in Table 27.3 and Figure 27.4 to set the location parameters.
After you enter and save the new location data, press the Telephon.ini button to bring up the TELEPHON.INI file. Find the new entry
in the [Location] section to see how the dialog box updated the INI file. Your new entry should look like the one in Listing 27.12.
Location4=6,"TAPI Book","9","8","999",1,2,0,1,"",0,"1170,"
You have now used the TAPI dialog boxes to add new locations to your TELEPHON.INI file. Next you'll use the same dialog box to add
calling card information for the location.
Another very important section in the TELEPHON.INI file is the [Cards] section. This section holds information on the calling cards
defined for this workstation. The first entry in the [Cards] section tells TAPI how many calling card definitions are in the section and the
next available entry ID. Each additional entry in the [Cards] section represents one set of calling parameters. Listing 27.13 shows what a
typical [Cards] section of the TELEPHON.INI file looks like.
[Cards]
Cards=23,23
Card0=0,"None (Direct Dial)","","","","",1
Card1=1,"AT&T Direct Dial via 10ATT1","","G","102881FG","10288011EFG",1
Card2=4,"MCI Direct Dial via 102221","","G","102221FG","10222011EFG",1
Card3=5,"MCI via 102220","","G","102220FG$TH","1022201EFG$TH",1
Card4=6,"MCI via 1-800-888-8000","","G","18008888000,,,,,,TH,,FG","
➂18008888000,,,,,,TH,,011EFG",1
Card5=7,"MCI via 1-800-674-0700","","G","18006740700,,,,,,TH,,FG","
➂18006740700,,,,,,TH,,011EFG",1
Card6=8,"MCI via 1-800-674-7000","","G","18006747000,,,,,,TH,,FG","
➂18006747000,,,,,,TH,,011EFG",1
Card7=9,"US Sprint Direct Dial via 103331","","G","103331FG","10333011EFG",1
Card8=10,"US Sprint via 103330","","G","103330FG$TH","1033301EFG$TH",1
Card9=11,"US Sprint via 1-800-877-8000","","G","18008778000,,,T0FG,,H","
➂18008778000,,,T01EFG#,H",1
Card10=12,"Calling Card via 0","","G","0FG$TH","01EFG$TH",1
Card11=13,"Carte France Telecom","","T3610,H,G#","T3610,H,16,FG#","
➂T3610,H,19,EFG#",1
Card12=14,"Mercury (UK)","","0500800800$TH,0FG","0500800800$TH,0FG","
➂0500800800$TH,0FG",1
Card13=15,"British Telecom (UK)","","144$H,0FG","144$H,0FG","144$H,010EFG",1
Card14=16,"CLEAR Communications (New Zealand)","","0502333$TH,OFG"," ➂0502333
$TH,0FG","0502333$TH,00EFG",1
Card15=17,"Telecom New Zealand","","012,0FG?H","012,0FG?H","012,00EFG?H",1
Card16=18,"Global Card (Taiwan to USA)","","G","0FG","0080,102880$TFG$H",1
Card17=19,"Telecom Australia via 1818 (voice)","","1818$TH,FG#","1818$TH,FG#","
➂1818$TH,0011EFG#",1
Card18=20,"Telecom Australia via 1818 (fax)","","1818$TH,FG#","1818$TH,FG#","
➂1818$TH,0015EFG#",1
Card19=21,"Optus (Australia) via 1812","","FG","FG","1812@TH,0011EFG",1
Card20=22,"Optus (Australia) via 008551812","","FG","FG","008551812@TH,0011EFG",1
Card21=3,"AT&T via 1-800-321-0288","634567890","G","18003210288$TFG$TH","
➂18003210288$T01EFG$TH",1
Card22=2,"AT&T via 10ATT0","42345678","G","102880FG$TH","1028801EFG$TH",1
Every calling card entry has seven parts. Listing 27.14 shows the prototype entry for the [Cards] section. Table 27.4 shows the [Cards]
entry parameters and short descriptions of their use.
Card<index>=<CardID>,"<FriendlyName>","<ScrambledCardNum>","<SameArea>","<LongDistance>",
➂"<International>",<Hidden>
Several entries in the [Cards] section contain dialing rules used by TAPI. These rules are expressed in a series of codes that appear as part
of the dialing string. The dialing string can contain the standard values of "0" through "9" along with many other values. These values can be
divided into three groups:
Dialable digits or tones include 0 through 9, A through D, and # and *. TAPI recognizes several pause or control codes and insertion codes.
Table 27.5 shows the valid control and insertion codes for TAPI dialing strings.
Table 27.5. Valid control and insertion codes for dialing strings.
Dialing Code Description
T Use tone (DTMF) dialing
P Use pulse dialing
! Flash the hookswitch (to make a transfer)
, Pause (for about one second)
W Wait for second dial tone (for DID calls)
@ Wait for quiet answer (call pick-up only)
$ Wait for "bong" tone (for billing cards)
E Insert the country code
F Insert the area code
G Insert the phone number
H Insert the calling card number
I Optionally insert the area code
You can use the information in Table 27.5 to decipher entries in the [Cards] section of the TELEPHON.INI file. For example, the dialing
rules for placing calls using the card entry shown in Listing 27.15 indicate the dialing rules for local, long distance, and international calls.
Local calls are placed by simply dialing the phone number ("G"). Long distance calls require dialing "102881" plus the area code ("F") and
the phone number ("G"). While the international calls must be placed using "10288011" plus the country code ("E"), the area code ("F"), and
the phone number ("G").
You can check the effects of TAPI dialog boxes on the long distance dialing rules by clicking the Dial Using Calling Card check
box and selecting a calling card to use. Fill in the calling card number as eight 9s ("99999999"). Refer to Figure 27.5. Then click OK to save
the calling card data and OK to save the location data.
After saving the data, press the Telephon.ini button to bring up the TELEPHON.INI file and find the [Cards] entry you modified. It
should look something like the one in Listing 27.16.
Notice that the dialog box scrambled the calling card number before saving it to the INI file
.
Warning
Be sure to restore your location settings after you finish with this chapter. If you do not, your
TSPs will not know how to place a call from your "experimental" location.
Summary
In this chapter you learned a bit about how TAPI works behind the scenes to place call requests generated by your programs. You learned
that the TAPI system keeps track of vital information in the TELEPHON.INI file kept in the WINDOWS folder on the workstation. You
learned there are four main sections in the TELEPHON.INI file:
● The Service Provider section holds information on all the Telephony Service Providers (TSPs) installed on the workstation.
● The HandOff Priorities section holds information about which line devices can support which media modes and the order in which
the line devices should be called.
● The Location section holds information on the dialing locations of the workstation.
● The Credit Card section holds dialing instructions for using telephone service credit cards to control billing.
You learned the different predefined media modes that can be handled by registered TAPI applications in the [HandOffPriorities]
section. You also learned the dialing codes that are used in the [Cards] section to tell TAPI how to place requested calls.
Finally, you built a small Visual Basic application that allowed you to gain direct access to the various TAPI line dialog boxes that can affect
the TELEPHON.INI file.
In the next chapter, you'll learn how the Telephony Service Providers work and how they fit into the TAPI model.
Chapter 26
CONTENTS
● Summary
In this chapter, you'll learn how to use a special custom control, the TAPILine control, to gain access to TAPI services
from within your Visual Basic 4.0 programs. You'll also learn about the TAPILine control's properties and methods,
and how to use a series of accompanying DLL calls that allow you to pass data structures between your Visual Basic
program and the TAPI32.DLL.
After a review of the control itself, you'll build a demonstration project in Visual Basic 4.0 that will use the methods,
properties, and DLL calls you learned about in the first part of the chapter. In the process you'll learn how to access all
the major operations of Basic Telephony from within your Visual Basic programs.
When you complete this chapter, you'll know how to use the new control and how to add Basic Telephony services to
your Visual Basic applications.
Accessing all but the simplest TAPI services requires the use of a notification callback (also known as a sink). Because
Visual Basic is not capable of passing a callback address to TAPI, there is a special TAPI control included with this
book, called TAPILINE.OCX, which allows the TAPI.DLL to notify your Visual Basic program whenever a critical
event happens during a TAPI operation. This is done through an OCX event called TAPICallBack.
There is also a handful of properties that can be set or read. These allow you to get information from the TAPI services
about the application ID, the line devices, and the identifying number of the call that is currently in progress at your
workstation.
TAPILINE.OCX has a wealth of methods. In fact, TAPILINE.OCX includes every single method for TAPI line
devices. You can use these methods to invoke dialog boxes, call TAPI functions, and get and read critical TAPI settings
and return values.
The TAPILINE development kit also includes a DLL file that is used to pass data structures between your Visual Basic
program and the TAPI system. This DLL file is needed because Visual Basic cannot use API calls to pass structured data
directly. Rather than create routines that handle long byte arrays, you'll use DLL helper functions to read and write data
structures used by the TAPI system.
Before you can use the TAPILINE control in your projects, you must add several files to your WINDOWS\SYSTEM
folder. Table 26.1 shows the files you must add:
Table 26.1. Required files for using TAPILINE.OCX with Visual Basic.
File Name Description
This is the actual OLE control that appears in your Visual Basic
TAPILINE.OCX
tool box.
TAPILINE.LIC A short license file to verify you have a legal copy of TAPILINE.
TAPILINE.OCA An additional control file used by Visual Basic.
A type library file. You can use this to access TAPILINE services
TAPILINE.TLB
without loading the control on the form.
The DLL that holds supporting routines for passing structured data
PSTAPIDLL32.DLL
between Visual Basic and TAPI.
The DLL that provides support for Microsoft Foundation Class
MCf40.DLL
services.
MSVCRT40.DLL The DLL that supports Microsoft Visual C++.
You can find these files in the TAPISYS and RUNTIMEC folders on the CD-ROM that ships with this book.
Warning
It is possible that you have the last two files already in your WINDOWS
\SYSTEM directory. If you already have them, do not copy them from the
CD-ROM to your system.
After you copy the files to the WINDOWS\SYSTEM directory, you can start up Visual Basic 4.0 and add the TAPILINE
control to your tool box. To do this, select Tools | Custom Controls... from the main menu. A dialog box
will appear showing all the controls that Visual Basic knows about. You should see one called "TAPILINE OLE Custom
Control Module" (see Figure 26.1).
Figure 26.1 : Loading the TAPILINE custom control
Once you locate the control, click it to turn the check mark on and then press OK to close the dialog box.
Tip
If you do not see the TAPILINE.OCX listed when you open the Tools
| Custom Controls... dialog box, select Browse... and load the
control from the WINDOWS\SYSTEM folder. Then you can select it from
the list and close the dialog box.
After loading the TAPILINE control into your toolbox, you'll be able to double-click the control to load it onto a Visual
Basic form. You'll notice that you cannot resize the control once it is on your form. You will also notice that it
disappears when you run your Visual Basic program. This is normal behavior for the TAPILINE control (see Figure
26.2).
The one event recognized by the TAPILINE.OCX control is the TapiCallBack event. This event fires each time the
TAPI system sends out a message. The TapiCallBack event receives a device ID, the message ID and program
instance, and three additional parameters. It is up to the Visual Basic program to respond to the messages appropriately.
Listing 26.1 shows how the event declaration appears within Visual Basic.
End Sub
The TAPI system can generate 14 different messages that will be passed to the TapiCallBack event. Listing 26.2
shows the message names along with their unique values. These are the values that Visual Basic must check for in the
TapiCallBack event.
'
' Line callback messages
'
Global Const LINE_ADDRESSSTATE = 0&
Global Const LINE_CALLINFO = 1&
Global Const LINE_CALLSTATE = 2&
Global Const LINE_CLOSE = 3&
Global Const LINE_DEVSPECIFIC = 4&
Global Const LINE_DEVSPECIFICFEATURE = 5&
Global Const LINE_GATHERDIGITS = 6&
Global Const LINE_GENERATE = 7&
Global Const LINE_LINEDEVSTATE = 8&
Global Const LINE_MONITORDIGITS = 9&
Global Const LINE_MONITORMEDIA = 10&
Global Const LINE_MONITORTONE = 11&
Global Const LINE_REPLY = 12&
Global Const LINE_REQUEST = 13&
Note
Chapter 23, "TAPI Architecture," describes the TAPILINE messages, their
parameters, and their use.
The TAPILINE.OCX has 12 unique, TAPI-related properties. TAPILINE uses these properties to keep track of
important handles, ID values, and other settings. You can read or write these values using standard Visual Basic code or
manipulate them at design time through the Properties window of Visual Basic. Table 26.2 shows the 12 TAPI-related
properties along with short descriptions.
Most of these values are set at run-time by TAPI. You can read them to get status information about the state of a TAPI
service request. As you will see in the example project built later in this chapter, there are a few properties here which
you can manipulate safely during run-time. However, most should be treated as read-only properties.
The TAPILINE.OCX has numerous methods that can be used to make TAPI service requests. In fact, all of the TAPI
line functions have been recreated as methods for the TAPILINE.OCX control. Instead of listing all the methods here,
you can use the TAPILINE.HLP file that ships with the TAPILINE.OCX control. This file contains detailed
information on each of the methods. To use the help file as a reference, click the TAPILINE.HLP file and then press
the Help Topics button to bring up the search dialog box. You can browse the search list or type in search strings to
locate the desired function. Figure 26.3 shows you how the help file looks when it is first loaded.
Tip
You can also refer to Chapter 23, "TAPI Architecture," to get a complete
list of all TAPILINE functions, including short descriptions.
The TAPI system makes extensive use of data structures. In fact, several of the structures are variable in length. These
are difficult to pass between Visual Basic and Windows DLLs or OCX controls. In order to simplify the process, the
TAPILINE control comes with a helper DLL called PSTAPIDLL32. This DLL contains 17 functions that are used to
pass data structures. Listing 26.3 shows a sample of the API declarations used to link these helper functions to your
Visual Basic programs.
Some of the DLL calls have a third parameter called sExtraData. This parameter contains string information that will
be used to augment the structured data sent in the same call. This is how the TAPILINE control passes variable-length
structures between Visual Basic and the TAPI system.
You'll see all 17 of the helper DLL calls in the next section of the chapter when you build the TAPILine Test
application.
The TAPILINE Test project will show you how to build TAPI applications using Visual Basic 4.0. You'll see how to
use the TAPILINE control and the PSTAPIDLL32 dynamic link library to perform TAPI service requests. You'll see
how these two added resources (along with a bit of Visual Basic code) can give you extensive access to the TAPI
services built into every Win95 and WinNT machine.
Before you can begin running TAPI applications from Visual Basic, you need a few support modules that contain DLL
declarations, structured data types, global constants, and several internal help routines to decipher the messages returned
by TAPI.
Once you have the DLL declarations, the line constants and structures, and the helper functions, you are ready to lay out
the TAPILINE Test form and place code behind several buttons to see how TAPI services work from within Visual
Basic programs.
Creating the support code of declarations, data structures, and constants is a big (and tedious) job. Instead of asking you
to type all the code yourself, a set of support modules is included on the CD-ROM that accompanies this book. You can
find the code support routines in the TAPILINE\VBTAPI folder on the CD-ROM. Table 26.3 lists the Visual Basic
support modules along with short descriptions of their uses.
Note
The TAPILINE.OCX can only handle line devices. The TAPIPHON.BAS
module is included to help you if you wish to develop additional OCX or
DLL code to support phone device programming from Visual Basic. It is
possible that TAPILINE will support phone device programming in the
future.
Copy the modules listed in Table 26.3 onto your own hard disk and use them whenever you use the TAPILINE.OCX
file. The next section of the chapter reviews the modules listed to help you understand what is in them and to give you a
good idea of how they work.
The first code in the TAPILINE.BAS module is the set of DLL declarations needed to call the TAPILINE helper
functions for passing structured data. Listing 26.4 shows the code that handles the DLL declarations.
'
' DLL declares for TAPILine control
'
Declare Sub LineCallParamsFunc Lib "pstapidll32" (ByVal nMode As
Long, structdata ➂As LINECALLPARAMS, ByVal ExtraData As String)
Declare Sub LineForwardListFunc Lib "pstapidll32" (ByVal nMode As
Long, structdata ➂As LINEFORWARDLIST, ByVal ExtraData As String)
Declare Sub LineGenerateToneFunc Lib "pstapidll32" (ByVal nMode As
Long, structdata ➂As LINEGENERATETONE_TYPE, ByVal ExtraData As
String)
Declare Sub LineAddressCapsFunc Lib "pstapidll32" (ByVal nMode As
Long, structdata ➂As LINEADDRESSCAPS, ByVal ExtraData As String)
Declare Sub LineCallInfoFunc Lib "pstapidll32" (ByVal nMode As Long,
structdata As ➂LINECALLINFO, ByVal ExtraData As String)
Declare Sub LineCallStatusFunc Lib "pstapidll32" (ByVal nMode As
Long, structdata ➂As LINECALLSTATUS, ByVal ExtraData As String)
Declare Sub LineCallListFunc Lib "pstapidll32" (ByVal nMode As Long,
structdata As ➂LINECALLLIST, ByVal ExtraData As String)
Declare Sub LineDevCapsFunc Lib "pstapidll32" (ByVal nMode As Long,
structdata As ➂LINEDEVCAPS, ByVal ExtraData As String)
Declare Sub VarStringFunc Lib "pstapidll32" (ByVal nMode As Long,
structdata As ➂VARSTRING, ByVal ExtraData As String)
Declare Sub LineDevStatusFunc Lib "pstapidll32" (ByVal nMode As
Long, structdata As ➂LINEDEVSTATUS, ByVal ExtraData As String)
Declare Sub LineTranslateCapsFunc Lib "pstapidll32" (ByVal nMode As
Long, ➂structdata As LINETRANSLATECAPS, ByVal ExtraData As String)
Declare Sub LineMonitorToneFunc Lib "pstapidll32" (ByVal nMode As
Long, structdata ➂As LINEMONITORTONE)
Declare Sub LineExtensionIDFunc Lib "pstapidll32" (ByVal nMode As
Long, structdata ➂As LINEEXTENSIONID)
Declare Sub LineDialParmsFunc Lib "pstapidll32" (ByVal nMode As
Long, structdata As ➂LINEDIALPARAMS)
Declare Sub LineMediaControlDigitFunc Lib "pstapidll32" (ByVal nMode
As Long, ➂structdata As LINEMEDIACONTROLDIGIT)
Declare Sub LineTranslateOutputFunc Lib "pstapidll32" (ByVal nMode
As Long, ➂structdata As LINETRANSLATEOUTPUT, ByVal ExtraData As
String)
Declare Sub LineAddressStatusFunc Lib "pstapidll32" (ByVal nMode As
Long, ➂structdata As LINEADDRESSSTATUS, ByVal ExtraData As String)
'
' constants for read/write of params
'
Global Const TAPI_WRITE = 0
Global Const TAPI_READ = 1
You'll notice that the global constants for TAPI_WRITE and TAPI_READ appear at the end of the DLL declarations.
These are used for the nMode parameter of the DLL calls.
After the DLL declares, you'll see a list of TAPI error message values that could be returned. Listing 26.5 shows the
error values that appear in the TAPILINE.BAS module.
'
' ************************************************
' general TAPI constants
' ************************************************
'
Global Const TAPI_REPLY = &H400& + 99&
Next, there is the list of callback message values. These will be used to check the MessageID values returned in the
TapiCallBack event of the TAPILINE control. Listing 26.6 shows the list of 14 possible messages.
Listing 26.6. The TAPICallBack message Ids.
'
' Line callback messages
'
Global Const LINE_ADDRESSSTATE = 0&
Global Const LINE_CALLINFO = 1&
Global Const LINE_CALLSTATE = 2&
Global Const LINE_CLOSE = 3&
Global Const LINE_DEVSPECIFIC = 4&
Global Const LINE_DEVSPECIFICFEATURE = 5&
Global Const LINE_GATHERDIGITS = 6&
Global Const LINE_GENERATE = 7&
Global Const LINE_LINEDEVSTATE = 8&
Global Const LINE_MONITORDIGITS = 9&
Global Const LINE_MONITORMEDIA = 10&
Global Const LINE_MONITORTONE = 11&
Global Const LINE_REPLY = 12&
Global Const LINE_REQUEST = 13&
There are numerous constants and declarations needed to support all the TAPI line device functions. There are too many
to include here, but one is shown in Listing 26.7 to give you an example.
dwLineDeviceID As Long
dwAddressSize As Long
dwAddressOffset As Long
dwDevSpecificSize As Long
dwDevSpecificOffset As Long
dwAddressSharing As Long
dwAddressStates As Long
dwCallInfoStates As Long
dwCallerIDFlags As Long
dwCalledIDFlags As Long
dwConnectedIDFlags As Long
dwRedirectionIDFlags As Long
dwRedirectingIDFlags As Long
dwCallStates As Long
dwDialToneModes As Long
dwBusyModes As Long
dwSpecialInfo As Long
dwDisconnectModes As Long
dwMaxNumActiveCalls As Long
dwMaxNumOnHoldCalls As Long
dwMaxNumOnHoldPendingCalls As Long
dwMaxNumConference As Long
dwMaxNumTransConf As Long
dwAddrCapFlags As Long
dwCallFeatures As Long
dwRemoveFromConfCaps As Long
dwRemoveFromConfState As Long
dwTransferModes As Long
dwParkModes As Long
dwForwardModes As Long
dwMaxForwardEntries As Long
dwMaxSpecificEntries As Long
dwMinFwdNumRings As Long
dwMaxFwdNumRings As Long
dwMaxCallCompletions As Long
dwCallCompletionConds As Long
dwCallCompletionModes As Long
dwNumCompletionMessages As Long
dwCompletionMsgTextEntrySize As Long
dwCompletionMsgTextSize As Long
dwCompletionMsgTextOffset As Long
End Type
Global Const LINEADDRESSCAPS_FIXEDSIZE = 176
Type LINEADDRESSCAPS_STR
Mem As String * LINEADDRESSCAPS_FIXEDSIZE
End Type
You'll notice a set of constants that appear before the type declaration. These can be used to set various TAPI parameters
within the data structure. Some declarations are return values that can be read once TAPI has set or modified the data
within the structure. The TAPILINE.BAS module contains many of these sets of constants and data structure
declarations. The VBTAPI folder also contains a similar module for phone device declarations. Although the
TAPILINE control currently only supports line devices, a future version may provide support for phone devices.
For now, you can load the TAPILINE.BAS module into your Visual Basic programs that use the TAPILINE.OCX
control.
The TAPICALL.BAS module contains several routines needed to support TAPI calls using TAPILINE.OCX and the
PSTAPIDLL file. Again, typing them all in is boring. Instead, this section reviews several of the more important
example routines. You can load the TAPICALL.BAS module into your Visual Basic projects and use these routines
without having to re-enter all the code.
The most important routine in the TAPICALL.BAS module is the TAPICallBackHandler function. This routine
takes the parameters returned in the TAPICallBack event of the TAPILINE.OCX and dispatches messages based on
the return values. Listing 26.8 shows the code for the TAPICallBackHandler.
This routine is designed to turn the callback MessageID value and other parameters into a meaningful message to
display in a status box on a form. In production applications, you could use this same SELECT CASE structure to
enclose calls to subroutines and functions that fire appropriate Visual Basic code based on the messages received.
You'll also notice that there are several calls to other support routines that translate parameters into meaningful strings
for extended messages. These routines could be replaced by Visual Basic code that performs other tasks based on the
parameters passed by TAPI into the TAPICallBack event.
There are several functions that convert call-state parameters into messages. These routines could be used to fire off
Visual Basic code based on the state of the current call. Listing 26.9 shows one of these routines.
This routine handles one of the parameters returned from a line disconnect event.
Some of the TAPI functions return data in variable-length strings. These data strings usually contain several items,
separated by zeros. You need two routines to help read the data in these strings. First, you need a routine to clean out the
zero character (0) values in a returned string. Second, you need a routine that can pick a block of characters out of the
data string based on an offset and a string size. This offset and size are returned as part of the fixed-size data structure.
The Clean function removes all character string 0 values from the returned data. Listing 26.10 shows how this is done.
You'll notice that the routine replaces all character zeros with character 32 (space). This maintains the original size of the
string, but prevents Visual Basic from encountering errors when it tries to read a string that contains character 0 values.
The second routine needed to handle the variable strings is one that picks out a substring based on the offset and size
values found in the fixed data structure. Listing 26.11 shows how the GetOffset function works.
That gives you a good idea of how the support routines work and how they are used throughout the program. In the next
section, you'll build a demonstration program that uses the tools you have reviewed so far.
For our example program, you'll build a form that has a series of buttons you can press to invoke various TAPI service
requests. The status of your requests will be displayed in a scrollable text box on the form. In a real application, you'd
capture the status information and use it to control program flow. But for now, you'll see each message sent back from
TAPI and you'll see how you can write code to capture those messages.
First, start Visual Basic and open a new project. Then add the TAPILINE.OCX control to the form.
Tip
If you do not see the TAPILINE.OCX in your Visual Basic toolbox
window, add it by using the Tools | Custom Controls menu
option. For more information on adding the TAPILINE.OCX to your
project see the TAPILINE.OCX section of this chapter.
Next, add the TAPILINE.BAS and TAPICALL.BAS modules to your project (File | Add File...). You can
find these in the VBTAPI folder on the CD-ROM.
Now you're ready to place the controls on the form. Refer to Figure 26.4 and Table 26.4 when laying out the form.
After completing the form, save the form as TAPITEST.FRM and the project as TAPITEST.VBP. Now you're ready to
place code into the form.
There are two sets of code for the TAPILine form. The first set is for the support routines. These are called by several
other routines on the form. Next you get to add the code behind all the buttons. This code is the actual TAPI service
requests.
Before adding code behind the buttons on the form, you need to add a few support routines to the project. First, three
form-level declarations are needed. Add the code in Listing 26.12 to the general declaration area of the form.
Option Explicit
Next, you need to add code to clear the values in four key properties used in this project. Create a subroutine called
ClearHandles and add the code shown in Listing 26.13.
You'll also need a routine to display the contents of these same four properties. Add a subroutine called ShowHandles
and add the code in Listing 26.14.
Next, add a new function called GetVarInfo. This will be used to parse some of the return values from the
GetDevCaps TAPI function. Place the code shown in Listing 26.15 into the GetVarInfo function.
The last support routine you need to code for this project is the one used to read the LINECALLINFO data structure.
This structure is quite long and contains a great deal of data. This routine (LocalGetLineInfo) reads the data, cleans
up the returned strings, and then calls a routine in the TAPICALL.BAS file to actually show the results. Add the code
from Listing 26.17 to your form.
This code sets the form caption, centers the form on the screen, initializes a display line, and clears the handle values.
This code just makes sure the TAPI session is closed out before exiting the program.
The next code you need to add is the code for the TAPICallBack event of the TAPILINE1 control. All you need to
do is pass the parameter list to the TAPICallBackHandler function and then display the results. Listing 26.20 shows
the code you need to add to the TAPICallBack event.
Now you're ready to start programming TAPI from Visual Basic! First add the code shown in Listing 26.21 to the
cmdLineInit_Click event.
This code initializes TAPI services for your program and displays the number of TAPI devices by reading the property
value set by the LineInitialize method.
Next add the code for the cmdLineNegAPI_Click event. This code walks through all the devices found by TAPI and
makes sure they all support the current TAPI version. Add the code shown in Listing 26.22.
Listing 26.22. The cmdLineNegAPI_Click code.
Now save and run the project. After clicking the first two buttons, you should see a number of lines on your screen along
with messages saying the API negotiation routine was successful (see Figure 26.5).
The next set of code collects the device capabilities of all the line devices recognized by TAPI. Add the code from
Listing 26.23 to the cmdLineGetDevCaps_Click event of the form.
Not only does this routine get the capabilities of each device, the routine also displays the results in the text box on
screen. You can scroll through this screen to inspect the various devices recognized by TAPI.
After initializing, negotiating the API, and getting the device capabilities, you are now ready to open a line for outbound
calls. The code behind the cmdLineOpen_Click event will handle this task. Add the code from Listing 26.24 to the
project.
You'll notice that the routine first prompts the user for a line number. For now, select a line that is capable of handling
outbound voice calls. In a production application, you could inspect the values returned by the GetDevCaps method to
locate a line device with the needed capabilities.
Next, the routine performs a line-open and then sets the LINECALLPARAMS structure and displays the results. Setting
the LINECALLPARAMS structure will affect how the call is finally placed. This is optional. If no LINECALLPARAMS
are set, TAPI will use the default values for the selected line device. Figure 26.6 shows how the status box looks after
selecting a line and displaying the call parameters.
After a line is opened successfully, you are ready to place an outbound call. Add the code shown in Listing 26.25 in the
cmdLineMakeCall_Click event.
This code reads the LINECALLPARAMS structure, displays the results, and then prompts the user for a phone number.
After the number is entered, the LineMakeCall method is invoked to place the call.
Once the call is placed, several TAPI messages cross through the TAPICallBack event. When you run the program
and place a call, you'll see the progress messages appear in the status box. Figure 26.7 shows an example of what you
should see when you run the program.
The next three buttons handle code that drops the current call, de-allocates the line handle, and closes the line device. All
these steps should occur at the end of a call. Listing 26.26 shows the code for the three button routines (cmdLineDrop,
cmdLineDealloc, and cmdLineClose). Add this code to your project.
Those are the steps to completing an outbound call using the TAPILine control. The other routines in this project all
illustrate additional line device functions you can access from the TAPILINE.OCX.
Once a line is open and a call is active, you can get call information about the line through the LineGetCallInfo
method. You can also get information about the address originating the call (that's you!) using the
LineGetAddressStatus method. Listing 26.27 shows the code for these two buttons. Add this code to your project.
The cmdLineGetCallInfo_Click code simply calls a routine you build earlier. This displays information about
the active call on the specified line device. The cmdLineGetAddrStatus_Click code gets information about the
originating address (or extension). When you run your project and place a call, you can monitor the call information and
address status by pressing these two buttons during the call (see Figure 26.9).
You can also watch the values of call handles change as you progress through completing a call. Add the code shown in
Listing 26.28 to the cmdShowHandles_Click event.
Save the project and then run it. Now you can press the cmdShowHandles button at various times-while you open a
line, place a call and then drop, de-allocate, and close the line-to monitor the creation and clearing of handles throughout
the call process.
The TAPILINE control supports four dialog boxes that you can call from within Visual Basic. They are:
The Provider Configuration dialog box is called by using the LineConfigProvider method. This brings up a dialog
box supplied by the TAPI Service Provider. The list of TAPI Service Providers is kept in the TELEPHON.INI file in
the WINDOWS\SYSTEM directory.
Note
You'll learn more about the TELEPHON.INI file in Chapter 27, "TAPI
Behind the Scenes-The TELEPHON.INI File."
Figure 26.11 shows the dialog box that appears on workstations where the UniModemV device driver is installed.
The Line Configuration dialog box is called using the LineConfigDialog method. This brings up a dialog box
supplied by the operating system. Figure 26.12 shows an example line configuration dialog box.
Tip
Don't be confused by the similarity of the dialog boxes from the
LineConfigDialog and the LineConfigProvider methods. The
first method (LineConfigDialog) calls the dialog box for configuring
the device associated with the selected line. The second method
(LineConfigProvider) calls the dialog box for configuring the device
access for the selected TAPI service provider. A TSP may have access to
more than one device. That is why you can use the
LineConfigProvider dialog box to access all the devices on the
workstation, but you can only access one device from
LineConfigDialog.
The last dialog box supported by the TAPILINE control is the Dialing Properties dialog box. This is called using the
LineTranslateDialog method. You must invoke the LineInitialize method before you call this dialog box.
This is the only dialog box that requires that TAPI be up and running. Figure 26.13 shows the Dialing Properties dialog
box.
Listing 26.29 shows the code for the four buttons mentioned above. Add this code to your project.
That is all the code for the TAPITEST.VBP project. You can use the code here to place simple voice outbound calls and
watch your progress using the status window. Later in this section, you use the code shown here to build a complete
telephony application in Visual Basic.
Summary
In this chapter you learned the properties, methods, and events of the TAPILINE control found on the CD-ROM that
accompanies this book. You also learned how to use this control to gain access to TAPI services from within Visual
Basic programs.
You also learned how to monitor call progress using TAPI messages and the LineGetCallInfo and
LineGetAddress methods. Finally, you learned how to call the four TAPI dialog boxes to gain access to
configuration dialogs to customize your TAPI interface.
In the next chapter you'll learn about the TELEPHON.INI file. This file contains control information affected by the
dialog boxes you learned about in this chapter.
Chapter 25
CONTENTS
In this chapter, you'll learn the differences between the three primary types of telephony hardware for pcs:
These three types of interface cards provide a wide range of telephony service for desktop workstations. You'll learn the
advantages and limits of each of the interface card types and how you can use them in your telephony applications.
Basic data modems can support Assisted Telephony services (outbound dialing) and usually are able to support only
limited inbound call handling.
Voice-data modems are a new breed of low-cost modems that provide additional features that come close to that of the
higher-priced telephony cards. These modems usually are capable of supporting the Basic Telephony services and many
of the Supplemental services. The key to success with voice-data modems is getting a good service provider interface for
your card.
Finally, telephony cards offer the greatest level of service compatibility. Telephony cards usually support all of the Basic
Telephony and all of the Supplemental Telephony services, including phone device control. Most telephony cards also
offer multiple lines on a single card. This makes them ideal for supporting commercial-grade telephony applications.
You'll also get a quick review of how modems work and how Win95 and WinNT use modem drivers to communicate
with hardware devices.
All TAPI services are routed through some type of modem. These modems also depend on the Windows operating
system to supply device drivers to communicate between programs and the device itself. While a detailed discussion of
device drivers is beyond the scope of this book, it is a good idea to have a general understanding of how Windows uses
device drivers and how modems work. In this section you'll get a quick review of modem theory and a short discussion
of the Universal Modem Driver that ships with Win95 and WinNT.
Before getting into the details of how the three types of telephony hardware differ, it is important to do a quick review of
how modems work. If you've seen all this before, you can skip to the next section.
Sending computer data over voice-grade phone lines is a bit of a trick. All data stored on a pc (documents, programs,
graphics, sound and video files, and so on) is stored as 1s and 0s-binary data. However, standard telephone lines are not
capable of sending binary data-only sounds. That means that any information sent over the telephone line has to be in the
form of sound waves. In order to accomplish this feat, hardware was invented to convert digital information into sound
(that is, to modulate it), then back again from sound into digital information (demodulate it). This process of modulating
and demodulating is how the device got its name: mo-dem (modulate-demodulate).
Sending data over phones lines involves three main steps. First, a connection must be established between two modem
devices over a telephone line. This is typically done by having one modem place a telephone call to the other modem. If
the second modem answers the telephone call, the two modems go through a process of determining if they understand
each other called handshaking. If that is successful, then information can be passed.
In the second step, the digital information is modulated into sound and then sent over the voice-grade telephone line to
the second modem. In the last step, the modem at the other end of the call converts (demodulates) the sound back into
digital information and presents it to the computer for processing (view the graphic, save the file, play the video or
audio, and
so on).
TAPI requires each workstation to have not just a TAPI-compliant application, but also a Telephony Service Provider
Interface (TSPI). This interface talks directly to the hardware to convert your TAPI service requests into commands
understood by the hardware. The TSPI is usually supplied by the hardware vendor, but Microsoft Win95 ships with a
simple TSPI called the UniModem Driver (Universal Modem Driver). The UniModem driver is designed to support
Assisted Telephony and some Basic Telephony. You can build simple applications that allow users to place and receive
voice and data calls using basic data modems and the UniModem driver that ships with Win95 and WinNT.
Note
You'll learn more about TSPI and modem drivers in Chapter 27, "TAPI
Behind the Scenes-The TELEPHON.INI File."
Microsoft has released a modem driver that supports additional voice features including playing and recording audio
files. This driver is called the UniModemV Driver (Universal Modem for Voice). This driver supports the use of voice
commands along with recording and playing back voice files. It can also handle caller ID and some other added service
features. Exactly what the UniModemV driver can do is also dependent on the hardware. The telephony hardware must
recognize any advanced features and be able to communicate them to the driver.
Tip
You can find a copy of the UniModemV driver on the CD-ROM that ships
with this book.
Basic Data Modems
The most basic type of hardware that supports TAPI is the basic data modem. This type of modem is designed to use
analog phone lines to send digital data. Any computer that can access online services (BBS, Internet, commercial
information services, and so on) has at least this level of modem hardware. You can get basic data modems with speeds
of 9600 to 14,400bps (bits per second) for $100 U.S. or less. You can get data modems that can handle 28,800bps for a
bit more.
It is not easy to find data modems for pcs that are rated beyond 28.8Kbps. The primary reason for this is that voice-grade
telephone lines cannot handle error-free throughput at speeds much faster than 28.8Kbps. Although there are now some
33.3 modems on the market, your local lines may not be able to handle those speeds. You can contact your local
telephone service provider to get an idea of the line quality in your area. Usually, if you want to use speeds faster than
28.8, you'll need to purchase special lines from your local telephone service provider.
Almost all basic data modems recognize a common set of control codes. This set of control codes is called the Hayes or
AT command set. This set of controls was developed by the makers of the Hayes modem. The first command in the set
(AT) is the "attention" command. This tells the device you are about to send control codes directly to the hardware. The
command set is known by the original author's name ("Hayes") or by the first command in the set ("AT").
Basic data modems support Assisted Telephony services without any problem (that is, placing outbound calls). Most
basic modems are capable of supporting some of the Basic Telephony services, including accepting inbound calls.
However, if you want to perform any of the more advanced TAPI services, such as playing or recording audio files,
you'll need more advanced hardware. Also, if you want to access advanced features available for voice telephones such
as caller ID, call hold, park, forward, and so on, you'll need more than a basic data modem. Figure 25.1 shows the TAPI
service levels and telephony hardware classes. The highlighted areas give you an idea of how basic data modems do in
supporting TAPI services.
If you are designing applications that allow users to select names or phone numbers and then place outbound voice or
data calls, basic modems will work just fine. In fact, unless you are planning to add voice recording, playback, or other
advanced telephony features to your application, the basic modem will provide all your TAPI needs.
There is a new type of modem available that offers all the services of a data modem, but also has added support for voice
services. These modems are often called voice-data modems (or data-voice modems). This hardware has additional
programming built into the chips that will support advanced telephone features such as caller ID, call hold, park,
forward, and so on. Just as basic data modems use the AT command set, the voice-data modems use an extension of that
set called the AT+V command set (AT plus Voice).
AT+V modems cost a bit more than basic data modems. You can find them in the U.S. packaged with sound cards and
other multimedia hardware. Generally the cost is about $250 U.S. If the modem is part of a bundled multimedia kit, you
could end up paying twice that amount.
Voice-data modems also require a TAPI-compliant modem driver in order to work with TAPI services. This driver is
usually supplied by the hardware vendor. Microsoft also supplies a modem driver that supports voice services-the
UniModemV driver. If your modem does not ship with a TAPI-compliant driver, you might be able to install the
UniModemV driver to enable your voice features.
Tip
A copy of the UniModemV driver ships with the latest version of Windows
95 and the new Windows NT 4.0. If you have an older version of Windows
95 or Windows NT, you can use the copy of UniModemV on the CD-ROM
that ships with this book.
A word of caution is in order when purchasing a voice-data modem. There are several modems on the market that offer
voice, voice-mail, telephone answering, and other TAPI-like services for pcs. The thing to keep in mind is that many of
them are not TAPI-compliant. While you may get a modem that can do all the things you want, it may not do it using the
TAPI calls and you many not be able to program it using TAPI services.
As of the writing of this book, there are a handful of voice-data modem vendors that have announced the release of
TAPI-compliant hardware. Here is a list of some vendors currently offering TAPI-compliant voice-data modems:
Tip
This list is growing all the time. For the most recent updates on this list you
can check the CD-ROM that ships with this book. You can also check out
the Communications Developer's Guide web site for updates on MAPI,
SAPI, and TAPI development. Point your browser to www.iac.net/
~mamund/mstdg.
Voice-data modems with supporting TAPI drivers offer a wide range of access to TAPI services. You can use voice-data
modems to perform both outbound and inbound call handling, play and record voice files, and (if the feature is available
on the telephone line) support caller ID and other advanced services for single-line phones. Figure 25.2 shows how voice-
data modems shape up in their support of TAPI services.
Telephony Cards
The most advanced level of hardware you can get for TAPI services on a desktop pc is a dedicated telephony card. This
is a piece of hardware dedicated to handling telephone services. Most telephony cards are designed to handle more than
one line at a time, too. If you are planning an application that must answer several phone lines or perform any line
transfers, and so on, you'll need a telephony card.
Most telephony cards are sold as part of a kit. You can get software development tools, cards for the pc, cables, and
documentation all for one price. This price usually starts at around $1000 U.S. and can easily climb depending on the
number of lines you wish to support. Even though the price is a bit high, if you are doing any serious TAPI work, you'll
need this kind of equipment.
As with other telephony hardware, telephony cards need an accompanying TAPI driver in order to recognize TAPI calls
from your program. While most telephony card vendors are working on TAPI drivers, not all of them supply one as of
this writing. It is important to check the specifications of the hardware and supporting materials before you buy.
It is also important to point out that there are lots of very sophisticated hardware and software tools for handling
telephony services that are not TAPI-based. It is possible that you will be able to find the right hardware and software to
meet your needs without using TAPI services at all. The only drawback is that you'll be using a proprietary system that
may (or may not) become obsolete in the future. If it is possible, it is a good idea to use TAPI-compliant products since
the power of Microsoft and the Windows operating system is likely to support interfaces like TAPI for quite some time.
Telephony cards (along with TAPI drivers to match) offer the greatest access to TAPI services. You can support all the
Assisted TAPI and Basic TAPI functions along with access to Supplemental TAPI services. Also, if the driver supports
it, you will be able to use Extended TAPI services to gain access to vendor-specific functions unique to the installed
hardware. Figure 25.3 shows how telephony cards support all levels of TAPI services.
Figure 25.3 : Telephony cards can support all levels of TAPI services
Summary
In this chapter you learned the differences between the three types of hardware options and how they rate in offering
support for TAPI services on pc workstations:
● Basic data modems support Assisted Telephony services (outbound dialing) and can support only limited
inbound call handling. Use this type of hardware if you are building simple outbound dialing applications.
● Voice-data modems are capable of supporting the Assisted Telephony and Basic Telephony services and many
of the Supplemental services. Use this type of hardware if you want to provide both inbound and outbound
services on a single-line phone.
● Telephony cards support all of the Basic Telephony and all of the Supplemental Telephony services, including
phone device control. Most telephony cards also offer multiple lines on a single card. This make them ideal for
supporting commercial-grade telephony applications.
You also got a quick review of modems and modem drivers. You learned that Win95 and WinNT rely on the UniModem
or UniModemV modem drivers to communicate between the telephony hardware and your program. You also learned
that, no matter what hardware you purchase, you will need a TAPI-compliant TSPI (Telephony Service Provider
Interface) that matches the hardware you purchased. Hardware vendors may recognize the UniModem or UniModemV
drivers, or ship their own TSPI drivers with their hardware.
In the next chapter, you'll learn how to use Visual Basic and a special custom OCX control to gain direct access TAPI
services for your Visual Basic programs.
Chapter 24
TAPI Basics
CONTENTS
In this chapter, you'll learn how to build a simple TAPI dialer application in C using the Basic Telephony level of
service. This application will be used to highlight the basic operations required to build TAPI applications (in any
language).
You'll learn how to perform line initialization, locate a usable outbound line, and open it in preparation for dialing.
You'll also learn how to place an outbound call and use the TAPI line callback function to monitor call progress. Finally,
you'll learn how to safely close down a line after the call has been completed.
When you are done with the example in this chapter, you'll understand the basics of writing TAPI applications and know
how to use Basic Telephony services in your own applications.
Note
The project in this chapter was written using the Microsoft Visual C++ 4.1
compiler. However, the code is compatible with the Microsoft VC 2.0
compiler. If you do not have a C compiler, you can still get a lot out of the
chapter. The same techniques covered here will be used when you build
TAPI applications in Microsoft Visual Basic 4.0 later in this section of the
book.
After you complete these steps, you can use the messages received by the registered callback function to track call
progress and respond accordingly. The next few sections cover the outbound calling steps in greater detail.
The first thing you need to do to start a TAPI session is to call the lineInitialize routine to initialize the link
between your application and the TAPI service provider. The lineInitialize routine includes a pointer to the
callback function in your code that will handle all messages.
After successful initialization, the routine returns a value in the lineHandle parameter. You'll use this value
throughout your TAPI session. You will also get a count of the total number of TAPI lines defined for this workstation.
You'll use that information to check the API version and line parameters of each line before you attempt to place a call.
If an error occurs, a non-zero value is returned. You can check the errors using a case switch and present a message to
the user.
After you successfully open the TAPI session, you need to call the lineNegotiateAPIVersion function for each
line in the collection. The total number of lines was returned as part of the lineInitialize routine. You need to
check this value because it is possible that you will be requesting a version of TAPI this is not available for this machine.
You pass your API version request to the function and get a value back that is the version of TAPI that the workstation
can provide to your application. You can also get a pointer to a structure that holds information about vendor-specific
extension services available on this workstation. This is a method for allowing non-TAPI services to be recognized using
the TAPI interface.
Once you have successfully negotiated an API version, you must use the lineOpen function to pass through each line
and request the appropriate level of service. For example, if you wanted to place an interactive voice call, you'd use the
lineOpen function to locate a line that supports interactive voice.
This is an important point. It is quite possible that the workstation will have several TAPI devices defined, but only one
may provide the type of service you need (voice, fax, data, and so on). If there is no device available (none exists or the
current one is busy), you'll get an error message. However, if an appropriate line is available, you'll receive a zero as a
return code and a value indicating the handle of the open line. You'll use this value in subsequent TAPI calls.
Once you locate an appropriate line, you can set calling parameters using the LINECALLPARAMS structure. You use
this structure to tell TAPI the speed and media type (data, voice, and so on) of your call and other values.
Setting the LINECALLPARAMS structure is optional. If you do not set any value for the LINECALLPARAMS, Microsoft
TAPI will use default values. For most calls, the default values will work just fine.
The last step in the process is actually placing the call using the lineMakeCall function. This function passes the
string that contains the phone number to call, a handle for the open line (you got that from lineOpen) and, optionally,
a pointer to the LINECALLPARAMS structure.
If the call is placed successfully, a call handle is returned. You'll use this call handle in subsequent TAPI functions. If
there is trouble making the call, the return code is non-zero and can be checked for appropriate action.
It is important to note that at this point the call has been placed but not completed. All TAPI knows for sure is that digits
have been dialed and the phone line is active. TAPI will continue to receive status information and route that to your
application through the callback function registered when you called lineInitialize. The quality of the status
information (dialing, ringing, busy, idle, and so on) is all determined by the hardware vendor and TAPI service provider
application. The more sophisticated the hardware, the more accurate the progress information.
For example, standard data/fax modems do not report call progress information. When you place your TAPI call, you'll
be notified by the hardware that the call is in progress and will see nothing else until the call is completed or a time-out
occurs. Other hardware (advanced voice/data modems) may provide additional call-progress data. High-end telephony
cards provide the most accurate information.
Now that you know the basics of placing an outbound call using TAPI, it's time to review the TAPIOUT project on the
CD-ROM that accompanies this book.
The TAPIOUT project that ships on the CD-ROM is a C program that allows users to enter a phone number and use
TAPI to place an outbound call. This project is very rudimentary. There are no extra bells and whistles. However, the
code in this project gives a good review of what it takes to provide basic TAPI services. The next several sections of this
chapter review the TAPIOUT project step-by-step. You'll see how you can use the TAPI functions described earlier in
the chapter to create a functional TAPI dialer.
If you have a copy of Microsoft VC++ 2.0 or later, start it now and load the TAPIOUT.MAK (or TAPIOUT.MDP)
project from the CD-ROM. You can follow along with the examples in the chapter
Tip
You'll need to have the TAPI SDK installed on your machine before you
can compile this project. Once you load the project, be sure to select
Tools | Update All Dependencies to resolve references to the
TAPI.H and TAPI32.LIB files in the project. You may need to reload
these files into the project using Insert | Files into
Project...
This first step in the process is declaring all the needed includes, defines, function prototypes, and global variables.
Listing 24.1 shows how this looks in the TAPIOUT project.
Listing 24.1. The initial declarations of the TAPIOUT project.
//
******************************************************************
// SIMPLE OUTBOUND TAPI DIALER APPLICATION
//
******************************************************************
//
// Title: TAPIOut
// Version: 1.0 - 05/24/96 (MCA)
//
// Equip: VC++ 4.0 / Win95 / TAPI SDK
//
// Client: MAPI, SAPI, TAPI Developer's Guide (SAMS 1996)
//
// Desc: Simple dialog to show how to use TAPI to place outbound
// calls. Takes dialing string and shows progress as the
// program attempts to complete the call.
//
// Files: TAPIOUT.C
// TAPIOUT.RC
// TAPIOUT.DEF
// RESOURCE.H
// TAPI.H
// TAPI32.LIB
//
//
******************************************************************
// ****************************************
// Includes and defines
//
#include "windows.h"
#include "tapi.h"
#include "resource.h"
// ******************************************
// global declares
//
LONG PlaceCall( HWND, LPTSTR );
void CALLBACK LineCallBackProc(DWORD hDevice,DWORD dwMessage,DWORD
dwInstance,DWORD ➂dwParam1,DWORD dwParam2,DWORD dwParam3);
BOOL WINAPI MainDialog(HWND hDlg, WORD msg, WORD wParam, LONG
lParam);
void ShowProgress( HWND hWnd, LPTSTR OutputString );
void SetVarProps( HWND hWnd, DWORD hDevice );
Notice the inclusion of the TAPI.H and WINDOWS.H files. You'll need these on your system if you want to compile this
project. You'll also need the TAPI32.LIB file.
The defines added here make it easy to provide a local message handler that responds to predefined TAPI messages.
You'll see how these are used in the MainDialog and lineCallBack routines later in this chapter.
Notice also the declaration of global handles and a lineParams structure. You'll use these throughout the project.
The WinMain code for this project is quite simple. Declare a message queue, get the current instance of this program,
and then call the main dialog box. All other activity is generated by the dialog box. Listing 24.2 shows the code for the
WinMain routine.
// *******************************************
// Initial Entry
//
// establish a message queue
// get the instance handle
// start the user dialog
//
int PASCAL WinMain( HANDLE hInstance, HANDLE hPrev, LPSTR lpCmd, int
nShow )
{
SetMessageQueue( 100 );
hInst = hInstance;
DialogBox( hInstance, MAKEINTRESOURCE( ID_MAIN_SCREEN ), NULL,
MainDialog );
return( FALSE );
}
The main dialog box contains only a few controls. An input box for the phone number, a list box to show the status
messages supplied by TAPI, and three command buttons (PlaceCall, Disconnect, and Exit). Figure 24.1 shows
the layout of the main dialog box.
Figure 24.1 : The main dialog box of the TAPIOUT project
The code for the main dialog box is a bit lengthy; however, it is rather simple, too. The code can be broken down into
three main sections:
The first part of the MainDialog code responds to the initial loading of the dialog box and to user actions on the
command buttons. Listing 24.3 shows how this code looks.
// ********************************************
// Main Dialog to handle user interface
//
BOOL WINAPI MainDialog(HWND hDlg, WORD msg, WORD wParam, LONG lParam)
{
switch (msg)
{
case WM_INITDIALOG: // when dialgo first starts up
{
// Set the necessary properties to null
SetProp( hDlg, "HCALL", NULL );
SetProp( hDlg, "HLINE", NULL );
SetProp( hDlg, "HCOMM", NULL );
break;
}
case WM_COMMAND: // user pressed a button
{
switch( wParam )
{
case ID_CALL: // user press PLACE CALL
{
char PhoneNumber[ 100 ]; // save some space
HCALL hCall; // declare a local handle
//
// Gotta call going? - Uh, oh!
hCall = (HCALL)GetProp( hDlg, "HCALL" );
if( hCall != NULL )
{
MessageBox( hDlg,"Please Disconnect before
making another
&nbs
p; ➂call!",
&nbs p; " Tapi
Error",
&nbs p;
MB_ICONSTOP );
break;
}
//
// Get digits from input box
GetDlgItemText( hDlg, ID_PHONE, PhoneNumber,
sizeof( ➂PhoneNumber ) );
//
// place the call (check return value)
if( PlaceCall( hDlg, PhoneNumber ) < 0 )
ShowProgress( hDlg, "Unable to start a TAPI
Function" );
break;
}
case ID_DISCONNECT: // user press DISCONNECT
{
LONG retcode; // some local stuff
HCALL hCall;
HANDLE hComm;
//
// try to get the handles
hCall = (HCALL)GetProp( hDlg, "HCALL" );
hComm = (HANDLE)GetProp( hDlg, "HCOMM" );
//
// if we have a comm handle, drop it
if( hComm != NULL )
{
CloseHandle( hComm );
SetProp( hDlg, "HCALL", NULL );
}
//
// if we have a call handle, drop it
if( hCall != NULL )
{
retcode = lineDrop( hCall, NULL, 0 );
ShowProgress( hDlg, "Call is Dropped" );
SetProp( hDlg, "HCALL", NULL );
}
break;
}
case IDOK: // user pressed the EXIT button
{
HCALL hCall; // declare some local vars
HLINE hLine;
HANDLE hComm;
//
// load the values
hCall = (HCALL)GetProp( hDlg, "HCALL" );
hLine = (HLINE)GetProp( hDlg, "HLINE" );
hComm = (HANDLE)GetProp( hDlg, "HCOMM" );
//
// if we have a comm handle, close it
if( hComm != NULL )
{
CloseHandle( hComm );
SetProp( hDlg, "HCOMM", NULL );
}
//
// if we have a call handle, close it
if( hCall != NULL )
{
lineDrop( hCall, NULL, 0 );
SetProp( hDlg, "HCALL", NULL );
}
//
// if we have a line handle, close it
if( hLine != NULL )
{
lineClose( hLine );
SetProp( hDlg, "HLINE", NULL );
}
//
// close down open line
if( LineHandle != NULL )
{
lineShutdown( LineHandle );
LineHandle = NULL;
}
//
// drop the save properties
RemoveProp( hDlg, "HCALL" );
RemoveProp( hDlg, "HLINE" );
RemoveProp( hDlg, "HCOMM" );
//
// close down the dialog
EndDialog( hDlg, FALSE );
break;
&nbs
p; }
The code here deserves some review. First, when the dialog box first starts, three properties are created. These will hold
values used throughout the dialog. The next event is the pressing of the ID_CALL button. This tells the dialog box to
attempt to place a call. The first step is to check to see if a call is already in progress. If so, a message is displayed to the
user. If no call is currently in progress, the phone number is gathered from the input box and then passed to the
PlaceCall function for final dispatch (you'll see the PlaceCall function in the next section of this chapter).
If the user presses the ID_DISCONNECT button, the program checks the comm and call handles and, if they are set,
clears them using the ClearHandle and lineDrop functions.
Finally, when the user presses the Exit button (IDOK), the same types of routines executed in ID_DISCONNECT must
also occur here. In addition to the ClearHandle and lineDrop functions, the lineClose and lineShutdown
routines are called. This performs final closure on all TAPI services for this session.
The second section of the MainDialog is used to respond to TAPI messages received via the lineCallBack
function and passed onto the MainDialog. The only message that needs attention is when a call goes idle. If a call
goes idle, the MainDialog needs to close down the line resources just as if the user had pressed the Disconnect
button. Listing 24.4 shows how this code looks in the TAPIOUT project.
//
// **************************************
// respond to TAPI Messages
// **************************************
//
case TAPI_LINE_REPLY:
{
ShowProgress( hDlg, "Line Reply" );
break;
}
//
case TAPI_LINECALLSTATE_CONNECTED:
{
ShowProgress( hDlg, "Line Call State is
Connected" );
break;
}
//
case TAPI_LINECALLSTATE_IDLE:
{
LONG retcode; // local stuff
HLINE hLine;
//
// call went idle, do cleanup
hLine = (HLINE)GetProp( hDlg, "HLINE" );
//
// if we have a live line, close it
if( hLine != NULL )
{
retcode = lineClose( hLine );
SetProp( hDlg, "HLINE", (HANDLE)NULL );
}
ShowProgress( hDlg, "Line Call State is idle" );
break;
&nbs
p; }
Notice that, unlike the disconnect which performs both a lineDrop and a lineClose, the idle line handler only calls
lineClose. This is because idle lines have already experienced the lineDrop. This is why they are idle!
The last section of the MainDialog is used to simply post status messages to the list box on the dialog box. The code
is added here to show you progress during the call. You may not need to code these messages at all in production
applications. Listing 24.5 shows how this code looks.
//
// *********************************************
// respond to forwarded TAPI messages
// *********************************************
//
case TAPI_LINECALLSTATE_DISCONNECTED:
{
ShowProgress( hDlg, "Line Call State is
Disconnected" );
break;
}
//
case TAPI_LINECALLSTATE_BUSY:
{
ShowProgress( hDlg, "Line Call State is Busy" );
break;
}
//
case TAPI_LINECALLSTATE_AccEPTED:
{
ShowProgress( hDlg, "Line Call State is
Accepted" );
break;
}
//
case TAPI_LINECALLSTATE_PROCEEDING:
{
ShowProgress( hDlg, "Line Call State is
Proceeding" );
break;
}
//
case TAPI_LINECALLSTATE_OFFERING:
{
ShowProgress( hDlg, "Line Call State is
Offering" );
break;
}
//
case TAPI_LINECALLSTATE_DIALTONE:
{
ShowProgress( hDlg, "Line Call State is
DialTone" );
break;
}
//
case TAPI_LINECALLSTATE_DIALING:
{
ShowProgress( hDlg, "Line Call State is
Dialing" );
break;
}
default:
break;
} // switch (wParam)
break;
} // case WM_COMMAND
default:
break;
} // switch (msg)
return (FALSE);
} // main dialog
The PlaceCall Function
The real heart of the project is the PlaceCall function. This routine is the one that actually places the requested call.
The code here follows the outline in the first part of this chapter. The steps of initialize, check API, look for open line,
set call parameters, and place call are all here in this one routine. You can use this code as a shell routine to place in your
other TAPI applications. Listing 24.6 shows the code for the PlaceCall function.
// ***********************************************
// This routine places the actual call
//
LONG PlaceCall( HWND hWnd, LPTSTR PhoneNumber )
{
LONG retcode; // local returns
DWORD i; // counter for lines
DWORD ApiVersion; // expected API version
DWORD RetApiVersion; // return version
LINEEXTENSIONID ExtensionID; // struc for API call
HLINE hLine; // local line handle
HCALL hCall; // local call handle
//
// make sure you have a phone number
if( lstrlen( PhoneNumber ) < 1 )
return( -1 );
//
// Initialize the line, register the callback
if( LineHandle == NULL )
retcode = lineInitialize( &LineHandle, hInst, ➂
(LINECALLBACK)LineCallBackProc, "TAPI Out", &lines );
if( retcode < 0 )
return( retcode );
//
// go through all lines to get API and properties
// if you find one that has the right properties,
// jump out and continue to next section of code
//
hLine = (HLINE)GetProp( hWnd, "HLINE" );
if( hLine == NULL )
{
for( i=0; i < lines; i++ )
{
// Negotiate the API Version for each line
ApiVersion = tapiVersionCur;
retcode = lineNegotiateAPIVersion( LineHandle, i,
ApiVersion, ➂ApiVersion, &RetApiVersion,
&ExtensionID );
retcode = lineOpen( LineHandle, i, &hLine,
RetApiVersion, 0, ➂(DWORD)hWnd,
LINECALLPRIVILEGE_OWNER | LINECALLPRIVILEGE_MONITOR,
LINEMEDIAMODE_DATAMODEM, NULL );
if( retcode == 0 )
break;
}
if( retcode != 0 )
return( -1 );
}
//
// found a good line
SetProp( hWnd, "HLINE",(HANDLE)(HLINE)hLine );
//
// now set of properties of the line for outbound dialing
memset( &LineParams, 0, sizeof( LINECALLPARAMS ) );
LineParams.dwTotalSize = sizeof( LINECALLPARAMS );
LineParams.dwMinRate = 9600; // setting data rates
LineParams.dwMaxRate = 9600; //
LineParams.dwMediaMode = LINEMEDIAMODE_DATAMODEM; // doing a
data call
//
// finally place the call!
retcode = lineMakeCall( hLine, &hCall, PhoneNumber, 0,
&LineParams );
return( retcode ); // tell'em how it turned out!
}
This code hardly needs review-you've seen this explained before. The key points to remember are:
It is also important to remember that once TAPI performs the lineMakeCall successfully you still are not connected
to your called party. You need to check callback messages to check the status of your call.
There are two helper routines in the TAPIOUT project. The first, ShowProgress, is used to post messages to the list
box on the main dialog form. Listing 24.7 shows how this code looks.
// ************************************************
// update list box to show TAPI progress
//
void ShowProgress( HWND hDlg, LPTSTR OutputString )
{
DWORD dwIndex;
int i;
dwIndex = SendDlgItemMessage( hDlg, ID_STATUS_LIST,
LB_ADDSTRING, 0, ➂(LPARAM)(LPSTR)OutputString );
if( dwIndex == LB_ERR ) // clear some space for full box
{
for( i = 0; i < 10; i++ )
SendDlgItemMessage( hDlg, ID_STATUS_LIST,
LB_DELETESTRING, 0, 0 );
// now send the message
dwIndex = SendDlgItemMessage( hDlg, ID_STATUS_LIST,
LB_ADDSTRING, 0, ➂(LPARAM)(LPSTR)OutputString );
}
SendDlgItemMessage( hDlg, ID_STATUS_LIST, LB_SETCURSEL, (WPARAM)
dwIndex, 0 );
return;
}
The second routine is used to pick the line ID out of the
LINECALLINFO structure and store it in the dialog properties set.
Listing 24.8 shows the SetVarProps routine.
Listing 24.8. The code for the SetVarProps routine.
// ***************************************************
// get line handle from LINECALLINFO structure
//
void SetVarProps( HWND hWnd, DWORD hDevice )
{
LINECALLINFO LineCallInfo;
memset( &LineCallInfo, 0, sizeof( LINECALLINFO ) );
SetProp( hWnd, "HCALL", (HANDLE)(HCALL)hDevice );
LineCallInfo.dwTotalSize = sizeof( LINECALLINFO );
lineGetCallInfo( (HCALL)hDevice, &LineCallInfo );
SetProp( hWnd, "HLINE", (HANDLE)(HLINE)LineCallInfo.hLine );
return;
}
The lineCallBackProc is the routine registered (using lineInitialize) to receive all TAPI messages for this
application. Since this routine must handle all messages from TAPI, it can get a bit long. In this chapter, the code is
broken into three segments:
The first section of the lineCallBackProc contains code to respond to changes in the LINE_CALLSTATE
message. Only two messages get our attention here: LINE_CALLSTATE_IDLE and
LINE_CALLSTATE_CONNECTED. Listing 24.9 shows the code that responds to these two messages.
// *******************************************
// The callback to handle TAPI messages
//
// This routine handles all messages generated by TAPI services.
// Most of these messages are ignored here or just passsed on to
// the main dialog for posting to the progress window.
//
void CALLBACK LineCallBackProc(DWORD hDevice,DWORD dwMessage,DWORD
dwInstance,DWORD ➂dwParam1,DWORD dwParam2,DWORD dwParam3)
{
switch (dwMessage)
{
case LINE_CALLSTATE: // review the call state messages
{
switch( dwParam1 )
{
case LINECALLSTATE_IDLE: // went idle
{
LONG retcode;
LINECALLINFO LineCallInfo;
//
// load call info into structure
memset( &LineCallInfo, 0, sizeof
( LINECALLINFO ) );
LineCallInfo.dwTotalSize = sizeof
( LINECALLINFO );
lineGetCallInfo( (HCALL)hDevice,
&LineCallInfo );
//
// deallocate the call
retcode = lineDeallocateCall( (HCALL)
hDevice );
//
// post message to main dialog
PostMessage((HWND)dwInstance, WM_COMMAND,
➂TAPI_LINECALLSTATE_IDLE, (LPARAM)(HLINE)LineCallInfo.hLine );
break;
}
case LINECALLSTATE_CONNECTED: // hey, we got
through!
{
//
// local vars for processing
LPVARSTRING lpVarStringStruct = NULL;
size_t sizeofVarStringStruct = sizeof
( VARSTRING ) + 1024;
HANDLE CommFile = NULL;
long lreturn;
// get the comm handle. Be sure to drop
this handle when
// the call is done or you'll get device
unavailable errors
// and have to REBOOT!
lpVarStringStruct = LocalAlloc( 0,
sizeofVarStringStruct );
do
{
memset( lpVarStringStruct, 0,
sizeofVarStringStruct );
lpVarStringStruct->dwTotalSize = ➂
(DWORD)sizeofVarStringStruct;
lreturn = lineGetID( 0, 0, (HCALL)
hDevice, ➂LINECALLSELECT_CALL, lpVarStringStruct, "comm/
datamodem" );
} while( lreturn != 0 );
//
// get comm device handle and save it to
properties area
CommFile = *( (LPHANDLE )( ( LPBYTE )
lpVarStringStruct + ➂lpVarStringStruct->dwStringOffset ) );
SetProp( (HWND)dwInstance, "HCOMM",
CommFile );
SetVarProps( (HWND)dwInstance, hDevice );
//
// tell main dialog we got through
PostMessage( (HWND)dwInstance, WM_COMMAND,
➂TAPI_LINECALLSTATE_CONNECTED, (LPARAM)(HANDLE)CommFile );
LocalFree( lpVarStringStruct ); // drop mem
space
break;
&nbs
p; }
Notice that the LINE_CALLSTATE_CONNECTED routine contains code that retrieves the comm handle and gets the
lineID. You'll need these values in subsequent calls to TAPI services. The LINE_CALLSTATE_IDLE routine simply
de-allocates the device handle to free up resources for the next call.
The second segment of the callback routine passes messages to the main dialog box for display. These are added here to
show you how the messages work and how call progress can be reported. You may not need to add these messages to
your production applications. Listing 24.10 shows the portion of lineCallBackProc that posts messages to the
MainDialog routine.
In the final section of the lineCallBack routine, several other potential messages are listed, but no code is added.
This is just to give you an idea of the other messages that can be received by lineCallBackProc. Listing 24.11
shows the final section of the lineCallBackProc routine.
case LINEDEVSTATE_RINGING:
break;
}
break;
//
// other messages that we'll ignore here
//
case LINE_REQUEST:
case LINE_ADDRESSSTATE:
break;
case LINE_CALLINFO:
break;
case LINE_DEVSPECIFIC:
break;
case LINE_DEVSPECIFICFEATURE:
break;
case LINE_GATHERDIGITS:
break;
case LINE_GENERATE:
break;
case LINE_MONITORDIGITS:
break;
case LINE_MONITORMEDIA:
break;
case LINE_MONITORTONE:
break;
} /* switch */
} /* LineCallBackProc */
That concludes the code review for the TAPIOUT project. In the next section, you'll get to test the TAPIOUT project.
If you have a C compiler that can handle the TAPIOUT code, compile it now. If you do not have a compiler, you can
load the TAPIOUT.EXE from the CD-ROM that ships with the book.
When you first load TAPIOUT you'll see a simple dialog box. Enter a valid phone number in the input box and press
PlaceCall. You'll see several messages appear in the status box as TAPI attempts to complete your call (see Figure
24.2).
In a production application you would add code that responded to the LINE_CALLSTATE_CONNECTED message by
notifying the user to pick up a connected handset (for voice calls), beginning a data send (for fax or data calls), or
possibly starting a recorded message (for automated voice calls).
Summary
In this chapter you learned how to use the Basic Telephony API functions to write a short dialer program in C. The
techniques you learned here will be used throughout all the TAPI projects in this book. Even the projects written in
Visual Basic 4.0 will use the same API calls in the same order.
In the next chapter, you'll learn about the details of hardware configurations including the limitations and advantages of
standard modem cards, voice-modem cards, and telephony hardware.
Chapter 28
CONTENTS
❍ Testing TAPIANS
● Summary
Now that you know how to use TAPI services to handle outbound calling, it's time to create an example of handling
inbound calls. In this chapter, you'll learn how to use the TAPILINE control that ships with the book to create a simple
Visual Basic program that will wait for an incoming call and notify you with "ring." You can then pick up an attached
receiver to accept the call. If you have a speaker phone card in your workstation, you can simply click a button on the
screen and begin to talk.
Before you can accept incoming calls, you need to open an available line device with parameters that tell TAPI you plan
to use the line for incoming calls. This chapter will show you how to select a valid line device and how to use the
lineOpen method to prepare TAPI to respond to inbound calls.
In order to respond to and accept incoming calls, you need to know how to interpret a new set of TAPI messages. These
messages are sent to the TapiCallBack event of the TAPILINE control. In this chapter, you'll learn which messages
are used to monitor inbound calls and how to write Visual Basic code that responds to each message.
Finally, you'll learn how to end inbound calls properly, including how to respond when the caller on the other line hangs
up unexpectedly. When you finish the project in this chapter, you'll know how to write telephone answering routines for
all your Visual Basic and VBA-language programs.
Before you start to code the example, it's important to go over the basics of how to use TAPI services to handle inbound
calls. The process is not very complex, but it can be tricky because you need to write a program that can wait for a
telephone event and then respond accordingly. Since the program is designed to wait for a call, it is the telephone that
really controls the program flow.
There are a total of six basic steps to using TAPI to process inbound calls (see Figure 28.1). Several of these steps are
pretty much the same as those used to handle outbound calls.
This process can best be illustrated using a typical voice call between two people. Let's assume you need to create a
Visual Basic program that will answer incoming calls and route them to a telephone handset or speakerphone. You'll
want to allow the user to start TAPI, wait for a call, accept any inbound calls, and, at the end of a conversation, end the
call and wait for another one.
Starting TAPI services on the workstation requires initializing the TAPI driver (lineInitialize), verifying the
current TAPI API version (lineNegotiateAPIVersion), and collecting information on all the available TAPI
devices on the workstation (lineGetDevCaps). Then you must select one of the line devices that is capable of
handling inbound calls and open it for inbound handling (lineOpen). By opening the device in this way, you are
telling TAPI that you want to be told whenever an inbound call shows up on the line.
At this point, all you need to do is wait for TAPI to tell you when a call appears on the selected line. When it does, your
program will receive a message (through the TapiCallBack event) telling you that a call is being "offered" to you. If
you decide to accept the call, you can retrieve the handle to the call and answer (using the lineAnswer method). Once
this is done, your program can pass the call to the user and wait until the conversation is completed.
There are basically two ways a phone conversation ends: Either you hang up or the person (machine) on the other end
hangs up. In the first case, you can use the lineDrop and lineClose methods to end the call. If, however, the other
party hangs up (possibly unexpectedly), the line is automatically closed by TAPI. However, you still need to release your
program's hold on the call handle (using lineDeallocateCall). In either case, performing lineClose or
lineDeallocateCall will prepare the line device for receiving additional inbound calls.
There are two main points to keep in mind when building applications to accept inbound calls. First, after you open the
device using parameters that inform TAPI that you want to be notified of any inbound calls, you need to watch for (and
respond to) TAPI messages that occur when a call appears. The second main point is that once a call is accepted and in
progress, your program needs to watch for (and respond to) TAPI messages that indicate the call has ended. In other
words, your inbound call handler has two main jobs:
The rest of this chapter shows you how to use the TAPILINE control and Visual Basic to create a program that performs
all the tasks necessary for handling inbound voice calls.
Note
The example in this chapter assumes your workstation has a modem or
telephony card that is capable of handling voice calls. If your modem is able
to handle only data calls, you can still build the project described here.
However, you will be able to complete only data modem calls, not voice
calls.
Note
For more information on the TAPILINE control and the TAPI library files,
see Chapter 26, "TAPI Tools-Using the TAPILINE Control."
Finally, you'll need to design an input form that allows users to start the TAPI call monitor, accept offered calls, and
hang up completed calls.
When you are done with this example, you'll have a handful of TAPI answering routines that you can use in all your
Visual Basic or VBA-compatible programs.
First, you need to lay out a Visual Basic form with four command buttons, one list box, and one multiline text box. This
form will be used to start TAPI services and accept and end calls on the selected line. Refer to Table 28.1 and Figure
28.2 when building the TAPIANS form.
There are a few things that you need to keep in mind when building this form. First, be sure to add the TAPILINE
control to your project (Tools | Custom Controls) before you start building the form. Also notice that you need
to add the MCI Multimedia control to the form. You'll use that control to produce the sound of a ringing telephone
whenever a call appears on the monitored line. Notice that the MCI control has its Visible property set to FALSE.
There is also an Image control on the form. You'll load an image of a multiline telephone into this control at run-time.
Be sure to set the Stretch property of the Image control to TRUE. Finally, by now you should recognize the control
array used with the command buttons. Be sure to add the first button, set its values (including the Index value), and
then use Edit | Copy and Edit | Paste to add all the buttons in the control array.
When you are finished building the form, save it as TAPIANS.FRM and save the project as TAPIANS.VBP before you
go on to the next section.
There are a handful of code routines you need to add to the TAPIANS form. But, before you add code to the form, you
need to add two BAS modules to the project (File | Add File). These modules can be found on the CD-ROM that
ships with this book. The first is the TAPILINE.BAS module. This module contains all the TAPI constants, structures,
and helper APIs needed to support the TAPILINE control. The second BAS module you need to add to the project is the
TAPICALL.BAS module. This routine contains several helper functions and subroutines for decoding TAPI messages.
There are also several general routines in this module to handle typical TAPI operations for Visual Basic programs
Tip
The easiest way to add the TAPICALL and TAPILINE modules to your
project is to first copy them into the project directory, and then use the
File | Add File menu option to place them into your project. You
probably have more than one copy of these routines on your hard disk. You
used them in Chapter 26, "TAPI Tools-Using the TAPILINE Control." If
you can't find them on your hard disk, you can find them on the CD-ROM
that ships with this book.
The code in the Form_Load event calls for support routines to set up TAPI and the MCI control. The rest of the code
centers the form and sets properties for the form and various controls on the form.
Listing 28.2 shows the code for the Form_Unload event. This code makes sure that TAPI services are properly closed
down before exiting the program. Add this code to the TAPIANS form.
Nothing complex here. The only item to note is the inclusion of the two parameters for the lineDrop method. These
parameters are used only when passing user information on ISDN lines. Since this program assumes you'll be using
voice-grade telephone lines, the values are left empty.
There are just a few support routines needed for the TAPIANS project. These routines handle the initialization of TAPI
and multimedia services for the workstation. You'll also add a routine to clear out TAPI pointers at the start of the project
and the end of a call.
First, you need a routine to handle the initial startup of TAPI services for the workstation. This routine should start
TAPI, get a count of all the available line devices, and confirm the API version of the devices. The code in Listing 28.3
shows you how to do this. Add a new sub-routine called TAPIStart on the form (use Insert | Procedure), and
enter the code shown in Listing 28.3.
You've seen most of this before. Notice that the lRtn value is checked after attempting to start TAPI. You'll see that the
test is for a value less than zero. Several TAPI functions return positive values instead of zero when they are successful.
However, error values are always less than zero. You'll also notice the hexadecimal values &H10000 (decimal 65536)
and &H10004 (decimal 65540) as parameters in the lineNegotiateAPIVersion method. These are the minimum
(1.0) and maximum (1.4) API versions supported by the TAPILINE control. Notice also that the numDevices
property of the TAPILINE control is set to the total number of recognized line devices for the workstation. You'll use
this value to collect information on all the registered line devices.
Next, you need to add a routine to get the capabilities of all the line devices you discovered in the TAPIStart routine.
You'll use the numDevices property just mentioned above. Listing 28.4 shows the new routine TapiGetDevCaps.
This routine fills a list box with the names of all the registered devices. With this list, the user can select the device most
appropriate for voice calls. Add the code in Listing 28.4 to your form.
Listing 28.4. Adding the TapiGetDevCaps routine.
You can see that after calling the lineGetDevCaps method of the TAPILINE control, you need to call the
lineDevCapsFunc API function to retrieve the data in the LINEDEVCAPS structure. This data is filled in by TAPI
when you use the lineGetDevCaps method. This routine uses the GetOffset helper function (from the
TAPICALLS library) to pull out the line device name and insert it into the list control on the form.
Since this project will use a WAV file to signal a ringing phone, you'll need to add some code to initialize the MCI
Multimedia control to handle WAV output. Add a new subroutine called MCIStart and enter the code shown in
Listing 28.5.
The last support routine you need to add to the project is one that clears TAPILINE control properties related to a
completed call. You'll use this routine at the very beginning of the program (for initialization) and at the end of each call
(for cleanup). Add a new subroutine called ClearHandles and enter the code shown in Listing 28.6.
These properties are all set when a call is accepted on a line device. Once a call is ended, you need to clear these
properties in preparation for the next call.
The next code you need to add to the project relates to detecting and responding to TAPI messages (TapiCallBack).
The TapiCallBack event of the TAPILINE control receives all messages generated by the TAPI system. As
mentioned in earlier chapters, there are several different messages, each with their own set of accompanying parameters
Note
For more information on TAPI messages, their use, and meaning, refer to
Chapter 23, "TAPI Architecture."
● LINE_CALLSTATE
● LINE_CLOSE
The LINE_CALLSTATE message is sent whenever the state of the selected line has changed. This program will watch
for messages that indicate there is a new call on the line (dwParam1 = LINECALLSTATE_OFFERED) or when the
party on the other end of the line closed the call unexpectedly (dwParam1 = LINECALLSTATE_IDLE). The
LINE_CLOSE message is sent whenever the user ends the call normally.
You can monitor the message activity by adding code to the TapiCallBack event that checks each message sent by
TAPI. This is done using a SELECT CASE structure to compare the incoming message to a list of messages for which
you are waiting. The code in Listing 28.7 shows how this is done. Add this code to the TapiCallBack event.
The code in Listing 28.7 shows the SELECT CASE structure monitoring two messages-LINE_CALLSTATE and
LINE_CLOSE. When the LINE_CALLSTATE message appears, additional checking is performed to determine the
exact state of the call. If a new call is appearing on the line (LINECALLSTATE_OFFERING), the program updates the
HandleToCall property and then rings the workstation to tell the user a call is coming in.
If the call state has changed to completed (LINECALLSTATE_IDLE), the routine releases memory allocated to the call
and clears the HandleToCall property in preparation for the next incoming call.
When the LINE_CLOSE message appears, that means the user has completed the call. The code here makes a call to the
cmdButton array that will act as if the user pressed the HangUp button (you'll code that in just a moment).
You'll also notice that the routine contains calls to the TapiCallBackHandler function. This function interprets the
TAPI message and creates a text string that can be shown to the user to indicate the progress of the call.
The last code you need to add will handle the user's actions on the command buttons. There are four possible actions that
the user can select:
Listing 28.8 shows the complete code for the cmdButton_Click event.
There's a lot going on in this code section, so it is worth reviewing in greater detail.
First, if the user has pressed the Start button, the code checks to make sure a line device has been selected from the list
box. If no line device was selected, the user gets an error message, and the code exits the procedure (see Listing 28.9).
If the user has selected a valid device and pressed the Start button, the routine attempts to open the selected line
device for interactive voice services with owner and monitor privileges.
Note
The privilege values tell TAPI that the program wants to "own" any
incoming calls. But, if for some reason this workstation cannot own the
calls, at least it wants to be able to "see" (monitor) them. This is the
standard privilege profile for incoming calls.
If an error occurs when attempting to open the line, an error message is delivered to the text box. If the line is opened
successfully, the appropriate message is sent to the text box, and the Answer and HangUp buttons are enabled while
the Start button is disabled (see Listing 28.10).
Once the line is opened, the user will receive a notice (the sound of a ringing phone) when a call appears on the line. The
user can press the Answer button to accept the call. Listing 28.11 shows the code that calls the LineAnswer method
to accept a call.
This code is quite simple, but it hides a very important aspect of accepting an incoming call. The lineAnswer method
assumes that you already have the HandleToCall property set to point to the call handle provided by TAPI. However,
this property is not automatically set for you. Since you may not want to accept a given call, TAPI supplies you with
only the call handle. This is done as part of the LINE_CALLSTATE message. Check out the code in Listing 28.7 to see
where the HandleToCall property is set for you.
The last bit of code in the cmdButton_Click event that needs some attention is the code that handles the HangUp
button. This button is pressed to close out an active call. When a user decides to end a call, both the lineClose and
lineDrop methods should be invoked to clear resources. It is also a good idea to clear any handle properties that relate
to the previous call. Listing 28.12 shows the HangUp code in detail.
That completes the coding of the TAPIANS project. Be sure to save the form (TAPIANS.FRM) and the project
(TAPIANS.VBP) before you continue. In the next section, you'll test the TAPIANS project to make sure it is working
properly.
Testing TAPIANS
Now that you have completed the TAPIANS project, you're ready to test it out. To do this, you'll need a workstation
configuration that includes either a built-in TAPI-compliant speakerphone or a standard telephone handset connected to
the "out" jack of a voice-capable modem. Figure 28.3 shows an example of how you can arrange a standard voice-data
modem and telephone handset to handle inbound TAPI calls.
Note
If you do not have a voice-capable modem or telephony card, you can still
test this project. You'll need to change the MONITORMEDIA_ mode from
INTERACTIVEVOICE to DATAMODEM. This will set up your application
to accept data modem calls. When the call is answered, TAPI will
immediately begin sending data handshaking signals to the party at the
other end of the phone.
In order to fully test the project, you'll need to answer an incoming phone call. If you are working from a home or office
that has multiple phone lines, you can simply set up the TAPIANS program to monitor a line and call that line from
another phone in the same building.
If you are working where you have only one line for both incoming and outgoing calls, you'll need to arrange for
assistance from a friend. After running the TAPIANS project and pressing the Start button, call a friend and ask him
or her to call you back at the same number that TAPIANS is monitoring.
Tip
You can purchase from office and telephone equipment suppliers devices
that will simulate an incoming call on the line. These devices cost very little
and are handy if you want to take your TAPI software to demonstrations,
shows, or other events where you cannot rely on access to active telephone
lines.
Once you have your test call lined up, you can run the TAPIANS project and begin testing.
The first thing you'll see is a list of all the line devices that TAPI recognizes on your workstation. Scroll through the list
and select the one that will be able to handle the media mode for your test call (voice-data modem). Once you highlight
the line device, you can press the Start button to tell TAPI to begin monitoring the line device for incoming calls. You
should receive a message in the status box that tells you that TAPI services were started and that the program is waiting
for a call (see Figure 28.4).
Now you need to get TAPIANS to see an incoming call appear on the line. If you have another phone line available to
you, use it to call the phone line that TAPI is monitoring. If you don't have multiple phone lines, now is the time to call
one of your friends to ask him or her to call the TAPI-monitored line.
Note
If you are using a call simulator, you can just flip the switch that sends a
"tip-and-ring" signal along the line. This will tell TAPI a call is coming in.
When a call appears on the line, TAPI sends a LINE_CALLSTATE message with a dwParam1 equal to
LINECALLSTATE_OFFERING. This tells your program that a new call is being offered to you for acceptance. At this
point, the TAPIANS program will copy the hDevice value passed in the LINE_CALLSTATE message into the
HandleToCall property of the TAPILINE control. This prepares the control to answer the offered call.
When the user presses the Answer button, TAPIANS invokes the lineAnswer method. If this function call is
successful, TAPI returns three messages in quick succession. First, TAPI sends a LINE_REPLY message back to
TAPIANS. This message is sent by TAPI whenever an asynchronous TAPI service request is completed. This message is
not important for this application and can be disregarded.
Note
LINE_REPLY messages are useful for applications that deal in multiple
phone lines. Since there are several possible asynchronous activities that
can occur during TAPI services, keeping track of the RequestID is
important. Since this application is working with only a single phone line, it
is not so important to keep track of the RequestID value.
The next message TAPI returns is a LINE_CALLSTATE message with a dwParam1 equal to
LINECALLSTATE_AccEPTED. This message tells TAPIANS that the lineAnswer method was successful. Finally,
TAPI will return a message of LINE_CALLSTATE with a dwParam1 set to LINECALLSTATE_CONNECTED. This
message indicates that the telephone call has been successfully connected. You can now pick up the telephone handset
and talk to the person at the other end of the line (or to yourself, depending on how you placed the call!).
This set of messages and responses from your Visual Basic program happens quite quickly. Figure 28.5 shows how the
messages appear in a typical call appearance.
When you are ready to end the telephone call, you press the HangUp button on the TAPIANS dialog box. This will
cause the program to drop the call and close the line. You'll get a short message telling you the line was closed
successfully.
That's all there is to it. You now have a functional Visual Basic program that can see calls coming in on the line and give
users the chance to answer those calls using a Visual Basic program.
Summary
In this chapter, you learned how to use the TAPILINE control to create a Visual Basic application that can monitor a
telephone line for incoming calls and allow users to answer those calls. In the process, you learned about the six steps
needed to handle inbound calls using TAPI services:
You also learned the importance of the LINE_CALLSTATE message sent by TAPI whenever a new call appears on the
line and when an active call becomes idle (the other party hangs up). You learned how to write code in the
TapiCallBack event of the TAPILINE control to watch for and respond to LINE_CALLSTATE messages.
Finally, you learned the importance of getting the call handle from the LINECALLSTATE_OFFERING message and
placing this value into the HandleToCall property of the TAPILINE control. This must be done before you can
invoke the lineAnswer method to accept the incoming call.
In the next chapter, you'll combine the skills you learned here with the techniques you learned in Chapter 26 for placing
outbound calls to build a "virtual phone" for your pc. This application will allow users to place outbound calls and accept
inbound calls.
Chapter 29
CONTENTS
● Summary
Now that you have a good idea of how the TAPI system is designed, it's time to start writing some TAPI programs! This
chapter covers the simplest form of TAPI-the outbound voice-phone call. When you complete this chapter you'll know
how to add phone-dialing capabilities to Excel spreadsheets (or any other VBA-compatible system) and you'll build a
complete Visual Basic 4.0 online phone book that can store names and numbers, place calls, and log your call history
into an Access database for tracking and reporting purposes.
Before jumping into the coding routines for Assisted TAPI, it is a good idea to review and test out the two API calls that
you'll use throughout the chapter. The two functions you need to work with in order to complete Assisted TAPI calls are:
● tapiGetLocationInfo-Returns the current country and city (area) codes set in the TELEPHON.INI file.
● tapiRequestMakeCall-Initiates a voice-phone call by passing the dialing address (phone number) and
other optional parameters including the dialing application to use, the name of the person you are calling, and a
comment about the nature of the call.
The TAPI system uses the tapiGetLocationInfo to determine the current country/area code settings when
attempting to dial a phone number passed in the tapiRequestMakeCall function. For example, if the code supplied
in the call request includes an area code, TAPI will check to see if it matches the current area code. If the two codes
match, TAPI will not dial the area code since it is understood that it is not needed to successfully place a call. This
means that you can store complete area code information with all your phone numbers in your address book. Just like
you, TAPI is smart enough to skip the area code when it's appropriate.
Tip
Although TAPI's ability to strip out area codes is handy, it can cause minor
problems. In several areas of the world, phone companies allow local calls
across area codes-especially in locations where both parties live close to one
another along the "dividing line." In these cases, TAPI will notice the area
code difference and attempt to place the call using the area code. This
usually results in error tones from the phone exchange. If you are in an area
where this occurs, you'll need to leave out area codes from the affected
address entries.
tapiGetLocationInfo is a read-only function. You cannot use it to set the current country or area code. However,
you can use this function to return the country/area code string to be used as a default in building new entries in an
online address book. You'll see how this works later in the chapter.
For now, let's put together a short project that illustrates the two Assisted TAPI functions you'll use throughout the
chapter. You'll need a modem connected to your pc and a telephone handset connected to the same line as the modem.
Figure 29.1 shows how the equipment should be connected for all Assisted TAPI calls.
Figure 29.1 : Modem, pc, and phone connections for Assisted TAPI
To test the Assisted TAPI functions, start a new Visual Basic 4.0 project and place buttons on the form. Set the caption
of Command1 to Get Info and the caption of Command2 to Place Call. Next add a code module to the project
(select Insert | Module from the main menu) and add the code shown in Listing 29.1 to the declaration section of
the module.
Option Explicit
'
' declare assisted tapi functions
'
#If Win32 Then
Declare Function tapiRequestMakeCall Lib "TAPI32.DLL" (ByVal
lpszDestAddress As
➂String, ByVal lpszAppName As String, ByVal lpszCalledParty As
String, ByVal ➂lpszComment As String) As Long
Declare Function tapiGetLocationInfo Lib "TAPI32.DLL" (ByVal
lpszCountryCode As ➂String, ByVal lpszCityCode As String) As Long
#Else
Declare Function tapiRequestMakeCall Lib "TAPI.DLL" (ByVal
lpszDestAddress As ➂String, ByVal lpszAppName As String, ByVal
lpszCalledParty As String, ByVal ➂lpszComment As String) As Long
Declare Function tapiGetLocationInfo Lib "TAPI.DLL" (ByVal
lpszCountryCode As ➂String, ByVal lpszCityCode As String) As Long
#End If
These are the two Assisted TAPI functions in their 16-bit and 32-bit form. If you are only working in one environment,
you can remove the extra code. But, if you plan to use this application code in more than one environment, leave the two
sets of declares in the file.
Now you need to add code behind the two buttons. Listing 29.2 shows the code for the
Command1 (Get Info) button. Add this code to the Command1_Click event.
Pressing this button causes Visual Basic to display a message box showing the current country and city code (area code)
stored in the TELEPHON.INI file/registry.Notice the variable declaration sets string sizes for the cCountry and
cCity variables. This is needed in order to make sure the tapiGetLocationInfo function returns clean data. You
also need to make sure you trim the returned variables. TAPI will return these two variables as zero-terminated strings
(the last character is a zero-Chr(0)). Zero characters are unprintable in Visual Basic and can produce unexpected
results. It's always a good idea to clean your string upon return from API calls.
The code in Listing 29.3 shows how to place a call using the tapiRequestMakeCall function. Add this code to the
Command2_Click event.
Only the first parameter (the phone number) is required for the tapiRequestMakeCall function. The other
parameters are optional (dialing application, called party, and comment). You'll use those variables in the Visual Basic
4.0 project at the end of the chapter.
Save the form as TAPI01.FRM, the module as TAPI01.BAS, and the project as TAPI01.VBP. Now run the project.
When you click on the Get_Info button, you'll see your country and city code. When you click on the Place_Call
button, you'll be asked to enter a phone number to dial. Visual Basic will hand the number to the TAPI DLL, which will
call the default dialer application (DIALER.EXE) which will then process the call. You'll hear the phone dialing and see
the dialog asking you to pick up the phone and begin speaking.
Those are the basics of Assisted TAPI calls. Now you can use this knowledge to add dialing capabilities to an Excel
spreadsheet.
It's really quite easy to add outbound dialing to any Excel spreadsheet. Since you only need one API call
(tapiRequestMakeCall), you have very little code to deal with. All you need is a single declare statement to cover
the API call and one subroutine to handle the details of gathering the phone number from the user and calling the API
function.
For this chapter, you'll create a very simple phone book using Excel. The example here will allow users to create a two-
column table within a worksheet that contains a name in one column and a phone number in the next column. Users can
highlight a name and then press an on-screen command button that will then place the call for them.
Note
The example illustrated here was done using Excel 95, but the same general
idea can be handled in Excel 5.0.
Start Excel and/or open a new workbook. Since you'll be doing a bit of coding, be sure that the Visual Basic toolbar and
the Forms toolbar are visible. If not, select View | Toolbars and then place a check mark next to Visual Basic
and Forms. Figure 29.2 shows you what your Excel spreadsheet should look like.
The first thing you need to do is add the TAPI function declaration. To do this you must first add a code module to the
project. Select Insert | Macro | Module from the main menu or click on the Insert Module icon in the
Visual Basic toolbar. Once the module has been added to the project, rename the tab to VBA Code. Now insert the code
in Listing 29.4.
Figure 29.2 : Adding the Visual Basic and forms tools to Excel
Warning
This example uses the 32-bit version of the API call. This code will not run
under 16-bit Windows environments (Window 3.11 or Windows for
Workgroups). If you are using Excel in a 16-bit environment, modify the
declare statement to reference TAPI.DLL rather than TAPI32.DLL.
Now you need to add a small subroutine that will determine the cell selected by the user, locate the associated phone
number and then place the call using the TAPI function declared in Listing 29.4. Listing 29.5 shows the code needed for
this routine. Place this code in the same module that contains the API declare.
'
' call number in active cell
'
Sub CallBtn()
Dim x As Long ' for return
Dim cPhone As String
Dim cName As String
'
cName = ActiveCell
cPhone = ActiveCell.Offset(0, 1)
'
x = tapiRequestMakeCall(cPhone, "", cName, "")
'
End Sub
You'll notice that this routine passes empty strings for the second and fourth parameters of the API call. These are
optional parameters and are not needed for our example. You'll use these extra parameters in the Visual Basic 4.0
example later in this chapter. Now all you need to do is lay out the worksheet page to contain the name/phone number
pairs. Figure 29.3 shows one way to lay out the form.
Notice that this worksheet contains a command button. Use the Create Button icon from the Forms toolbar to add
the button to the worksheet. When you are prompted to enter the macro associated with this button, be sure to enter the
name of the subroutine shown in Listing 29.5 (CallBtn). It doesn't matter where you place things on the form. Just be
sure to arrange the Name and Phone columns next to each other. The CallBtn subroutine will only work if the two
columns are arranged side-by-side in the proper order. This example also has some friendly instructions to help the first-
time user.
Save the file as QikDial.xls and then highlight a name and press the Call button. You should hear the modem in
your pc dialing the number and see a dialog box on your screen that looks like the one in Figure 29.4.
You now have a short set of code that can be added to any VBA-compatible program, including Microsoft Project and
Microsoft Access. In the next section, you'll create a complete application in Visual Basic 4.0 that performs basically the
same function.
TeleBook is a sample telephone dialing application that can be used to place voice calls using the modem attached to
the pc. Once the phone number has been dialed, a dialog box will appear telling the user to pick up the handset and start
talking. The user can then click on another button to hang up the call upon completion.
This project has three forms, one class module, and one basic code module. The main form contains a list box showing
the list of all the people in the phone book and a set of pushbuttons that mimic the keys on a standard single-line phone.
A set of command buttons appears at the bottom of the form to allow users to add, edit, delete, find, and dial numbers
selected from the phone list. Figure 29.5 shows how the form will look when it is completed.
The second form in the project is the phone list entry form. This form appears when the user selects the add or edit
buttons on the main form. This is where the user can add or edit TeleBook entries (see Figure 29.6).
The last form shows a log of all calls placed using the TeleBook application. TeleBook saves all call data in a
Microsoft Access database for later use. The call history data can be shared with other programs that are capable of
reading Microsoft Access files, including Excel, Microsoft Query, and several reporting tools. Figure 29.7 shows the
Call Log form.
Before laying out the forms, you first need to create the basic code module that contains the TAPI declare statement and
the support routines. You also need a class module to encapsulate the API call into an easy-to-use method that allows
four property settings.
Start a new VB4 project and insert a new module. Add the code shown in Listing 29.6. This is the only Telephony API
you need for the TeleBook application.
'
' declare tapi-assist API
'
#If Win16 Then
Declare Function tapiRequestMakeCall Lib "TAPI.DLL" (ByVal
lpszDestAddress As ➂String, ByVal lpszAppName As String, ByVal
lpszCalledParty As String, ByVal ➂lpszComment As String) As Long
#Else
Declare Function tapiRequestMakeCall Lib "TAPI32.DLL" (ByVal
lpszDestAddress As ➂String, ByVal lpszAppName As String, ByVal
lpszCalledParty As String, ByVal ➂lpszComment As String) As Long
#End If
Notice that the code in Listing 29.3 uses the #If...#Else...#End If compiler directive. This is done to create a
code piece that will compile properly for both 16-bit and 32-bit environments.
Before continuing, save this module as TBOOK0.BAS and save the project as TELEBOOK.VBP.
Now that the API declare is done, you are ready to build a class module that will encapsulate the API. By writing a class
module as a "wrapper" for the API, you'll make your program easier to read and much easier to update in the future-
especially if the syntax of the API declare changes in the future.
Insert a class module into your VB project, set its Name property to clsPhoneCall, and add the code shown in listing
29.7.
Option Explicit
The code in Listing 29.7 declares four variables used within the class module to keep track of the property values set
using the Property Get and Property Let routines. Next you need to add these routines.
Warning
Be sure to use the Insert | Property menu option from the Visual
Basic main menu. If you simply type Property Get and Property
Let in the module, it will not work properly.
Listing 29.8 shows all four sets of Property Get/Property Let statement pairs. Use this listing to build your
class module.
The last routine you need to add to the class module is a function called RequestMakeCall. Since this function is
built within a Visual Basic class module, it will act as a VB method. You'll call this method in the main program (see
Listing 29.9).
Now it's time to build the onscreen forms for the TeleBook application. Use the information in Table 29.1 and Figure
29.5 to build the main form.
There are a few things to be aware of as you build this form. It contains two command button arrays-cmdKey(0-11)
and cmdBtn(0-5). You can save typing by adding the first button and then using Edit | Copy, Edit | Paste
from the Visual Basic main menu.
Also, be sure to place the Frame control on the form before you place any of the controls that appear within the frame.
When you place controls in the frame, you must click the control from the toolbox and then draw the control within the
frame.
Finally, be sure to set the Data1.DatabaseName property to point to the TELEBOOK.MDB database that ships with
the CD-ROM. It can be found in the chAP29 directory.
Note
If you want to run the project, but don't want to do all the typing, you can
find the source code in the chAP29 directory. Simply load this project
(make sure the data control is set properly) and run it!
Now that the form controls have been placed, there are six form events that need to be coded and two form-level
variables that must be declared. Open the code window of the form and add the code in Listing 29.10 in the general
declaration area.
Option Explicit
'
' form level vars
'
Dim nWidth As Integer
Dim nHeight As Integer
Next add the code for the Form_Load event of the form (see Listing 29.11). If you are not saving the code in the same
directory that contains the TELEBOOK.MDB database, you need to modify the line that sets the Data1.
DatabaseName property to point to the directory that contains the database.
The first two lines of code in Listing 29.11 store the form's initial width and height. This will be used in the resize event
to override users who attempt to resize the form. This could be done by setting the form's BorderStyle property to
something other than 2 (Sizable). However, other styles do not allow the minimize button to appear. Because we want to
allow users to minimize the TeleBook (to keep it handy!), we'll use this workaround to prevent users from resizing the
form.
Listing 29.12 shows the code for the Form_Resize event that uses the variables we are talking about. Add this code to
the main form.
The main code for the TeleBook form is contained in the cmdBtn_Click event. This is where all the command
button clicks are handled. Listing 29.13 shows the code needed to handle the Add, Edit, Delete, Call, Log, and
Exit buttons for the form.
Note
This routine contains calls to four support routines (ClearRec, SaveRec,
LoadRec, and CallRec). You'll build these routines as the last step in the
project. If you attempt to run the program before you build these routines,
you'll get an error message.
The code needed to handle the phone keypad buttons is much simpler than the code shown in Listing 29.13. Add the
code shown in Listing 29.14 to the cmdKey_Click event.
The code in Listing 29.14 takes the Index property of the cmdKey button and uses that as a pointer into a string that
contains all the associated digits.
Another short code routine is the one in the DBList1_DblClick event. This code simply mimics the pressing of the
Edit button whenever the user double-clicks a name in the phone list. Add the code shown in Listing 29.15.
The last bit of code for this form is the one that actually makes the phone call (finally!). Listing 29.16 shows the code
needed for the cmdDial_Click event. This event reads the phone number entered into the txtDial control from the
direct keypad and passes that to the properties of the new clsPhoneCall object. Once the properties are set, the
RequestMakeCall method is invoked on the clsPhoneCall object.
That completes the code for the frmTeleBook form. Save the form as TBOOK1.FRM and save the project. You'll
receive errors if you run the project now since there are four support routines called in the cmdBtn_Click event that
will be defined in the following section.
The phone book entry form is used to add or edit phone entries. It contains two large control arrays-txtFields(0-8)
and lblLabels(0-8). These controls contain the data field contents and the data field names, respectively. Along
with the Save and Cancel buttons, there is an additional invisible label control used as a flag value read from the main
form. Use the information in Table 29.2 and Figure 29.6 to build the data table entry form.
Once you complete the form design, save the form as TBOOK2.FRM.
Only two events need some code. Listing 29.17 shows the code needed for both the Form_Load event and the
cmdBtn_Click event of the frmTbMaster form.
After entering the code from Listing 29.17, save the form as TBOOK2.FRM and save the project before continuing.
The last form in the project is the Phone Log. This form shows a list of all the calls made using the TeleBook
application. Use the data in Table 29.3 and Figure 29.7 to build the form.
Note
Be sure to change the DatabaseName property of the Data1 control to
point to the directory on your workstation that holds the TELEBOOK.MDB
file that was installed from the CD-ROM that ships with this book.
There are only two code routines needed for the frmTBLog form. The first just centers the form at load time. Add the
code in Listing 29.18 to the Form_Load event.
Finally, add the code in Listing 29.19 behind the Command1_Click event of the two-button control array.
Listing 29.19. Adding code to the click event of the button array.
The first case (Case Is = 0) handles the process of deleting all existing records in the log table. The second case
exits the form.
There are four support routines needed to complete the TeleBook project. Three of these routines deal with reading
and writing phone book data records. The fourth routine uses the clsPhoneCall object to place a call.
Open the TBOOK0.BAS module and insert a new subroutine called ClearRec. This routine will be used to clear the
data entry fields before adding a new record. Add the code in Listing 29.20.
Next, add the routine needed to read an existing record from the database into the input form. Insert a new function
called LoadRec and add the code in Listing 29.21. Be sure to modify the function declaration line to include the string
parameter and the As Boolean return declaration.
Listing 29.21. Coding the LoadRec support function.
The next routine needed is the one that writes the updated input data back to the data table. Insert the SaveRec
subroutine and add the code shown in Listing 29.22. Be sure to add the two parameters to the declaration line.
The final routine of the project is used to place a call from the command button array on the main form. Insert a new
subroutine called CallRec and add the code shown in Listing 29.23.
After entering this routine, save the TBOOK0.BAS module and save the project. Run the project to check for errors and
correct any problems before you create a compiled version of the TeleBook application.
You are now ready to run the TeleBook application. When you first start the application, you'll see an empty phone list
and the phone keypad. You can dial a number by clicking on the keypad, then click the Direct Dial button. Once
you click the dialing button, you'll see the Windows DIALER.EXE program start and show you the number you are
dialing along with the name of the person you are calling (see Figure 29.8).
You can also click on the Add button at the bottom of the form to bring up the phone book entry form. When you fill out
the form and click the Save button, you'll return to the main form and see the new phone book entry in the phone list.
You can dial a name displayed on the phone list by highlighting the name (click once) and then clicking on the Call
button.
The same code you used to build this form can be used to add dialing capabilities to all your VB applications.
Summary
In this chapter you learned how to use the Assisted TAPI function (tapiRequestMakeCall) to build outbound voice
phone solutions in both Excel and Visual Basic 4.0.
A key point to remember is that the tapiRequestMakeCall function provides Windows applications with access to
outbound dialing only.
You now have tools that allow you to add phone dialing to any VBA-compatible application. And you have a complete
online phone book that will log all your calls into a Microsoft JET database that can be read, analyzed, and reported by
other Windows programs.
This chapter covered the most rudimentary TAPI functions. In the next chapter, you'll learn how to write programs that
use the API functions that belong to the Basic TAPI set.
Chapter 30
CONTENTS
● Testing TAPIFONE
● Summary
In this chapter, you'll put together all the things you learned about TAPI in a single application. The TAPIFONE
application is a complete single-line telephone device that runs on a Windows workstation. With a voice-data modem
and sound card and this program up and running on your machine, you can completely eliminate the telephone handset
from your desk.
In this program you'll add code for handling both inbound and outbound calls. You'll also give users access to the
various TAPI dialog boxes and will maintain a set of configuration values for TAPIFONE in the Windows registry.
You'll be able to write (and store) call notes on each outbound call made through TAPIFONE.
You'll use Data Access objects to maintain a simple phone book database and you'll also be able to keep track of
outbound calls in a call log. This log can be exported to a comma-separated value (CSV) text file that can be loaded into
Excel or Word.
When you complete this project you should have a full understanding of TAPI services and how you can use them to
build full-featured telephone devices in Visual Basic.
Note
The best hardware configuration for this application is a voice-data modem
and a sound card with external speakers and a microphone. You can also
use this application with a standard modem and an attached telephone
handset, but you'll only be able to make outbound calls.
The TAPIFONE project has three forms and three code modules. The forms are:
● frmTAPI-This is the main form. It uses a tab layout with five tabs for dialing, a phone book, reviewing the call
log, selecting a line device, and updating setup values.
● frmCall-This is a small dialog box that appears each time the user attempts to place a call. Users will be able
to add call notes from this screen or, if needed, cancel the outbound call completely.
● frmVPhone-This is a simple About dialog box that lists the name of the application and its version number.
● libTAPI-This BAS library contains a handful of support and helper routines used by the TAPIFONE project.
These routines are called by one or more of the forms in the preceding list.
● TAPILine-This is the standard BAS module that contains all the structures, constants, and API routines
needed to support the TAPILINE control.
● TAPICall-This is a standard BAS module that contains helper routines from the TAPILine module.
The TAPILine and TAPICall modules need not be keyed in since they are included on the CD-ROM that comes with
this book. These are stock structures, constants, and API declares for the TAPILINE control covered earlier in this book
(see Chapter 26, "TAPI Tools-Using the TAPILINE Control"). The libTAPI module will be built for this chapter.
Tip
If you do not want to enter all the code, you can locate and load the
TAPIFONE project on the CD-ROM that ships with this book.
Along with the standard dial keypad, the TAPIFONE application has a Phone Book page that lists the person's name and
phone number along with the last day and time that person was called using TAPIFONE. Users can select a name from
this list and press the Dial button directly from the phonebook. As each call is placed, users will be asked to add call
notes. These call notes are written to a log that can be viewed online or exported to a comma-delimited text file for
further manipulation.
Users can set several TAPIFONE parameters to control the behavior of the application. These control values are stored
in the Windows registry and recalled each time the program is run. Values are stored in the HKEY_CURRENT_USER
\Software\Visual Basic and VBA Program Settings\TAPIFONE branch of the registry tree. Key
values stored there are:
● MinimizeOnStart-When this is set to "1" the TAPIFONE application loads and immediately minimizes
itself. Users can click on the application from the start bar to place calls. When a call comes in, the application
automatically pops up on the user's workstation. The default is "0."
● Monitor-When this is set to "1" the application automatically opens the selected line device and waits for
incoming calls. The default is "0."
● OutLog-When this is set to "1" all outbound calls are sent to the log table. The default is "1."
● OrigNumber-This contains the telephone number from which calls are originating. The default is blank.
● StartPage-When this is set to "1" the Phone Book page appears upon startup. When this is set to "0" the Dial
Pad page appears. The default is "0."
● Selected-This is set to the line device number that is used for all in- and outbound voice calls. The default
value is "-1."
The libTAPI Module
The first step in building the TAPIFONE project is to start Visual Basic and create a new project. Load the TAPILINE.
BAS and TAPICALL.BAS modules from a previous project (See Chapters 26 and 28 for more on these modules) by
selecting File | Add File from the main menu. Once you've added these two BAS modules, save the project as
TAPIFONE.VBP.
Next you need to add a new module to the project (Insert | Module). Set its Name property to LibTAPIWrapper
and save it as LIBTAPI.BAS.
First, add some declaration code to the module. You need to declare a handful of public variables to handle flags and
global values for the project. You'll also add two private (module-level) variables used to handle TAPI device
information. Finally, you'll add a user-defined type to make it easy to place outbound calls. Open the declaration section
of the form and enter the code shown in Listing 30.1.
'
Private udtLineDevCaps() As LINEDEVCAPS
Private cLineDevCapsExtra() As String * 2048
'
Public gDialString As String ' phone number to dial
Public gPlaceCall As Boolean ' ok to place call
Public gName As String ' name to call
Public gLineDev As Integer ' selected line device
'
Public gMinimize As Integer ' minimize at start
Public gMonitor As Integer ' monitor at startup
Public gOutLog As Integer ' log all outbound calls
Public gOrigNumber As String ' for calledID
Public gStartPage As Integer ' for default start page
'
Type DialParams
DeviceNumber As Integer
DialableString As String
Privilege As Long
MediaMode As Long
End Type
Next, add the routine to handle the initialization of TAPI services for the application. This routine will actually perform
three things:
Add a new function called initTAPI to your project and enter the code shown in Listing 30.2.
Listing 30.2. Adding the initTAPI function.
You'll notice that there are two parameters for the function. The first is the TAPILINE control used on the main form.
The second is the application name when initializing TAPI services. You'll also notice that this routine loads the
capabilities of each device and stores that information in a private array. This array can be accessed using another
function to be defined later in this section.
Note
You might be thinking this kind of code should be placed in a Visual Basic
class module. And you'd be right-except for one little thing. Since the
TAPILINE control must be placed on a form, we cannot use a class module
to handle properties and methods on the control. Forms and controls cannot
be encapsulated in class modules.
Next, you need to add the routine used to read the device capabilities stored in the udtLineDevCaps array. Add a new
function to the project called ReadLineDevCaps and add the code shown in Listing 30.3.
Next add the routine that will be called to place an outbound call. Add a new function called TAPIDial and enter the
code from Listing 30.4.
You'll notice that this routine uses the DialParams user-defined type. It also calls another custom routine-
SetCallParams. You'll define this next. Add a new subroutine called SetCallParams to the project and enter the
code shown in Listing 30.5.
This routine sets an important TAPI structure. This structure contains information about the type of call that will be
requested. Notice that the gOrigNumber variable is used to fill in the originating address. This will tell those who have
caller ID who you are when they receive your calls.
There is one more high-level routine used when placing a call. This is the routine that will fill the DialParams
structure with the actual number to dial and the type of call to make. Add a new subroutine called TAPIPlaceCall to
the project and enter the code from Listing 30.6.
Only three support routines remain to be added. All three deal with the closing down of TAPI services. One,
TAPIHangUp is used to gracefully end an active call. The TAPIClearHandles routine cleans up control properties
after a call is completed. Finally, the TAPIShutdown routine closes down all TAPI services for the application. Add
the three routines shown in Listing 30.7 to your project.
Those are all the routines for the LIBTAPI.BAS module. Be sure to save the module and the project (TAPIFOINE.
VBP) before you continue.
The main form for the TAPIFONE project is the frmTAPI form. This form uses a tabbed dialog box to present five
different services from one form:
● The Dial Pad page is used to place a call by pressing keys directly, as when using a physical telephone handset.
● The Phone Book page presents an online telephone book that users can update and edit. Users can also place
outbound calls by selecting a name in the list and pressing the Dial button.
● The Call Log page shows a list of all calls made using TAPIFONE. Users can view the log, export it to a text
file, or clear it.
● The Lines page shows all the TAPI devices recognized for this workstation. Users can select the line device that
will be used for all TAPIFONE services.
● The Setup page contains a list of all the user configuration values stored in the Windows Registry.
The form also contains a menu that allows users to view information about the application (Help | About), exit the
program (File | Exit), and access TAPI configuration dialog boxes (Configure...).
The next few sections show you how to lay out the frmTAPI form. Since a tabbed dialog box is used, you are actually
building five different dialog boxes in one. Refer to the figures in each section while adding the controls shown in the
accompanying tables. Be sure to add the tab control first and to draw all the other controls on the tab control workspace.
This will ensure that the other controls are child controls of the tab control.
Before building the details of each tab, you need to add the tab control itself along with a few command buttons. Refer to
Table 30.1 and Figure 30.1 when adding these controls.
After you've added the base controls, save the form as FRMTAPI.FRM before continuing.
The first page of the dialog box is the Dial Pad. This provides the user with a keypad similar to that found on desktop
phones. The user can also type the phone number directly into the text box, perform a redial on the last number dialed,
clear the text box, or use its contents as the start of a new address listing.
Refer to Figure 30.2 and Table 30.2 when laying out this page.
Warning
Be sure to place the Frame control on the dialog page first. Also, you'll
want to add the small command buttons using the control array feature of
Visual Basic 4.0.
The next tab in the form is the Phone Book tab. This tab is a data entry form for the phone book. Users can also press the
Dial button to place a call from this form. Use Figure 30.3 and Table 30.3 to place the controls on the form.
Figure 30.3 : Adding the Phone Book controls
After you've built this page, be sure to save the project before continuing.
The Call Log tab shows the user the list of all calls logged through the TAPIFONE application. Refer to Figure 30.4 and
Table 30.4 when you build this page.
The Line tab allows user to select which line is active for the project. Refer to Figure 30.5 and Table 30.5 when building
this page.
Before building the last tab for this form, save the form and project.
The Setup tab contains a small group of controls that are used to update registry settings. Refer to Figure 30.6 and Table
30.6 to build this last page of the form.
This is the last of the dialog pages for the main form. Save the form as FRMTAPI.FRM and the project as TAPIFONE.
VBP.
The main form also has a set of menu options. These options allow the user to call up TAPI-generated dialog boxes to
control communication between TAPI and your programs. Refer to Figure 30.7 and Table 30.7 when building the menu.
There are several code routines that need to be added to the frmTAPI form. These routines fall into four main groups:
● Form-level routines-These routines execute when the form is loaded, and exit and act as support for the general
form events.
● Menu-level routines-These routines execute when a user selects a menu item.
● Page-level routines-These routines execute based on user actions on one of the five pages of the Tab dialog
box.
● The Support routines-These routines are called by one or more other routines on the form.
The first form-level code is the variable declaration code. Add the code shown in Listing 30.8 to the declaration section
of the form.
Option Explicit
'
Dim lAdd As Boolean ' phonebook adding flag
Dim lMonitor As Boolean ' call monitoring flag
Dim lDevErr As Boolean ' line device select flag
'
Dim iWidth As Integer ' remember my size
Dim iHeight As Integer ' remember
'
Dim uLineDevCaps As LINEDEVCAPS ' line devices
Dim cLineDevCapsExtra As String ' extra data strings
Next, add the code for the Form_Load event. This code calls several support routines you'll add later. Enter the code
from Listing 30.9 into the Form_Load event of the form.
Listing 30.10 shows the code for the Form_Resize and Form_Unload events. Add this to your project.
Listing 30.11 shows the code that belongs in the SSTab1_Click event. This is fired each time the user clicks to
change a tab. This code will continue to remind the user to select a line device, if needed.
The last bit of form-level code is the code for the Tapiline1_TapiCallBack event. This code is the same code you
used in the TAPIANS project of Chapter 28. Add the code shown in Listing 30.12 to the TAPICallBack event of the
TAPILINE control.
That's it for the form-level code. Save the form and project before continuing.
There are three main menu branches. The first is the File menu. It has only one submenu-Exit. Listing 30.13 shows
the code you need to add to the mnuFileExit_Click event.
The next branch in the menu tree is the Configure branch. This was built as a menu array with three elements. Listing
30.14 shows the code that should be added to the mnuConfItem_Click event.
The final menu routine is the one that handles the Help menu branch. This branch also contains a menu array for
displaying the TAPILINE control About box and a custom About box for the application (you'll build it later). Add the
code in Listing 30.15 to the mnuHelpAbout_Click event.
That's the end of the menu-level routines. Save the form and project before continuing on to the page-level routines.
There are ten page-level routines in the frmTAPI form. Most of these routines are for handling selections of button
array commands. Listing 30.16 shows the code for the cmdButtons_Click event. This handles the button presses on
the bottom of the form. Add this code to your project.
Next you need to add code to handle the command buttons on the Dial Pad itself. Enter the code from Listing 30.17 into
the cmdDialPad_Click event.
This same page has the array of command buttons that simulate the telephone handset keypad. Listing 30.18 shows the
code that should be copied into the cmdKey_Click event.
The Call Log page has one event routine-the cmdLog_Click event. Add the code shown in Listing 30.21 to your
project.
The Line page has a single event, too. Add the code from Listing 30.22 to the cmdLines_Click event of the form.
The final page-level routine is the one that handles the Startup page. The code in Listing 30.23 shows what you should
add to the cmdStartUp_Click event.
That is the end of the page-level routines for the frmTAPI form. Save the form and project before continuing.
There are eight support routines for the frmTAPI form. Half of these routines are called from the Form_Load event as
initialization routines. The rest are called at various times throughout the project.
First, add a new subroutine called LoadImages to the form. Then add the code shown in Listing 30.24 to the project.
Next, add the LoadDevices subroutine. This is the code that loads the device names into the list box on the Lines
page. Enter the code from Listing 30.25.
Listing 30.25. Adding the LoadDevices routine.
Next add a new subroutine called ReadStartUp to the project and enter the code from Listing 30.26. This code loads
the registry values at program startup.
The next two routines deal with loading or, if needed, creating the TAPI.MDB database file. First, create a subroutine
called InitDB and enter the code from Listing 30.27.
The next two routines are called from the Call Log page of the form. Listing 30.29 shows the ClearLog routine. Add
this to your project.
Listing 30.29. Adding the ClearLog routine.
Listing 30.30 shows the ExportLog routine. This is used to create a text file version of the call log. Add this code to
your project.
The last support routine for the frmTAPI form is the LineMonitor function. This routine toggles on or off the TAPI
call monitoring service. When monitoring is on, TAPIFONE answers the incoming call. Add the code in Listing 30.31 to
your form.
That is all the coding needed for the frmTAPI form. Save this form and update the project before you add the Call and
About dialog boxes.
The Call and About dialog boxes are two small forms that are used to support actions from the frmTAPI form. The Call
dialog box appears just before an outbound call is placed. This allows the user to confirm the call and add any call notes
if desired.
The About dialog box shows the name of the program, the current version, and other important information about the
TAPIFONE application.
Add a new form to your project and build the Call dialog box shown in Figure 30.8. You can also refer to Table 30.8
when laying out the frmCall form.
After you finish laying out the frmCall form, you need to add three code routines. The first is the Form_Load event
code. Listing 30.32 shows the code you need to add to the frmCall form.
Next you need to add the code shown in Listing 30.33 to the cmdCall_Click event of the form.
Finally, you need to add some code to support the cmdCall_Click event. Add a new subroutine called UpdateDB to
the form and enter the code shown in Listing 30.34.
That is the end of the frmCall form. Save this form and update the project before you add the About dialog box.
The About dialog box is displayed when the user selects Help | About TAPIFONE. Add a new form to the project.
Refer to Figure 30.9 and Table 30.9 when laying out the new form.
There are only two code routines needed for this form. Listing 30.35 shows the Form_Load event code. Add this to the
form.
Listing 30.35. Adding the Form_Load event code.
Listing 30.36 shows the line of code to add the Command1_Click event. This exits the About dialog box.
That's the end of the coding for the TAPIFONE project. Now save the form and the project. You are ready to test the
application.
Testing TAPIFONE
When you first run TAPIFONE you'll be prompted to select a line device. This device will be used for all inbound and
outbound calls (see Figure 30.10).
You can place a call using the keypad on the Dial Pad page (see Figure 30.12) or by selecting an entry in the phone
book.
In either case, when you press the Dial button, the Call dialog box appears asking you to confirm your call and enter
any call notes (see Figure 30.13).
Once your call is completed you can view the results on the log page and even export the log to a text file (see Figure
30.14).
Summary
In this chapter you applied all the information you have learned in the TAPI section to create a complete phone in
software. You can use this device on any workstation that has TAPI services installed and has at least a voice-data
modem. If the workstation has a speaker phone, you do not even need a telephone handset on your desk.
This chapter showed you what you can build in Visual Basic using the TAPILINE control. In the next chapter, you'll
learn about three other TAPI tools that you can use to create TAPI-enabled applications. Two of the products are TAPI
development tools. The third product is Microsoft Phone-Microsoft's e-mail-aware, telephone answering machine that
can read text messages to you over the telephone.
Chapter 31
CONTENTS
● Microsoft Phone
❍ Adding Announcement, Message, and AutoFax Mailboxes
● Summary
In the previous chapters you learned how the TAPI system works and how you can use Visual Basic and Microsoft C++
to build your own TAPI-compliant applications. In this chapter you'll learn how to use two of the most popular third-
party development tools for TAPI services. Each of these tools has a unique approach to building TAPI-enabled
applications. At the end of the chapter, you'll also get a look at Microsoft Phone, the new application from Microsoft that
combines MAPI, SAPI, and TAPI services.
The first TAPI tool you'll review is the Visual Voice Telephony Toolkit for Windows from Stylus Innovation, Inc. This
system includes a handful of OCX controls that you place on your Visual Basic forms to gain access to TAPI services.
By setting control properties, invoking methods, and responding to defined events, you can build solid multiline
telephony applications in a very short time.
Note
The Visual Voice system comes in several versions for both 16- and 32-bit
Windows operating systems. Visual Voice also sells versions for TAPI- and
MWAVE-based systems. The version covered here is the 32-bit TAPI
system. It runs fine on both 16- and 32-bit Windows.
The second TAPI development tool you'll test is the VBVoice system from Pronexus. The VBVoice approach to TAPI
design is quite different. Instead of placing controls on your form and using them to gain access to TAPI services, the
VBVoice system allows you to visually build your telephony application by linking the controls in a flow diagram.
Using this method, you can build complete telephony applications without writing a single line of Visual Basic code.
Finally, you'll get a peek at Microsoft Phone-the new application from Microsoft that combines MAPI, SAPI, and TAPI
services into a single communication application. You'll learn how to install and configure Microsoft Phone and see how
you can combine telephony, e-mail, and speech into a single system.
The Visual Voice Telephony Toolkit is one of the most popular TAPI systems for Visual Basic development. The kit
contains eight basic Visual Voice components. Three of these components are controls:
● The Voice control-This is the main TAPI control. It gives you access to the standard TAPI events and
methods.
● The Test control-This control is used to simulate a live telephone line. With this control and a sound card, you
can fully test your application without having to use live telephone lines.
● The Vlink control-This control is used to create conversation links between separate Visual Basic applications
on the same machine. This is similar to a Visual Basic OLE server.
There are also five valuable support applications you can use to build and manage your TAPI applications:
● The Voice Workbench-This is a tool for creating and managing Visual Voice objects, including voice files,
voice strings, voice queries, and code templates.
● The Voice Monitor-This is a Visual Basic application that can be used to monitor line activity on a multiline
TAPI workstation. You'll use this to keep track of telephony activity on your TAPI pc.
● The Virtual Phone-This is a Visual Basic application that allows you to completely test your TAPI system
without having to connect to and use live telephone lines. This application was built using the Test control
mentioned above.
● The TAPI Examiner-This is a Visual Basic program that allows you to test individual TAPI services. It is a
helpful debugging tool.
● Stylus Trace-This is a stand-alone program that you can use to trace all Visual Voice activity on your
workstation. It is excellent for debugging and checking out your Visual Voice installation.
Note
You'll find a demonstration copy of the Visual Voice Toolkit on the CD-
ROM that ships with this book. You can read this section of this chapter
without installing the Visual Voice software, but you'll get more out of the
material if you have Visual Voice up and running.
There are three Visual Voice controls. The Voice control is the main TAPI interface control. You'll use this control to
gain access to TAPI services on your workstation. The Test control can be used to perform tests on your applications
without the need for a live telephone line. The VLink control can be used to pass information between running Visual
Basic Visual Voice applications.
The following sections review the controls and their methods, properties, and events more closely.
Note
The reviews of the methods, properties, and events of the Visual Voice
components covers the OCX controls only. The Visual Voice Toolkit for
Win32 also contains DLLs for use with C++ applications (or other
programming languages that use DLLs). The descriptions of methods,
properties, and events described for the Visual Basic controls are the same
for the API calls in the DLL support files.
The Voice control has 33 methods that you can invoke. Most of them correspond to methods found in the TAPILINE
control and specific API functions defined within TAPI. For example, there is an AllocateLine method to open a
line for voice output. There are also DeallocateLine and Stop methods for closing a line and shutting down TAPI
services (see Figure 31.1).
You can use the Voice control to place an outbound call by setting the Value property to "555-1212" and then
invoking the Call method. When a call comes in to the line, the RingDetected event fires and you can use the
PickUp method to answer the call.
The Voice control has several other unique, and very useful, methods. For example, the Voice control contains
several Play... methods that can be used to play back numbers, dates, words, phrases, even complete sound files.
There are also properties that identify the caller, including CallerID, ANI (Automatic Number Identification), and
others.
Tip
There is also a Visual Basic BAS module called VOICETP.BAS. This
module contains constant definitions and sample and helper functions and
subroutines. This additional code is not required when using the Voice
control, but it does come in quite handy. When you add the Voice control
to your project, you should add the VOICETP.BAS module, too.
Table 31.1 lists all of the TAPI-related methods along with short descriptions of their use.
The Voice control sports 46 TAPI-related properties. Many of these properties are read-only and available only at run-
time. Table 31.2 shows the list of properties for the Voice control.
Finally, there are eight TAPI-related events for the Voice control. Table 31.3 shows the events, along with descriptions
of their use.
The Test control simulates the existence of a live telephone line for your Visual Voice applications. When you are
building and testing telephony applications, one of the hardest parts of the process is testing the application's response to
inbound calls. The Test control solves this problem. Now, as you build your TAPI applications, you can test them
without having to actually link your workstation to a live telephone line (see Figure 31.2).
The control works by communicating directly with the Voice control. Whenever the Voice control invokes a Call
method, the Test control's VoiceCallInitiated event occurs. This simulates the receipt of an outbound call
initiated by your application. You can also use the VoiceConnect method of the Test control to simulate the
appearance of an inbound call for your TAPI application.
Tip
When you load the Test control into your Visual Basic projects, load the
VVTEST.BAS module, too. This contains predefined constants that you can
use to make it easier to program with the Test control.
Table 31.4 shows the four methods of the Test control along with short descriptions of their use.
Note
There are five other methods listed in the Test control documentation, but
they have no effect in the current version of Visual Voice for TAPI. These
methods are FaxConnect, HangUpHandset, PickUpHandset,
SendCallProgress, and SendHandsetDigits. These methods
appear for backward compatibility with earlier versions of Visual Voice.
There are nine valid properties of the Test control. These properties are listed in Table 31.5 along with descriptions of
their use.
Note
There are three properties of the Test control that are no longer valid:
FaxPageTotal, TestFaxFile, and TestStationID. These are
included for backward compatibility with other versions of Visual Voice,
but have no effect in this version of the product.
It is important to comment on the CalledID and CallerID properties-don't confuse them. The CalledID value
indicates the telephone number that the inbound caller dialed to reach you. In some PBX systems, this information is
used to sort incoming calls for customized greetings (for example, calls coming in on line 1 might be answered
"Welcome to Customer Service" and calls coming in on line 2 might be answered "Administration. May I help you?").
The CallerID value indicates the telephone number of the person who is placing the call. This can be used to
automatically associate a name, address, or other account information with the calling party.
The Test control has three events. Table 31.6 lists these events along with a description.
Note
There is also a FaxSendInitiated event that is no longer operable, but
that is included for backward compatibility.
The Vlink control is used to send data between two Visual Basic programs running on the same workstation. This
control was developed before Visual Basic 4 offered easy OLE server construction. Although you could accomplish the
same tasks using OLE servers, the Vlink control is geared toward supporting Visual Voice services and should be used
whenever you need to share telephony data between Visual Voice TAPI applications (see Figure 31.3).
Tip
When you use the Vlink control, add the VLINK.BAS and VLVOICE.
BAS modules to your project. These modules contain useful constants and
helper functions that you can use to manipulate Vlink control events and
data.
The Vlink control has only two methods. Table 31.7 lists and describes the methods.
The Vlink control has 14 TAPI-related properties. Table 31.8 lists these properties and describes them.
There are three events defined for the Vlink control. Table 31.9 shows the three events and their meaning.
The Voice Workbench is an essential tool for building Visual Voice TAPI applications. The Workbench allows you to
create and maintain four key Visual Voice objects:
● Voice files
● Voice strings
● Voice queries
● Subroutine templates
Voice files are the actual WAV format recordings of human speech. The Voice Workbench allows you to record and
catalog voice files for later reference in your Visual Basic project. Typical voice file objects would be "Welcome to
Visual Voice," or "Press one to continue," and so on. Figure 31.4 shows the Voice Workbench while reviewing a set of
voice files.
Voice strings are a collection of one or more voice files plus literal or variable values. The Workbench lets you link
several prerecorded phrases along with fixed or variable data retrieved from a database or computed by your Visual
Basic program. For example, you might create a voice string object that returns the total number of messages in your
voice-mail inbox:
The phrase above links two voice files (possibly named ThereAre.WAV and NewMsgs.WAV) with a pointer to a field
(MsgCount) in a database table (MsgTable). This is one of the most powerful aspects of the Workbench. You can
very easily link your voice output to variables in your program or to data stored in a shared database. Figure 31.5 shows
the Workbench building a voice string using a database link.
You can also build and maintain voice query objects with the Voice Workbench. Voice queries are just like voice strings
except that they ask the user for a predetermined response in the form of keypad selections. You can build your queries
to expect a set amount of input before continuing with your TAPI application. Figure 31.6 shows the Workbench editing
an existing voice query.
Lastly, the Workbench also lets you create and store your own subroutine templates. These templates can link voice
strings, voice queries, and pure Visual Basic code into a single unit. Once the template is completed, you can ask the
Workbench to generate Visual Basic code for you to place into your application. In this way you can build complex
telephony logic without spending a great deal of time writing Visual Basic code. Figure 31.7 shows an example of
Workbench-generated code using a Visual Voice template object.
This is very useful when you have a multiline telephony card in your TAPI workstation and are running one or more
Visual Basic applications on each line.
The Virtual Phone is a very handy testing tool. This tool simulates the appearance of a live telephone line for your Visual
Voice applications (see Figure 31.9).
The program is actually written in Visual Basic using Visual Voice's Test control, and the source code is included in
the complete toolkit. The program works by talking directly to the Voice control rather than to a telephone line. For this
reason, you cannot use the Virtual Phone for any other TAPI applications that do not use the Voice control.
You can set values for the CallerID and CalledID in the Virtual Phone and have these values passed into your
Visual Basic application that uses the Voice control. In this way you can even test applications that require advanced
telephony service features without having to use a live telephone line.
The TAPI Examiner is an excellent tool for testing your hardware's compatibility with TAPI services. The examiner runs
through all the major service requests, including selecting and opening lines, placing outbound calls, receiving inbound
calls, even testing advanced features such as transfer, park, and hold (see Figure 31.10).
You also get the ability to log your test results to a text file for later reference. This is a very handy tool for testing new
workstations and is a useful general diagnostic tool as well.
The Stylus Trace application provides an easy way to log and trace all activity involving Stylus controls. Listing 31.1
shows a sample of trace output.
Executing VV_VoiceInit
VVE ?: TAPI initialized 6 lines, 6 phones
VV_VoiceInit returned 0x00000000
VVE ?: 1 line license detected
Using profile C:\WINDOWS\VVOICETP.INI
Executing VV_SetSystemFileExtension(wav)
VV_SetSystemFileExtension returned 1
Executing VV_SetBeepFileName(C:\VVOICETP\TESTSNDS\BEEP.WAV, 0)
VV_SetBeepFileName returned 1
Executing VV_AllocateLine(PhoneLine = 1 Rings = 1)
VVE ?: Mapping VV PhoneLine 1 to TAPI device 5 [Windows Telephony
Service Provider for Universal Modem Driver - TeleCommander 2500
Voice]
VVE ?: line 5 - total appearances = 1
VVE 5: appearance 0: [´]
VVE ?: lineOpen (OWNER, AUTOMATEDVOICE)
VVE ?: Allocated line ed0084. Line handle = 372f1e3e
VV_AllocateLine returned 0
Allocated line 1. 1 appearances
Note
Since this product works by monitoring activity within the Stylus
components, not within the Windows Telephony API, you cannot use the
Stylus Trace to monitor non-Visual Voice telephony programs.
The Pronexus VBVoice telephony development system is quite different from the Visual Voice toolkit. Each provides
the same basic services, but in a very different way. While the Visual Voice toolkit emphasizes the use of custom
controls linked together with Visual Basic code, the VBVoice system emphasizes the use of visual programming instead
of Visual Basic code. In fact, you can build an entire VBVoice application without writing any Visual Basic code at all.
The VBVoice system is made up of 20 different OCX controls. These 20 controls represent all the major actions that can
occur during a telephony session. You place these controls on your Visual Basic form and set their properties at design
time. You then link them together in a call-flow diagram that visually defines your program.
After completing your call-flow diagram and setting the desired properties, you can test your VBVoice application by
starting the application in TestMode. TestMode is similar to the Visual Voice Virtual Phone. VBVoice TestMode
simulates call activity without the use of a live telephone line.
In the next few sections, you'll learn how the VBVoice controls work and how they can be used to develop Windows
telephony programs.
Note
There is a demonstration version of the VBVoice development system on
the CD-ROM that accompanies this book. You do not need a telephony card
to use the demonstration version of the system. However, you do need a
sound card to run the system in TestMode.
Every VBVoice project contains at least one VBFrame control. This control is the "host" control for all other VBVoice
controls in the project. The VBFrame acts as a visual workspace for your telephony project. Figure 31.11 shows a
VBFrame control hosting several other VBVoice controls.
Figure 31.11 : Using VBFrame to host other VBVoice controls
After you have placed a VBFrame control on a form, you are ready to add other VBVoice controls. Each VBVoice
control handles a single typical event in a telephony application (playing a greeting, collecting digits, transferring a call,
and so on). Along with the VBFrame control, there are another 19 VBVoice controls that you can use to build your
telephony applications. Table 31.10 lists and describes the 19 VBVoice controls.
As mentioned earlier, you build VBVoice telephony applications by placing the various VBVoice controls into a
VBFrame and then linking them together in a call diagram. Once they are linked together, you can set the properties of
each control to alter the behavior of the system. Figure 31.12 shows the process of setting properties for the
GetDigits control.
VBVoice allows you to build voice files using its own library of phrases or by recording your own phrases. You access
the voice files through the VBVoice controls that use voice files. For example, you can call up the properties page of the
PlayGreeting control, select Properties from the context menu (alternate mouse button), and then select the
Greetings tab to gain access to the recorded voice library (see Figure 13.13).
VBVoice allows you to record, playback, and store messages in a variety of file formats including the Windows standard
WAV format.
Once you have built your VBVoice project, you can test it by selecting the Start Test option from the context menu
(alternate mouse button) of the control. All VBVoice controls can be started in this way. This makes it very easy to
debug a telephony application. You can start your testing from anywhere in the call flow.
Once you start your application, you'll see a small dialog box appear asking you to start the test mode. While test mode is
running, you'll also see a log window of all activity in the VBVoice application (see Figure 31.14).
You can view the trace logs onscreen, send them to a printer, or save them to a disk file. Listing 31.2 shows sample
output from a VBVoice log.
Even though you can build complete telephony applications using only the VBFrame control and the visual call
diagrams, you can also use the controls to build programs using traditional Visual Basic coding techniques. Each control
has its own series of events, methods, and properties that can be accessed at run-time. You can read and set properties,
invoke methods, and add code to the events just as you do with any other Visual Basic controls.
Microsoft Phone
Microsoft Phone is the first application released by Microsoft that combines the Messaging, Telephony, and Speech
APIs into a single program (see Figure 31.15).
While Microsoft Phone is a very good pc phone and answering machine, it offers several other valuable features. First,
you can use Microsoft Phone to designate several fax mailboxes. These mailboxes will send a selected fax back to the
caller based on a telephone number left by the caller. You can also set up multiple message boxes that the caller can use
to leave messages. Finally, you can build announcement-only mailboxes that can give the caller valuable information
such as office hours, delivery directions, and so on.
The best part of all this is that you can link these three types of mailbox objects into a set of menu-driven options that
provide callers with a full-featured auto-attendant system. All without writing a single line of code!
Note
As of this writing, Microsoft Phone is not available as a stand-alone
product. Microsoft Phone is to be included as the default telephony
application for several voice-data modem products sold in stores. You
cannot get a copy of Microsoft Phone unless you buy the voice-data modem
hardware. The list of hardware vendors committed to shipping Microsoft
Phone is constantly changing. To get the latest information, check the TAPI
vendors list in appendix C, "TAPI Resources." You can also check the
Communications Developer's Guide Web site for more information on
MAPI, SAPI, and TAPI tools.
Microsoft Phone can answer your telephone for you, record incoming messages, and send these recorded messages to
your Microsoft Exchange inbox for your review at another time. You can call into Microsoft Phone from a remote
location and have the program play your e-mail messages back to you. You can even ask Microsoft Phone to "read" your
text e-mail to you over the phone!
You can also use Microsoft Phone to place outbound calls. You can dial calls directly using the Microsoft Phone keypad,
you can use one of the speed dial buttons, or you can call up the Microsoft Exchange address book and select a user's
phone number from the address book. Microsoft Phone even checks incoming caller ID information against your
Exchange address book to see if you already have that person on file.
When you first install Microsoft Phone, you can configure it to record messages and place them in your Exchange
mailbox. This is done using the Greeting Wizard (see Figure 31.16).
Microsoft Phone allows users to add several different mailboxes to handle incoming calls. Microsoft Phone recognizes
three types of mailboxes:
You can set up your Microsoft Phone application to accept an incoming call, play a general message, and then allow the
user to navigate between announcement, message, and fax mailboxes using his telephone keypad (see Figure 31.17).
Each message-type mailbox can be associated with a Microsoft Exchange mailbox. All messages sent to this Microsoft
Phone mailbox will be delivered to the Microsoft Exchange box, too.
Each announcement-type mailbox can give the caller a complete message (for example, directions to the retail location,
office hours, and so on) or can give the caller instructions on how to select a message box or receive a fax document.
Each AutoFax-type mailbox can send one (and only one) selected document to the user. The AutoFax box will ask the
user for a telephone number as a destination address for the document and then send the document when the line is free.
Configuring the Microsoft Phone Answering Machine
Once you install all the mailboxes, greetings, and fax documents you need, you can configure the answering machine
parameters (see Figure 31.18).
● General-Use this page to control how Microsoft Phone handles incoming calls, including the use of caller ID
and your Exchange address book, screening calls, and the use of Microsoft Voice to place speed-dial calls.
● View-Use this page to control how Microsoft Phone looks to the pc user. You can control the appearance of the
toolbar, speed-dial buttons, and other onscreen displays.
● Answer-Use this page to control how Microsoft Phone behaves as an answering device. You can set the number
of rings before Microsoft Phone picks up the call.
● Message-Use this page to control how long the recorded message will be and which Microsoft Exchange
Profile is used to copy the messages from Microsoft Phone to Microsoft Exchange.
● Remote-Use this page to control how Microsoft Phone behaves when you call from a remote location to retrieve
messages. You can direct Microsoft Phone to give either detailed or summary information about each message
that is stored. You can also control which Microsoft Voice computer voice is used to read your text messages.
Summary
In this chapter you got a chance to review three tools that can help you develop end-user TAPI applications:
● Visual Voice-This is a set of OCX controls and support applications for creating telephony applications by
using Visual Basic or C++ to place the Voice control on a form and then write Visual Basic code to set and
get properties, invoke methods, and respond to TAPI events.
● VBVoice-This is a set of OCX controls that are used to create Visual Basic telephony applications without the
use of Visual Basic code. You place a VBFrame control on a Visual Basic form, then place one of the other
controls on the VBFrame. By setting control properties and linking the controls to each other, you can build a
complete telephony application without writing Visual Basic code.
● Microsoft Phone-This is an end-user program that combines MAPI, SAPI, and TAPI services into a single
virtual phone application. You can create message, announcement, and fax mailboxes and record menus and
other greetings that instruct callers on how to leave messages and receive fax documents. Saved messages are
placed in your Microsoft Exchange inbox and you can call Microsoft Phone from a remote location and direct it
to use SAPI services to read your text e-mail messages over the phone.
Chapter 32
CONTENTS
In the first chapter of this section you learned the general outline of the TAPI model including the use and meaning of
two devices defined within the TAPI model:
You also discovered the relative advantages and disadvantages of the four main types of TAPI hardware configurations:
Finally, you learned how to identify the four main types of telephone service used to transmit voice and data signals:
● Analog POTS (Plain Old Telephone Service)-for general voice-grade transmissions and for data transmission up
to 28.8kbps speed
● Digital T1-for dedicated high-speed voice and/or data services (56kbps and above)
● ISDN (Integrated Services Digital Network)-for high-speed multichannel simultaneous voice and data services
● PBX (Private Branch Exchange)-for use within proprietary switchboard hardware in an office setting
You also learned that the TAPI function set defines two different devices to handle telephony services:
● Line devices are used to control the connection between a data source and the physical telephone line.
● Phone devices are used to control the connection between a desktop handset and a line device.
Finally, you reviewed the API calls, data structures, and Windows messages defined for each level of TAPI service.
In this chapter you learned how to build a simple TAPI dialer application in C using the Basic Telephony level of
service. This application was used to highlight the basic operations required to build TAPI applications (in any
language).
You learned how to perform line initialization, locate a usable outbound line, and open the line in preparation for dialing.
You also learned how to place an outbound call and use the TAPI line callback function to monitor call progress. Finally,
you learned how to close safely down a line after the call has been completed.
You learned the following key steps to placing outbound calls using Basic TAPI services:
You also learned how to write a callback function to handle messages sent while the TAPI services are in action.
Chapter 25 showed you the differences between the three types of hardware options and how they rate in offering
support for TAPI services on pc workstations.
● Basic data modems support Assisted Telephony services (outbound dialing) and can support only limited
inbound call handling. Use this type of hardware if you are building simple outbound dialing applications.
● Voice-data modems are capable of supporting the Assisted Telephony and Basic Telephony services, and many
of the Supplemental services. Use this type of hardware if you want to provide both inbound and outbound
services on a single-line phone.
● Telephony cards support all of the Basic Telephony and all of the Supplemental Telephony services, including
phone device control. Most telephony cards also offer multiple lines on a single card. This makes them ideal for
supporting commercial-grade telephony applications.
You also got a quick review of modems and modem drivers. You learned that Win95 and WinNT rely on the UniModem
or UniModemV modem drivers to communicate between the telephony hardware and your program. You also learned
that, no matter what hardware you purchase, you will need a TAPI-compliant TSPI (Telephony Service Provider
Interface) that matches the hardware you purchased. Hardware vendors can recognize the UniModem or UniModemV
drivers, or ship their own TSPI drivers with their hardware.
In this chapter you learned the properties, methods, and events of the TAPILINE control that ships on the CD-ROM that
accompanies this book. You also learned how to use this control to gain access to TAPI services from within Visual
Basic programs.
You also learned how to monitor call progress using TAPI messages and the lineGetCallInfo and
lineGetAddress methods. Finally, you learned how to call the four TAPI dialog boxes to gain access to
configuration dialogs to customize your TAPI interface.
In Chapter 27 you learned that the TAPI system keeps track of vital information in the TELEPHON.INI file on the
workstation. You learned there are four main sections in the TELEPHON.INI file:
● The Service Provider section holds information on all the Telephony service providers (TSPs) installed on the
workstation.
● The HandOff Priorities section holds information about which line devices can support which media modes and
the order in which the line devices should be called.
● The Location section holds information on the dialing locations of the workstation.
● The Credit Card section holds dialing instructions for using telephone service credit cards to control billing.
You learned the different predefined media modes that can be handled by registered TAPI applications in the
[HandOffPriorities] section. You also learned the dialing codes that are used in the [Cards] section to tell
TAPI how to place requested calls.
Finally, you built a small Visual Basic application that allowed you to gain direct access to the various TAPI line dialog
boxes that can affect the TELEPHON.INI file.
In Chapter 28 you learned how to use the TAPILINE control to create a Visual Basic application that can monitor a
telephone line for incoming calls and allow users to answer those calls. In the process, you learned about the six steps
needed to handle inbound calls using TAPI services:
● Initialize TAPI services on the workstation.
● Select and open valid line devices for incoming calls.
● Wait for an inbound call to appear on the line.
● When a call appears, accept the call and begin conversation.
● When the call ends, close the line and prepare for the next call.
● When the session is over, shut down TAPI services on the workstation.
You also learned the importance of the LINE_CALLSTATE message sent by TAPI whenever a new call appears on the
line and when an active call becomes idle (the other party hangs up). You learned how to write code in the
TapiCallBack event of the TAPILINE control to watch for and respond to LINE_CALLSTATE messages.
Finally, you learned the importance of getting the call handle from the LINECALLSTATE_OFFERING message and
placing this value into the HandleToCall property of the TAPILINE control. This must be done before you can
invoke the LineAnswer method to accept the incoming call.
In this chapter you learned how to use the Assisted TAPI function (tapiRequestMakeCall) to build outbound
voice-phone solutions in both Excel and Visual Basic 4.0. You developed tools that allow you to add phone dialing to
any VBA-compatible application. You created a complete online phone book that can log all outbound calls into a
Microsoft JET database that can be read, analyzed, and reported by other Windows programs.
You learned about the two API functions you can use to complete Assisted TAPI calls:
● tapiGetLocationInfo returns the current country and city (area) codes set in the TELEPHON.INI file.
● tapiRequestMakeCall initiates a voice-phone call by passing the dialing address (phone number) and
other optional parameters including the dialing application to use, the name of the person you are calling, and a
comment about the nature of the call.
You learned how to use these API calls to build a simple dialing routine in an Excel worksheet, and you used Visual
Basic 4.0 to create an online telephone book that allows you to edit a phone list and place and track outbound calls.
In Chapter 30 you applied all the information you have learned in the TAPI section to create a complete telephone in
software. This "virtual phone" can be installed on any workstation that has TAPI services installed and at least a voice-
data modem. If the workstation has a speaker phone, you do not even need a telephone handset on your desk.
You created the Visual Basic TapiFone project that used the TAPILINE control and contained code for handling both
inbound and outbound calls. You also gave users access to the various TAPI dialog boxes and allowed them to maintain
a set of configuration values for TAPIFONE in the Windows registry. TapiFone is also able to write and store call
notes on each outbound call made through TapiFone.
Finally, in Chapter 31, you tested two third-party tools for developing TAPI solutions for Windows using Visual Basic
4.0. You learned how the Stylus Visual Voice Controls work with Visual Basic to provide a complete TAPI-compliant
telephony solution.
You learned the Stylus Visual Voice kit contains eight basic Visual Voice components. Three of these components are
controls:
● The Voice control is the main TAPI control. This gives you access to the standard TAPI events and methods.
● The Test control is used to simulate a live telephone line. With this control and a sound card, you can fully test
your application without having to use live telephone lines.
● The VLink control is used to create conversation links between separate Visual Basic applications on the same
machine. This is similar to a Visual Basic OLE server.
There are also five support applications that you can use to build and manage your TAPI applications:
● The Voice Workbench is a tool for creating and managing Visual Voice objects, including voice files, voice
strings, voice queries, and code templates.
● The Voice Monitor is a Visual Basic application that can be used to monitor line activity on a multiline TAPI
workstation. You'll use this to keep track of telephony activity on your TAPI pc.
● The Virtual Phone is a Visual Basic application that allows you to completely test your TAPI system without
having to connect to and use live telephone lines. This application was built using the Test control mentioned
earlier.
● The TAPI Examiner is a Visual Basic program that allows you to test individual TAPI services. This is a helpful
debugging tool.
● Stylus Trace is a stand alone program that you can use to trace all Visual Voice activity on your workstation. It
is excellent for debugging and checking out your Visual Voice installation.
You also learned how to use the VBVoice controls to create interactive telephony applications to handle all sorts of
personal and commercial needs. The VBVoice system is made up of 21 different OCX controls. These controls
represent all the major actions that can occur during a telephony session. You place these controls on your Visual Basic
form and set their properties at design time. You then link them together in a call flow diagram that visually defines your
program.
Finally, you got a quick tour of the Microsoft Phone system. This is a stand alone product that combines MAPI, SAPI,
and TAPI services in a single package. With Microsoft Phone, you can create custom answering messages, have
Microsoft Phone forward your messages to Microsoft Exchange, and have Microsoft Exchange read your text message
back to you using the Speech API (SAPI).
The material covered here is just the tip of the iceberg for Windows telephony services. As the TAPI interface becomes
more common, hardware prices will fall to levels that are affordable to almost anyone who purchases a pc. In the near
future, the integration of e-mail, telephony, and speech services (such as Microsoft Phone) will become the norm instead
of a unique occurrence.
As telephones become easier to program and operate from within pc workstations, it is quite possible that the telephone
handset that adorns almost every desktop today will disappear. In its place will be a single workstation that can answer
and place voice calls, transfer data between locations by way of e-mail and direct links, even read messages aloud to you
both in the office and while you are away.
The next round of innovations will be the ability to switch between voice and data on the same line automatically. Some
hardware is already available to handle such chores. In the not too distant future, users will be able to transfer voice and
data at the same time over a single line-without switching modes at all.
In order to achieve the next level of telephony services, local loop service will need to improve to allow reliable data
transfers at rates above 28.8Kbps. New data compression and correction technologies can help, but only to a point. The
promised installation of high-speed ISDN lines that stretch from the local switch stations all the way to the home will do
the most for improving the quality and capabilities of computerized telephony services in the future.
Chapter 33
CONTENTS
● Introduction
● General Considerations
❍ Rules of Complexity
Introduction
Now that you have had experience with the three Windows communications extensions (MAPI, SAPI, and TAPI), you're
ready to tackle the task of producing programs that integrate these API sets into a single application. But before you leap
into the dark waters of multi-API application development, it's a good idea to take some time to test the depth of your
ideas and review the pros and cons of using one or more of the API sets covered in this book.
As you've learned throughout this book, there are several places where these three API sets provide overlapping services.
This chapter shows you how to determine when each of the three API sets covered in this book should be added to your
program. You'll also learn how to best implement each of the services depending on the Windows operating system and
programming tools available to you.
There are a number of things to consider when building an application that combines multiple services. This chapter will
cover the following main points:
● General considerations-Some general rules of thumb regarding complexity, mixing of API sets, and the
advantages and disadvantages of using native API calls vs. high-level third-party tools. This section wraps up
with a discussion of the pros and cons of building standalone applications vs. creating extensions or add-ins for
existing desktop applications.
● MAPI versus Comm API-This section covers some things to consider when building applications that will
perform file transfers. Depending on the client and destination, it may be wise to use the Comm API to create a
custom file transfer application instead of using MAPI services.
● Assisted TAPI versus Full TAPI-Much of Part IV was spent discussing the possibilities of full TAPI. Is it
always needed? Often Assisted TAPI provides all the capability you need to implement TAPI services for your
applications.
● When to use the Speech API-SAPI services are still quite new. Often you can use recorded WAV files to meet
most of your human speech output needs. However, there are times when SAPI is the best answer. This section
goes through some checklists to help you decide if the Speech API is the best route for your application.
When you finish this chapter, you should have a pretty good idea of how to evaluate your application needs and
recognize how each of the Windows extension services fits into your application's design and implementation plans.
General Considerations
When first beginning the design phase of an application, ideas about what users need and how they will interact with the
system are purposefully vague. The general flow of a software system and the interface needed to support that flow are
only hinted at. Once it comes time to actually build the application, however, things are different. Now all the promises
and hopes of the design group rest on the few brave souls who have the task of implementing the design.
As Windows services become more common and familiar to users, the minimum requirement for new applications is
constantly being increased. A few years ago users were pleased to get friendly screens that beeped at the moment they
entered invalid data instead of waiting until the batch was completed and the error report printed. Now users expect to
see drop-down list boxes, context-sensitive help, and "wizards" and "assistants" that will tell them ahead of time what
data they should be entering! And they often get just that.
It is very common for users to expect e-mail to be an integral part of every application. Now users are learning the
possibilities of linking telephones to their systems, too. And where telephones appear, speech processing cannot be far
behind. The hardest thing to keep in mind is that not all applications need all the extensions. And even the ones that do
need extension services can often get by with less than a full plate of service options.
Adding features and services without taking the time to think about how they will interact can lead to major headaches.
This section covers some general rules to keep in mind when designing and implementing multiservice apps.
Rules of Complexity
The first thing to keep in mind is that each new service you add to your system adds complexity. And this complexity
increases by orders of magnitude. When you decide to add one of these services, think long and hard. And if you still
want (or need) to add the service, prepare for additional time to redesign, code, and test your application. Despite what
anyone tells you, adding e-mail or telephony or speech services to an application is no simple matter.
For example, adding e-mail to a system doesn't just mean you need to add a few menu items to the screens to allow users
to send documents. You now have to deal with users attempting to format documents for printers and for e-mail
messages. Now users will want to send multiple copies of the same message, possibly to someone halfway around the
world. And that person probably uses a different e-mail format, too! While good e-mail service models (like MAPI) will
mask much of this detail, you can still end up with requests for features that push the limits of your system.
Now consider adding telephony or speech services to the same program. While the possible applications of new
technologies are exciting, the possible collisions of APIs can be daunting. For this reason, it is always a good idea to
limit the feature sets you build into an application. While users will often ask for (and get) the latest technological
breakthroughs, they don't often use them to their fullest potential. It is important to keep a clear head about just which
"bells and whistles" are required and which are desired. Each new service extension adds a level of complexity to the
system-for designers, programmers, and users. It is a good idea to do your best to limit the extension sets that are added
to any single application. Usually this can be done without sacrificing features.
Once you decide on just what services you will include in your application you need to decide on how you will deliver
these services. Often the first inclination is to acquire the latest API/COM specification documents and begin coding
function and class libraries to encapsulate the desired services. While this is good, it may be unnecessary. Often there is
a very good high-level toolkit that you can use to gain access to the service. And these high-level tools come with
support and documentation already built.
Visual Basic programmers tend to think first of third-party add-ons. This is because they usually live in a programming
universe that is inundated with OLE, OCX, and VBX solutions to problems most of them never knew existed! But even
C++ programmers can find more high-level tools on the market today-especially MFC libraries that encapsulate complex
APIs. It pays to look around before you start any "deep coding" of the API set.
Of course, there are drawbacks to using high-level third-party tools. Sometimes they are limited in their performance.
Sometimes, they even turn out to be buggy. While the latter is a rarity, the former is often the case. Most third-party tools
are designed to solve a particular set of problems. If you attempt to use the tool to solve problems outside the original
scope of the tool, you can easily become frustrated and disappointed. It's a good idea to thoroughly test controls and
libraries before adding them to a production application.
Finally, using home-grown libraries or native API calls means that you will need to keep up with the latest bug lists and
upgrades for the underlying API. This, too, can be a big job. Often, it is easier to spend the time shopping for a good tool
and keep current on its upgrades. (Assuming, of course, that the product you purchase actually keeps up with the latest
releases. Nothing is more annoying than discovering that the product you based your whole system upon is no longer in
release!)
The last thing to keep in mind when designing and implementing applications that take advantage of the new Windows
services is the standalone versus extensions/add-ins debate. Is it better to write your own standalone voice-activated e-
mail client or to create extensions or add-ins to existing clients already on the desktop (such as Windows Messaging or
Microsoft Mail clients)?
Often it is easier to build your own standalone versions of common applications. It takes very little time to build a
working e-mail client using the MAPI client interface. This allows you to add whatever features you need without
concern for generating conflicts or confusion when adding features to existing products.
The advantage of extensions and add-ins is that you don't have to reinvent major portions of your application. Most of
the primary desktop tools (e-mail, databases, spreadsheets, word-processors, and so on) are easier than ever to program.
Most of the Microsoft Office applications are able to host the Visual Basic language, and even ones that do not will
allow simple API calls into predefined "hooks" inside the target application. With a bit of reading and lot of
experimentation, you can get your extensions to coexist comfortably with the desktop application.
The general rule to use in deciding whether to go the standalone or extensions/add-ins route is: Check your client base. If
most clients have the targeted desktop application (and they are likely to keep it), then go the extension route. However,
if most of your users have a mix of client applications, or no client applications at all, you would be better to build your
own standalone application (based on the pertinent Windows extension service). Then you can deploy it among a wider
collection of workstations without having to battle different hosting applications, and so on.
A common dilemma for programmers is whether to use MAPI services or Comm API services to perform file transfers
and retrievals. In the past almost all file transfers (such as daily payment batches, sales figures, and so on) were handled
using dedicated applications. It sometimes took hours for these specialized transfer routines to perform their tasks and,
instead of tying up existing systems, this kind of transfer work was off-loaded to dedicated machines.
With the maturing of e-mail and the expansion of the Internet, much of this activity is moving through electronic mail
instead of direct dialup applications. For the most part this is good. But there are times when a simple dialup application
can be a better solution than e-mail.
The advantage of e-mail is the widespread coverage that is so easy using a single protocol (MAPI). Also, the MAPI
model has fairly good recovery in the cases of non-deliverable messages. While it is quite possible that an e-mail
message will get routed to the wrong address or return to you marked "undeliverable," it's rare to actually lose a message
sent via e-mail. Another plus for MAPI services is that there is a wealth of utilities and support software for both
programmers and users. MAPI is the standard and almost any e-mail client supports MAPI services.
The disadvantage of MAPI is that this protocol can be quite limiting in the types and lengths of message formats that can
be sent. The most reliable data format is an ASCII text file. If you plan to send binary data, you'll need to be sure your
routing path to all points is able to handle the binary format. Also, large files tend to get garbled when sent using MAPI
protocol. Many public mail servers even truncate messages to reduce bottlenecks in the system. This can lead to tedious
conversions of binary data into multifile messages that must be reassembled and converted back to binary before they are
usable for the recipient. While utilities exist for this type of arrangement (MIME encoders, for example), it's not always
the best way to go.
The advantage of using a dedicated Comm API-based application is that you can write small, fast, and relatively simple
file transfer programs using any one of several reliable and well-documented pc file transfer protocols (the X-, Y-, and Z-
modem protocols are good examples). By writing small, dedicated applications, you can focus on the things you need
and leave out all the "headroom" required by MAPI e-mail systems.
One of the biggest headaches when using dedicated programs is file transfer scheduling. Usually both systems must be
up and running at the exact same moment. This is often handled by establishing a fixed file transfer schedule. Each
machine is expected to be available at a certain time of the day, week, or month to receive or send data files. Anyone
who has nursed a system like this knows how troublesome this kind of scheduling can be. Unless the calls are timed
correctly, sites that receive a lot of calls will send out frustrating busy signals. Also, if a receiving site goes down (due to
power failure, for example) the whole system can grind to a halt in short order. As long as there are not a lot of sites
involved in this kind of arrangement, dedicated binary transfers can often be faster and more reliable than ASCII text e-
mail messaging.
So, to sum it up, if your application needs to distribute information to a wide number of locations on demand, mostly in
ASCII text format, then e-mail and Internet/Intranet distribution is probably the best method. If you need to send binary
data to just a few sites on a regular schedule (daily, weekly, and so on), then you should consider building a dedicated
dialing application that uses the Comm API (or OCX) instead of MAPI services.
As TAPI systems become more commonplace, it is more likely that creative minds will come up with a reason to use the
new extensions for as yet undreamed-of applications. While the benefits of Full TAPI services are clear, most
applications-especially client-side applications-do not need full use of the TAPI extension. For the most part, server
systems tend to work with inbound calls and client systems usually handle outbound calling. If you are building a TAPI
application, you need to determine if you are building a client- or a server-side application.
Of course, just what constitutes a server is hard to define. With the introduction of Win95 to the market, lots of end users
have all the tools they need to act as a server in telephony or any other Windows extension service. But, despite changes
in the operating systems, the rule still holds-clients place calls, servers answer them.
If you are designing a system for sales force automation, you may be able to provide solid support for key features using
only Assisted (outbound) telephony services. In fact, some telemarketing shops do not want their employees handling
any inbound calls at all. Keep in mind that you can still build serious "power-dialer" applications using only Assisted
TAPI services. You can create applications that read phone number databases, automatically place calls, and present
telemarketing staff with dialog boxes that contain customer-specific information without using Full TAPI services.
Another major advantage of using Assisted TAPI to meet your telephony needs is that almost any data modem is capable
of successfully placing outbound calls. You will rarely need to purchase specialized hardware in order to allow users to
call out using their workstations.
On the flip side, if you are in charge of building customer service center applications that can accept and route incoming
calls based on ANI (Automatic Number Identification), caller ID, or other values such as an account number entered at
the telephone keypad, you'll need the Full TAPI service model to reach your goals. If you decide you need to accept
digits and other call-progress information from incoming calls, you should also consider using a third-party TAPI
development tool. These are relatively inexpensive and usually provide excellent support and documentation. A good
TAPI tool can make telephony programming relatively painless.
Of course, with the added capabilities comes the requirement for increased hardware features. TAPI applications that
handle inbound calls will need to use at least a high-grade voice-data modem. If you are building an application that will
get heavy use or one that uses multiple lines, you need to replace your modem with a telephony card. While these cost
more, they mean a huge increase in capacity for your Windows TAPI applications.
So, again, if you are building client-side applications that will be used primarily for placing outbound calls, it is quite
possible that you can solve your problems using Assisted TAPI services. If, however, you are building applications that
will act as telephony server devices-answering and routing incoming calls-you'll need to use Full TAPI and you will
probably need a full-featured telephony card, too.
Knowing when to use SAPI services can be a bit tricky, too. While speech systems for computers have been around for
some time, the Microsoft SAPI model is still a newcomer. For the most part, SAPI works best as a text-to-speech engine
instead of as a speech recognition system. Even as a TTS tool, the SAPI system has its limits.
Reading extensive amounts of text using SAPI's TTS engine can quickly tire the average user. This is because the
computer-generated voice lacks the human prosody (speech patterns) to which users are accustomed. If you have
material that is relatively stable (for example, directions to your office), it is a better idea to use a recorded WAV format
file than to have SAPI "read" the text to users.
Another consideration is the amount of processing power needed for SAPI services. The SAPI system requires more
memory and processor time than the other two services covered in this book-and for good reason. The amount of
calculation needed to convert ASCII text into recognizable human speech is quite large. Converting speech into models
that can be compared to registered voice menus is even more resource intensive. If you expect most (or even some) of
your users to have less than 16MB of memory on their workstations, you'll have a hard time successfully deploying
SAPI services within your applications.
Finally, the accuracy of SAPI speech recognition (SR) is highly variable. The current state of technology is only able to
reliably handle single words or short phrases. SR systems are best used as an addition to keyboard or mouse controls
within an application. It's frustrating to have to repeat the same phrase over and over again (getting ever louder each
time) in an attempt to get your workstation to perform some simple task that could have been accomplished with a few
clicks of the mouse or keyboard.
For the near term, save your users aggravation (and embarrassment) by providing speech recognition as an option in
your programs, not a sole source of input. This will give users time to get used to the idea of talking to inanimate objects
without feeling silly, and it will give the technology a bit more time to mature and improve its performance.
Summary
In this chapter you learned some of the advantages and disadvantages of deploying software that uses the MAPI, SAPI,
and TAPI extension services for Windows operating systems. The general points discussed included:
● The Rule of Complexity-Where possible, try to limit the number and depth of service extensions used in a single
application. With added services comes added complexity-both for the user and the programmer.
● Native APIs versus High-Level Tools-Consider the pros and cons of using native API calls vs. employing high-
level third-party add-on tools to provide access to extension services. The Native API calls may be faster, but
they will also require more coding and maintenance for you and your staff. Building applications with third-
party tools can be more efficient as long as that tool is used correctly and it is well supported and documented
by its authors.
● Standalone versus Extensions and Add-Ins-When adding new extensions to your applications consider the
advantages of building simple standalone versions of the target application instead of adding features to existing
applications. There are times when a standalone solution is most efficient. However, if you need to deploy
MAPI, SAPI, or TAPI services to a large number of people who already have products they know and use
often, it may be simpler to distribute add-ins or extensions instead of attempting to replace well-established user
tools.
● MAPI versus Comm API-As a general rule, if your application needs to distribute information to a wide number
of locations on demand, mostly in ASCII text format, your best bet is to use MAPI services. If, however, you
need to send binary data to just a few sites on a regular schedule (daily, weekly, and so on) then you should
consider building smaller, more focused, dedicated dialing applications that use the Comm API (or OCX).
● Assisted TAPI versus Full TAPI-Client-side applications usually need only outbound calling services. It is quite
possible that you can meet client-side design goals using Assisted TAPI services and simple data/fax modems.
If, however, you are building applications that will act as telephony server devices-answering and routing
incoming calls-you'll need to use Full TAPI and you will probably need a full-featured telephony card, too.
● Using the Speech API-Current speech technology is more consistent when delivering TTS (text to speech)
services than SR (speech recognition) services. The SAPI system requires a hefty pool of memory (no less than
16MB is recommended on the workstation). Also, SR services are not always accurate. It is best to offer SAPI
services as an additional service rather than as the primary service at this time. As the technology matures and
users become more accustomed to hearing TTS output and speaking in a manner easily understood by SAPI
systems, you can increase the use of speech services in your applications.
Now that you've got a handle on the ups and downs of mixing extension services, it's time to work through some sample
applications that do just that. In the next three chapters you'll build applications that mix MAPI, SAPI, and/or TAPI
services within the same application in order to fulfill a design goal.
Chapter 34
CONTENTS
● Introduction
● Project Resources
❍ The VBVoice Controls
Introduction
In this chapter you'll build a Visual Basic 4.0 project that combines TAPI and MAPI services to create a dial-up FAX
server. With this application running on a workstation, users could dial into the workstation, receive prompts for entering
their own FAX telephone number, and select a document number. Then, when the user hangs up, the workstation will
format and send the selected FAX document to the user's FAX address.
This example application will be a bit Spartan in construction. The caller prompts are simple and the error handling is
very basic. This was done to focus on the details of putting together the larger pieces of the project. If you plan to
implement a production version of this program, you'll need to spend a bit more time polishing it up. However, when
you are done with this project, you'll have a working FAX server that you can install on any 32-bit Windows
workstation.
Note
The evaluation version of VBVoice is not fully functional. You'll be able to
complete a demonstration version of the project, but you will not be able to
install a production version unless you purchase a licensed copy of
VBVoice.
Project Resources
The VBVoice controls answer the incoming call and prompt the caller to enter a FAX number and the selected document
number. Once this is done, the VBVoice controls turn the collected data over to the MAPI system for delivery of the
selected FAX.
The following resources must be available on your workstation in order to build and run the FaxBack project:
● Microsoft Exchange with FAX transport services installed-The Microsoft Exchange Server and Workstation
versions both come with FAX transport support built in. If you haven't installed FAX services for your
Exchange profile, you should do it before you start this project. If you are running Microsoft Mail you can still
complete this project if the Microsoft Mail server supports FAX transmissions.
● FAX modem-If you're running a stand-alone workstation, you'll need to have a FAX modem attached. If you are
running as a client to a mail server that supports FAX transmissions, you may not need a FAX modem on your
workstation.
● Sound card and speakers-This example program will use the sound card and speakers to simulate an incoming
telephone call. The evaluation copy of VBVoice will not work with an installed telephony card.
The next few sections go into a bit more detail on the VBVoice controls and how they are used. You'll also get a quick
review of the SMAPI interface and the support files you'll need to load in order to complete this project.
To gain access to the TAPI services, you'll use an evaluation version of the VBVoice control set from Pronexus, Inc. An
evaluation version of the controls can be found on the CD-ROM that ships with this book. With this evaluation set, you'll
be able to build and compile your Visual Basic 4.0 application and run it in test mode using your sound card and
speakers.
Note
The VBVoice controls were briefly reviewed in Chapter 31, "Third-Party
TAPI Tools." Refer to that chapter for more information on the VBVoice
controls and how they are used to build telephony applications.
If you haven't already done so, install the evaluation copy of VBVoice32 from the CD-ROM that ships with this book.
Warning
If you already own a working copy of VBVoice do not install the evaluation
version because this may trash your working copy.
When you install VBVoice, you'll have a rich set of controls available for creating telephony applications. Be sure to add
these controls to your FaxBack project before you begin coding (select Tools | Custom Controls).
The FaxBack project is based on a FAX demonstration project available from the Pronexus FTP site (ftp.pronexus.
com). The demonstration program includes a voice prompt file (VAP) that has all the basic prompts you need to
complete this project. Instead of going through the process of recording your own prompts for the FaxBack example,
you'll use the FAX.VAP file that is provided by Pronexus. You'll find this on the CD-ROM that comes with this book,
too.
Note
For those who are curious, the voice you'll hear in the VAP file is not mine
(sorry!).
If you plan on putting a version of this application into production, you'll want to use the Pronexus voice recording tools
to create your own set of prompts.
In this project you'll use the SMAPI interface to provide access to the message services of Windows. The SMAPI
interface was chosen because it has a very small "footprint" in your project. While the MAPI.OCX controls and the OLE
Messaging library would also work fine, the SMAPI provides all the MAPI access that is needed for this project.
Tip
The SMAPI interface was covered in depth in Chapter 6, "Creating MAPI-
Aware Applications." You can refer to that chapter for more information on
the SMAPI interface and how it works.
There are two support modules that you can add to your Visual Basic project to provide access to the SMAPI level of
messaging services.
● VBMAPI32.DLL-This is the support DLL that sits between your Visual Basic calls and the MAPI subsystem.
You can find this on the CD-ROM that ships with this book.
● VBAMAP32.BAS-This file contains type definitions, constants, and the API declarations needed to access
Simple MAPI from any 32-bit platform. The API calls in this file go directly to the VBAMAP32.DLL
mentioned above.
● MAPIERR.BAS-This file provides a handy function that translates the error codes returned by SMAPI into
friendly strings. This project does not use this function, but it is a good idea to include it in all your SMAPI
projects.
Note
All four files discussed here were covered in greater detail in Chapter 6,
"Creating MAPI-Aware Applications." Refer to that chapter for more
information on SMAPI services.
When you begin this project, you can add these BAS files (select File | Add Files) instead of typing them in
again.
So, here's a quick review-you'll need the following resources for this project:
Once you've assembled all these resources, you're ready to start your project. Start Visual Basic 4.0 and add the
VBVoice controls to your toolbox (select Tools | Custom Controls). Next add the two SMAPI support files
(MAPIERR.BAS and VBAMAP32.BAS) to your project (select Files | Add Files). It's a good idea to copy the
support files to the sample directory that you'll be using to build the FaxBack project.
At this point your Visual Basic 4.0 design-mode environment should look something like the one in Figure 34.1.
Figure 34.1 : Visual Basic design-mode environment for the FaxBack project.
Notice the VBVoice controls in the toolbox and the two SMAPI support files in the project window. Now you're ready to
start coding!
The FaxBack support module has five routines and several project-level variables. Add a new module to the project
(Insert | Module) and set its Name property to modFaxBack. Save the empty module as FAXBACK.BAS.
Now add the project-level variables. These will be used throughout the application. Listing 34.1 shows the code that you
should place in the general declaration section of the module.
Option Explicit
'
' fax variables
Public cFAXPrefix As String ' file prefix
Public cFAXSuffix As String ' file tail
Public cTestFAXAddr As String ' local FAX machine
Public cFAXFolder As String ' document folder
'
' SMAPI variables
Public lReturn As Long ' return flag
Public lSession As Long ' session handle
Public udtMessage As MapiMessage ' message object
Public udtRecip As MapiRecip ' recipient object
Public udtRecips() As MapiRecip ' recipient collection
Public udtFile As MapiFile ' attachment object
Public udtFiles() As MapiFile ' attachment collection
You can see there are two sets of variables. The first set will be used to keep track of FAX related values. The second set
of variables will be used to handle MAPI service requests.
The next routine you'll add to the project is a function to verify the existence of a file. Add a new function called
FileExists to your project (Insert | Procedure | Function), then add the code shown in Listing 34.2.
This routine attempts to open a filename passed into the function. If the open is successful, the routine returns TRUE. If
the open fails, it is assumed the file does not exist and the routine returns FALSE.
Now add a new subroutine called CenterForm. This will be used to center forms on the display. Listing 34.3 has the
code you need to fill out this routine.
The next two routines handle logging in and out of MAPI services. The first routine (SMAPIStart) initializes the
workstation for MAPI services. Add the code shown in Listing 34.4 to your project.
Listing 34.4. Adding the SMAPIStart routine.
The SMAPIStart routine attempts to log in with the profile name passed into the routine. If the profile name is invalid,
a dialog box will appear to prompt you for a valid logon ID. The logon will result in a new MAPI session for this
workstation. This way, any other open MAPI session you have on your station will not be affected by the FaxBack
application.
Now add the SMAPIEnd routine to your project and enter the code from Listing 34.5.
This routine simply logs off the current active MAPI session. You'll call this at the end of your program.
The only other support routine left is the routine that will actually compose and send the FAX by way of MAPI services.
Add a new subroutine called SendSMAPIFax to the module and enter the code from listing 34.6.
'
' get simple file name
Dim cName As String
cName = Mid(cFile, Len(cFAXFolder) + 2, 255)
'
' make message
udtMessage.Subject = "Requested FAX From FaxBack(tm)"
udtMessage.NoteText = "Here's the FAX document you requested
from FaxBack(tm)." ➂& Chr(13) & Chr(13) & " #"
'
' make attachment
ReDim udtFiles(1)
udtFiles(0).Position = Len(udtMessage.NoteText) - 1
udtFiles(0).PathName = cFile
udtFiles(0).FileName = cName
udtFiles(0).FileType = 0
'
' make recipient
udtRecip.Reserved = 0
udtRecip.RecipClass = 1
udtRecip.Name = "FAX:FaxBack_Request@" & cRecipient
udtRecip.Address = "FAX:FAXBack_Request@" & cRecipient
udtRecip.EIDSize = 100
udtRecip.EntryID = ""
'
' update message
udtMessage.RecipCount = 1
udtMessage.FileCount = 1
'
' now call sendmail w/o dialog
lReturn = MAPISendMail(lSession, lhWnd, udtMessage, udtRecip,
udtFiles(0), &H0, ➂0)
'
End Sub
There's a lot going on in this routine. First, the routine accepts three parameters as input. The first parameter is the
window handle used in all SMAPI calls. The second parameter is the file to be faxed. The third parameter is the phone
number to use as the address for the MAPI fax. Once the routine has all the parameters, it's time to go to work.
The first step is to create a "simple" filename by stripping the folder name from the second parameter. This will be used
as part of the attachment. Next, the routine builds a simple subject and message body for the MAPI message. The "#" at
the end of the message marks the position where the attachment will be placed.
After building the message body, the attachment is created. Most of this should make sense. Setting the FileType
property to "0" tells MAPI that this is an embedded data file instead of some type of OLE attachment.
Next comes the fun part-creating the recipient. Remember "one-off" addressing? That's where you use the actual MAPI
transport name as part of the message. In this routine the .Name and .Address properties are both set to the internal
complete MAPI address required for FAX messages. The FAX: tells MAPI to handle this message using the FAX
transport provider. The stuff between the ":" and the "@" is just filler-MAPI ignores it. The last part of the address is the
telephone number the FAX transport will use to deliver the message. The other property settings are default values used
in all SMAPI recipient types.
Finally, after building the message, attaching the file, and creating the FAX address, the routine updates the recipient and
file count and then calls the MAPISendMail function to place the message into the MAPI outbox.
What happens next depends on how your FAX transport is configured. If you have your FAX transport set to deliver
faxes immediately, you'll see the FAX transport kick in, format the attachment, and send it out over the modem line. If
you have your transport set to delay delivery for off-peak hours, you'll see the formatting, but the FAX will sit in a queue
until later. Also, the cover sheet used for your FAX messages is established by settings for the FAX transport provider.
Tip
You can review your FAX transport settings from the Windows Messaging
client by pressing the alternate mouse button on the Exchange inbox icon
and selecting Properties from the context menu. Then select the FAX
transport from the Services list and click the Properties button. You
can even edit or invent new cover pages from this dialog box.
That's the end of the support routines. Save this module (FAXBACK.BAS) and the project (FAXBACK.VBP) before
continuing with the next section.
The FaxBack form is the main form of the project. Although there is not much code in the project, setting up the form
takes quite a bit of doing. This is because most of the logic of the program is kept in the properties of the VBVoice
controls on the form.
The next three sections cover these three steps in greater detail.
Laying out the controls on the FaxBack form is more involved than doing the same for standard Visual Basic forms. This
is because most of the controls on the form are VBVoice telephony controls. These controls require additional property
settings and must be linked to one another to complete the call-flow diagram that will control each incoming call. You'll
actually diagram the call progress and set the program properties at the same time. By the time you've diagrammed the
call, you will have built almost all the logic needed to handle incoming fax requests.
First you need to lay out the controls on the form. Refer to Figure 34.2 and Table 34.1 for details on laying out the
FaxBack form.
Warning
Be sure to add the VBVFrame control to the form first. Then draw the
VBVoice controls onto the VBVFrame control. If you do not draw them on
(instead of double-clicking the control in the toolbox), you'll get error
messages while you are trying to build your form.
After you've placed the controls on the form, save it as FAXBACK.FRM and update the project (FAXBACK.VBP). Now
you're ready to fine-tune the properties of the VBVoice controls.
Tip
As you work through this part of the project, refer to Figure 34.2 to get an
idea of what the finished product should look like.
There is one property that must be set on the VBVFrame control. This property will tell all the other controls where to
find the recorded prompts. Locate a blank spot on the frame and click the alternate mouse button to bring up the context
menu, then select Properties to call up the custom properties box (see Figure 34.3).
Make sure the Project Voice Files property is set to the folder that contains the FAX.VAP voice prompt file. All
the other properties on this control can be left to their default values.
The AcceptCall LineGroup control is where the incoming call will enter the program. You do not need to set any
properties for this control. However, you need to connect the "Ring" node of the AcceptCall control to the input node
of the GetFaxAddr GatherDigits control (see Figure 34.2).
The GetFaxAddress GatherDigits control will be used to get the destination FAX telephone address from the
caller. You need to add a custom node to the control by adding an entry on the Routing page of the custom properties
box. First, remove all the numerical entries from the routing page by highlighting each one and pressing the Delete
button. Then press the New button on the Routing page and enter "$" for the digits and "FaxAddr" for the condition
name (see Figure 34.4).
Next, you need to set the properties on the Terminations page. First, clear the Use Default Error Handler
check box and make sure the Clear digits on entry and Disable global help boxes are checked. Next,
set the Maximum Silence to "5," the retries on error to "2," and the Always terminate on to "#" (see
Figure 34.5).
You also need to add a greeting to this control. Select the Greetings page in the custom properties dialog box and press
Add Phrase to bring up the Select Phrase dialog box. Then find the FAX.VAP file in the VAP combo box. Select the
first phrase in the file-"Enter the number of your fax machine followed by a pound sign" (see Figure 34.6).
Figure 34.6 : Adding a new phrase to the GetFaxAddress control.
Finally, connect the FaxAddr node of the GetFaxAddress control to the input node of the GetFaxFile control.
This second GatherDigits control will be used to collect the three-digit document number that the user wishes to
receive. Erase the default routine options and add a custom node to the control by pressing the New button on the
controls Routing page. Enter "nnn" in the Digits field (you'll accept three, and only three, digits) and enter DocNbr in
the condition name field.
Set the termination values for the GetFaxFile control the same as you did for the GetFaxAddress control. The
only difference is that you should leave the Clear Digits on Entry check box empty (unchecked). The rest of
the termination settings are identical to those you used in the GetFaxAddress control.
Next, add a greeting for this control to tell the user to enter a document number. Press the Add Phrase button on the
Greetings page, select the FAX.VAP prompt file, and pick the second prompt in the file-"Enter the number of the
document you require."
Lastly, connect the DocNbr node of the GetFaxFile control to the input node of the VerifyFile control.
The VerifyFile control is a VBVoice User control. The exact actions taken by this control are determined by the
user, usually through the results of Visual Basic code. For this project, make sure there are only two possible options: "0"
and "1" (press the Delete button to remove the rest). Next, click off the Clear digits on Entry check box.
Now connect the "0" node of the VerifyFile control to the input node of the HangUp control. Then connect the "1"
node of the VerifyFile control to the back of the Err node of the GetFaxFile control.
The last control in the call-flow diagram is the HangUp OnHook control. This control's job is to hang up the phone to
terminate the call. The only property you need to add to this control is a parting salutation. Call up the custom properties
box and press the Add Phrase button to bring up the Select Phrase dialog box. Locate the RECORD.VAP prompt file
(it was installed with VBVoice) and pick the last phrase in the list-"Your message has been sent."
That is the last of the property settings for the VBVoice controls. You now have defined the call-flow diagram for your
FaxBack application. Be sure to save this form (FAXBACK.FRM) and update the project (FAXBACK.VBP) before you
continue.
Since most of the logic is kept in the VBVoice controls, you only need to add a few code routines to the form itself.
You'll add code to the Form_Load and Form_Unload events, code for the command button array, and code for the
VerifyFile_Enter and AcceptCall_Disconnect events.
First, you need to declare three form-level variables. Enter the code from Listing 34.7 into the general declarations
section of the form.
Listing 34.7. Declaring the form-level variables.
Option Explicit
'
Dim bFaxNbrCollected As Boolean
Dim bRunMode As Boolean
Dim cLogID As String
Next, add the code from Listing 34.8 to the Form_Load event of the form.
This routine sets several variables for the fax handler, initializes form properties, and logs into MAPI services. Notice
that this version of the program will be looking for FAX documents stored in the same directory as the application (App.
Path) that have "FAX" as the first three letters of the filename and that have ".DOC" as the file tail. If you decide to put
this project into production use, you may need to create a more flexible naming convention for your documents.
Also note that you need to set the cLogID variable to a valid MAPI profile name (one that has access to FAX services).
You should also set the cTestFAXAddr variable to point to a FAX you can use to test the application.
The Form_Unload code is quite short. Listing 34.9 shows the line you need to add to log off the MAPI services before
exiting the program.
Listing 34.9. Coding the Form_Unload event.
Now add the code to handle the user selections on the command button array. Add the code in Listing 34.10 to the
cmdFax_Click event.
There's not much that's special in the code in Listing 34.10. The first command button sends a test fax, the second and
third start and stop VBVoice in run mode, the fourth button ends the program, and the last button calls the About box.
Note
The evaluation copy of VBVoice will operate only in test mode. If you wish
to be able to use VBVoice in run mode, you'll need to purchase a licensed
copy of VBVoice from Pronexus.
There are only two remaining code events for this form. The first is the Disconnect event of the AcceptCall
control. This event fires each time the HangUp control ends a call. This is where the program attempts to send the
requested FAX document to the recipient's FAX number. Add the code from Listing 34.11 to the
AcceptCall_Disconnect event.
This routine first checks to make sure a document was selected, then confirms that it is on file, and then sends it out with
a call to the SendSMAPIFax routine you coded at the start of this chapter. Once this is done, the program will wait for
the next incoming call.
Note
You'll see from the comments, that this routine never executes in test mode.
But if you have a licensed copy of VBVoice, you'll be able to run the code
in this event.
The final code routine is the code for the VerifyFile_Enter event. This is the event tied to the User control.
Under run mode, after a caller selects a document, this routine verifies it is on file and then passes the information on to
the AcceptCall_Disconnect event. Under test mode, this routine verifies the file and then sends it out and exits
the program. This exit is required in test mode since the program can never execute a disconnect in test mode.
Enter the code from Listing 34.12 into the VerifyFile_Enter event.
Now that you've completed all the code for the FaxBack form, save the form (FAXBACK.FRM) and the project
(FAXBACK.VBP) before moving on to the last coding section.
The About dialog box is very simple. It displays a graphic, a label control that shows some basic information about the
project, and an OK command button. Refer to Figure 34.7 and Table 34.2 for details on laying out the About dialog box.
After laying out the form, you're ready to add code to the About box. You only need to add code to the Form_Load
event and the Command1_Click event. Listing 34.13 shows all the code you need for the About form.
Notice that most of the information displayed in the About box is drawn from the App object properties. You can set
these properties using the File | Make EXE File | Options screen. Call up that screen now and enter the
values from Table 34.3 into the dialog box.
That's the last code for this project. Save the form as FBABOUT.FRM and the project (FAXBACK.VBP). Now is a good
time to compile your project and check for bugs. Once you're sure you have all the bugs worked out, you're ready to test
your Visual Basic fax handler.
Testing the FaxBack application with the evaluation copy of VBVoice is a bit tricky, but not impossible. First, start the
program in design mode (not compiled). Your first screen should look like the one in Figure 34.8.
For a simple test, press the Test FAX button. This will test the SMAPI side of the program without using any TAPI
features. You should see the program locate the test document on your local drive, format it for FAX transport, and, if
you have immediate delivery turned on, the workstation will dial the address and send the FAX (see Figure 34.9).
Tip
If you are getting error messages telling you that Microsoft Exchange could
not find a transport provider for your fax message, try moving the FAX
service to the first service in the list of transports for your machine. You can
do this from the Properties window of Microsoft Exchange.
Once you have the FAX portion of the system working, you're ready to add the TAPI side of the application. You'll test
the telephony services using the test mode of VBVoice. Test mode uses your workstation's sound card and speakers to
simulate an incoming call.
To start test mode, move your pointer over the AcceptCalls control, press the alternate mouse button, and select
Start Test from the context menu. You'll then see a test dialog box appear. This dialog box will act as a caller's
telephone handset. When you press the Start button, you'll see a debug screen and a "Ring or Call" dialog box appear.
Now VBVoice is ready to start testing your application (see Figure 34.10).
Press the Ring button to start the test. This simulates a ringing phone for your application. You should see the debug
window fill with tracking information and hear the prompts as they walk you through the process of entering a FAX
address and selecting a document. Valid document numbers are "000," "100," and "200." These three Microsoft Word
documents have been included in the same folder on the CD-ROM that has the FAX.VAP file and the other support files
for this project. You can add other files if you wish.
Once the FAX document has been sent, the program exits to Visual Basic design mode. This is only true in test mode. If
you were running a licensed copy of VBVoice in run mode, after sending the FAX, the program would wait for the next
caller and start the process all over again.
Summary
In this chapter, you learned how to combine telephony and e-mail services to build a fax-on-demand server in Visual
Basic 4.0, using VBVoice controls from Pronexus, and with the simple MAPI API declarations from Microsoft
Exchange running the FAX transport.
This sample application does not contain all the bells and whistles needed to operate a production FAX server, but all the
basics are here. If you wish, you can expand on this program by adding customized prompts and providing better error
handling within the program and call flow. You should also consider writing a transaction log to keep track of all the
calls you get and see what documents are being requested. With just a bit more work, you could have your very own
FAX server up and running in no time!
Chapter 35
CONTENTS
● Project Resources
● Coding the Library Modules
❍ The AssistedTAPI Module
The example program in this chapter uses SAPI and TAPI services to create a true "hands-free" telephone. With this
program and a pc speaker phone, you can lookup and dial telephone numbers by simply giving voice commands to your
pc. You'll be able to initiate database adds, edits, and deletes; issue a voice command that will search the database for a
name; and tell the program to Dial Mike. The Voice Phone will locate the record in the database, pull up the phone
number, place the call, and then prompt you to begin speaking.
As an added bonus, this program gives audible responses to help requests and speaks the names and telephone numbers
of selected records in the database. Even the About box is "read" to you! Figure 35.1 shows a completed version of
Voice Phone in action.
Project Resources
There is one main form for the project and three support forms:
● VPhone-This is the main form. All primary operations are performed here, and it calls all other forms.
● VRec-This is the form used to add and edit database records.
● VHelp-This is a small help screen that displays and speaks help hints on using Voice Phone.
● VAbout-This is the standard About box. It also speaks the application information to the user.
In addition to the forms, there are four support modules for the Voice Phone project:
● VTAPI.BAS-This holds the Assisted TAPI API calls, two user-defined types, and two functions that are used
to request Assisted TAPI services.
● SRCBK.CLS-This is the speech recognition callback class module. This module will process the commands
spoken to Voice Phone by users.
● TTSCBK.CLS-This is the text-to-speech callback class module. The Voice Phone project does not use this
module, but it is a good idea to include it in all projects that use the TTS engine.
● VPHONE.BAS-This module contains the bulk of the project code. All supporting subroutines and functions are
kept here.
Before you begin coding the project, you'll need to make sure you have the following resources loaded available on your
workstation:
● An Assisted TAPI compatible modem (almost any modem will do) with a telephone handset attached.
● A sound card, a microphone, and speakers for speech recognition and text-to-speech services.
When you first start this project, you'll need to make sure you have loaded the proper support libraries for Visual Basic.
Select Tools | References and load the following:
Note
If you don't have these libraries available from your References dialog
box, you'll need to use the Browse button to find them. Usually they can be
found in the SYSTEM folder of the WINDOWS directory. The Voice libraries
can also be found on the CD-ROM that ships with this book. You probably
have other resources loaded for your programs. That's fine. Just be sure you
have the three resources listed above.
The first step in the process of building the Voice Phone project is creating the library modules. Three of the modules
exist to provide simple support for the Windows extension services (SAPI and TAPI). The fourth module holds most of
the project code.
The AssistedTAPI module holds the Telephony API declarations, two type definitions, and two Visual Basic
wrapper functions. With this one code module you can provide any Visual Basic or VBA-compatible program with basic
TAPI services.
Tip
You might think it a better idea to implement Assisted TAPI support using a
Visual Basic class module instead of a simple code module. However, the
code module can be loaded into any VBA-compatible environment
(including Visual Basic 3.0). The class module could only be used for
Visual Basic 4.0.
Add a BAS module to your project (Insert | Module) and set its Name property to AssistedTAPI. Save the
module as VTAPI.BAS. Now you're ready to start coding.
The first things to add to this module are the Assisted TAPI API declarations. Listing 35.1 shows the code that imports
the tapiRequestMakeCall and tapiGetLocationInfo API calls.
'
' declare assisted tapi functions
'
#If Win32 Then
Declare Function tapiRequestMakeCall Lib "TAPI32.DLL" (ByVal
lpszDestAddress As ➂String, ByVal lpszAppName As String, ByVal
lpszCalledParty As String, ByVal ➂lpszComment As String) As Long
Declare Function tapiGetLocationInfo Lib "TAPI32.DLL" (ByVal
lpszCountryCode As ➂String, ByVal lpszCityCode As String) As Long
#Else
Declare Function tapiRequestMakeCall Lib "TAPI.DLL" (ByVal
lpszDestAddress As ➂String, ByVal lpszAppName As String, ByVal
lpszCalledParty As String, ByVal ➂lpszComment As String) As Long
Declare Function tapiGetLocationInfo Lib "TAPI.DLL" (ByVal
lpszCountryCode As ➂String, ByVal lpszCityCode As String) As Long
#End If
Notice that the code in Listing 35.1 uses the conditional compilation directives along with the code for both 16- and 32-
bit Visual Basic 4.0. This ensures that the code will work in either version of Visual Basic 4.0 that you use.
Next you need to add two user-defined types to the module. These types encapsulate the parameters needed for the two
TAPI calls. Using UDTs in this way reduces the complexity of your code and makes it easier to read. Add the code
shown in Listing 35.2 to the general section of the module.
'
' TAPILocation Type
Type TAPILocation
Country As String * 1
City As String * 3
End Type
'
' TAPIMakeCall Type
Type TAPICall
Address As String
AppName As String
CalledParty As String
Comment As String
End Type
After declaring the API routines and defining the type variables, you're ready to add the wrapper functions that will
encapsulate the API calls. Listing 35.3 shows the function that supports the tapiGetLocationInfo API call. Add
this to your module.
Notice that this function returns a UDT that contains both the country code and the city code for the workstation.
Note
The TAPIGetLocation function is not called in this project. It is
included here for completeness. When you use this module for other TAPI
projects, you'll have this function call already defined.
The second API wrapper function is the TAPIMakeCall function. This function accepts the TAPICall user-defined
type as input and places an outbound call. Listing 35.4 shows the code for this function.
Listing 35.4. Adding the TAPIMakeCall function.
This function returns a value that indicates the results of the TAPI request. If the value is less than 0, you've got an error
condition. Only the first parameter is required (Address).
Tip
For a more detailed look at Assisted Telephony, refer to Chapter 29,
"Writing TAPI-Assisted Applications."
That is the end of the Assisted TAPI support module. Save this module (VTAPI.BAS) and the project (VPHONE.VBP)
before continuing.
Since this project uses the SAPI Voice Command and Voice Text type libraries, you'll need to add a class module to
your project for each of the two libraries. These classes were designed to allow high-level languages (like Visual Basic)
to register services that require the use of notification callbacks. The callback module for the TTS engine will not be
used in this project. However, you'll use the SR callback routines to trap and respond to spoken commands.
Warning
The function names for these callbacks cannot be altered. The OLE libraries
for SR and TTS services are looking for these specific function names. If
you use some other names, the callbacks will not work and you will get an
error report when you request TTS or SR services.
First add the TTS callback functions. In this project, the functions will be empty, but you'll need to register them
anyway. Add a class module to your project (Insert | Class Module). Set the class name to TTSCallBack, set
its Instancing property to Creatable, MultiUse, and its Public property to TRUE. Now add the two
functions from Listing 35.5 to the class.
Option Explicit
Public Function SpeakingDone()
'
' this method will execute when the
' TTS engine stops speaking text
'
End Function
Now add a second class module to the project. Set its Name property to SRCallBack, its Instancing property to
Createable, MultiUse, and its Public property to TRUE. Now add the code shown in Listing 35.6 to the class.
Option Explicit
End Function
You'll notice that the only routine used is the CommandRecognize function. This routine looks at each command that
passes through the speech recognition engine, parses the input and, if it's a known command, executes the appropriate
code.
The reason the command line must be parsed is that several of the voice commands will be in the form of lists. You may
remember that you can register command lists with the SR engine without knowing the members of the list ahead of
time. In your project, you'll fill these lists with the names of people in the user's phone directory at run-time.
After you've entered the code, save this module as SRCBK.CLS before continuing with the chapter.
The LibVPhone module contains most of the code for the project. It is here that you'll put the routines that initialize the
speech engines, load the database engine, and handle the processes of adding, editing, and deleting records from the
phone book.
First, add a code module to the project. Set its Name property to LibVPhone and save it as VPHONE.BAS. Next, add
the project-level declarations shown in Listing 35.7 to the general declaration section of the module.
Option Explicit
'
' establish voice command objects
Public objVCmd As Object
Public objVMenu As Object
Public lVMenuIdx As Long
Public objVText As Object
Public iVType As Integer
Public cUserMsgs() As String
'
' spoken message handles
Public Const ttsHello = 0
Public Const ttsVPhone = 1
Public Const ttsList = 2
Public Const ttsExit = 3
Public Const ttsEdit = 4
'
' database objects
Public wsPhone As Workspace
Public dbPhone As Database
Public rsPhone As Recordset
The code here declares the required objects for SAPI and for database services. There are also several constants that will
be used to point to text messages that will be spoken by the engine at requested times in the program.
Next add the routine that will start the SAPI service initialization. Add a subroutine called InitVCmd to the project and
enter the code from Listing 35.8.
This code initializes the voice command object that will be used to handle speech recognition services for the program.
Notice the line that registers the SRCallBack class. The class name is prefixed with the application name. You must
set this application name manually. To do this, select Tools | Options | Project and enter VPHONE in the
Project Name field.
Warning
You must update the Project Name field to match the App.EXEName
value before you attempt to run the project. If you fail to do this, you will
receive errors from the SR engine.
The InitVCmd routine calls another routine to handle the creation of the command menu. Add a new subroutine to the
module called BuildVMenu and enter the code shown in Listing 35.9.
'
' create list commands
lVMenuIdx = lVMenuIdx + 1
objVMenu.Add lVMenuIdx, "Edit <Name>", "Edit Name", "Edit Name"
'
lVMenuIdx = lVMenuIdx + 1
objVMenu.Add lVMenuIdx, "Delete <Name>", "Delete Name", "Delete
Name"
'
lVMenuIdx = lVMenuIdx + 1
objVMenu.Add lVMenuIdx, "Dial <Name>", "Dial Name", "Dial Name"
'
lVMenuIdx = lVMenuIdx + 1
objVMenu.Add lVMenuIdx, "Find <Name>", "Find Name", "Find Name"
'
End Sub
There's a lot going on in this routine. First, internal variables are declared and set to their respective values. Next, the
menu object is created using the MenuCreate method. After the menu object is created, the Add method of the menu
object is used to add simple voice commands to the menu. Finally, the list commands are added to the menu (Edit,
Delete, Dial, and Find). All these list commands refer to the Name list. This list will be filled in at run-time.
Now add the routine that will fill in the name list for the voice commands. Create a new subroutine called
LoadNameList and enter the code you see in Listing 35.10.
As you can see, the LoadNameList routine reads each record in the open database table and adds the names, separated
by chr(0), to a single string. This string is the list that is registered with the menu object using the ListSet method.
Now you're ready to add the routines that will initialize the Voice Text object for TTS services. First, add a new
subroutine called InitVTxt and enter the code shown in Listing 35.11.
The only other TTS support routine you'll need is the one that builds a set of messages that will be spoken by the TTS
engine at different times in the program. Add a new subroutine called LoadMsgs to the module and enter the code from
Listing 35.12.
The public constants declared at the top of this module will be used to point to each of these messages. This will make it
easier to read the code.
Next you need to add several routines to initialize and support database services. First, add the InitDB subroutine to
your project and enter the code shown in Listing 35.13.
Warning
Using the App.Path property to set the location of the MDB file assumes
that you have created a project directory. If you attempt to place the project
and the MDB files in the root directory of a drive, you'll receive error
messages. It's recommended that you create a project directory and store the
MDB in that directory.
The only real purpose of this routine is to check for the existence of the database file. If it is not found, the
BuildDatabase routine is called before the OpenDatabase routine. Now add the BuildDatabase subroutine to
your project as shown in Listing 35.14.
The code here is quite handy. First, the database file is created. Then, three SQL statements are executed. The first one
creates the new VPHONE table. The second two statements add two records to the new table.
Tip
This is a great technique for building databases upon installation of a new
application. This way, users don't have to worry about copying data files,
confusing older versions of the data, and so on. Even better, if you need to
start the database from scratch, all you need to do is remove the database
file and start the program-it will create the initial database for you!
After the BuildDatabase routine has been added, you need to add the code that will open the existing database and
select the phone records. Create the OpenDatabase subroutine and enter the code from Listing 35.15.
Nothing real fancy here. The database is opened and a single SQL SELECT statement is executed to create a Dynaset-
type recordset for use in the program.
● AddRec
● EditRec
● DeleteRec
● LookUp
The AddRec and EditRec routines use a secondary dialog form (frmVRec), which you'll build in the next section of
this chapter.
Create a new subroutine called AddRec and enter the code from Listing 35.16.
Now add the EditRec subroutine and enter the code from Listing 35.17.
The DeleteRec subroutine consists of a single message box confirmation and the delete action. Add the code in
Listing 35.18 to the module.
Finally, add a new function called LookUp to the module. This function takes one parameter (the Name) and returns the
corresponding phone number. Enter the code from Listing 35.19.
Only two support routines are left. The PlaceCall routine is used to perform the Assisted TAPI service request.
Listing 35.20 shows you the code for this routine.
The last routine is one that is used to center dialog boxes on the screen. Add the code from Listing 35.21 to your project.
That is the end of the LibVPhone module code. Save this module (VPHONE.BAS) and the project (VPHONE.VBP)
before you move to the next section.
The Vphone form is the main dialog box of the project. The first step is to lay out the controls on the form. Then you
can add the menu and the code behind the form. Refer to Figure 35.2 and Table 35.1 for details on the size and position
of the controls on the form.
Note
The VPHONE.ICO icon file that is used in this project can be found on the
CD-ROM that ships with the book. Be sure to copy that file to the
application directory before you start the project.
Along with the control layout, there is also a small menu that goes with the VPhone form. Refer to Figure 35.3 and
Table 35.2 for details on laying out the Vphone menu.
Note
Be sure to lay out the menu using menu arrays. You'll add code to the menu
array in the next section.
After laying out the form, save it as VPHONE.FRM and the save the project as VPHONE.VBP before moving on to the
next section.
There's not a lot of code for the VPhone form. Most of the important stuff was built in the LibVPhone module.
However, you'll need to add code to the control events that call the LibVPhone routines.
First, add the code from Listing 35.22 to the Form_Load event.
The code in Listing 35.22 sets up some basic form properties and then calls the initialization routines for the various
services. Finally, the application sends out a greeting message to the user.
This code destroys the programming objects created at startup. It's always a good idea to do this before exiting your
application.
The Form_Load event calls a custom routine called LoadList. This subroutine fills the onscreen list box with the
names and phone numbers from the database. Add the LoadList subroutine to your form and enter the code from
Listing 35.24.
Listing 35.24. Adding the LoadList routine.
Next, add the code from Listing 35.25 to handle the user selections on the main command button array (cmdPhone).
Notice that help is delivered in the form of three spoken messages to the user.
You also need to code the cmdDial_Click event. This code fires each time the user presses the Place Call
command button. Enter the code from Listing 35.26.
The cmdDial_Click event will first check the txtDial control to see if a phone number is present. If it is, that
number is used to place the call. If no number is present, the routine will see if the user has selected a name from the list
box. If so, the routine first calls the List1_DblClick event to force the name into the txtDial text box, then calls
the PlaceCall routine to make the call. Finally, if none of this works, a message is displayed telling the user to select
a name from the list.
Now add code to the List1_DblClick event to move the phone number from the list box into the txtDial text
box. Listing 35.27 shows how this is done.
The only code left to create is the code to handle the menu arrays. Listing 35.28 shows the code for both the
mnuFileItem_Click and the mnuHelpItem_Click events. Add these two code modules to your form.
That's the end of the code for the VPhone form. Save the form (VPHONE.FRM) and the project (VPHONE.VBP) before
continuing.
There are three small support forms for the VPhone project. The VRec form is used to handle adds and edits to the data
table. The Vhelp form displays a set of help strings, and the VAbout dialog box just shows the standard program
information.
The VRec form is used to handle adding new records to the data table and editing existing records. There are two text
boxes, three label controls, and three command buttons on the form. Add a new form to the project and lay out the
controls on the form as shown in Figure 35.4 and Table 35.3.
Notice that the lblAction label control is an invisible control. This control is used to pass information from the
VPhone form to the VRec form, and is not used for direct display at run-time.
There is very little code needed for the VRec form. Listing 35.29 shows the code for the Form_Load and
Form_Activate events. Add this code to your form.
Nothing fancy in the Form-Load event. The Form_Activate event contains code that will read the database record
aloud to the user.
The only other code needed on the form is code for the cmdRec_Click event. Enter the code from Listing 35.30 into
your project.
You'll notice that this routine checks the contents of the invisible label control (lblAction) to see if this is an "add" or
"edit" form. If this is an "add" form, the contents of the control are used to add a new record to the table.
That's it for this form. Save the form as VREC.FRM and update the project (VPHONE.VBP) before going on to the next
section.
The VHelp form is a simple one-screen help box. This screen not only displays help tips on using the Voice Phone, it
also speaks those tips to the user. Refer to Figure 35.5 and Table 35.4 for help in laying out the form.
The only code needed for the form is the Form_Load event and the one-line cmdOK_Click event. Listing 35.31
shows both these routines. Add this code to your form.
That's it for the VHelp form. Save the form as VHELP.FRM and update the project (VPHONE.VBP) before moving on
to the last coding section of the project.
The VAbout form shows the standard application information. This information is read from the properties of the App
object. You set these properties using the File | Make EXE | Options menu selection. Table 35.5 shows the
App object properties and their settings.
After setting these properties, you're ready to lay out the form and add the code. Refer to Figure 35.6 and Table 35.6 for
the size and position of the controls on the form.
After laying out the form, you need to add code to the Form_Load and Command1_Click events. Listing 35.32
shows all the code you need for the form.
Notice the use of the App object properties to fill in the Label control. This is a great way to provide up-to-date
application version information for your projects.
Save the form as VABOUT.FRM and update the project file (VPHONE.VBP). That's the last of the coding for this project.
Before moving on to the next section, compile the project and check for any errors. Once you're sure all the bugs have
been ironed out, you're ready to test your Voice Phone.
When you first start Voice Phone, you'll get a short friendly greeting ("Hello. Welcome to Voice Phone."). Once the
program has started, several voice commands have been registered with the Windows operating system. You can view
these available commands by asking the workstation, "What can I say? "or by clicking the Microsoft Voice icon in the
system tray and selecting "What can I say?" from the context menu. Figure 35.7 shows what your display should look
like.
New data table records can be added by pressing the New button or by speaking the New Record command. You can also
edit an existing record by selecting it from the list and pressing Edit or by speaking the command Edit <Name> where
<Name> is the name of the person whose record you wish to edit (see Figure 35.8).
You can place a call by selecting a name from the list and pressing the Place Call button. Or you can simply tell
Voice Phone to Dial <Name> where <Name> is the name of the person you wish to call. For example, if you wanted to
call Susan, you'd speak the command "Dial Susan." Voice Phone will look up Susan's phone number, and place the call
for you.
Since this project is using Assisted TAPI, the actual handling of the call is performed by the application on the
workstation that is registered to handle Assisted TAPI requests. If you have Microsoft Phone installed on your
workstation, you'll see Microsoft Phone appear and handle the call. If you have not installed any special telephony
support applications, the default dialer, DIALER.EXE, will appear.
Finally, you can get help by pressing the Help button. This will force Voice Phone to speak helpful tips to you. If you
want to view the help tips, select Help | Help from the menu (see Figure 35.9).
Summary
In this chapter you built an application that combined SAPI and TAPI services to create a "hands-free" telephone
interface. You learned how to register speech recognition and text-to-speech services, how to register Assisted TAPI
services, and how to use both services to access database information and place telephone calls using the registered
telephony application for handling Assisted TAPI service requests.
In the next chapter, you'll learn how to build an e-mail client that records audio messages instead of handling text
messages.
Chapter 36
CONTENTS
● Design Considerations
❍ Project Forms and Resources
❍ Coding tmView
❍ Coding tmNew
❍ Coding tmRead
The last integration project in the book combines MAPI services with audio recording. The Talk Mail project allows you
to record messages and then send them to others via e-mail. It's a bit like an asynchronous telephone conversation.
You will need a sound card, a microphone, and speakers (or headphones) in order to run this project. You will be able to
select one or more recipients, press the record button, record a message, and ship both the header data and the audio
binary file to any other person in the MAPI directory.
Note
In this version of the Talk Mail project, all MAPI addresses are resolved
against current names in your address book(s). You could add code to allow
any user with a universal e-mail address (for example,
yourname@internet.com) to receive the audio recordings. However,
you'll need to ensure that the file is sent and received properly by Internet
users before you add this feature.
You'll use the OLE Messaging library to implement the MAPI services. The audio recording will be handled using the
Windows Media Control Interface (MCI). This project will make use of the new Windows 95 controls for Visual Basic,
including the listview control, the toolbar control, and the statusbar control.
Warning
Because this project makes extensive use of the Windows 95 controls, you
will not be able to build it on a 16-bit Windows platform. You'll need to use
the 32-bit version of Visual Basic and run it on Windows 95 or Windows
NT.
Design Considerations
One of the advantages of the OLE Messaging library over the MAPI OCX tool is the added access to MAPI objects and
properties. In this project we'll define a new message type (IPM.Note.TalkMail) for all messages generated by our
Talk Mail application. The real benefit in this is that we'll be able to tell our Talk Mail client to pay attention only to the
messages generated by other Talk Mail clients. This way, when our new voice-mail client checks the MAPI inbox for
new messages, only the Talk Mail messages will appear on the screen.
The voice recordings, in the form of WAV files, will be shipped as an attachment with each MAPI message. The Talk
Mail client will automatically load the WAV file into the MCI form control, ready for playback (or further recording).
The Talk Mail client never tells the user that there is an attachment to the message. In fact, as far as the user is
concerned, there is no attachment!
Tip
All Talk Mail messages can be viewed from the standard Windows
Messaging or Microsoft Mail MAPI clients. When you use the standard
MAPI clients, you'll see the WAV file appear as an attachment to the text
message.
It may seem that we should use the SAPI services for the "talking" portion of the application. However, because the
primary focus of this application is voice, it's a better idea to use the pure voice recording instead of rendering text into
speech. Also, by using the MCI tool that ships with Visual Basic Professional and Enterprise Editions, you can provide
full record and playback services with very little coding. Therefore, this application will take advantage of users'
tendency to prefer actual voices over computer-generated sound.
The project has four forms and one code module. The code module holds routines for handling several OLE operations
along with global variable declarations and other support routines. The four forms for the project are
● tmView-This is the main viewer form. When the application first starts, users will see this form. This is where
the user performs MAPI logon and logoff, accesses the MAPI address book and creates new Talk Mail
messages, and reads or deletes existing Talk Mail messages.
● tmNew-This is the form for creating new Talk Mail messages. The heart of the form is the MCI audio control.
Users can manipulate the control like any other tape device (rewind, play, fast-forward, stop, and so on).
● tmRead-This form is almost identical to the tmNew form except that tmRead is for reading Talk Mail
messages sent to you by others.
● tmAbout-This is a tiny form displaying the basic About box information for the project.
You'll need to make sure you have the following references and custom controls added to your project:
Tip
You load the object libraries using the Tools | References menu
option. You load the OCX controls using the Tools | Custom
Controls menu option.
Now that you have a good idea of how the project works, let's jump right into creating the BAS module that will hold the
general support routines for the project.
The LibTalkMail module has seven subroutines and three functions. Most of these routines deal with MAPI service
requests but others include error handling and simple helper functions to manage the Talk Mail messages. This module
also has several public variables that are declared here and used throughout the program.
First, start a new Visual Basic project and make sure you have loaded the OLE Messaging library and the custom
controls listed in the preceding section.
Next, add a module to the project (Insert | Module) and set its Name property to LibTalkMail. Now add the
code in Listing 36.1 to the general declaration section of the module.
Listing 36.1. Public variables and objects for the Talk Mail project.
Option Explicit
'
' internal message pointer
Type MsgPtr
ID As String
Subject As String
End Type
'
' to track msgs
Public uMsgP() As MsgPtr
Public iMsgCount As Integer
Public cMsgID As String
'
' ole message libary objects
Public objSession As Object
Public objInBox As Object
Public objOutBox As Object
Public objMsgColl As Object
Public objAttach As Object
Public objAttachColl As Object
Public objRecipColl As Object
Public objRecip As Object
Public objMsg As Object
Public objAddrEntry As Object
'
' for MCI wav work
Public cWavFile As String
Public Const conInterval = 50
Public Const conIntervalPlus = 55
There are three sets of variables and objects in the code in Listing 36.1. First you see the creation of a user-defined type
(UDT) that will hold the unique MAPI message ID and the MAPI subject. This UDT will be used to perform quick
lookups of a selected record in the mail system. After defining the MsgPtr type, three variables are defined to hold
information about the message pool. The next set of declarations defines the OLE message objects. These will be used
(and re-used) throughout the program. Finally, the module contains a few variables for handling the MCI control.
Next you need to add the MAPI-related subroutines, the first of which is the MAPIStart routine. This routine is called
to begin a MAPI session. Add a new subroutine called MAPIStart (select Insert | Procedure) and enter the
code shown in Listing 36.2.
The MAPIEnd routine is called when the user wants to end the MAPI session completely. Most of the code in the
MAPIEnd routine is required to free precious workstation memory.
Tip
It is always a good idea to set Visual Basic objects equal to Nothing when
you are through with them. This frees workstation memory and can improve
overall workstation performance.
Add a new subroutine (MAPIEnd) and enter the code from Listing 36.3.
Two short MAPI routines are MAPIAddrBook and MAPIDeleteMsg. These two routines give the user access to the
MAPI address book and delete a selected message, respectively. Add these two subroutines to your project from Listings
36.4 and 36.5.
The MAPIDeleteMsg routine will remove a selected message object from the collection. Add the code from Listing
36.5 to your project.
Listing 36.5. Adding the MAPIDeleteMsg routine.
Next you need to add two routines that call the tmRead and tmNew dialog boxes to read or create new messages. These
two routines are almost identical. The only difference is the name of the form that is launched. Add the MAPINewMsg
subroutine and enter the code form Listing 36.6.
Note that this routine (and the MAPIReadMsg routine) both just hide the main form rather than unloading it. This is
necessary because the project has some values that must be shared between forms. Now add the MAPIReadMsg routine
shown in Listing 36.7.
Listing 36.7. Adding the MAPIReadMsg routine.
The next routine is the largest one in the module. The MAPISendMsg routine is the one that builds the MAPI message
object and makes sure it is sent off to the recipient(s). Add the MAPISendMsg subroutine to the project and enter the
code from Listing 36.8.
There are four main tasks performed by this routine. First, the message body is assembled. Note the use of IPM.Note.
TalkMail in the Type property. You'll use this value as a search criterion when you refresh the list of available Talk
Mail messages.
Next, the recorded WAV audio file is added to the package as an attachment. There are a couple of important points to
make here. First, it is important that you use ".WAV" as the suffix of the Name property of the attachment object. This
will make sure you can click the object, and Windows will call up the audio player automatically. Also, when you are
attaching data to the message (not just linking, but attaching), you need to invoke the ReadFromFile method to
actually load the selected file into the message package. Unlike the OCX controls, OLE does not do this for you.
Third, you need to add the recipients from the collection returned by the address book into the collection for the
message. This requires iterating through the source object and using the results to insert into the message's receipt object.
Lastly, the routine updates all its child objects and quietly sends the MAPI package to the MAPI spooler for delivery.
There are three general helper functions that need to be added to the LibTalkMail module. The first one is the
FindSubject function. This is used to return a unique MAPI message ID using the UDT you built at the start of this
section. Add the FindSubject function and enter the code shown in Listing 36.9.
Another very valuable helper function is the GetRecipients function. This routine accepts a collection of recipients
and extracts the display name into a single string for producing an onscreen list of the message recipients. After adding
the new GetRecipients function to the project, enter the code shown in Listing 36.10.
The last helper function you need to add to the LibTalkMail module is the ErrMsg function. This routine uses the
new Visual Basic 4.0 Err object to build an informative message concerning the name, type, and originator of the
message. Add the code in Listing 36.11 to your project.
Listing 36.11. Add the ErrMsg function.
That's all you need to add to the BAS module of the Talk Mail project. Be sure to save this file under the name
LIBTMAIL.BAS and the project under the name TALKMAIL.VBP before continuing.
The main form shows a list of all the Talk Mail messages in the user's inbox. It also has a set of toolbar buttons for
performing the basic MAPI actions of logon, logoff, viewing the address book, creating a new Talk Mail message,
reading an Existing Talk Mail message, and deleting a Talk Mail message. There are also buttons for adjusting the list
view from large icon, small icon, list, and detail formats.
The first step is to lay out the form's controls. Refer to Table 36.1 and Figure 36.1 for details on the layout of the
tmView form.
Tip
The menu layout makes extensive use of menu arrays. Be sure to pay close
attention to the index number values when building the menu. You'll use
these index values throughout the program.
Once you have the form controls and menus in place, save the form as TMVIEW.FRM and save the project (TALKMAIL.
VBP) before you go on to add the form code.
Coding tmView
Most of the code for the tmView form is needed to set up the toolbar, listview, and statusbar controls. There
are also several code sections for handling the menu selections and the basic form events. There are a few short events
for handling toolbar and list view clicks and there is one routine for requesting new messages from the MAPI service.
The Form_Load event calls the routines to build the form controls and then set some properties of the form itself. Enter
the code shown in Listing 36.12 into the Form_Load event.
Warning
The code example above uses the App.Path property to locate the icon
file. This will not work properly if you locate your project in a root
directory of a drive. It is recommended that you place all project files in a
single directory. If you place your files in a root directory, you'll need to
modify the code that uses the app.path object.
You also need to add code to the Form_Resize event. This code will resize the listview control to make sure it
fills the form. Enter the code from Listing 36.13 into your project.
Three of the controls (listview, toolbar, and statusbar) require extensive setup. While this could be done at
design time using the custom property boxes, it is easier to modify the properties if you build the controls at run-time.
Add a new subroutine called BuildStatusBar and enter the code shown in Listing 36.14.
With StatusBar1.Panels
.Item(1).Style = sbrText
.Item(2).Style = sbrCaps ' Caps lock
.Item(3).Style = sbrIns ' insert
.Item(4).Style = sbrDate ' date
.Item(5).Style = sbrTime ' time
End With
'
StatusBar1.Panels(1).Text = "Talk Mail" & Space(50)
StatusBar1.Style = sbrNormal
'
End Sub
The code in Listing 36.14 sets up a status bar that will show the status of the CAPS and INS buttons, the date and time,
and reserve some space for a status message.
Next, add a new subroutine called BuildListView and enter the code shown in Listing 36.15.
The code in Listing 36.15 performs several tasks. First, columns are defined for the detail view. Next, two image
controls are initialized for the large and small icon views. Finally, those two image controls are bound to the listview
control so that the list view knows which icons to use when displaying the contents.
The last of the "build" routines is the BuildToolBar routine. This is the most involved of the three because it contains
fourteen buttons (some of them separators) with all their icons, tooltips, and keys. Add the new subroutine
(BuildToolBar) and enter the code shown in Listing 36.16.
Note
This routine refers to several icon files. These icons can be found on the CD-
ROM that ships with this book. They should have been copied to your local
drive when you installed the CD-ROM. Make sure you copy them to your
project directory before you run the project.
There are four main menu branches: File, Message, View, and Help. Each branch is built as a menu array. You
need to add code to handle user selections for each menu branch.
First, add the code shown in Listing 36.17 to the mnuFileItem_Click event.
Next, add the code from Listing 36.18 to the mnuMsgItem_Click event.
The next code section deals with adjustments in the list view options. Add the code from Listing 36.19 to the
mnuViewItem_Click event.
The last menu code is for the mnuHelpItem_Click event. Enter the code from Listing 36.20 into your project.
The last control event code you need to add is the code that handles the toolbar selections. This code looks very similar
to the menu event code. In this case, the user selection is determined by the value of the Button.Key property. Enter
the code from Listing 36.21 into the tbrMain_ButtonClick event.
The last set of code routines for the tmView form is the code that is executed for various control events. There are three
events for the list control. These events adjust the sorting order, collect the subject of the selected item in the list, and
launch the Talk Mail message reader. Add code for the three routines shown in Listing 36.22.
That's the end of the code routines for the tmView form. Be sure to save the form (TMVIEW.FRM) and the project
(TALKMAIL.VBP) before you continue.
The tmNew and tmRead forms are where the real action happens. These forms present the user with some input
controls for selecting recipients, setting a subject line, and entering a supporting text message. The center of the form is
the MCI control. This can be used for playback and recording of the audio WAV file.
First, refer to Figure 36.3 and Table 36.3 for the layout of the tmNew form.
The tmNew form also has a short menu. Refer to Figure 36.4 and Table 36.4 for laying out the form menu.
Now is a good time to save the new form (TMNEW.FRM) and the project (TALKMAIL.VBP) before you add the code to
the form.
Coding tmNew
Most of the code for the tmNew form is needed to handle MCI control events. However, there are also menu and button
events that need to be addressed.
Option Explicit
'
Dim CurrentValue As Double
This variable is used to keep track of the progress of the audio playback.
Next, add the Form_Load event code shown in Listing 36.24. This code sets some basic form properties, creates a
temporary filename for the audio file, and calls the HandleOpen routine to establish the audio WAV file.
Listing 36.24. Coding the Form_Load event.
Warning
The code in Listing 36.25 refers to a file called "xxx.wav." This is an empty
WAV format audio file that is used as a "starter" file for each new message.
This file can be found on the CD-ROM that ships with this book. If you
cannot find the file, you can make a new one using the WAV audio applet
that ships with Windows or the one that came with your sound card.
Listing 36.25 shows a short bit of code for the Form_Unload event. Add this to your project.
The next big chunk of code is for the HandleOpen subroutine. This routine initializes the MCI control and clears the
scrollbar values. Add the code from Listing 36.26 to your form.
Listing 36.26. Adding the HandleOpen routine.
MCI_ERROR:
MsgBox ErrMsg(Err), vbCritical, "frmTMNew.HandleOpen"
Resume MCI_EXIT
MCI_EXIT:
Unload Me
End Sub
The HandleOpen routine calls another support routine-the SetTiming routine. This short routine establishes the total
length of existing recordings and is used to set the start and stop limits of the scrollbar. Add the code from Listing 36.27
to your form.
Next you can add the code to handle the menu array. This code allows the user to save the recorded file to disk, send the
message, and exit the form without sending the message. Add the code from Listing 36.28 to your form.
There are two command buttons on the form in a control array-Cancel and OK. The code in Listing 36.29 should be
added to the cmdNewMsg_Click event.
Notice the call to the MAPISendMsg routine. You built this routine in the LibTalkMail module at the start of the
chapter.
There is one other button on the form-the Send To... button. When the user presses this button, the MAPI address
book appears. When the user is finished selecting addresses, a recipients collection is returned. This is the list of people
who will receive the message.
Note
In this program users can send messages only if they use the To: option
and not Cc: or Bcc: This was done to simplify the project. If you want,
you can modify the project to address messages to courtesy copy and blind
courtesy copy recipients.
Once the collection is returned, the GetRecipients function is used to pull the names out of the collection and insert
them into the label control on the form. Add the code shown in Listing 36.30 to the cmdAddrBook_Click event.
The last set of code routines for the tmNew form are for the MCI multimedia control. First, add the code in Listing 36.31
to the MMControl1_StatusUpdate event.
Listing 36.31. Coding the MMControl1_StatusUpdate event.
Hscroll1.value = value
End Sub
This code is used to compute playback progress and update the scrollbar control on the form.
There are four other code events for the MCI control. These are executed whenever the user presses Pause, Play,
Rewind, or Stop. Listing 36.32 shows the code for these four events.
MMControl1.Command = "Prev"
End Sub
That's the end of the code for the tmNew form. Save the form (TMNEW.FRM) and project (TALKMAIL.VBP) before
coding the tmRead form.
The tmRead form is almost identical to the tmNew form. This form is used to read Talk Mail messages sent to the user.
For this reason, most of the controls have been disabled and the form is basically in a "read-only" mode.
Normally, users would read a message and then generate a reply to the sender or forward the message to another party.
For this example, no reply buttons have been placed on the form. This has been done to keep the project simple and to
focus on the integration of MAPI and MCI services. You can add these options later if you wish.
Add a new form to the project and lay out the controls as shown in Figure 36.5 and in Table 36.5.
After laying out the form controls, you can add the form menu. Refer to Figure 36.6 and Table 36.6 for details on the
tmRead menu.
After building the form, save it as TMREAD.FRM before you go on to add the code.
Coding tmRead
As mentioned earlier in the chapter, the tmNew and tmRead forms are very similar. In fact, the two largest sections of
form code, the HandleOpen and the MMControl1... events, are the same in both projects. As long as you are
careful, you can copy the code from the tmNew form onto the tmRead form. Do this by bringing up the tmNew form,
highlighting all of the HandleOpen routine, select Edit | Copy, and then move to the tmRead form and use
Edit | Paste to place the code in the general declarations section.
The following code sections from tmNew can be copied to tmRead using the same technique:
● OpenHandle()
● MMControl1_PauseClick()
● MMControl1_PlayClick()
● MMControl1_PrevClick()
● MMControl1_StatusUpdate()
● MMControl1_StopClick()
● SetTiming()
Once you've successfully copied these routines from tmNew to tmRead you need to add just a few more routines to the
tmRead form.
First, add the following lines to the general declaration section of the form.
Option Explicit
'
Dim CurrentValue As Double
Next, add the Form_Load event code shown in Listing 36.33.
This code is slightly different from the code in the Form_Load event of tmNew. Here, you want to open the selected
message object and extract the audio WAV attachment, save it to a temporary file, and then place that audio file into the
MCI control for playback (using the OpenHandle routine).
Next, add the code for the Form_Unload event shown in Listing 36.34.
Listing 36.34. Coding the Form_Unload event.
Because this form only has a Close button, you need to add one line
of code to the cmdNewmsg_Click event:
Private Sub cmdNewMsg_Click(Index As Integer)
Unload Me
End Sub
]Next, you need to add a line of code to the txtBody control to prevent users from typing the read-only form.
Tip
You may notice that the txtBody control's background color was set to
light gray to make it look like a label control instead of an input control.
Under the Windows GUI standards, "actionable" controls have a white
background and read-only controls have a light gray background.
The only form code left is the code that handles the user's menu selections. Add the code shown in Listing 36.35 to your
form.
The tmABout box is a simple About dialog box for the project. Figure 36.7 and Table 36.7 show the layout information
for the form.
You need to add just a little bit of code to the Form_Load event to complete the tmAbout form. Listing 36.36 shows
the code you should add to the form.
Now save the form (TMABOUT.FRM) and the project (TALKMAIL.VBP). You are now finished coding and ready to test
the program.
First, you need to compile Talk Mail and save it to disk, but before you compile it, you need to add a bit of information
to the App object properties. These properties will be displayed in the application's About box.
Select File | Make EXE | Options to bring up the dialog box for setting the App objects properties. Refer to
Table 36.8 and Figure 36.8 for setting the properties.
Once you set these properties, press OK and then compile the project. When you are able to compile the project without
errors, you're ready to start testing Talk Mail.
Tip
It is a good idea to compile the Talk Mail project before running it. The
process of compiling will point out any coding errors and the resulting
program will run faster than in design mode.
When you first start Talk Mail, you see the populated button bar and an empty listview control. Press the green
stoplight button (or select File | Log In from the menu) to log into MAPI and collect any Talk Mail messages you
have waiting in your inbox (see Figure 36.9).
The first time you log into Talk Mail, you'll see no messages for you. You can fix this by using Talk Mail to send
yourself the first message. To do this, press the microphone toolbar button or select Message | New from the menu.
You'll see the Talk Mail compose form ready to record your message (see Figure 36.10).
The form comes up with a default subject line and a notation in the text message area. You can press the record button
to begin recording your message and press stop when you are finished. You can also press playback, pause,
rewind, and fast-forward to review your message. You can even append additional information to the end of your
message if you wish.
You must select at least one recipient before you can send your message. To address the Talk Mail message, press the
Send To... button at the top left of the form. You'll see the MAPI address dialog box appear (see Figure 36.11).
For your first message, address it to yourself. You can then "read" this message using Talk Mail. After addressing the
message, press the Send button at the bottom of the form or select File | Send Message from the menu. You'll
receive notification that the message was sent and then you will be returned to the list view form.
Now you can select File | Scan InBox from the menu or press the inbox button to re-read your inbox. Your
message should now appear in the list view. You can listen to your message by selecting the item and pressing the read
button (it's the ear!), or by selecting Message | Read from the menu. You'll see the read form and be able to use the
MCI control to play back the recording (see Figure 36.12).
You can also use the toolbar to adjust the list view the same way you can adjust the Windows 95 Explorer views. Figure
36.13 shows the Talk Mail client in large icon view while showing the About box.
Summary
In this chapter, you learned how to combine the MAPI and Media Control Interface (MCI) services to create a voice-
mail client. In the process you learned about declaring your own MAPI message type and about how to use the Windows
95 common controls.
This project is handy, but not quite complete. If you wish to use this application as more than a demonstration, you'll
need to add the ability for users to reply to and forward received messages. You may also want to consider deploying
this as a Microsoft Exchange form instead of as a standalone MAPI client. That way, all Microsoft Exchange users could
install the form and use it throughout your organization.
Chapter 37
Integration Summary
CONTENTS
● Design Issues
● The FaxBack Application
● The Voice Phone Application
● The Talk Mail Project
● Some Final Remarks
In this last part of the book, you've had the chance to build three sample applications that combine two or more of the
Windows Extension services covered in this book. You also reviewed some of the common design issues that you'll face
when building applications that require access to multiple extension services. The sections below briefly summarize the
material covered in the last four chapters.
Design Issues
In Chapter 33, "Design Considerations for Integrated Communications Applications," you learned some of the
advantages and disadvantages of deploying software that uses the MAPI, SAPI, and TAPI extension services for
Windows operating systems. The following general points were discussed:
● First, remember the Rule of Complexity. Where possible, try to limit the number and depth of service
extensions used in a single application. With added services comes added complexity-both for the user and the
programmer.
● Consider the pros and cons of using native API calls versus employing high-level third-party add-on tools to
provide access to extension services. The native API calls may be faster, but they will also require more coding
and maintenance for you and your staff. Building applications with third-party tools can be more efficient as
long as the tool is used correctly and is well supported and documented by its authors.
● Also, when adding new extensions to your applications, consider the advantages of building simple stand-alone
versions of the target application instead of adding features to existing applications. There are times when a
stand-alone solution is most efficient. However, if you need to deploy MAPI, SAPI, or TAPI services to a large
number of people who already have products they know and use often, it may be simpler to distribute add-ins or
extensions instead of attempting to replace well-established user tools.
● As a general rule, if your application needs to distribute information to a wide number of locations on demand,
mostly in ASCII text format, your best bet is to use MAPI services. If, however, you need to send binary data to
just a few sites on a regular schedule (daily or weekly, for example), you should consider building smaller,
more focused dedicated dialing applications that use the Comm API (or OCX).
● Client-side applications usually need only outbound calling services. It is quite possible that you can meet
client-side design goals using Assisted TAPI services and simple data/fax modems. If, however, you are
building applications that will act as telephony server devices-answering and routing incoming calls-you'll need
to use Full TAPI, and you will probably need a full-featured telephony card, too.
● Finally, current speech technology is more consistent when delivering TTS (text-to-speech) services than SR
(speech recognition) services. The SAPI system requires a hefty pool of memory (no less than 16MB is
recommended on the workstation). Also, SR services are not always accurate. It is best to offer SAPI services
as an additional, rather than primary service at this time. As the technology matures and users become more
accustomed to hearing TTS output and to speaking in a manner more easily understood by SAPI systems, you
can increase the use of speech services in your applications.
If you keep these general rules in mind, you'll save yourself a lot of headaches when it comes time to deploy multiple-
service applications.
In Chapter 34, "Building the FaxBack Application," you built a Visual Basic 4.0 project that combined TAPI and
MAPI services to create a dialup fax server. With this application running on a workstation, users can dial into the
workstation, receive prompts for entering their own FAX telephone number, and select a document number. Then, when
the user hangs up, the workstation formats and sends the selected FAX document to the user's FAX address.
This project used an evaluation version of Pronexus' VBVoice telephony control set. The VBVoice controls were used to
answer the incoming call, and prompt the caller through entering their FAX number and the selected document number.
Once this was done, the VBVoice controls turned the collected data over to the MAPI system for delivery of the selected
FAX.
The Simple Mail API (SMAPI) interface was used to provide access to the message services of Windows. The SMAPI
interface was chosen because it has a very small "footprint." While the MAPI.OCX controls and the OLE Messaging
library would also work fine, the SMAPI provided all the MAPI access that was needed for the FaxBack project. This
was a good example of how you can simplify programming and deployment by selecting the simplest implementation
model that fulfills the project requirements.
In Chapter 35, "Creating the Voice Phone Application," you used SAPI and TAPI services to create a true "hands-free"
telephone. With this program and a pc speaker phone, users can look up and dial telephone numbers by simply speaking
voice commands to the workstation. Users are able to initiate database adds, edits, and deletes, or issue a voice command
that will search the database for a name. Users can also tell the program to Dial Mike and Voice Phone will locate the
record in the database, pull up the phone number, place the call, and prompt the user to begin speaking.
As an added bonus, this program has audible responses to help requests, and speaks the names and telephone numbers of
selected records in the database. Even the About box is "read" to you.
Again, a simple TAPI interface was all that was needed for this project. Instead of adding Full TAPI services to the
application, only Assisted TAPI was implemented. This provided all the power and features needed to implement a
voice-activated outbound dialing program.
Chapter 36, "The Talk Mail Project," presented the last integration project in the book, in which you combined MAPI
services with an audio recording. The Talk Mail project allows you to record messages, and then send them to others via
e-mail. It's a bit like an asynchronous telephone conversation.
With Talk Mail you can select one or more recipients, press the record button, record a message, and ship both the
header data and the audio binary file to any other person(s) in the MAPI directory.
The OLE Messaging library was used to implement the MAPI services for this program. This offered the ability to
define a unique message class for audio e-mail. This way, the Talk Mail client can search incoming mail for audio
messages, and present only those to the user for review.
The audio recording was handled using the Windows Media Control Interface (MCI). The Visual Basic 4.0 MCI control
provided easy access to recording, storage, and playback facilities for WAV format audio files. Adding WAV support
for an e-mail client is as easy as adding the MCI control to a project.
This project also made use of the new Windows 95 controls for Visual Basic, including the listview control, the
toolbar control, and the statusbar control.
The material in this book covers the multiple implementations of MAPI services, SAPI services, and TAPI services for
32-bit Windows operating systems. Even though this book is no quick read, it does not cover the three important
Windows extension services completely. No single book could hope to accomplish that. The best way to learn about
each of these API sets in detail is to work with them in your own programs. Only through constant experimentation and
invention will you really learn the intricacies of these important Windows services.
It's also important to keep up with the latest developments regarding each of these API services. You can find lots of
pointers to online and printed resources in the appendixes at the back of this book. You'll even find a Web site dedicated
to supporting this book (iac.net/~mamund/CDGPage.htm).
I hope you'll find this book helpful in your quest to learn more about MAPI, SAPI, and TAPI implementations. I suspect
many of you have already been struck with new ideas on how you can use these services to create new and valuable
Windows applications. I also hope to hear from many of you as you discover new ways to bring speech, telephony, and e-
mail services to the desktop. You can write me at my e-mail address (MikeAmundsen@msn.com), or visit the Web
site. I look forward to hearing from you.
Good luck!
MCA
appendix A
MAPI Resources
CONTENTS
● Books
● Web Links
● Other Online Resources
● Books
● Web links
● Other on-line resources
Tip
For the most recent list of MAPI-related resources, including updated
books, Web links, other on-line resources, and MAPI software, use your
Web browser to connect to the Communications Developer's Guide home
page at:
www.iac.net/~mamund/mstdg/index.htm
Books
This is a sample list of books available at most large libraries. While this is not a definitive list, it is a good
representation of writings on the subject.
Note
A more complete listing of books, including publisher data, can be found in
the BOOKLIST table of the CDGLISTS.MDB database. You can use
Microsoft Access, Microsoft Query, or any Access/Visual Basic database
product to read this data file.
Web Links
This is a list of World Wide Web links on MAPI subjects. The list is constantly changing. An updated list is contained in
the MAPIWEB.htm data file on the companion CD-ROM. This file can be loaded into most Web browsers and used as a
launch document to connect to the associated links.
Note
A more complete listing of these links can also be found in the WEBLIST
table of the CDGLISTS.MDB database on the companion CD-ROM. You
can use the database table to search for specific vendors, products, and so
on.
Other Online Resources
Below is a list of MAPI-related topics that you can browse through on CompuServe.
Below is a list of MAPI-related newsgroups maintained by Microsoft at their msnews.microsoft.com news server:
microsoft.public.exchange.admin
microsoft.public.exchange.applications
microsoft.public.exchange.clients
microsoft.public.exchange.connectivity
microsoft.public.exchange.misc
microsoft.public.exchange.setup
microsoft.public.mail.admin
microsoft.public.mail.connectivity
microsoft.public.mail.misc
microsoft.public.messaging.misc
An extensive list of topics for the Microsoft Network can be found in the MAPIMSN folder on the CD-ROM. This
folder contains a list of MSN shortcuts. If you are an MSN subscriber, you can click these icons to connect directly to the
topic area on MSN.
appendix B
SAPI Resources
CONTENTS
● Books
● Web Links
● Other Online Resources
● Software and Hardware Resources
● Books
● Web links
● Other online resources
● Software and hardware resources
Tip
For the most recent list of SAPI-related resources, including updated books,
Web links, other online resources, and SAPI software and hardware, use
your Web browser to connect to the Communications Developer's Guide
home page at
www.iac.net/~mamund/mstdg/index.htm
Books
Table B.1 provides a sample list of books available at most large libraries. It is not a definitive list, but it is a good
representation of writings on the subject of SAPI.
Note
A more complete listing of books, including publisher data, can be found in
the BOOKLIST table of the CDGLISTS.MDB database. You can use
Microsoft Access, Microsoft Query, or any Access/Visual Basic database
product to read this data file.
Web Links
Table B.2 lists World Wide Web links related to SAPI. This list is constantly changing. An updated list is contained in
the SAPIWEB.htm data file in the RESOURCE folder on the companion CD-ROM. This file can be loaded into almost
any Web browser and used as a launch document to connect to the associated links.
The following is a list of SAPI-related topics that you can browse on CompuServe:
An extensive list of topics for The Microsoft Network can be found in the SAPIMSN folder on the CD-ROM. This
contains a list of MSN shortcuts. If you are an MSN subscriber, you can click these icons to connect directly to the topic
area on MSN.
Table B.3 lists software and hardware vendors who are currently providing, or have pledged to provide, SAPI-compliant
software and/or hardware products. A more complete list of vendors, including contact names, addresses, and phone
numbers, can be found in the PRODUCTS table of the CDGLISTS.MDB database on the companion CD-ROM. You can
use the data table to perform searches for selected products or vendors.
TAPI Resources
CONTENTS
● Books
● Web Links
● Other Online Resources
● Software and Hardware Resources
● Books
● Web links
● Other online resources
● Software and hardware resources
Tip
For the most recent list of TAPI-related resources, including updated books,
Web links, other online resources, and SAPI software and hardware, use
your Web browser to connect to the Communications Developer's Guide
home page at:
www.iac.net/~mamund/mstdg/index.htm
Books
Table C.1 lists books on TAPI that are available at most large libraries. This is not a definitive list, but it is a good
representation of writings on the subject.
Note
A more complete listing of books, including publisher data, can be found in
the BOOKLIST table of the CDGLISTS.MDB database on the companion
CD-ROM. You can use Microsoft Access, Microsoft Query, or any Access/
Visual Basic database product to read this data file.
Web Links
Table C.2 provides a list of World Wide Web links on the subject of TAPI. This list is constantly changing. An updated
list is contained in the TAPIWEB.htm data file in the RESOURCE folder on the companion CD-ROM. This file can be
loaded into almost any Web browser and used as a launch document to connect to the associated links.
Note
A more complete listing of these links can also be found in the WEBLIST
table of the CDGLISTS.MDB database on the companion CD-ROM. You
can use the database table to search for specific vendors, products, and so
on.
Table C.3 lists TAPI-related topics that you can browse on CompuServe.
An extensive list of topics for The Microsoft Network can be found in the TAPIMSN folder on the CD-ROM. This
contains a list of MSN shortcuts. If you are an MSN subscriber, you can click on these icons to connect directly to the
topic area on MSN.
Table C.4 lists software and hardware vendors who are currently providing, or have pledged to provide, TAPI-compliant
software and/or hardware products. A more complete list of vendors, including contact names, addresses, and phone
numbers can be found in the PRODUCTS table of the CDGLISTS.MDB database on the companion CD-ROM. You can
use the data table to perform searches for selected products or vendors.
The CD-ROM that ships with this book has a wealth of information that will be useful to you when you begin
developing your own applications using MAPI, SAPI, and TAPI services. Along with the source code from the book, the
CD-ROM contains pointers to additional developer resources and copies of Microsoft files that can be redistributed with
your applications. There are also a number of third-party tools and demos for you to test.
Tip
Even if you don't plan on working through all the code examples, it is a
good idea to run the installation routine on the CD-ROM. This routine will
alert you to any late-breaking news regarding the book or any related
software.
The CD-ROM that ships with this book is divided into four main sections:
● Source code libraries-This section contains folders that correspond to the chapters in the book. Each chapter
that contains source code exercises is represented here. If you are interested in a particular set of code, but don't
want to have to type it in from scratch, look in this portion of the CD-ROM for the code you need.
● Developer resources-This section contains folders for each API set (MAPI, SAPI, and TAPI). In turn, these
folders contain information on third-party vendors (including e-mail addresses), available development tools,
and even pointers to Internet Web sites and other online resources. Although much of this material is covered in
the book's appendices, check here for the most recent version of the resource lists. There is even an Access
database that contains names, phone numbers, and addresses of third-party vendors that provide tools for
MAPI, SAPI, and TAPI developers.
● Microsoft redistribution libraries-This section of the CD-ROM contains a set of Microsoft files that can be
redistributed with your API applications. You will need to have access to the MSDN Professional Level CD-
ROM in order to develop some of the projects in this book, but you do not need MSDN to distribute completed
API applications.
● Third-party tools and product demos-This section contains a wealth of product demos, shareware, and freeware
tools that can be handy when developing your applications. Some of the material here is free for your use, other
material is for evaluation only. Please be sure to honor the copyrights on all material on the CD-ROM.