—S——
Web Services
Development
with Delphi
Peter Darakhvelidze
Eugene Markov
altCopyright (c) 2002 by A-LIST, LLC
All rights reserved.
No part of this publication may be reproduced in any way, stored in a retrieval system
of any type, or transmitted by any means or media, electronic or mechanical, including,
but not limited to, photocopy, recording, or scanning, without prior permission in writing
from the publisher.
A-LIST, LLC
295 East Swedesford Rd.
PMB #285
Wayne, PA 19087
702-977-5377 (FAX)
[email protected]
http://www alistpublishing.com
This book is printed on acid-free paper.
All brand names and product names mentioned in this book are trademarks or service
marks of their respective companies. Any omission or misuse (of any kind) of service marks or
trademarks should not be regarded as intent to infringe on the property of others.
The publisher recognizes and respects all marks used by companies, manufacturers, and
developers as a means to distinguish their products.
Web Services Development with Delphi
By Peter Darakhvelidze, Eugene Markov
ISBN: 1-931769-08-7
Printed in the United States of America
02037654321
A-LIST, LLC titles are distributed by Independent Publishers Group and are available for
site license or bulk purchase by institutions, user groups, corporations, etc.
Book Editor: Jessica Mroz
LIMITED WARRANTY AND DISCLAIMER OF LIABILITY
A-LIST, LLC,, INDEPENDENT PUBLISHERS GROUP AND/OR ANYONE WHO HAS BEEN INVOLVED IN THE WRITING,
CREATION OR PRODUCTION OF THE ACCOMPANYING CODE (‘THE SOFTWARE") OR TEXTUAL MATERIAL IN THE
BOOK, CANNOT AND DO NOT WARRANT THE PERFORMANCE OR RESULTS THAT MAY BE OBTAINED BY USING
THE CODE OR CONTENTS OF THE BOOK. THE AUTHORS AND PUBLISHERS HAVE USED THEIR BEST EFFORTS TO
ENSURE THE ACCURACY AND FUNCTIONALITY OF THE TEXTUAL MATERIAL AND PROGRAMS CONTAINED
HEREIN; WE HOWEVER MAKE NO WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, REGARDING THE
PERFORMANCE OF THESE PROGRAMS OR CONTENTS.
THE AUTHORS, THE PUBLISHER, DEVELOPERS OF THIRD PARTY SOFTWARE, AND ANYONE INVOLVED IN THE
PRODUCTION AND MANUFACTURING OF THIS WORK SHALL NOT BE LIABLE FOR DAMAGES OF ANY KIND
ARISING OUT OF THE USE OF (OR THE INABILITY TO USE) THE PROGRAMS, SOURCE CODE, OR TEXTUAL
MATERIAL CONTAINED IN THIS PUBLICATION. THIS INCLUDES, BUT IS NOT LIMITED TO, LOSS OF REVENUE OR
PROFIT, OR OTHER INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THE PRODUCT.
THE USE OF "IMPLIED WARRANTY” AND CERTAIN “EXCLUSIONS” VARY FROM STATE TO STATE, AND MAY NOT
APPLY TO THE PURCHASER OF THIS PRODUCT.Contents
a
Introduction,
PART I. COM and COM+ APPLICATIONS.
Chapter 1: COM Mechanisms in Delphi
Basic Concepts
COM Objects in Delphi
Class Factories in Delphi
COM Servers in Delphi
Type Libraries in Delphi
Simple COM Objects within In-Process Servers
Summary
19
25
29
31
34
53
Chapter 2: Automation,
Basic Concepts of Automation
Automation Implementation in Delphi.
Automation Object
Example of an Automation Application
Summary
55
56
60
63
16
81
Chapter 3: ActiveX Components
How ActiveX Controls Work
Implementing ActiveX Components in Delphi,
Using Preprepared ActiveX Components
Developing Custom ActiveX Components
Summary
83
85
91
94
100
108IV___ Contents
Chapter 4: COM+ Technology (Microsoft Transaction Server)
How MTS Works
Creating MTS Applications in Delphi
Testing and Installing MTS Components
Optimizing Work with MTS
Example of a Simple Transactional Object
Summary
PART II: DATA ACCESS TECHNOLOGIES
Chapter 5: The Architecture of Database Applications.
General Structure of a Database Application
Datasets
Indexes
Parameters of Queries and Stored Procedures
Mechanisms for Managing Data.
Searching Data
Filtering Data
Using Bookmarks
Fields
Field Objects.
Summary
Chapter 6: dbExpress Technology
Accessing dbExpress Data.
Data Access Drivers
Connecting to a Database Server
Managing Datasets
Transactions
Using Dataset Components
The TSQLClientDataSet Component
Data Editing Methods
dbExpress Interfaces
109
lll
119
129
132
133
140
141
143
146
153
169
174
180
182
183
185
186
187
202
203
205
206
207
212
215
216
224
228
233Contents
Debugging Applications with dbExpress Technology
Distributing dbExpress Applications
Summary
Vv
237
239
240
Chapter 7: Using ADO with Delphi
ADO Basics
241
242
ADO Providers
Realizing ADO in Delphi
The TADOConnection Component
ADO Datasets,
249
250
252
266
ADO Commands
ADO Error Object
Developing a Sample ADO Application
Summary
283
286
286
292
PART III: DISTRIBUTED DATABASE APPLICATIONS
Chapter 8: DataSnap Technology. Remote Access Mechanisms.
Structure of a Delphi Multi-Tier Application
Three-Tier Delphi Applications
Application Servers
DataSnap Remote Access Mechanism
The TSocketConnection Component
Additional Components — Connection Brokers.
Summary
293
295
297
299
300
303
305
311
314
Chapter 9: An Application Server
Application Server Architecture
The JAppServer Interface
Remote Data Modules
Data Providers
The /ProviderSupport Interface
Registering Application Servers
315
316
318
321
328
332
333Vi___ Contents
Creating a Sample Application Server
Summary
Chapter 10: A Client of a Multi-Tier Distributed Application
Client Application Architecture
Client Datasets
The TClientDataSet Component.
Aggregates
Nested Datasets
Additional Properties of Client Dataset Fields
Handling Errors
Creating a Sample Thin Client
Summary
PART IV: THE BASICS OF DEVELOPING
AN INTERNET APPLICATION
Chapter 11: Sockets
Introduction to Network Architecture
Handling Sockets with Delphi
Summary
Chapter 12: Cryptographic Protection on the Internet
Fundamental Terms and Concepts in Cryptography
Digital Signatures, Certificates, and How to Use Them
Introduction to CryptoAPI
Implementing a Secure Network Connection with Internet Protocols
Summary
Chapter 13: Threads and Processes
Threads Overview
The TThread Class
Example of a Multi-Threaded Application in Delphi
334
338
339
341
342
344
354
358
360
360
363
368
369
371
373
388
398
399
400
406
413
423
432
433
434
441
445Contents
Thread Synchronization Problems
Means of Thread Synchronization
Local Data of a Thread.
vil
450
451
459
How to Avoid the Concurrent Starting of Two Instances of One Application 460
Summary 461
PART V: XML DATA IN DISTRIBUTED APPLICATIONS 463
Chapter 14: Using XML 465
Understanding XML. 466
Fundamentals of XML Syntax 469
Document Object Model 475
Implementing DOM in Delphi 6 485
Summary 507
Chapter 15: Using Data in XML Format, 509
Converting XML Data 510
The XML Mapper Uti 514
Converting XML Data in Distributed Applications 519
Using XML Data in Distributed Applications 523
Sample Application Using XML Data 529
Summary 533
PART VI: DISTRIBUTED APPLICATIONS AND WEB SERVICES ______—535
Chapter 16: Web Server Applications. WebBroker Technology 537
Publishing Data on the Internet. Web Servers 539
Types of Web Server Applications 540
Introduction to the CGI and ISAPI Interfaces, 541
Structure of a Web Server Application in Delphi 543
HTML Pages in Web Server Applications 554
Cookies: 562
Using Databases 563
Example of a Simple Web Server Application 572
Summary 579Vill_ Contents
Chapter 17: The SOAP Protocol and Web Services. The Client Part
Why SOAP?
SOAP: The Principles of Its Operation.
Architecture of Web Services in Delphi
Web Service Client
Summary
Chapter 18: WEB Service Server. Interoperability Issues
Creating a Test Example — the SimpleEcho Service
Purposes and Options of the Server Part Components.
SOAP Development Tools: The Microsoft Approach.
Using the SOAP Trace Utility
Summary
Chapter 19: WebShap Techology
Structure of a WebSnap Application.
Designing a WebSnap Application
Designing the Interface and Handling Data
Authentificating Users
Using XML and XSL
Summary
On the CD.
Index
581
582
584
593
594
609
611
612
616
625
633
636
637
638
659
663
675
679
685
687
689Introduction
a
The first time developers came across Delphi 6, they probably noticed that the
majority of new capabilities and components were geared towards creating distrib-
uted web applications. And this is surely not just because Borland was following
some fleeting fashion, but rather because circumstances truly necessitated these
additions.
It’s interesting to follow the evolution of the developer community's ideas con-
cerning the capabilities of the Internet. Their initial notion of the Global Network
as a "technological toy" that had huge possibilities not only for communications,
but for the world of business as well, spurred the inevitable Internet boom. Later,
a stir caused by the wrong people trying to use the Internet for the wrong purposes
couldn't help but produce some disappointment and backsliding. Nonetheless, the
sharp decrease in the interest in the Internet as a multi-purpose and global means
of business communication, and the subsequent stagnation, somewhat helped in
bringing about the realization that the Internet could not only be used as an ex-
tremely important technological tool, but as a system-forming factor for planning
modern distributed applications.
The entire history of the development of business applications — from mainframes
to the modern state of affairs — can be seen as a sequence of steps, each of which
made applications more global. Certainly, this opinion might seem a bit one-sided,
and not necessarily what you might call unbiased historical research, but within the
framework of this book it is completely justified, since it allows us to better focus
on the issue at hand. In this context, the widespread use of the Internet as a net-
work environment for distributed business applications is an appropriate and solidly
grounded solution.
The popularity of the Internet as a system-forming basis for distributed applica-
tions is also based on the very recent appearance of the XML language, which
allows for the unification of the data structure presentation process. Presenting
XML data is done using a different language — eXtended Stylesheet Language
(XSL), as well as a modification of it — XSL Transformations (XLST). This is very2 Introduction
important, since developers now have a universal means of presenting these data in
the sea of various client applications, web browsers, protocols, and their extensions.
In this book, we try to illustrate in detail all the basic aspects of developing web
applications using the abundant means for this that are provided in the Delphi
development environment. However, this is not the main advantage (or rather not
the only main advantage) of using Delphi as a development environment for cre-
ating web applications. Yet another important factor is that Delphi allows you to
go through the entire development cycle for complex applications with a distrib-
uted infrastructure using various technological solutions.
Indeed, the variety of technologies and components allows you to create applica-
tions for any kind of customer and to be able to comply with most of their wishes.
So as not to leave our claim unsubstantiated, let's take a little tour of the modern
Internet technologies that will be covered in this book.
COM and COM+
‘We'll begin with a brief survey of the Component Object Model (COM) technol-
ogy and its child technologies. Right about now, the experienced reader is probably
groaning something along the lines of "Oh no, not COM again!". However, we
thought that it was absolutely necessary to touch on this issue, even though COM
and similar technologies seem to have relatively little to do with web applications.
Still, de facto, many "100% Internet" technologies use COM capabilities as the
final link in data exchange, something akin to the “last mile" of the network infra-
structure.
Of no less importance is the COM+ technology (and its closely related ancestor,
the good old Microsoft Transaction Server), which helps solve scale problems for
applications on a Windows 2000 platform.
DataSnap
In discussing web applications, we shouldn't forget about the fact that the Internet
is without a doubt wonderful and promising, but still only a "means of transport"
for the data received by clients of distributed business applications. Therefore, the
features of the architecture of such applications and issues of their interaction with
database servers are still relevant.Introduction
The DataSnap technology, having inherited and developed the capabilities of the
MIDAS technology unfamiliar to developers who work in Delphi, is intended for
solving just such problems. In particular, this concerns multi-layer distributed ap-
plications and means of accessing various data sources.
Here, we must say a few words about the data access object model ActiveX Data
Objects (ADO), which has become the de facto standard in this area. It allows you
to solve the problem of distributing and installing software of intermediate level
data access for the Windows platform.
XML
Everyone has heard the term "XML," and many know the language itself, but far
from everyone realizes the practical possibilities this language holds. XML stands
for eXtensible Markup Language. It is currently being developed by the World
Wide Web Consortium (W3C, www.w3c.org/XML). This consortium includes
representatives from the largest vendors, research organizations, and educational
institutions.
XML is called "extensible" because it gives anyone the opportunity to define and
build their own system of tags to describe a particular area of expertise.
Besides the irrefutable value that XML has in and of itself, XML is also the basis
upon which many other technologies used in Delphi for creating web applications
are built.
SOAP
Since the time web applications came into being up until the present day, web
application developers have been running into the problem of web application
interoperability, some of which function only on different software and hardware
platforms. There have been many proposed solutions to this problem. For example,
the Common Object Request Broker Architecture (CORBA) allows you to create
full-fledged multi-platform solutions, but requires that special client software be
installed. If you want to distribute solutions based on VisiBroker — the object re-
quest broker that comes with Delphi — you have to make sure to acquire the
proper license for it. (By the way, CORBA is supported in Delphi, and the set of
corresponding components, tools, and utilities are included in the DataSnap tech-
nology.)4 Introduction
The Simple Object Access Protocol (SOAP) is supported by all the main platforms
on the Internet, does not require a complicated client part, and provides for safe
data exchange over the Net. SOAP came about in order to solve the common
problem of interoperability of applications that work on various platforms. In the
two short years of SOAP's existence, it has already been approved by the majority
of the IT community.
The set of Delphi components that implement SOAP allows you to construct
complete client and server parts of a web application. Besides this, we will examine
the Microsoft SOAP Toolkit — a tool for publishing Automation objects as web
services — and we will also discuss issues of creating web services.
WebSnap and WebBroker
And finally, Delphi developers were able to unite all the main steps of creating
a web application into one whole technological solution. The WebSnap and
WebBroker technologies simplify and accelerate going through the “routine” op-
erations for creating an application's user interface, data access, and user authenti-
cation. Besides which, developers will undoubtedly appreciate the ease with which
VBScript and JScript scripts can be included into source code.
The widespread use of HTML and XML templates is unquestionably an advantage
of WebSnap, since it makes the modernization of finished applications in the most
fickle and changeable area — the user interface — much simpler.
Cryptography and Data Security
The Internet network is by definition an open, decentralized, and minimally con-
trollable one. In the early stages of Internet programming, the problem of data se-
curity was not a concern of very many, and talk of creating industry standards in
this area was all theoretical. Today, the paradigm of protecting any important data
sent through the Internet is no longer a subject to be only talked about.
We couldn't completely ignore such an important topic in this book. We will ex-
amine some applied tasks of cryptography, and then introduce you to the concepts
of digital signatures and certificates. Then, in an example of a certificate manager,
we'll look at the features of the CryptoAPI interface, and end our discussion with
some issues of configuring web tools and components used in distributed applica-
tions for cryptographic protection.PART |
—
COM and COM+
Applications
Chapter 1: COM Mechanisms in Delphi
Chapter 2: Automation
Chapter 3: ActiveX Components
Chapter 4: COM+ Technology
(Microsoft Transaction Server)Chapter 1
COM Mechanisms
in Delphi8 Part |: COM and COM+ Applications
any of the technologies described in this book are based on the Com-
M ponent Object Model (COM) technology and those technologies cre-
ated based on it. Therefore, we shall start by reviewing COM's basic
capabilities.
One of the most pressing tasks software developers can encounter has always
been the interaction, or lack thereof, between certain applications. To solve
this problem, a large number of various methods and techniques have been
employed.
At the dawn of Windows, shared files, the clipboard, and the Dynamic Data Ex-
change (DDE) technology were introduced.
To provide data exchange and services, the first version of the Object Linking and
Embedding technology — OLE | — was developed. It was intended for the creation
of compound documents, to which we have been accustomed for a long time al-
ready. As a result of its many imperfections, this technology has been replaced by the
OLE 2 technology. This solves a more common problem: how to make applications
allow other applications to perform their functions (services), and how to use these
functions properly.
To solve this problem, a whole range of other technologies has been developed be-
sides OLE. The core of all of them was the basic Component Object Model technol-
ogy. It described means of interaction for any number of applications. One part of
the software renders its own services, while the other gets access to them, and the
location of these parts is absolutely unimportant — within one process, in different
processes on one computer, or on different computers.
In addition, for applications created using COM technology, it is not important what
programming language was used in the course of its development — if the COM
standard is observed, interaction should go on without a hitch.
A modification of basic technology — Distributed COM, or DCOM — provides de-
velopers of the distributed applications with additional options.
Currently, COM is widely used in various fields of software development.
The technologies of Automation, ActiveX, ActiveForm, and Microsoft Transaction
Server are COM-based. Distributed applications would not function without COM
objects, whether they operate in a local network or on the Internet.
Delphi provides the developer with a set of tools for creating valuable COM appli-
cations.Chapter 1: COM Mechanisms in Delphi
Later, this chapter will deal with the main parts of the COM specification and the
methods for creating COM objects and interfaces in Delphi. Much space is allot-
ted to the Type Library Editor — the main tool facilitating work with COM objects
in the project.
This chapter deals with the following issues:
© Objects and interfaces
© The tunknown interface
© Type libraries
© Class factories
© Server types
Basic Concepts
Using COM technology, an application provides its services via COM objects.
Every application contains at least one object. Each object has at least one, but
perhaps several, interfaces. Each interface combines the methods of the object that
enable access to the properties (data) and the execution of operations. The interface
usually combines all the methods that perform operations of one type, or those
dealing with homogeneous properties.
The client gains access to the object's services through the interface and its meth-
ods only. This is a key routine. In order to obtain comprehensive information on
the properties and methods of the object, it is enough for the client to know just a
few basic interfaces. Therefore, any client may work with any object irrespective of
their development environment. According to the COM specification, an existing
interface cannot be changed by any means. This guarantees continuous perform-
ance of COM-based applications, despite any changes.
The object always operates within the COM server. The server may be a dynamic
library or an executable file. The object may have its own properties and methods
or use data and services from the server.
To gain access to an object's methods, the client must get the pointer to the cor-
responding interface. Each interface has its own pointer. After that, the client may
use the services by simply calling their methods. Access to the objects’ properties is
possible through their methods only.10 Part |: COM and COMs+ Applications
Suppose a COM object is integrated into a worksheet and provides access to mathe-
matical operations. It would be logical to divide the mathematical functions into
groups according to their types, and create an individual interface for each group.
For example, you can separate them into linear, trigonometric, aggregate functions,
etc. The object in Fig. 1.1 is within the server — a worksheet. The interfaces are
represented by small bubbles linked with the object. Let the linear functions’ interface
be called ILinear, and aggregate ones TAggregate.
According to the rules of naming accepted in Delphi, the names of interfaces begin with
a capital letter |. The names of classes, however, begin with a capital letter T.
IUnknown
ILinear O—
lAggregate 0—
COM Object
Server
Fig. 1.1. A server and an object with its interface
According to the rules for marking COM objects, the basic Unknown interface that each
object has is marked as a bubble on the upper side of the object rectangle. The re-
maining interfaces are marked to the right or to the left.
Fig. 1.2 shows the schema of interaction between the client and the COM object.
Suppose that among the methods of the Aggregate interface there is a method
for calculating averages. To gain access to the aggregate function that calculates an
average, the client must get the pointer to the IAggregate interface, and only then
can it call this function.
The interaction between the client and the object is provided by the basic COM
mechanisms. At the same time, the exact location of the object is concealed fromChapter 1: COM Mechanisms in Delphi 11
>
the client: in the address space of the same process, in another process, or on an-
other computer. Therefore, from the point of view of the client software developer,
the using worksheet functions looks like a general call to the object's method.
The mechanism of the interaction among the remote COM elements is called
marshaling.
Pointer to the COM
object's interface
IUnknown
ro
o4
COM Object
oe +O
Client Server
Fig. 1.2. Schema for the interaction of the client and the COM object
Here an appropriate question might emerge: how is a COM object created and
initialized upon the client's first call? It is doubtful that the OS independently
creates instances of all the classes registered within it, hoping that one of them
may be of use at some point. Moreover, the performance of an object relies on the
servers. Imagine that every time you start Windows, Word, Excel, Internet Ex-
plorer, etc, were all automatically launched.
Any COM object is a normal instance of some class, describing its properties and
methods. Information on all COM classes registered and available in this OS is
collected in the special COM Library used for launching the class instance — the
object.
First, the client refers to the COM Library, communicating the name of the re-
quired class and the name of the interface needed first. The library finds the re-
quired class and initiates a server process that creates an object — a class instance.
Then the library returns the object pointer and the interface to the client. From
now on, the client may interact directly with the object and its interfaces.
After this, it is time for initialization; the object should load the necessary data,
read the settings from the System Registry, etc. All of this falls under the responsi-
bility of special COM objects called monikers. Their performance is concealed
from the client. Usually, the moniker is created along with the class.12 Part |: COM and COMs+ Applications
It is quite possible that several clients will simultaneously call the same object. Pro-
viding that certain settings have been established, a separate instance of the class is
created for each client. A special COM object called a class factory performs this
operation.
And the last issue to be considered is how the client obtains information about the
object. For example, a client software developer is aware that a worksheet is cre-
ated in accordance with the COM specification, but has no idea about the COM
objects that provide its services to clients. To avoid such situations, the COM
object developer can distribute the type information together with the object.
This includes interface data, their properties and methods, and the methods’
parameters.
This information is contained in the ‘ype library created using the Interface Defi-
nition Language (IDL).
Objects
An object is the central element of COM. Applications supporting COM have
one or more COM objects within them. Each object is an instance of the
corresponding class, and contains one or more interfaces. So what exactly is a
COM object?
Without going into a lot of detail on implementing COM objects in Delphi, it is
possible to say that a COM object differs somewhat from ordinary objects.
Any object is an instance of a certain class, i.e., a variable of an object type.
Therefore, an object has a range of properties and methods that operate with these
properties. Three main characteristics are applicable to objects: encapsulation, in-
heritance, and polymorphism. COM objects meet all these demands (there are
some features of inheritance).
With reference to objects as a whole, the notion of the object interface explained
above is not used. As a first approximation, it is possible to say that all of an ob-
ject's methods make up its one and only interface, the object pointer being the in-
terface pointer as well.
A COM object may have any number of interfaces (more than zero), each one
having its own pointer. This is the first difference between COM objects and ordi-
nary ones.Chapter 1: COM Mechanisms in Delphi 13
>
Some programming languages, e.g., Java, allow an object to have several interfaces.
COM objects have another peculiarity: inheritance. In general, there are two ways
of inheritance. Implementation inheritance transfers the entire software code from
the ancestor to the descendant. Interface inheritance refers transferring the decla-
ration of methods only, and the descendant has to get the methods’ program code
independently.
COM objects support only interface inheritance, thus avoiding possible violations
of the ancestor's encapsulation. Nevertheless, implementation inheritance cannot
be simply disregarded. Instead, COM objects use the mechanism of containment,
i.e., when needed, the descendant calls for the required method of the ancestor.
Also, the mechanism of aggregation may be used, when one or several interfaces
of one object are temporarily included in another object by transferring the
pointers.
Thus, from the point of view of Object-Oriented Programming (OOP), a COM
object is definitely an object. Being the key element of COM technology, however,
it possesses a few peculiarities when implementing basic routines.
Interfaces
If the COM object is the key element of COM implementation, then the interfaces
are the central link of COM ideology. How can two fundamentally different ob-
jects interact? The answer is simple: they must agree on the way they will interact
beforehand. (We deliberately avoided the word “language,” since it might provoke
an objectionable association with a programming language, which has nothing to
do with the interaction of COM elements.)
An interface is the means of enabling a client to refer correctly to the COM ob-
ject, and how to get the the object to react so that the client understands.
This can be illustrated by an example. Two people meet on the street: a local
(COM object) and a foreigner (client). The foreigner has a dictionary with him
(type library or IUnknown interface). The foreigner needs, however, the local's
knowledge of the city. He takes a pen and paper, looks into the dictionary, makes
up a phrase and diligently copies the strange words onto the paper. The local reads
the phrase, takes the dictionary, makes up his own phrase, and writes it on the
paper.14 Part |: COM and COMs+ Applications
The story ends happily: the pleased client (the foreigner) receives from the COM
object (the local) the result of the service performance (the route), and the local
leaves with the dictionary.
As you might guess, the interface in this example is the pen and the paper: the for-
eigner does not know the native's language, but does know how to ask correctly in
order to obtain the right answer.
Each interface has two attributes for identification. The first is its name, which is
made up of symbols according to the rules of the programming language used.
Each name should begin with the symbol "I." This name is used in the program
code. The second is the Globally Unique IDentifier (GUID), which is a truly
unique combination of symbols that is never reproduced on any computer in the
world. For interfaces, such an identifier is called Interface IDentifier (IID).
In general, the client may not know what interfaces the object has. To obtain a list
of interfaces, the basic Unknown interface is used, which every COM object has.
The client then needs to find out the methods of the interface he or she has se-
lected. For this purpose, the developer should distribute the method descriptions
together with the object. This problem is solved with the help of the Interface
Definition Language (IDL) (which is also used in the type libraries).
Its syntax is very much like that of C++.
The most important part is yet to come: to call the method itself correctly. For this,
the interface implementation definition in COM is used based on standard binary
format. This provides independence from the programming language.
1Unknown
Internal pointer to
the virtual table > terete
Pointer 4 Method
Pointer? O——»| Method?
Reference to the ner? -
interface
Client [_romern“Q-——+[_ wea]
COM Object
Fig. 1.3. COM interface format
b}-—ro—Chapter 1: COM Mechanisms in Delphi 15
>_>
The interface pointer that is available for the client refers to the inner pointer of
the object and then to the special virtual table (Fig. 1.3). This table contains the
pointers for all methods of the interface. (It looks very much like the table of the
object's virtual methods in OOP, doesn't it?)
The first three rows of the interface table are always allotted to the methods of the
Unknown interface, since any COM interface is a descendant of this interface.
As a result, the client's call to the method goes through a chain of pointers, gets the
pointer for the specified method, and the appropriate program code is executed.
The /Unknown Interface
Each COM object contains an interface called Unknown. This interface has three
methods that play a key role in the functioning of the object.
The QueryInterface method returns the pointer to the interface of the object, and
its IID identifier is communicated in the parameter of the method. If the object
has no such interface, the method returns NULL.
Upon the first call to the object, the client usually obtains the interface pointer.
Since every interface is a descendant of IUnknown, every interface has the
QueryInterface method. It's not particularly important which interface the client
uses. With the help of the QueryInterface method, the client may gain access to
any of the object's interfaces.
The 1Unknown interface also performs another important routine of the COM ob-
ject: the reference counting method. The object must exist if it is used by at least
one client. And no client may dispose of the object on its own, insofar as other
clients may still work with it.
Therefore, the object increases the special unit reference count upon when the next
interface pointer is transferred. If one client gives another pointer to the interface of this
object, then the client receiving the pointer should increment the reference count once
again. For this, the method addref of the TUnknown interface is used.
After closing the session with the interface, the client should call the Release
method of the 1Unknown interface. This method decreases the unit reference count.
Once the reference count reaches zero, the object disposes of itself.16 Part |: COM and COMs+ Applications
Server
The COM server is an executable file: an application or dynamic library that may
contain one or several objects of the same or different classes. There are three
types of servers.
An in-process server is enabled by the dynamic libraries connected to the client
application and operates in the same address space.
A local server is created by a separate process operating on the same computer with
the client.
A remote server is created by a process operating on a computer other than the cli-
ent's.
Let's consider a local server. Here, the interface pointer received by the client re-
fers to the special COM proxy object (we shall call it the substitute), which operates
within the client's process. The substitute provides the client with the same inter-
faces as the COM object called on the local server. Once the client's call is re-
ceived, the substitute bundles its parameters and sends the call to the server proc-
ess through the OS services. The local server has another special object to
communicate the call: the stub, which unpacks the call and sends it to the required
COM object. The result of the call is sent back to the client in reverse order.
Now let's look at a remote server. It operates in the same way as the local one,
except for that call transfer between two computers is provided by the DCOM fa-
cility with the help of the Remote Procedure Call (RPC) routine.
To enable local and remote servers, the mechanism of marshaling and demarshal-
ing is used. Marshaling implements the COM integrated packaging format of
the call parameters, while demarshaling takes care of the unpacking. In the server
implementations described above, these operations are enabled by the stub
and the proxy. These object types are created jointly with the main COM object,
using IDL.
COM Libraries
To provide for the fulfillment of basic functions and interfaces, there is a special
COM library within the Operating System (the specific implementation varies). Ac-
cess to the library is gained in the standard way, via a function call. According to the
specification, the names of all the library functions begin with the prefix "Co."Chapter 1: COM Mechanisms in Delphi 17
>
When installating an application that supports COM, the information for all COM
objects being implemented is written into the System Registry:
OG The Class Identifier (CLSID), which uniquely defines the object class.
© The type of object server: in-process, local, or remote.
© The file path of the DLL or local executable file is retained for local and in-
process servers.
OC The full network address path is written for remote servers.
Suppose the client is trying to use a COM object that heretofore has not
been used. The client obviously has no pointer for the required object or the
interface. The client must thus refers to the COM library and calls the special
CoCreateInstance method (see the "Class Factory" section), transferring CLSID of
the required class, the 11D of the interface, and the required type of server as a
parameter.
Using the Service Control Manager (SCM), the library refers to the System Reg-
istry, uses the class identifier to locate the server information, and launches the
server. The server creates the class instance — an object — and returns the pointer
for the called interface to the library.
The COM library communicates the pointer to the client who, later on, may refer
directly to the object. A diagram of the creation of the first instance of the object
using the COM library and the System Registry is shown in Fig. 1.4.
In Fig. 1.4, the client uses the cocreateInstance method by invoking the COM object
with GUID CLSID_2 and calling the IID2_A interface. The appropriate record is found in
the System Registry by the global identifier CLSTD_2, and the client sends the pointer
back to the required interface.
To implicitly initialize the created object (set up the property values), a special
object — a moniker — may be used. The client may also initialize the object
on his or her own, using special interfaces (IPersistFile, IPersistStorage,
IPersistStream).18 Part |: COM and COMs+ Applications
Client asks
isteciace Inknown,
COM Object
peeps eceeeaeca||
Client ‘Server:
ee
CoCreateinstance(CLSID_2)
Pointer to the ID2_A
Stee econ
CLSID_1 [C\Servert.oxe
CLSID_1 |CAServer2.ai!
COM Library
Server started by the Registry
Library request to the Registry item
Fig. 1.4. Creation of the first instance of the object using the COM library
and the System Registry
Class Factories
Objects are created by a special type of object called a class factory. With the help
of the class factory, it is possible to create a single object, as well as several copies
of it. An individual class factory must exist for each class.
According to the COM specification, a class factory is also a COM object.
A COM object is entitled to be called a class factory if it supports the
IClassFactory interface. Only two methods are implemented here:
OG cocreateInstance creates a new class instance. Besides the IID, this method
obtains all the necessary parameters from the class factory. This is what distin-
guishes the library from the general function of the same name.
OC Lockserver leaves the server operating after the creation of an object.
In fact, the general cocreateInstance method calls the corresponding class factory
and CoCreateInstance method of the IClassFactory interface, using the CLSID
transferred to it.Chapter 1: COM Mechanisms in Delphi 19
>_>
CoGetClassObject is a special function used to call the class factory:
function CoGetClassObject (const clsid: TCLSID; dwClsContext: Longint;
pvReserved: Pointer; const iid: TIID; var pv): HResult; stdcall;
The necessary class of cLsrp and the I1D interface (IclassFactory) is transferred
as a parameter. The function searches for the required factory and returns the
pointer to the interface. With its help, the client makes the factory class create the
object using the cocreateInstance method.
Type Library
In order to document the object interfaces for the users, the developer creates in-
formation on the object types using the IDL language. All information is integrated
into a special type library. It can describe the properties and the methods (as well
as their parameters) of the interfaces, and contain the information on the necessary
stubs and proxies. Information on the specific interface is designed as a separate
object within the library.
To create the type library described by the IDL statements, a special compiler is
used. Access to the library is gained via the cLsrp of the object class. Besides this,
the library has its own GUID that is saved in the System Registry when the object
is registered.
Each type library has the ITypeLib interface, which enables it to work with the li-
brary, as well as with a single object. To get access to the information on a par-
ticular interface, the ITypeInfo interface is used.
To access the library via GUID, the LoadRegTypeLib function is used. If the client
knows the file name of the library, it is possible to use the LoadTypeLib function.
COM Objects in Delphi
Now let's look at how to create COM objects in Delphi. As mentioned above,
a COM object should provide for the creation of an arbitrary number of interfaces,
interfaces being understood as a combination of methods accessed via the interface
pointer.
It used to be quite difficult, however, to implement such a requirement directly
using standard OOP approaches. Then Delphi developers found the following so-
lution.20 Part |: COM and COMs+ Applications
>
The COM object itself is described by the standard class Tcomobject, which is gen-
erated directly from the Topject. All the properties and methods implementing the
object assignment are declared and described in its declaration. Therefore, the class
of the new COM object is not significantly different from any other.
When a COM object is created, an auxiliary class that describes all the interfaces is
linked to it. This class is generally called coclass, and when a real object is created,
its name is prefixed by co. coclass combines all the information on the types pre-
sented in the library. The declaration and description of the coclass can be found in
the type library.
Thus, the standard class declaration in Object Pascal provides for the creation of
the object code; this is exactly what is compiled in binary code. The coclass is the
superstructure, or shell, of this code, providing for the presentation of the object
instance according to the COM specification and guaranteeing the correct proc-
essing of the client's reference to the object.
The Tcomobject class, together with the coclass instance created for each object,
possesses all the features of the COM object considered above. It may support any
number of the interfaces, including the basic one: IUnknown.
To allow the COM object to work with the type library, the new TTypedcomObject
class has to be generated from the basic Tcomobject class. It has another interface
in addition: TProvideClassinfo. Should this interface have access to the type li-
brary, knowledge of its class identifier will be sufficient to obtain complete infor-
mation on the object. This class is used to create objects with the use of the type
library.
The TComObject Class
The Tcomobject class provides for the implementation of the basic functions of the
object, those that actually make it a COM object. Its properties and methods en-
capsulate the functionality of the IUnknown interface. It also stores the CLSID class
identifier.
Like any other object, a COM object is created by the constructor
constructor Create;
which creates an independent instance of the class.Chapter 1: COM Mechanisms in Delphi 21
>_>
At the same time, the constructor
constructor CreateAggregated(const Controller: IUnknown);
creates the new object as part of the aggregate. Here the aggregate is a range of
objects that have their own interfaces, with the exception of the one general inter-
face. This general interface is TUnknown, which, as is well known, implements the
object call counter and controls all the objects of the aggregate. The object with
the general controlling interface is called the controller.
In the createAggregated constructor, the Controller parameter defines the con-
trolling interface and, after the reference to the constructor, it is communicated
into the property
property Controller: IUnknown;
To actually create an object and initialize its parameters, the following constructor
is used:
constructor CreateFromFactory (Factory: TComObjectFactory;
const Controller: IUnknown);
The Factory parameter enables the definition of the property
property Factory: TComObjectFactory;
which defines the class factory for the object. While the object is being created, the
method
procedure Initialize; virtual;
performs the initialization. For the Tcomobject class this method is empty, but it
could well be overflowing with descendants.
Both of the other constructors simply invoke the createFromFactory constructor,
each in its own way initializing the controller variable, which defines the affilia-
tion with the aggregate:
constructor TComObject.Create;
begin
FNonCountedObject := True;
CreateFromFactory (ComClassManager .GetFactoryFromClass (ClassType), nil);
end;
constructor TComObject .CreateAggregated(const Controller: IUnknown) ;
begin22 Part |: COM and COMs+ Applications
>
FNonCountedObject := Trues
CreateFromFactory (ComClassManager.GetFactoryFromClass (ClassType) ,
Controller);
end;
After the object is created, the property
property RefCount: Integer;
is available for reading, which returns the number of opened references to the ob-
ject. The meaning of this property is defined by the calls of the TUnknown interface
The methods of the Tunknown object interface may be used by calling the analogous
methods of the Tcomobject class.
The method
function ObjaAddRef: Integer; virtual; stdcall;
increases the reference counter by one. Accordingly, the property RefCount is in-
creased.
The method
function ObjQueryInterface(const IID: TGUID; out Obj): HResult;
virtual; stdcall;
enables definition if the object uses the interface defined by the 11D parameter.
And the method
function ObjRelease: Integer; virtual; stdcall;
reduces the reference counter by one. The RefCount property is also reduced.
The TtypedComObject Class
This class is the direct descendant of the Tcomobject class, and is used to create
COM objects using the type library. This class has the additional interface
IProvideClassiInfo, which allows you to use an additional method:
function GetClassInfo(out TypeInfo: ITypeInfo): HResult; stdceall;Chapter 1: COM Mechanisms in Delphi 23
>_>
This function returns the pointer to the coClass of a particular class, thus opening
access to all information about the types.
The /Unknown Interface in Delphi
The Tcomobject class has methods that correspond to those of the encapsulated
TUnknown interface.
As the general ancestor of all the interfaces in Delphi, the following interface is
used
TInterface = interface
[" £00000000-0000-0000-c000-000000000046}"1
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function AddRef: Integer; stdcall;
function Release: Integer; stdcall;
end;
which, as you can see from the declaration, contains all three methods that allow
the IUnknown interface to work. Therefore, the IUnknown interface is declared as
follows:
TUnknown = IInterface;
The method
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
returns the pointer to the 11D interface, if it is available.
The method
function AddRef: Integer; stdcall;
increases the call counter by one.
The method
function Release: Integer; stdcall;
reduces the call counter by one.
According to the COM specification, all interfaces in Delphi are descendants of
the TUnknown interface.24 Part |: COM and COMs+ Applications
Globally Unique Identifier Type
To represent the Globally Unique Identifier (GUID) in Delphi, a special type is
defined in the System module:
TGUID = record
D1: LongWord;
D2: Word;
D3: Word;
D4: array[0..7] of Byte;
In order to generate a new identifier, it is possible to use the cocreatecuzD func-
tion from Win API. To convert the identifier into a string, the following function
is used:
function GUIDToString(const ClassID: TGUID): string;
To reverse the operation, the following function is used:
function StringToGUID(const S: string): TGUID;
The TinterfacedObject Class
Especially for work with COM objects that encapsulate interfaces, the hierarchy
of basic Delphi classes provides the tInterfacedobject class, which creates
a class descendant that inherits not only the properties and methods of the class
ancestor, but also the methods of the ancestor interface:
TInterfacedObject = class(TObject, IInterface)
The declarations of all classes descending from the TInterfacedObject class should
indicate not only the class ancestor, but also the interface ancestor. As a result, the
new class inherits the methods of the interface ancestor.
The creation of an instance of such class is done not with the class factory, but by
using a common constructor:
type
TSomeInterfacedObject = class(TInterfacedObject, IUnknown)
end;Chapter 1: COM Mechanisms in Delphi 25
a
var SomeInterfacedObject: TSomeInterfacedObject;
SomeInterfacedObject := TSomeInterfacedObject .Create;
Since the TinterfacedObject class is inherited from the IInterface interface (also
IUnknown), it contains the property of the call counter
property RefCount: Integer;
and three basic methods:
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
Memory allocation for the new object is carried out by the method of the
NewInstance class. For the TInterfacedObject class, the method
class function NewInstance: TObject; override;
aside from the main function, also initializes the call counter.
The methods of this class may be called without creating an object instance if you indi-
cate the class name where they are described. Before the description of the method, the
reserved word “class” should be inserted.
Class Factories in Delphi
When creating of COM objects in Delphi, the developer needn't worry about cre-
ating a factory for each class. As you can see below (see Listing 1.1), in addition to
the object, not only the CoClass is created, but also the class factory as well —
a special COM object with the 1classFactory interface that is used for the crea-
tion of COM objects.
Careful readers will have already noted that when creating the basic class
TComObject instance, the CreateFromFactory constructor, which is charged with
calling the corresponding factory class, is invoked in any case. The COM object con-
tacts its class factory via the Factory property, which has the TComObjectFactory26 Part |: COM and COMs+ Applications
type. The TComObjectFactory class is the basic one when using class factories in
COM applications in Delphi.
In cases where there are several class factories functioning within one server,
a special COM class manager is used. It is created on the basis of the
TComClassManager class, and used mainly for in-process operations where the
server needs to perform certain actions with all the class factories, e.g., to delete
them correctly.
To create a factory for the declared class with the help of the TTypedcomobject
class, the TTypedCcomObject Factory class is used.
The TComObjectFactory Class
This class encapsulates the functions of a multipurpose class factory for COM
objects created as instances of the TComobject class. It provides for the function-
ing of the IUnknown, IClassFactory, and IClassFactory2 interfaces. The
IClassFactory interface is the defining interface in the operation of the class factory.
Usually, the class factory is created on the operating server of the object that the
factory has created. The factory constructor is described in the section of the mod-
ule initialization that includes the corresponding server (see Listing 1.1).
To create the factory, the following constructor is used:
constructor Create (ComServer: TComServerObject; ComClass:
TComClass; const ClassID: TGUID; const ClassName, Description:
string; Instancing: TClassInstancing; ThreadingModel:
TThreadingModel = tmSingle);
The comserver parameter defines the server where the object will function.
The comclass parameter defines the type of the class used to identify the class
factory while the GetFactoryFromClass procedure of the TComClassManager class
is functioning.
The classID parameter specifies the class identifier created using this class factory.
The Instancing parameter defines the means for creating the object creation.
The ThreadingModel parameter defines the technique of the interaction between
the object and the client.Chapter 1: COM Mechanisms in Delphi 27
>_>
After the class factory is created, the main parameters of its COM object are avail-
able via properties.
The property
property ClassID: TGUID;
returns the GUID of the object class.
The property
property ClassName: string;
contains the name of the object class.
The property
property ComClass: TClass;
defines the type class used for identification of the class factory.
The means for creating the object is defined by the property
type TClassInstancing = (ciInternal, ciSingleInstance, ciMultiInstance) ;
property Instancing: TClassInstancing;
And the property
property ThreadingModel: TThreadingModel;
specifies the means of interaction between the object and the client.
The key method of the class is the function
function CreateComObject (const Controller: IUnknown): TComObject;
It performs the operations of the cocreateInstance function of the IclaccFactory
interface. This method calls the createFromFactory constructor that is linked to
this class factory, transferring the necessary parameters.
The method
procedure RegisterClassObject;
registers the class of the object created. This is done by launching the executable
file encapsulating the COM object.
The method
procedure UpdateRegistry (Register: Boolean); virtual;28 Part |: COM and COMs+ Applications
_>
registers or unregisters the COM object. For executable applications, this is done
upon the first startup or upon startup with the /regserver key. This method allows
for the saving of the main parameters of the COM object and the location of the
executable file in the System Registry.
After the COM object is created, the developer may provide for a special interface
responsible for error handling. Its GUID is defined by the property
property ErrorIID: TGUID;
and the property
property ShowErrors: Boolean;
switches on or off the display of error data from the object's creation.
When distributing the COM object, the developer may include license information
and information about the developer in the executable code.
The textual description of the COM object may be defined upon entering the
property
property Des
ption: string;
The property
property ProgID: string;
contains the name of the developer, class, and version.
The property
property LicString: WideString;
must contain the license information on the object.
If the developer provides for a user registration procedure and license agreement,
the property
property SupportsLicensing: Boolean;
makes it possible to define the startup mode with the licensing. For this purpose,
the property must be defined as True.
The TTypedComObjectFactory Class
This class is generated from the TcomObjectFactory class, and is used to create the
class factories for classes declared using the TTypedCcomObject class — i.e., this classChapter 1: COM Mechanisms in Delphi 29
>_>
is used to describe the class factory in the type library. The TTypedcomObjectFactory
class has one additional method:
function GetInterfaceTypeInfo (TypeFlags: Integer): ITypeInfo;
This function returns the pointer to the ITypeInfo interface, which contains in-
formation about a certain type.
The TComClassManager Class
This class is used to manage the class factories within a particular COM server.
This is the class of the ComClassManager variable within the ComObj module.
It is possible to obtain a reference to the class instance using a function of the
same module:
function ComClassManager: TComClassManager;
The developer may make use of the class method
function GetFactoryFromClassID(const ClassID: TGUID): TCombjectFactory;
which performs a search by ClassID among the functioning class factories of the
server.
The method
function GetFactoryFromClass(ComClass: TClass): TComObjectFactory;
returns a reference to the class factory for the comClass class. In particular, this
method is used in the COM object constructor to obtain a reference to the class
factory.
COM Servers in Delphi
When a COM object is created, the ComServ module is automatically added to the
module with its description. This module describes the TComServer class that en-
capsulates the properties of the COM server where the corresponding object is op-
erating. The reference to the properties and methods of this class makes it possible
to obtain information about the objects operating within the server, their status,
and the status of the server itself.30 Part |: COM and COMs+ Applications
>
‘When the ComServ module is included in the object module, an instance of the
TComServer Class is automatically created, the pointer to which is assigned to the
variable
var ComServer: TComServer;
Using this variable, it is possible to obtain information from the server.
The server class is used to create instances of the class factories, i.e., it takes part
directly in the interaction of clients and COM objects. Global functions are de-
clared and described in the ComServ module, and these are automatically exported
to each in-process server, where they perform basic operations of registration, re-
registration, and server rollout. It is not necessary to call them directly.
The TComServer Class
This class combines the properties and methods that make it possible to obtain
information about the server itself and the object functioning within the COM
server. This class is the descendant of the TcomserverObject abstract class.
This class is used for in-process and local servers.
If the property
property IsInprocServer: Boolean;
has a value of True, then the server is in-purpose. A more accurate definition of
the server type is obtained using the property
property Serverkey: string;
If it contains the value 'InprocServer32", then the server is in-purpose and DLL.
If you see the value 'Localserver32", then this is the local server in the form of
an executable file.
The server file startup technique is defined by the property
type TStartMode = (smStandalone, smAutamation, smRegServer, smlnregServer) ;
property StartMode: TstartMode;
which can have the following values:
© smstandalone: the server is started as a separate application.
G smautomation: the server is started as an Automation server (see Chapter 2,
Automation").Chapter 1: COM Mechanisms in Delphi 31
>
1 smRegServer: the server is started with the /regserver key (or started for the
first time) to be registered in the Registry.
1 smUnregserver: the server is started with the /unregserver key for unregistration.
Upon startup of the server, the following method is called:
procedure Initialize;
It registers (upon the first startup or upon the use of the /regserver key) all the
COM objects related to this server. These may be the principal or subsidiary re-
mote data modules — special COM objects used in the DataSnap technology (see
Chapter 9, "An Application Server").
The name is defined by the read-only property
property ServerName: string;
It can also be defined by the method
procedure SetServerName (const Name: string);
Note, however, that if the server uses a type library, then it defines the server
name. The reference to the type library is available via the property
property TypeLib: ITypeLib;
which returns the 1TypeLib interface — the main interface of the type library.
It is possible to load a type library via the method
procedure LoadTypeLib;
The full filename is in the property
property ServerFileName: string;
and if there is a help file, its full name is defined by the property
property HelpFileName: string;
Another useful read-only property
property ObjectCount: Integer;
is used in the process of the server's operation. It returns the total number of COM
objects operating on this server.
Type Libraries in Delphi
Type libraries store information about objects, interfaces, functions, etc. They are
used in Delphi projects that use COM-based technologies.32 Part |: COM and COMs+ Applications
Traditionally, type libraries are created using the Interface Description Language —
IDL. As the basic option, the syntax and operators of the Object Pascal language
are used. You may, however, export this code into IDL format, thus providing for
the use of your own objects in any Windows application.
Clients may use the type library upon calling an object in order to obtain initial
information about the available identifiers, interfaces, and methods. Basically,
these data may be obtained by programmatic means using the System Registry and
the options of the main COM interfaces. It is not always convenient, however, to
supplement the application with such a complicated block of code because you
need use a small object for auxiliary purposes.
Usually, a version of the library in Object Pascal is used within the project and,
when the objects are distributed, the type library is exported into IDL format.
The version of the type library in Object Pascal is saved as a file with the PAS ex-
tension and a file name ending in _TLB.
In Delphi, the code of the type library is generated automatically when an object
is created. To work with the library, the special Type Library Editor is used
(Fig. 1.5). All the operations here modify the source code of the type library and
corresponding objects and interfaces, so the developer needs not study the peculi-
arities of the library code's construction thoroughly.
The developer can either use the type library in the project or not. To include the
type library when a new COM object is created, it is necessary to set the
Include Type Library checkbox (Fig. 1.6). Or, you could use the Delphi Object
Repository, where the type library is available on the ActiveX page.
The Type Library Editor provides the developer with a full toolkit, enabling him or
her to automate the process of creating objects and interfaces. The Editor window
is divided into three main parts (Fig. 1.5).
On top, there is a narrow toolbar. Using the toolbar's buttons, one may create
COM elements and perform general operations for the entire library. The toolbar
is divided into four parts. On the left we find buttons of new types that may be
added to the type library:
# (Interface) creates a new interface.
& (Dispatch) creates a new dispinterface (see details in the next chapter).Chapter 1: COM Mechanisms in Delphi 33
& (CoClass) creates a new coClass for the COM object.
é (Enum) creates a new enumerator. It may contain constants. It is used for
object initialization.
» (Alias) creates new aliases (do not confuse with database aliases). It is basically
the naming of an existing data type. It has its own identifier.
# (Record) creates a new record, which is understood as a list of fields — types
of data that have been named but do not have their own identifiers.
ade
“y" (Union) creates a new union, presented as an indexed and ordered list of
fields.
rT (Module) creates a new module combining separate functions and constants.
FOSbbS93/%9- (AS G-
> a Ease a | al Ca
ten ae
suo Jferranio sos TeAsoranBspeDEEOTET —
we eee ee eee
Lev: eee eee
Hep
Help Sting: Proeatt Lib See
— #§=«=«©.—
HoStinaCotet[
Ah
Help Ee: S seeaetaetat at vee see eEnEneueueaey
Fig. 1.5. The Type Library Editor34 Part |: COM and COMs+ Applications
Then come two buttons that alter their assignment depending on the current li-
brary type selected from the tree on the left. These buttons are for creating the new
elements of the type. For example, new methods and variables may be created for
the interface.
Then come two groups of buttons that provide for the functioning of the general
operations of the library.
(Refresh) button updates the source code of the library, objects, and
interfaces in other modules.
© The #F (Register) button registers the type library in the Registry.
13 The FS} (Export) button exports the library code in the format of the
Microsoft IDL or CORBA IDL language.
On the left, there is a hierarchical list of the project parts available in the type li-
brary. It is used to select the required type and type handling — the pop-up menu
contains the main commands of the Editor.
On the right, there is an information bar showing the properties of the element
selected from the list and enabling the handling of its parameters. Its multipage
notebook changes the settings and contents of the pages, depending on the current
type.
There are always two pages for any type on the bar. The Attributes page specifies
the main parameters of the type. The Text page can contain a textual description
of the type.
An example using a type library for the interface's methods is considered below.
Simple COM Objects within In-Process
Servers
Now let us consider an example that, despite its simplicity, encompasses all the
main stages of work with COM objects.
To demonstrate the operation of a COM object, we have to create an
in-process server and, within it, a simple COM object to represent its interface.
The object will contain a few methods that perform very simple mathematical
operations.Chapter 1: COM Mechanisms in Delphi 35.
>_>
Object Creation
To create a COM object within an in-process server (dynamic library), it is nec-
essary to carry out the following actions.
On the ActiveX page of the Delphi Object Repository, you must click on the
ActiveX Library icon. As a result, a new project will be created, called
ClientInProccom. The source code of the new dynamic library will be created
automatically, and is presented in Listing 1.1.
Source Code of the InProccom Dynamic Library
InProcCOM;
uses
ComServ,
Client InProcCOM_TLB in 'ClientInProcCOM_TLB.pas',
uSimpleCOM in 'uSimpleCOM.pas' {SImpleCOM: CoClass};
exports
Dllcetc.
D11CanU:
ssObject,
loadNow,
DllRegisterServer,
DllUnregisterServer;
{$R *.TLB}
{$R *.RES}
begin
end.
The following four functions exported by the library provide for its interaction with COM:
G Diicetclassobject provides access to the class library.
OG piicanUnloadNow manages the server depending on the state of the objects.
CG bllregisterServer registers the server in the System Registry.
a
DllUnregisterServer deletes the server information from the System Registry.36 Part |: COM and COMs+ Applications
>_>
You should then click on the COM Object icon on the ActiveX page in the re-
pository. After clicking on the icon, a dialog box will open where the initial pa-
rameters for the new object may be specified (Fig. 1.6).
Sree
Clsss Name:
Instancing Mutiple Instance
Thieading Mode [Apariment
Implemented
Irverface:
Description
> Options
Inchide Type Liseary /¥ Matk interface Dlesutomation
Fig. 1.6. Window for setting the initial parameters of a COM object
The Class Name string editor must contain the name of the new class.
The Instancing combined list defines the means of creating the object:
G Internal — the object is used in the process.
G Single Instance — if several clients call the object, the necessary number of
objects are created on a single instance of the server.
G Multiple Instance — if several clients call the object, a separate instance of the
object server is created for each call.
If the object is used within the process, this list's settings are of no importance.
The Threading Model combined list defines the method of interaction between the
object and clients:
OG Single — the server can service client calls sequentially, one by one.
G Apartment — a call for the object is performed only in the thread in which
the COM object itself was created, and the object can serve only one client at
a time; several objects may simultaneously work in this mode in the server.Chapter 1: COM Mechanisms in Delphi 37
>
G Free — an object can simultaneously serve an arbitrary number of clients.
O Both — an object can work with clients using both the Apartment and Free models.
G Neutral — an object may serve several clients in various threads, but does not
resolve conflicts that occur; used only for COM+ technology.
The Implemented Interfaces string editor contains the name of the interface en-
capsulated by the object being created. By default, a separate interface is created
for an object, whose name consists of the object name and the prefix "I." The de-
veloper may, however, attribute to the object an existing COM interface. You may
specify the required interface manually or select it from the Interface Selection
Wizard dialog box, which opens when you press the List button.
(eee
Interface Type Library Path
DAD :da0360.dl CAAProgram Fits,
“DBEngine da0360.dl CA\Progiam Files.
Enor da0360.dl C:\Program Files.
Enors da0360. dl CAProgiam Fits,
Propeity d30360. dl CAProgiam Files
Properties d30360.dl CAProgiam Files.
Recordset da0360.dl C:\Program Fils,
Recordsete dac360 dl CAPragram Fies,
‘Wrkspace da0360.dl Program Fes,
Workspaces d30360.dl CAProgiam Files,
Connection d30360. dl C\Progiam Fes,
Connection dac360.dl C:\Program Fes.
_TableDet da0360.dl CAProgram Files,
TableDets d30360.dl i CANProgram Fes,
Field da0360.dl Program Files... 9
‘ >
Library
[Finished loading interfaces
Fig. 1.7. The Interface Selection Wizard window
The Description string editor contains a description of the object.
The Include Type Library checkbox handles the creation of the type library for an
object. It is inaccessible, however, unless you select an existing interface for the
object.38 Part |: COM and COMs+ Applications
>_>
The Mark Interface OleAutomation checkbox makes an object suitable for use
within Automation. If the checkbox is checked, a dispinterface is created for the
object interface (see Chapter 2, "Automation").
The set parameters of the simplecom object are shown in Table 1.1.
Table 1.1. Parameters of the TSimpleCOM Object
Parameter Value
Class Name SimpleCOM
Instancing tmApartment
Threading Model ciMultiInstance
Implemented Interfaces ISimpleCoM
Description ‘Simple COM Object Sample
Include Type Library Enabled
Mark Interface OleAutomation Disabled
After the parameters are set and the OK button is pressed, the project will be sup-
plemented with a new module that includes the source code of the new class.
The source code of the COM object module immediately after its creation is pre-
sented in Listing 1.2.
Listing 1.2. Source Code of the TSimpleCOM COM Object Imme¢
after Creation
unit uSimpleCoM;
{SWARN SYMBOL PLATFORM OFF}
interface
uses
Windows, ActiveX, Classes, ComObj, ClientInProcCOM_TLB, StdVcl;
type
TSImpleCOM = class (TTypedComObject, ISImpleCoM)
protectedChapter 1: COM Mechanisms in Delphi 39
>
end;
implementation
uses ComServ;
initialization
TIypedComObjectFactory.Create (ComServer, TSImpleCOM, Class_SImpleCOM,
ciMultiInstance, tmApartment) ;
end.
As is seen in the declaration, the nearest ancestor of the new object class is the
TTypedComObject class, since, according to the initial settings of the object, a class
factory is created for it.
The object constructor Tsimplecom contains the name of the class factory and the
basic interface, which has the same name as the class. There is also a class factory
constructor in the initialization section. It should be noted that the class of the
class factory was created automatically.
To provide for the server's operation, the comsexv module and the comserv variable
indicating the COM server class are used. The variable is used in the constructor
of the class factory.
You can see that only the Tsimplecom class is described in the module of the
COM object, this class being the basis of the COM object's functioning.
The interfaces 1simplecom and coClass are declared in the type library in the
ClientInProcCOM_TLB.PAS file.
The source code of the library was created automatically, together with the new
COM object, and is saved in the ClientInProcCOM_TLB.PAS file.
Listing 1.3. Type Library Source Code for the TSimpleCOM COM Object
unit InProcCOM_TLB;
[CHARS OHSU OES HC USUI UIE D HOODEO REDE EED IDS HERE EEE //
// WARNING40 Part |: COM and COMs+ Applications
>
Mf =
// The types declared in this file were generated from data read from a
// Type Library. If this type library is explicitly or indirectly (via
// another type library referring to this type library) re-imported, or the
// ‘Refresh’ command of the Type Library Editor activated while editing the
// Type Library, the contents of this file will be regenerated and all
// manual modifications will be lost.
Wererestecececcecererteccevectecrctccrceccscecrscecrccrcrtercetectectecrsca@ 7]
// PASTLWIR : $Revision: 1.130.3.0.1.0 $
// Pile generated on 11.03.2002 17:46:03 from Type Library described below.
1] OBSESSED BOOS OS ORDO SONS SOB OSU Sau uniuniuniunionianinnon 7 /
// Type Lib: C:\Mark\Bin\Books\d6WEB\DemosE\01_1\Server\InProcCOM.t1b (1)
// LIBID: {246D6A47-D078-498C-8E4C-9D94B76BFE22}
// LID: 0
// Helpfile:
// Depndbst:
// (1) v2.0 stdole, (C:\WINNT\System32\stdole2.t1b)
// (2) v4.0 StaVCL, (C:\Program Files\Common Files\SuperCollect \stdvel40.d11)
Wrerre ree rereereeteccececrertetccrecrectetscrrcterteccrtercetetrcrretera#7]
{STYPEDADDRESS OFF} // Unit must be compiled without type-checked pointers.
{$WARN SYMBOL_PLATFORM OFF}
{$WRITEABLECONST ON}
interface
uses Windows, ActiveX, Classes, Graphics, StdVCL, Variants;
| OBR B SORES IESE S EIS IIDIDSIEISEI SESS DEI SISSIES / /Chapter 1: COM Mechanisms
Delphi 41
>
// GUIDS declared in the TypeLibrary. Following prefixes are used:
// Type Libraries : LIBID_xxxx
Mt CoClasses : CLASS_xxxx
iy DISPInterfaces + DIID_xxxx
// Non-DISP interfaces: IID_xxxx
[COO O CROSSES UICC OIC III EOI III IEEE EER /
const
// TypeLibrary Major and minor versions
InProcCOMMajorVersion = 1;
InProcCOMMinorVersion =
LIBID_InProcCOM: TGUID = '{246D6A47-D078-498C-8E4C-9D94B76BFE22} ";
IID_ISimpleCOM: TGUID = '{76247090-8BCD-4AAD-8EE3-EEE4DE68872C} ";
CLASS_SimpleCOM: TGUID = '{E76AEOBE-14DD-431D-B923-324EBA987770}";
type
| HOB BESIDE BIOS IIIS IIIS III SIE IOI III DIS ISIS IIIS IIS IIIS SIDI]
// Porward declaration of types defined in TypeLibrary
])] COBDS EOD C SIDED OSI ISOS EIDE SII DEITIES IDE III IETS //
ISimpleCOM = interface;
J] COB DEDUCE BISBEE SISSIES IIIS IIE III SEI IIIS //
// Declaration of CoClasses defined in Type Library
// (NOTE: Here we map each CoClass to its Default Interface)
/)/ HODES BO SESS SESS SDI ESOS S IIIS IOSD EOD DIDO /
SimpleCOM = ISimpleCoM;
Teeereeereretererereccrcerrretcriecerscecrsterecerrc etre ccs ccrrecersg 7]
// Interface: ISimpleCoM42 Part |: COM and COMs+ Applications
_>
// Flags: (0)
// GUID: {16247090-8BCD-4AAD-8EE3—EBE4DB68872C}
WT Breerereerecetrcceereceeccctrctecerrecerertcrcecercecercetereresrcetsg 7]
ISimpleCOM = interface (IUnknown)
['{76247090-8BCD-4AAD-8EE3-EEE4DE68872C} ']
end;
Were cere cectees cree tececectsescstscrtrcrtrcetrstecscsercrtrccersecrseg 7]
// The CoSimpleCOM Class provides a Create and CreateRemote method to
// create instances of the default interface ISimpleCOM exposed by
// the SimpleCOM CoClass. The functions are intended to be used by
// clients wishing to automate the CoClass objects exposed by the
// server of this typelibrary.
CoSimpleCOM = class
class function Create: ISimpleCoM;
class function CreateRemote(const MachineName: string): ISimpleCoM;
end;
implementation
uses Combi;
class function CoSimpleCOM.Create: ISimpleCoM;
begin
Result := CreateComObject (CLASS_SimpleCOM) as ISimpleCom;
end;
class function CoSimpleCOM.CreateRemote(const MachineName: string): ISimpleCOM;
beginChapter 1: COM Mechanisms in Delphi 43
>
Result := CreateRemoteComObject (MachineName, CLASS_SimpleCOM) as ISimpleCoM;
end;
The type library contains the automatically generated cuip of the Isimplecom
interface, and then comes the declaration of the interface itself. The coSimplecom
class provides for the operation of the simplecom object's interfaces. So far,
it contains only one interface. There are two functions created in this class:
Create is used for work on a server within the process and on a local server;
CreateRemote is used for work on a remote server. Both functions return pointers
to the Isimplecom interface.
Creation of Interface Methods
Having created the new COM object, we are now going to develop the methods
used to implement its functions. Let's suppose the object is intended to perform
simple mathematical functions.
It is necessary to show how the several interfaces of one object are used. Other-
wise, the COM object will not differ in principle from a regular object. Let the first
and second interfaces implement a number of simple linear and power functions.
We already have the first interface, Isimplecom, which was created together with
the COM object in the previous step.
In order to create the second interface and all the required methods, we use the
Type Library Editor (Fig. 1.5). For this purpose, we have to perform the following
actions.
1. Select the Isimplecom interface from the hierarchical list and press the New
Method button on the toolbar (Fig. 1.7).
2. Rename the resulting method (Method1) in the hierarchical list to Linearx.
Note that only methods’ names may be specified in the Attributes menu.
3. Go to the Parameters menu in the right part of the Editor and specify the type
long in the Return Type list (the type returned by the result method) instead of
the standard HResult type.44 Part |: COM and COMs+ Applications
FossdeoslHo- (Oe) F-
Era laPiosCM Atibutes | Paemeers | Flgs | Text |
Name: Method?
wo: ft
eS
Fig. 1.7. Creating new method in the Type Library Editor
FOSbaeeds He O8/5
a ea i Attibutes Parameters | Flags | Text |
te Linea
c= RSs essai nea
Parameters
i Moder J
long. fn]
Add Delete | Mover | Move Down
Modied =
Fig. 1.8. Setting the method's parameter
4. Now press the Add button on the bottom of the page to add the first parameter
to the method. In the resulting line in the Name cell, change the name of the
parameter to AValue (Fig. 1.8).
5. The data type of the parameter is specified in the Type column. The drop-down
list of the column contains all permissible types of data. For our parameter,
select the long type.
6. The type of the parameter itself is specified in the Modifier column. After
pressing the button, the Parameter Flags window appears (Fig. 1.9), in which youChapter 1: COM Mechanisms in Delphi 45
>
can specify the parameter type and, if necessary, the default value. In our case,
the parameter must be incoming, so the In checkbox should be ticked in the
window.
x
Pig Fie
PT Ow F Optional
T BetVal [~ Has Defaul Value
Dou Yolue
ree
Caneel Hep
Fig. 1.9. The Parameter Flags window
Once the above operations are completed, the method declaration is finished.
Now let's perform the same sequence of operations for another method: squarex.
In order to create the source code for these methods, press the Refresh button
on the toolbar of the Type Library Editor. As a result, the source code for the cre-
ated methods will automatically appear in the simplecom object module. Only
the corresponding mathematical functions are left to be entered into them
(Listing 1.4).
To create the second interface and all the required methods, we use the Type
Library Editor. To do this, we have to perform the following actions.
Press the Interface button on the toolbar. The new interface appearing in the hier-
archical list is to be renamed 1simplecom2.
A curp for the new interface is generated on the Attributes page (Fig. 1.10), and
Unknown — the name of the ancestor interface — is to be selected from the Parent
Interface list. Besides this, the Dual and OleAutomation checkboxes should be dis-
abled on the Flags page. For the newly created interfaces, these checkboxes are
switched on by default, since the ActiveX dynamic library was selected as the basis
of the in-process server (see Chapter 3, “ActiveX Components").
Then, the Linear2x and Cubex methods are created using the same operations as
for the new isimplecom2 interface described above.46 Part |: COM and COMs+ Applications
ios)
Po Sbasos He-|daiS- E :
Oop sep Atte | Raps | Tex |
ara Name: |SimpleCOM2
cup: [ESPERO Ga TDS RS DTT —
@ SimplecoM aa ee
ee ee
Help SEE PPPS SPEER EEE’
Help Sting: |
Help Context: a)
Help Sting Context:
Modied Zi
Fig. 1.10. Creating a GUID for the new interface
nix
PSCSARSIS/HS-\Ds
Pe InProclOM
OM (Sipe OM ‘toutes Implements | Flags | COM*| Tet |
be Lineaed Terace, Bu Souce [Detaik | Rewicted] Viato_]
‘& Square TSimleCOM (7624708 Fake Tue Fae Feleo
@ \SinpleCOM2 |SimpleCOM2 (GAF2806. Fake Fale False: False
[modfied |
Fig. 1.11. Connecting the interface with the COM objectChapter 1: COM Mechanisms in Delphi 47
>
Now the created interface is to be connected to the Tsimplecom COM object.
For this, the object is selected from the hierarchical list, and on the right side of
the window, the Implements page should be chosen. Here you can find a list of all
the interfaces of the object, though there will be only one basic interface here as of
yet. Click the right mouse button on the list and select the Insert Interface com-
mand from the pop-up menu. Select the 1simp1ecom2 interface from the list that ap-
pears, and press OK. The new interface will appear within the object.
To conclude, press the Refresh button to create the corresponding source code.
Then you may shift to the uSimpleCOM.PAS module and create a very simple
source code for the new methods (Listing 1.4). Each method performs a simple
arithmetical operation.
Listing 1.4. The uSimpleCOM Module of the SimpleCOM Object
after the Second Interface Has Been Created
unit uSimpleCoM;
{SWARN SYMBOL_PLATFORM OFF
interface
uses
Windows, ActiveX, Classes, ComObj, InProcCOM_TLB, StdVcl;
type
TSimpleCOM = class (TTypedComObject, ISimpleCoM, ISimpleCoM2)
protected
function LinearX(AValue: Integer): Integer; stdcall;
function SquareX(AValue: Integer):
Integer; stdcall;
function CubeX(AValue: Integer): Integer; stdceall;
function
near2X(AValue: Integer): Integer; stdcall;
end;
implementation
uses ComServ;48 Part |: COM and COMs+ Applications
>
function TSimpleCOM.LinearX(AValue: Integer): Integer;
begin
Result := AValue;
end;
function TSimpleCOM.SquareX(AValue: Integer): Integer;
begin
Result := AValue*AValue;
end;
function TSimpleCOM.Cubex(AValue: Integer): Integer;
begin
Result := AValue*AValue*AValue;
end;
function TSimpleCOM.Linear2X(AValue: Integer): Integer;
begin
Result := AValue*2;
end;
initialization
TlypedComObjectFactory.Create(ComServer, TSimpleCOM,
ciMultiInstance, tmApartment) ;
end.
Class_SimpleCoM,
It should be noted that the code presented does not suggest any connection what-
soever between the methods and the various interfaces. This division is done in the
Type Library (Listing 1.
appeared in the TSimp1ecom class declaration.
But note that a second interface — Isimplecomz — hasChapter 1: COM Mechanisms
Delphi 49
g 1.5. Source Code of the Type
Has Been Created (Comments Deleted)
unit InProcCOM_TLB;
{$TYPEDADDRESS OFF}
{SWARN SYMBOL_PLATFORM OFF}
{SWRITEABLECONST ON)
interface
uses Windows, ActiveX, Classes, Graphics, StdVCL, Variants;
const
InProcCOMMajorVersion = 1;
InProcCOMMinorVersion = 0;
LIBID_InProcCOM: TGUID = '{246D6A47-D078-498C-8E4C-9D94B76BFE22}"j
IID_ISimpleCOM: TGUID = '{76247090-8BCD-4AAD-8EE3-EEE4DE68872C} ";
IID_ISimplecoM2: TGUID
CLASS_SimpleCOM: TGUID
"{3AF28063-3513-11D6-A805-B081814EB47E} ';
* {E76AEOBE~14DD-431D-B923-324EBA987770}";
type
ISimpleCOM = interface;
ISimpleCOM2 = interface;
SimpleCOM = ISimpleCoM;
ISimpleCOM = interface (IUnknown)
["{76247090-8BCD-4AAD-8EE3-EEE4DE68872C} ']
function LinearX(AValue: Integer): Integer; stdcall;
function SquareX(AValue: Integer): Integer; stdcall;
end;
ISimpleCOM2 = interface (IUnknown)50 Part |: COM and COMs+ Applications
>
[ (3AF28063-3513-11D6-A805-B081814EB47E} ']
function Linear2X(AValue: Integer): Integer; stdcall;
function CubeX(AValue: Integer): Integer; stdcall;
end;
CoSimpleCoM = class
class function Create: ISimpleCom;
class function CreateRemote(const MachineName: string):
ISimpleCoM;
end;
implementation
uses ComObj;
class function CoSimpleCOM.Create: ISimpleCOM;
begin
Result := CreateComObject (CLASS_SimpleCOM) as ISimpleCOM;
end;
class function CoSimpleCOM.CreateRemote(const MachineName: string): ISimpleCOM;
begin
Result := CreateRemoteComObject (MachineName, CLASS_SimpleCOM) as ISimpleCOM;
end;
end.
Let's now look at the changes in the Type Library.
First, the declaration of a second interface, Isimplecom2, has appeared in the li-
brary. Its curp has been generated and the necessary variables have been declared.
Second, the methods created in the Type Library Editor are declared in the cor-
responding interfaces. It should be noted that the long data type specified for the
method parameters in the Type Library Editor was converted into the integer
type.Chapter 1: COM Mechanisms in Delphi 51
>
In-Process Server Registration
After completing the development of the object and the compiling of the
library, the library must be registered as the server. In order to do this, the
Register ActiveX Server command is selected from the Run menu of the Delphi
main window. The in-process server information is located in the System
Registry.
When the Tsimplecom COM object is created, the InProccom dynamic library is
loaded into the address space of the client application and operates as the in-
process server. As a result, if the client application has references to the interfaces
of the COM object, it may use the methods of these interfaces.
We are now finished creating the simplecom object, and may turn to the client
part of the project.
Using In-Process COM Server Interfaces
The simple client InProccom application, which has only the fmMain form, will play
the part of the client application. It is necessary to adjust it so that the perform-
ance of the two interfaces of the created object can be tested (Fig. 1.12).
Taal
Interface ISimpleCOM using Interface ISimpleCOM2 using
InialValue x: 1° a IniatVabiex 1° a
¥’ Run iSimpleCOM methods ¥’ un SimpleCOM2 methods
Linear Functions Resule NULL Linear? Functions Recut NULL
Square Functions Result’ NULL Cube Functions Result NULL
Bi Coxe
Fig. 1.12. The main form of the Client InProcCoM project
The TspinEdit components are intended to specify the x value for the functions
performed in the methods of the isimplecom and ISimplecom2 interfaces. The
values from these visual components will be communicated to the methods as pa-
rameters.52 Part |: COM and COMs+ Applications
>
The buttons should call the methods of the object interfaces. To display the result,
the TLabel components are used.
In order for the client application to make use of the interfaces of the
InProcCoM in-process server, it is necessary to add the InProcCOM_TLB.PAS
Type Library file to the client's project.
After this, you can declare two variables for the Tsimplecom COM object inter-
faces:
Interfacel: ISimpleCcoM;
Interface2: ISimplecom2;
ig 1.6 shows the source code of the client that allows you to use the COM
object methods.
ing 1.6. Section Implementation of the uMain Module
of the ClientinProcCOM Project
implementation
uses InProcCOM_TLB;
var
Interfacel: ISimpleCoM;
Interface2: ISimpleCoM2;
{$R *.dfm}
procedure TémMain.FormShow(Sender: TObject);
begin
Interfacel := CoSimpleCOM.Create;
Interfacel.QueryInterface(ISimpleCOM2, Interface2);
end;
procedure TfmMain.bbRunSimpleCOMClick (Sender: TObject) ;
begin
laLinearRes .Caption
laSquareRes.Caption :
= IntToStr (Inter facel .LinearX(seSimpleCoMValue.Value) );
Int ToStr (Inter facel .SquareX (seSimpleCOMValue.Value) );
end;
procedure TfmMain.bbRunSimpleCOM2Click (Sender: TObject);Chapter 1: COM Mechanisms in Delphi
begin
JaLinear2Res Caption
IntToStr (Inter face2.Linear 2x (seSimpleCOMValue.Value) )j
laCubeRes Caption := IntToStr (Interface2.CubeX(seSimpleCOM2Value.Value) ) ;
end;
end.
When the form is opened in the Formshow method handler, the coClass
of the Tsimplecom class — CoSimplecom — is created. Its constructor returns the
pointer to the main 1Simplecom interface to the Interface1 variable of the Isim-
plecom type. All these operations are performed by the Inproccom dynamic library.
Its record in the System Registry can be found by the global identifier from
the InProcCOM_TLB.PAS Type Library.
Since any interface is a descendant of 1unknown, you may use the QueryInterface
method to get the pointer to the second interface. The identifier of the required
interface and the variable to which the pointer will be returned are indicated in the
method's parameters. In our example, this is the 1simplecom2 interface and the
Interface2 variable.
After these operations are completed, the client gets pointers to both interfaces
of the simplecom object. To execute the methods of these interfaces, the method
handlers of the bbRunSimplecom and bbRunSimplecom2 buttons are used.
The result is displayed on the form by appropriating the results of the work of
the interface methods of the Tsimplecom COM object of the InProcCOM.DLL
in-process server.
Summary
COM technology is a basic concept necessary to understand the topics considered
in this book. It helps the objects created by various programming means in various
programming languages to interact.
The key COM notions are "object," "interface," and "class factory.”
© An interface combines several methods and provides access to them via a gen-
eral pointer.
G An object encapsulates one or several interfaces and presents them to the clients.
G A cclass factory creates instances of COM objects.Chapter 256 Part |: COM and COMs+ Applications
>_>
technologies. One of them — Automation — was created some time ago and
T he COM technology considered in Chapter J is the basis of many other
is widely used in the most common client applications of the Windows OS.
This technology enables certain applications to use the functions of other applica-
tions. The interaction is performed using specially created interfaces. The basic
interface is Dispatch.
Such widely used applications as Word, Excel, and Internet Explorer support Auto-
mation. It is not necessary to list the advantages of this technology. For example, any
user program that has information on the functions provided by the Microsoft Word
Automation interface gains access to the numerous text editing options.
From the programmer's point of view, creating applications that use Automation is
very similar to the technique considered in Chapter I for COM objects. This
chapter deals with the peculiarities of programming in Delphi using Automation.
Among them, the following issues are stressed.
G Automation interfaces
© Automation objects
© Creation of the Automation server
© Creation of the Automation controller
© Automation examples
Basic Concepts of Automation
As a COM-based technology, Automation provides certain applications with the
functions of other applications. Naturally, this is done with interfaces. These in-
terfaces are contained by various Automation objects which, in turn, are located in
Automation servers.
The main difference between Automation and its ancestor technology COM is the
way methods of interfaces are called.
In COM, the pointers to the interface methods are contained in the special Virtual
Tables only (see Fig. 1.3). This access technique is called early binding.
In Automation, access to the interface methods is performed both in the tradi-
tional COM way, and using a special method that searches for the called method
by a special identifier. This access technique is called /ate binding.Chapter 2: Automation 57
In all other respects, Automation technology is similar to COM technology. Let's
consider the main entities used in Automation.
Automation Interfaces
All Automation interfaces have a common ancestor, the interface IDispatch,
which provides the unique functionality of the technology. Of course, the common
ancestor of any Automation interface is the 1Unknown interface (see Chapter 1,
"COM Mechanisms in Delphi").
The interfaces considered here may be divided into two groups — dispinterfaces
and dual interfaces.
The [Dispatch Interface and Dispinterfaces
The basic interface of Automation is the tDispatch interface. This is a quite com-
mon COM interface, although it has some methods that are very important for
Automation. Like all similar interfaces, it is implemented by a virtual table of
pointers to its methods (see Chapter 1,"COM Mechanisms in Delphi").
The distinguishing feature of the Ipispatch interface is the special method Invoke,
which provides for the calls of other methods. Besides this method, 1Dispatch has
three important methods. These are GetTypeInfoCount, GetTypeInfo, and
Get IDsO£Names. All of them provide the late binding mechanism.
The object operating with the 1pispatch interface must define an additional inter-
face where the methods that can be called via the Invoke method are listed. This
interface is called a dispatching interface, or dispinterface.
It does not use a virtual table. Instead, the Invoke method is used to access
the methods. A realization of the Invoke method is the case operator: here
the required method is selected based on the method passed in the identifier pa-
rameter.
With this call, each method must have a unique identifier, called the dispatch iden-
tifier (DISPID).
The methods of the dispinterfaces are somewhat limited in the data types of the
parameters; the values of all parameters are converted to the variant type when the
methods are called. The reverse procedure takes place when the results are re-
turned.58 Part |: COM and COMs+ Applications
Unknown
Internal pointer to aaa
the virtual table fe
Pointer 1
Pointer 2 Case DISPID of
IDispatch.Invoke(DISPID) DISPID1: | Method 1
Invoke __@->) pispip2: | Method 2
DISPIDN:| Method N
Client Pointer N
Automation Object
Fig. 2.1. The call mechanism of the dispinterface methods
Let's look at the late binding mechanism (Fig. 2.1).
When the client needs to use a method of the Automation interface, it calls the
Invoke method, passing it the dispatcher identifier of the required method as a pa-
rameter. Then the Automation object finds the pointer to the Invoke method using
the virtual table and calls it. The tnvoke method starts a search for the required
method by its dispatcher identifier. The pointer found is returned to the main in-
terface and used for processing the client's query.
Like any other interface, each dispinterface has its own unique identifier. It is used
in cases when there are several dispinterfaces in the application. As the interface
IDispatch is the descendant of the 1Unknown interface, you can use the inherited
method QueryInterface to call another dispinterface.
As a result, interaction between Automations applications is simplified — only one
virtual table is needed for successful performance of the Dispatch interface; all
other methods are called by the mechanisms described above.
There is one more advantage of dispinterfaces. You can directly define the methods
that return and specify property values. The method returning the value of the property
reads only. The method specifying the value writes only.Chapter 2: Automation 59
Dual Interfaces
However, dispinterfaces perform somewhat slower than standard interfaces. Therefore,
there is another type of interface used in Automation applications — a dual interface. Its
methods may be called by both Invoke and the virtual table.
The dual interface must be a descendant of IDispatch. Its virtual table includes
references to three methods of tunknown, four methods of ipispatch, and the
methods of the corresponding dispinterface.
Type Library
Since the client should possess information on the dispatcher identifiers of the
methods called when the dispinterface methods are called, the Type Library plays
an important role when creating interfaces (see Chapter 1, "COM Mechanisms in
Delphi"). The method identifiers provided in the Type Library, as well as informa-
tion on the parameters and the interfaces themselves, are used by the client when
the Invoke method of the 1Dispatch interface is called.
Automation Interface Marshaling
Marshaling and demarshaling mechanisms are also used to support the operation
of local and remote Automation servers. Marshaling is responsible for packing the
call parameters, while demarshaling unpacks them (see Chapter 1, "COM Mecha-
nisms in Delphi").
Like any interface with a virtual table of methods, the 1Dispatch interface and its
descendants make use of two additional objects for marshaling and demarshaling —
proxy and stub (see Chapter 1, "COM Mechanisms in Delphi"). This pair, however,
can only process the method parameters of one interface. But the methods of the
dispinterface called by the Invoke method may have parameters that differ greatly
from those of the basic interface.
In this case, additional processing is done by the Invoke method itself, which con-
verts the parameters into variables of the variant type. The marshaling of dispin-
terfaces has a special feature: to pass the different-type parameters via the Invoke
method, the parameters are converted to the variant type and back. The methods
of the Automation interfaces thus use a limited set of data types suitable for con-
version through the variant.
It is only because no proxy and stub are needed in the dispinterface marshaling
that dispinterfaces support late binding.Part |: COM and COMs+ Applications
Automation Object
Within the Automation technology (just as in COM) all the methods are contained
in the Automation object, which may have one or more interfaces. Like the COM
object, the Automation object operates within the Automation server providing the
clients with the methods of its interfaces. All Automation object interfaces should
be dispatching interfaces.
Automation Server
An application that provides its functions via the 1pispatch interface is called an
Automation server. The server must include an Automation object — a common
COM object that has an 1Dispatch interface and dispinterfaces.
The Automation server is an executable file — an application or dynamic library
that may contain one or several objects of the same or different classes. Like COM
servers, Automation servers can be of three types: In-process server, Local server,
and Remote server (see Chapter 1,"COM Mechanisms in Delphi").
Automation Controller
Any standard COM client referring to the Automation server via IDispatch is
called an Automation controller.
Automation Implementation in Delphi
Let's see how Automation technology is implemented in Delphi. The basis of any soft-
ware implementation of Automation in Delphi is the Tautoobject class — a descen-
dant of the Tcomobject class. It provides the methods of the 1Dispatch interface. Ac-
cordingly, the Automation objects have all the capabilities of Delphi COM objects.
When Automation applications are developed, type information is used. The
Automation Type Library is created in the same way as for common COM appli-
cations (see Chapter 1,"COM Mechanisms in Delphi").
Automation Interfaces
All the Delphi interfaces used in the Automation (both standard and those created
by developers) have a common ancestor — the 1Dispatch interface. Besides theChapter 2: Automation 61
>_>
usual methods, it is possible for them to declare properties with read and write
methods. This option is implemented in the Type Library Editor.
The [Dispatch Interface
For Automation technology, IDispatch is the most important interface. It is de-
clared in the System.pas module:
IDispatch = interface (IUnknown)
[* (00020400-0000-0000-c000-000000000046}"]
function GetTypeInfoCount (out Count: Integer): HResult; stdcall;
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo):
Result; stdcall;
function GetIDsOfNames (const IID: TGUID; Names: Pointer;
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
stdeall;
function Invoke(DispID: Integer; const IID: TGUID; LocaleID:
Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer):
HResult; stdcall;
end;
Besides the methods inherited from the tunknown interface, it contains four addi-
tional methods.
The main method of the interface
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer):
HResult; stdcall;
is used to call all the methods of the dispinterface. For this purpose, you must pass
the dispatcher identifier of the called method in the pisprp parameter.
The curp of the interface whose method is called is defined by the 11D parameter.
The LocaleID parameter specifies the localization of the passed values. The Flags
parameter specifies whether a usual method is called, or if it is a reading and writing
method of the property. The params parameter contains the pointer to an array of
the TDispParams type that contains the parameters of the called method.
The varResult parameter returns the value passed back by the called method.
If an error occurs during the call, the ExcepInfo parameter contains information
about it. The argerr parameter contains the index of a parameter wrongly speci-
fied from the Params array.62 Part |: COM and COMs+ Applications
_>_
The method
function GetTypeInfoCount (out Count: Integer): HResult; stdcall;
tells us whether this object can return the type information during execution. The
Count parameter returns the number of object interfaces supporting the type in-
formation.
The method
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult;
stdeall;
returns the pointer to the 1TypeInfo Type Library interface (if there is one). If the
method is successfully executed, the typeInfo parameter contains a pointer to the
ITypeInfo structure containing the type information.
The method
function GetIDsOfNames (const IID: TGUID; Names: Pointer; NameCount,
LocaleID: Integer; DispIDs: Pointer): Hresult; stdcall;
returns the dispatcher identifier by the specified name of the dispinterface method.
The name array is passed in the Names parameter with the number of NameCount
elements. The DispIDs pointer defines the array of identifiers.
Dispatching Interfaces
When full-fledged Automation servers that controllers may refer to without full
information on the server functions are created, it is necessary to implement dis-
patching interfaces. These are descendants of the 1Dispatch interface, but the
keyword dispinterface should be used when they are declared:
ISomeDisp = dispinterface
[' {ABE41D32-9FFE-94D0-4395-1120AA74DE39}"]
function Method1: OleVariant; dispid 1;
function Method2: OleVariant; dispid 2;
procedure Method3; dispid 3;
procedure Method4; dispid 4;
end;
Each method of the dispatching interface must have a dispid unique identifier.
The properties must have a read-only or write-only attribute. The parameters and
the returned results of the methods can only have the following types: Byte,
Currency, Real, Double, Real48, Integer, Single, Smallint, AnsiString,
ShortString, TDateTime, Variant, OleVariant, WordBool.Chapter 2: Automation
The methods GetipsofNames and Invoke are used to call the methods and
properties of dispatching interfaces. Dispatching interfaces operate using late
binding.
Dual Interfaces
The dual interfaces used in Delphi Automation applications are created based
ON Dispatch. When declared, their methods must use the safecall directive.
The parameters and the returned results of the methods can only have the follow-
ing types: Byte, Currency, Real, Double, Real48, Integer, Single, Smallint, An-
siString, ShortString, TDateTime, Variant, OleVariant, and WordBool.
These interfaces provide for both the binding done by application compilation
(virtual table), and binding during execution (or late binding) used in Automation
(the Invoke method).
The first three methods of the dual interface are inherited from tunknown, the next
four methods are inherited from Dispatch, and then come the individual methods
of the interface.
Automation Object
The capabilities of the Automation object in Delphi are contained by the class
TAutoObject. It inherits from the classes TcoMobject and Trypedcomobject.
TObject
TComObject
TTypedComObject
TAutoObject
Fig. 2.2. Ancestor hierarchy of the TAutoObject class
Let's consider its main properties and methods. Below in this chapter, you'll find
an example of creating an Automation object.Part |: COM and COMs+ Applications
The TAutoObject Class
Since the TAutoobject class of the Automation object contains the IDispatch in-
terface, it has four methods similar to those of the interface. These are Invoke,
GetTypeInfo, GetTypeInfoCount, and Get IDsOfNames. And, of course, as a descen-
dant of the Tcomobject class, the class of the Automation object inherits three
methods of the tunknown interface (see Chapter 1,"COM Mechanisms in Delphi").
To create an Automation object instance, as for any COM object, a class factory
is needed. The factory used in the created object is indicated by the property
property AutoFactory: TAutoObjectFactory;
When creating the object, the method is called
procedure Initialize; override;
It is empty for the class TAutoobject, although developers may cover it in the
class-descendants and implement the necessary actions there when the object is
initialized.
Use the Delphi Repository to create a new Automation object. After the
Automation Object icon is clicked, the window for Automation object creation ap-
pears on the ActiveX page.
xi
Coflass Name:
Instancings [Mutpleinstmce
Threading Model [apatmet ==
> Options
TP Generate Event support code
Fig. 2.3. Automation object creation window
The field CoClass Name is used for naming the new object.
The Instancing list defines the way the server is started. The Threading Model list defines
the means of interaction between the object and the controllers. The possible values of
these parameters are described in detail in Chapter 1,"COM Mechanisms in Delphi."Chapter 2: Automation 65.
The checkbox Generate event support code adds an additional interface to the
code of the Automation object being created, which enables management of the
object events from the server.
A CoClass with the same name as the Automation object and two interfaces are cre-
ated at the same time as the Automation object inself. One interface has the same name
as the object inherited from the IDispatch interface, and the other is the dispinterface.
Like any other COM object, the cociass of the Automation object has two class
methods — create and createRemote — for creation of an instance of the object's
class factory.
For example, when the Automation object someAuto is created, the following type
will be generated in the new module:
type
TSomeAuto = class (TAutoObject, ISomeAuto)
protected
{ Protected declarations }
end;
which inherits from the class TAutoobject and implements the methods of the in-
terface of the same name created to support the new object.
Like COM objects, the class factory instance is created in the initialization section
of the new object module:
initialization
TAutoObjectFactory.Create(ComServer, TSomeAuto, Class_SomeAuto,
ciMultiInstance, tmApartment) ;
The new interface is declared in the Type Library (if it already exists in the pro-
ject, then the new entities are added to the Library; otherwise it is created):
ISomeAuto = interface (IDispatch)
[' (9C2A1E76-391E-11D6-A80B-BA576D9FB37E} ']
end;
TSomeAutoDisp = dispinterface
[' {9C2A1E76~-391E-11D6-A80B-BA576D9FB3 7E} ']
end;
Note that a dispinterface is also created for the main interface. Later, when meth-
ods are added to the main interface, the same methods will be added to the
dispinterface, and dispatching identifiers will be assigned to them.66 Part |: COM and COMs+ Applications
>_>
Also, here, in the Type Library, coclass is declared for the new Automation
object.
Automation Object Event Handling
If the checkbox Generate event support code is activated during the creation of the
Automation object, the event handling mechanism will be added to the new object.
This mechanism allows the events generated in the Automation object to be passed
to the controller.
To allow the server to refer to the controller, the Automation object (and any
other COM object) must support at least one outgoing interface.
The property
property EventSink: IUnknown;
represents this interface. It allows for transfer of the events happening within the
object to be passed to the controller.
An object with outgoing interfaces is called an object with connection and must sup-
port the IconnectionPointContainer interface, from which the client can find
available outgoing interfaces.
Each outgoing interface must have a special corresponding object that implements
its methods. This object is called a sink.
Let's consider the source code that is generated automatically when the Generate
event support code checkbox is activated.
Listing 2.1. Automation Object Type Library (Comments Excluded)
unit Project1_TLB;
interface
uses Windows, ActiveX, Classes, Graphics, OleCtrls, StdVCL;
const
LIBID_Project1: TGUID = '{(B2211452-E7A8-11D2-80F3-008048A9D587)";
IID_ISomeObj: TGUID = '{B2211453-E7A8-11D2-80F3-008048A9D587} ';
DIID_ISomeObjEvents: TGUID = '{B2211455-E7A8-11D2-80F3-008048A9D587} ";Chapter 2: Automation
67
>_>
CLASS_SomeObj: TGUID = '{B2211457-E7A8-11D2-80F3-008048A9D587) ';
type
ISomeObj = interface;
ISomeObjDisp = dispinterface;
ISomeObjEvents = dispinterface;
SomeObj = ISomeObj;
ISomeObj = interface (IDispatch)
[ (B2211453-E7A8-11D2-80F 3-008048A9D587} "]
end;
ISomeObjDisp = dispinterface
[ (B2211453-E7A8-11D2-80F3-008048A9D587) "]
end;
ISomeObjEvents = dispinterface
[ (B2211455-E7A8-11D2-80F3-008048A9D587) "]
end;
CoSomeObj = class
class function Create: ISomeObj;
class function CreateRemote(const MachineName: string): ISomeObj;
end;
implementation
uses ComObj;
class function CoSomeObj.Create: ISomeObj;
begin
Result := CreateComObject (CLASS_SomeObj) as ISome0bj;
end;
class function CoSomeObj.CreateRemote (const MachineName: string): ISomeObj;
begin
Result := CreateRemoteComObject (MachineName, CLASS_Some0bj) as ISomeObj;
end;
end.68 Part |: COM and COMs+ Applications
>
Listing 2.1 shows that there are three interfaces created for the Automation object.
The dual interface tsomeobj is designed to work with the TSsomeobj object.
The dispatching interface IsomeObjDisp provides for the Automation. The
ISomeObjEvents interface provides for object event handling in the controller.
Listing 2.2. An Automation Object Module
unit Unit2;
interface
uses ComObj, ActiveX, AxCtrls, Project1_TLB;
type
TSomeObj = class(TAutoObject, IConnectionPointContainer, ISomeObj)
private
FConnectionPoints: TConnectionPoints;
FEvents: ISomeObjEvents;
public
procedure Initialize; override;
protected
property ConnectionPoints: TConnectionPoints read
FConnectionPoints
implements IConnectionPointContainer;
procedure EventSinkChanged (const EventSink: IUnknown); override;
end;
implementation
uses ComServ;
procedure TSome0bj.EventSinkChanged(const EventSink: IUnknown) ;
begin
FEvents := EventSink as ISomeObjEvents;
end;
procedure TSome0bj. Initialize;
begin
inherited Initialize;
FConnectionPoints := TConnectionPoints.Create (Self) ;Chapter 2: Automation
>_>
if AutoFactory.EventTypeInfo <> nil then
FConnectionPoints .CreateConnectionPoint (AutoFactory.EventIID,
ckSingle, EventConnect) ;
end;
initialization
TAutoObjectFactory.Create(ComServer, TSomeObj, Class_SomeObj,
ciMultiInstance, tmApartment) ;
end.
Within the object itself, the interface 1someobjEvents is passed to the FEvents
field. To have it initialized, the EventsinkChangea method is automatically cov-
ered. The ISomeObjEvents interface is based on the options of the interface
IConnectionPointContainer, which is included in the class declaration.
Note that the initialization of additional objects is carried out by the covered
method Initialize.
The program code presented in Listings 2.1 and 2.2 is created automatically.
If the developer wants to pass the event to the controller, he or she must do the
following:
OA method of the event handler interface must be created for each event
handler.
© The coclass is created for the event handler interface.
G When the Automation object is initialized, an event handler class instance must
be created.
Let's consider the above steps in more detail with examples from the source code
of the Automation object TsomeObject.
Creating Methods (Event Analogs)
The interface of the event handler should have methods that correspond to the
events handled:
ISomeObjEvents = dispinterface
[' (B2211455-E7A8-11D2-80F3-008048A9D587} "]
procedure Open; dispid 1;
procedure Close; dispid 2;
end;70 Part |: COM and COMs+ Applications
>
Creating CoClass
You should create the coclass for the event handler interface manually. This class
should contain the selected events and allow them to be handled in the Invoke
method:
TSomeEventSink = class(TInterfacedObject, IUnknown, IDispatch)
private
(eel
Fowner : TObject;
FOnOpen : TNotifyEvent;
FOnClose : TNotifyEvent;
fev}
protected
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
public
ea)
property OnOpen:
property OnClose:
TNotifyEvent read FOnOpen write FOnOpen;
TNotifyEvent read FOnClose write FOnClose;
end;
function TSomeEventSink.Invoke(DispID: Integer; const IID: TGUID;
LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
ArgErr: Pointer): HRESULT;
begin
case dispid of
5: if Assigned(FOnOpen) then FOnOpen(FOwner) ;
6: if Assigned(FOnClose) then FOnClose(FOwner) ;
end;
end;
Initializing the Automation Object
During initialization, the Automation object should create the TSomeEventSink class
instance and use the analogous events of the event handler class in its own events.
TSomeObject = class
privateChapter 2: Automation 71
>_>
eo
FEventSinl
TSomeEventSink;
function GetOnOpen: TNotifyEvent;
procedure SetOnOpen(Value: TNotifyEvent) ;
function GetOnClos
TNotifyEvent;
procedure SetOnClose (Value: TNotifyEvent);
public
constructor Create;
{eee}
published
ee
property OnOpen: TNotifyEvent read GetOnOpen write SetOnOpen;
property OnClose: TNotifyEvent read GetOnClose write SetOnClose;
end;
i
function TSomeObj.GetOnOpen: TNotifyEvent;
begin
Result := FEventSink.OnOpen;
end;
procedure TSomeObj.SetOnOpen (Value: TNotifyEvent) ;
begin
FEventSink.OnOpen := Value;
end;
function TSomeObj.GetOnClose: TNotifyEvent;
begin
Result := FEventSink.OnClose;
end;
procedure TSome0bj.SetOnClose(Value: TNotifyEvent) ;
begin
FEventSink.OnClose := Value;
end;72 Part |: COM and COMs+ Applications
_>
Class Factory
The class factory for the Automation object is created based on the
TAutoObjectFactory class. The Automation class factory inherits from the standard
COM class factory.
TObject
TComObjectFactory
TTypedComObjectFactory
TAutoObjectFactory
Fig. 2.4. Hierarchy of the ancestor classes of the Automation class factory
The Automation class factory is created by the constructor
constructor Create(ComServer: TComServerObject; AutoClass: TAutoClass;
const ClassID: TGUID; Instancing: TClassInstancing;
ThreadingModel: TThreadingModel = tmSingle);
As you can see from the declaration, assigning its parameters is done the same as
for the parameters of the COM class factory constructor (see Chapter 1, "COM
Mechanisms in Delphi").
After creation, the class factory provides access to information on the object dis-
patching interface. The property
PInterfaceEntry = “TInterfaceEntry;
TInterfaceEntry = packed record
IID: TGUID;
VTable: Pointer;
I0ffset: Integer;
ImplGetter: Integer;
end;
property DispIntfEntry: PInterfaceEntry;
returns a reference to the corresponding structure.Chapter 2: Automation 73
The method
function GetIntfEntry(Guid: TGUID): PInterfaceEntry; virtual;
will return information on the dispinterface specified by the curp parameter.
The type information of the dispinterface created by the Automation object factory
is returned by the property
property DispTypeInfo: ITypeInfo;
To support the event handler interface considered above, the following property is
used
property EventIID: TGUID;
which returns the curp of the event handler interface. The type information for
this interface is contained in the property
property EventTypeInfo: ITypeInfo;
The TAutoIntfObject Class
If an Automation object needs to be used within an application, then the TAuto-
Intf£Object class may be used, which does not require a class factory for class in-
stance creation. It supports the IDispatch and IsupportError Info interfaces, and
performs a number of functions intrinsic to the class factory.
Its constructor
constructor Create(const TypeLib: ITypeLib; const DispIntf: TGUID);
creates an Automation object with the global identifier pispint¢ on the basis of
information on the TypeLib type.
After creation, the dispinterface of the Automation object is available through the
property
property DispIID: TGUID;
The type information of the dispinterface is provided by the property
property DispTypeInfo: ITypeInfo;
And the structure TInterfaceEntry is available through the property
property DispIntfEntry: PInterfaceEntry;74 Part |: COM and COMs+ Applications
>_>
Automation Server
How is an Automation server created in Delphi? Any application with a normally
functioning Automation object and that is properly registered in the OS becomes
an Automation server.
According to the COM canons, three types of Automation servers may be
created:
© In-process
© Out-process
© Remote
The Automation object is contained in the TAutoobject class, which is a descen-
dant of the base COM class — tcomobject. To include the Automation object into
an application, use the wizard opened by clicking on the Automation Object icon
on the ActiveX page in the Delphi Repository. This is described below.
COM objects, and therefore Automation objects, contain the program code for the
methods of the interfaces connected with them.
The application is registered in the OS as an in-process Automation server by the
command Register ActiveX Server from the Run menu. To cancel registration, use
the command Unregister ActiveX Server.
The out-process server is registered by the /regserver key when the application is
launched. To cancel registration, use the /unregserver key.
After registration, any application with information on the methods of its interface
may call the server.
Automation Controller
The Automation controller manages the server using the methods of the server in-
terfaces. Information on the interfaces is usually distributed in the form of Type
Libraries (see Chapter 1, "COM Mechanisms in Delphi," for details on Type Library
languages). Any application with access to the corresponding Type Library may
become the Automation controller.Chapter 2: Automation 75
To include a Type Library into the project, the command Import Type Library in
the Project menu may be used. Or, if the library was created in Delphi, it is possi-
ble to include the name of its file in the uses section of the module.
i
Ineo Type Lay |
|Aweferu 1.0 Type Library (Version 1.0)
BdeMTSD spenser Listy (¥etsion 1.0)
bindaush 1.0 Type Libra (Verson 1.0)
\
Borland standard VCL ype Ibraty (Version 4.0),
CaleSveRpeCpp 1.0 Type Librars Version 1.0)
CaleSvofRpevb [Version 30) ‘
CAWINNT\Syetema2\STOVCLI2 DLL
Balete page: [Activex il
Unit diname: [ESFrogramn Fles\Boianc\DebhiBiimpots
Search path: [s{DELPHISLb S{DELPHNBin SIDELPHINmpor
Instat |[Ceste unt | Cancel Help
I Generate Component Wiapper
Fig. 2.5. The Import Type Library dialog box
The list in the Import Type Library dialog box (Fig. 2.5) contains all the Automa-
tion servers registered in the system. A Type Library may be imported for any of
them. The list may be edited using the Add and Remove buttons.
Next, it is necessary to create the shellclass instance of the interface (coclass) in the
controller and apply the needed methods of the Automation server where necessary.
If the Type Library is inaccessible, the developer should do the following:
1. The Automation object is created in the controller application based on a vari-
able of the olevaliant type. For this, the following function is used:
function CreateOleObject (const ClassName: string): IDispatch;
which returns a pointer to the Dispatch interface. The className parameter
defines the name of the Automation class. This is a program identifier, which76 Part |: COM and COMs+ Applications
>_>
is the Automation server, and corresponds to the identifier of the cLs1p class.
This function is used both for in-process and out-process servers. Naturally, the
server for which the object is created should be registered.
2. After the Automation object is created, the methods of its interfaces may be
used in the controller application. If the server does not function on the first
call, it is automatically started by the OS.
Example of an Automation Application
Now let's consider the simplest example of creating a server and an automation
controller. The out-process server will be the model. Since it is important to dem-
onstrate the performance principle of the Automation mechanism in Delphi, the
server will be limited to performing the simplest functions. Afterwards, you may
add real operations to this extremely simple working server.
Automation Server
The creation of a server begins with the opening of a regular application project
in Delphi. The developer may program any necessary operations for it. The appli-
cation becomes the Automation server after it is included in the Automation
object.
The Object Repository is used for this purpose. Select the Automation Object icon
on the ActiveX page.
While creating the new Automation object, the Automation Object Wizard dialog
box appears (see Fig. 2.3). The process of object creation is considered in detail
above in this chapter. The tsimple object was created strictly according to this
description (Table 2.1).
Table 2.1. TSimple Object Parameters
Parameter Value
CoClass Name Simple
Instancing tmApartment
Threading Model ciMultiInstance
Generate Event support code EnabledChapter 2: Automation
Projects |
New
ee
Active Form
= =i
ee ARGOS aca YaST oela LCSE
Active | Multtier | Proiectt | Forms | Diabgs |
et ele
Active Server ActiveX Conttol ActiveX Library
Object
COMObect = COM+Event Property Page
Object
Fig. 2.6. Delphi Repository during creation of the Automation object
Creating and setting the properties and methods of the object interfaces is done in
the Type Library Editor. Here the declarations for two methods were created:
Methodl and Method2. See Chapter 1,"COM Mechanisms in Delphi," for more de-
tails on the creation of interface methods.
Note that the Isimple interface of the Automation object fully conforms to the
requirements of the dual interface (see Listing 2.3). Along with it, the dispatcher
interface ISimpleDisp is automatically created. In all other respects, the source
code of the Type Library corresponds to a standard COM object.
Listing 2.3. Type Library of the Tsimp1e Automation Object
(Comments Excluded)
unit DemoAutoServer_TLB;
interface
uses Windows, ActiveX, Classes, Graphics, OleCtrls, StdVCL;
const
LIBID_DemoAutoServe:
TGUID = '{F290AEE0-E858-11D2-80F3-008048A9D587} ";
IID_ISimple: TGUID = '{F290AEE1-E858-11D2-80F3-008048A9D587}";
CLASS_Simple: TGUID = '{F290AEE3-E858-11D2-80F3-008048A9D587} ';78 Part |: COM and COMs+ Applications
>_>
type
Isimple = interface;
ISimpleDisp = dispinterface;
Simple = ISimple;
ISimple = interface(IDispatch)
[ {F290AEE1-E858-11D2-80F3-008048A9D587} ']
procedure Methodl; safecall;
procedure Method2; safecall;
end;
ISimpleDisp = dispinterface
[ {F290AEE1-E858-11D2-80F3-008048A9D587} "]
procedure Methodl; dispid 1;
procedure Method2; dispid 2;
end;
CoSimple = class
class function Create: ISimple;
class function CreateRemote(const MachineName: string):
end;
implementation
uses Com0bj;
class function CoSimple.Create: ISimple;
begin
Result := CreateComObject (CLASS_Simple) as ISimple;
end;
Isimple;
class function CoSimple.CreateRemote (const MachineName: string): ISimple;
begin
Result := CreateRemoteComObject (MachineName, CLASS_Simple) as ISimple;
end;
end.Chapter 2: Automat
Like the conventional COM interface, the dual automation interface should have
a special corresponding shell object in which the properties and methods of the
CoClass interface are implemented.
1g 2.4. TSimple Automation Object Module
unit SimpleOb3;
interface
uses
ComObj, ActiveX, Dialogs, DemoAutoServer_TLB;
type
TSimple = class(TAutoObject, ISimple)
protected
procedure Method1; safecall;
procedure Method2; safecall;
implementation
uses ComServ;
procedure TSimple.Method1;
begin
ShowMessage('Method2 executed");
end;
procedure TSimple.Method2;
begin
ShowMessage(' Method2 executed");
end;
initialization
TAutoObjectFactory.Create(ComServer, TSimple, Class_Simple,
ciMultiInstance, tmApartment) ;
end.80 Part |: COM and COMs+ Applications
>_>
After the development of the server application is complete, it is necessary to reg-
ister it in the OS. To do this, just launch the application with the /regserver key.
It can be placed in the Start Parameters dialog box (the Parameters command
in the Run menu).
Automation Controller
The Automation controller application should have information on the capabilities
of the interfaces provided by the Automation server. For this, it should import
the Type Library or include the corresponding modules into the uses section.
The second method works well if the server and controller are developed simulta-
neously. It is used in our example, as both the server and controller are located
in one group of projects.
The form of the controller includes only two buttons, which will be used to call the
methods of the Automation server.
ing 2.5. Automation Controller Main Form Module
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, DemoAutoServer_TLB;
type
TContrForm = class (TForm)
Labell: TLabel;
BitBtnl: TBitBtn;
BitBtn2: TBitBtn;
dure FormCreate (Send:
dure BitBtn1Click (Sende:
dure BitBtn2Click (Sender:
public
Simple: ISimple;
var ContrForm: TContrForm;
implementationChapter 2: Automation 81
>
{$R *.DFM}
procedure TContrForm.FormCreate (Sender: TObject);
begin
Simple := CoSimple.Create;
end;
procedure TContrForm.BitBtnlClick (Sender: Tobject);
begin
Simple.Methodl;
end;
procedure TContrForm.BitBtn2Click (Sender: TObject);
begin
Simple .Method2;
end;
end.
When the controller form is created, the coclass instance for the Automation
object is created in the Formcreate method. The constructor returns a pointer to
the simple interface.
By pressing the buttons of the Automation controller form, the appropriate meth-
ods of the Isimple interface of the DemoAutoServer server are called. If the server
is not working at the moment, the OS starts it automatically.
Summary
Automation is based on COM technology. The basis of Automation is the
IDispatch interface.
The Automation server provides clients with its interfaces, and should be registered
in the OS. The Automation controller makes use of the server's capabilities.
To organize interaction between the server and the controller, the interface should
comply with a number of conditions. This may be either a dispatcher interface or a
dual interface.Chapter 384 Part |: COM and COMs+ Applications
>_>
Automation technologies. These objects provide clients with sets of stan-
dard (defined within the corresponding specification) and custom inter-
faces. The methods and properties of these interfaces may be used by other applica-
tions. Therefore, a programmatic COM interface is implemented for such objects.
T he previous chapters dealt with the issues of object creation for COM and
Now it is time to expand the field of application of the COM objects to the visual
user interface of the application. Indeed, if the object provides its methods for use,
why not do the same for its visual functionality?
Of course, the visual presentation of the COM objects still needs to be imple-
mented. This task is carried out by another child COM technology — ActiveX.
The processes of development and distribution of controls based on COM objects —
ActiveX controls — are done within the ActiveX technology. These controls can
implement various additional functions of the visual interface of the applications,
providing users with additional features and options.
The Delphi Component Palette contains the ActiveX page, where there are four
components that are ActiveX controls. In Delphi notation, all the controls avail-
able for the programmer in the development environment are components.
In Delphi, the ActiveX controls do not differ from traditional components, so in
the future we will call them ActiveX components.
Let's turn to the history of this issue.
Component programming began with Microsoft Visual Basic. It was in this envi-
ronment that 16-bit Visual Basic eXtension (VBX) modules were used for the first
time. The developers were very interested in this feature, and over a short period
of time, the options of this programming environment were considerably expanded
thanks to the appearance of hundreds and thousands of new VBXs.
The idea behind component development was elaborated, and soon other products
(e.g., Delphi) were built on this principle. When 32-bit programming was intro-
duced, the component approach was developed even further, and now has a com-
mon standard — ActiveX. Alongside with these processes, Internet and WWW
technologies were actively developing. It turned out that the first controls based on
COM objects were a poor match for these new fields of application. The core of
the problem was that, according to the specification, the first controls (then called
“OLE controls") had to support too many standard interfaces. And most of these
interfaces had never been used in most of the controls. As a result, the controls
were cumbersome, and took too long to load using Internet technologies.Chapter 3: ActiveX Components 85.
>_>
The requirements were also considerably simplified. As a result, the controls
became more compact and spurred the development of a range of adjacent tech-
nologies, and were named ActiveX controls.
From the programmer's point of view, ActiveX is a black box with its own proper-
ties, methods, and events. ActiveX is integrated into Delphi so well that you can
know nothing about COM objects but still make exhaustive use of their features.
From the point of view of the COM objects model, the ActiveX control is a server
that supports automation, is implemented as a dynamic library, is executable in the
address space of your application, and allows visual editing.
This chapter deals with the following issues:
CG How ActiveX controls work
© How to include and use in Delphi the preprepared controls created by third-
party developers
G How to convert standard Delphi components into ActiveX components
CG How to convert a form into an ActiveX form
How ActiveX Controls Work
The ActiveX control is a COM object that operates most often in the in-process
server (see Chapter 1,"COM Mechanisms in Delphi"). Of course, there are a num-
ber of additional requirements imposed on the controls by the ActiveX technology,
but all of them are conditional upon the necessary mechanisms for the loading and
functioning of the controls.
As part of the visual interface of the application, the controls must interact with
the application by giving it the relevant information about the user's actions, react
to these actions independently, and correctly change their own state according to
the application's commands.
For example, if the user presses an ActiveX control's button, this control must de-
pict this button being pressed and inform the application about it. If, for example,
the application considers it necessary to make its controls inaccessible, then our
control must also be able to do this, reacting to the command of the application.
From a formal point of view, the ActiveX control only has to support the tUnknown
interface. However, to implement the principal functions intrinsic by definition to
the ActiveX controls, it must implement a subset from the set of basic ActiveX
interfaces. These are listed in Table 3.1.86 Part |: COM and COMs+ Applications
Method’s call
IDispatch
Fig. 3.1. Dispinterfaces are used to access ActiveX controls
Table 3.1. Basic Interfaces of the ActiveX Controls
Interface Description
IUnknown
Container
IoleObject Provides the most important methods for the interaction
of the control with the container.
I0leControl ‘Supports the basic functionality of the control: property
settings and the reaction to events.
TOleInPlaceObject This interface manages the activation and deactivation
of the control in the container.
I0leInPlaceActiveObject Provides a direct interaction channel between the con-
trol and the frame window of the container with this
control.
TObjectSafety This interface provides two ways to set the safe mode
of the control. If this mode is set, then, when loaded via
the Internet, the browser will obtain information on
whether this control is safe both for the environment into
which it is loaded and for the data used.
IPersistPropertyBag Provides methods for saving and loading the individual
properties of the control into storage.
IPersistStreamInit Provides for the initialization of the storage area used
for saving the property values of the control, based on
the use of the streams within the storage area.
IPersistStorage Provides the control with a reference to the storage area
instance provided by the container for saving the con-
trol's properties.
continuesChapter 3: ActiveX Components 87
>
Table 3.1 Continued
Interface Description
IPerPropertyBrowsing Provides methods for passing the property values of the
control to the property page.
ISimpleFrameSite Allows the control to function as a frame site for placing
other ActiveX Controls.
IsPecifyPropertyPages Contains a method that returns data on the availability
and support of the property page by this control.
IviewObject Provides the interaction between the control and the
container when it is necessary to inform the container of
changes to the appearance of the control.
IViewObject2 An extension of the IviewObject interface. Provides
information on the size of the drawing area of the control.
Interface methods are accessed by the Invoke method of the Dispatch interface.
Thus, the main tasks for ActiveX controls may be defined as follows:
© Registering the control
G Implementing the user interface and visualization
CG Providing its own methods and properties to the application
G Notifying the application of the events occurring
G Getting information on the application's properties
It is the ActiveX technology which defines and implements the component's
behavior's standards in the user interface of the application.
Let's see how these tasks are accomplished.
Containers and ActiveX Control Registration
The tasks listed above are accomplished thanks to the fact that all ActiveX controls
provide standard interfaces. These interfaces are able to support COM objects
in the specified state and efficiently interact with an application that uses this
control.
An application that has ActiveX components, and that is able to use them,
is called a control container. Users work with many programs without even sus-88 Part |: COM and COMs+ Applications
>_>
pecting that they are full-fledged ActiveX containers. These programs include Mi-
crosoft Internet Explorer, Microsoft Visual Basic, and Microsoft Word. Of course,
Delphi is also a container.
Just as ActiveX controls are not obliged to support all the standard interfaces, the
containers don't have to allow the application of every ActiveX component.
If the application can only implement a small part of the features of the ActiveX
component — for example, its visualization and its reaction to the user's actions —
it is still a container. The list of basic interfaces for ActiveX containers is shown
in Table 3.2.
Table 3.2. Basic Interfaces of ActiveX Controls
Interface Description
TAdviseSink Gives the container the option to keep track of changes to
controls, bound documents, etc.
IOleClientSite By means of this interface, attached objects may obtain
information on container resources
1OleDocumentSite Used for activation of OLE Documents
IOleInPlaceSite Manages the interaction between the container and the site
of its control
IOleUIObjInfo Provides information on the container controls for use in
the dialogs for setting the container properties
The containers also implement their functions by providing methods of standard
interfaces.
For an ActiveX control to be used by an application, it must be registered on the
computer. For this purpose, the ActiveX controls enter data on themselves into the
System Registry during installation.
Providing Methods
All standard interfaces of the containers and ActiveX components are dispatch in-
terfaces that originate from IDispatch. Correspondingly, any calls of the methods
of controls from the container are carried out by the Invoke method of the
IDispatch interface (see Chapter 2, Automation"). This means that in reality, the
container uses only the 1Dispatch interface, and nothing else.Chapter 3: ActiveX Components 89
>
To obtain information on the methods of the dispinterfaces, the container uses the
Type Library provided by the control.
Events
When the ActiveX control reacts to the user's actions, it sends the container in-
formation about the events, making it possible for the container to react. In this
way, the ActiveX controls perform their main assignment.
So how does notification work in ActiveX?
When an event occurs, the control simply calls the necessary method of the con-
tainer. In this case the container provides its own interfaces, and the control is
the client. For this simple schema to work, the control should have a reference
to the container interface.
The control should provide each event with a corresponding method of the con-
tainer interface. The set of these methods is called the oufgoing interface. Any
regular interface is called an incoming interface.
In this case, however, a complete, correctly written container should have the
methods for all possible events, for any component types. It is obvious that such an
extensive implementation procedure would be extremely ineffective.
Therefore, the ActiveX controls must provide the container with a Type Library
with interface descriptions — both incoming and outgoing. After the container
obtains information about its methods that are being used by the ActiveX control
in the outgoing interfaces, it must dynamically implement the Dispatch. Invoke
method that supports these methods.
Properties
Since the ActiveX control use dispinterfaces, they can use the properties of inter-
faces (see Chapter 2, "Automation").
There are two methods which may be defined for the property — the reading
method and the writing method. Like all other dispinterface methods, they are
called via Dispatch. Invoke.90 Part |: COM and COMs+ Applications
Property Pages
To standardize the display of the ActiveX control properties, property pages are im-
plemented in them. These property pages are a set of objects that can display the
values of the control's properties in the form of a dialog box with a multipage notebook.
If the control supports the 1specifyPropertyPages interface, then, with the only
method of this interface — GetPages — the container can obtain information
about the properties provided by this interface. Next, the container creates a
frame object to map the property page. Then the frame object creates a property
sheet object. Each such object will correspond to a page of the property pages note-
book.
The frame object and the property sheet object interact via two interfaces: the first
is the 1PropertyPageSite interface, and the second is the 1PropertyPage interface.
If the user has changed the value of the property in the property page dialog, then
the corresponding tab object passes the new value to the control.
Licensing
For ActiveX controls, just as for regular applications, illegal use is a major prob-
lem. Licensing is used to solve this problem.
When a control is created, a license key is generated for it, which is saved in a file
with the LIC extension. This is static key creation.
If the control is loaded via the Internet, a license key batch file is created for it,
having the LPK extension. A reference to this file should be included on the cor-
responding HTML page. When the page is opened, the browser loads the batch
file, obtains the key, and compares it with the existing key. This method of licens-
ing is called dynamic.
When a control instance is created, the container checks for the presence of a li-
cense to use this control on this computer with the help of the methods of the
IClassFactory2 interface.
The GetLicInfo method checks for the presence of a global license. If there is
such a license, then the element is created in the usual way. If there is no such
license, then the container must call the createInstanceLic method, where the
number of the license must be passed as the parameter. The class factory of the
control checks the license key and, if it is valid, creates an instance of the control.Chapter 3: ActiveX Components 91
>_>
In order to obtain the license key to use the above method, the container must use
another method — Request Lickey — which returns the required key (if there is one).
Implementing ActiveX Components
in Delphi
The ActiveX components in Delphi use the same mechanisms as regular
COM objects. The classes of the components inherit from the class ancestor and
interface ancestor. The ActiveX components provide clients with their interfaces
that descend from IDispatch. To create ActiveX component instances, class
factories are used. Information about the component is contained in the Type
Library.
The common ancestor of all classes of ActiveX controls in Delphi is the
TActiveXxControl class. It contains basic functions that enable ActiveX components
to interact with the container and provide for the component's reaction to events.
It also contains all the basic interfaces of the ActiveX control.
TObject j
TComObject
TTypedComObject
TAutoObject
TActiveXObject
Fig. 3.2. Class hierarchy of ActiveX components in Delphi
As is obvious from Fig. 3.2, the hierarchy of the class ancestors of ActiveX com-
ponents shows that the ActiveX technology is based on the functionality of its par-
ent technologies — COM and Automation.92 Part |: COM and COMs+ Applications
>
The process of creating ActiveX components in Delphi is considerably simplified
thanks to the wizard that generates the entire source code necessary for the new
component, its class factory, and the Type Library.
Additionally, a special class superstructure, which is created for ActiveX compo-
nents, allows the ActiveX component to interact with its container, Delphi. Class
superstructures are generated from the tolecontrol class.
The ActiveX Component Class
Thus, the direct ancestor of any ActiveX component in Delphi is the TActivexcontrol
class. Originating from the basic classes of the COM objects and Automation, it
encapsulates for its descendants the mechanism of using the dispinterfaces neces-
sary for the operation of ActiveX controls.
Additionally, it contains all the ActiveX basic interfaces listed in Table 3.1.
Since the class factory is used to create an ActiveX component instance, there
is no need to use the class constructor. But while creating the component, the
method
procedure Initialize; override;
will be called, which you can override and use for setting initial values and for ini-
tialization of auxiliary objects in the ActiveX control.
The class provides a ready-to-use mechanism for notification upon actions with
the component. If it is necessary to change the value of a property in response to
the user's actions, you must make sure that this property can be changed. For this
purpose, the overload method is used:
function PropRequestEdit (const PropertyName: WideString): Boolean; overload;
function PropRequestEdit (DispID: TDispID): Boolean; overload;
You must specify the PropertyName property name or its DispIp dispatch identifier
as a parameter. If the property can be edited, the function returns True.
When you change the value of a property, you can call the overload method:
procedure PropChanged(const PropertyName: WideString); overload;
procedure PropChanged(DispID: TDispID); overload;
passing it the name of the PropertyName property or its Disprp dispatch identifier
as a parameter. The method will generate the onchanged event.Chapter 3: ActiveX Components 93
>_>
When using the above methods, try to use the dispatch property identifiers, since the
names of the properties are handled within methods of the TActivexControl class and
are still converted into identifiers.
After the component instance is created, you can access the VCL class instance
that is implementing your ActiveX component. For this, the following property is
used:
property Control: TWinControl;
To obtain pointers to the required interfaces, you can use the method
function ObjQueryInterface(const IID: TGUID; out Obj): HResult; override;
Note that this is only an implementation of the corresponding 1Unknown interface
method, which is of course supported by the TActivexContro1 class.
The Class Factory of the ActiveX Component
To create ActiveX component instances in Delphi, a class factory is used.
Its functionality is inherited from the analogous Automation technology
factory. The class factory for ActiveX components is contained in the
TActiveXControlFactory Class.
The Delphi Environment as an ActiveX
Container
The Delphi environment serves as the ActiveX container.
When a new ActiveX component is created in Delphi using the ActiveX Control
Wizard, a special shell class is automatically created. Its purpose is to allow all the
interface methods of the ActiveX component to interact with the development en-
vironment. And thus the mechanism for adequate presentation and behavior of
ActiveX controls within the Delphi environment is implemented.
The main functions of the class superstructure are implemented in the
TOleControl class. Its properties and methods provide the necessary control
of the ActiveX component. As a rule, there is no need for the developer to use
the methods of this class directly.94 Part |: COM and COMs+ Applications
>_>
Registration of ActiveX Components
To register ActiveX controls created in Delphi, you can use the capabilities of ei-
ther the OS or the development environment.
In the OS, you can use the regsvr32.exe system utility.
In Delphi, use the tregsvr utility, which is supplied with Delphi in the source texts
(..\Demos\ActiveX\TRegSvr folder), or you can use special menu items.
To register a component as an ActiveX control, select the Register ActiveX Server
command from the Run menu.
To unregister the control, select the Unregister ActiveX Server command from the
Run menu.
Using Preprepared ActiveX Components
A number of ActiveX components created by third-party developers (chartFx,
vsSpel11, and others) are supplied with Delphi. However, you will probably have to
install other controls as well, to expand the capability of the environment. Let's
describe this path and the pitfalls that may occur along the way.
Installing Preprepared ActiveX Controls
First of all, you must ensure that the information about the ActiveX controls you
require is available in the System Registry. Select the Component/Import ActiveX
Control menu item. In the list at the top of the dialog box (Fig. 3.3), information
on all the ActiveX controls registered in the system is presented. The line under the
selection list shows what files they are contained in.
As a tule, all the ActiveX controls distributed on large-scale software are automati-
cally registered during installation. If you have downloaded the file from
the Internet or borrowed it from a friend, then there are at least three ways
to register:
G By pressing the Add button in the Import ActiveX dialog box
OC By calling the regsvr32.exe system utility (included in the OS)
© By calling the tregsvr utility, which is delivered with Delphi in the source texts
(..\Demos\ActiveX\TRegSvr folder)Chapter 3: ActiveX Components 95
>_>
Riosot Incmet Trance Cond coors} veron C)
Microsoft Multimedia Contral §.0 (SPZ] (Version 1.1)
Microsoft Sergt Contral 7.0 (Version 1.0)
| CAWINNTSSystem324shdocves dll
Ty/ebBronser v1
TwebBiowser
Balette page: [ActiveX a
Unit dirname: [C:\Program Fles'Boriand\De(phi6\Imports
Search path [8(DELPHINLib-S{DELPHINBin:S{DELPHI]\Impor
Fig. 3.3. ActiveX control import dialog box
The control is unregistered by pressing the Remove button.
‘When you have stopped at one of the controls, all the classes that this file contains
will be listed in the Class names field. Then you are offered the option to select
the "landing ground" in the Palette page list for the future use of the control se-
lected in Delphi — the Component palette page.
If you decide to continue working with this ActiveX control, the next step is to
create a wrapper. This file is the description of the type library — all the methods,
properties, and events contained in the control — written in the Object Pascal
language. Its name is formed from the name of the ActiveX and the "_TLB.PAS"
line. Pressing the Create Unit button creates this file (for purposes of fami-
liarization and testing), and pressing the Install button continues the installation
process.
The control being installed should be located in one of the packages. You may se-
lect one of the existing DPK files or create a new one. After the package is com-
piled, the ActiveX control is at your disposal.96 Part |: COM and COMs+ Applications
>_>
If you are going to perform numerous experiments with all possible ActiveXes, deter-
mining to what extent they fit your needs, you should create a new package. You will
also need a new package if you have already selected the ActiveX and do not want to
waste any more memory.
Uninstalling Preprepared ActiveX Controls
The uninstallation of an ActiveX control should begin by removing the references
to it. Open the required DPK file, remove the unnecessary controls, and
re-compile the package. Then the control (or controls) will disappear from the
Component palette page. If you are sure that this control is not being used by any-
one, you may remove the information about it from the System Registry (with the
Remove button in the Import ActiveX dialog box). Only then may you remove the
OCX file or the dynamic library containing it from the disk.
An Example of Installing the TWebBrowser Control
As an example, let's examine the use of the TwebBrowser control from the Micro-
soft Internet controls (SHDOCVW.DLL file). This is the core of the Microsoft
browser, which is most likely already installed on your computer. Additionally,
using it is easy and only requires some common sense.
After you have completed all the above operations, you will have the
SHDOCVW_TLB.PAS file to work with. This file, among other things, contains
the description of the IwebBrowser interface. Apart from the interface, this file
contains the description of the TwebBrowser_v1 Delphi object, which is the super-
structure over the IwebBrowser interface, and contains methods and properties that
correspond exactly to the methods and properties of the interface.
Why is this done? Well, it's done so that all the added-in components have a
common foundation and provide them with similar behavior from the point of
view of Delphi. Basically, all the class superstructures above ActiveX are generated
by the ToleControl class. This class contains the oleobject property, which is a
reference to the interface you need. However, it is more reliable to call the meth-
ods in the usual way — via the methods of the corresponding Delphi class.
The methods of the IwebBrowser interface execute the main operations for the
Internet browser. All the names of the properties and methods of twebBrowserChapter 3: ActiveX Components 97
>_>
are simple and clear, aren't they? The 1web8rowser interface is described in the
SCHDocW.pas module, and the methods of the interface are presented in Listing 3.1.
Listing 3.1. IWebBrowser Interface Declaration in the SCHDocVv.pas Module
IWebBrowser = interface (IDispatch)
[' {BAB22AC1-30C1-11CF-A7EB-0000COSBAEOB} ']
procedure GoBack; safecall;
procedure GoForward; safecall;
procedure GoHome; safecall;
procedure GoSearch; safecall;
procedure Navigate(const URL: WideString; var Flags: OleVariant;
var TargetFrameName: OleVariant; var PostData: OleVariant; var
Headers: OleVariant); safecall;
procedure Refresh; safecall;
procedure Refresh2(var Level: OleVariant); safecall;
procedure Stop; safecall;
function Get_Application: IDispatch; safecall;
function Get_Parent: IDispatch; safecall;
function Get_Container: IDispatch; safecall;
function Get_Document: IDispatch; safecall;
function Get_TopLevelContainer: WordBool; safecall;
function Get_Type.
function Get_Left
WideString; safecall;
Integer; safecall;
procedure Set_Left (pl: Integer); safecall;
function Get_Top: Integer; safecall;
procedure Set_Top(pl: Integer); safecall;
function Get_Widt!
Integer; safecall;
procedure Set_Width(pl: Integer); safecall;
function Get_Height: Integer; safecall;
procedure Set_Height (pl: Integer); safecall;
function Get_LocationName: WideString; safecall;
function Get_LocationURL: WideString; safecall;
function Get_Busy: WordBool; safecall;
property Application: IDispatch read Get_Application;
property Parent: IDispatch read Get_Parent;
property Container: IDispatch read Get_Container;
property Document: IDispatch read Get_Document;
property TopLevelContainer: WordBool read Get_TopLevelContainer;98 Part |: COM and COMs+ Applications
>_>
property Type_: WideString read Get_Type_;
property Left: Integer read Get_Left write Set_Left;
property Top: Integer read Get_Top write Set_Top;
property Width: Integer read Get_Width write Set_Width;
property Height: Integer read Get_Height write Set_Height;
property LocationName: WideString read Get_LocationName;
property LocationURL: WideString read Get_LocationURL;
property Busy: WordBool read Get_Busy;
end;
Now, let's use the data on the capabilities of the twebBrowser ActiveX component
that we have obtained in practice. We will try to create our own simple browser in
the space of a few minutes.
We first create a new project — a common application — and use its form.
The twebBrowser ActiveX component is transferred onto the form, and we follow a
simple recipe.
Ingredients: a form, a selection list, and several buttons of your choice (see
Fig. 3.3). The buttons may correspond to commands that you are accustomed to,
like in Microsoft Internet Explorer, for example.
For the buttons in our example, we create click handlers, including in them the
appropriate methods from the IwWebBrowser interface provided by the TwebBrowser
ActiveX component.
Listing 3.2. Method Handlers of Events for a Browser Based
on the TWebBrowser Control
procedure TForml.BackToolButtonClick (Sender: TObject);
begin
WebBrowser_V11.GoBack;
end;
procedure TForml.GoToolButtonClick (Sender: TObject);
var ovl,ov2,ov3,ov4: OleVariant;
begin
WebBrowser_V11.Navigate (ComboBoxl.Text, ov1, ov2, ov3,0v4) i
end;
procedure TForml.FrwToolButtonClick (Sender: TObject);
beginChapter 3: ActiveX Components 99
yr
WebBrowser_V11.GoForward;
end;
procedure TForml.StopToolButtonClick (Sender: TObject);
begin
WebBrowser_V11.Stop;
end;
procedure TForml.HomeToolButtonClick (Sender: TObject);
begin
WebBrowser_V11.GoHome;
end;
procedure TForm1.SrchToolButtonClick (Sender: Tobject);
begin
WebBrowser_V11.GoSearch;
end;
(ceaifiet =} Back Forward Stop Home Search Go |
Borland vs,
[Products] Downloads Services | Support Partners | News & Events | Company | Developers
ae Delphi”
1 x
Enterprise
© Case Study | OProductitews | OEvents | © Training
| © Products Next-generation e-business development [Product Informatie
‘fDelphi 4 BH data sheet
Enterprise ‘@ a
Professional ie
Berea | ‘@@ System Reaur
DataSnep se '@ Fesiwes & Be
Previous Version $@ Feature Matt:
Awards 4 > Next generation e-business [© Trial Versic
ee development
Fig. 3.4. A browser compiled based on Microsoft ActiveX controls100 _— Part I: COM and COM+ Applications
>_>
Here is the result. After the compilation and startup of the application, we have
an efficient Internet browser with a set of the most important functions (see
Fig. 3.3). And all we did was link the methods of the ActiveX component with
buttons.
Tf you put a little more effort in, you will have a full-fledged browser. It may be of
use to you while debugging applications; examples will be given later on.
Developing Custom ActiveX Components
COM-based technologies are as attractive as they are complicated. Or, to be more
precise, as complicated as they are laborious. Building objects on your own takes
a lot of time. Therefore, both advanced users and beginners do this in Delphi with
a set of wizards. We will say outright that there is no wizard for "direct" creation,
or creation of ActiveX components from scratch. You may choose one of two op-
tions instead — converting either one of the common Delphi components or a
whole form into a control.
Converting Delphi Components into ActiveX
Components
The first of the features provided by Delphi is conversion of any window compo-
nent (descendant of the twincontro! class) into an ActiveX component. This wiz-
ard is available by clicking the ActiveX Control icon on the ActiveX page of the
Delphi Repository (Fig. 3.5). You may initially think its name to imply wider ca-
pabilities, but it can do only what it can.
Using it is very simple.
1. Select the VCL component from the VCL Class Name list.
2. The wizard offers you names for the future ActiveX component and related
files. You may either accept this name or specify your own in the New ActiveX
Name, Implementation Unit, and Project Name fields.
3. Select the method of interaction between the object and the client from the
Threading Model list (see details in Chapter 1, "COM Mechanisms in Delphi’).
4. There are three additional options available: Include About Box — includes
in the project a dialog box with information about the developer; IncludeChapter 3: ActiveX Components 101
>
Version Information — includes information about the version of the control
(this is required by some containers); finally, if you do not want to allow your
product to be freely distributed, you can include licensing information, and
only users with the key will be able to use it in development mode (i.e., in all
environments like Delphi or Visual Basic). Use the Make Control Licensed
checkbox for this.
x
VCL Class Name:
New Activer¢ Name: [ComboBox
Implementation Unit’ [ComboBoxmpl. pas.
Project Nem PerProit
Threading Mode [Apaitment
ls
© Active Contil Options
FF Make Control Licensed Include About Box
7 Include Version Information
OK ] Cancel Help
. 3.5. ActiveX component conversion wizard
After you have edited the suggested parameters, the wizard will automatically gen-
erate all the necessary files:
G The project itself (note that the ActiveX controls are always in the form of DLL
libraries — in this case with the OCX extension)
OC The Type Library with its representation in Object Pascal
G One more file with the source text — the implementation file
The implementation file and the classes specified here also play the role of a
bridge, but if the Tolecontroi class provided the connection between the features
of the ActiveX and Delphi requirements in the case of TLB, then here, the de-
scendants of the TActivexControl class establish a correspondence between the
former component and its new "hosts" — the containers in which the ActiveX that
is created will be located.
We stress this because you will find two classes of the same name in the files (say,
when working with the TCheckBox component, this will be the TcheckBoxx class).102__— Part I: COM and COM+ Applications
_>
But the one that is the descendant of tolecontro1 will be needed during import
of the ActiveX, and the descendant of TActivexContro1 will be needed during export.
Thus, after all the files created have been saved, the ActiveX component is ready
for export and use in other applications.
Converting Forms into ActiveX Forms
Even more interesting is to create in Delphi a complex control consisting of many sim-
ple controls. This is an object of envy with developers who work in other environments.
No one will be very impressed if you convert a standard Delphi component into
ActiveX. If you develop your own, there will be more interest, but almost all the nec-
essary ideas are still already implemented. On the other hand, after searching through
dozens of created controls, you will most likely find a feature or detail in each one that
makes that particular control unsuitable for you needs. Only the creation of an ActiveX
based on a form will make it possible to combine abundance of features with simple
implementation. This technology is called Active Forms.
We will now try to combine the pleasant with the useful, and select a practical ex-
ample to illustrate Active Forms. Many people who create graphic applications
need to be able to select the parameters for the pen and brush. At the same time, it
is not good to transfer routine code from application to application. The alterna-
tive is to combine it within our control.
Thus, the following sequence of operations should be performed to create it.
There is a special wizard designed for creating an ActiveX form in Delphi. To call
it, select the ActiveForm icon on the ActiveX page in the Delphi Repository.
Enter the name of the class of the new form in the New ActiveX Name field, or
accept the default setting. Accept the defaults or change the names of the form and
project module. Finally, select the model of interaction between the object and the
client (see details in Chapter 1, "COM Mechanisms in Delphi") in the Threading
Model list.
We're going to change the name of the new control — penx. Note that the rest of
the names are changed along with it.
Now save the project. It now consists of PENIMPLI.PAS files (this is actually the
implementation code of our ActiveX component), the Type Library file connected
with it — PENPROJ1_TLB.PAS, and the project file PENPROJI.PAS (the DLL
header is contained here).Chapter 3: ActiveX Components 103
>
Now the whole surface of the Tpenx form (module PENIMPLI.PAS) is available.
Put two TComboBox components and one TtrackBar on it. Place them as you wish
(but approximately as shown in Fig. 3.7) and reduce the size of the form so that
our ActiveX control doesn't get too big.
x
GL Giass Name:
‘New ActiveX Name:
Implementation Urit: [ActiveFcrmlmplT pss
Project Nor PerPioit
Threading Modet [apartment >
j- Actives Control Options
[> Make Contiol Licensed FF Inckide About Box
> Inchide Version Information
Fig. 3.6. The Active Form Wizard dialog box
Fig. 3.7.The appearance of the TPenx object
The drop-down lists are designed for color and pen style selection, and the
TTrackBar Component is to select the thickness. Set the Min and Max properties of
the TUpDown component to | and 8, respectively. The lists will visually demonstrate the
fixed properties of the pen, so assign the csOwnerDrawFixed values (redrawn by the
user) to their style properties, and specify the event handlers (Listing 3.3).
1g 3.3. Method Handlers of the Standard Components on the ActiveX Form
const DefColors : array [0..15] of TColor =( clBlack,
clMaroon, clGreen, clOlive, clNavy, clPurple, clTeal, clGray, clSilver,
clRed, clLime, clYellow, clBlue, clFuchsia, clAqua,104__— Part |: COM and COM+ Applications
_>
clWhite) ;
var GblWidth: Integer;
procedure TPenX.FormCreate (Sender: TObject) ;
var i: Integer;
begin
for i := Low(DefColors) to High(DefColors) do
ClrComboBox. Items Add (IntToStr (i) );
ClrComboBox.ItemIndex := 0;
for i = 0 to 7 do
StyleComboBox. Items .Add(IntTostr (i) )¢
StyleComboBox.ItemIndex := 0;
end;
procedure TPenX.ClrComboBoxDrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState) ;
begin
with ClrComboBox.Canvas do
begin
Brush.Color
FillRect (Rect) ;
end;
DefColors [Index] ;
end;
procedure TPenX.StyleComboBoxDrawltem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState) ;
var FPos: Integer;
begin
FPos := Rect.Top + (Rect.Bottom - Rect.Top) div 2;
with StyleComboBox.Canvas do
begin
Brush.Color
FillRect (Rect) ;
Pen.Style := TPenStyle (Index) ;
Pen.Width := WidthTrackBar.Position;
MoveTo(Rect.Left, FPos);
LineTo(Rect .Right, FPos);
end;
clWhite;Chapter 3: ActiveX Components 105
>
end;
procedure TPenX.ComboBoxChange (Sender: Tobject);
begin
(Sender as TWinControl) .Repaint;
if FEvents<>nil then FEvents.OnPenChanged;
end;
procedure TPenX.WidthTrackBarChange (Sender: TObject);
begin
if WidthTrackBar.Position > 1 then StyleComboBox.ItemIndex:=0;
ComboBoxChange (StyleComboBox) ;
When the button of the clrcomboBox list is pressed, a selection of 16 main colors
appears; when the button of the styleComboBox list is pressed, the selection of all
possible types of lines appears.
Formally, the ActiveX control is ready. But to get any use out of it, it still needs
an interface with the user. We don't need the selection of colors and styles just to
be there for no particular reason, but rather to pass them to the user.
Select the Type Library command from the View menu of the development envi-
ronment. Open the penx interface in the window of the Type Library Editor that
appears.
Pay attention to the methods of the TPenx interface that are created automatically. They
execute the standard functions of the ActiveX control, and are based on the corre-
sponding methods of the interfaces described in Table 3.1.
Add three properties to the interface — PenWidth, PenStyle, and PenColor. See
the details on the Type Library and its Editor in Chapter 1, "COM Mechanisms in
Delph
The new properties will be displayed as three pairs of methods — for reading and
for writing (Fig. 3.8).
Press the Refresh Implementation button on the upper toolbar. The property de-
scription appears in both files of the project, and the code templates for their im-
plementation appear in PENIMPLI.PAS.106__— Part I: COM and COM+ Applications
_>
There is another way of adding properties to ActiveX in Delphi — via the Edit/Add
to Interface menu.
EE
POSEbSoS HS DHE
y a
Ped FU) sins [uses | rags | Tex |
89 Active ZT
i Drop Target Name: peter
jb DropTaiget UID: fiEEzSFAD-A7e5-1102-3F60-000000000000)
469) HelpFile
Gi HelpFie Version: fo
449) DoubleButered Loo.
GP DeubleBultered ie
4&9 Enabled Help
@ Ena Help Sting JPenFioj Library
9 BiDMode ——_—_——<—<—$<$$ $< $$ $< $$$
SB BDMede Help Context
3 rae Help String Context:
be Aboutiox Help Sting DLL:
89) Penwith a ae
Gf PenWich Hepes
9) PenCelr
end;
function TPenX.Get_PenWidth: Integer;
begin
Result := WidthTrackBar.Position;
end;
We are now another step closer to our goal — you could stop further development
of the control, compile it, install it, and find in the Object Inspector the three
properties we created and work with them further. But when are users supposed to
reset the parameters of their pen? So far, they haven't even been informed that
anything has happened inside the control.
This will be the final touch.
Once again, refer to the Type Library, this time to the 1penxEvents interface. Not
the property, but the method should be added here, and it should be named
OnPenChanged. Unlike the properties, in this case only the reference to the
method appears, not the template for it. It is up to the programmer to implement
the reaction to the event using our ActiveX control, so our task is to initiate this
event at the right time. This code must be placed everywhere that at least one of
the pen parameters is changed:
procedure TPenX.ComboBoxlChange (Sender: TObject) ;
begin
if FEvents <> nil then FEvents.OnPenChanged;
end;
The ActiveForm control is basically ready (Fig. 3.9).
Fig. 3.9. The appearance of the Penx control in the test application108 _— Part I: COM and COM+ Applications
_>
All we have to do now is register the control, include it in one of the packages,
and enjoy the capabilities offered by the component approach to programming.
Summary
Developing ActiveX controls is based on the capabilities of the COM and Auto-
mation technologies.
ActiveX controls are COM objects, and are subject to the rules for such objects.
They may operate within the in-process server (dynamic library) only.
To use the control, the application should possess the capabilities of the ActiveX
container. There are many applications that are containers. Among them are
Microsoft Internet Explorer, Microsoft Visual Basic, Microsoft Word, and Delphi.
Using the classes and Delphi wizards, you can create your own controls or forms
and distribute them.hapter 4
COM+ Technology
(Microsoft Transaction
Server)110 _— Part I: COM and COM+ Applications
_
for distributed applications. The Microsoft Transaction Server (MTS)
software is intended for the support of transaction system handling, allow-
ing you to create an environment for highly efficient and reliable distributed appli-
cations for Internet and Intranet.
T he issues of reliability, efficiency, and scalability are of great importance
MTS can be installed on computers with Windows 95/98, Windows NT, and
Windows 2000 operating systems. The product was adapted for the Windows 2000
family and given the name "COM+."
MTS technology is based on COM functions, and provides support of distributed
applications on a component basis. MTS transactional objects have the basic prop-
erties of COM objects. Additionally, the transactional objects implement specific
capabilities inherent to MTS objects only:
© Transaction handling
O Security
© Resource pooling
CO Object pooling
The MTS system of transaction security is not implemented for Windows 95/98 operat-
ing systems, due to the objective limitations imposed by these systems.
To manage the transactional objects and parameter settings of MTS, a range of
applications and utilities is used:
G Distributed Transaction Coordinator — DTC — is a service for handling low-
level transactions using the two-phase transaction commitment protocol.
G The MTS Explorer administrative application allows you to set the parameters
of the MTS environment, kept in the System Registry; it also handles MTS
packages and roles.
© The MTS utilities are for work in the command line or batch file.
© The MTX.EXE executive file implements automatic transactions, security, and
activation (Just-In-Time — JIT).
Besides the means listed above, you can also use the standard system means for
managing MTS. Here, to perform any operation, you need to have administrator
rights on the given computer.Chapter 4: COM+ Technology (Microsoft Transaction Server) 111
>
However, a discussion of component installation and setting the MTS environment
for the server and clients is beyond the scope of this book.
This chapter deals with issues of support implementation and the use of MTS
technology in Delphi. Below are the main topics:
CG How transaction objects function
© Managing transaction objects
OC Managing transactions
O Transaction security
© Resource management
How MTS Works
Microsoft Transaction Server is a set of program facilities providing for the devel-
opment, distribution, and functioning of distributed applications for Internet and
Intranet. MTS includes:
OG Middleware that allows transaction objects to function during execution
G The MTS Explorer utility that enables handling of transaction objects
© Application programming interfaces
OG Means of resource management
The standard model for applications using MTS is a three-tier architecture of dis-
tributed applications that consists of servers, clients, and middleware. The business
logic of the application is concentrated in the MTS transactional objects, and the
middleware handling these objects is constructed with the use of the component
model.
Developers who use MTS in their applications create business logic objects that
meet the requirements of the MTS objects, and then compile and implement them
in the MTS environment using packages.
An MTS package is a container that groups the objects for the sake of data secu-
rity, improvement of resource management, and efficiency. MTS packages are
managed using the MTS Explorer utility.112 _—‘ Part I: COM and COM+ Applications
_
The MTS Object
Since the MTS technology is based on COM, MTS objects must meet the main
requirements for COM objects. Additionally, MTS objects have a number of spe-
cial features:
G The object must be implemented within the in-process server (dynamic
library).
© The object must contain a reference to the MTS type library.
© The object must use the standard marshaling COM mechanism.
G The object must implement the tobjectcontro! interface.
Based upon the main principles of how MTS works, the created object may be of
two types:
OG Stateful
O Stateless
The choice of the object type depends on the specific task and purpose of the
object.
Like any COM object, the MTS objects may retain the internal state when using
one instance many times. For each use, the object retains the current values of its
properties. At each subsequent call, the object can return the current state to the
calling client. This kind of object is of the stateful type. Objects of this type allow
a wider range of tasks to be solved.
For the object state to be saved, the object must remain active and retain valuable
resources, such as the connection with the database. In practice, this is nothing
less than work with global variables, for it is here that the intermediate state of the
object is saved.
If the object can not retain its intermediate state, it is of the stateless type. Objects
of this type are more efficient.
When the transaction is successfully completed or terminated, all the transaction
objects are deactivated, losing the information on their state acquired during the
transaction. This helps to ensure that the transaction is isolated and that the data-
base is in compliance, and frees up server resources for use by other transactions.
The completion of the transaction allows MTS to deactivate the object and update
the resources.Chapter 4: COM+ Technology (Microsoft Transaction Server) 113
Transactions
The ability of the MTS object to "live" within its own transaction or to be part
of a bigger group of similar objects belonging to one transaction is a great advan-
tage of MTS. This enables the component to be used for various tasks in such
a way that the developers may use the code again without modernizing the appli-
cation logic.
MTS transactions guarantee that:
G All changes within one transaction will be either accepted or returned to their
previous state.
G The transaction converts the state of the system correctly and univocally.
G Simultaneous transactions do not accept partial and unsaved changes, which
may create conflicts.
© Confirmation of the changes to the controlled resources (such as the database
records) protects from errors, including network and process errors.
G Registration of the transactions allows the initial state to be restored, even after
disk errors have occurred.
Clients may gain direct control over the transactions by means of the object con-
text, using the ITransactionContext interface. However, for user convenience,
MTS can automatically handle the transactions.
Using MTS transactions, you may employ the business logic of the application in
server objects. Server objects can implement business logic in such a way that the
client does not have to know anything about the rules of the business logic.
There are three ways to set the attributes of transactions:
© During the development stage
O With the help of the Type Library Editor
G In the MTS Explorer environment
The transaction is completed by default, after the time specified in the Transaction
timeout parameter (set for each object separately via the MTS Explorer utility)
runs out. By default, this time is equal to 60 secs. After this time has expired,
an incomplete transaction is automatically terminated.114‘ Part I: COM and COM+ Applications
_>
MTS Object Context
For each MTS transactional object, the transaction server automatically creates
a special object, called the object context. The functionality of the context is sup-
ported by the robjectContext interface.
Two methods of the interface define the way the object leaves the transaction.
The setComplete method informs the transaction that it is ready to end its work in
the transaction.
The use of the setAbort method means that executing the code of the object has
led to circumstances preventing successful completion of the transaction.
After either of these two methods is used, the object ends its participation in the
transaction.
The EnableCommit and DisableCommit methods inform you about the current state
of the object. The EnableCommit method informs you that the object is allowing
the transaction to be completed, although its own functioning has not been com-
pleted.
Transactional :
Object Transaction
State
Object Context
Transac!
Object2
Object Context
SetComplete SetAbort
Transaction
Fig. 4.1. Role of the MTS object context
The call of the Disablecommit method shows that at this moment the current state
of the object does not allow the transaction to be completed. If you try to com-
plete the transaction after this method is called, the transaction will be terminated.
Using the methods above, the object context provides the MTS environment with
information about the state of the transactional object.
For example, in order to provide services based on the transactions, the resource
dispenser may use the context of the MTS object. Let the object be implemented
within the transaction that has reserved the connection with the database viaChapter 4: COM+ Technology (Microsoft Transaction Server) 115
_
the ADO provider. This connection automatically organizes the transaction.
All the changes in the database with this connection become part of the transac-
tion, and then are either accepted, or rolled back.
There are several more methods of the IobjectContext interface that developers
may use.
When the MTS object is used in an open transaction, the IsInTransaction func-
tion of the context object returns True.
If the data security mode is activated for the MTS object or MTS package con-
taining this object, then the 1ssecurityEnablea method returns the True value.
If the application using the MTS object performs some MTS role, then the
IsCallerInRole method returns the True value.
Data Security in MTS
One of the convenient services provided by MTS is the ability to access compo-
nents (and even separate interfaces), depending on the rights of the client. MTS
data security consists of two parts:
OG Declarative data security
© Program data security
In both cases, the MTS environment uses MTS roles to provide security. An MTS
role is an abstract concept of a certain set of users. These may be separate users or
groups of users. Using the MTS Explorer application, the administrator creates the
required roles and registers the users and the groups there. Each role is endowed
with the necessary rights. The Windows authentification mechanism may be used
here.
Declarative Data Security
Declarative data security is created in the MTS environment setting stage using the
MTS Explorer utility. It consists of limiting access to a specific object or package
to users and groups that are members of certain roles.
By default, there is a System Package built into the MTS environment, for which
there are two roles: Administrator and Reader. Before you start work, you need to
set the Administrator role to at least one account.116 _— Part I: COM and COM+ Applications
>
The security setting is impossible for Windows 95/98 because of the limited functionality
of these operating systems.
Program Data Security
Program data security is provided by the context object and the IssecurityEnabled
and IsCallerInRole methods of the IobjectContext interface of the object.
The program data security is planned during the application development stage,
and is executed while the application using this MTS object is functioning. When
the application tries to use a specific MTS object, it must use the IscallerInRole
method. The role executed by the application is communicated as the parameter of
the method. If this role is played by the MTS object or package, its use is permit-
ted, and the method returns the True value.
If there are several MTS objects used within one process, then the IsCallerInRole
method always returns True. In this case, for more accurate identification, the
IsSecurityEnabled method is used.
For applications requiring stricter security, the object context uses the ISecurityProperty
interface, the methods of which retum the Windows Security Identifier (SID) for the direct
call and object creation.
Resources
There are three methods for managing MTS resources:
OC Just-in-time activation
G Resource pooling
© Object pooling
We will now consider each of these methods in detail.
Just-In-Time Activation
The ability of the object to be deactivated and activated again while the client has
the reference to it is called just-in-time activation. During work with the applica-Chapter 4: COM+ Technology (Microsoft Transaction Server) 117
>_>
tion, it is often necessary to use one instance of the MTS object several times at
certain intervals. When the object is called, it is activated, and the application
keeps the reference to the unused object for some time after work with the appli-
cation is completed.
When a COM object is created as part of the MTS environment, the correspond-
ing context of the object is created at the same time. This context of the object
exists for the whole "life" of the MTS object, in one or several cycles. The MTS
uses the context of the object to save information on it when deactivation takes
place.
A COM object is created in the deactivated state, becoming active only after the
client's call.
An MTS object becomes inactive if the following events occur:
© The call of the setcomplete or SetAbort methods of the IobjectContext in-
terface. If the object calls the setcomplete method once it has successfully
completed its operation, then it is not necessary to save the internal state of the
object for the next client's call. If the object calls the setabort method, then it
points out the inability to successfully complete its work, and that the state of
the object does not need to be saved. Then the object goes back to the state
preceding this transaction. In a normal implementation of a stateless object, de-
activation takes place after calling each method.
G The transaction is either saved or terminated. After this, the object is also deac-
tivated. Among these objects, the only ones that can continue their existence
are objects that have a reference to clients outside this transaction. Subse-
quently calling these objects will activate them again, and cause them to be
executed in the next transaction.
OG The last client frees the object. This object is deactivated, and the object con-
text is also freed.
Resource Pooling
After the resources are freed during MTS object deactivation, they become avail-
able for other server objects. This process is called resource pooling.
Consider, for example, the connection to the database via the ADO provider. Of
course, allocating resources and opening and closing the connection with the data-118 _—‘ Part I: COM and COM+ Applications
—_>_
base takes quite a lot of time. Frequent repetition of this operation by various MTS
objects to one database will cause an increased waste of resources.
In cases like these, resource pooling is used. If the connection with the database is
not used by one server object, it may be used by another object.
For MTS resource pooling, the pooling dispenser is used.
Freeing Up Resources
Freeing up resources is usually done by calling the setcomplete and setAbort
methods after the client's call has been serviced. These methods free up resources
reserved by the MTS dispenser.
At the same time, it is necessary to free up references to the other resources, includ-
ing references to other objects (MTS objects and the object contexts) and memory
occupied by component instances. This is not recommended unless you need to save
information on the state in between the clients’ calls.
Object Pooling
MTS implements not only resource pooling, but object pooling as well. This
option, however, is only available within the COM+ technology that functions
under the Windows 2000 operating system. In the MTS of older versions, object
pooling was only declared for future use.
MTS
. Transactional
«
Transactional
object
constructor
' (a eal ya
Client ext ‘Transactional object
Fig. 4.2. MTS object pooling diagramChapter 4: COM+ Technology (Microsoft Transaction Server) 119
>
The idea behind this mechanism is simple.
When the applications are implemented in the MTS environment, a special pool
of objects is created.
The tobjectControl interface is used for handling the pool and positioning objects
in it. If the object is intended for pooling use, than the canBePoolea method of the
interface should return the True value.
After this object is deactivated, the MTS server places it into the pool. The objects
inside the pool are available for immediate use by any other requests of clients.
If the object is called and the object pool is empty, MTS automatically creates
a new instance of the object.
Creating MTS Applications in Delphi
Delphi provides developers with a wide spectrum of means to create distributed
applications using MTS technologies.
Above all, these are the MTS transactional objects, and a special wizard is used for
their creation.
To develop the server part of the applications, you can use the MTS data modules
that handle the client calls in the MTS environment.
There are also means of resource management in Delphi that may be used while
writing programs.
We will look at these tools in more detail.
MTS Transaction Objects
MTS transaction objects are COM objects (see Chapter 1, "COM Mechanisms
in Delphi") that possess a range of specific features. These features are imple-
mented by the MTS interfaces. The main interfaces are tobjectControl and
IObjectContext.
The MTS transactional objects are normally used for implementing small blocks of
the business logic of the application. One object may either work with one trans-
action exclusively, or share it with the other objects. This depends only on the
chosen means of business logic implementation and its algorithms.120 _—‘ Part |: COM and COM+ Applications
The TMTSAutoObject Class
The capabilities of the transaction object in Delphi are implemented in the
TMTSAutoObject class base (Fig. 4.3).
TObject j
TComObject
TTypedComObject
TAutoObject
TMTSAutoObject
Fig. 4.3. The TMTSAutoObject Class hierarchy
Its properties and methods provide for the execution of the main functions of the
MTS object:
O The transaction's notification on the object's state
© Provision of program data security
G Object pooling (for COM+ technology only)
All these options were considered above and, since the methods of the
TMTSAutoObject class fully coincide with the methods of the interfaces described
above, we will just describe them briefly.
The methods
procedure SetComplete;
procedure SetAbort;
call the methods of the 1objectContext interface of the same name, and provide
for transaction saving or rollback.Chapter 4: COM+ Technology (Microsoft Transaction Server) 121
>_>
The methods
procedure DisableCommit;
procedure EnableCommit;
function IsInTransaction: Bool;
call the methods of the IobjectContext interface of the same name, and provide
for the transaction's notification on the object's state.
The methods
function IsCallerInRole(const Role: WideString): Bool;
function IsSecurityEnabled: Bool;
implement the program data security. The Role parameter of the IsCcallerInRole
method must contain the name of the MTS role called.
The property
property Pooled: Boolean;
handles object availability for the pooling. If the property has the True value, the
object may be placed into the pool for multiple use.
Creating a Transactional Object
In order to create a new MTS transaction object, the New Transactional
Object wizard is used in Delphi, which is available in the Delphi Repository on the
ActiveX page.
According to the requirements, MTS transactional objects may be implemented
within the in-process server only. Therefore, when a new transactional object is
created with the New Transactional Object wizard, the type of the project is auto-
matically changed to ActiveX dynamic library (see Chapter 3, "ActiveX Compo-
nents").
It is necessary to type the name of the transactional object in the CoClass Name
line, or rather the name of the co-class used for the object's representation in the
application.
The co-classes are used in Delphi for any COM objects or child technologies. See
Chapter 1, "COM Mechanisms in Delphi," for details on the tole of coclasses in applica-
tions using COM.122‘ Part |: COM and COM+ Applications
>_>
New Transactional Object
CoClass Name; [MTSExample
Thieading Model [Apartment
Trarezaction made!
© Roquites a transaction
C Flequites @ new transaction
© Suppatts transactions
Does not support transactions
© Ignotes Transactions
> Options
I Generate Event support code
Fig. 4.4. The transactional object creation wizard
The Threading Model list allows you to choose the method of interaction between
the server and the clients’ calls. It is called a thread-oriented model. The possible
variants of the model were considered in Chapter 1,"COM Mechanisms in Delphi."
The features of various models’ application for MTS objects are considered below.
The group of Transaction Model radio buttons determines the object's behavior in
the transaction.
The Generate Event support code checkbox enables or disables transactional object
event handling. Then, when the source code of the MTS object class is created,
the IconnectionPointContainer interface will be created automatically. The noti-
fication mechanism is similar to that used for Automation objects. See Chapter 2,
“Automation,” for more detailed information.
After the parameters of the new object have been specified, and the OK button is
pressed, Delphi automatically generates the source code for the class of the new
object.
Listing 4.1. MTS Transactional Object Class Declaration
unit Unitl;
{$WARN SYMBOL_PLATFORM OFF}
interfaceChapter 4: COM+ Technology (Microsoft Transaction Server) 123
>_>
uses
ActiveX, Mtsobj, Mtx, ComObj, Projecti_TLB, StdVcl;
type
TMISExample = class(TMtsAutoObject, IMTSExample)
protected
{ Protected declarations }
end;
implementation
uses ComServ;
initialization
TAutoObjectFactory.Create(ComServer, TMTSExample, Class_MTSExample,
ciMultiInstance, tmApartment) ;
end.
You can see that the code presented in the Listing 4.1 is quite common for COM
objects, and, with some variations, was already encountered in previous chapters.
Apart from direct class declaration, there is a class factory constructor in the
initialization section as well.
At the same time as the class, an interface is created (in our example, this is the
IMTSExample interface), which is designed for presenting the methods of the new
class. This is the dual interface (it is shown in Fig. 4.3 that the class of an MTS
object inherits from the Automation object class).
And at the same time as the class declaration, a corresponding Type Library is cre-
ated. Here, according to the requirements of the COM and Automation technolo-
gies, the main interface and the dispinterface are described, along with the coclass
for the new class.
Thread-Oriented Model Types
The MTS object creation wizard requires the type of the thread-oriented model to
be specified. Let's take a look at these models.124 __— Part |: COM and COM+ Applications
>_>
The Single model requires the execution of all the objects created in response to
clients’ calls in a single thread. This is the default model for all COM objects.
This model is of limited scalability. Objects of the stateful type using
this model may cause backups and blocks. You can eliminate this problem by
using an object of the stateless type and calling the setcomplete method before
exiting it.
The Apartment model creates a separate thread for each new instance of the object
used during the lifetime of this object.
Within this model, two objects may be executed in parallel as long as they are em-
ployed in different activities.
The Both model is the same as the Apartment model, except that the responses
of the objects to calls are handled consecutively, not all at once. This model
is recommended as the main model to use with object pooling.
The Free model used in other COM objects is not applicable to objects run in the MTS
environment, since so-called activities are supported. See more detailed information on
MTS activities in the "Optimization of Work with MTS" section.
The Behavior of MTS Objects in Transactions
The MTS object creation wizard requires that you specify the type of transaction
model. There are five available types:
© Requires a transaction. The MTS objects are to be executed within the transaction.
When a new object is created, its object context inherits the transaction from
the client context. If the client has no transaction, MTS creates it automati-
cally.
CG Requires a new transaction. The MTS objects must be executed within their
transactions. When a new object is created, MTS automatically creates a new
transaction for the object, whether the client has it or not. The object is never
run within its own transaction. Instead, the system always creates an independ-
ent transaction for new objects.Chapter 4: COM+ Technology (Microsoft Transaction Server) 125
>_>
© Supports transactions. The MTS objects are executed within their client trans-
action. When a new object is created, its object context inherits the transaction
from the client's context. This enables several objects to operate within one
transaction. If the client has no transaction, it will be created by the new con-
text.
G Transactions Ignored. When a new object is created, a linked object of the
transaction context is created without a transaction, and independently of
whether or not one exists. Applicable for COM+ technology only.
CG Does not support transactions. Depends on the technology used. For MTS,
the activity is similar to Transactions Ignored. For COM+, the object of the
transaction context is created without the transaction. The object still depends
on the transaction, but is not activated immediately if a transaction exists.
MTS Data Remote Module
The MTS data remote module allows you to create server parts of distributed ap-
plications. It provides the basic functionality of the application server. When cor-
rectly registered on the server computer, server created based on the remote mod-
ule performs the following functions:
© It is the MTS transactional object container.
Gi It encapsulates the business logic of the distributed application implemented in
the set of MTS transactional objects.
Gi It provides handling of client requests according to the thread-oriented model
specified.
Gi It interacts with the MTS environment, providing the operability of the MTS
objects with it.
The base functionality of the application server is provided by the tAppServer in-
terface encapsulated by the data module. In the development stage, the MTS data
module is the container for all non-visual components.
Detailed information on work with remote data modules and the creation of appli-
cation servers can be found in Part III, "Distributed Database Applications."
The capabilities of the remote data module are encapsulated in the
TMTSDataModule class.126 _— Part |: COM and COM+ Applications
>_>
To create a new remote MTS data module, the New Transactional Data Module
Object wizard is used. It is invoked from the Delphi Repository. The icon
of the Transactional Data Module wizard is on the Multitier page of the Reposi-
tory.
(ree ss FE)
Coblass Name; [MTSDataModulel
Threading Modet [Apartment >
Transaction mode
© Bequites a tansaction
© Requires a new tieneaction
© Supports transactions
© Does not cuppat irancactions
© Ignores Transactions
Fig. 4.5. MTS remote data module creation wizard
You must indicate the name of the data module in the CoClass Name line.
The Threading Model list allows you to choose the method of interaction between
the server and the client requests.
The group of Transaction Model radio buttons determines the object's behavior in
the transaction.
After it is created, the new module is available to the programmer. In the devel-
opment stage, it is used as common data module. Here it is possible to include any
non-visible components and create the source code for them.
In addition to the capabilities of a remote data module inherited from the
TRemoteDataModule class, the MTS data module implements the methods of the
IObjectContext interface.
Resource Dispensers
There are two resource dispensers in Delphi:
© BDE (Borland Database Engine) resource dispenser. Handles the pool of con-
nection with the database for MTS components that use BDE's capabilities forChapter 4: COM+ Technology (Microsoft Transaction Server) 127
work with databases. BDE is middleware that provides the access to several
types of databases.
OC Shared Property Manager is used for sharing states among several objects
within one server process.
When the Shared Property Manager is used, you do not need to add any code
into the application. It also eliminates name conflicts with the help
of shared property groups that set unique name spaces for the properties
they contain. In using the Shared Property Manager, you mainly use
the createSharedPropertyGroup function to create the shared property group.
Then you can save and restore all the properties of this group. Additionally, the
state information may be distributed among all the MTS objects installed in the
same package.
The next example demonstrates the procedure of adding code to support the
Shared Property Manager in the MTS object.
Listing 4.2. Example of Using the Shared Property Manager
unit Unitl;
{SWARN SYMBOL_PLATFORM OFF
interface
uses
ActiveX, Mtsobj, Mtx, Com0bj, Project
type
TMTSExample = class (TMtsAutoObject, IMTSExample)
private
Group: ISharedPropertyGroup;
protected
procedure OnActivate; override;
procedure OnDeactivate; override;
procedure IncCounter;128 _—_— Part I: COM and COM+ Applications
_>
end;
implementation
uses ComServ;
procedure TMTSExample.OnActivate;
var Exists: WordBool;
Counter: ISharedProperty;
begin
Group := CreateSharedPropertyGroup ('MyGroup");
Counter i= Group.CreateProperty('Counter', Exists);
end;
procedure TMTSExample.IncCounter;
var Counter: ISharedProperty;
begin
Counter := Group.PropertyByName['Counter'];
Counter.Value := Counter.Value + 1;
end;
procedure TMTSExample.OnDeactivate;
begin
Group := nil;
end;
initialization
TAutoObjectFactory.Create(ComServer, TMTSExample, Class_MTSExample,
ciMultilnstance, tmApartment) ;
end.Chapter 4: COM+ Technology (Microsoft Transaction Server) 129
>_>
In this example, the shared property group, called Mycroup, and the shared
Counter property for this group in the onActivate method handler of the
TMTSExample object are created.
The createSharedPropertyGroup function is used to create the shared property
group and the property group itself. Then, to create the property, use the
CreateProperty method of mycroup.
In order to get the string value of the property, you need to use the PropertyByName
method of the group object.
Note that the functionality of the shared property group is contained in the meth-
ods of the r:
redPropertyGroup interface.
All objects with the shared state must be run in the same server process. Objects with
shared properties must have the same activation attributes. For example, if one object
is configured for running in the client process, and another is configured for the server
process, these objects will be launched in different processes even if they are installed
in one package.
Testing and Installing MTS Components
When the MTS environment is set up, among the many parameters the adminis-
trator may specify is the time for which the transactional object will be active
without client calls. This parameter is called the transaction timeout. By default, it
is equal to 60 seconds.
During the debugging of MTS objects, this parameter must be deactivated
(to assign a zero value), otherwise the object may be unloaded by the MTS envi-
ronment while you work with the source code or the values of the variables during
the setup process.
The MTS component cannot be recompiled in the development stage while it is
in the memory. The following error message will appear: "Cannot write to DLL
while executable is loaded." To avoid this situation, you need to set the "Shut
down after being idle for 3 minutes" parameter, changing the time, with the MTS
Explorer.130 __— Part I: COM and COM+ Applications
To do this:
1. In MTS Explorer, right click on the package where the MTS transactional ob-
ject you need is installed, and select the Properties command from the pop-up
menu.
2. Select the Advanced tab in the dialog box that appears.
3. Change the time idle to 0.
4. Press the OK button to save the parameters and return to the MTS Explorer
environment.
To launch the MTS transactional object for debugging, you need to set the run
parameters (Fig. 4.6). To do this:
1. Select Parameters from the Run menu in Delphi.
2. Specify the full path to the mtx.exe file in the Host Application line.
3. Type in the Parameters line: the p key and the name of the package where the
transactional object is installed —
/pi"TestPack"
In this example, testPack is the name of the package where the object is
installed.
Lecal | emote |
Host pplication
[EAWINNT\systema2\micexe
Parameters:
/p"TestPack” =
Fig. 4.6. Setting the run parameters of the transactional object
Then, with the breakpoints set in the proper places, you can start the server object
and proceed to debugging the application, sending requests from the client.Chapter 4: COM+ Technology (Microsoft Transaction Server) 131
>
Each package is started in a single instance of the mtx.exe file. This is seen when sev-
eral packages are started from the Task Manager on the Processes page.
To install the transactional object into the package from Delphi, you need to do
the following. Note that for Windows 2000 the technology is called COM+.
1. Select Install COM+ Objects from the Run menu.
2. In the Install COM+ Objects dialog box (Fig. 4.7), select the MTS object to be
installed from the list; for this purpose, check the checkbox to the left of the
object's name.
3. When the checkbox is activated, another Install COM+ Objects dialog box ap-
pears automatically (Fig. 4.8), where you have to select the package into which
the MTS transactional object will be installed. Here, to create a new package
you must select Install into new Application, or Install into existing Application
for installation into an existing one (selected from the list).
4. Press OK to complete the operation.
A single object may not be installed into several packages.
Applicaton
Cancel Help
Fig. 4.7. Selecting the MTS transactional object for installation132‘ Part I: COM and COM+ Applications
Ts I
‘Objects:
Name Application
EAMTSExamole
Fa)
Inetellito existing Application | install into new Application |
ApplicationNene [SSS YS
Description:
Fig. 4.8. Selecting the application for the MTS transactional object
Optimizing Work with MTS
It is quite easy to develop an application that uses MTS or COM+. More compli-
cated is designing an application in such a way that it is not only functional, but
gives the maximum effect in its work, making full use of all the capabilities of the
technology. For this, it is necessary to clearly understand the performance mecha-
nism of COM objects and the principles of work with transactions.
Transaction Lockout
One of the main principles of competent handling of transactions is their isolation.
To achieve this, various mechanisms of blocking transactions’ influence on each
other are used. MTS implements the system of transaction isolation on a high
level. The data will not be visible in one transaction until it has been processed in
another. This reduces system efficiency. To eliminate this problem, it is necessary
to minimize the working period of each transaction and properly use both the
SetComplete and SetAbort methods of the tobjectContext interface.
MTS Activities
Furthermore, one of the most important and fundamental aspects of programming
for MTS is the concept of the MTS activity. The correct use of activities is oftenChapter 4: COM+ Technology (Microsoft Transaction Server) 133
_>_
neglected, but it is actually the incorrect usage of activities that causes a number of
problems and difficulties.
An MTS activity is a set of objects working jointly in the interest of one client.
The activity may contain the objects from various packages. Each MTS object ex-
ists in one activity only, but the activity may contain several transactions.
Programming for MTS implies that the MTS objects must not be shared among
activities. The parallel use of objects within the activity is very dangerous, for
a situation may occur in which an object working for one thread may try to accept
the transaction, while an object working for another thread is in a process within
the same transaction. If the transaction is accepted, this would lead to the com-
mitment of the partially completed transaction.
Example of a Simple Transactional Object
After the main principles and ideas of MTS have been considered, we will create
a simple example where the client will request the server for information on cur-
rent projects. The Microsoft Access database DBDEMOS.MDB will be used as the
data source. This is the standard demo database supplied with Delphi. By default,
the database file is installed in the ..\Program Files\Common Files\Borland Shared\
Data folder.
In this simple example, the server will return the total number of orders within the
database.
We will create a new group of projects in Delphi, and the first project will be used
for server creation.
Server Creation
Using the wizard described above, create an MTS data module called pmrest.
Once the data module is created, you must create an Apartment thread-oriented
model and a Supports transactions transactional model in the New Transactional
Data Module Object wizard.
Here place the group of non-visual components necessary for access to the data-
base via the ADO Microsoft Jet 4.0 OLE DB Provider. In our case, these are the
TADOConnection connection component and TADOQuery query component. See
Chapter 7, "ADO Usage in Delphi," for details on work with the ADO technology.
Now we'll set the connection and request.134___— Part |: COM and COM+ Applications
>_>
ATTENTION
While compiling the example, you need to specify the path to the database on your
computer again, using the editor of the ConnectionString property of the
TADOConnection component. In our example, this property is blank, as the true location
of the database file may vary.
Now, to handle the client's request, create the GetorderCount method for the
IDMTest interface in the type library of the MTS remote data module (Listing 4.3).
With the help of the request component, this method will determine the total
number of orders.
4.3. Description of the GetOrderCount Method in the implementation Section
Integer);
procedure TDMTest.GetOrderCount (out OrdCoun’
begin
try
conDBDemos .Open;
try
quOrdCount . Open;
OrdCount := quOrdCount Fields [0] .AsInteger;
qu0rdCount .Close;
finally
conDBDemos .Close;
end;
SetComplete;
except
OrdCount := DefOrdCount;
SetAbort;
end;
end;
Pay attention to the use of the setcomplete and setAbort methods, also imple-
mented for the tMrsDataModule class.
Now, compile the project and install it into the new TestPack package (see
Figs. 4.7 and 4.8).Chapter 4: COM+ Technology (Microsoft Transaction Server) 135,
yr
Client Creation
To create the client application, add a new executable application into the group
of projects. We'll call it simplemrsclient. The main form of the application is
shown in Fig. 4.9.
Orders count in the DBDEMOS database::
eat
ua, Calculate
Bees cbs
Fig. 4.9. The main form of the MTS client application
The connection between the server and the client may be fixed via various compo-
nents: TDcOMConnection, TWebConnection, and TSocketConnection. In the exam-
ple, we will use a component working with Distributed COM, for it is the simplest
to set up, and well suited for use on one computer with a server or in a local net-
work. To set it up, you need only indicate the name of the server computer in the
ComputerName property, and then select the name of the server from the list of the
ServerName property.
ATTENTION
When you work with this example, it is necessary to fill in the values of the ComputerName
and ServerName properties of the TDCoMConnect ion component.
In order to send the call to the server, use the handler method for the
buCalculate button click. The source code is very simple:
procedure TfmMain,buCalculateClick (Sender: TObject);
var OrdCount: Integer;
begin
Screen.Cursor := crHourGlass;
try
conDCOM. Open;
conDCOM.AppServer .GetOrderCount (OrdCount) ;
laOrdCount Caption := IntToStr (OrdCount) ;
conDCOM.Close;136 _— Part I: COM and COM+ Applications
>
finally
Screen.Cursor := crDefault;
end
end;
When the button is pressed, the rpcomconnect ion component connects to the da-
tabase via the server we created.
In the second stage, the Appserver property of the tpcoMconnect ion component is
used. This property is a reference to the IAppServer interface of the remote data
module of the MTS server. Apart from its own methods, this interface provides
access to all the methods of the rpmtest interface of the remote data module. We
are interested, in part, in the Getordercount method.
According to Listing 4.3, this method returns the integer in the ordcount parame-
ter, denoting the number of requests in the database. After type conversion, place
this number into the TLabel component.
The connection with the database is closed in the last stage.
After compilation, the application is ready for testing.
Example of Creating a Client and Server
in the Case of a Distributed Transaction
In a distributed transaction, several MTS objects must participate in the process.
In our case, these will be two remote MTS data modules. It is not important
which database is used with these data modules — the main thing is that the dis-
tributed transaction uses different MTS objects. So, for the sake of simplicity, we
will use one database in the second connection.
Server Creation
In this example, we will use a connection made with the database by means of
BDE. There is a set of files of the dBASE format for the DBDEMOS database in
the ..\Program Files\Common Files\Borland Shared\Data folder. This database is
similar to the on used in the previous example.
The previous example will be taken as the basis for the first data module (called
DMTesti), and a database request returning the total number of orders will be used.Chapter 4: COM+ Technology (Microsoft Transaction Server) 137
>
The structure of this project fully coincides with the above example, except for the
data access components.
To provide access to the database via BDE, employ the standard BDE alias
DBDEMOS. It is both created and automatically set up when Delphi is installed.
Since we are only using one query component, it is sufficient to set the connection
parameters in the TQuery component itself, without using the special BDE con-
nection component. For this purpose, select the name of the DBDEMOS alias in
the list of the DatabaseName property of the TQuery component. Then enter the
text of the request in the soL property.
Compile the project and register the remote data module in the MTS TestPack
package (see Figs. 4.7 and 4.8).
Now, create another MTS remote data module project and name it pMTest2. Its
structure fully coincides with the prest1 data module, except that the request
returns the total number of clients.
Compile the second project and add the new data module to the same TestPack
package.
For convenience when working with projects, a group of projects will be the best solution
in this example.
Developing Distributed Transaction Objects
To implement a distributed transaction, it is necessary to create an additional ob-
ject to handle distributed transactions.
For this purpose, we will create a new MTS object using the transactional object
creation wizard.
ing 4.4. A Module of a Distributed Transaction Object
unit uDistObject;
{SWARN SYMBOL_PLATFORM OFF}
interface
uses138 __— Part I: COM and COM+ Applications
>
ActiveX, Mtsobj, Mtx, ComObj, DistrObject_TLB, MTSServerl_TLB,
MTSServer2_TLB, StdVcl;
type
TDistrObject = class (TMtsAutoObject, IDistrobject)
private
DMTestl: IDMTest1;
DMTest2: IDMTest2;
protected
procedure ExecTransaction(out OrdCount, CustCount: Integer); safecall;
end;
implementation
uses ComServ;
procedure TDistrObject .ExecTransaction(out OrdCount, CustCount: Integer);
var Tr: ITransactionContextEx;
begin
Tr := CreateTransactionContextEx;
try
OleCheck (Tr .CreateInstance (CLASS_DMTest1,IDMTest1, DMTest1));
OleCheck (Tr .CreateInstance (CLASS_DMTest2,IDMTest2, DMTest2));
DMTest1.GetOrderCount (OrdCount) ;
DMTest2.GetCustCount (CustCount) ;
Tr Commit;
except
Tr Abort;
end;
end;
initialization
TAutoObjectFactory.Create (ComServer, TDistrObject, Class_DistrObject,
ciMultiInstance, tmBoth);
end.
‘We must use two previously created servers in the TDistrObject transactional ob-
ject. For this purpose, create two variables in the private section pointing to the
interfaces of these servers. Here, the corresponding type libraries must be con-
nected in the uses section.Chapter 4: COM+ Technology (Microsoft Transaction Server) 139
>
Create the ExecTransaction method, which should return the results of the work
of two servers. The method is created in the usual way in the type library of the
TDistrObject object.
At the beginning of the method, we create an instance of the ITransactionContextEx
interface, which encapsulates the capabilities of the distributed transaction context.
All the work is done by this interface.
The createInstance method provides for the the creation of instances of server
interfaces. The GUIDs of the servers and interface variables are passed as the
method's parameters.
Then, using the GetorderCount and GetCustCount methods, we receive the infor-
mation from two servers.
The commit method completes the transaction. In case of error, the Abort method
rolls the distributed transaction back.
Finally, compile the project and install the object of the distributed transaction
in the same TestPack package.
nt Creation
The client part of the application is created in the same way as in the previous ex-
ample, with some slight changes.
You have to use the TDistrobject distributed transaction object as the server for
the TpcomMconnection component. It is specified by the serverName property (see
the previous example), or by the servercu1p property, where the GUID of the
object must be contained.
So, the handler method of the bucaiculate button looks as follows:
procedure TimMain.buCalculateClick (Sender: TObject);
var OrdCount, CustCount: Integer;
begin
Screen.Cursor := crHourGlass;
try
conDCOM. Open;
conDCOM.AppServer .ExecTransaction(OrdCount, CustCount) ;
laOrdCount .Caption:=IntToStr (OrdCount) ;
laCustCount .Caption:=IntToStr (CustCount) ;
conDCOM. Close;140 ___— Part I: COM and COM+ Applications
>
finally
Screen.Cursor := crDefault;
end
end;
The returned values are passed to the TLabe1 components for display.
Summary
Microsoft Transaction Server technology is based on the COM and Automation
technologies. The name "MTS" is used for the versions operating in Windows 95/98
and NT. For Windows 2000, a new version has been developed with the name
COM+.
Using MTS in distributed applications enhances their efficiency and data security.
The main element of the technology, used when the applications are created, is the
transactional object. As a rule, it is of moderate size and encapsulates part of the
business logic of the application, and the MTS environment "knows" how to work
with it. For each transactional object, the MTS environment creates an auxiliary
object of the transaction context.PART Il
> <<
DATA ACCESS
TECHNOLOGIES
Chapter 5: The Architecture
of Database Applications
Chapter 6: dbExpress Technology
Chapter 7: Using ADO with DelphiChapter 5
The Architecture
of Database
Applications144 __ Part Il: Data Access Technologies
data source — a database. The interaction involves the following opera-
B y definition, any database application is aimed at interaction with some
tions:
OG Retrieving information from a database
© Representing data in a specified format for the user
© Editing data according to the business algorithms implemented in a program
© Returning the changed records back to the database
Along with databases proper, data sources can also be represented by normal
files — text files, spreadsheets, etc. However, this chapter deals only with applica-
tions that interact with databases.
As you probably know, databases are served by special programs — database man-
agement systems — which are divided into local, mostly single-user systems for
working with desktop applications, and network systems (often remote), which are
multi-user oriented systems that run on specially allocated computers, or servers.
The primary criteria for this classification are the database capacity and average
database management system load.
However, in spite of the variety of implementations, the basics of database appli-
cations remain the same.
The application itself includes a mechanism for receiving and sending data,
a mechanism for internal representation of data in some form, a user interface for
presenting and editing data, and business logic for processing data.
The mechanism for receiving and sending data provides a connection to a data
source (often indirectly). This mechanism must "know" the address of the data
source, as well as the transfer protocol to use for a two-way data stream.
The mechanism for internal representation of data is the core of the database appli-
cation. It allows received data to be stored in the application and enables these
data to be passed to other parts of the application on request.
The user interface allows users to browse and edit the data, as well as manage data
and the application as a whole.
The business logic of the application is a set of data processing algorithms imple-
mented in the program. There is special software between the application and the
database that connects the program and the data source and manages the data ex-
change process. This middleware can be implemented in a wide variety of ways,Chapter 5: The Architecture of Database Applications 145
>
depending on the database capacity, the system tasks, the number of users, and the
techniques used to establish a connection between the application and the data-
base. This middleware can be implemented as an application environment that is
essential for the application to work at all, as a set of drivers and DLLs that the
application refers to, it can be integrated into the application itself, or it can be a
remote server that services thousands of applications.
A data source is the data storage area (the database itself) and a database manage-
ment system that ensures data integrity and consistency.
This chapter covers in detail the traditional methods of developing database appli-
cations with Delphi. Although there are a variety of implementation techniques
and an abundance of technical details, the general architecture of Delphi database
applications follows the above schema.
Delphi 6 supports quite a large number of various technologies for accessing data
(those that are applied with distributed and web applications will be dealt with later
in this book). However, the sequence of operations for constructing database ap-
plications are still virtually the same. Essentially, the same key components are
used, but just slightly adapted in order to suit the requirements of the specific data
access technology.
This chapter discusses general approaches to developing Delphi database applica-
tions, base classes, and mechanisms which do not change, regardless of whether
you choose Borland Database Engine, ADO, or dbExpress for your application.
Thus, this chapter deals with the following issues:
O The structure of Delphi database applications
G The basic components used for developing database applications, as well as
their interaction
© The data module
Programmatic implementation of different parts of a database application
aa
The concept of a dataset and its role in the basic mechanisms of the database
application
The types of components that contain datasets
Fields, field types, and field objects
The use of indexes, and techniques for managing datasets that involve indexing
gaaa
The parameters of queries and stored procedures146 ___ Part Il: Data Access Technologies
General Structure
of a Database Application
To start with, we will examine how the database application is structured as
a whole, and which components and program structures are required by the appli-
cation. Here we will only concentrate on fundamental issues. All the concepts used
here will be considered in more detail later in this chapter.
How a Database Application Works
Delphi Repository doesn't include a separate template for developing database ap-
plications. Therefore, like any other Delphi application, the database application
starts with a standard form. This approach is undoubtedly justified, as a database
application has a user interface. This user interface is created by using standard and
specialized visual components in conventional forms.
Visual data-aware components are located in the Data Controls page of the
Component palette. The majority of them are modifications of standard controls,
adapted for managing datasets.
An application can contain an arbitrary number of forms and use any interface
(MDI or SDI). Usually, one form is responsible for executing a group of similar
operations united by the same purpose.
Every database application is based on datasets, which are groups of records (they
can be conveniently presented in tabular form in the memory) that have been re-
trieved from the database for browsing and editing. Each dataset is contained in
a specific data access component. For data access technology, Delphi VCL im-
plements both a set of base classes that support dataset functionality and sets of
child components. These sets are practically identical in their composition, and
have a common ancestor in the TDataset class.
The link between a dataset and data viewing controls is provided by a special com-
ponent — TDataSource. It manages the data flows between the dataset and the
appropriate data controls. This component facilitates data transfer to data controls
and returns the results of the editing session to the dataset. It is also responsible for
changes in the status of the data controls when the dataset undergoes modifications
and passes control signals from the user (of the data controls) to the dataset.
The tDataSource component is located in the Data Access page of the Compo-
nent palette.Thus, the
Chapter 5: The Architecture of Database Applications 147
>_>
basic data access mechanism is created by three kinds of components:
CG Components that contain datasets (descendants of the TDataset class)
GC tbatasource components
© Data c
ontrols
Let's examine the interaction between these components in a database application
(Fig. 5.1).
F Selections ® Selection:
Seteciond
Selection
PE -
£7 og
TDataSource components
Data Access components
Data Access software
Database |
Fig. 5.1. The data access mechanism in a database application148 ___ Part Il: Data Access Technologies
In an application, a data source or middleware interacts with a data access com-
ponent that contains a dataset and addresses the functions of the corresponding
data access technology in order to perform various operations. A dataset compo-
nent is an "image" of a database table in the application. The permitted number of
components in the application is unlimited.
Each data access component can be associated with at least one TDataSource
component. The latter provides a link from a dataset to the relevant data controls.
The tdataSource component enables the current values of dataset fields to be
passed to these components, and returns the changes made.
Another function of the TDataSource component is synchronizing the behavior
of data controls with the dataset status. For example, if a dataset is not active, the
TDataSource component deletes the data from the data controls and disables these
controls. Or, if the dataset is working in read-only mode, the TDataSource compo-
nent is responsible for informing the data controls that the data cannot be modified.
Each tdataSource component can be connected to several data controls. These
components are modified controls designed to display information from datasets.
Once a dataset is open, the component enables the transfer of records from the
required database table into the dataset. The dataset cursor is placed at the first
record. The TDataSource component passes the values of the specified fields of the
current record to the appropriate data controls. While navigating through the rec-
ords in the dataset, the current values of fields in the data controls are automati-
cally refreshed.
The user can browse and edit data using the data controls. Changed values are
immediately passed from the data control to the dataset with the TDataSource
component. Subsequently, these changes can either be passed to the database or
cancelled.
Now that you have a general idea of how a database application operates,
let's move to a step-by-step examination of the process of developing such an ap-
plication.
Data Modules
It is recommended that a special "form" be used for placing data access compo-
nents in a database application — the TDataModule class. Note that a data module
has nothing in common with the traditional application form, as its direct ancestorChapter 5: The Architecture of Database Applications 149
>
is the Tcomponent class. TDataModule can contain only non-visual components.
A data module is available to a programmer in the design stepe, just as any other
project module. The user cannot see the data module during runtime.
To create a data module, you can use the Object Repository or the Delphi Main
Menu. The Data Module icon can be found on the New page. As I mentioned
before, a data module has little in common with the standard form, if for no
other reason than the fact that the TDataModule class is directly derived from the
TComponent class. Since non-visual components require practically nothing from
the platform, TDataModule has few properties and event handlers of its own —
though its descendants that operate in distributed applications perform very im-
portant tasks (see Chapter 8, "The DataSnap Technology. Remote Access Mecha-
nisms").
To design a data structure (model, diagram) for your application, you can take ad-
vantage of the capabilities provided by the Diagram page of the code editor. You can
import any element from the hierarchical tree of the data module components that
constitute the Diagram page and specify its relationship with other components.
With the control buttons you can set a synchronous browsing relationship or a
master-slave relationship for the elements in the diagram. As soon as the relation-
ships are specified, the properties of the relevant components are automatically set.
Pp DataModulet Co
a
ADOComection! ADOComman
ES ae
ADOTabe! DataSource!
ADOGuent Daleseutce2
Fig. 5.2. A data module
To call data access components that reside in a data module from any other pro-
ject module, you need to include the name of this module in the uses clause:
implementation150__— Part Il: Data Access Technologies
uses uDataModule;
DM.Tablel.Open;
The advantage of placing data access components in the data module is that if the
value of a property changes, this change is immediately shown in all the regular
modules to which this data module is attached. Additionally, all the event handlers
of these components — i.e., the entire logic of working with the application — are
gathered in the same place, which is also very convenient.
Connecting to Data Sources
The first step for creating a database application is to connect it to a data source.
This is what data access components are used for.
The data access component is the foundation of a database application. Using a
database table as the base, it creates a dataset and enables you to effectively man-
age it. This component closely interacts with the functions of the corresponding
data access technology. The functionality of the data access technology is usually
accessed through a range of interfaces.
All the data access components are non-visual. It is best to place them in the data
module. Let's examine the sequence of steps for setting up the database applica-
tion. As an example, we'll use a tabular component. The following guidelines show
you how to customize a table component.
1, Move the data access component to the data module and attach it to the data-
base. Depending on the technology, either a special-purpose component
establishes a connection, or the data source, driver, interface, or DLL is ad-
dressed directly. Techniques for connecting to databases are covered in later
chapters.
2. Attach the component to the database table. To do this, use the TableName prop-
erty that is available in the Object Inspector window. Once you have performed
the first step, all the available tables from the database will appear in the list of
this property. After the name is selected in the TableName property, the compo-
nent becomes attached to it.
3. Rename the component. This is an optional operation. However, in any case,
it makes sense to assign pertinent names to your data access components, onesChapter 5: The Architecture of Database Applications 151
that correspond to the names of the connected tables. Component names
usually duplicate the names of tables (for example, orders, OrdTable,
OF tbhlorders).
4. Activate the communication channel between the component and the database ta-
ble. To do this, use the Active property. If you set this property to True in the
Object Inspector, the connection will be activated. This operation can also be
performed in the source code of your application. There is also the open
method for opening a dataset, and the close method for closing it.
Customizing the TDataSource Component
At the second stage of development of a database application, you need to place
the TDataSource component in a form and customize it for optimal performance.
This component allows interaction between the dataset and data controls. For the
most part, one component will correspond to one TDataSo component, al-
though there can also be several of them.
To set the properties of the TDataSource component, proceed as follows.
1. Connect the TDataSource component with the dataset. Use the dataset property
of the TDataSource component, which is available in the Object Inspector. This
is a pointer to a DataSet component instance. All the available pataset com-
ponents are listed in this property, and can be accessed through the Object
Inspector.
2. Rename your component. This operation is optional, but it is advisable to assign
names to your components that correspond to the names of the attached da-
tasets. Usually the name of a component includes the name of the dataset (for
example, ordSource OF dsOrders).
You can attach the TDataSource component not only to a dataset from the same form,
but from any form whose module is declared in the uses clause.
The TpataSource component has a range of useful properties and methods.
The following property enables you to set a connection for your dataset compo-
nent:
property DataSet: TDataSet;152 __ Part Il: Data Access Technologies
_
And the property below lets you define the current state of a dataset:
type TDataSetState = (dsInactive, dsBrowse, dsEdit, dsInsert,
dsSetKey, dsCalcFields, dsFilter, dsNewValue, dsOldValue, dsCurValue,
dsBlockRead, dsInternalCalc) ;
property State: TDataSetState;
By using the property
property Enabled: Boolean;
you can activate or disable all the connected data controls. If this property is set to
False, none of the connected data controls are active. For example, if you need to
implement accelerated navigation through records in a large dataset, you may want
to disable all connected data controls.
The property
property AutoEdit: Boolean;
if set to True, will always switch a dataset to edit mode as soon as it receives the
focus of one of the connected data controls.
Similarly, the method
procedure Edit;
activates the edit mode for the connected dataset.
The following method
function IsLinkedTo (DataSet: TDataSet): Boolean;
returns True if the component indicated in the DataSet parameter is truly con-
nected to the given TDataSource component.
The event handler
type TDataChangevent = procedure(Sender: TObject; Field: TField) of object;
property OnDataChange: TDataChangeEvent;
is called when you edit data in one of the connected data controls.
The event handler
property OnUpdateData: TNotifyEvent;
is called prior to updating data in the database.
The event handler
property OnStateChange: TNotifyEvent;
is called when the state of the connected dataset is changed.Chapter 5: The Architecture of Database Applications 153
>_>
Displaying Data
In the third stage of developing a database application, you have to design a user
interface on the basis of data controls. These components are specifically designed
for viewing and editing data. Outwardly, most of these components do not look
any different from traditional controls. Moreover, many data controls descend
from standard controls.
Data controls must be connected to the TDataSource component, and through
it to the data access component. To do this, use the property
property: TDataSource;
This property can be found in every data control.
Most data controls are used for displaying data from a single field. These type of
components have an additional property —
property DataField: String;
Here, the dataset field to be displayed in the control is specified.
You thus need to perform the following operations for every data control:
1, Connect the data control to the TDataSource component. Use the DataSource
property, which must point to the required TDataSource instance. A data
control can only be connected to one TDataSource component. The required
component can be selected from the list of this property in the Object
Inspector.
2. Define the field in the dataset. Use the DataFiela property of the TFields type.
In this property, you should specify the name of the field of the attached da-
taset. If the DataSource property is set, you can select the field name from the
list. This step is only applicable for components that display a single field.
Datasets
A set of records from one or more database tables that is passed to an application
as the result of activating a data access component, from this point on will be re-
ferred to as a dataset. Obviously, to represent a group of records in an object-
oriented environment, an application must use the capabilities of a certain class.
This class should contain a dataset and be equipped with methods for managing
records and fields.154 ___ Part Il: Data Access Technologies
The tdataset class is the base class in the hierarchy; it contains an abstract dataset
and implements the most general methods for handling it. You can pass either a
record from a database table or a string from a traditional text file to it — the da-
taset will function equally well either way.
Special VCL components for various data access technologies are implemented
based on the base class. These allow the developer to create database applications
using identical techniques and setting the same properties. Thus, by studying the
capabilities of the Tpataset class, we obtain information that is equally applicable
to all components that contain a dataset.
An Abstract Dataset
The tTDataset class forms the basis for the hierarchy of classes that implement the
functionality of datasets in Delphi database applications. In spite of the fact that
this class contains almost no methods that actually support the work of the main
dataset mechanisms, its importance is difficult to overestimate.
This class sets the structural foundation needed for any dataset to function.
In other words, it serves as the dataset skeleton, which provides methods that need
only to have the required calls for the functions of this technology added to them.
You do not need to use the tpataset class for solving more routine programming
tasks while developing database applications. However, developers can use the class
as a basis for creating their own custom components.
A dataset is opened and closed by the property
property Active: Boolean;
which should be set to True or False, respectively. Similar operations are per-
formed by the methods
procedure Open;
procedure Close;
Navigating through a Dataset
Once a dataset is open, you can navigate through its records. The current record
corresponds to the concept of the cursor in database servers. As soon as a dataset is
open, the cursor is placed at the first record.Chapter 5: The Architecture of Database Applications 155,
>_>
The following methods move the cursor next or to the previous record, respectively:
procedure Next;
procedure Prior;
You can move to the beginning or to the end of a given dataset using the method
procedure First;
procedure Last;
You have reached the last record in a given dataset if the value of the property
property Eof: Boolean;
is True.
A similar function for the first record is performed by the property
property Bof: Boolean;
Moving forward and backward by a specified number of records is performed by
the method
function MoveBy (Distance: Integer): Integer;
The Distance parameter defines the number of records. If its value is negative, the
cursor moves to the beginning of the dataset; otherwise it moves to the end.
The following method is used for disabling all the data controls in order to acceler-
ate navigation:
procedure DisableControls;
The reverse operation is performed by the method
procedure EnableControls;
The total number of records in the dataset is returned by the property
property RecordCount: Integer;
However, this property should be used carefully, because every call for it leads to
an update of the dataset, which can cause problems with large tables or complex
queries. If you need to determine whether dataset is empty (a common operation),
you can use the method
function IsEmpty: Boolean;
which returns True if the dataset is empty. Or, you could use the above-mentioned
properties
if MyTable.Bof AND MyTable.Eof156__— Part Il: Data Access Technologies
>
then ShowMessage('DataSet is empty");
The number of the current record is returned by the property
property RecNo: Integer;
The record size (in bytes) is returned by the property
property RecordSize: Word;
Dataset Fields
Every dataset record is a set of table field values. Depending on the type of the com-
ponent and its settings, the number of fields in a dataset can vary. Note that a dataset
doesn't necessarily include all the fields of a database table.
The collection of dataset fields is contained in the property
property Fields: TFields;
while all the required parameters of these fields are contained in the property
property FieldDefs: TFieldDefs;
The total number of fields in a dataset is returned by the property
property FieldCount: Integer;
and the total number of BLOB fields is contained in the property
property BlobFieldCount: Integer;
The property below lets you access the values of the fields in the current record:
property FieldValues [const FieldName: string]: Variant; default;
where the FieldName parameter sets the name of a field.
The developer very often refers to dataset fields in the process of programming.
If the structure of the fields in a dataset is fixed and never changes, this can be
done as follows:
for i := 0 to MyTable.FieldCount - 1 do
MyTable.Fields[i].DiplayFormat := '#.###';
Otherwise, if the arrangement and composition of dataset fields tend to vary, you
can use the method
function FieldByName(const FieldName: string): TField;Chapter 5: The Architecture of Database Applications 157
>_>
which is done as follows:
MyTable.FieldByName('VENDORNO').AsInteger := 1234;
The name of a field contained in the FieldName parameter is not case-sensitive.
The method
procedure GetFieldNames (List: TStrings);
returns a full list of the names of dataset fields to the List parameter.
Fields and various techniques for handling them will be further discussed later in
this chapter.
Editing a Dataset
The tTDataSet class contains a number of properties and methods that let you edit
datasets.
But it is first useful to find out if a dataset can be edited at all. Use the property
property CanModify: Boolean;
If the value of this property is True, then the dataset supports editing. Before you
begin editing, make sure that the dataset is set to edit mode, using the method
procedure Edit;
To save the changes made to the dataset, use the method
procedure Post; virtual;
The developer can either call the post method manually, or have the dataset call it
itself when there is a move to another record.
If necessary, all the changes made after the Eait method was last called can be
cancelled with the method
procedure Cancel; virtual;
A new empty record is appended to the end of the dataset with the method
procedure Append;
A new empty record is inserted in place of the current one with the method
procedure Insert;
while the current record as well as all the following records are shifted one position
down.158 ___ Part Il: Data Access Technologies
When the Append and Insert methods are used, the dataset switches to edit mode
automatically.
Additionally, you can add or insert a new record with fields that have already been
assigned values. Use the methods
procedure AppendRecord(const Values: array of const);
procedure InsertRecord(const Values: array of const);
which is done as follows:
MyTable.AppendRecord([2345, ‘New customer’, '+7(812)4569012", 0, '"]);
Once these methods are called and executed, the dataset automatically returns to
the browse state.
You can fill all the fields in the current records in a similar way with the method
procedure SetFields(const Values: array of const);
The current record is removed by the method
procedure Delete;
Note that the dataset never prompts you for confirmation, but simply does what it
is told.
The contents of all the fields in the current record can be deleted by the method
procedure ClearFields;
Note that all the fields are cleared (11211), not reset to the ni value.
The following property informs you whether the current record has been edited:
property Modified: Boolean;
If the value of the property is True, it has.
You can update a dataset without closing and reopening it. Use the method
procedure Refresh;
However, this method works only with tables and those queries that cannot be edited.
Handling Dataset Events
Event handlers for the tpataset class provide the developer with a huge range of
capabilities for tracking events that occur with a dataset.Chapter 5: The Architecture of Database Applications 159
>_>
A pair of event handlers (one for before and one for after the event) is supplied for
the following dataset events:
© Opening and closing a dataset
OG Activating edit mode
OG Activating insert mode
G Saving changes made
O Canceling changes
© Navigating through the records
© Refreshing a dataset
Note that in addition to the insert mode event handlers, you can use another
method
property OnNewRecord: TDataSetNotifyBvent;
which is called directly when inserting or adding a record.
Additionally, you can use event handlers for errors that occur. These event han-
dlers are provided for errors in deletion, editing, and saving changes.
The event handler:
property OnCalcFields: TDataSetNotifyEvent;
is very important when you set values for calculated fields. It is called for every
record displayed in the data controls that a dataset uses each time the values of
the fields in the data controls are changed.
If the calculations performed by the oncalcFields event handler are too compli-
cated, you can avoid calling it too frequently with the property:
property AutoCalcFields: Boolean;
By default, this property is set to True and fields are recalculated every time the
values of fields are changed. But if you assign a value of False, the OnCalcFields
event handler will be called only when you open a dataset, activate edit mode,
or refresh a dataset.
All the above event handlers belong to the same type:
type TDataSetNotifyEvent = procedure (DataSet: TDataSet) of object;
And the event handler160 __ Part Il: Data Access Technologies
type TFilterRecordEvent = procedure(DataSet: TDataSet; var Accept:
Boolean) of object;
property OnFilterRecord: TFilterRecordEvent;
is called for every dataset record if the property Filtered = True.
Standard Components
If you examine the data access components in the Component palette, you will
notice that the sets for each of the various data access technologies are basically
identical. Every set includes a component that contains tabular functions,
an SQL query component, and a stored procedure component. And although they
have different direct ancestors, the functions of these components in different
technologies are still practically identical.
Therefore, it makes sense to examine the properties and methods common to the
components by pretending that the table, query, and stored procedure components
in each of the above groups descend from the same virtual ancestors.
Table Components
Table components provide access to entire database tables by creating datasets in
which the structures of the fields completely duplicate the database table. This
feature means that the component is easy to set-up and has a number of extra
functions that enable the use of table indexes.
However, in practice programming rarely involves using an entire database table.
And when you work with database servers, the middleware used by the data access
technologies always translates a request for a table data into a simple SQL query,
for example:
SELECT * FROM Orders
In this situation, using table components becomes less productive than using queries.
Once the connection to the data source has been established, you need to indicate
the name of the table in the property
property TableName: String;
Sometimes, the TableType property also specifies the type of table (for example,
Paradox tables).Chapter 5: The Architecture of Database Applications 161
>
If the connection with the data source has been set correctly, the name of the table
can be selected from the drop-down list of the TableName property in the Object
Inspector window.
Table components have the advantage of supporting indexes, which accelerates the
whole process of working with a table. All indexes created for a table in the data-
base are automatically loaded to the component. Their parameters can be accessed
through the property
property IndexDefs: TIndexDefs;
The TIindexDefs class will be described later in this chapter.
The developer can manipulate indexes while working with the given component.
An existing index can be selected from the Object Inspector list with the property
property IndexName: String;
or the property
property IndexFieldNames: String;
where you can specify a random combination of names of the indexed fields
of a table. Field names are delimited by semicolons. You can thus using the
IndexFieldNames property specify composite indexes.
The IndexName Or IndexFieldNames properties cannot be used simultaneously.
The number of fields used in the current index of a table component is returned by
the property
property IndexFieldCount: Integer;
The property
property IndexFields: [Index: Integer]: TField;
is an indexed list of the fields included in the current index:
for i := 0 to MyTable.IndexFieldCount — 1 do
MyTable.IndexFields[i].Enabled := False;
Table components have a number of methods that allow you to manipulate tables
and indexes.
The method
procedure CreateTable;162 __ Part Il: Data Access Technologies
_>
creates a new table in the database using the defined name and description of the
fields and indexes from the TFieldDefs and TIndexDefs properties. If any table
with this name already exists in the database, it will be deleted and overwritten by
the new version with a new structure and data.
The method
procedure EmptyTable;
deletes all the records from a dataset and a database table.
The method
procedure DeleteTable;
deletes the database table associated with a component. The dataset must be closed.
The following method:
type
TIndexOption = (ixPrimary, ixUnique, ixDescending,
ixCaseInsensitive, ixExpression, ixNonMaintained) ;
TIndexOptions = set of TIndexOption;
procedure AddIndex(const Name, Fields: String; Options: TIndexOptions,
const DescFields: String='');
adds a new index to a database table. The name parameter sets the name for the
index. The Fields parameter lists the names of the fields contained in the index,
which are delimited by semicolons. The DescFields parameter specifies the de-
scription of the index from the constants declared in the TIndexOption type.
The method
procedure DeleteIndex(const Name: string);
deletes an index.
For more information on table components, refer to the "Mechanisms for Managing
Data" section in this chapter.
Query Components
Query components are designed for creating SQL queries, preparing parameters for
these queries, passing them to a database server, and presenting the result of the
query in a dataset. Sometimes the dataset can be edited, and sometimes it cannot.Chapter 5: The Architecture of Database Applications 163
_>
If every single string in the dataset of a query component uniquely corresponds to
a string in a database table, you can edit this component. For instance, the query
SELECT * FROM Country
can be edited. If the above rule does not hold, this dataset can be used only for
viewing, and the capabilities of components are irrelevant. Where, for example,
should the results of editing a record of the following query be recorded?
SELECT CustNo, SUM(AmountPaid)
FROM Orders
GROUP BY CustNo
Every record in this query is the sum of other records, the exact number of which
is unknown in advance.
However, query components represent a powerful and flexible tool for handling
data. You can solve far more difficult tasks with query components than you can
with table components.
All in all, a query component works faster, since the structure of the fields re-
turned by a query can change, and TFieldDe¢ class instances that store information
on field properties can be created on demand at run time.
A table component creates all the classes used for describing fields, which accounts for
its slower performance as compared to a query in a client-server application.
Let's now take a look at the general properties and methods of query components.
The text of a query is defined by the property
property SQL: TStrings;
The property
property Text: PChar;
contains the final version of the query text which will be passed to the server.
A query can be executed using one of three approaches.
If the query is to return a result set, use either the method
procedure Open;
or the property
property Active: Boolean;164 __ Part Il: Data Access Technologies
>
which you should set to True. Once your query has been processed, the dataset of
the component is open. The query is closed by the method
procedure Close;
or the same Active property.
If the query doesn't return a result to the dataset (as is the case with the InsERT,
DELETE, and UPDATE statements), use the method.
procedure ExecSQL;
and, after your query has been processed, the dataset of the component will not be
opened. Any attempt to use the open method or the Active property for such
a query will produce an error of a pointer to the dataset cursor being created.
Once a query has executed, the property
property RowsAffected: Integer;
returns the number of records processed by the query.
To enable editing of a query's dataset, set the following property
property RequestLive: Boolean;
to True. This property can be specified, but does not work for a query that returns
a result which cannot be modified because of the query itself.
To prepare a query for execution, use the method
procedure Prepare;
which allows you to allocate the necessary server resources, and provides optimization.
The method
procedure UnPrepare;
releases the resources used for preparing a query.
The result of these two operations is reflected in the property:
property Prepared: Boolean;
where a True value indicates that the query is ready to run.
The Prepare and UpPrepare methods are optional, because your component will
do this automatically. However, if you plan to run this query several times in a
row, you need to prepare it manually prior to the first run. Then the server will not
waste time on useless operations in subsequent execution, as the resources have
already been allocated.Chapter 5: The Architecture of Database Applications 165
>
Quite often, queries have parameters that can be set, the values of which are de-
termined immediately before running the query.
The property
property Params: TParams;
is a list of Tparam objects, each one of which includes settings for a particular pa-
rameter. The Params property is automatically refreshed when the text of a query is
changed. The TParams class will be discussed in more depth later in this chapter.
In the TADOQuery component, the counterpart of the Params property described above
is called Parameters and has the TParameters type.
The property
property ParamCount: Word;
returns the number of query parameters.
The property
property ParamCheck: Boolean;
determines whether the params property should be refreshed when the content of
the query is changed at run time. If the value is set to True, then the property is
updated.
Additionally, query components contain several properties and methods described
in the "Mechanisms for Managing Data" section of this chapter.
Stored Procedure Components
Stored procedure components are designed for determining stored procedures, set-
ting their parameters, executing these procedures, and returning the results to the
component.
Depending on the data access technology selected, each stored procedure compo-
nent has its own technique for connecting to the server. As soon as the connection
to the data source has been established, you can select the name of the stored pro-
cedure from the list by the property:
property StoredProcName:
xing;166 __ Part Il: Data Access Technologies
>_>
After this, the property
property Params: TParams;
which is designed for storing parameters of a procedure, is automatically filled.
It is important to divide parameters for stored procedures into two input and out-
put parameters. The former contain basic data, and the latter give the results of
procedures.
The tTParams class is discussed at greater length later in this chapter.
The total number of parameters is returned by the property
property ParamCount: Word;
To prepare a stored procedure, use the method
cedure Prepare;
or the property
property Prepared: Boolean;
by setting it to True.
The method
procedure UnPrepare;
or the Prepared False property reverse the above operation.
Additionally, by checking the value of the Prepared property you can learn
whether or not a given procedure has been prepared for execution.
WARNING
After executing a stored procedure, the initial order in which the parameters of a given
procedure are arranged in the Params list may change. Therefore, to access any par-
ticular parameter, the following method is recommended:
function ParamByName(const Value: String): TParam;
If a stored procedure returns a dataset, the component can be opened by the
method
dure Open;
or by the property
property Active: Boolean;Chapter 5: The Architecture of Database Applications 167
Otherwise, use the method
procedure ExecProc;
which will assign the calculated values to the output parameters.
Dataset States
A dataset can perform extremely varied operations from the moment it is opened
by the open method until it is closed by the close method. You can simply navi-
gate through its records, edit data and delete records, make searches by different
parameters, etc. Here, of course, it is desirable that all operations are executed as
quickly and as effectively as possible.
A dataset is always in a certain state, i.e., it is ready to perform actions of a strictly
defined kind. For every group of operations, a dataset performs a sequence of pre-
liminary actions.
All states of a dataset fall into two groups:
OG The first group comprises the states which a dataset assumes automatically, in-
cluding short-term states that accompany various operations on the fields in a
dataset (Table 5.1).
© The second group includes the states that can be controlled from applica-
tions — for example, switching a dataset to edit mode (Table 5.2).
Table 5.1. Automatic States of Datasets
State Constant Description
dsNewValue Activated when the NewVa ue property of a dataset field is addressed
dsOldvalue Activated when the 01dVa ue property of a dataset field is addressed
dsCurValue Activated when the CurValue property of a dataset field is called
dsInternalCalc Activated when the values of the fields for which
FindKind = fkInternalCalc are calculated
dsCalcFields Activated when the onCalcFields method is called
dsBlockRead The block read mode, which enables accelerated navigation through
a dataset
dsOpening Exists when a dataset is opened by the open method or the Active
property
dsFilter Activated when the onFilterRecord method is called168 __ Part Il: Data Access Technologies
_>
Table 5.2. Controllable States of Datasets
State Constant Method Description
dsInactive Close The dataset is closed
dsBrowse Open The dataset can be accessed only for
browsing; the data in it cannot be edited
dsEdit Edit The dataset can be edited
dsInsert Insert New records can be added to the dataset
dsSetKey SetKey The mechanism for searching by key is acti-
vated; ranges also can be used
The base TDataSet class that contains the properties of a dataset allows you to
check the current state of a given dataset, and also to change this state.
The current state of a dataset is passed to the state property of TDataSetState type:
type TDataSetState = (dsInactive, dsBrowse, dsEdit, dsInsert,
dsSetKey, dsCalcFields, dsFilter, dsNewValue, dsOldValue, dsCurValue,
dsBlockRead, dsInternalCalc);
Dataset states are managed through the open, Close, Edit, and Insert methods.
Now let's see how the state of a dataset changes when standard operations are
performed.
A closed dataset always has the dsInactive state.
Once a dataset is open, its state changes to dsBrowse. This state permits a user to
move around the dataset and view its content, but does not allow the data to be
edited. This is the main state for any open dataset; a dataset can move from this
state into other states, but any changes in state occur through the browsing of data
(Fig. 5.3).
If a dataset needs to be edited, it should be switched to edit mode (dsEdit) using
the Edit method. Once the method has executed, you will be able to modify values
of the fields in the current record. When you move to the next record, the dataset
automatically assumes the dsBrowse state.
If you need to insert a new record in the dataset, use the dsInsert method, which
adds a new empty record at the location of the current cursor. After the move to
the next record, the primary key (if it exists) is checked for uniqueness, and the
dataset resumes the dsBrowse state.Chapter 5: The Architecture of Database Applications 169
>_>
Inactive
DataSet opening =
and closing ~~ insertion
=
/ ee
[ Dataset editing / Key Searching
|_andsaving™ SetKey
Fig. 5.3. The schema of changes in dataset states
The dsSetKey state is used only for table components if you need to use the
FindKey and FindNext method to search, and also if you use ranges (the setRange
method). This state is preserved until either the method for searching by key or the
method for canceling a range is called. Then the dataset resumes the dsBrowse
state.
The dsBlockRead state can be used for a dataset if you need to navigate quickly
through large arrays of records without displaying intermediate data in data con-
trols and without calling the event handler for navigating through records. This can
be done with the DisableControls and EnableControls methods.
Indexes
One of the most pressing problems for any database is to provide the highest possi-
ble level of performance and to maintain this level as the amount of data stored
increases. This task can be solved by using indexes.
An index is a database entity that contains information on the organization of data
in database tables.
Unlike keys that just serve as identifiers for individual records, indexes take up extra
memory resources (quite a substantial amount) and can be stored either together with
tables or as separate files. Indexes are created at the same time as the associated ta-
ble, and are changed when the data in this table undergoes any modification. Up-170 ___ Part Il: Data Access Technologies
dating indexes for a big database table usually requires significant resources, which
makes it worthwhile to limit the number of indexes for such tables if the data in
them need updating often. An index includes unique identifiers of records, as well
as extra information on the organization of data. Obviously, if a server or local
database management system uses indexes to process a query, it takes significantly
less time, as indexes "know" how the records are arranged and can speed up proc-
essing by clustering records by the values of their parameters.
Naturally, Delphi VCL uses all the capabilities of such a powerful tool as indexes
in data access components. It should be mentioned, though, that the properties
and methods for handling indexes can be found only in table components, because
query components handle indexes with SQL.
You can also manipulate a dataset without using indexes, but to do so the table
must not have a primary key — which is rather uncommon. Therefore, datasets use
default primary indexes.
Setting Indexes
In order to set secondary indexing for a dataset, you need to indicate the name
of the index using the property
property IndexName: String;
If no value has been specified for this property, it means that the dataset uses
a primary index.
Alternatively, you can specify an index by the property
property IndexFieldNames: String;
where the names of the fields that comprise the required index are listed; the field
names are delimited by semicolons. Moreover, lists of fields for all existing indexes
are created automatically for this property in the Object Inspector, so the devel-
oper can make the choice. This property also lets you create composite indexes,
but this is possible only if all the fields in the list are indexed.
You can change the current index without closing the dataset, which makes sorting by
indexes very convenient. This method of adjusting indexes is referred to as "on-the-fly"
indexing.Chapter 5: The Architecture of Database Applications 171
>
Once indexing is set, the number of fields in the index is passed to the following
property:
property IndexFieldCount: Integer;
Index Definition Lists
All the information on the indexes of a dataset is stored in the property of the
TDataSet class:
property IndexDefs: TIndexDefs;
Here, each index is supplied with a unique definition in the form of a TIndexDef
object. The following property enables you to access the information on indexes
property Items[Index: Integer]: TIndexDef; default;
which is a list of TIndexDef objects.
Objects of the TIndexDef type can be added to the list by calling the method
function AddIndexDef: TIndexDef;
To search for the index description object, use the method
function Find(const Name: String): TIndexDef;
which returns the object that matches the index name specified by the Name pa-
rameter.
The following pair of methods:
function FindIndexForFields(const Fields: string): TIndexDef;
function GetIndexForFields(const Fields: String; CaseInsensitive:
Boolean): TIndexDef;
lets you find the required definition object by the list of the fields that constitute
a given index. If the index has not been found, the first index that begins with the
specified fields will be looked for. If the search has failed, the first method gener-
ates the EDatabaseError exception, and the second one returns nil.
The list is automatically updated every time you open a dataset. However, you can
update the IndexDefs list of index definitions without opening the dataset by using
the method
procedure Update; reintroduce;172 Part Il: Data Access Technologies
_>
Index Definition
The parameters for every individual dataset index are declared in the TIndexDef
class, while the full list of these parameters for the entire dataset can be accessed
through the IndexDefs property of the TDataset class.
The property
property Name: String;
defines the name of the index.
The list of the fields of the index is contained in the property
property Fields: String;
The fields are delimited by semicolons.
The property
property CaseInsFields: String;
lists all the fields which can be treated as case-insensitive when they are being
sorted. The fields are delimited by semicolons. All the fields in this list must be
included in the Fields property. By default, every dataset uses case-sensitive sort-
ing for its records, but some database servers support a combined technique of
sorting fields — both case-insensitive and case-sensitive.
The property:
property DescFields: String;
contains the list of fields that are sorted in descending order. The fields are delim-
ited by semicolons. All the fields in this list must be included in the Fields prop-
erty. The default sorting order for fields is ascending. Several database servers sup-
port both ascending and descending sorting order.
The property:
property GroupingLevel: Integer;
enables you to limit the area of indexing. If this property is set to nil, then index-
ing is applied to all the records in a dataset. Otherwise, only those groups of rec-
ords that have identical values for the number of fields specified by the property
are indexed.
The parameters of an index are specified by the property
property Options: TIndexOptions;Chapter 5: The Architecture of Database Applications 173
_>_
An index can have the following combinations of parameters:
ixPrimary — the primary index.
ixUnique — the values of this index are unique.
ixDescending — this index sorts in descending order.
ixCaseInsensitive — this index uses case-insensitive sorting.
ixExpression — this is an expression index (used for dBASE indexes).
ey |) ee)
ixNonMaintained — this index is not automatically updated when the table is
opened.
The method
procedure Assign(ASource: TPersistent); override;
fills the properties of an object with the values set for the analogous properties of
the ASource object.
For example, you can use properties and methods of index definition objects in this way:
with Tablel do
begin
with IndexDefs do
begin
Clear;
AddIndexDef;
with Items[0] do
begin
Name := 'Primary';
Fields := 'Fieldl';
Options := [ixPrimary, ixUnique]
end;
AddIndexDef;
with Items[1] do
begin
Name := 'SecondIndex';
Fields := 'Fieldl;Field2';
Options := [ixCaseInsensitive];
end;
end;
end;174 __ Part Il: Data Access Technologies
When descriptions for indexes are created, the AddIndexDef method is used, which
adds a new TIndexDef object to the Items list of the TIndexDefs object. This tech-
nique is used first for setting primary indexes, and afterwards for specifying secon-
dary indexes (secondIndex). Every description must be supplied with the fields
that comprise the index, as well as with the index parameters (the Fields and
Options properties).
Parameters of Queries and Stored
Procedures
The property
property Params: TParams;
contains a set of customizable parameters for a query or a stored procedure. This
property is a collection of TParam objects that contain individual parameters.
Consider the following SQL query:
SELECT SaleDat, OrderNo
FROM Orders
WHERE SaleDat >= '01.08.2002' AND SaleDat <= '31.08.2002"
This query selects the numbers of the orders that were placed in August, 2002.
Naturally, the user may require a similar report for a different month, or for the
first ten days in August, etc.
In this case, you can act as follows:
with Query] do
begin
Close;
SQL.Clear;
SQL[O} "SELECT PartDat, ItemNo, ItemCount, InputPrice';
SQL[1] := 'FROM Parts";
SQL[2] := 'WHERE PartDat>= '+chr (39) +DatelEdit.Text+chr (39) +
' AND PartDat<="+chr (39) +Date2Edit .Text+chr (39) ;
Open;
end;
Here, an SQL property is used to modify the text of a query in accordance with
the date values specified by the Date1zdit and Date2zdit single-line editors.Chapter 5: The Architecture of Database Applications 175
>
To solve tasks like these, parameters are used. In this case, the text of the query
will look as follows:
SELECT PartDat, ItemNo, ItemCount, InputPrice
FROM Parts
WHERE PartDat>= :PD1 AND PartDat<= :PD2
The colon before the pp1 and pp2 names denotes parameters. The name of a pa-
rameter is arbitrary. The first parameter in the list of the Params property is the
parameter that comes first in the text of the query.
Once you have entered the text of the query in the SQL property, a TParam object
is automatically created for each one of the parameters. These objects can be ac-
cessed through the specialized editor called by pressing the button of the Params
property in the Object Inspector (Fig. 5.4). You have to specify the type of data
for every parameter, which should match the type of data of the corresponding
field.
. 5.4. The specialized editor for handling query parameters
Now you can use the params property to set current date boundaries:
with Query] do
begin
Close;
Params[0].AsDateTime := StrToDate(DatelEdit.Text) ;
Params[1].AsDateTime := StrToDate(Date2Edit.Text) ;
Open;
end;
The parameters are used for specifying the current values of the date boun-
daries.176___ Part Il: Data Access Technologies
_
You can also set the values for the parameters of your query from another dataset.
To do this, you need to use the DataSource property of the DataSet component.
The tDataSource component that is specified in this property must be linked
to the dataset whose field values need to be passed to the parameters. The names
of parameters should match the names of the fields in this dataset, and then the
DataSource property will begin to work. When you navigate through the dataset
records, the current values of the parameters of the same name are automatically
passed to the query.
For instance, the SQL property of the first query component looks as follows:
SELECT OrderNo, CustNo, SaleDate, AmountPaid FROM Orders
It should be linked to a component of the TDataSource type.
The SQL property of the second query looks like this:
SELECT CustNo, Company FROM Customer
WHERE CustNo = :CustNo
Note that the name of the parameter matches the name of the field in the Orders
table, which contains the customer number. This condition is essential.
The DataSource property of the second query component must point to the
TDataSource component of the first query. As soon as both datasets are open, the
current value of the custNo field of the orders dataset is automatically passed to
the query parameter of the customers component. In this way, a one-to-many re-
lationship is implemented for these two datasets through the field that contains the
customer number.
To conclude our discussion of query parameters, let's examine the properties and
methods of the TParams class, part of the params property, and the TParam class,
which contains an individual parameter.
The TParams Class
The TParams class is a list of parameters.
The elements in this list can be accessed via the indexed property
property Items[Index: Word]: TParam;
while the values of these parameters are contained in the property
property ParamValues[const ParamName: String]: Variant;Chapter 5: The Architecture of Database Applications 177
_>
A new parameter can be added by the method
procedure AddParam(Value: TParam);
However, to do this you need to create a parameter object using the method
function CreateParam(FldType: TFieldType; const ParamName: string;
ParamType: TParamType): TParam;
where FldType indicates the type of data used by the parameter, ParamName defines
the name of the parameter, and ParamType indicates the type of parameter (see below).
Both these methods can work in tandem:
MyParams.AddParam (MyParams.CreateParam(ftInteger, 'Paraml', ptInput));
Instead of setting these parameters one at a time, you can use the method.
function ParseSQL(SQL: String; DoCreate: Boolean): String;
which, if Docreate = True, parses the text of an sou query and builds a new list of
parameters.
Alternatively, you can set all the parameters at once using the method
procedure AssignValues (Value: TParams) ;
A parameter can be deleted from the list by the method
procedure RemoveParam(Value: TParam);
It is useful to call your parameters by their names when you need to identify them;
just as when handling stored procedures, their order can change after they execute.
The same is also true for dynamic queries (their SQL text can change during the
execution process).
Use the following method to call a parameter by its name:
function ParamByName(const Value: String): TParam;
When the developer deals with complex SQL queries, or when multiple corrections
have been made to a query, it is possible to create two different parameters with
the same name by mistake. In such a case, when the query executes, these pa-
rameters are treated as a single parameter, and are both set to the value of the first
query. To ensure that all the names assigned to parameters are unique, use the
following method:
function IsEqual(Value: TParams): Boolean;
which returns true if a duplicate of the value parameter has been found.178 Part Il: Data Access Technologies
_>
The TParam Class
The tTParam class contains the properties of an individual parameter.
The following property specifies the name of the parameter:
property Name: String;
The type of data used by the parameter is defined by the property
property DataType: TFieldType;
The types of data used in the parameter and in the related field must coincide.
The type of a parameter is defined by the set
type
TParamType = (ptUnknown, ptInput, ptOutput, ptInputOutput, ptResult) ;
TParamTypes = set of TParamType;
which has the following values:
© ptUnknown — the type is unknown.
G ptinput — this is an input parameter used for returning values from an appli-
cation.
© ptoutput — this is an output parameter used for passing values to a application.
OC ptInputoutput — this parameter is used for both passing and returning values.
OG ptResult — this parameter is used for passing information on the status of an
operation.
The property
property ParamType: TParamType;
sets the type of parameter.
It is often necessary to ascertain whether the value of a parameter is Null. To do
this, use the property:
property IsNull: Boolean;
This property returns True if the value of the parameter has not been set or if the
value of the parameter is Nui1.
The property:
property Bound: Boolean;
returns True only if the parameter has not been assigned any value.Chapter 5: The Architecture of Database Applications 179
>
The method
procedure Clear;
sets a parameter to Null.
The value of a parameter is specified by the property
property Value: Variant;
However, when you require maximum speed, using variants is not very effective. In
this case, you can use the whole set of As.. properties, which not only return the
value, but also cast it into the required type. For example, the property
property AsInteger: LongInt;
returns an integer that denotes the value of a field.
You need to be careful when using type cast properties, as an attempt to cast an invalid
value will generate an exception.
The following methods allow you to read a parameter value from and write it to
the buffer:
procedure SetData (Buffe:
procedure GetData (Buffer:
Pointer);
Pointer);
and the method below lets you set the necessary data size when you write data to
the buffer:
function GetDataSize: Integer;
You can copy the type of data, the name, and the value for a parameter directly
from the data field using the following method:
procedure AssignField(Field: TField);
while the method below lets you assign a name and a value to a parameter:
procedure AssignFieldValue(Field: TField; const Value: Variant);
The total number of characters for numeric values is set by the property
property Precision: Integer;
The property
property NumericScale: Integer;
determines the number of decimal places.180 __ Part Il: Data Access Technologies
The size of a string parameter can be set by the property
property Size: Integer;
Mechanisms for Managing Data
Related Tables
Database tables can be connected by one-to-many or many-to-many relationships,
and these relationships must be set between indexed fields of two tables.
When a relationship between database tables is created, any component that en-
capsulates a dataset can be used as a master table, while only table components
can be used to set a detail table.
One-to-Many Relationships
To create a one-to-many relationship in a dataset, we use two properties,
MasterSource and MasterFields, which are set for a detail table. The dataset of a
master table doesn't require any specific settings, and the connection created will
work only when you navigate through the records in the master table.
The property
property MasterSource: TDataSource;
defines the tbataSource component that is linked to the master table.
Then, by using the property:
property MasterFields: String;
you can specify the relationship between the fields of the master and detail tables.
This property contains the name of the indexed field that serves as the link be-
tween the tables. If there are several such fields, their names are delimited by
semicolons. Note that it is not necessary to use all the fields of the index to estab-
lish a relationship.
The MasterFields property can be set by the Field Link Designer, called by
pressing the button in the edit field of this property in the Object Inspector.
Here you can select the required index for the detail table from the list of indexes
that can be accessed through the Available Indexes drop-down list. However,Chapter 5: The Architecture of Database Applications 181
>
it should be noted that some data access technologies do not provide you with the
Available Indexes list.
Ayailable Indexes Primary id
Detai Fields ‘Master Fields
[Ordeto
SaleD ate
oe
Shp conact
Joined Fields
Delete
Cleat
ok Cancel | Help
Fig. 5.5. The Field Link Designer window
After that, the names of all the fields that constitute this index appear in the Detail
Fields list. The Master Fields list shows the fields of the master table.
‘Now you have to create the link between the fields. Select the field of the detail
table from the list on the left and then choose the corresponding field of the master
table from the list on the right. After this, the Add button is activated — press it to
create the relationship between the two fields of the master and detail tables. This
link will be displayed in the Joined Fields list.
Once a link between two indexed fields is created, the given index becomes the current
index for the dataset. Depending on the type of database management system, either
the IndexName or IndexF ieldNames property is filled automatically.
You can also delete any existing link. The Delete button deletes a selected link,
while the Clear button deletes all existing links.
As soon as you've set a link between the fields, a one-to-many relationship is es-
tablished. You just need to open both datasets to make sure that the relationship
works.182 ___ Part Il: Data Access Technologies
_>
Many-to-Many Relationships
A many-to-many relationship differs in that the detail table is linked to another
detail table as a master table using a sequence of operations similar to the one used
when creating a one-to-one relationship.
Searching Data
To search through a random selection of fields, you can use the Lookup and Locate
methods.
function Locate(const KeyFields: string; const KeyValues: Variant;
Options: TLocateOptions): Boolean;
function Lookup(const KeyFields: string; const KeyValues: Variant;
const ResultFields: string): Variant;
When you work with the Locate method, you need to pass it a list of the fields that
will be searched (the KeyFields parameter — the names of the fields are separated
by semicolons), their required values (the Keyvalues parameter — the values are
delimited by semicolons) and the search settings (the options parameter). You can
specify the 1oCaseInsensitive option, which disables checking the case of char-
acters, and the loPartialKey option, which activates a search with partial-match
retrieval. If the search was successful, the cursor is positioned on the matching re-
cord, and the method returns True.
Tablel.Locate('Last_Name;First_Name', VarArrayOf(['Edit1.Text',
‘Edit2.Text']), [])7
When you deal with the Lookup method, you need to pass it a list of the fields that
will be searched (the KeyFields parameter — the names of the fields are separated
by semicolons) and their required values (the KeyValues parameter — the values
are delimited by commas). If the search was successful, the method returns the
array of values of variant type for the fields whose names are contained in the
ResultFields parameter.
Tablel.Lookup('Last_Name;First_Name', VarArrayOf(['Editl.Text',
"Edit2.Text']), 'Last_Name;First_Name');
Both these methods can automatically implement a quick index search if you
specify fields of an index in the KeyFields parameter.Chapter 5: The Architecture of Database Applications 183
>
Filtering Data
The most effective way to extract information (especially from large database ta-
bles) in order to populate a dataset is to create and run a corresponding SQL
query. But what if a dataset is based on a table component? Here the filtering
mechanism — an integral part of a dataset — comes to the rescue.
Filtering is based on two basic properties, as well as one additional one. The text
of the filter should be contained in the Filter property, and the Filtered pro-
perty activates and disables the filter. The parameters for a filter are set by the
FilterOptions property.
Query components also support filters. This feature at times allows developers to easily
and elegantly solve complex problems that would otherwise require the text of the query
to be changed and/or a new query component to be created.
When you use a filter, its text is translated into SQL syntax and passed to the
server or through the appropriate driver to the local database management system.
You can create a filter in two ways:
G Simple filters that can be implemented by the native syntax of the filtering
mechanism are created by using the Filter property
G More sophisticated filters that require full use of all the capabilities of the given
programming language are created using the onFilterRecord dataset event han-
dler
All filters can be divided into two categories: persistent and dynamic.
Persistent filters are created at design time and can use both the Filter property
and the onFilterRecora method.
Dynamic filters can be built and edited at run time; they use only the Filter
property.
The text of a filter for the Filter property is compiled from the names of the
fields in the relevant database table; the comparison criteria are specified by util-
izing all comparison operators (>, >=, <, <=, =, <>) as well as the logical operators
AND, OR, NOT:
Fieldl > 100 AND Field2 = 20184 _ Part Il: Data Access Technologies
_>
You cannot compare two fields. The following filter will generate an error if you
attempt to use it:
ItemCount = Balance AND InputPrice > OutputPrice
Dynamic filters can be created by changing an entire filter expression and its
constituent parts. For example, a constraint value for a field can be set by
using the controls of a form, which lets the application user manage the dataset
filtering.
procedure TForml.Edit1Change (Sender: TObject) ;
begin
with Tablel do
begin
Filtered := False;
Filter := 'Fieldl >=" + TEdit (Sender) .Text;
Filtered := True;
end;
end;
Filters can select parts of strings for string fields — use an asterisk:
TtemName="A**
The filter will work only if the Filtered property is set to True. If you need to
modify the text of a dynamic filter or disable a filter, set the Filtered property to
False.
You can set the parameters for a filter with the Filteroptions property:
property FilterOptions: TFilterOptions;
TFilterOption = (foCaseInsensitive, foNoPartialCompare) ;
TFilterOptions = set of IFilterOption;
By activating the foCaseInsensitive parameter, you specify a case-insensitive
pattern of comparing string values. The foNoPartialCompare parameter enables
selection of string values by comparing parts of strings.
The onFilterRecord event handler is declared as follows:
type TFilterRecordEvent = procedure(DataSet: TDataSet; var Accept:
Boolean) of object;
property OnFilterRecord: TFilterRecordEvent;
If this event handler is created for a dataset, then it is called for every record of
this dataset. The program code of the onFilterRecora event handler must assignChapter 5: The Architecture of Database Applications 185
>
either the True or False value to the Accept parameter. As a result, the record is
either passed to the dataset or rejected:
procedure TForml.TableFilterRecord(DataSet: TDataSet; var Accept: Boolean);
begin
Accept := Orders.FieldByName['SaleDate'] .AsString >= DateEditl.Text;
end;
The most important advantage of onFilterRecora over the Filter property is that
this event handler enables you to compare fields and calculate their values.
The disadvantage of this method is that event handler lacks flexibility, although this
filter can be modified by assigning an event handler a procedure variable that pro-
vides a link to a new method.
Using Bookmarks
A bookmark is another tool for handling datasets, which enables you to quickly
move to the required record. A dataset can contain an unlimited number of book-
marks, each one of which is a pointer to a particular record. A bookmark can be
created only for the current record of a dataset.
A bookmark is simply a pointer to a database record:
type TBookmark = Pointer;
A dataset has the property
type TBookmarkStr: string;
property Bookmark: TBookmarkStr; Bookmark,
which contains the name of the current bookmark.
Bookmarks are handled by three basic methods.
The method
function GetBookmark: TBookmark; virtual;
creates a new bookmark for the current record.
The method
procedure GotoBookmark (Bookmark: TBookmark) ;
implements a move to the bookmark specified in the parameter.186 __ Part Il: Data Access Technologies
—_>
The method
procedure FreeBookmark (Bookmark: TBookmark); virtual;
removes the bookmark specified in the parameter.
Additionally, you can use the method
function BookmarkValid (Bookmark: TBookmark): Boolean; override;
which checks if the bookmark points to an existing record. The method
function CompareBookmarks (Bookmarkl, Bookmark2: TBookmark): Integer;
override;
lets you compare two bookmarks:
var Bookmark1, Bookmark2: TBookmark;
if Tablel.CompareBookmark (Bookmarkl, Bookmark2) = 1
then ShowMessage('The bookmarks are the same").
The TDBGria component also supports bookmarks. It has the SelectedRows property
of TBookmarkList type, which is a list of bookmarks that point to simultaneously se-
lected records.
Fields
Every database table, and consequently every dataset, has its own structure deter-
mined by its collection of fields. Each field in a dataset is an object that contains
a description of the type of data that must correspond to the value located in a
particular place in a record. In other words, a field can be thought of as an accu-
mulation of cells which contain data of a particular type and have a fixed place
in every record of the dataset. Or, to put it even more simply, a field is a column
in a table.
In a dataset of a Delphi database application, each field is associated with its own
object. All field objects are based on the TFrie1d class, which contains the funda-
mental properties of an abstract field that does not depend on the data type. This
base class is the common ancestor for all the classes providing the functionality of
actual field objects that depend on the type of data.Chapter 5: The Architecture of Database Applications 187
>
Field Objects
Field objects contain properties and methods of various types of data. These ob-
jects operate in close cooperation with a dataset. For example, to retrieve the val-
ues of fields in the current dataset record, the developer has to write code such as
the following:
Editl.Text +
Tablel.Fields [0] .AsString;
The property of a dataset component
property Fields: TFields;
represents an indexed list of the field objects of a dataset. If the developer doesn't
change the arrangement of the fields in the dataset, the order of field objects in the
Fields list corresponds to the structure of the database table.
Every field object stores a number of parameters that describe this field. For exam-
ple, you can address a field object in a dataset if all you know is the name of this
field:
Editl.Text := Tablel.FieldByName('SomeField') .AsString;
To assign a value to a field in the current record, you can either use the techniques
described earlier, or if the data type is unknown, use the FieldValues property:
Tablel.FieldValues['SomeField'] := Editl.Text;
The simplest way to access the current value of a field is by using the name of this
field:
Tablel['SomeField'] := Editl.Text;
Editl.Text := Tablel['SomeField'];
When you assign values to dataset fields, always check the state of this dataset.
All the classes that describe the hierarchy of the fields with typified names are
based on the Field class. It is the ancestor of classes that enable the functioning
of groups of fields organized by data type.
So, what is a field object and what capabilities does it have to offer to developers?
First, the purpose of having the tField class as the base field class is to be able to
interact with data controls to correctly present data. For example, a field object
stores techniques of alignment, font parameters, heading text, etc.188 _ Part Il: Data Access Technologies
Second, from the point of view of a dataset, a field object is a storage area for the
current value of a field (and not for the entire column of data, as its name seems
to suggest).
It is with fields that data controls interact when they work with a dataset. For in-
stance, if no special settings have been made, the columns in the TDBGrid compo-
nent correspond to the arrangement of the field objects in the connected dataset.
Persistent and Dynamic Fields
Delphi supports two techniques for creating field objects.
Dynamic fields are used by the program if objects for them have not been explicitly
created at design time. Every such object is automatically created in conformity with
the structure of the database which it is linked to as soon as the dataset is open. Each
field object directly descends from the TFie1d class, and its type depends on the type
of data used for the fields of the database table. The properties of a dynamic field are
determined by the parameters of the fields in the database table.
If no additional settings have been specified, a data access component uses only
dynamic fields once it is connected to a database table. You can call properties
and methods of dynamic fields programmatically, using the indexed Fields prop-
erty of a data access component that contains all the dataset fields, or by the
FieldByName method.
Dynamic fields are used when the characteristics of the fields in a database table
are satisfactory for the developer, and there is no need to consider any field out-
side the dataset.
Persistent fields are created by the programmer during the design stage; their prop-
erties can be accessed through the Object Inspector and their names can be se-
lected from the list of objects of the active form in the top part of the Object
Inspector's window. The name of a persistent field object usually combines names
of the table and field, for example, orderscUSTNo.
Persistent field objects are designed using the special-purpose Field Editor, called
by double-clicking the data access component, or with the Fields Editor command
that can be selected from the pop-up menu of this component.
The Field Editor is essentially a list of existing persistent fields; all manipulations
here are performed through the commands of the pop-up menu. The top part of
the editor features the navigation buttons for navigating through datasets; theseChapter 5: The Architecture of Database Applications 189
>
buttons are active only if a dataset is open. If a dataset has aggregate fields
(Fig. 5.6), they are grouped in a separate list in the bottom part of the Field Edi-
tor's window.
(RogeaaeSUM
AogegsteAvG
Fig. 5.6. The window that shows a separate list of aggregate fields
You can add a new field to the list of already available persistent database fields
using the Add fields command, which can be selected from the Field Editor's
pop-up menu. To remove a field, just hit the key. You can rearrange the
positions of the items in a list by dragging them with the mouse. In this way you
can create random combinations of persistent fields.
As soon as the very first persistent field object is created for a given dataset, the dataset
is assumed to contain only those fields, which are available from the list of persistent
fields. This feature can be used to artificially limit the data structure of tables. All the
fields of a dataset are arranged in the order specified by the Field Editor list, irrespec-
tive of their actual location in the database table.
The New field command in the Field Editor's pop-up menu lets you create a vir-
tual persistent field that doesn't actually exist in the data structure of the table (see
Fig. 5.7). The type of such fields can be selected via the set of radio buttons —
Field Type: Data, Calculated, Lookup.
Data fields, which must be based on actually existing table fields, are less common.
If the table field, which corresponds to an object, is deleted, or if the type of data
used for this field is changed, an exception is generated as soon as the dataset is
opened.190 __ Part Il: Data Access Technologies
| Field properties
Heme: [MencacFied Component: [biCustomelencaichel
Type [Stina =| See: [20
Field ype
© Data © Galested © Lookup |
jLooku dei
BF He
"El Best rele 7
Concel Help
Fig. 5.7. The dialog for creating a new persistent field in the Field Editor's dataset
The dialog for creating a new field, which is used for client datasets in multi-tier applica-
tions, enables you to select two extra field types — aggregate fields (the Aggregate
radio button) and internal calculated fields (the InternalCalc radio button).
The TField Class
As mentioned earlier, the TFie1d class performs the role of a base class in the big
hierarchy for fields with various data types, and contains the properties and meth-
ods of an abstract data field. It is from the Trie1a class that all the classes of typed
fields are derived. Although this class is never used for solving real-world problems,
its significance is difficult to overestimate. Practically all fundamental properties of
classes of typified fields are inherited from the trieid class without any additional
modifications, and a set of extra properties and methods facilitate the functioning
of each individual type of data.
As far as event handlers are concerned, four basic handlers declared in the TFiela
class are inherited by all descendants without any changes or additions.
Following is the summary of the properties and methods of the Trie1a class.
The name of an object is contained in the property
property Name: TComponentName;
The name of a field object is created at design time by combining the respective
names of the data access component and the field.Chapter 5: The Architecture of Database Applications 191
_>
The property
property FieldName: String;
returns the name of a field of a database table.
The property
property FullName: string;
is used if the current field is a child field in relation to another field. In this case,
the property contains the names of all parent fields.
The name of the field in the database table is contained in the property
property Origin: String;
The property
property FieldNo: Integer;
returns the initial order number of a field in the dataset. If field objects are persis-
tent, their actual arrangement can be changed in the Field Editor. The property
property Index: Integer;
contains the index of a field object in the Fields list.
The functionality of a field is specified by the property:
type TFieldKind = (fkData, fkCalculated, fkLookup, fkInternalCalc,
fkAggregate) ;
property FieldKind: TFieldKind;
As a rule, the value of this property is set automatically when a field object is created.
It is, in fact, unlikely that the real data field will ever need to be made a calculated
type. Attempts to modify the value of the Fieldkind property usually generate an er-
ror. Let's look at the possible values which can be assigned to this property:
© fkpata — a data field
© fkcalculated — a calculated field
O) £kLookup — a lookup field
© fkInternalcalc — an internal calculated field
O fkaggregate — an aggregate field
If the field belongs to a calculated type, the property:
property Calculated: Boolean;
assumes the True value.192 ___ Part Il: Data Access Technologies
_
The connected dataset is indicated by the property
property DataSet: TDataSet;
which is filled automatically when the object is created with the means of the de-
velopment environment.
The property
property DataType: TFieldType;
returns the type of data which is used for the data field, and the property
property DataSize: Integer;
specifies the amount of memory required for storing the value of the field.
One of the most important tasks of the TField class is providing access to the
current value of a field. In this situation, the Tried class interacts with the buffer
of the current dataset record, and the value itself can be returned by using a num-
ber of properties.
The property
property Value: Variant;
always contains the value saved after the most recent execution of the Post method
of the dataset:
with Tablel do
begin
Open;
while Not EOF do
begin
if Fields[0].Value > 10 then
begin
Edit;
Fields[1].Value := Fields[1].Value*2;
Post;
end;
Next;
end;
Close;
end;Chapter 5: The Architecture of Database Applications 193
>
In this example, all the records within the dataset are searched using the Next
method. If the value of the first field is more than 10, the value of the next field is
doubled. The value property of the dataset field objects is used to do this.
However, because of the use of variants the value property is relatively slow.
To recast the current field value to the required type, you can use the entire as...
group of rapid properties, which contain a value in a particular data type. Among
these properties, Asstring is the most widely used — for example, it can be used
for displaying numeric values of fields in data controls:
Editl.Text := Tablel.Fields[0].AsString;
It is advisable to use properties from the As... group while handling persistent field ob-
jects, since implicit setting of the type by the Value property may result in invalid con-
version of variant type data.
If the property
property CanModify: Boolean;
is set to False, the values of a field cannot be edited. This property, however, is
just a means of establishing whether a field supports editing. The property
property ReadOnly: Boolean;
enables you to forbid editing (Readonly := True) or allow it (ReadOnly := False).
A large group of properties is responsible for representing and formatting field val-
ues.
The property
property DisplayText: String;
contains the values of the field in string format before editing.
The property
property Text: String;
is used by data controls during editing. Therefore, these two properties can have
different values if the string value of the field in string format differs during editing
and browsing. With descendant classes of the tField class, this effect can be
achieved by simply setting the corresponding templates for displaying field data
(the DisplayFormat property) and editing field data (the EditFormat property).194 __ Part Il: Data Access Technologies
_>
For example, a real number can have separators for thousands during browsing and
not have them during editing. The above properties will then look as follows:
DisplayText = '1 452.32"
Text = '1452.32"
The text and DisplayText properties determine the operation of the onGetText
event handler. If the DisplayText parameter has the True value, then the Text
parameter contains the value of the DisplayText property; otherwise, this event
handler is passed the value of the field in string format.
If no value has been assigned to a field, then you can specify a certain default
value which will be displayed when the field is empty with the DefaultExpression
property. If a default value contains any characters besides numbers, then the en-
tire expression must be enclosed in single quotation markes.
If an exception arises while you are working with a field, a message that uses the
value of the pisplayName property as the name of the field is generated. If the
DisplayLabel property has been set, then DisplayName automatically assumes the
same value; otherwise you need to use the FieldName property to specify the value
for the DisplayName property. There is no other way to set the DisplayName property.
The property
property DisplayWidth: Integer;
defines the number of characters for displaying a field value in the data controls.
The property
property Visible: Boolean;
determines if a field is visible in the data controls. Components that display one
field no longer show its value, and rpBcrid components no longer show the col-
umns related to this field.
Several more TField class properties and event handlers will be covered later in this
chapter.Chapter 5: The Architecture of Database Applications 195
>_>
Types of Fields
We will now examine the classification of dataset fields according to their func-
tionality. The most widely used fields are data fields based on real database table
fields. The properties of these field objects are set in conformity with the parame-
ters of database table fields.
Additionally, in practice, programming often involves using lookup fields and cal-
culated fields.
The techniques for creating all types of dataset fields are practically the same (see
above). Nevertheless, this diversity allows developers to create extremely complex
database application programming tasks.
In this section, we will only examine lookup and calculated fields, as data fields do
not present any particular problems for users.
From the point of view of a dataset, there is no crucial difference between these
two types of fields. However, values for all lookup fields are calculated before the
same operation is performed for calculated fields. Therefore, you can use lookup
fields in expressions for calculated fields, but cannot do the reverse.
Lookup Fields
To create a new lookup field for the original dataset, you need to use the following
properties.
The property
property LookupDataSet: TDataSet;
specifies the reference dataset.
The property
property LookupResultField: String;
indicates the string field from the LookupDataSet reference dataset, the data from
which will be shown in the newly created field.
The property
property LookupKeyFields: String;
specifies the field (or fields) from the LookupDataset reference dataset, the value of
which is used to select the value in the LookupResultField.196 __ Part Il: Data Access Technologies
The property:
property KeyFields: String;
sets the field (or fields) in the original dataset for which the lookup field is
created,
Lookup fields are very convenient in the Tpzcria component. If you create such a field
for a column of this component, the appropriate lookup list will be automatically filled.
The elements of this list are stored in the PickList property supplied for every column.
Now, in order to obtain the list of all possible values that can replace the current one,
you just need to select the required column and click the button that appears in the
current cell. The value of the foreign key field (the KeyFields property) of the original
dataset is replaced by the value of the current LookupResultField field.
You do not necessarily need to open the lookup reference dataset if you use lookup
fields in a TDBGrid component. Howerer, the LookupCache property must have
the False value.
You can identify a lookup field using the Boolean Lookup property of the base
TField class, which assumes the true value for such fields.
The property
property LookupCache: Boolean;
sets the operating mode for the lookup cache. If its value is true, the lookup cache
is activated.
The performance of this lookup cache is founded on the property
property LookupList: TLookupList;
As soon as the original dataset is open, each lookup field is assigned its value; si-
multaneously, the lookup cache is filled with the appropriate values that corre-
spond to the values of all key fields of the original dataset. From this point on,
while you are moving to the next record, the lookup value is taken from the
lookup cache rather than from the reference dataset itself. If the lookup values are
of small size, this technique allows you to speed up work with the original dataset,
particularly in remote access mode under low-speed network conditions.
During changes in the lookup dataset, use the method
procedure RefreshLookupList;
which updates the current field value in the cache.Chapter 5: The Architecture of Database Applications 197
>_>
For developers’ convenience, the base tried class has the following property:
property Offset: Integer;
which returns the cache size in bytes.
The easiest way to create lookup fields is to use the Field Editor of the data access
component for this purpose. After you have selected the New field command from
the pop-up menu in the dialog with the same name (see Fig. 5.7) and performed
the conventional operations relevant to creating any data field, you also need to set
the values of the properties in the Lookup definition group. The controls of this
group become available once you've selected the type of the field (the Lookup ra-
dio button in the Field type group).
The Dataset list shows all the available datasets in the module, from which
you need to select the lookup dataset (the LookupDataSet property). The lookup
field (the LookupResultField property) is selected from the Result Field list.
The Lookup Keys list indicates the key field of the lookup dataset (the
LookupKeyFields property). Finally, the Key Fields list defines the key field of the
original dataset (the KeyFields property).
Calculated Fields
Calculated fields effectively facilitate the process of developing database applica-
tions, as they make it possible to obtain additional data on the basis of already ex-
isting data without changing the structure of the database table. The expressions for
obtaining values for calculated fields must be included in the oncalcFields event
handler of a dataset. Here you can use any arithmetic and Boolean operations and
expressions, any language statement, as well as the properties and methods of any
components, including SQL queries:
procedure TForml.TablelCalcFields (DataSet: TDataSet);
begin
with Tablel do
TablelCalcFieldl.Value := Fields[0].Value + Fields{1].Value;
with Query] do
begin
Params [0].AsInteger := Tablel.Fields [0] .AsInteger;
Open;
TablelCalcFieldl.AsString := Fields[0].AsString;
Close;
end;
end;198 _ Part Il: Data Access Technologies
The oncalcFields event handler executes when you open a dataset, switch to edit
mode, move a focus between data controls or grid columns, or delete a record.
Note, however, that for this event handler to execute, the AutoCalcFields prop-
erty of the dataset must always be set to True.
You need to take into account the fact that including complex calculated fields tends to
considerably slow down the performance of the dataset (particularly if you use SQL que-
ries with it). Additionally, every editing operation (including modifying field values, saving
changes, or moving to another record) results in the recalculation of the calculated
fields. By setting AutoCalcFields := False you can reduce the number of calls for
the OnCalcFields event handler.
If you choose to create a calculated field, just specify a calculated type as the se-
lected type for your new field in the New Field dialog of the Field Editor, and then
continue with the usual procedure for creating a data field.
Other calculated fields can be used in expressions for calculated fields, but they
must be defined in the oncalcrields method beforehand.
Calculated fields cannot be used when you filter data with the onFilterRecord
event handler, because this handler is called before the oncalcFields event han-
dler, and the values of calculated fields are not saved.
Internal Calculated Fields
Internal calculated fields (Fieldkind = fkInternalCalc) are a variety of calculated
fields used in client datasets (tclientDataSet components). They are distinctive in
that the values of such fields are saved in a dataset.
Internal calculated fields can also be used for filtering data by the onFilterRecord
event handler.
Aggregate Fields
Aggregate fields are designed for calculating field values of datasets by employing
various SQL aggregate functions. Functions of this kind are:
© ave — calculates the average value
© count — returns the number of non-null values
© in — returns the minimum valueChapter 5: The Architecture of Database Applications 199
>_>
Oj max — returns the maximum value
© sum — returns the sum of the values
Aggregate fields are not part of the field structure of a dataset, as aggregate func-
tions imply processing multiple records in a table to obtain a result. Consequently,
a value of an aggregate field cannot be bound to any particular field, it is associ-
ated with either all records or a number of records.
You can use aggregate fields only with the TclientDataset component or one of
its counterparts, as these components support data caching, which is essential for
performing calculations (see Chapter 10,"A Client of a Multi-Tier Distributed Appli-
cation").
Aggregate fields are not displayed with all other fields in T>Bcria components; they
make up a separate list in the Field Editor, and their Index property (mentioned
above) is always set to —1. You can represent the value of an aggregate field with a
single-field data-aware component (for instance, TDBText or TDBEdit) or by using
the properties of this particular field:
Labell.Caption := MyDataSetAGGRFIELD1.AsString;
Aggregate fields are created by selecting the New field command from the pop-up
menu of the Field Editor.
Object Fields
Along with traditional types of data (strings, integers, etc.), you can use more
complex data types while handling dataset fields. These are usually a combination
of a number of simpler types.
Delphi supports four classes of object fields: TaDTField, TArrayField, TDataSetField,
and TReferenceField. Their common ancestor is the TobjectField class.
The TADTField and TArrayField classes provide access to a set of child fields of a
particular type from their common parent field. These types of fields can be used if
the database server supports object types and the appropriate fields are present in
the dataset in use. This means that, like simple fields, object fields can be persis-
tent and dynamic.
These classes access child fields by the properties
property Fields: TFields;
which contains an indexed list of child field objects, and200 __— Part Il: Data Access Technologies
—_
property FieldValues[Index: Integer]: Variant;
which includes values of child fields.
The property
property FieldCount: Integer;
returns the number of child fields.
The tReferenceField and TDataSetField classes provide access to data in the
connected datasets.
The following property provides a pointer to the dataset in use:
property DataSet: TDataSet;
Data Types
The Delphi development environment enables developers to create applications
used in work with extremely diverse databases. This universality implies the neces-
sity of using means to ensure that the many types of data used in these databases
can be handled.
Naturally, there are many data types whose practical implementation does not dif-
fer from one platform to another. These types include strings, characters, real
numbers, integers, etc.
However, there are certain data types that are not supported by any platform. And,
finally, there are also unique types of data.
In order to meet developers’ demands, Delphi implements the following technique
for handling various types of data.
A data type is uniquely associated with a particular field of a database table. Without
this field, the very concept of a data type has no practical meaning. With Delphi, the
behavior of an abstract field is contained by the Tried class, which does not have a
specific type of data. From this class, a whole family of classes for typified fields is
derived, which are specifically designed for handling individual data types.
The TField class contains the Datatype property, which is responsible for the type of
data. This property, however, cannot be modified.Chapter 5: The Architecture of Database Applications 201
_>
The full list of all available types of data is contained in the TFileldtype type:
type TFieldType = (ftUnknown, ftString, ftSmallint, ftInteger, ftWord,
ftBoolean, ftFloat, ftCurrency, ftBCD, ftDate, ftTime, ftDateTime,
ftBytes, ftVarBytes, ftAutoInc, ftBlob, ftMemo, ftGraphic, ftFmtMemo,
ftParadoxOle, ftDBaseOle, ftTypedBinary, ftCursor, ftFixedChar,
ftWideString, ftLargeInt, £tADT, ftArray, ftReference, ftDataSet,
ftOraBlob, ftOraClob, ftVariant, ftInterface, ftIDispatch, ftGuid,
ftTimeStamp, £tFMTBcd) ;
Delphi doesn't provide direct support for the BCD data type. This type is implemented
through the ftCurrency type. This is why BCD's precision is limited to 20 decimal
places. However, this restriction can be overcome by using the FMTBed type, which en-
ables the required accuracy.
Along with traditional types of data, Delphi uses specialized types, thus further
enhancing the functionality of applications. Specifically, the £tiInterface,
ftIDispatch, and ftGuid types allow you to build full-fledged database applica-
tions for COM and OLE DB.
Almost all database servers provide users with the ability to create their own cus-
tom data types. With Delphi, you can do this by employing an abstract data type
and the TapTField class. An abstract data type can include any scalar type of data
(numbers, dates, references, arrays, datasets).
An autoincremental type of data, ftautotnc, has long been used by various data-
base management systems. A field of the autoincremental type automatically in-
creases its value by one for every new record. This means that every record has a
unique identifier that is often used as a primary key.
The Binary Large Object (BLOB) data type £tBlob — is a binary array of arbitrary
size. A BLOB field itself contains only a pointer to an individual database file that
stores the binary array. Therefore, BLOB fields are essentially universal carriers for
any data that has a scalar or non-scalar structure and that can be converted to
binary representation.
The Memo type ftMemo represents a sets strings of random length (a formatted
Memo is a variety of this type), based on the BLOB format. It is used for saving
text from a TMemo component or word editor.202 __— Part Il: Data Access Technologies
—_>
The graphic data type is utilized for storing images, and based on the BLOB for-
mat. A TGraphicField field directly interacts with a data-aware control (for exam-
ple, TDBImage). Images must be saved in BMP format.
The data types ParadoxOle and dBaseOle were specially developed to facilitate
handling OLE data in the Paradox and dBASE database management systems.
In Delphi, both these types are also based on the BLOB format.
The CLOB and BLOB data types are specially designed for work with Oracle 8
servers.
The ftarray type is used for organizing an array from data of any structure, except
for arrays of the same kind. Each element in this array can be supplied with
a TField object of its own. This mechanism is managed through the sparseArrays
property of the TDataset class.
Another special data type, ftDataset, as well as the TDataSetField class, lets you
use any arbitrary dataset as a field in the context of another dataset. This pattern
also allows you to manage every single field in the integrated dataset.
The data type ftReference also uses external datasets, but it only enables you to
attach and use individual fields.
Summary
A database application can access data sources using a wide range of data access
technologies, many of which are also used in Delphi applications. However, every
Delphi database application has a standard core with a structure that is determined
by the database application architecture.
Data access technologies are based on the same set of core components and devel-
opment methods. This has made it possible to unify the process of developing da-
tabase applications.
The following three components form the basis of the development process:
OG Non-visual data access components
G Non-visual tpatasource components
G Visual data-display componentshapter 6204 ___‘ Part Il: Data Access Technologies
>>
various data access strategies used by Delphi applications is the issue of
delivering complete applications to end users. BDE needs to be installed
separately and takes up about 15 MB of disk space; additionally, this technology
requires special alias settings. Though ADO comes with the operating system as
part of the package, it still requires customized data providers (see Chapter 7, "Us-
ing ADO in Delphi"). Furthermore, when you are eventually forced to update your
ADO version, your application will “put on" an extra 2 MB at the very least.
O ne of the major problems that poses a constant challenge to developers of
Now, however, all these difficulties and shortcomings (which considerably compli-
cate the task of building robust applications) have been solved by the introduction
of a new, easy-to-use but powerful mechanism for accessing data — dbExpress.
The dbExpress technology consists of a collection of drivers and components that
encapsulate connections, transactions, queries and datasets, and interfaces that
provide universal access to dbExpress functions.
dbExpress components are stored in the dbExpress tab of the Component palette.
The dbExpress strategy enables interaction between an application and database
servers through a set of specialized drivers. dbExpress drivers use exclusively SQL
queries for retrieving data. As there is no data caching on the client side in this
schema, the technique involves using only forward-only cursors and prohibits di-
rect editing of datasets.
dbExpress solves the problem of editing data in a number of ways (which will be dis-
cussed later). However, all of these mechanisms are costly in terms of programming
efforts, and also tend to impair code performance
dbExpress components require only one driver to function, which directly interacts
with the client software appropriate to the selected database server. The following
four database drivers are shipped as part of the distribution kit:
oO DB2 © InterBase
O MySQL O Oracle
These drivers are implemented as dynamic libraries and, if need be, can easily be
compiled directly into an executable file of your application. This means that
dbExpress successfully solves the problem of distributing means of data accessChapter 6: dbExpress Technology 205
—_
along with your applications. Of course, this doesn't do away with the necessity of
installing the client software appropriate to the SQL server on your system.
In addition, owing to the fact that this technology is used in identical ways both in
Delphi and in Kylix, it provides Windows and Linux applications with reliable ac-
cess to data on all major computing platforms.
Thus it is obvious that the dbExpress data access strategy is the best choice for appli-
cations that require a fast and easy mechanism for browsing data on SQL servers. On
the other hand, you will find dbExpress inadequate for sophisticated client-server or
multilink applications that involve handling data in a more complicated way.
This chapter covers the following issues:
O Techniques of configuring connections with diverse database servers, installing
drivers and specifying their settings
© Mechanisms of using dbExpress components for browsing data and the process
of creating a user interface for your applications
© Methods of programmatic data editing
© Approaches to handling data in update caching mode and using the
TSQLClientDataSet component
© Utilizing interfaces
OC Principles of distributing applications with the integrated dbExpress technology
Accessing dbExpress Data
Access to data in the dbExpress framework is organized through the use of three
constituent parts:
G Client software that is relevant to the database server which you mean to access
GA dbExpress driver
G dbExpress components that are contained in an application which uses the
database server
Properly installed and configured client software for the database server is a pri-
mary prerequisite for accessing data. Installation processes for each of the four
servers that are supported by dbExpress vary slightly, but these technicalities are
beyond the scope of this book.206 __— Part Il: Data Access Technologies
>_>
\
J
Database server
os
dbExpress driver ]
Client software
TSQLConnection component
dbExpress DataSet components
TDataSource components
DataControls components
dbExpress application
Fig. 6.1. A schematic diagram that illustrates the mechanism for accessing
data used within the framework of dbExpress technology
In order to implement interaction with the client software, the dbExpress technol-
ogy uses special drivers that enable the passing of queries from applications to
servers.
An application must contain at least one component that provides a connection
to a server, as well as the required number of components to encapsulate the da-
taset. In all other aspects the application is similar to a traditional database appli-
cation.
All necessary components can be found in the dbExpress tab of the Component
palette.
Data Access Drivers
The dbExpress technology enables customers to access a database server by using
a driver implemented in the form of a dynamic library. Every server has its own
dynamic library.Chapter 6: dbExpress Technology 207
—_>
The files listed in Table 6.1 are stored in the ..\Delphi6\Bin directory.
Table 6.1. dbExpress Drivers
Database Server Driver it Software
DB2 Dbexpdb2.dll. db2cli.dil
InterBase Dbexpint.dll GDS32.DLL
MySQL Dbexpmy.dil LIBMYSQL.DLL
Oracle Dbexpora.dil OCI.DLL
In order to provide access to data on a server, a driver must first be set up on the
client PC. This driver interacts with the server's client software, which also must be
installed on the client side.
Standard settings for each driver are stored in the ..\Program Files\
Common Files\Borland Shared\DBExpress\dbxdrivers.ini file.
Connecting to a Database Server
The data dbExpress access strategy enables you to establish a connection with
a server by using a TsQLConnection component. This component is an absolutely
indispensable link: all other components are interconnected with it and utilize
it for retrieving data from databases.
As soon as you have included this component in a data unit or form, you need to
select the desired server type and specify the connection settings.
The following property
property ConnectionName: string;
enables you to select any already-set connection from a drop-down list. By default,
the developer is permitted one set connection to each database server. Once the
desired connection is selected, relevant values are automatically assigned to the
following properties (see Table 6.1).
The property
property DriverName: string;
indicates the driver that is being used.208 __ Part Il: Data Access Technologies
>_>
The property
property LibraryName: strings
defines the dynamic library of the dbExpress driver.
The property
property VendorLib: strings
indicates the dynamic library of the client-side software for the server.
And, finally, the property
property Params: Tstrings;
encapsulates parameters for the connection and contains a list of the settings for
the selected connection.
If necessary, you can set all the above properties manually.
The developer is free to extend and modify the list of the connections that have
been set by using the special dbExpress Connections edit window (Fig. 6.2). This
edit window is opened as soon as you double-click the TsgLconnect ion component
or select the Edit Connection Properties command from the component's pop-up
menu.
Connection Settings
Key
BlobSize 4
CommitRetain Falee
Database shared\date\mactsal odb
MSConnection DiiverNtame Interbase
Oracle EnoResouceFile
LoceleCode 000
Password masterkey
FioleName FioleName
ServerCharSet
[SOLDislect 1
Interbase Translsolatio ReadCommited aA
Cancel Help
Fig. 6.2. The edit window that shows the connections set
for the TSQLConnect ion componentChapter 6: dbExpress Technology 209
—_>
The list on the left shows all the existing connections, while the list on the right
includes the current settings for the selected connection. Using the buttons of the
toolbar, you can create, rename, and remove connections. Additionally, you are
able to modify existing settings.
The items listed in the left part of the window correspond to the items included in
the connectionName property list in the Object Inspector. The settings in the right
part of the window are contained in the Params property.
The names of the configured connections and the settings that have been specified
for them are saved in the ..\Program Files\Common Files\Borland Shared\
DBExpress\dbxconnections.ini file.
The particular settings of any connection depend on the database server used and
on the application's requirements (Table 6.2).
Table 6.2. Setting a dbExpress Connection
Parameter Value
General Settings
BlobSize Sets the limit for the size of a BLOB package.
DriverName The name of the driver.
ErrorResourceFile The file that stores error messages.
LocaleCode The locale code that specifies how a particular national
character set affects the data sorting schema.
User_Name The name of the user.
Password The password.
DB2
Database The name of the client kernel.
DB2 Translsolation The level of isolation for transactions.
InterBase
CommitRetain Specifies the behavior of the cursor after completing the
transaction. If this argument is set to True, the cursor is
refreshed; otherwise it is deleted.
Database The name of the GDB file (a database file).
InterBase Translsolation The level of the transaction's isolation.
continues210 __ Part Il: Data Access Technologies
—_
Table 6.2 Continued
Parameter Value
RoleName The role of the user.
ServerCharSet The server character set.
$QLDialect The SQL dialect used. InterBase 5 supports only one
value — 1.
WaitOnLocks The permission to wait for resources that are currently in
use.
MySQL
Database The name of the database.
HostName The name of the computer on which the server runs
MySQL.
Oracle
AutoCommit The flag that specifies the end of the transaction. It can be
installed by the server only.
BlockingMode Specifies the mode for completing processing queries. If
this argument is set to True, the connection waits for the
current query to be completed before beginning the exe-
cution of the next one (synchronous mode); otherwise, it
starts processing the next query immediately (asynchro-
nous mode).
Database The database record in the TNSNames.ora file.
Oracle Translsolation
The level of transactions’ isolation.
As soon as you have selected the connection (or the type of server and your own
settings for the connection parameters), your TSQLConnection component is ready
for work.
The property
property Connected: Boolean;
opens a connection to the server if it is set to the True value. The same effect is
achieved by using the following method:
procedure Open;
Once the connection is opened, all the dbExpress components that encapsulate
datasets and are linked to the open TsQLConnection component get access to the
database.Chapter 6: dbExpress Technology 211
A connection is closed either by using the same property, where connected = False,
or by using the method
procedure Close;
During the process of opening and closing an application, the developer can use
event handlers, called before and after opening and closing the connection:
property BeforeConnect: TNotifyEvent;
property BeforeDisconnect: TNotifyEvent;
property AfterConnect: TNotifyEvent;
property AfterDisconnect: TNotifyEvent;
For instance, you can organize an authentication procedure for application users
from the client side:
procedure TForml .MyConnectionBeforeConnect (Sender: TObject) ;
begin
if MyConnection. Params.Values['User_Name']) <> DefaultUser then
begin
MessageDlg("Wrong user name', mtError, [mbOK], 0);
Abort;
end;
end;
The property
property LoginPrompt: Boolean;
defines whether the dialog for user authorization should be displayed before open-
ing the connection. In the property is set to True, the dialog is displayed.
The value of the following property indicates the current state of your application:
TConnectionState = (csStateClosed, csStateOpen, csStateConnecting,
csStateExecuting, csStateFetching, csStateDisconnecting) ;
property ConnectionState: TConnectionState;
You can specify the settings for your connection in the design stage by using the
Object Inspector or the Connection Editor (see Fig. 6.1). Alternatively, you can do
this immediately before opening the connection either by using the Params prop-
erty, or with the following method:
procedure LoadParamsFromIniFile( AFileName : String = '' );
which loads the parameters that have been loaded in advance from the INI file.
The property below enables you to check if the operation was a success:
property ParamsLoaded: Boolean;212 ‘Part Il: Data Access Technologies
—_
where the True value notifies you about a successful loading.
procedure TForml.StartBtnClick (Sender: TObject) ;
begin
if MyConnection. Params.Values['DriverName'] = '' then
MyConnect ion. LoadParamsFromIniFile('c:\Temp\dbxalarmconnections. ini");
if MyConnection.ParamsLoaded then
try
MyConnection.Open;
except
MessageD1g("Database connection error‘, mtError, [mbOK], 0);
end;
end;
Managing Datasets
An additional Tsguconnection component enables you to perform a number of
operations on connected datasets and control their status. Let's take a closer look
at them.
property DataSetCount: Integer;
returns the number of connected datasets.
But these include only open datasets that have been passed to linked components.
The total number of queries being executed at any given time can be obtained by
using the following property:
property ActiveStatements: LongWord;
If the database server has specified a maximum number of concurrent queries that
can be processed in the connection, you can learn this upper limit using the prop-
erty
property MaxStmtsPerConn: LongWord;
This is why, just to be on the safe side, you can use the following piece of code,
which will further ensure that your application is rannung smoothly before opening
a connection to a dataset:
if MyQuery.sQLConnection.ActiveStatements <=
MyQuery. SQLConnect.ion.MaxStmtsPerConn
then MyQuery.Open
else MessageDlg('Database connection is busy', mtWarning, [mbOK], 0);Chapter 6: dbExpress Technology 213
In case of emergency, all datasets that have been opened through the connection
can be quickly closed by using the method
procedure CloseDataSets;
without closing the connection.
If necessary, the TSgLConnection component can process SQL queries on its own,
without resorting to the TsQLQuery or TSQLDataSet component. The function given
below is designed for this purpose:
function Execute(const SQL: string; Params: TParams;
ResultSet:Pointer=nil): Integer;
If you are dealing with a parameterized query, you first need to create a TParams
object and enter all the required details in it. As the TParams object exists in its
own right and is not yet linked to any query, you should be especially careful and
ensure that the parameters are listed in the same order in TParams and in the SQL
statement.
If the query returns a result, the method in question automatically creates a
TCustomSQLDataSet object and returns a pointer to it in the Resultset parameter.
The function gives you the number of records that have been processed by your
query. The code fragment below illustrates this using the Execute function.
procedure TForml.SendBtnClick (Sender: TObject);
var FParams: TParams;
FDataSet: TSQLDataset;
begin
FParams TParams.Create;
try
FParams.Items [0] .AsInteger := 1234;
FParams.Items(1].AsInteger := 6751;
MyConnection.Execute('SELECT * FROM Orders WHERE OrderNo >=
:Ord AND EmpNo = :Emp', FParams, FDataSet);
if Assigned(FDataSet) then
with FDataSet do
begin
Open;
while Not EOF do
begin
{eee}
Next;
end;214 ‘Part Il: Data Access Technologies
>_>
Close;
end;
finally
FParams. Free;
end;
end;
If the query has no parameters that must be specified prior to running it and does
not return a dataset, you can use the following function:
function ExecuteDirect (const SQL: string ): LongWord;
which returns either zero — if the query has been successfully completed, or an
error code.
The method:
procedure GetTableNames (List: TStrings; SystemTables: Boolean = False);
returns a list of database tables. The systemTables parameter enables you to in-
clude system tables in the list that is being constructed.
The GetTableNames method also handles such properties as
TTableScope = (tsSynonym, tsSysTable, tsTable, tsView);
TTableScopes = set of TTableScope;
property TableScope: TTableScopes;
which allow you to specify the type of the tables whose names are incorporated
into the list.
You can obtain a list of the fields contained in each table by using the following
method:
procedure GetFieldNames (const TableName: String; List: TStrings);
and a list of indexes by using the method
procedure GetIndexNames (const TableName: string; List: TStrings);
Both the above methods return the resulting list in their List parameter.
In a similar fashion, the method
procedure GetProcedureNames (List: TStrings) ;
returns a list of the available stored procedures, whereas the method
procedure GetProcedureParams (ProcedureName: String; List: TList);
determines the parameters for a specific procedure.Chapter 6: dbExpress Technology 215
Transactions
Like its counterparts in BDE and ADO, the TsgLconnection component supports
transactions and implements them in much the same way.
The methods listed below are responsible for beginning, committing, and rolling
back a transaction:
procedure StartTransaction(TransDesc: TTransactionDesc) ;
procedure Commit (TransDesc: TTransactionDesc) ;
procedure Rollback (TransDesc: TTransactionDesc) ;
while the parameters of the transaction are returned by the TtransactionDesc rec-
ord:
TTransIsolationLevel = (xilDIRTYREAD, xilREADCOMMITTED,
xilREPEATABLEREAD, xilCUSTOM) ;
T?ransactionDesc = packed record
TransactionID —: LongWord;
GlobalID : LongWord;
IsolationLevel : TTransIsolationLevel;
CustomIsolation : LongWord;
end;
This record contains characteristics such as Transact ionID, which is unique within
the framework of the connection, as well as IsolationLevel. If the isolation level
is set to xilcusTom, the CustomIsolation parameter must be specified. GlobalIp is
used when you work with an Oracle server.
Some database servers don't support transactions, and this feature is defined by
using the following property:
property TransactionsSupported: LongBool;
If the connection is already being used for conducting another transaction, the
property
property InTransaction: Boolean;
is set to the True value. Thus, the server does not support multiple transactions on
a single connection, it is worth making sure that the connection in question is not
currently being used by another transaction:
var TransInfo: TTransactionDesc;
{ase}
if Not MyConnection.InTransaction then216 __ Part Il: Data Access Technologies
—_
try
MyConnection. StartTransaction (TransiInfo) ;
{..+}
MyConnection.Commit (TransInfo) ;
except
MyConnection.Rollback (TransInfo) ;
end;
Using Dataset Components
The collection of dbExpress components that encapsulate datasets is typical,
and comparable to analogous components used in BDE, ADO, and InterBase Ex-
press. The dbExpress collection includes such components as TSQLDataSet,
TsQLTable, and TsQLQuery, TSQLStoredProc.
Although the TSoLCLientDataset component also belongs to this group, it will be dis-
cussed separately, as it uses a number of specific functions.
However, the necessity of developing an easy-to-use strategy for accessing data —
which is what the dbExpress technology is — has imposed certain restrictions on
these components.
Although all the components discussed in this section descend from the same
TDataSet ancestor class, which provides a complete toolkit for handling datasets,
dbExpress components use forward-only cursors exclusively and do not support
data editing. Forward-only (or non-scrollable) cursors only allow you to navigate
through a dataset by moving to the record following your current position, or to
the beginning of the dataset. These cursors do not support operations that involve
buffering data — i.e., searching, filtering, and synchronous browsing.
As a result, another intermediate class — TcustomsQLDataset — has been intro-
duced with the purpose of disabling a number of the mechanisms of the TDataset
class.
Certain limitations are also imposed on data display when using the components
from the Data Controls page. You cannot utilize components such as TDBGridChapter 6: dbExpress Technology 217
—_
and TpBctricria at all, and you must not forget to disable the buttons that
allow you to move to the previous record and the last record in TDBNavigator.
Attempting to use the components for synchronous browsing is also of no use.
The rest of the components on this page can be employed in the regular way.
All the components under consideration implement access to data using the same
pattern — through a connection that is encapsulated by the TsgLconnection com-
ponent. The following property is responsible for linking components to the con-
nection:
property SQLConnection: TSQLConnection;
Let us now discuss the dbExpress components in more depth.
The TCustomSQLDataSet Class
Owing to the fact that all dbExpress components ultimately descend from the
TDataset class, the designation of the TcustomsQLDataset class is to correctly limit
the capabilities which are inherent to TDataset, rather than to add a new function-
ality to already existing ones. Although this class is not used directly in applica-
tions, you may find information about it helpful in order to gain a better under-
standing of the behavior of other dbExpress components and to create your own
custom components from it.
The TcustomsoLDataSet class is a common ancestor for those components that
encapsulate queries, tables, and stored procedures. The properties given below pro-
vide support for all these components.
TsQLCommandType = (ctQuery, ctTable, ctStoredProc) ;
property CommandType: TSQLCommandType;
specifies the type of a command which is passed to the server;
property CommandText: string;
contains the text of the command.
If an SQL query is passed to the server (CommandType = ctQuery), the CommandText
property contains the textual content of this query. If a command for retrieving
a table is issued to the server, the CommandText property contains the name of the
required table, which enables this property to automatically create an SQL query
to get all the fields of the table using this table name. If a procedure must be per-
formed, the CommandText property contains the name of this procedure.218 Part Il: Data Access Technologies
—_
The text of a command that is actually passed to the server for execution is con-
tained in the following protected property:
property NativeConmand: string;
The property below is utilized specifically for handling tabular datasets:
property SortFieldNames: string;
and for specifying the sorting order for the records in a tabular dataset. This prop-
erty must contain a list of fields delimited by semicolons. The property in question
is used for building an orDER By expression for the command being generated.
In order to handle exceptions that occur in descendant classes, the following pro-
tected property can be used:
property LastError: string;
This property returns the text of the most recent dbExpress error.
You can speed up the process of executing a user's request for data by disabling the
function that retrieves the metadata that pertain to a specific queried object (a ta-
ble, procedure, fields, or indexes), which, as a rule, are passed to a client along
with the queried data. If you choose to do this, assign the True value to the fol-
lowing property:
property NoMetadata: Boolean;
However, avoid overusing this method, as there is a certain category of commands
that require metadata (for example, operations with indexes).
The developer can control the process of retrieving metadata. This can be done by
using the following method:
TSchemaType = (stNoSchema, stTables, stSysTables, stProcedures,
stColums, stProcedureParams, stIndexes );
TSchemaInfo = record
Frype : TSchemaType;
ObjectName : String;
Pattern : String;
end;
which is available from the protected property with the syntax
property SchemaInfo: TsQLSchemaInfo;
which in turn means that it can be used only when you derive new components
from TcustomsgLDataset.Chapter 6: dbExpress Technology 219
—_
The Frype parameter indicates the type of required data. The objectName parame-
ter specifies the name of a table or stored procedure if the rrype parameter con-
tains fields, indexes, or parameters of procedures.
If you want a component to get the resulting dataset, the FType parameter must always
be set to the stNoSchema value. This condition is fulfilled automatically once the value
of the CommandText property is changed.
The Pattern parameter indicates the type of restrictions imposed on the metadata.
It contains a character mask that is similar to the property of many visual compo-
nents. A sequence of characters in a mask is defined by the s character, while
a single character is specified by the _ character.
If you need to use control characters for masking, utilize the corresponding double
characters — 33 and __.
Like the Tag property of the Tcomponent class, the TcustomsQLDataSet class pos-
sesses a string property
property DesignerData: string;
which can be used by a developer for storing various information. Essentially, it is
just an extra string property that doesn't really need to be declared.
The TSQLDataSet Component
The TsgQLDataSet component is universal, and allows you to execute SQL queries
(like TsQLQuery), browse through entire database tables (like TsoLTable), and exe-
cute stored procedures (in the same manner as TSQLStoredProc).
The CcommandType property of this component sets the mode that it works in.
If this property is set to ctTable, you will be able to select the name of the table
from the commandText property's list — if, of course, the component is attached to
a connection. Selecting the ctquery value from the CommandText property requires
that you define the text of an SQL statement. In order to work in stored procedure
mode, assign the ctStoredProc value to the CommandType property, and you will
then be able to select the desired procedure from the commanaText list.220 __ Part Il: Data Access Technologies
>_>
Traditional techniques are used to open and close a dataset: the Active property
and the open method. If an SQL query or a stored procedure doesn't return a da-
taset, the method below can be invoked to execute them:
function ExecSQL(ExecDirect: Boolean = False): Integer; override;
The ExecDirect parameter determines whether or not any parameters must be set
prior to executing a command. If a query or procedure does have parameters, the
False value must be assigned to the ExecDirect parameter.
In addition, you can use an extra property — sortFieldNames (see earlier in this
chapter), which specifies the filtering order for the records in a table.
The Params or ParamCheck property is used to specify parameters in query or
stored procedure mode.
Details on the indexes used in the resulting dataset are stored in the following
property:
property IndexDefs: TIndexDefs;
For more information on TIndexDefs, refer to Chapter 5, "The Architecture of
Database Applications."
The TSQLTable Component
The TsoLTable component is designed for browsing through entire tables, and
its functionality is analogous to components such as TTable, TADOTable, and
TIBTable. Chapter 5 covers the issue of working with table components in more depth.
Use the property below in order to set a name for your table:
property TableName: string;
If the component is attached to the connection, you can select the name of a table
from the list. As soon as the name for the table is chosen and the connection is
properly configured, your component is ready for work. Once you have activated it
(by using the traditional active property or the open method), the dataset from the
selected table will be passed to this component.
In order to obtain a tabular dataset, the TsoLTable component queries the ser-
ver by utilizing the capabilities that it inherited from its ancestor,
‘TCustomsQLDataset.
The method
procedure PrepareStatement; override;Chapter 6: dbExpress Technology 221
_>
generates the query text for the selected table, which the component prepares for
passing to the server.
The major advantage of all table components is their ability to work with indexes.
To activate simple or composite indexes, use the IndexFieldNames, IndexFields,
and IndexName properties. And the method
procedure GetIndexNames (List: Tstrings);
returns a list of the indexes being used to the List parameter.
The master-slave link between datasets is implemented using the MasterFields and
MasterSource properties.
Additionally, the TsoLTable component provides a developer with a sort of editing
tool. The following method is used for clearing all the records from a database ta-
ble linked to the component:
procedure DeleteRecords;
The TSQLQuery Component
The TsQLouery component generates client SQL queries to be executed on the
server. Its capabilities are standard for all components of this type (see Chapter 5,
"The Architecture of Database Applications").
The connection to the server is configured using the following property:
property SQLConnection: TsQLConnection;
in which the TsQLConnection component is selected.
The content of a query is contained in the property below:
property SQL: TStrings;
And its string representation is stored in the following property:
property Text: string;
The text of queries can be edited by utilizing a standard editor, which is activated
when you click the property button in the Object Inspector window.
The parameters of a query must be contained in the indexed property with the
following syntax:
property Params: TParams;222 ‘Part Il: Data Access Technologies
In order to prepare the parameters and the query as a whole, use the property
property Prepared: Boolean;
If this property is set to True, resources for the query are allocated on the server,
which significantly accelerates its performance:
SQLQuery. Prepared := False;
soLQuery.ParamByName ('SomeParaml') .AsString := 'SomeValue';
sQLQuery. ParamByName ('SomeParam2") .Clear;
sQLQuery. Prepared := True;
The following property
property ParamCheck: Boolean;
defines whether or not the parameter list of the query will be updated if the query
text has been modified. As an example, consider this scenario: a query is used for
creating stored procedures, and the source text of the procedures in question is
passed to the server in this query. The body of the stored procedure may contain
its own parameters which, while the query is being processed, may be interpreted
as the parameters of the query itself. In this case, parameters are likely to be cre-
ated in error. It is situations like these that justify using the Pacamcheck property:
sQLQuery.ParamCheck := False;
sQLQuery. SQL. Clear;
sQLQuery. SQL.Add ('"SomeSQLText ForNewStoredProc') 7
SQLQuery.ExecSQL();
If your query returns a dataset, it is executed using either the Active property or
the open method. Otherwise, use the method
function ExecSQL(ExecDirect: Boolean = False): Integer; override;
The ExecDirect = False parameter means that the query doesn't have parameters
that should be specified.
The TSQLStoredProc Component
The tTsgLstoredProc component encapsulates the functionalities of stored proce-
dures in order to execute them in the framework of the dbExpress technology.Chapter 6: dbExpress Technology 223
_
All the functions of this component are standard. For more details on the subject
of component functions of stored procedures, refer to Chapter 5.
Configure the connection to the server by using the following property:
property SQLConnection: TSQLConnection;
where the TsQLConnection component is selected.
The name of a stored procedure is specified by the following property:
property StoredProcName: string;
If the connection is already properly configured, the name of a stored procedure
can be selected from the corresponding drop-down list in the Object Inspector.
In order to work with input/output parameters, use the property
property Params: TParams;
When you work with parameters, it is advisable to call an individual parameter by name using
the ParamByName method. This is because when you work with several servers simultaneously,
the order of parameters before and after executing the procedure may be different.
The property
property ParamCheck: Boolean;
specifies whether the list of parameters will be modified if the stored procedure is
changed. To enable this kind of change, this property must be set to True.
A procedure is performed by using the method below
function ExecProc: Integer; virtual;
if it doesn't return a dataset. Otherwise, either the Active property or the open
method should be utilized.
If a stored procedure returns several linked datasets (as which is a case with hierar-
chical ADO queries), the next dataset can be accessed by using the method
function NextRecordSet: TCustomSQLDataSet;
which automatically creates an object of the TcustomsQLDataset type for encapsu-
lating new data. You can return to the previous dataset if you have specified the
object variables for each dataset:
var SecondSet: TCustomSQLDataSet;224 ‘Part Il: Data Access Technologies
>_>
MyProc. Open;
while Not MyProc.Eof do
begin
{.
Ne:
end;
SecondSet := MyProc.NextRecordSet;
SecondSet . Open;
{eo}
SecondSet .Close;
MyProc.Close;
The TSQLClientDataSet Component
The TsQLClientDataSet component provides client-side caching of returned data,
and updates and subsequently passes them to the server so they can be put in
place. Unlike the TclientDataset component, which is designed primarily for
servicing a dataset that has been returned from a remote server using DataSnap
server components, the TSQLClientDataSet component is intended simply as an
editing tool in the dbExpress framework.
}
Unlike other dbExpress components, the TsQLClientDataSet component uses a bi-
directional cursor (i.e., a cursor that allows backward scrolling) and enables you to
edit data, although only when working in caching mode. In other words, a dataset
is buffered locally in the component, and all the current updates are saved in it.
If you need to save changes on the server, use the special method that passes up-
dates to the server.
Thus, in a way the TSQLClientDataSet component makes up for major deficiencies
of dbExpress.
Connecting to a Database Server
In order to connect to a data source, use the following property:
property DBConnection: TsQLConnection;
which enables you to link the data source to the TsoLConnection connection (see
earlier in this chapter). As an alternative, you can utilize the property
property ConnectionName: string;
which allows you to directly select the type of dbExpress connection.Chapter 6: dbExpress Technology 225
_
However, this component lacks a mechanism for creating a remote connection,
which is provided by the TclientDataset component in its RemoteServer and
ProvidexName properties (for more information on the subject, see Chapter 10,
"A Client of a Multi-Tier Distributed Application").
As soon as the connection to a database server is established, you can specify the
type of command in use, somewhat like you do for the TsQLDataSet component.
The type of command is defined by the following property:
TsQLCommandType = (ctQuery, ctTable, ctStoredProc);
property CommandType: TSQLCommandType;
The contents of this command are specified by using the property
property CommandText: string;
After that, a component can be linked to the components that are responsible for
displaying data in order to browse and edit the data.
Saving Updates
In order to pass updates that have been cached locally to the server, use the fol-
lowing method:
function ApplyUpdates (MaxErrors: Integer); Integer; virtual;
where the MaxErrors parameter specifies the maximum permissible number of er-
rors that may occur when the updates are being saved. If the actual number of er-
rors exceeds the MaxErrors value, the process of saving is terminated. As a rule,
this parameter is set to -1, which lifts the restriction on the number of errors.
The method with the following syntax:
function Reconcile (const Results: OleVariant): Boolean;
deletes updates that have been successfully saved on the server from the local
cache of the component. Generally, you don't have to use this method, since it is
called by the ApplyUpdates method.
Before and after saving the updates that you have made to the server, the following
event handlers are called:
type
‘TRemoteEvent = procedure (Sender: Tobject; var OwnerData: OleVariant)
of object;226 __ Part Il: Data Access Technologies
>_>
property BeforeApplyUpdates: TRemoteEvent;
property AfterApplyUpdates: TRemoteEvent;
‘You can cancel local updates using the method
procedure CancelUpdates;
Note that the component uses such traditional dataset methods as Edit, Post,
Cancel, Apply, Insert, and Delete. However, these methods are applicable only to
records that have been cached locally. You are free to make changes to a dataset
using these methods, but all these changes will affect only the content of the
cache. Only the applyUpdates method actually updates the data on the server.
The data exchange between a server and the TsoLClientDataset component is
conducted in the form of packets. You can get access to the required packet using
the following property:
property Data: OleVariant;
The changes that you have made are contained in the property with the following
syntax:
property Delta: OleVariant;
The developer can modify the size of a packet as he or she chooses. For example,
if the connection's performance is deteriorating you can reduce the size of the
packets. The size of a packet can be specified by setting the following property:
property PacketRecords: Integer;
which determines the number of records to be included in the packet. Packetizing
is done automatically if PacketRecords := -1.
If PacketRecords is set to 0, the client and client server exchange only metadata.
If the PacketRecords property has a positive value, you need to organize data
swapping from the server manually. Use the following method:
function GetNextPacket: Integer;
Before and after executing this method, the event handlers presented below are
called:
property BeforeGetRecords: TRemoteEvent;
property AfterGetRecords: TRemoteEvent;Chapter 6: dbExpress Technology 227
For instance, you can use the Afterscroll event handler in order to implement
this swapping:
procedure TDM. SQLClientDataSetAfterscroll (DataSet: TDataSet);
begin
if SQLClientDataSet .Eof
then SQLClientDataSet .GetNextPacket;
end;
Working with Records
The TsQLclientDataSet component incorporates means for working with individ-
ual records. You can learn the number of records from the following property:
property RecordCount: Integer;
The number of the current record is contained in the property
property RecNo: Integer;
The size of any particular record is saved in the property with the following syntax:
property RecordSize: Word;
All the changes made to the current record can be cancelled using the method
procedure RevertRecord;
You can refresh the values of the fields of the current record with the method
procedure RefreshRecord;
Before and after calling the RefreshRecord method, the following event handlers
are called:
property BeforeRowRequest: TRemoteEvent;
property AfterRowRequest: TRemoteEvent;
Processing Exceptions
Processing exceptions for the TsQLClientDataset component involves two stages.
First, you need to track down client-side errors — these may include invalid en-
tries, caching errors, etc. Here you can use all standard mechanisms that are
commonly applied to datasets. You can also use try ... except blocks:
try228 _ Part Il: Data Access Technologies
>_>
DM, SQLClientDataSet .Edit;
DM, SQLClientDataSet. Fields [1] .AsString
DM. SQLClientDataSet. Post;
except
on E: EDatabaseError do DM.SQLClientDataSet.Cancel;
SomeString;
end;
Second, errors can occur when you save updates on the server. And since the
event that triggered the exception takes place on another machine or in another
process, this type of errors requires a special event handler:
TReconcileErrorEvent = procedure (DataSet: TCustomClientDataset;
E: EReconcileError; UpdateKind: TUpdateKind;
var Action: TReconcileAction) of object;
property OnReconcileError: TReconcileErrorEvent;
This handler starts if an error message is passed from the server. Information on an
error is contained in the E: EReconcileError parameter. For example:
procedure TIM, SQLClientDataSetReconcilefrror (DataSet: TOustarClientDataset;
E: EReconcileError; UpdateKind: TUpdateKind;
var Action: TReconcileAction) ;
begin
if (E.ErrorCode = SomeCode) and (UpdateKind = ukModify) then
begin
MessageDlg('Server Error’, mtError, [mbOK], 0);
Action := raCorrect;
end
else
begin
Action
end;
end;
Client datasets are discussed in more depth in Chapter 10,"A Client of a Multi-Tier
Distributed Application."
raCancel;
Data Editing Methods
Despite the above-mentioned deficiencies of the dbExpress technology — forward-
only cursors and the lack of support for editing — there are ways to get around
these problems, or even solve them altogether.Chapter 6: dbExpress Technology 229
_
First, you have the TsQLclientDataset component, which enables you to imple-
ment a scrolling cursor, and allows you to edit data by caching it on the client side.
Second, editing can be done by specifying the settings and running such SQL que-
ries aS INSERT, UPDATE, and DELETE.
Both of these approaches have their advantages and drawbacks.
The TsgLclientDataset component is by all means a very effective tool. It is stan-
dardized, relatively easy-to-use, and most importantly, it hides its functionality
from the user behind several properties and methods. The problem here, however,
is that not all applications support local caching of updates.
For instance, you may experience certain difficulties with maintaining data integ-
rity and adequacy when you edit data by caching it locally in an environment
where multiple users are accessing the same data source. Consider a typical exam-
ple: at Christmas time, a salesperson in a department store reserves a number of
items for you that are currently in great demand. While you are racking your brain
over the most suitable present to buy for your old aunt, another salesperson (on
account of the fact that the goods been reserved for you are still located in the lo-
cal cache) has already sold some of the items that you requested.
True, you could update the data on the server every time you save a record locally,
but this will result in a connection overload and an overall performance loss.
On the one hand, using modifier queries enables you to quickly update data on the
server, but on the other hand, you will have to pay the price — extensive pro-
gramming efforts and more tedious debugging. The code will be much more com-
plex in such a case.
Let's now consider a sample application that implements both these approaches.
The Demo DBX application is connected to the ..\Program Files\Common Files\,
Borland Shared\Data\MastSQL.gdb database, located on an InterBase server.
Listing 6.1. A Sample dbExpress Application with Edited Datasets
implementation
{$R *.dfm}
procedure TfmDemoDBX.FormCreate (Sender: TObject) ;
begin230 __— Part Il: Data Access Technologies
>_>
tblVens. Open;
cdsCusts.Open;
end;
procedure TfmDemoDBX.FormDestroy(Sender: TObject) ;
begin
tblVens.Close;
cdsCusts.Close;
end;
{Editing feature with updating query}
procedure TimDemoDBX.tblVensAfterScroll (DataSet: TDataSet) ;
begin
edVenNo.Text := tblVens. FieldByName ('VENDORNO") .AsString;
edVenName.Text := tblVens.FieldByName ('VENDORNAME') .AsString;
edVenAdr.Text := tblVens. FieldByName('ADDRESS1") .AsString;
edVenCity.Text := tblVens.FieldByName('CITY') .AsString;
edVenPhone.Text := tblVens.FieldByName ("PHONE") .AsString;
end;
procedure TfimDemoDBX.sbCancelClick (Sender: TObject) ;
begin
tblVens. First;
end;
procedure TimDemoDBX, sbNextClick (Sender: TObject) ;
begin
tblVens.Next;
end;
procedure TfmDemoDBX, sbPostClick (Sender: TObject) ;
begin
with quUpdate do
try
ParamByName ("Idx") AsInteger
ParamByName ("No") .AsString
ParamByName ('Name') .AsString := edVenName.Text;
ParamByName ('Adr') .AsString edVenAdr.Text;
ParamByName('City').AsString := edVenCity.Text;
thlVens. FieldByName ( 'VENDORNO') AsInteger;
= edVenNo.Text;Chapter 6: dbExpress Technology 231
ParamByName ('Phone') .AsString edVenPhone. Text;
ExecSQL;
except
MessageDlg('Vendor''s info post error’, mtError, [mbOK], 0);
tblVens. First;
end;
end;
{Editing feature with cached updates}
procedure TimDemoDBX.cdsCustsAfterPost (DataSet: TDataSet);
begin
cdsCusts.ApplyUpdates (-1);
end;
procedure TfmDemoDBX.cdsCustsReconcileError
(DataSet: TCustomClientDataset;
E: EReconcileError; UpdateKind: TUpdateKind;
var Action: TReconcileAction) ;
begin
MessageDlg('Customer''s info post error", mtError, [mbOK], 0);
cdsCusts.CancelUpdates;
end;
end.
In the above example, two tables — VENDORS and CUSTOMERS — have been
selected for browsing and editing. The first table is connected to the tbivens com-
ponent of the TsoLTable type through the established connection (the cnMast
component). The values of five fields are displayed in traditional TEait compo-
nents, because the data display components — which are linked to a dbExpress
component through the TDataSource component — can operate only in browse
mode, and do not support data editing.
The Afterscroll event handler provides an effective and easy solution to the
problem of filling TEait components when you navigate through the dataset.
The quUpdate component of the TsoLQuery type is used for saving updates (by
pressing the sbPost button). The current values of the fields from the TEait com-
ponents are passed as query parameters. As a forward-only cursor is used
in this case, the problem of refreshing the data after a modifier query is executed232 ‘Part Il: Data Access Technologies
>_>
does not arise, and the dataset in question is updated only when you call the First
method of the tb1vens component.
Vendor Number : : JedvenNio :
Wendot Name |. JecvenNane
Vendor Addiesst - }*¢¥er“ct
Vendor iy: = <<< JecVenCiy
Vendor Phone... [ed¥erPhone
Fist Next Post
: a
Fig. 6.3. A window of the Demo DBX application
The second table is connected to the cdscusts component of the TsQLClientDataset
type through the same cnMast component, which operates in tabular mode. Data is
displayed in a regular TDBcrid component.
Here, all updates are saved by calling the ApplyUpdates method located in the
AfterPost handler, when the updates in question have already been cached locally.
AfterPost is called every time a move to the next line is performed in the TDBGrid
component.
A simplistic technique of handling exceptions that occur on the server is also pro-
vided for the cdscusts component.
Notice also the settings of the cnMast component of the TsgLConnection type. By
assigning the False value to the Keepconnection and LoginPrompt properties, you
ensure opening of the required datasets when creating a new form, and the auto-
matic closing of the connection as soon as you have closed your application with
minimum source code.Chapter 6: dbExpress Technology 233
dbExpress Interfaces
The dbExpress technology is based on the use of four major interfaces, whose
methods are used by all dbExpress components. If you intend to use the technol-
ogy seriously or to develop your own custom components, you will certainly find
information on these interfaces helpful in your work.
The /SQLDriver Interface
The IsoLDriver interface encapsulates just three methods for handling a dbExpress
driver. An interface instance is created in order to establish a connection and pro-
vide its link to the driver.
The following methods:
function SetOption (eDoption:
sQlResult; stdcall;
function GetOption (eDoption: TSQLDriverOption; PropValue: Pointer;
MaxLength: SmallInt; out Length: SmallInt): SQLResult; stdcall
let you work with the parameters of the driver. And the following method:
TSQLDriverOption; PropValue: LongInt):
function getSQLConnection(out pConn: IsQLConnection): SQLResult;
stdceall;
returns a pointer to the interface that is linked to the driver of the IsgLConnection
connection.
You can access the IsgLDriver interface by using the
property Driver: IsQLDriver read FSQLDriver;
property of the TsoLConnect ion component.
The /SQLConnection Interface
The IsgLconnection interface is responsible for the connection's performance. It is
used for passing queries to the server and for returning results while building
TsQLCommand interface instances; in addition, it manages transaction processing and
supports metadata passing via the TsgLMetaData interface.
The method below is invoked in order to open a connection to the server:
function connect (ServerName: PChar; UserName: PChar; Password: PChar) :
sQLResult; stdeall;234 ‘Part Il: Data Access Technologies
_>
where pszServerName is the name of the database, while pszUserName and
pszPassword is the name and password of the user.
The following method is used for closing a connection:
function disconnect: SQLResult; stdcall;
You can modify the parameters of your connection by calling such methods as
function SetOption (eConnectOption:
LongInt): SQLResult; stdcall;
function GetOption (eDoption: TsQLConnectionOption; PropValue: Pointer;
MaxLength: SmallInt; out Length: smallInt): SQLResult; stdcall;
TsQLConnectionOption; 1Value:
In order to process a query that passes through a connection, an IsQLcommand in-
terface instance is created.
function getSQLCommand (out pComm: IsQLCommand): sQLResult; stdcall;
Transaction processing is done using three methods:
function beginTransaction(TranID: LongWord): SQLResult; stdcall;
function commit (TranID: LongWord): SQLResult; stdcall;
function rollback(TranID: LongWord): SQLResult; stdcall;
Exceptions that occur in the TsQLconnection component are handled by the
method
function getErrorMessage (Error: PChar): SQLResult; overload; stdcall;
It implements the protected soLError procedure that can be utilized in your cus-
tom components and in order to enhance the functionality of your code.
For instance, you can write your own custom procedure for checking errors. It will
probably look something like this:
procedure CheckError(IConn: IsQLConnection) ;
var FStatus: SQLResult;
FSize:smallint;
FMessage: pChar;
begin
FStatus := IConn.getErrorMessageLen (FSize) ;
if (FStatus = SQL_SUCCESS)and(FSize > 0) then
begin
FMessage := AllocMem(FSize + 1);
FStatus := IConn.getErrorMessage (FMessage) ;
if Fstatus = SQL_SUCCESS
then MessageDlg(FMessage, mtError, [mbOK], 0)Chapter 6: dbExpress Technology 235
—_
else MessageDlg('Checking error', mtWarning, [mbOK], 0);
if Assigned (FMessage)
then FreeMem(FMessage) ;
end;
end;
The IsoLconnection interface can be accessed through the
property SQLConnection: IsgLConnection;
property of the TsoLConnect ion component.
The [SQLCommand Interface
The IsoLcommand interface provides for the functiot
Components of dbExpress that work with datasets utilize it to implement their
methods.
The parameters of a query can be set using the method
function setParameter(ulParameter: Word ; ulChildPos: Word ;
eParamType: TSTMTParamType ; uLogType: Word; uSubType: Word;
iPrecision: Integer; iScale: Integer; Length: LongWord ; pBuffer:
Pointer; lInd: Integer): sQLResult; stdcall;
where ulParameter is the ordinal number of the parameter. If the parameter
is a child parameter for complex data types, then its number is specified by
ulChildPos. eParamType defines the type of the parameter (input, output, mixed),
uLogType sets the data type of the parameter, and usubType indicates the
subparameter of the data type. iscale determines the maximum value in bytes,
iPrecision sets the maximum precision of the data type, Length sets the size of the
buffer, puffer indicates the buffer that contains the value of the parameter, and
finally, 11nd sets the flag that determines if the parameter can be set to zero.
This method is called for each parameter.
You can obtain information on a parameter by calling the method
function getParameter (ParameterNumber: Word; ulChildPos: Word; Value:
Pointer; Length: Integer; var IsBlank: Integer): SQLResult; stdcall;
where ParameterNumber is the ordinal number of the parameter. If the parameter
in question is a child parameter for complex data types, its number is set by236 _— Part Il: Data Access Technologies
>_>
ulChildPos. Value is a pointer to the parameter value's buffer, Length specifies the
size of the buffer, and tsBiank indicates that the parameter is currently blank.
The method
function Prepare (SQL: PChar; ParamCount: Word): SQLResult; stdcall;
prepares a query for processing on the basis of the specified parameters.
A query is executed by calling the method
function Execute (var Cursor: ISQLCursor): SQLResult; stdcall;
which returns the interface of the cursor in the cursor parameter if the query has
been executed.
Or, the following method can be used instead:
function ExecuteImmediate (SQL: PChar; var Cursor: IsQLCursor):
sQbResult; stdcall;
This method executes a query that doesn't require preparation (i.e., doesn't have
any parameters). It also returns a prepared cursor interface in the cursor parame-
ter if the query has been processed successfully. The text of the query is defined by
the soL parameter.
And finally, the method
function getNextCursor (var Cursor: IsQLCursor): sQLResult; stdcall;
defines the cursor of the next dataset in the cursor parameter if a stored proce-
dure has been executed that returns several datasets.
The tsoLconmand interface is used by the TCustomsQLDataset component, and is
not available to descendants.
The /SQLCursor Interface
The IsoLcursor interface contains a number of methods that provide information
‘on cursor fields and their values. All these methods look exactly the same. To get the
required information, indicate the ordinal number of a field in the cursor structure.
The method
function Next: SQLResult; stdcall;
updates the cursor by inserting the information contained in the next string of the
dataset into it.Chapter 6: dbExpress Technology 237
>
This interface is used by the TcustomsoLDataset component, and is not available
to descendants.
Debugging Applications
with dbExpress Technology
Along with the traditional methods for debugging your code, dbExpress enables
you to control queries which are passed to the server through the connection. This
is achieved by using the TsQLMonitor component.
By using the property
property SQLConnection: TSQLConnection;
the component is linked to the connection being debugged.
The component is then activated by setting Active = True.
While the application is running, and as soon as the connection is opened, infor-
mation on all the commands that are passed will be given by the property
property TraceList: TStrings;
The contents of a list can be saved to a file using the method
procedure SaveToFile(AFileName: string);
You can also add this information to a text file, which can be specified by the fol-
lowing property:
property FileName: string;
but this is only if the
property AutoSave: Boolean;
property is set to True.
The property
property MaxTraceCount: Integer;
defines the maximum number of controllable commands and manages the process
control. If the value is -1, all restrictions are lifted, and if the value is 0, control is
disabled.
The current number of commands that have been traced is contained in the fol-
lowing property:
property TraceCount: Integer;238 _— Part Il: Data Access Technologies
>_>
Before adding a command, the following handler is invoked to the list:
TTraceEvent = procedure (Sender: TObject; CBInfo: pSQLTRACEDesc;
var Log?race: Boolean) of object;
property OnTrace: TTraceEvent;
Immediately after it is added to the list, the following procedure is called:
‘TrraceLogEvent = procedure (Sender: TObject; CBInfo: pSQLTRACEDesc) of object;
property OnLogTrace: TTraceLogEvent;
As a result, the developer obtains a compact code which allows him or her to ef-
fortlessly access the information on whether the commands have succesfully passed
through the connection, etc.
If the TsolMonitor component cannot be used for some reason, utilize the method
procedure SetTraceCallbackEvent (Event: TSQLCallbackEvent; IClientInfo:
Integer);
of the TsoLconnection component. The Event parameter of the procedure type
specifies the function that will be called during the execution of each command.
The IclientInfo parameter must contain a number.
It enables the developer to manually declare a function of the TsQLcallbackEvent
type:
TRACECat = TypedEnum;
TSQLCallbackEvent = function (CallType: TRACECat; CBInfo: Pointer):
CBRType; stdcall;
This function will be called every time a command is passed, and the text of this
command will be given to the cBInfo buffer. The developer simply needs to per-
form the necessary operations with the buffer inside the function.
By way of example, let's consider the following source code:
function GetTraceInfo (CallType: TRACECat; CBInfo: Pointer): CBRType; stdcall;
begin
if Assigned(Forml.TraceList) then Forml.TraceList .Add (pChar (CBinfo) )
end;
procedure TForml .MyConnectionBeforeConnect (Sender: Tobject) ;
begin
TraceList := TStringList.Create;Chapter 6: dbExpress Technology 239
>
end;
procedure TForml.MyConnectionAfterDisconnect (Sender: TObject)
begin
if Assigned(TraceList) then
begin
TraceList.SaveToFile('c:\Temp\TraceInfo. txt");
TraceList Free;
end;
end;
procedure TForm1.StartBtnClick(Sender: Tobject) ;
begin
MyConnect ion. SetTraceCallbackEvent (GetTraceInfo, 8);
MyConnect ion. Open;
2
MyConnect ion.Close;
end;
An object of the TstringList type is created in the BeforeConnection method
before opening a connection. Once the connection is closed, this object is saved to
a file and deleted.
Before opening a connection (the event handler for the pressing of the Start
button), the GetTraceInfo function is joined to the connection using the
SetTraceCallbackEvent method.
Thus, information on the commands will be accumulated in the list as each is
passed. Once the connection is closed, the list is saved as a text file.
The TSQLMcnitor component also uses calls for the Set TraceCallbackEvent method,
This means that you cannot use this component and your own functions simultaneously.
Distributing dbExpress Applications
A dbExpress application that is ready for work can be delivered to its users in two
ways.
The DLL for the selected server comes with the dbExpress application (see the
Driver column in Table 6.1). This DLL is contained in the ..\Delphi6\Bin directory.240 __ Part Il: Data Access Technologies
Additionally, if the application uses the TsoLclientDataSet component, you need
to include the midas.dll dynamic library.
An application is compiled with the following DCU files: dbExpInt.dcu,
dbExpOra.deu, dbExpDb2.dcu, and dbExpMy.dcu (depending on the selected da-
tabase server). If the application uses the TsQLclientDataset component, you
need to add the Crtl.dcu and MidasLib.dcu files. As a result, only the executable
file of your application need be delivered.
The dbxconnections.ini file doesn't need to be included if your application doesn't
require additional settings for connections.
Summary
The dbExpress technology is intended for developing applications that require fast
and easy access to databases stored on SQL servers. This access is achieved by us-
ing compact drivers implemented as DLLs. Currently, drivers for four database
servers have been created:
0 DB2 1 InterBase
© MySQL O Oracle
The dbExpress technology is based on using standard types of data access compo-
nents, and it also allows for lightweight distribution (as a single executable applica-
tion file or as a couple of DLLs). It supports cross-platform development for
Linux, and can easily be integrated into CLX applications.
However, the technology does have a number of deficiencies, among them the use
of non-scrollable cursors only, and certain limitations that are imposed on editing
data. (You can edit data only by locally caching it on the client side, or by exe-
cuting special modifying queries.)Chapter 7242 Part Il: Data Access Technologies
>_>
ADO Basics
Along with such traditional data access methods as Borland Database
Engine and ODBC, Delphi applications support Microsoft ActiveX Data Objects
(ADO) based on COM resources, or more specifically, OLE DB interfaces.
ADO gained wide popularity among developers thanks to its versatility: a basic set of
OLE DB interfaces comes with every modern Microsoft operating system. This means
that to enable your application to access a data source, all you need to do is correctly
specify the ADO connection provider and then transfer the program to any computer
with the required database (of course, the compoter must also have ADO installed).
The Delphi Component palette has an ADO page with a set of components that
allow for the creation of full-fledged database applications for referring to data
through ADO.
This chapter covers the following issues:
0 A brief overview of ADO, available ADO providers, and objects and interfaces
that work with ADO
OC Establishing a connection to a database using ADO in Delphi applications
G Using an ADO dataset object in an application
© How to use tables, SQL queries, and stored procedures
© Understanding ADO commands and ADO command objects
© ADO Basics
The Microsoft ActiveX Data Objects technology enables universal access to data
from database applications. This is made possible by the functions of a set of in-
terfaces designed based on the general model of COM objects, and described in
the OLE DB specification.
ADO technology and the OLE interfaces provide applications with a single tech-
nique for accessing diverse kinds of data. For example, an application using ADO
can perform equally complicated operations with data stored on an SQL corporate
server, and with spreadsheets and a local DBMS. An SQL query directed to any
data source using ADO will be successful.
This raises a question: how can data sources process this query?
There is no need to worry about database servers: processing SQL queries is their
main responsibility. But what about the series of files, spreadsheets, e-mails, etc.?
It is here that ADO mechanisms and OLE DB interfaces come to the rescue.Chapter 7: Using ADO with Delphi 243
—_
OLE DB is a set of specialized COM objects that contain standard functions for
processing data and specialized functions of specific data sources, and interfaces
that provide for data exchange between objects.
ADO Application
ae
ADO
OLE DB
Data Store
Se eS
Databases Files. Excel
Fig. 7.1. The process of ADO data access
According to ADO terminology, any data source (a database, spreadsheet, or file)
is called a data store if an application interacts with it through a data provider. The
minimum set of application components can include a connection object, a dataset
object and a request processing object.
OLE DB objects are created and used in the same way as other COM objects. Every
object has the class identifier CLSTD that is stored in the system registry. Objects are
created using the CocreateInstance method and the corresponding class factory. An
object corresponds to a set of interfaces, the methods of which can be referred to after
the object has been created. See Chapter 1, "The COM Mechanisms in Delphi," for
more information on working with COM objects.
As a result, the application doesn't refer to a data source directly, but rather to the
OLE DB object, which can represent data (for instance, from an e-mail file) in the
form of a database table or an SQL query result.244 Part Il: Data Access Technologies
>_>
The ADO technology as a whole incorporates not just OLE DB objects, but also
mechanisms that allow for interaction between data objects and applications. On
this level, the most important role is played by ADO providers, which coordinate
the operations of applications with data stores of various kinds.
This architecture enables the set of objects and interfaces to be open and extensive.
The set of objects and a corresponding provider can be created for any data
store without changing the primary ADO structure. Here, the very concept
of data is significantly widened, as a set of objects and interfaces for handling non-
traditional tabular data can be developed. These can include graphic
data of geoinformation systems, tree structures of system registries, data of CASE
tools, etc.
As ADO is based on the standard COM interfaces that are part of the Windows
system mechanism, the overall amount of system code is reduced, allowing for the
distribution of database applications without accessory programs and libraries.
The OLE DB specification described below is presented in compliance with the official
Microsoft terminology for this subject area
The OLE DB specification distinguishes the following types of objects that will be
examined below:
© An enumerator searches for data sources or other enumerators. It is used to
support the functioning of ADO providers.
GA data source object is a data store.
© A session combines a collection of objects that refer to one data store.
GA rransaction contains the mechanism for executing transactions.
© A command contains the text of a command and enables its execution. A com-
mand can be an SQL query, a call to a database table, etc.
0 A rowser is a collection of data lines that are the result of the execution of an
ADO command.
G An error contains information on an exceptional situation.
Let's examine the functional capabilities of the basic OLE DB objects and inter-
faces.Chapter 7: Using ADO with Delphi 245
—_
Enumerators
Enumerator objects search for any ADO objects that provide access to data
sources. Other enumerators are also visible in the enumerator.
The primary search for data sources is conducted in the ADO provider. The enu-
merators can only select data sources of specific types, and so the provider can
provide access only to a specific type of data source.
ADO has a system root enumerator that does a basic search for other enumerators
and data source objects. It can be used if you know its class identifier,
CLSID_OLEDB_ENUMERATOR.
In Delphi, the GUID of the global enumerator object is stored in the
.. \Delphi6\Source\Vcl\OleDB. pas file:
CLSID_OLEDB_ENUMERATOR: TGUID = ' {C8B522D0-5CF3-11CE-ADES-00AA0044773D} ";
The functions of the enumerator are contained in the TsourcesRowset interface.
The method:
function GetSourcesRowset (const punkOuter: Unknown; const riid:
TGUID; cPropertySets: UINT; rgProperties: PDBPropSetArray; out
ppSourcesRowset: IUnknown): HResult; stdcall;
returns a pointer to the rowset object that contains information on found data
sources or enumerators.
Connection Objects with Data Sources
The internal ADO mechanism that provides a connection with data stores uses two
types of objects — data source objects and session objects.
The data source object presents detailed information on the required real data
source and provides a connection to it.
The rpBProperties interface is used to input information about a data store.
The required information must be supplied to make a successful connection. It is
likely that for any data source, the name, user name, and password will be needed.246 __ Part Il: Data Access Technologies
>_>
However, each type of data store has its own unique settings. To obtain a list of all
the required parameters for connecting to a data store, you can use the method:
function GetPropertyInfo(cPropertyIDSets: UINT; rgPropertylDset:
PDBPropIDsetArray; var pcPropertyInfoSets: UINT; out prgPropertyInfoset:
PDBPropInfoSet; ppDescBuffer: PPOleStr): HResult; stdcall;
that returns the completed pBPROPINFo Structure.
PDBPropInfo = “TDBPropInfo;
DBPROPINFO = packed record
pwszDescription: PWideChar;
dwPropertyID: DBPROPID;
dwFlags: DBPROPFLAGS;
vtType: Word;
walues: OleVariant;
end;
TDBPropInfo = DBPROPINFO;
The DBPROPFLAGS_REQUIRED value is set for each required parameter in the element
dwFlags.
To initialize a connection, you must use the method:
function Initialize: HResult; stdcall;
of the IDBInitialize interface of the data source object.
Sessions
A session object can be created from a data source object. In order to do this, use
the method
function CreateSession(const punkOuter: IUnknown; const riid: TGUID;
out ppDBSession: IUnknown): HResult; stdcall;
of the 1pBcreateSession interface. The session is designed for managing transac-
tions and rowsets.
Transactions
Transaction management in OLE DB is implemented at two levels.
First, the session object has all the necessary methods. It has the rTransaction,
IfransactionJoin, ITransactionLocal, and ITransactionObject interfaces.Chapter 7: Using ADO with Delphi 247
—_
Within the session, a transaction is controlled by the ITransactionLocal,
Itransactionsc, and ITransaction interfaces, and their methods — the
StartTransaction, Commit, and Rollback.
Second, you can create a transaction object for the session object with the method
function GetTransactionObject (ulTransactionLevel: UINT;
out ppfransactionObject: ITransaction): HResult; stdcall;
of the ITransactionObject interface, which returns a pointer to the interface of
the transaction object.
Rowsets
The rowset object is the main ADO object that handles data. It contains a collec-
tion of rows from the data source, as well as mechanisms for navigating the rows
and keeping them in an active state.
The session object must have the 1openRowset interface with the method
function OpenRowset(const punkOuter: IUnknown; pTableID: PDBID;
pIndexID: PDBID; const riid: TGUID; cPropertySets: UINT; rgPropertySets:
PDBPropSetArray; ppRowset: PIUnknown): HResult; stdcall;
which opens the required rowset.
Depending on the capabilities of the data source, the rowset can support diverse
interfaces. But five of them are essential:
Cj trowset navigates through the rows.
© taccessor presents information on the format of the rows stored in the rowset
buffer.
CG tRowsetInfo receives information on rowsets (for example, the number of rows
or the number of updated rows).
© Icolumnsinfo receives information on the columns of rows (their names, the
data type, update capability, etc.).
CG tconverttype contains the single method canconvert, which determines the
conversion capability of data types in a rowset.
In contrast with the usual practice of developing interfaces within the COM model,
OLE DB interfaces often have only one or two methods. As a result, a large group
of interfaces implement several fully standard functions.248 Part Il: Data Access Technologies
>_>
The following interfaces provide additional features for managing rowsets:
© trowsetChange carries out changes in rowsets (makes changes, adds new rows,
deletes rows, etc.).
CO rrowsetIdentity compares rows from different sets.
© tRowset Index allows the use of indexes.
© tRowsetLocate searches in a rowset.
CO tRowsetUpdate implements the mechanism for caching changes.
Commands
The ADO development kit would be incomplete if it didn't use SQL in working
with data. DML and DDL statements, and a range of special ADO statements,
known as text commands.
A command object contains the textual command itself and the mechanism for
processing and transferring this command. The command object performs the fol-
lowing operations:
G Analyzing the text of a command
O Binding a command to the data source
CO Optimizing a command
© Transferring a command to the required data source
The main Icommand interface of the command object uses three methods:
function Cancel: HResult; stdeall;
which cancels the command,
function Execute(const punkOuter: IUnknown; const riid: TGUID;
var pParams: DBPARAMS;
pcRowsAffected: PInteger; ppRowset: PIUnknown): HResult; stdcall;
which executes the command, and
function GetDBSession(const riid: TGUID; out ppSession: TUnknown):
HResult; stdcall;
which returns a pointer to the session interface that issued the command.
In addition to the main interface, the command object enables access to additional
interfaces:
CO icomnandprepare contains two methods (prepare and Unprepare) for preparing
a command.Chapter 7: Using ADO with Delphi 249
—_
OC iccmmandProperties sets the properties for a command that should be sup-
ported by the dataset returned by this command.
© tcomnandrext manages the text of a command (this interface is required for the
command object).
© tcommandwithParameters handles the parameters of a command.
ADO Providers
ADO providers establish a connection between an ADO-compliant application and
a data source (an SQL server, a local DBMS, a file system, etc.). Each type of
data store must have an ADO provider.
The provider "knows" the location and contents of data stores, and can refer que-
ries to data and interpret returned service information and the results of queries for
the purpose of transferring them to the application.
A list of the providers installed on the system can be made available for selection
by setting the connection through the TADoconnection component.
The following standard providers are installed on the operating system when
Microsoft ActiveX Data Objects is installed.
Microsoft Jet OLE DB Provider provides a connection with the Access database
using DAO technology.
Microsoft OLE DB Provider for Microsoft Indexing Service provides read-only access
to file systems and Microsoft Indexing Service Internet resources.
Microsoft OLE DB Provider for Microsoft Active Directory Service provides access to
the Active Directory Service.
Microsoft OLE DB Provider for Internet Publishing allows you to use the resources
provided by Microsoft FrontPage, Microsoft Internet Information Server, and
HTTP files.
Microsoft Data Shaping Service for OLE DB allows you to utilize hierarchical datasets.
Microsoft OLE DB Simple Provider is designed to organize access to the data
sources that support only basic OLE DB capabilities.
Microsoft OLE DB Provider for ODBC drivers provides access to any data
that have already been registered by ODBS drivers. In practice, however, making250 __ Part Il: Data Access Technologies
>_>
a connection in such an unusual way can be problematic. ODBC drivers
are already notorious for their slow performance, so an additional layer of services
here is undesirable. Microsoft OLE DB Provider for Oracle allows you to establish a
connection to an Oracle server.
Microsoft OLE DB Provider for SQL Server is used to connect to a Microsoft SQL
server.
Realizing ADO in Delphi
The mechanism for accessing data using ADO and a wide range of objects and
interfaces is realized in Delphi VCL in the form of a set of components that
reside on the ADO page. All the interfaces necessary for working with these com-
ponents are described in the OleDB.pas and ADODB.pas files stored in the
.\Delphi6\Source\Vel directory.
ADO Components
The TADOConnection component combines the capabilities of enumerator, data
source, and session with transaction service capabilities.
ADO text commands are realized in the TADOCommand component.
Rowsets (a Microsoft notation) can be gotten using the components TADOTable,
TADOQuery, and TADOStoredProc. Each of them lets you access a particular type
of data presentation in a data store. From here on, when referring to Delphi appli-
cations, the set of rows returned from a store of data lines will be called
a recordset. This is in keeping with the Borland documentation (see
www.Borland.com) and the style of the previous chapters.
The set of ADO properties and methods allows for realization of all the functions
required by database applications. Ways of using ADO components are somewhat
different from standard VCL data access components (see Chapter 5, "The Archi-
tecture of Database Applications").
However, if necessary, the developer can use all the capabilities of ADO interfaces
by addressing them through the appropriate ADO objects. Pointers to these objects
can be found in components.Chapter 7: Using ADO with Delphi 251
_>
The Mechanism for Connecting
to an ADO Data Store
ADO data access components can use two methods for connecting to a data store.
These are the standard ADO method and the standard Delphi method.
In the first scenario, components use the connectionstring property to refer
to a data store directly. In the second case, the special TADOConnect ion component
is used, which allows for extended management of a connection, and enables sev-
eral components to refer to the same data store simultaneously.
The connectionstring property is designed for storing information on a connec-
tion with an ADO component. It lists all the required parameters, delimited by
semicolons. At the very least, the list should contain the names of the provider for
the connection or remote server:
ConnectionString:='Remote Server=ServerName; Provider=ProviderName' ;
If necessary, the path to the remote provider can be specified:
ConnectionString:="Remote Provider=ProviderName';
and the parameters required by the provider as well:
"User Name=User_Name;Password=Password!'
Every component that refers to an ADO data store independently by specifying the
parameters for the connection in the connectionString property opens its own
connection. The more ADO components an application contains, the more con-
nections can be open at once.
Therefore, it is advisable to implement the ADO connection mechanism using
a special component — TADOConnection. This component opens the connection
that is also set by the connectionstring property, and provides the developer with
extra tools for managing the connection.
The components that operate on an ADO data store through the connection con-
nect to the TADOConnection component using the property
property Connection: TADOConnection;
which can be found in every component that contains an ADO dataset.252 ___ Part Il: Data Access Technologies
The TADOConnection Component
The Tapoconnection component is designed for managing a connection with ADO
data store objects. It enables ADO components that contain datasets to access
a data store.
Using this component gives the developer a number of advantages:
© All ADO data access components address the data store through one connection
© Direct specification of the connection provider object
G Access to the ADO connection object
© Execution of ADO commands
© Execution of transactions
G Extended connection management using event handlers
Connection Setup
Before opening a connection, you have to set its options. To do this, use the property
property ConnectionString: WideString;
which was examined in detail in the previous section. It only remains to add here
that the set of parameters can vary depending on the type of provider, and can be
set both manually and by using an editor to specify the connection parameters.
Call the editor by double-clicking the TaDoconnect ion component tranferred to the
form, or by clicking the button in the connectionstring edit field in the Object
Inspector window.
FEE i El
Source of Connection
© Use Dats Link File
Use Connection String
Cancel Help
Fig. 7.2. The ADO connection setup editorChapter 7: Using ADO with Delphi 253
—_
Here you can set the connection using the connectionstring property (the Use
Connection String radio button) or by loading the connection parameters from
a UDL extension (the Use Data Link File radio button).
A UDL file (Listing 7.1) is a regular text file that contains the name of a parameter and
its value after the equals sign. The parameters are delimited by semicolons.
Listing 7.1. ADemo DBDEMOS.UDL File
[oledb]
; Everything after this line is an OLE DB initstring
Provider=Microsoft .Jet.OLEDB.4.0;Data Source=C:\Program Files\
Common Files\Borland Shared\Data\DBDEMOS.mdb
If the file with connection parameters is not available, you will have to make the set-
tings manually. Press the Build button, and the Data Link Properties dialog will be
displayed in which you can set the connection parameters manually. The dialog is a
four-tab window that allows you to specify all the necessary parameters step-by-step.
SEs 3)
Provider | Connection| Advanced] Al |
Select the data you want to connect ta:
COLE: CO vss) S555 SSSR SNS
Microsoft Jet 4.0 OLE DB Provider
| Microsoft OLE DB Provider for Indexing Service
} Mictosoft OLE DB Provider for Internet Publishing
Mictosoft OLE DB Provider for ODBC Divers
Mictosoft OLE DB Provider tor OLAP Services
Mictosott OLE DB Simple —
MSDalaShape
OLE DB Provider for Microsoft Directoy Services
Next>>
cont |_ te
Fig. 7.3. The dialog for setting connection parameters on the Provider selection tab254 __‘ Part Il: Data Access Technologies
>_>
The first tab, Provider, lets you select the OLE DB provider for a particular type
of data source from the providers installed on your system. Here you can see the
providers not only for database servers, but also for services installed on the oper-
ating system. The controls for managing the following tabs depend on the type of a
data source, but the difference is not that great. Further on, at almost every stage,
you will need to assign the data source (server name, database, file, etc.) and the
user authentication mode, and define the user name and password.
Let's examine the setup process using the OLE DB provider for the Microsoft SQL
Server as an example.
Serer
Poi Correction [Aderced| aa |
‘Specity the following to connect to A@L Server dete:
1. Select or enter a server name:
TESTSERVER
2. Enler information to log on lo the server
Use Windows NT Intecrated security
© Use a specific user name and password:
Use
Pas
FE Blerk password (aA
3, © Select the database on the server
master
© Aitach a database file as a database name:
erate nevare
ie
Fig. 7.4. The dialog for setting connection parameters on the Connection tab
The next tab, Connection (Fig. 7.4), allows you to set the data source.
The first step is to select the name of a server from the servers available on your
computer.
The second step is to specify the user authentication mode. This is either the
Windows integrated security system or the server's own authentication system.
You also need to define the user name and password.Chapter 7: Using ADO with Delphi 255
_>
The third step is to select the database of the server.
After you have made the settings for the data source, you can test the connection
by pressing the Test Connection button.
Now you can move to the next tab.
x
Provider| Connection Advanced | ail |
Netwerk setings
impersonetion Ie aEEaEs |
f z
Other
Connect timeout | seconds.
persons [Read =]
(Reatiite
Share Deny None
I) Shate Dery Read
(Share Dery Wie Se
(share Exclusive 4
Cancel Help.
Fig. 7.5. The dialog for setting connection parameters on the Advanced tab
The Advanced tab (Fig. 7.5) allows you to set additional connection parameters.
Depending on the data store, some elements in this tab may be unavailable.
The Impersonation Level list specifies the level of impersonation for clients ac-
cording to the authority of their roles. The following values can be selected for this list:
G Anonymous — the client role is inaccessible to the server.
G Identify — the client role is recognized by the server but the client is not
granted permission to access system objects.
CG Impersonate — the server process can be represented by the protected context
of the client.256 ___‘ Part Il: Data Access Technologies
>_>
CG Delegate — the server process can be represented by the protected context of
the client, but the server can also carry out other connections.
The Protection Level list allows you to set the security level for the data. The fol-
lowing values can be selected for this list:
© None — no confirmation is required.
Connect — confirmation is required only when connecting.
a
OG Call — confirmation from the data source is required for every query.
OG Pkt — confirmation that all data have been received from the client.
a
Pkt Integrity — confirmation that all data have been received from the client
and the integrity of the data has been maintained.
a
Pkt Privacy — confirmation that the data have been received from the client in
encoded form and that the integrity of the data has been maintainted.
In the Connect Timeout field you can specify the time to wait for the connection
in seconds. After the time has elapsed, the process is interrupted.
If necessary, the Access Permissions list lets you set access permissions for specific
operations. The following values can be selected for this list:
Read — read-only permission
ReadWrite — read/write permission
Share Deny None — read/write permission for all users
Share Deny Read — read permission denied to all users
Share Deny Write — write permission denied to all users
Share Exclusive — read/write permission denied to all users
GG og 8 a og Gi
Write — write-only permission
The last tab, All (Fig. 7.6), enables you to view and, if necessary, change all the
settings for the selected provider (the Edit Value button is designed for this).
As soon as you have confirmed the settings made in the dialog, a new
ConnectionString property value is given to them.Chapter 7: Using ADO with Delphi 257
_>
0:
Provider| Connection| Advanced All |
(ead
‘These ate the intiazation propatties for this type of data, To edit a
value, select a property, then choose Edt Valve below.
Name
Auto Translate True
Connect Timeout
Curent Language
Data Source TESTSERVER
Extended Propesties
General Timeout a
Irilial Catalog master
Initia! File Name
Integrated Security SsPl
Locale Identifier 1049
Network Address
Network Library
Packet Size 4095
Cancel Help
‘ig. 7.6. The dialog for setting connection parameters on the All tab
for viewing the settings
Managing Connections
A connection to an ADO data store is opened and closed using the property:
property Connected: Boolean;
or the methods:
procedure Open; overload;
procedure Open(const UserID: WideString; const Password: WideString) ;
overload;
and
procedure Close;
The open method can be overloaded if you need to use a remote or local connec-
tion. For a remote connection, you have to use the option with the UserID and258 __ Part Il: Data Access Technologies
>_>
Password parameters. Before and after opening and closing a connection, the de-
veloper can use the corresponding standard event handlers:
property BeforeConnect: TNotifyEvent;
property BeforeDisconnect: TNotifyEvent;
property AfterConnect: TNotifyEvent;
property AfterDisconnect: TNotifyEvent;
Additionally, the TADoconnection component has a number of extra event han-
dlers. After the provider has confirmed that the connection will be opened, and
before its actual opening, the following method is invoked:
TWillconnectEvent = procedure (Connection: TADOConnection;
var ConnectionString, UserID, Password: WideString; var
ConnectOptions: TConnectOption; var EventStatus: TEventStatus)
of object;
property OnWillConnect: TWillConnectEvent;
The connection parameter contains a pointer to the component that has called
this event handler.
The connectionstring, UserID, and Password parameters define the parameter
string and the user name and password.
The connection may be synchronous or asynchronous, which can be determined
using the ConnectOptions parameter of the type TConnectOption:
type TConnectOption = (coConnectUnspecified, coAsyncConnect) ;
G coconnectUnspecified — a synchronous connection that always waits for the
result of the last request
G coAsyncConnect — an asynchronous connection that can execute a new request
without waiting for an answer from the previous request
Finally, the Eventstatus parameter determines the success of the connection re-
quest:
type
TEventStatus = (esOK, esErrorsOccured, esCantDeny, esCancel,
esUnwantedEvent) ;
G esox — the connection request connection was successfully executed.
G esErrorsOccured — an error occurred in the process of executing the request.
© escantDeny — the connection cannot be interrupted.Chapter 7: Using ADO with Delphi 259
_>
G escance1 — the connection was cancelled before opening.
© estnwantedEvent — an internal ADO flag.
For example, if the connection is successful, you can select a synchronous operat-
ing mode for the component:
procedure TForm1.ADOConnectionWillConnect (Connection: TADOConnection;
var ConnectionString, UserID, Password: WideString;
var ConnectOptions: TConnectOption; var EventStatus: TEventStatus);
begin
if EventStatus = es0K
then ConnectOptions := coConnectUnspecified;
end;
Incidentally, the parameter for synchronous/asynchronous mode can also be set
using the property
ConnectOptions property ConnectOptions: TConnectOption;
Once the connection is opened, you can use the following event handler for exe-
cuting your custom code:
TConnectErrorEvent = procedure (Connection: TADOConnection; Error:
Error; var EventStatus: TEventStatus) of object;
property OnConnectComplete: TConnectErrorEvent;
Here, if an error has occurred in the process of opening the connection, the
EventStatus parameter will assume the esErrorsOccured value, and the Error
parameter will contain the ADO Error object.
Let's now move to additional properties and methods of the TADOConnection com-
ponent that safeguard the connection.
To limit the connection opening time for slow communication channels, use the
property:
property ConnectionTimeout: Integer;
which specifies how many seconds to wait for the connection to open. The default
value is 15 seconds.
You can also determine a component's reaction to an unused connection. If no
active component uses this connection, the property
property KeepConnection: Boolean;260 __ Part Il: Data Access Technologies
—_
keeps the connection open when the value is set to True. Otherwise, as soon as
the last active TCustomADODataSet component is closed, the connection is also
closed.
If necessary, you can directly indicate the ADO connection provider using the
property
property Provider: WideString;
The default name of the data source is set by the property
property DefaultDatabase: WideString;
However, if the same parameter is specified in the ConnectionString property,
it overrides the value of the property.
If necessary, an OLE DB connection object can be accessed directly using the
property
property ConnectionObject: _Connection;
When a connection is opened, the user name and password must be entered.
The standard dialog for this operation is managed with the property
property LoginPrompt: Boolean;
You can specify the same parameters without this dialog using the ConnectionString
property, the open method, or the following event handler
type TLoginEvent = procedure (Sender:TObject; Username, Password:
string) of object;
property OnLogin: TLoginEvent;
The property
type TConnectMode = (cmUnknown, cmRead, cmWrite, cmReadWrite,
cmShareDenyRead, cmShareDenyWrite, cmShareExclusive, cmShareDenyNone) ;
property Mode: TConnectMode;
sets the operations that are accessible to the connection:
OG cmUnknown — permission is unknown or cannot be determined.
cmRead — read-only permission.
cnilizite — write-only permission.
cmReadWrite — read/write permission.
gaaa
cmShareDenyRead — read permission denied to other connections.Chapter 7: Using ADO with Delphi 261
_>
CO cmShareDenyirite — write permission denied for other connections.
CO cmShareExclusive — opening permission denied for other users.
OG) cmShareDenyNone — opening other connections with permission is denied.
Accessing Connected Datasets
and ADO Commands
The TaDOConnection component provides access to all the components that use it
for access to an ADO data store. All datasets opened in this way can be accessed
with the indexed property
property DataSets[Index: Integer]: TCustomADODataSet;
Each item in this list contains the index of an ADO data access component (of the
TCustomADODataSet type). The total number of components connected to datasets
is returned by the following property:
property DataSetCount: Integer;
The following property enables you to centralize the type of cursor used for these
components:
type TCursorLocation = (clUseServer, clUseClient);
property CursorLocation: TCursorLocation;
The cluseclient value sets the client-side local cursor, which allows you to
perform any operations with data, including operations not supported by the
server.
The clUseServer value specifies the server-side cursor that only realizes the capa-
bilities of the server, but allows for fast processing of large amounts of data.
For instance:
fori:
begin
if ADOConnection.DataSets[i].Active
then ADOConnection. DataSets [i] .Close;
ADOConnection.DataSets[i].CursorLocation := clUseClient;
end;
0 to ADOConnection.DataSetCount — 1 do
‘True262‘ Part Il: Data Access Technologies
>_>
In addition to handling datasets, the TADoconnection component enables the proc-
essing of ADO commands. Each ADO command is contained in a special
TADOCommand component, which will be explained in detail later in this chapter. All
ADO commands that work with the data store through the connection can be
managed with the following indexed property:
property Commands[Index: Integer]: TADOCommand
Each item in this list is represented by an TADOCommand class instance.
The total number of available commands is returned by the following property:
property ConmandCount: Integer
For example, you can execute all the linked ADO commands immediately after
the connection has been opened by using the following script:
procedure TForml .ADOConnect ionConnectComplete (Connection:
TADOConnection; const Error: Error; var EventStatus: TEventStatus) ;
var i, ErrorCnt: Integer;
begin
if EventStatus = esOK then
for i := 0 to ADOConnection.CommandCount — 1 do
try
if ADOConnection.Commands[i].CommandText <> ''
then ADOConnection. Commands [i] .Execute;
except
on E: Exception do Inc (ErrorCnt) ;
end;
end;
Besides datasets, the TADOConnection allows you to execute ADO commands. The
ADO command contains a special component, TADOCommand, which is examined
below. All ADO commands that work with a data source through a connection can
be accessed through the indexed property
property Commands[Index: Integer]: TADOCommand
Each element of this list is an example of the TADOCommand class.
The total amount of accessible commands are returned by the property
property Commandcount: Integer
For example, immediately after closing a connection you can execute all con-
nected ADO commands with the following script:
procedure TForm1.ADOConnect ionConnect Complete (Connection:
TADOConnection; const Error: Error; var EventStatus: TEventStatus) ;Chapter 7: Using ADO with Delphi 263
—_>
var i, ErrorCnt: Integer;
begin
if EventStatus = esOK then
for i := 0 to ADOConnection.CommandCount — 1 do
try
if ADOConnection.Commands [i] .CommandText <> '"
then ADOConnect ion. Commands [i] «Execute;
except
on E: Exception do Inc(ErrorCnt);
end;
end;
However, the TADOConnection component can execute ADO commands independ-
ently, without help from other components. To do this, we use the overload method
function Execute (const CommandText: WideString; ExecuteOptions:
TExecuteOptions = []): _RecordSet; overload;
procedure Execute (const CommandText: WideString; var RecordsAffected:
Integer; ExecuteOptions: TExecuteOptions = [eoExecuteNoRecords]) ;
overload;
Commands are executed by the Execute procedure (if the command doesn't return
a recordset) or the Execute function (if the command does return a recordset).
The commandText parameter should contain the text of the command. The
RecordsAffected parameter returns the number of the records processed by the
command (if there are any). The parameter
type
TExecuteOption = (ecAsyncExecute, eoAsyncFetch,
eoAsyncFetchNonBlocking, eoExecuteNoRecords) ;
TExecuteOptions = set of TExecuteOption;
specifies the conditions for the execution of the command:
© eoAsyncExecute — the command is executed asynchronously (the connection
will not wait for the command to be executed, but continue operating and pro-
cess the command completion signal command when it arrives).
a
eoAsyncFetch — the command receives the required records asynchronously.
G eoAsyncFetchNonBlocking — the command receives the required records asyn-
chronously, but the created thread is not blocked.
O eokxecuteNoRecords — the command does not return any records.264 __‘ Part Il: Data Access Technologies
>_>
As soon as a data source has received a command and informed the connection,
the following event handler is called:
TWillExecuteEvent = procedure (Connection: TADOConnection; var
CommandText: WideString; var CursorType: TCursorType; var LockType:
TADOLockType; var ExecuteOptions: TExecuteOptions; var EventStatus:
TEventStatus; const Command: _Command; const Recordset: _Recordset)
of object;
property OnWillExecute: TWillExecuteEvent;
Immediately after execution of the command, the following event handler is called:
TExecuteCompleteEvent = procedure (Connection: TADOConnection;
RecordsAffected: Integer; const Error: Error; var EventStatus:
TEventStatus; const Command: Command; const Recordset: Recordset)
of object;
property OnExecuteComplete: TExecuteCompleteEvent;
Errors
All run-time errors that occur while the connection is open are saved in the special
ADO object that contains the collection of error messages. This object can be ac-
cessed using the property
property Errors: Errors;
See "ADO Error Object" section for more information on the ADO errors.
Transactions
The TaDoconnection component allows you to carry out transactions. The methods
function BeginTrans: Integer;
procedure CommitTrans;
procedure RollbackTrans;
enable the start, commission, and completion of a transaction. The event hand-
lers:
TBeginTransCompleteEvent = procedure (Connection: TADOConnection;
TransactionLevel: Integer; const Error: Error; var EventStatus:
TEventStatus) of object;
property OnBeginTransComplete: TBeginTransCompleteEvent;Chapter 7: Using ADO with Delphi 265
—_>
TConnectErrorEvent = procedure (Connectior TADOConnection; Error:
Error; var EventStatus: TEventStatus) of object;
property OnCommitTransComplete: TConnectErrorEvent;
are called after the start and commission of the transaction. The property
type TIsolationLevel = (ilUnspecified, ilChaos, ilReadUncommitted,
ilBrowse, ilCursorStability, ilReadCommitted, ilRepeatableRead,
ilserializable, illsolated);
property IsolationLevel: TIsolationLevel;
enables you to specify the level of isolation for the transaction:
© iivnspecified — the level of isolation is not specified.
ilChaos — changes to more secure transactions cannot be overwritten.
ilReadUncommitted — uncommitted changes to other transactions are visible.
ilBrowse — uncommitted changes to other transactions are visible.
Goo
ilCursorStability — changes to other transactions are visible only after they
have been committed.
a
ilReadCommitted — changes to other transactions are visible only after they
have been committed.
Gl ilrepeatableRead — changes made to other transactions are not visible, but
can be accessed during data updating.
© ilserializable — the transaction is executed in isolation from other transac-
tions.
© iltsolated — the transaction is executed in isolation from other transactions.
The property
TkactAttribute = (xaCommitRetaining, xaAbortRetaining) ;
property Attributes: TxactAttributes;
sets the technique for managing transactions during commission and rollback:
O xacommitRetaining — as soon as the current transaction is committed, the next
one is automatically started.
© xaAbortRetaining — once the current transaction is rolled back, the next one
is automatically started.266 __— Part Il: Data Access Technologies
ADO Datasets
In addition to connection components, the ADO tab of the Delphi Component
palette contains standard components that encapsulate a dataset and are adapted
for work with ADO data stores. These components are:
© rapopataset — the universal dataset
© taporable — a database table
OG tapoguery — an SQL request
© rapostoredProc — a stored procedure
As can be expected of all components that contain datasets, their common ances-
tor is the TDataSet class, which provides basic functions for managing datasets (see
Chapter 5,"The Architecture of Database Applications").
TDataSet |
TCustomADODataSet
TADOQuery TADOStoredProc
TADOTable TADOQuery
Fig. 7.7. The hierarchy of ADO DataSet classes
ADO components have a standard set of properties and methods, and inherit the
mechanism for data access through ADO from their common ancestor, the
TCustomADODataSet class. Additionally, the TCustomaDoDataset class contains
a range of properties and methods common to all its descendants that it would be
useful to examine here. Therefore, we will first study the TcustomaDoDataSet class,
and then proceed to ADO components.Chapter 7: Using ADO with Delphi 267
_>
The TCustomADODataSet Class
The TcustomaDoDataSet class contains the mechanism for accessing stored data
with ADO. This class combines the abstract methods of its common ancestor,
Tdataset, with the functions of a specific mechanism for accessing data.
Therefore, here we will examine only the unique properties and methods of the
TCustomADODataSet class that support ADO.
A dataset is connected to an ADO data store using the TADOConnect ion component
(the Connection property), or by setting the connection parameters with the
ConnectionString property (see above).
Datasets
Prior to opening a dataset, you need to specify the type of lock that will be used
during editing. Use the following property:
type TADOLockType = (1tUnspecified, 1tReadOnly, 1tPessimistic,
ltOptimistic, 1tBatchOptimistic);
property LockType: TADOLockType;
where:
GC itunspecified — the lock is specified by the data source and not by the com-
ponent.
© 1tReadonly — the dataset is opened in read-only mode.
G itPessimistic — the record remains locked throughout the editing session un-
til it has been saved in the data store.
G itoptimistic — the record is locked only while changes are being saved in the
data store.
GC itBatchoptimistic — the record is locked while its is being saved in the data
store using the UpdateBatch method.
To guarantee that the lock is correctly implemented, the LockType property must be
modified before opening a dataset.
A dataset is opened and closed with the open and close methods. You can also use
the property
property Active: Boolean;268 __ Part Il: Data Access Technologies
—_
The current state of any dataset can be determined by the property
type
TobjectState = (stClosed, stopen, stConnecting, stExecuting, stFetching);
TObjectStates = set of TobjectState;
property RecordsetState: TObjectStates;
A dataset in ADO components is based on the use of an ADO recordset object,
which can be accessed directly using the property
property Recordset: _Recordset;
But as all key methods of the ADO recordset object interfaces are overlapped by
methods of the class, you don't normally need to access this object directly.
The following event handler is called every time a dataset is refreshed:
TRecordsetEvent = procedure (DataSet: TCustomADODataSet; const Error:
Error; var EventStatus: TEventStatus) of object;
property OnFetchComplete: TRecordsetEvent;
where Error is a link to the ADO error object, if any error has occurred.
If a dataset operates in asynchronous mode, the following event handler is invoked
every time it is refreshed:
TFetchProgressEvent = procedure (DataSet: TCustomADODataSet; Progress,
MaxProgress: Integer; var EventStatus: TEventStatus) of object;
property OnFetchProgress: TFetchProgressEvent;
where the Progress parameter indicates how the operation is progressing.
Dataset Cursors
The type and location of the cursor you use for ADO datasets will depend on their
function.
The location of the cursor is set by the property
type TCursorLocation = (clUseServer, clUseClient);
property CursorLocation: TCursorLocation;
The cursor can be located either on the server (clUseServer) or on the client side
(clUseClient).
CA server cursor is used for handling large datasets that are inexpedient to send to
the client as a whole. Here, the performance of the client dataset is somewhat
slowed down.Chapter 7: Using ADO with Delphi 269
—_>
0 A client cursor enables the dataset to be transferred to the client. This consid-
erably accelerates the performance, but this type of cursor is only worth using
with small datasets that do not increase network load.
When you use a client cursor, you need to specify an additional property —
TMarshaloption = (moMarshalAll, moMarshalModifiedOnly) ;
property MarshalOptions: Tmarshaloption
which controls data exchange between the client and the server. If the connection that
you are using is fast enough, you can use the moMarsha1A11 value, which permits you
to return all the records in the dataset to the server. Otherwise, you can speed up the
performance of the component by using the moMarshalModifiedonly property, which
ensures that only records modified by the client will be retumed to the server.
The type of cursor is specified by the property:
TCursorType = (ctUnspecified, ctOpenForwardonly, ctKeyset, ctDynamic,
ctstatic);
property CursorType: TCursorType;
© cttnspecified — the cursor is unspecified, the type of cursor is determined by
the capabilities of the data source.
OG ctopenForwardonly — a forward-only cursor that enables only forward naviga-
tion; it is used when you require fast single movement through all the records in a
dataset.
G ctkeyset — a keyset (bidirectional) local cursor that does not allow you to look
at records added or deleted by other users.
GO) ctDynamic — a dynamic (bidirectional) cursor that displays all changes, but also
takes up many resources.
OG ctstatic — a static (bidirectional) cursor that ignores all changes made by
other users.
Client-side cursors (CursorType = clUseClient) support only one type — ct Static.
Before and after every move of the cursor within a dataset, the following event
handlers are called:
TRecordsetReasonEvent = procedure (DataSet: TCustomADODataSet;
const Reason: TEventReason; var EventStatus: TEventStatus) of object;270 __ Part Il: Data Access Technologies
—_
property OnWillMove: TRecordsetReasonEvent;
and
TRecordsetErrorEvent = procedure (DataSet: TCustomADODataset;
const Reason: TEventReason; const Error: Error; var EventStatus:
TEventStatus) of object;
property OnMoveComplete: TRecordsetErrorEvent;
where the Reason parameter informs you which method caused this move.
Local Buffer
As soon as the dataset records are transferred to the client, they are cached in the
local buffer, the capacity of which is defined by the property:
property CacheSize: Integer;
The value of this property indicates the number of records in the local buffer, and
cannot be less than 1. Obviously, if the buffer is large enough, a component need
not refer to the data source very often, but on the other hand, a large cache will
significantly slow down the opening of a dataset.
Furthermore, in selecting the size of the local buffer, it is necessary to take into
account the amount of memory available to a component. This can be done with
simple calculations:
CacheSizeInMem := ADODataSet.CacheSize * ADODataSet.RecordSize;
where Recordsize is the property
property RecordSize: Word;
that returns the size of a single record in bytes. As you can see, ADO components
face a problem common to all client data — with a poor connection, the applica-
tion slows down. However, there is still something you can do. If you don't need
to display data in the visual components of a user interface while navigating
through records, the property
property BlockReadSize: Integer;
enables the block transfer of data. This property specifies the number of records
that can constitute a single block. The status of the dataset changes to dsBlockRead.
By default, block transfer is not used, and the value of this property is 0.
You can also limit the maximum size of a dataset. The property
property MaxRecords: Integer;Chapter 7: Using ADO with Delphi 271
_
sets the maximum number of records that can be contained in a dataset. The de-
fault value of this property is 0, which means that the number of records is unlim-
ited. The total number of records at any given moment is returned by the read-
only property
property RecordCount: Integer;
Once the last record of a dataset is detected, the following event handler is called:
TEndOfRecordsetEvent = procedure (DataSet: TCustomADODataSet; var
MoreData: WordBool; var EventStatus: TEventStatus) of object;
property OnEndOfRecordset: TEndOfRecordsetEvent;
The MoreData parameter indicates whether the record is really the last one.
If MoreData = True, this means that there are still more records in the data store
that have not yet been sent to the client.
Handling Record Status
The tTcustomaDoDataset class has additional capabilties that allow it to monitor the
status of every single record.
You can define the status of every current record by using the property:
TRecordstatus = (rsOK, rsNew, rsModified, rsDeleted, rsUnmodified,
rsInvalid, rsMultipleChanges, rsPendingChanges, rsCanceled,
rsCantRelease, rsConcurrencyViolation, rsIntegrityViolation,
rsMaxChangesExceeded, rsObjectOpen, rsOutofMemory, rsPermissionDenied,
xsSchemaViolation, rsDBDeleted) ;
property RecordStatus: TRecordstatusset;
where:
rsOK — the record has been saved.
rsNew — the record has been added.
rsModified — the record has been modified.
rsDeleted — the record has been deleted.
rsUnmodified — the record has not been modified.
rsInvalid — the record cannot be saved because it is invalid.
gouaaaada
rsMultipleChanges — the record cannot be saved because of multiple changes
made to it.272 Part Il: Data Access Technologies
>_>
CO) rsPendingChanges — the record cannot be saved because it references unsaved
changes.
CG rscanceled — the operation with the record has been cancelled.
a
rsCantRelease — the record is locked.
rsConcurrencyViolation — the record cannot be saved because of the type of
current lock.
a
rsIntegrityViolation — referential integrity has been violated.
xsMaxChangesExceeded — too many changes have been made.
xsObjectopen — a conflict with a database object has occurred.
rsOut0fMemory — lack of memory.
rsPermissionDenied — access denied.
gQaaaaa
rsSchemaViolation — the data structure has been violated.
© rsppDeleted — the record has been deleted in the database.
As you can see, thanks to this property, the status of an individual record can be
determined with great accuracy.
Additionally, the following method:
type
TUpdateStatus = (usUnmodified, usModified, usInserted, usDeleted);
function UpdateStatus: TUpdateStatus; override;
returns details on the status of the current record.
Accordingly, before and after a record is updated, the relevant event handlers are
called:
TWillChangeRecordEvent = procedure (DataSet: TCustamADODataset; const
Reason: TEventReason; const RecordCount: Integer; var EventStatus:
TEventStatus) of object;
property OnWillChangeRecord: TWillChangeRecordEvent;
and
‘TRecordChangeCompleteEvent = procedure (DataSet: TCustomADODataset;
const Reason: TEventReason; const RecordCount: Integer; const Error:
Error; var EventStatus: TEventStatus) of object;
property OnRecordChangeComplete: TrecordChangeCompleteEvent;
where the Reason parameter indicates the method used for updating the record,
and the Recordcount parameter returns the number of modified records.Chapter 7: Using ADO with Delphi 273
—_>
Managing Filtering
In addition to traditional filtering based on the Filter and Filtered properties
and the onFilterRecord event handler, the TcustomaDoDataSet class provides the
developer with a number of additional functions.
The property
TFilterGroup = (fgUnassigned, fgNone, fgPendingRecords,
fgAffectedRecords, fgFetchedRecords, fgPredicate,
fgConflictingRecords) ;
property FilterGroup: TFilterGroup;
sets a group filter for records based on information on the update status of every
record in the dataset, much like the Recordstatus property that we examined
earlier.
Filtering is possible with the following parameters:
O £qUnassigned — the filter is not specified.
G fgNone — all restrictions specified by the filter are removed, and all records of
the dataset are displayed.
CG fgPendingRecords — modified records that have not been saved in the data
source using the UpdateBatch or CancelBatch method are displayed.
OG) feaffectedRecords — the records that were processed during the most recent
save in the data source are displayed.
© fgFetchedRecords — records received during the most recent update in the
data source are displayed.
© fgPredicate — only deleted records are displayed.
G) fgconflictingRecords — modified records that caused an error to occur when
they were saved in the data source are displayed.
For batch filtering to work, two additional conditions are required. First, filtering
must be activated — the Filtered property should be set to True. And secondly,
the LockType property must be set to 1tBatchOptimistic.
with ADODataSet do
begin
Close;
LockType
= 1tbatchOptimistic;274 Part Il: Data Access Technologies
—_>_
Filtered := True;
FilterGroup := fgFetchedRecords;
Opens
end;
The method:
procedure FilterOnBookmarks (Bookmarks: array of const);
activates filtering based on the existing bookmarks. To do this, you must set
bookmarks beforehand at all the records that you are interested in, using the
GetBookmark method. The FilterOnBookmarks method automatically clears the
Filter property and assigns the gUnassigned value to the FilterGroup property.
Running Searches
The following method provides a fast and versatile search through the fields of the
current dataset index:
SeekOption = (soFirstEQ, soLastEQ, soAfterEQ, soAfter, soBeforeEQ,
soBefore) ;
function Seek(const KeyValues: Variant; SeekOption: TSeekOption =
soFirstEQ): Boolean;
The KeyValues parameter must list all the required values of the indexed fields.
The seekoption controls the search process:
OG sorirstzg — the cursor is positioned at the first record found.
G sotastEQ — the cursor is positioned at the last record found.
OG) soafterkQ — the cursor is positioned at the matching record or, if such a rec-
ord is not found, immediately after the place where the record would have been
located.
© soatter — the cursor is positioned immediately after the record found.
G soBeforezo — the cursor is positioned at the matching record or, if such a rec-
ord is not found, immediately before the place where the record would have
been located.
CO) soBefore — the cursor is positioned immediately before the record found.
Sorting
The property
property Sort: Widestring;Chapter 7: Using ADO with Delphi 275
—_
provides a simple method of sorting through a random collection of fields. This
property must contain the names of the required fields delimited by semicolons,
and also must specify the sorting order (ascending or descending):
ADODataSet.Sort := 'FirstField DESC’;
The default sorting order is ascending.
ADO Command Object
To run a request to any data source, every ADO component should contain the
special ADO Command object.
If you use components descended from the TcustomADoDataset class, there is usu-
ally no need to employ the Command object directly. Although all the actual in-
teraction between an ADO Dataset object and a data source is accomplished
through the Command object, the settings and command execution are hidden in
the properties and methods of ADO components. Nevertheless, you can access the
Command object in the TcustomaDoDataset class using the property:
property Command: TADOCommand;
If the developer needs to execute a ADO command that is not directly linked to any par-
ticular dataset, he or she can utilize the special TADOCommand component, which is also
located in the ADO tab of the Component palette.
The type of command is set by the property
type
TCommandType = (cmdUnknown, cmdText, cmdTable, cmdStoredProc,
cmdFile, cmdTableDirect) ;
property CommandType: TCommandType;
where:
© cmatinknown — the type of command is unknown and will be specified by the
data source.
G cmatext — a text command (for example, an SQL request) interpreted by the
data source; the textual content must be compiled in compliance with the rules
for the specific data source.
OG cmaTable — a command for receiving a table dataset from the data store.276 ___ Part Il: Data Access Technologies
—_
OG) cmastoredProc — a command to execute a stored procedure.
OG cmaFile — a command for receving a dataset saved in a a file with the format
used by a specific data source.
O cmdTableDirect — a command for receiving a table dataset directly, for exam-
ple from a file of this table.
The text of the command is set by the property
property ConmandText: Widestring;
and must match the command type.
To restrict the time of waiting for the execution of a command, use the property:
property ConmandTimeout: Integer;
ADO Dataset components execute commands using the following operations:
© Opening and closing datasets
OC Processing requests and stored procedures
O Updating datasets
© Saving datasets
OC Performing batch operations
The developer can modify the way a command is processed by changing the fol-
lowing property:
type
TExecuteOption = (eoAsyncExecute, eoAsyncFetch,
eoAsyncFetchNonBlocking, eoExecuteNoRecords) ;
TExecuteOptions = set of TExecuteOption;
property ExecuteOptions: TExecuteOptions;
where:
GO eoAsyncExecute — the command executes asynchronously.
© eoAsyncFetch — the command for updating datasets executes asynchronously.
GC eoAsyncFetchNonBlocking — the command for updating datasets executes
asynchronously, and subsequent operations are not blocked.
© eokxecuteNoRecords — the command does not demand the return of a dataset.Chapter 7: Using ADO with Delphi 277
_>
Batch Operations
As mentioned above, ADO Dataset components use a client-side local cache for
storing data and changes. Thanks to this, it is possible to implement batch opera-
tions. In this mode, all the changes made are accumulated in the local cache in-
stead of being immediately passed to the data source. This speeds up performance
and allows you to save an entire batch of modified records at once.
The downside to this method is that while the changes are located on the client,
they are unavailable to other users. Data could be lost in this way.
In order to switch a dataset to group operations mode you need to proceed as fol-
lows.
The dataset must use the client cursor:
ADODataSet .CursorLocation := clUseClient;
The cursor must have the ctStatic type:
ADODataSet..CursorType := ctStatic;
The lock must be set to the 1tBatchoptimistic value:
ADODataSet.LockType := 1tBatchOptimistic;
To pass changes made in the data store, ADO components use the method
procedure UpdateBatch (AffectRecords: TAffectRecords = arAll);
To cancel all changes made but not saved using the UpdateBatch method, use the
method
procedure CancelBatch (AffectRecords: TAffectRecords = arAll);
The TAffectRecords type used by these methods enables you to define the type of
records processed by the operation:
TAffectRecords = (arCurrent, arFiltered, arAll, arAllChapters);
where:
OC arcurrent — the operation processes only the current record.
G arFiltered — the operation processes only records that match the current filter.
© raii — the operation processes all records.
a
arAllchapters — the operation processes all the records in the current dataset
(including records which are not visible because of the active filter), as well as
all the embedded datasets.278 Part Il: Data Access Technologies
Parameters
Many ADO Dataset components that contain recordsets must supply parameters to
requests. To do this, the special TParameters class is used.
An individual TParameter class is created for each parameter in the TParameters
collection.
This class is a descendant of the TCollection class and contains an indexed list of
individual parameters. Remember that in working with regular request parameters
in request and stored procedure components, you need to use the TParams
class (for example, in dbExpress components), which also descends from the
‘TCollection class.
The methods used by these two classes coincide, while their properties have some
differences. To display command parameters, ADO uses a special parameter object
that is actively used by all ADO components that encapsulate datasets.
This is why ADO components in VCL had their own class of parameters created
for them.
The TParameters Class
The main purpose of the TParameters class is to contain a list of parameters.
An indexed list of parameters is represented by the property
property Items[Index: Integer]: TParameter;
The current values of parameters can be obtained from the indexed property
property ParamValues[const ParamName: String]: Variant;
You can access a particular value by the name of the parameter
Edit1.Text := ADODataSet . Parameters. ParamValues('ParamOne");
A list of parameters can be updated using the methods
function AddParameter: TParameter;
and
function CreateParameter (const Name: WideString; DataType: TDataType;
Direction: TParameterDirection; Size: Integer; Value: OleVariant):
TParameter;
The first method simply creates a new Parameter object and adds it to the list.Chapter 7: Using ADO with Delphi 279
—_>
The next step is to specify all the properties of the new parameter:
var NewParam: TParameter;
NewParam := ADODataSet Parameters .AddParameter;
NewParam.Name := 'ParamtTwo';
NewParam.DataType := ftInteger;
NewParam.Direction := pdInput;
NewParam.Value := 0;
The createParameter method creates a new parameter and sets its properties:
CG name — the name of the parameter
OG bataType — the data type of the parameter corresponding to the the field type
of the database table (TFieldType)
© bDirection — a parameter type that is an addition to the standard types
dUnknown, pdInput, pdoutput, pdInputoutput; TParameterDirection also has
the additional type pdReturnvalue value, which determines any returned value
OG size — the maximum size of the parameter value
G value — the value of the parameter
When you work with parameters, it is more convenient to call them using names,
and not the absolute indexes from the list. You can do this using the method
function ParamByName (const Value: WideString): TParameter;
The list of parameters must always match a request or procedure. To refresh the
list, use the property
procedure Refresh;
You can also create a list of parameters for a request that is not linked to
a Parameter object. Use the method:
function ParseSQL(SQL: String; DoCreate: Boolean): string;
where Docreate defines whether existing parameters should be deleted prior to
parsing the request.
The TParameter Class
The TParameter class contains an individual parameter.
The name of the parameter is set by the property
property Name: Widestring;280 __ Part Il: Data Access Technologies
>_>
The type of data which must express the value of the parameter is specified by the
property
TDataType = TFieldType;
property DataType: TDataType;
Finally, since parameters interact with fields of database tables, the parameter data
type must coincide with the field data type. The size of the parameter depends on
the data type:
property Size: Integer;
which can be modified for a string and character data type and the like.
The value of the parameter is contained in the property
property Value: OleVariant;
And the property
type
TParameterAttribute = (paSigned, paNullable, paLong);
TParameterAttributes = set of TParameterAttribute;
property Attributes: TParameterAttributes;
controls the values assigned to parameters:
OC pasignea — the value can be a character value.
© paNullable — the value can be empty.
G patong — the value can contain BLOB type data.
The following property sets the direction of a parameter:
type TParameterDirection = (pdUnknown, pdInput, pdOutput,
pdInputoutput, pdReturnValue) ;
property Direction: TParameterDirection;
where:
OG pdtnknown — an unknown parameter — the data store must try to determine
the type independently.
OG patnput — an input parameter used in requests and stored procedures.
CO pdoutput — an output parameter used in stored procedures.Chapter 7: Using ADO with Delphi 281
—_
G patnputoutput — an input/output parameter used in stored procedures.
OC pdreturnValue — a parameter for returning any value.
If a parameter must pass large binary arrays (images or files, for example), the
value for this parameter can be loaded using the methods
procedure LoadFromFile(const FileName: String; DataType: TDataType) ;
and
procedure LoadFromStream (Stream: TStream; DataType: TDataType) ;
The TADODataSet Component
The TADODataset component is used for representing datasets from ADO data stores.
This component is easy-to-use, with just a few properties and methods of its own.
It mostly uses the functions of its direct ancestor, the TcustomADODataset class.
This is the only ADO component that contains a dataset with published properties
that enable it to manage ADO commands. The properties in question (see above) are
property ConmandText: Widestring;
and
property ConmandType: TCommandType;
As a result, the component is a flexible tool that enables you (depending on the
type and text of the command) to receive data from tables, SQL requests, stored
procedures, files, and so on. For example, you can select the necessary value of the
property CommandType = cmdText and enter the text of an SQL request in the
CommandText property from the editor:
ADODataSet .CommandType = cmdText;
ADODataSet. CommandText := Memol.Lines.Text;
and the SQL request is ready to execute.
Only the Data Manipulation Language can be used for SQL queries (use only SELECT).
The connectionstring and Connection properties are used for establishing con-
nection with databases.
A dataset can be opened and closed by the Active property or the open and close
methods.282 ‘Part Il: Data Access Technologies
>_>
This component can be used in applications just as all other usual data access
components — by linking the dataset that it contains to visual data-aware compo-
nents through the TDataSource component.
The TADOTable Component
The TADOTable component allows Delphi applications to use database tables
through OLE DB providers. The functional capabilities and use of this component
are similar to the standard Table component (see Chapter 5, "The Architecture of
Database Applications").
As you already know, the component is based on the ADO command, however,
the properties of the command are set in advance and cannot be modified.
The name of the required database table is set by the property
property TableName: WideString;
Other properties and methods of the component are provided by indexing (which
any other query component lacks).
Since not all ADO providers support direct handling of database tables, an SQL
request is required to get access to them. If the property:
property TableDirect: Boolean;
has the True value, you can directly access a database table. Otherwise, the com-
ponent will generate the appropriate query.
The property
property ReadOnly: Boolean;
allows you to activate or disable the read-only mode for the table.
The TADOQuery Component
The TADOQuery component allows applications that use ADO to run SQL queries.
Its functionality is similar to the standard query component (see Chapter 5, “The
Architecture of Database Applications").
The text of the query is specified by the property
property SQL: TStrings;
The parameters of the query are defined by the following property:
property Parameters: TParameters;Chapter 7: Using ADO with Delphi 283
—_
If the query is to return a dataset, use the following property to open it:
property Active: Boolean;
or the method
procedure Open;
Otherwise, you can use the method
function ExecSQL: Integer; ExecSQL
The number of records processed by the query is returned by the property:
property RowsAffected: Integer;
The TADOStoredProc Component
The TADOStoredProc component enables Delphi applications that connect to data-
bases through ADO to use stored procedures. This component is similar to the
standard stored procedure component (see Chapter 5, “The Architecture of Database
Applications").
The name of the stored procedure is specified by the property
property ProcedureName: WideString;
The following property defines the input/output parameters for the stored proce-
dure
property Parameters: TParameters;
If the procedure is to be used many times without changes, it makes sense to pre-
pare its execution on the server in advance. This can be done by setting the fol-
lowing property to True:
property Prepared: Boolean;
ADO Commands
The ADO command, which we have already devoted so much attention to in this
chapter, corresponds to the TADOCommand component in Delphi VCL. The methods
of this component in many ways coincide with the tTcustomADoDataset class,
although this class is not an ancestor of the component. It is used to execute
commands that do not return datasets.284 __— Part Il: Data Access Technologies
>_>
TADOCommand
Fig. 7.8. The TADOCommand component hierarchy
As the TADOCommand component doesn't require dataset handling, its direct ancestor
is the TCcomponent class. It has simply gained the mechanism for connecting to da-
tabases through ADO and means of implementing commands.
A command passes to an ADO data store through either its own connection or
the TADOConnect ion component, just like to other ADO components.
The text of the command must be contained in the property
property CommandText: WideString;
However, you can also specify a command using another technique. A direct
pointer to the required ADO command can be defined by the property
property CommandObject: _Command;
The type of command is set by the property
type TCommandType = (cmdUnknown, cmdText, cmdTable, cmdStoredProc,
cmdFile, cmdTableDirect) ;
property CommandType: TCommandType;
Since the TcommandType type is also used in the TcustomaDoDataset class, where it is
necessary to display all possible types of command in relation to the TADOCommand
component, this type is redundant. Here, you cannot set the values cmdTable, cmdFile,
cmdTableDirect, and the cmdStoredProc type can be assigned only to stored procedures
which do not retum datasets.
If the command must contain the text of an SQL query, the conmandType property
must have the value cmaText.
To call a stored procedure, the cmdstoredProc type must be specified, and the
name of the procedure must be entered in the commandText property.
If parameters must be specified for the command to execute, use the property
property Parameters: TParameters;Chapter 7: Using ADO with Delphi 285
>_>
Commands are executed with the Execute method:
function Execute: _RecordSet; overload;
function Execute (const Parameters: OleVariant): Recordset; overload;
function Execute (var RecordsAffected: Integer; var Parameters: OleVariant;
ExecuteOptions: TExecuteOptions = []): _RecordSet; overload;
The developer can use any of the above overload method notations.
The RecordsAffected parameter returns the number of records processed.
The Parameters parameter indicates the parameters of the command.
The ExecuteOptions parameter specifies the conditions for executing the com-
mand:
TExecuteOption = (eoAsyncExecute, eoAsyncFetch,
eoAsyncFetchNonBlocking, eoExecuteNoRecords) +
‘TExecuteOptions = set of TExecutedption;
where:
© eoAsyncExecute — the command is executed asynchronously.
GO) eoAsyncFetch — data are fetched asynchronously.
© eoAsyncFetchNonBlocking — data are fetched asynchronously without blocking
the stream.
CO ecokxecuteNoRecords — if the command returns a dataset, the records are not
passed to the component.
The eokxecuteNoRecords option is recommended for handling the TADoconnection
component.
The following method is used to abort a command:
procedure Cancel;
The current state of a command can be defined by the following property:
type
TObjectstate = (stClosed, stOpen, stConnecting, stExecuting,
stFetching) ;
TObjectStates = set of TObjectState;
property States: TObjectStates;286 __— Part Il: Data Access Technologies
ADO Error Object
We have encountered the ADO error object in this chapter quite often while dis-
cussing various ADO components. ADO error objects contain information on er-
rors that occur during the execution of any ADO object.
Delphi doesn't provide any specific type for the error object, but developers can
use the methods of the Error interface, which provides many methods for other
ADO objects. For example, the type
TRecordsetEvent = procedure (DataSet: TCustomADODataSet; const Error:
Error; var EventStatus: TEventStatus) of object;
which is used for the event handler called after a dataset has been refreshed, con-
tains the Error parameter that supplies us with the sought-after link.
Let's examine some useful properties of the ADO error object.
The property
property Description: WideString read Get_Description;
returns the error description passed from the object in which the error occurred.
The property
property SQLState: WideString read Get_soLState;
contains the text of the command that caused this error.
The property
property NativeError: Integer read Get_NativeError;
returns the code of the error, passed from the object in which the error occurred.
Developing a Sample ADO Application
Now let's try to put this information on using ADO in Delphi into practice. As an
example, we'll create a simple application, ADO Demo, that can access a couple
of database tables, save changes with the help of batch operations, sort records,
and place filters on selected records.
Let's use the dBase files stored in the ..\Program Files\Common Files\,
Borland Shared\Data demo Delphi database as the data source. We will select the
INDUSTRY and MASTER tables to use in the new application. These tables are
linked by a foreign key in the columns IND_CODE and INDUSTRY.Chapter 7: Using ADO with Delphi
287
——.
lol xt
‘PogianFles\Conmen FiewGotand Shared _..| | ll BF Fea nee
[SYMBOL[CO_NAME [EXCHANGE[CUR_PRICE[YRL_HIG]=
DplusMo Us. weD NYSE 15625, IE
[JwHop HOPEHOSPITALS NONE 62875 Ba,
eo [cor comucare NYSE 16826) 123
7000 Hotel HoteV/Garing [NMED NewMED INC NYSE 22.28
[]vck MONITOR CARE NvSE ae)
3573Comp —_Conpuler Hanae [EJNaHC (NA HEALTHCARE NYSE 6325
2510 Tel Telsconmunieatons || JWCR WEST CARE INC NONE mis
[[J2MED BOSTON MED. CARE NYSE 2025, 284
[[JHS1 HEALTH SYSTEMS INC NYSE ieee
WEN WELLESLEY ENTERPAI NYSE 13|__14
[pie —Jnewneacta cane [Nowe | ——7aas| —aas
sila AY;
Fig.7.9. The main window of the ADO Demo application
The INDUSTRY table can be edited; it is contained in the tblIndustry compo-
nent of the TADOTable type and is displayed in the left TDBGrid component. As for
the MASTER table, it is contained in the tbiMaster component designed for
viewing only. These two components are related to each other by a one-to-many
dependence through the MasterSource and MasterFields properties.
Listing 7.1. The implementation Section of the uMain Unit
of the ADO Demo Application
implementation
uses IniFiles, Filectrl;
const sIniFileName: string
sEmptyDefDB: String =
"ADODemo. ini";
"Database path is
empty";288 _ Part Il: Data Access Technologies
>_>
sEmptyFilter: String = 'Records for filter are not selected";
{SR *.dfm)
procedure TfimMain.FormShow (Sender: Tobject);
begin
with TIniFile.Create(sIniFileName) do
try
DefDBStr := ReadString('DefDB', 'DefDBStr', '');
edDefDB.Text := DefDBStr;
finally
Free;
end;
SetLength (Bookmarks, 0);
end;
procedure TfnMain.FormClose (Sender: TObject; var Action:
TCloseAction) ;
begin
with TIniFile.Create(sIniFileName) do
try
WriteString("DefDB', 'DefDBStr', edDefDB.Text) ;
finally
Free;
end;
end;
procedure TfmMain.sbDefDBClick (Sender: TObject);
begin
if SelectDirectory(DefDBStr, [], 0)
then edDefDB.Text := DefDBStr;
end;
procedure TinMain.tbConnectClick (Sender: TObject) ;
begin
ADOConn.Close;
ADOConn.DefaultDatabase := '';
if DefDBStr = '' then
begin
MessageDlg(sEmptyDefDB, mtError, [mbOK], 0);
Abort;Chapter 7: Using ADO with Delphi 289
—_
end
else
begin
ADOConn. DefaultDatabase
ADOConn. Open.
end;
end;
DefDBStr;
procedure TfmMain.tbSaveClick(Sender: TObject);
begin
tbliIndustry.UpdateBatch ();
end;
procedure TfinMain. tbFilterClick (Sender: TObject);
var i: Integer;
begin
if dbgIndustry.SelectedRows.Count > 0 then
begin
SetLength (Bookmarks, dbgIndustry.SelectedRows.Count)
= 0 to dbgIndustry.SelectedRows.Count — 1 do
for i
begin
Bookmarks [i].VType := vtPointer;
Bookmarks [i].VPointer := pointer (dbgIndustry.SelectedRows [i]);
end;
tblindustry. FilterOnBookmarks (Bookmarks) ;
end
else
MessageDlg(sEmptyFilter, mtWarning, [mbOK], 0)
end;
procedure TfmMain.tbUnFilterClick (Sender: TObject);
begin
tblIndustry.Filtered := False;
dbgIndustry. SelectedRows.Clear;
end;
procedure TfmMain.dbgIndustryTitleClick(Column: TColumn) ;
begin
if tblIndustry.Active then
if (Pos (Column.FieldName, tblIndustry.Sort) > 0)
and(Pos("ASC', tblindustry.Sort) > 0)290 __ Part Il: Data Access Technologies
>_>
then tblIndustry.Sort
else tblIndustry.sort :
end;
Column.FieldName + ' DESC’
Column.FieldName + ' ASC’;
procedure TfmMain.ADOConnAfterConnect (Sender: TObject);
var i: Integer;
begin
for i:
0 to adoConn.DataSetCount — 1 do
ADOConn. DataSets [i] .Open;
end;
procedure TfmMain.ADOConnBeforeDisconnect (Sender: TObject);
var i: Integer;
begin
for i := 0 to adoConn.DataSetCount — 1 do
ADOConn. DataSets [i] Close;
end;
end.
Connecting to the Data Source
Use the TADoconnection component to connect the application to the data source,
and then set the connection options by pressing the ConnectionString property
button in the Object Inspector window.
The next step is to move to the Data Link Properties editor and select Microsoft
OLE DB Provider for OLE DB Drivers (see Fig. 7.3). As a rule, this editor is part
of the operating system, unless you have gone to the trouble of removing it. Then,
on the Connection page (see Fig. 7.4), select the Use data source name radio but-
ton, and choose the dBase files from the list. Now the application is fully prepared
to connect to the ODBC provider.
Let's look at other properties of the TADoConnect ion component.
The LoginPrompt property must be set to False in order to disable the display of a
user authorization dialog, which is unnecessary for dBase files.
Leave the DefaultDatabase property empty for the time being. We will use it later
to indicate the path to the database files, using elements of the application user
interface.Chapter 7: Using ADO with Delphi 291
_>
The cursorLocation property is set to the c1lUseClient value to ensure the use of
dataset cursors on the client side.
The default value of the connectoptions property is coconnectUnspecified, which
means that all commands will execute synchronously. This means that the connec-
tion will wait for a response to every command.
Set the Mode property to the cmshareDenyNone value to prevent other connections
from setting any restrictions, as we do not plan on giving multiple-users access to
the data source in this case.
Once you have started your application, you need to indicate the location of the
data store to open the connection. Use the corresponding button and the single-
line editor in the Control bar. After the path is selected, its value is saved to the
DefpBstr variable and to the eaDefpB editor. This variable is used for establishing
the connection. Press the tbconnect button to open the connection. The appropri-
ate event handler checks the state of the Defpastr variable and assigns the proper
value to the DefaultDatabase property of the TADOConnect ion component.
The DefaultDatabase property will work because the path to the data store was not
specified in the process of setting the options for the connection. Otherwise, the value of
this property would be overwritten by the settings of the connect ionst ring property.
The application accesses the ADO dataset through the ADoconnAfterconnect event
handler, which is called as soon as the connection has been established. Similarly,
the datasets are closed before disconnecting with the aDoconnBeforeDisconnect
event handler.
The current value of the path to the data store is saved to the DemoADO.ini file
and uploaded when the application is opened.
Batch Operations
The tblindustry component is designed for performing batch operations. This is
why the Loci Type property has the value 1tBatchOptimistic. The CursorLocation
property is set to clUseClient to enable you to use the client dataset. The type of
cursor (the CursorType property) must be set to ctStatic.
All changes can be saved to the data store using the updateBatch method in the
event handler for the tbsave button.292‘ Part Il: Data Access Technologies
>_>
Filtering
The records in the tblIndustry dataset are filtered by the FilterOnBookmark
method. The user should select the required records in the dbgIndustry compo-
nent (which operates in the dgMultiselect mode). Then, when the tbFilter but-
ton is pressed, the bookmarks specified in the selectedRows property of the
dbgIndustry component are passed to the Bookmarks array of the TVarRec type,
which in turn is passed as a parameter of the FilterOnBookmark method for filter-
ing.
The Bookmarks array serves here as an intermediate link for converting
the dbgIndustry component's boolmark type into a parameter of the
FilterOnBookmark method.
Sorting
Sorting is also applied to the tbiIndustry dataset. When you click the heading of
a column in the dbgIndustry component, the dbgIndustryTitleClick event han-
dler is called. Depending on the current state of the tbiIndustry.sort sorting
property (which indicates the fields to be sorted, as well as the sorting order), this
event handler gives a new value to the sort property.
Summary
The ADO technology provides you with a universal strategy for accessing hetero-
geneous data sources. As the ADO functions are based on OLE DB and COM in-
terfaces, applications don't require any additional libraries. All they need is for
ADO to be installed on the system.
The TADOConnection component provides connections to data sources through
OLE DB providers. The TADoDataSet, TADOTable, TADOQuery, and TADOStoredProc
components enable you to use recordsets in applications. The properties and
methods of these components allow you to develop full-fledged applications.
The TADOCommand component contains ADO text commands.
Besides the standard capabilities for handling data, required ADO interfaces and
objects can be directly accessed from components as well.PART Ill
> <<
DISTRIBUTED
DATABASE
APPLICATIONS
Chapter 8: DataSnap Technology. Remote
Access Mechanisms
Chapter 9: An Application Server
Chapter 10: A Client of a Multi-Tier Distributed
ApplicationChapter 8
DataSnap Technology.
Remote Access
Mechanisms296 __ Part Ill: Distributed Database Applications
>_>
tional database applications that access databases on local machines or on
a local network. However, we have not examined situations where we need an
application that is equally well prepared for dealing both with computers in
a local network and with multiple remote machines.
I n the chapters of the previous part, we covered issues of developing tradi-
Obviously, in this case the access model for data should be widened, since with
a large number of remote machines, traditional schemas for creating database ap-
plications are ineffective.
This chapter discusses the model of a distributed database application, called
multi-tiered, and specifically, its simplest version — a three-tier distributed appli-
cation. The parts of this application are:
© Database server
OG Application server (middleware)
O Client-side application
All three parts are united by the transaction mechanism (the transport level) and
the mechanism of processing data (the business logic level).
In generalizing a three-tier model, it should be noted that increasing the number
of tiers doesn't affect the database server or the client-side part of the application.
All additional tiers actually only complicate the middleware, which can include,
for example, a transaction server, a secure server, etc.
All Delphi components and objects that enable the development of multi-tier ap-
plications are collectively referred to as DataSnap.
In earlier versions of Delphi (Delphi 4 and 5), these components were called MIDAS
(Multi-tier Distributed Applications Services).
Most of the components discussed in the following chapters are available from the
special DataSnap page of the Delphi Component palette. However, we will need a
number of extra components for designing multi-tier applications, and these addi-
tional components are also given due attention here.
This chapter covers the following issues:
O The structure of multi-tier applications
© The DataSnap strategy for accessing remote databases
GF Remote data modulesChapter 8: DataSnap Technology. Remote Access Mechanisms 297
© Provider components
G Transaction components of DataSnap remote connections
© Extra components — connection brokers
Structure of a Delphi Multi-Tier
Application
The multi-tier architecture of database applications came into being because of the
necessity of processing requests from multiple remote clients on the server. On the
face of it, this task can be adequately solved by using traditional client-server ap-
plications, the key elements of which were covered in the previous section. How-
ever, with a large number of clients, the entire processing burden is placed on the
database server, which has rather meager resources for implementing sophisticated
business logic (stored procedures, triggers, views, etc.). Developers are forced to
significantly complicate the program code of client-side software, which is ex-
tremely undesirable when multiple remote client machines are accessing the same
server. With more complex client-side software, the probability of errors is in-
creased, and service becomes more difficult.
The multi-tier architecture of database applications was introduced in order to
correct the above defects. A multi-tier database application (Fig. 8.1) consists of:
G "Thin" client applications that provide only transmission, presentation, and ed-
iting of services, as well as very basic data processing
G One or more middle software tiers (an application server), which can function
either on a single machine or be distributed in a local network
CA database server (Oracle, Sybase, MS SQL, InterBase, etc.) that supports the
functioning of data in a database and processes requests
Thus, within this architecture, "thin" clients are very simple applications that pro-
vide only data transmission services, local caching, presentation services by means
of a user interface, editing, and very basic data processing.
Client applications never access a database server directly; they do it through the
middleware. The middleware can be a single intermediary layer (in the simplest
three-tier model) or a more complex structure.298 _ Part Ill: Distributed Database Applications
>_>
Thin client
Database
Fig. 8.1. The multi-tier architecture of database applications
Middleware receives requests from clients, processes them according to pro-
grammed rules of business logic, converts them to a format convenient for the da-
tabase server if necessary, and sends them to the server.
Database servers execute requests received and send the results to the application
server, which addresses the data to the clients.
A simpler three-tier model contains the following elements:
0 "Thin" clients © An application server
CA database server
Our discussion here will focus on the three-tier model. In the Delphi development en-
vironment, there is a set of tools and components for building client and middleware
software. The server section is covered in Chapter 9, "An Application Server," while
Chapter 10,"A Client of a Multi-Tier Distributed Application,” covers issues of designing
client software. An application server interacts with a database server using one of the
technologies for accessing data implemented by Delphi (see Part I/, "Data Access Tech-
nologies"). These are ADO, BDE, InterBase Express, and dbExpress. The developer
can select the most suitable technology based on the nature of the task at hand and the
parameters of the database server.
For an application server to interact with clients, it must be developed on the basis
of one of the following standards that support distributed access:
© Automation 0 MTS
© WEB © SOAP
G CORBAChapter 8: DataSnap Technology. Remote Access Mechanisms 299
Remote client applications are created using a special set of components collec-
tively called DataSnap. These components contain standard transports (DCOM,
HTTP, CORBA, and sockets) and establish a connection between a client applica-
tion and the application server. Additionally, DataSnap components allow a client
to access the functions of the application server through the interface Tappserver
(see Chapter 9,"An Application Server").
An important role in developing client applications is played by the compo-
nent that contains client datasets. This also depends on the data access tech-
nology, and is examined in Chapter 10, "A Client of a Multi-Tier Distributed
Application.”
Along with the benefits listed above, an intermediary level — an application
server — also provides several additional bonuses that can be very useful as far as
increased reliability and enhanced performance are concerned.
As client computers are often rather weak machines, the use of sophisticated busi-
ness logic on the server side helps to considerably speed up the overall system
performance. This is not just the work of more powerful hardware, but also thanks
to the optimization of executing similar user requests. For example, if the load on
the database server is excessive, the application server can execute requests from
users on its own (queue these requests or cancel them), without putting an addi-
tional load on the database server.
Using an application server enhances your security system, as you can organize
user authorization as well as any other security measures without direct access to
data.
Additionally, you can easily use protected data communication channels —
HTTPS, for instance.
Three-Tier Delphi Applications
Let's take a closer look at the parts of a three-tier distributed Delphi application.
As mentioned above, in Delphi, it makes sense to develop both the client part of a
three-tier application and application server middleware.
The parts of three-tier applications are developed using DataSnap components,
along with a number of other special components that are mainly responsible for
client operation. Data is accessed using one of the data access technologies imple-
mented in Delphi (see Part II, “Data Access Technologies").300 __— Part Ill: Distributed Database Applications
>_>
Client's dataset
Remote data module
‘Application server
| Connection
component
Thin client
Fig. 8.2. Diagram of a multi-tier distributed application
It makes sense to develop a three-tier application using a set of projects in the devel-
‘opment environment rather than just a single project. This is what the Project Manager
utility is used for (View\Project Manager).
Data is transmitted between the application server and clients by the Iappserver
interface provided by the application server. This interface is used by both server-
side provider components — TDataSetProvider — and client-side TClientDataset
components.
Let's now examine the parts of a three-tier application in more depth.
Application Servers
An application server contains the bulk of business logic of a distributed applica-
tion and enables clients to access a database.
From the point of view of the developer, by far the most important part of any
application server is its remote data module. Let's try to find out why.
First, depending on the implementation, a remote data module contains a ready
remote server, which only needs to be registered and have some parameters set.
Delphi comes with five types of remote data modules. To create them, use the
Multi-tier, WebSnap, and WebServices pages of the Delphi Repository (Fig. 8.3).
© Remote Data Module — a remote data module that contains the Automation
server. It is used for establishing connections via DCOM, HTTP and sockets.
For more detail, see Chapter 9, "An Application Server."Chapter 8: DataSnap Technology. Remote Access Mechanisms _ 301
© Transactional Data Module — a remote data module that contains a Microsoft
Transaction Server (MTS).
© CORBA Data Module — a remote data module that contains a CORBA
server.
OG Soap Server Data Module — a remote data module that contains a SOAP server
(Simple Object Access Protocol).
GO WebSnap Data Module — a remote data module that uses web services and
a web browser as a server.
Second, a remote data module enables interaction with clients. The module sup-
plies a client application with the methods of the special tappserver interface or
its descendant. The methods used by this interface help to organize the process of
transmitting and receiving data packets for a client application.
Third, like a traditional data module (see Chapter 5, "The Architecture of Database
Applications"), a remote data module is a platform for the placement of non-visual
data access components and provider components. All connection and trans-
action components, as well as components that contain datasets placed
in a remote data module, provide a connection between the three-tier application
and the database server. These can be sets of components for various data access
technologies.
bomen | WebServices | Corba
| Proiectt | Forms | Dialogs
COREADS § © CORBADbject © RemoteData Transactional
Module Module Dato Module
Cancel Help
Fig. 8.3. Selecting a remote data module in the Delphi Repository302 ‘Part Ill: Distributed Database Applications
>_>
Besides the remote data module, another integral part of any application server is
the TDataSetProvider provider components. Each component that contains a da-
taset designed to be passed to the client must be associated with a provider com-
ponent in the remote data module.
To do this, the remote data module must contain the required number of
TDataSetProvider components. These components pass data packets to the client
application, or more precisely, to the TclientDataset components. They also pro-
vide access to the methods of their own IProviderSupport interface. Using the
methods of this interface, you can manage packets of sent data at a low-level.
Usually, the developer does not have to do this. You simply need to know that all
components that handle data — both on the client side and on the server side —
use this interface. However, if you intend to create your own version of DataSnap,
you will find the description of the interface very useful (see Chapter 9, “An Appli-
cation Server").
Client Applications
A client application in a three-tier model should have only the minimum required
set of functions, and delegate the majority of data processing operations to the ap-
plication server.
Above all, a remote client application must provide a connection to the application
server. DataSnap connection components are used:
© tpcomconnection — uses DCOM
G TsocketConnection — uses Windows sockets
© tTwebcomnection — uses HTTP
© tcorBaconnection — uses a connection within the CORBA architecture
The TSOAPConnection component is discussed separately.
DataSnap connection components use the TAppserver interface, which utilizes the
server-side provider components and client-side TclientDataset components for
passing data packets.Chapter 8: DataSnap Technology. Remote Access Mechanisms _ 303
Data are handled using TclientDataset components that operate in data caching
mode.
Data are presented and a user interface is created in a client application by using
the standard controls from the Data Controls page of the Component
palette.
For more information on how to design client applications for multi-tier database
applications, refer to Chapter 10, "A Client of a Multi-Tier Distributed Application.”
DataSnap Remote Access Mechanism
For data packets to be passed between a provider component and a client dataset
(Fig. 8.2), a transport link, which provides physical transmission of data, must be
established from the client to the server. This can be accomplished by using a vari-
ety of transport protocols supported by the operating system. Different types of
connections that allow you to configure the communication channel and begin
passing and receiving information are contained in several DataSnap components.
To create a connection by using a specific transport protocol on the client side,
the developer simply needs to place the appropriate component in a form and set a
number of properties correctly. This component can interact with the remote data
module of the same, which is part of an application server.
Below we'll look at transport protocols for connection components that use
DCOM technology, TCP/IP sockets, HTTP, and CORBA.
The TDCOMConnection Component
The tpcoMconnection component implements data transmission on the basis of the
Distributed COM technology, and is used primarily for establishing connections
within the local network.
To configure a DCOM connection, you first need to specify the name of the ma-
chine where the application server is installed. For TpcoMconnection components,
it must be a registered Automation server. The name of the computer is specified
by the property
property ComputerName: string
If it is correct, in the list of the property
property ServerName: string;304 __— Part Ill: Distributed Database Applications
>_>
you can select one of the servers available in the Object Inspector.
When a server is selected, the property
property ServerGUID: string;
is filled automatically with the global identifier of the registered Automation server.
For a client to connect with the application server successfully, both properties
must be given in the required order. Only giving the server name or the server
GUID will not guarantee access to the remote COM object.
A connection is opened and closed by the property
property Connected: Boolean;
or the methods
procedure Open;
procedure Close;
Data transmission between a client and a server is organized by the IappServer
interface of the TDcoMconnection component:
property AppServer: Variant;
which can also be accessed using the method
function GetServer: IAppServer; override;
The property
property ObjectBroker: TCustomObjectBroker;
lets you use a TSimpleObjectBroker component instance to obtain the list of avail-
able servers at run time.
The event handlers for the tpcoMconnect ion component are listed in Table 8.1.
Table 8.1. The Event Handlers for the TDCOMConnection Component
Declaration
property AfterConnect: Called after a connection is established.
TNotifyEvent;
property AfterDisconnect: Called after a connection is closed.
TNotifyEvent;
property BeforeConnect: Called before establishing a connection.
TNotifyEvent;
continuesChapter 8: DataSnap Technology. Remote Access Mechanisms _ 305
Table 8.1 Continued
Declaration
property BeforeDisconnect: Called before closing a connection.
TNotifyEvent;
type TgetUsernameEvent = Called immediately prior to displaying the logon
procedure (Sender: TObject; dialog for authorization of a remote user. This is
var Username: string) of achieved if the LoginPrampt property is set to
object; True. The Username parameter can contain the
property OnGetUsername: default name of a user, which will then appear in
TGetUsernameEvent; this dialog.
type TLoginEvent = Called after a connection is established if the
procedure (Sender:TObject; LoginPrompt property is set to True. The
Username, Password: string) Username and Password parameters contain
of object; the user name and password entered at authori-
property OnLogin: zation.
TLoginEvent;
The TSocketConnection Component
The TsocketConnection component provides a connection between a client and an
application server using TCP/IP sockets. For a connection to open successfully,
the server part must be equipped with a socket server (a ScktSrvr.exe application).
For successful connection, the property
property Host: String;
must contain the name of the server computer. Additionally, the property
property Address: String;
must contain the IP address of the server.
To open a connection, both these properties must be specified.
The property
property Port: Integer;
sets the name of the port in use. The default port is 211, but the developer is free
to change the port, for example, to allow it to be used by different categories of
users, or to create a protected communication channel.
If the name of the computer has been correctly specified in the property list
property ServerName: string;306 _— Part Ill: Distributed Database Applications
>_>
the Object Inspector will show a list of the available Automation servers. Once you
have selected the server, the property
property ServerGUID: string;
which contains the curp of the registered server, is set automatically, though it can
also be set manually.
Ports Connections
Pott Bi
i=] User |
22 Pat
Listen on Ports [217 2
any values of Post re asscisted by corvention ith a
paticlr sevice such as tp et ip Pats tho I ofthe
annecon on wich the sve eters or een request:
ce —
Thtead Cache Size: [10 a
“Thiead Cache Size is the maximum rumber of treads that can
be reused fornew clent connections.
ping
CSC
Inactive Timeout |B a
Inactive Timeout species the number of minutes a cent can
be inactive before being disconnected (0 indicates ininke)
Ievercept GUID
‘GUID: [[8C7369E5.CEBT-4EAF ADTE CASED O7203ED)
Intercept GUID isthe GUID fora data intercoptox COM abject.
See help forthe TSockelConnecton for deal
|
Fig. 8.4. The ScktSrvr.exe Socket Server
The method
function GetServerList: OleVariant; virtual;
returns a list of registered Automation servers.
Open and close a connection using the property
property Connected: Boolean;
or one of the corresponding methods:
procedure Open;
procedure Close;Chapter 8: DataSnap Technology. Remote Access Mechanisms _ 307
The channel of a TCP/IP socket can be encrypted using the property
property InterceptName: string;
which contains the program identifier of the COM object that provides encryp-
tion/decryption data services for a channel, and the property
property InterceptGUID: string;
contains the curp of this object.
This COM object intercepts data in the channel and processes it according to its
own program code. This can be encryption, compression, noise manipulation, etc.
The COM object that provides additional processing of data in the channel must be
created by the developer. The intercepting object should support the standard
IDataIntercept interface.
Irmeroept GUID
BUD: [{BC73ESEE-CERT-AEAF ADTE C4OSDO7203ED)
Intercept GUID isthe GUID for a data interceptor COM objec.
See help forthe TSocketConnection for details.
|
Fig. 8.5. Registering an Interceptor COM Object in a Socket Server
Of course, on the server side there must be a registered COM object that performs
the reverse operation. The socket server is used for this (Fig. 8.5). The Interceptor
GUID string in this page must contain the curp of the intercepting COM object.
The method
function GetInterceptorList: OleVariant; virtual;
returns a list of the intercepting objects registered on the server.
The TsocketConnection component provides the TAppServer interface that organ-
izes the process of data transmission between a client and a server:
property AppServer: Variant;
which can also be gotten by the method
function GetServer: IAppServer; override;
The property
property ObjectBroker: TCustomObjectBroker;308 _ Part Ill: Distributed Database Applications
>_>
lets you use a TSimpleObjectBroker instance to obtain the list of the available
servers at run time.
The event handlers of the TsocketConnection component are identical to the event
handlers of the component TDcoMConnect ion (see Table 8.1).
The TWebConnection Component
The TwWebConnection component connects a client to the server based on the
HTTP transport. To manage this component, the wininet.dll library must be regis-
tered on the client computer. This does not usually require any extra effort, as this
file is contained in the Windows system folder if Internet Explorer is installed on
the computer.
A server computer needs to have Internet Information Server 4.0 or higher
installed, or Netscape Enterprise 3.6 or higher. This software enables the
TwWebConnect.ion component to access the HTTPsrvr.dll dynamic library, which should
also be located on the server. For instance, if the HTTPsrvr.dll file is located in the
Scripts IIS 4.0 folder on the www.someserver.com web server, then the property
property URL: string;
should contain the following value:
http: //someserver.com/scripts/httpsrvr.dl1
If the URL is correct and the server is properly configured, then the following
property list:
property ServerName: string;
will show the names of all registered application servers in the Object Inspector.
One of the names must be contained in the serverName property.
Once the server name is set, its GUID automatically appears in the property
property ServerGUID: string;
The properties
property UserName: string;
and
property Password: strings
can, if necessary, contain the user name and password that will be used during
authorization.Chapter 8: DataSnap Technology. Remote Access Mechanisms 309
The property
property Proxy: string;
contains the name of the proxy server used.
You can include the name of an application in an HTTP message header using the
property
property Agent: string;
A connection is opened and closed using the property
property Connected: Boolean;
Similar operations are performed by the methods
procedure Open;
procedure Close;
The 1Appserver interface is accessed either through the property
property AppServer: Variant;
or the method
function GetServer: IAppServer; override;
The list of available application servers is returned by the method
function GetServerList: OleVariant; virtual;
The property
property ObjectBroker: TCustomObjectBroker;
lets you use a TSimpleObjectBroker Component instance to obtain the list of avail-
able servers at run time.
The event handlers for the Twebconnection component are identical to the event
handlers of the TDcomconnect ion component (see Table 8.1).
The TCORBAConnection Component
The TCoRBAConnect ion component enables a client application to access a CORBA
server. Here, you need just set a single property to configure the connection to
a server.
type TRepositoryId = type string;
property RepositoryId: TRepositoryTd;
where the names of a server and a remote data module are specified, separated by
a slash. For example, the property for the coRBAServer server and the CORBAModule
module will have the following value:
CORBAServer /CORBAModule310 ___ Part Ill: Distributed Database Applications
—_
Alternately, the address of a server can be presented in IDL (Inteface Definition
Language) notation:
IDL: CORBAServer/CORBAModule:1.0
The property
property HostName: string;
should contain the name of a server computer or its IP address. If no value has
been specified for this property, the TcoRBAConnect ion component connects to the
first server found, which has the parameters specified by the RepositoryId prop-
erty. The name of the CORBA server is contained in the property
property ObjectName: string;
A connection is opened and closed using the property
property Connected: Boolean;
Similar operations are executed by the methods
procedure Open;
procedure Close;
If, for some reason, a connection fails to open, using the following property can
prevent the application from hanging up:
property Cancelable: Boolean;
When this property is set to True, the connection is canceled after waiting for
more than one second for a connection to establish. You can also set this event
handler:
type TCancelEvent = procedure (Sender: TObject; var Cancel: Boolean;
var DialogMessage: string) ofobject;
property OnCancel: TCancelEvent;
which is called prior to canceling a connection.
Once a connection is open either using the property
property AppServer: Variant;
or the method
function GetServer: IAppServer; override;
the client has access to the TAppserver interface.
Event handlers for the TcorBAConnection component are inherited (except for the
OnCancel method described above) from the ancestor TCustomConnection class.
These event handlers are described in Table 8.1.Chapter 8: DataSnap Technology. Remote Access Mechanisms _ 311
Additional Components —
Connection Brokers
The DataSnap collection includes a set of additional components that are designed
to facilitate managing connections between remote clients and application servers.
Let's take a look at them.
The TSimpleObjectBroker Component
The TsimpleObjectBroker component contains a list of servers available to the
clients of the multi-tier distributed application. The list is created in the design
stage. If necessary (if the server shuts down or overloads, etc.), a connection
component of the client application can use one of the extra servers from the
TSimpleObjectBroker component list directly during runtime.
To enable this function, fill in the list of TSimpleobjectBroker Component servers
and specify a pointer to it in the ObjectBroker property of your connection com-
ponent (see above). Thus, if the connection is reestablished, the server name can
be acquired from the TSimpleObjectBroker component list.
The list of servers is defined by the property
property Servers: TServerCollection;
At design time, a list of servers is compiled by a special editor (Fig. 8.6), which is
called by pressing the property button in the Object Inspector window.
The servers property is a collection of TserverItem class objects. This class has
several properties which allow you to describe the key parameters of the server.
The property
property ComputerName: string;
defines the name of the machine on which the application server is working. Addi-
tionally, you can set the name of the server to be displayed in the server list:
property DisplayName: String;
By using the following property, you can make the server record accessible or in-
accessible for selection:
property Enabled: Boolean;
Once an attempt to use a record of this list for connecting has failed, the property
property HasFailed: Boolean;312 _ Part Ill: Distributed Database Applications
—_
is assigned the True value, and from this point on, the record is ignored.
The property
property Port: Integer;
contains the number of the port used for connecting to the server.
[irre xl
Galts
1- Local Application Server
Fig. 8.6. The server list editor of the TSimpleObjectBroker component
When the connection is being established, the values of these properties are sub-
stituted for relevant properties of the connection component:
DCcoMConnection.ComputerName :=
TSimpleObjectBroker (DCOMConnection.ObjectBroker) . Servers [0] .ComputerName;
Besides the list of servers, a TSimpleObjectBroker component has only a few addi-
tional properties and methods.
The method
function GetComputerForGUID (GUID: TGUID): string; override;
returns the name of the machine where the GUID server that is specified by the
parameter is registered.
The method
function GetComputerForProgID(const ProgID): string; override;
returns the name of the computer where the server with the name specified by the
ProgID parameter is registered.
The property
property LoadBalanced: Boolean;
selects a server from the list. If its value is True, a server is selected at random;
otherwise, the first available server name is suggested.Chapter 8: DataSnap Technology. Remote Access Mechanisms _ 313
The TLocalConnection Component
The TLocalconnection component is used locally for accessing existing provider
components.
The property
property Providers[const ProviderName: string]: TCustomProvider;
contains pointers to all the provider components that reside in the same data mod-
ule as a given TLocalConnection component. Items in this list are indexed by the
names of provider components.
The total number of provider components in the list is returned by the property
property ProviderCount: Integer;
In addition, by employing TLocalconnection, you can get access to the
TaAppServer interface locally. Use the property
property AppServer: IAppServer;
or the method
function GetServer: IAppServer; override;
The TSharedConnection Component
If the tappserver interface of a remote data module uses a method that returns
a pointer to an analogous interface of another remote data module, then the first
module is called the parent module, and the second is called the child module (see
Chapter 9, "An Application Server"). The TSharedConnection component is used to
connect a client application to a child data module of the application server.
The property
property ParentConnection: TDispatchConnection;
should contain a pointer to the connection component with the parent remote
data module of the application server. The name of a child remote data module is
specified by the property
property ChildName: string;
which should contain its name. If the interface of the parent remote data module
has been configured correctly, the property list in the Object Inspector shows the
names of all the child remote data modules.314 ___ Part Ill: Distributed Database Applications
—_
The 1Appserver interface of a child remote data module is returned by the property
property AppServer: Variant;
or the method
function GetServer: IAppServer; override;
The event handlers for the TsharedConnection component are inherited from the
TCustomConnect ion ancestor class (see Table 8.1).
The TConnectionBroker Component
The TconnectionBroker component provides centralized control of the connection
between client datasets and the application server. The corresponding Connect ionBroker
properties of all client datasets must point to a TConnectionBroker component in-
stance. Then, to change a connection (for example, to switch from HTTP to
TCP/IP sockets), you needn't change the value of the RemoteServer property of all
the TclientDataSet components — simply change the following property:
property Connection: TCustomRemoteServer;
The IAppServer interface is accessed by the property
property AppServer: Variant;
or the method
function GetServer: IAppServer; override;
The Tconnect ionBroker event handlers fully correspond to Table 8.1.
Summary
Multi-tier distributed applications provide effective interaction between a large
number of remote "thin" clients and database servers with the help of middleware.
Among multi-tier applications, the most widely-used model is the three-tier model,
where the middleware consists of just one application server.
To create three-tier distributed applications in Delphi, DataSnap components and
remote data modules are used. All these tools are implemented for various trans-
port protocols.
Also, three-tier distributed applications use TbataSetProvider and TClientDataset
components, which contain datasets on the client side.(Oi akele) i) ane)316 ___ Part Ill: Distributed Database Applications
—_
performance access to databases, since they use intermediary-level, spe-
cial-purpose software. In the most commonly used schema — the three-
tier application — this is the application server, which performs the following
functions:
M ulti-tier distributed applications provide remote clients with high-
© Authorizes users
G Receives and transfers requests from users and data packets
G Coordinates access of client requests to the database server by balancing the
processing load on the server
G Can contain a part of the business logic of a distributed application, thereby
enabling you to use "thin" clients
Delphi supports developing application servers on the basis of a range of different
technologies:
OG Web OG CORBA
© Automation 0 SOAP
OG MTS
This chapter covers the following issues:
Program building blocks of Delphi application servers.
The structure of an application server
Types of remote data modules
a
a
a
OG Creating and configuring remote data modules
G The role of provider components in the process of passing data to clients
CO tappserver interface methods
a
Registering application servers
Application Server Architecture
Thus, as stated above, an application server is the software of the intermediary tier
in a three-tier distributed application (Fig. 9.1). The key feature of such a server is
the remote data module. Delphi supports five types of remote data modules (which
will be discussed later).Chapter 9: An Application Server 317
—_
The issues surrounding the use of remote data modules that encapsulate the func-
tionality of automation servers will be discussed in more detail later in this chapter.
Other types of remote data modules will be discussed later in the book.
Each remote data module encapsulates the TAppserver interface, whose methods
are used to enable clients to access a database server (see Chapter 8, "DataSnap
Technology. Remote Access Mechanisms").
To Client
Interface IAppServer
Component TDataSetProvider
i Remote Data
Module
Interface IProviderSupport
I
Application
Servers DataSet pion
Database Connection Component
To Database
Server
. 9.1. The structure of an application server
In order to exchange data with a database server, a data module is equipped with
several data-access components (connection components and components that
encapsulate datasets).
To support interaction with clients, the remote data module must have the neces-
sary number of TDataSetProvider components, each of which must be associated
with a corresponding dataset.
a ted
Data exchange between an application server and its clients is provided by the
MIDAS.DLL library, which must be registered on the computer where the application
server resides.318 Part Ill: Di:
—_
tributed Database Applications
You can create a new application server by performing the following sequence of
rather simple operations:
le
7
Create a new project (specify its type as a traditional application through
File\New\ Application) and save it.
. Depending on the technology in use, select the appropriate type of remote data
module from the Delphi Repository. Remote data modules are listed on the
Multitier, WebSnap, and WebServices pages.
. Customize the options of the remote data module under creation.
. Place data access components in the remote data module and customize their
settings. The developer can choose one of several available sets of components
(see Part II, “Data Access Technologies"), depending on the database server used
and on the requirements of the application.
. Place the necessary number of TDataSetProvider components into the remote
data module and link them to the components that encapsulate datasets.
. If necessary, create additional methods for the IAppserver interface descendant
that is used by the remote data module. This operation is done by building
a new type library.
Compile the project and create the executable file for the application server.
Register the application server and, if need be, configure the extra software.
The entire remote access mechanism encapsulated in remote data modules and
provider components runs automatically, and does not require any additional pro-
gram code from developers.
The discussion that follows provides you with a simple example of how to imple-
ment in practice all the above steps for building application servers.
The /AppServer Interface
The tappServer interface is a core feature of the remote access mechanism that
the client application uses to get to application servers. It is through this interface
that client datasets communicate with the provider component on the application
server. Client datasets get an IAppServer instance from the connection component
in the client application (see Fig. 9.1).Chapter 9: An Application Server 319
—_
When remote data modules are being created, each module will correspond to a
newly created interface. This interface will be a descendant of the Iappserver in-
terface.
A developer can add a few custom methods to the interface, which, thanks to the
capabilities of the remote access mechanism employed by multi-tier applications,
will be available to the client application.
In client applications, the property
property AppServer: Variant;
is available both in remote-connection components and in client datasets.
By default, this interface is stateless. This means that all calls for it are independent
and not related to previous calls. This is why the IAppserver interface does not
have properties that store information on the state between calls.
As a rule, a developer has no need to directly use the methods of this interface, but
its significance for multi-tier applications is difficult to overestimate. If you'll be
doing detailed work with remote access mechanisms you'll need to use the inter-
face at some point, in any case.
The Iappserver interface methods are listed in Table 9.1.
Table 9.1. The Methods of the [AppServer Interface
Declaration Description
function Passes updates received from a client dataset to
AS_ApplyUpdates (const the provider component specified by the
ProviderName: WideString; ProviderName parameter.
Delta: OleVariant; MaxErrors:
Integer; out ErrorCount:
Integer; var OwnerData:
OleVariant): OleVariant;
safecall;
Changes are stored in the Delta parameter.
The MaxErrors parameter determines the maxi-
mum permissible number of errors that can be
ignored while saving data before aborting the
operation. The actual number of errors is returned
by the ErrorCount parameter.
The ownerData parameter contains additional
information exchanged between the client and the
server (for example, values that were assigned to
event handlers).
This function returns the data packet that con-
tains all the records which, for some reason, have
not been saved to the database.
continues320
—_
Table 9.1 Continued
Part Ill: Distributed Database Applications
Declaration
Description
function AS DataRequest (const
ProviderName: WideString;
Data: OleVariant) :
OleVariant; safecall;
procedure AS Execute (const
ProviderNare Widestring;
const CommandText :
WideString; var Params:
OleVariant; var OwnerData:
OleVariant); safecall;
function AS GetParams (const
ProviderName: WideString; var
OwnerData: OleVariant) :
OleVariant; safecall;
function AS _GetProviderNames:
OleVariant; safecall;
function AS_GetRecords (const
ProviderName: WideString,
Count: Integer; out RecsOut:
Integer; Options: Integer;
const CommandText:
Widestring; var Params:
OleVariant; var
OwnerData:OleVariant) :
OleVariant; safecall;
function AS RowRequest (const
ProviderName: WideString;
Row: OleVariant; RequestType:
Integer; var OwnerData:
OleVariant): OleVariant;
safecall;
Generates the OnDataRequest event for the pro-
vider specified in ProviderName.
Executes the query or stored procedure defined
by the CommandText parameter for the
ProviderName provider. Query or stored proce-
dure settings are stored in the Params parameter.
Passes the current values of the client dataset's
parameters to the ProviderName provider.
Returns a list of all remote data module providers
that are currently available.
Returns the data packet with records of the server
dataset that is linked to the provider component.
The CommandText parameter contains the name
of the table, the content of the request, or the
name of the stored procedure from which records
should be fetched. But this works only if the
poAllowCommandText option in the options
parameter was activated for the provider. Query
‘or stored procedure parameters are found in the
Params parameter.
The parameter sets the required number of rec-
ords, beginning with the current one, if its value is
positive. If the value is zero, only metadata are
returned; if the value is -1, all records are re-
tumed.
The Recsout argument retums the actual number
of passed records.
Retums the record of the dataset provided to the
ProviderName component and specified in the
Row parameter.
The RequestType parameter stores the value of
the T£etchOptions type.Chapter 9: An Application Server 321
<_
Most of the interface methods use the ProviderName and ownerData parameters.
The former specifies the name of the provider component, and the latter contains
a set of parameters passed for use in event handlers.
The observant reader has probably noticed that using the as_GetRecords method
implies saving information during the course of working with the interface, since
this method returns records starting with the current one, even though the
TappServer interface has the stateless type. This is why it is recommended that you
refresh the client dataset prior to using this method.
The type
TFetchOption = (foRecord, foBlobs, foDetails);
TFetchOptions = set of TFetchoption;
is used by the RequestType parameter of the As_RowRequest method:
© forecora returns the field values of the current record.
G foBlobs returns the values of BLOB fields of the current record.
G fobetails returns all detail records of the nested datasets for the current
record.
Remote Data Modules
Remote data modules are one of the core elements of application servers (see
Fig. 9.1) for three-tier distributed applications.
First, remote data modules, depending on their implementation, encapsulate the
remote server.
Second, remote data modules encapsulate the TappServer interface, thereby pro-
viding the functions of a server and enabling data exchange with remote clients.
Third, they perform the role of a traditional data module, which means that you
can use them as containers for various data-access components.
Depending on the technology used, Delphi lets you select any of five available re-
mote data modules.
OG Remote Data Module. The TRemoteDataModule class encapsulates the automa-
tion server.tributed Database Applications
322 Part Illl: Di:
—_
© Transactional Data Module. The TwTsDataModule class is a descendant of the
TRemoteDataModule class, and adds MTS functionality to the automation
server.
OO WebSnap Data Module. The TwebDataModule class creates an application server
that uses Internet technologies.
OG Soap Server Data Module. The TsoapDataModule class encapsulates a SOAP
server.
© CORBA Data Module. The TcoRBADataModule class is a descendant of the
TRemoteDataModule class and performs the functions of a CORBA server.
The following sections of this chapter will concentrate on the process of creating
an application server on the basis of the TremoteDataModule class. The other data
modules (except for CORBA data modules) will be the subjects of in-depth dis-
cussions later in the book.
Remote Data Modules for Automation Servers
TremoteDataModule remote data modules are created with the Delphi Repository
(through File\New\Other). The icon of the TRemoteDataModule class can be found
on the Multi-tier page. The process of building a new remote data module begins
with a dialog box (Fig. 9.2), in which you need to set three options.
Eire
CoGlass Name [SinpieADM
Instancing: [Single Instance a
Threading Modet [Free i
Cancel Help
Fig. 9.2. The wizard for creating TRemoteDataModule instances
The CoClass Name string must contain the name of the new data module, which
will also be used to name the new class built to support the newly created data
module.
The Instancing list lets you define the method of creating a data module:
Gi Internal — the data module only provides for the functioning of an internal
automation server.Chapter 9: An Application Server 323
—_
G Single Instance — an instance of the remote automation server is created for
each client connection in the framework of its own process.
G Multiple Instance — an instance of the remote automation server is created for
each client connection in the framework of one common process.
The Threading Model list sets the mechanism for processing client requests:
G Single — requests from clients are processed in strict order.
G Apartment — the data module processes one request at a time. Nevertheless, if
the DLL creates multiple COM objects to execute queries, then separate threads
can be created for these queries, and their processing is carried out in parallel.
G Free — the data module supports the creation of multiple threads for parallel
processing of requests.
G Both — this pattern is almost identical to the previous one (Free), except that all
processed requests are returned to the corresponding clients strictly one by one.
G Neutral — client requests can be passed to data modules using multiple con-
current threads. This pattern is utilized only for the COM+ technology.
When building a new remote data module, a special class — a descendant of the
TRemoteDataModule class — is created, along with a class factory based on the
TComponent Factory Class.
The TComponent Factory class is a class factory for Delphi components that encapsu-
late interfaces. It supports the IClassFactory interface.
Let's consider an example of creating the simpleRDM remote data module. Using
the Remote Data Module Wizard, specify Single Instance as the technique to cre-
ate your new module, and Free as the request processing pattern. Once all the set-
tings are made, press OK, and Delphi will automatically generate the source code
for your new data module (Listing 9.1).
Listing 9.1. The Source Code for a New Remote Data Module
and Its Class Factory
type
TSimpleRDM = class (TRemoteDataModule, TSimpleRDM)
private324 __ Part Ill: Distributed Database Applications
—_
{ Private declarations }
protected
class procedure UpdateRegistry (Register: Boolean; const ClassID,
ProgID: string); override;
public
{ Public declarations }
end;
implementation
{$R *.DEM}
class procedure TSimpleRDM.UpdateRegistry (Register: Boolean; const
ClassID, ProgID: string);
begin
if Register then
begin
inherited UpdateRegistry (Register, ClassID, ProgID);
EnableSocket Transport (ClassID) ;
EnableWebTransport (ClassID)
end else
begin
DisableSocketTransport (ClassID) ;
DisableWebTransport (ClassID) ;
inherited UpdateRegistry (Register, ClassID, ProgID);
end;
end;
initialization
‘TComponentFactory.Create (ComServer, TSimpleRDM,
Class_SimpleRDM, ciMultiInstance, tmApartment) ;
end.
Note that the settings that were initially specified for the new data module are also
used in the initialization section of the TComponentFactory class factory.
The TComponent Factory class factory is responsible for producing Delphi component
instances that support interfaces.Chapter 9: An Application Server 325
—_
The UpdateRegistry class method is created automatically, and used for the regis-
tration and invalidation of an automation server. If the Register argument is set to
True, registration is implemented; otherwise, it is canceled.
Since this method is used automatically, the developer should not invoke it manually.
Developing a new data module is always accompanied by creating its interface, a
descendant of the IAppserver interface. The source code of this interface is stored
in the type library of the application server project. The code of the IsimpleRDM
interface of the simplerDM remote data module is presented in Listing 9.2. For the
sake of convenience, all automatically included comments are removed from the
listing.
Listing 9.2. The New Type Library for the Application Server,
with the Source Code for the Remote Data Module Interface
LIBID_SimpleAppSrvr: TGUID = '{93577575-OF4F-43B5-9FBE-A5S745128D9A4 }
IID_ISimpleRDM: TGUID
CLASS_SimpleRDM: TGUID
type
" {E2CBEBCB-1950-4054-B823-62906306E840} "7
" {DB6A6463-SF61-485F-8F23-EC6622091908} ";
IsimpleRDM = interface;
IsimpleRDMDisp = dispinterface;
SimpleRDM = ISimpleRDM;
ISimpleRDM = interface (IAppServer)
[' {E2CBEBCB-1950-4054-B823-62906306E840}"]
end;
IsimpleRDMDisp = dispinterface
[' {E2CBEBCB-1950-4054-B823-62906306E840}"]
function AS ApplyUpdates (const ProviderName: WideString; Delta:
OleVariant; MaxErrors: Integer; out ErrorCount: Integer; var
OwnerData: OleVariant): OleVariant; dispid 20000000;
function AS_GetRecords (const ProviderName: WideString; Count:
Integer; out RecsOut: Integer; Options: Integer; const CommandText:
WideString; var Params: OleVariant; var OwnerData: OleVariant):
OleVariant; dispid 20000001;
function AS _DataRequest (const ProviderName: WideString; Data:
OleVariant): OleVariant; dispid 20000002;326 __ Part Ill: Distributed Database Applications
—_
function AS_GetProviderNames: OleVariant; dispid 20000003;
function AS_GetParams (const ProviderName: WideString; var
OwnerData: OleVariant): OleVariant; dispid 20000004;
function AS_RowRequest (const ProviderName: WideString; Row:
OleVariant; RequestType: Integer; var OwnerData: OleVariant):
OleVariant; dispid 20000005;
procedure AS Execute (const ProviderName: WideString; const
CommandText: WideString; var Params: OleVariant; var OwnerData:
OleVariant); dispid 20000006;
end;
CoSimpleRDM = class
class function Create: ISimpleRDM;
class function CreateRemote (const MachineName: string):
IsimpleRDM;
end;
implementation
uses Comb};
class function CoSimpleRDM.Create: ISimpleRDM;
begin
Result := CreateComObject (CLASS_SimpleRDM) as ISimpleRDM;
end;
class function CoSimpleRDM.CreateRemote (const MachineName: string):
IsimpleRDM;
begin
Result
TsimpleRDM;
CreateRemoteComObject (MachineName, CLASS_SimpleRDM) as
end;
end.
Note that the IsimpleRps interface descends from the TAppServer interface, which
we covered earlier.
Since the remote data module implements the automation server, a dispatch in-
terface — IsimpleRDMDisp — is automatically generated to supplement the main
dual IsimpleRpM interface. The methods created for this dispatch interface are
analogous to the methods of the IAppServer interface.Chapter 9: An Application Server 327
—_
The cosimplerpM class provides for the creation of COM objects that support the
interface used. Two class methods are automatically created for it.
The first method,
class function Create: ISimpleRDM;
is used for handling local and in-process servers.
The second method,
class function CreateRemote (const MachineName: string): ISimpleRDM;
is used in a remote server.
Both methods return a pointer to the Tsimp1eRpM interface.
At this point, if you save the project with the newly created data module and then
register it, it will become available in remote client applications as the application
server.
However, as long as this application server is empty, it cannot pass any datasets to
a client application.
Once created, the remote data module becomes a platform for storing data-access
components and provider components (discussed below), which, along with the
remote data module, perform the key functions of the application server.
Child Remote Data Modules
A single application server can accommodate several remote data modules, which
can perform different operations, deal with different database servers, etc. This
does not at all change the process of developing the server part. It simply means
that, once you have selected the name of the server in the client-side remote con-
nection component (described in Chapter 10, "A Client of a Multi-Tier Distributed
Application"), you will have access to the names of all remote data modules that
this application server contains.
However, such a scenario means that every module must be supplied with its own
individual connection component. If you need to avoid this, you can use the
TSharedConnection component (see Chapter 8, “DataSnap Technology. Remote Ac-
cess Mechanisms"), but you will have to introduce certain changes to the remote
data module interfaces.328 _ Part Ill: Distributed Database Applications
>_>
To provide access to several data modules in the context of a single remote con-
nection, you have to define one of them as the main data module, while the rest
must be specified as child modules.
Let's now consider the implications of this approach on the process of creating
remote data modules. Essentially, the idea is very simple. The interface of the main
remote data module (which module is to be the main one is up to the developer)
must contain properties that point to the interfaces of all other data modules that
are to be utilized on the client in the framework of a single connection. Such
modules are referred to as child data modules.
If the properties in question (which must have the "read-only" attribute) do exist,
then you are able to access all the child modules through the childName property
of the TsharedConnection component (see Chapter 8, "DataSnap Technology. Re-
mote Access Mechanisms").
For example, if a child data module is named secondary, the main data module
must contain the secondary property:
TsimpleRDM = interface (TAppServer)
(' {E2CBEBCB-1950-4054-B823-62906306E840}"]
function Get_Secondary: Secondary; safecall;
property Secondary: Secondary read Get_Secondary;
end;
The implementation of the cet_secondary method looks as follows:
function TSimpleRDM.Get_Secondary: Secondary;
begin
Result := FSecondaryFactory.CreateCoMObject (nil) as Isecondary;
end;
As you can see, the simplest possible solution is just to return a pointer to the
newly created child interface.
A detailed description of the step-by-step development of a child remote data
module is provided later in this chapter.
Data Providers
The TDataSetProvider provider component works as a bridge between a dataset of
a given application server and a client dataset. This component forms data packets,
sending these packets to a client dataset, and receiving datasets that have been
changed by the client (Fig. 9.3).Chapter 9: An Application Server 329
—_
Client
DataSet
TDataSetProvider TClientDataSet
Fig. 9.3. The interaction of a provider component with a client
All necessary operations are performed by this component automatically. The de-
veloper just needs to place a TDataSetProvider component inside the remote data
module on the server side and establish a link between this component and a da-
taset of the application server using the following property:
property DataSet: TDataset;
Then, provided that the connection options are correctly configured (in the way
described earlier), the list of the ProviderName property of the TClientDataset
component, which can be accessed through the Object Inspector, will show
the names of all provider components of the application server in question.
If you connect a client dataset to the provider component, and then open it, the
records of the application server dataset specified in the Dataset property of the
TDataSetProvider provider component will be passed to the client dataset.
The TDataSetProvider component also contains properties that facilitate data ex-
change.
The property
property ResolveToDataSet: Boolean;
coordinates the process of data transmission from a client to the database server.
If this property is set to True, the dataset of the application server that is specified
in the DataSet property is affected by all the updates received from the client
(which means that all these updates are applied to it — its status changes, the ap-
propriate event handlers are invoked, etc.). Otherwise, the updates are passed di-
rectly to the database server. If you do not want the application server to process
the changes made by the client, set the ResolveToDataset property to False,
which will noticeably accelerate the performance of your application.
The property
property Constraints: Boolean;330 __— Part Ill: Distributed Database Applications
—_
manages the process of passing the constraints imposed on the server dataset to the
client dataset. If its value is True, the constraints are applied to the client dataset.
The property
property Exported: Boolean;
enables the client dataset to use the IAppServer interface's data. This is done by
setting this property to True.
The parameters of a provider component are set via the following property:
type
TProviderOption = (poFetchBlobsOnDemand, poFetchDetailsOnDemand,
poIncFieldProps, poCascadeDeletes, poCascadeUpdates, poReadOnly,
PpoAllowMultiRecordUpdates, poDisableInserts, poDisableEdits,
poDisableDeletes, poNoReset, poAutoRefresh, poPropogateChanges,
PpoAllowCommandText, poRetainServerOrder) ;
TProvideroptions = set of TProvideroption;
The options parameter set is assigned by giving it a True value.
property Options: TProviderOptions;
poFetchBlobsOnDemand enables the passing of BLOB field values to a client dataset.
By default, this feature is disabled in order to accelerate performance.
poFetchDetailsOnDemand enables the passing of records from a detail dataset that
relate to the master dataset in a one-to-many relationship to a client dataset.
By default, this option is disabled in order to speed up performance.
poIncFieldProps enables the passing of several field properties to the client dataset,
specifically: Alignment, DisplayLabel, DisplayWidth, Visible, DisplayFormat,
EditFormat, MaxValue, MinValue, Currency, EditMask, and DisplayValues.
poCascadeDeletes enables the automatic removal of detail records in a one-to-
many relationship on the server side if the master records of the client dataset have
been deleted.
poCascadeUpdates enables the automatic updating of detail records in a one-to-
many relationship on the server side if the master records of the client dataset have
been modified.
poReadOn1ly activates a read-only mode for the server dataset.
poAllowMultiRecordUpdates enables concurrent updates for multiple individual
records. Otherwise, updates are made consecutively, one by one.Chapter 9: An Application Server 331
—_
poDisableInserts prevents the client from inserting new records into the server
dataset.
poDisableEdits forbids the client from introducing changes to the server dataset.
poDisableDeletes forbids the client from deleting records from the server dataset.
poNoReset disables the updating of records in the server dataset before these rec-
ords are passed to the client (prior to calling the As_GetRecords method of the
TAppServer interface).
poAutoRefresh enables automatic updating of records in the client dataset. By de-
fault, this option is disabled in order to speed up work.
poPropogateChanges — once changes made to the BeforeUpdateRecord and
AfterUpdateRecord event handlers are fixed in the server dataset, they are passed
to a client. When this feature is activated, it allows you to fully control the process
of updating records on the server.
poAllowCommandText lets you modify the text of an SQL query or the names of
stored procedures or tables in the appropriate dataset component of the application
server.
poRetainServerOrder forbids a client from changing the record sorting order. Dis-
abling this option may result in errors in datasets, or more specifically, in the ap-
pearance of duplicate records.
The event handlers for the TbatasetProvider component are listed in Table 9.2.
Table 9.2. The Event Handlers for the TDataSetProvider Component
Declaration Desct
Property AfterApplyUpdates: Called after updates passed from a client are saved
TRemoteEvent; to the server dataset
Property AfterExecute: Called after an SQL query or stored procedure is
TRemoteEvent; executed on the server
Property AfterGetParams: Called after a provider component forms a set of
TRemoteEvent; server dataset parameters to be passed to a client
property AfterGetRecords: Called after the provider component builds a data
‘TRemoteEvent; packet for passing a server dataset to a client
property AfterRowRequest: Called after the current record in the client dataset is
‘TRemoteEvent; refreshed by the provider component
continues332 ‘Part Ill: Distributed Database Applications
—_>
Table 9.2 Continued
Declaration
Description
property AfterUpdateRecord:
TA£terUpdateRecordEvent;
property
BeforeapplyUpdate
TRemoteEvent;
property BeforeExecute:
TRemoteEvent;
property BeforeGetParams:
TRemoteEvent;
property BeforeGetRecords:
TRemoteEvent;
property BeforeRowRequest:
TRemoteEvent;
property BeforeUpdateRecord:
TBeforeUpdateRecordEvent;
property OnDataRequest:
TDataRequestEvent;
property OnGetDat:
TProviderDataEvent;
property
OnGetDataSet Properties:
TGetDSProps;
property OnGetTableName:
‘TGetTableNameEvent;
property OnUpdateData:
TProviderDataEvent;
property OnUpdateError:
TResolverErrorEvent;
Called immediately after an individual record in the
server dataset is updated
Called before saving updates passed by a client to
the server dataset
Called prior to executing an SQL query or a stored
procedure on the server
Called before the provider component compiles a
set of parameters for the server dataset to pass
them to a client
Called before the provider component builds a data
packet for passing a server dataset to a client
Called before the current record in the client dataset
is refreshed by the provider component
Called immediately before an individual record in the
server dataset is updated
Called when a client request for data is being proc-
essed
Called in the interim between receiving queried data
from a server and passing them to a client
Called when a set of properties is being created for
a server dataset in order to subsequently pass them
toaclient
Called when the provider component receives the
name of a table that needs updating
Called when changes are saved to a server dataset
Called when an error occurs while updates are being
applied to a server dataset
The /ProviderSupport \nterface
In order to enable data exchange with a dataset located on a server, the provider
component uses the IProviderSupport interface, which is a necessary feature
of every component that descends from the TDataset class. The data access tech-
nology used determines the technique that is employed by each component thatChapter 9: An Application Server 333
—_
encapsulates a dataset in order to implement the IProvidersupport interface's
methods.
The developer may need the methods of this interface when he or she faces the
task of designing custom components that encapsulate datasets and descend from
the Tpataset class.
Registering Application Servers
For a client to "see" an application server, this server must be registered on the
computer where it resides. The process of registration itself can vary, depending on
the technology used. Later in the book, you will learn how to register MTS, Web,
and SOAP servers.
Here we will dwell on the subject of registering only one type of server — automa-
tion servers that use TRemoteDataModule remote data modules — which is a sur-
prisingly easy operation.
For executable files, you need just launch the server with the /regserver key, or
simply launch the executable file itself.
In the development environment, the /regserver key can be stored in the dialog
box of the Run\Parameters command.
‘cl | Reno |
Host Application
rs |
Parameters:
[regserver sae
iF Cancel Help
Fig. 9.4. The dialog box for setting an application's launching options
If you choose to cancel a registration, use the /unregserver key, but note that you
can do this only via the command line.
Dynamic libraries are registered with the /regsvr32 key.334 Part Ill: Distributed Database Applications
>_>
Creating a Sample Application Server
By way of example, let's discuss developing a simple application server based
ON TRemoteDataModule. To start, create a new project and save it under the
Simpleappsrvr name. This project is a part of the simpleRemote project group, to
which a client application will subsequently be added.
Table 9.3. Files of the SimpleAppSrvr Project
ile Description
uSimpleAppSrvr.pas The standard project file
SimpleAppSrvr_TLB.pas The type library. This contains the declarations of all inter-
faces used in a given project
uSimpleRDM.pas The file of the simp1eRDM remote data module
uSecondary.pas The file of the secondary child remote data module
Designing a client for the SimpleAppSrvr application server is covered in Chapter 10,
"A Client of a Multi-Tier Distributed Application."
The Main Form of the Application Server
When you run your application server for the first time, you need to customize
the ADO connection, which is used to access a data source in the data modules.
Specify the real path to the data source in the fmMain main form of the application
server (the uSimpleAppSrvr.pas file) by pressing the button of the standard dialog
box used to choose files. The selected file will be displayed in the
edDataPath single-line editor.
From this point on, this path will be used in the Beforeconnect event handlers of
the remote data modules’ Tapoconnect ion components to customize connections.
The path to the database is stored in the SimpleApp.ini file.
The Main Remote Data Module
Now, using the Delphi Repository, add a new remote data module to your
project (see Fig. 8.3). When the appropriate dialog box is displayed (see Fig. 9.2),Chapter 9: An Application Server 335
—_
specify the name for the module — say, simpleRDM — together with its options,
which include:
OG Single Instance, which is the technique for creating individual data modules for
each client
G Free, which is the approach to processing requests (described earlier)
The UpdateRegistry class method is created automatically for the data module in or-
der to provide for registration/invalidation of an automation server (see Listing 9.1).
Created at the same time as the remote data module is the new type library, which
contains the dual IsimpleRDM interface and the IsimpleRDMDisp dispatch interface
(see Listing 9.2).
Every newly created interface is automatically assigned a unique GUID.
Place the components for accessing the ...\Program Files\Common Files\
Borland Shared\Data\dbdemos.mdb Microsoft Access database demo into the
SimpleRDM module. We will access data using an OLE DB provider. This is the
TADOConnection component, which implements the connection, and three
TADOTable table components, which encapsulate the datasets of the Orders,
Customer, and Employee tables.
Prior to opening the connection, the following event handler is called:
procedure TSimpleRDM.conMastAppBeforeConnect (Sender: TObject);
begin
if fmMain.edDataPath.Text <> ''
then conMastApp.ConnectionString := 'Provider =
Microsoft. Jet.OLEDB.4.0;Persist Security Info-False;Data Source=" +
frMain.edDataPath.Text
else Abort;
end;
which is used for customizing the connect ionstring property.
The LoginPrompt = False property disables the display of the logon dialog box
when the connection is being opened.
Each table component is linked to the TDataSetProvider component. The
ResolveToDataSet = False property of the provider component prevents the336 Part Ill: Di:
—_>
application of updates from a client to the dataset of the connected component.
Instead, the updates are saved directly to the database. This feature speeds up the
overall performance of the application.
tributed Database Applications
The Child Remote Data Module
In addition to the main data module, let's build a child data module named
Secondary. This child module can be linked to the main one by supplying the
TSimpleRDM interface with an extra method that returns a pointer to the interface
of the child data module. In this example, this is the Get_Secondary method.
This method is created using the type library of the server (Fig. 9.5).
a
POSSEdTPS HB-
BEE
zw ||SR~
“$e SinghepeSwvt
‘= @ ISnploRDM
85) Seconday
& SinpeOM
@ WSecondery
& Seconday
Type
Hep
Help Stig:
Hebb Comext:
Help Sting Contest
Altibtes |Paxaneters| Fags | Text |
Seconda
Pictu
ISAFEARRAYong)
SCODE,
chert
SimpleROM *
StcFork~
StcPictue «|
Fig. 9.5. The type library of the SimpleAppSrvr application server
Select the IsimpleRpDM interface from the tree in the left part of the window, and
create a new read-only property for this interface with the name Secondary. This
operation is accompanied by the creation of a method that allows you to read the
value of this property. Name this method Get_secondary. This method must return
the secondary type, set using the Type list on the Attributes page in the right panel
of the type library window.Chapter 9: An Application Server 337
—_
As soon as the source code of the type library is updated (via the Refresh
Implementation button), a description of the new property and method of the
TSimpleRDM interface appear in the SimpleAppSrvr_TLB.PAS file. Now the decla-
ration of the ISimpleRDM interface looks as follows:
ISimpleRDM = interface (IAppServer)
('" {E2CBEBCB-1950-4054-B823-62906306E840}"]
function Get_Secondary: Secondary; safecall;
property Secondary: Secondary read Get_Secondary;
end;
At the same time, the Get_Secondary method is added to the declaration of the
SimpleRDM remote data module included in the uSimpleRDM. pas file. The source
code of this method should look like this:
function TSimpleRDM.Get_Secondary: Secondary;
begin
Result := FSecondaryFactory.CreateCOMObject (nil) as Secondary;
end;
As a result, the secondary data module is now a child module of the simplerDM
module.
The secondary module also contains components for accessing a Microsoft Access
database. The dbdemos.mdb database, which is used in this example, comes with
Delphi. The connection is provided by the Tapoconnection component, which is
customized using the TSecondary.conMastAppBeforeConnect event handler.
Two TaDoTable table components encapsulate the Vendors and Parts tables of the
dbdemos.mdb database, respectively. In addition, a one-to-many relationship
is established for these table components. The MasterSource property of the
tblParts component points to the dsVendors component (the TDataSource class),
which is linked to the tb1Vendors component. The MasterFields and IndexFieldNames
properties of the tb1Parts component contain the name of the VendorNo field that
is shared by these two tables.
The one-to-many relationship specified for these two tables will help demonstrate
how to use nested datasets in the sample client application (see Chapter 10, "A Cli-
ent of a Multi-Tier Distributed Application"
Registering the Sample Application Server
Now that your application server is ready to work, you just need to perform the
final step — registering. Just run the executable file of the project on the machine
on which you mean to place this application server.