Open navigation menu
Close suggestions
Search
Search
en
Change Language
Upload
Sign in
Sign in
Download free for days
0 ratings
0% found this document useful (0 votes)
497 views
665 pages
Web Services Development With Delphi
Uploaded by
poloab
AI-enhanced title
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content,
claim it here
.
Available Formats
Download as PDF or read online on Scribd
Download
Save
Save Web Services Development with Delphi(1) For Later
0%
0% found this document useful, undefined
0%
, undefined
Embed
Share
Print
Report
0 ratings
0% found this document useful (0 votes)
497 views
665 pages
Web Services Development With Delphi
Uploaded by
poloab
AI-enhanced title
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content,
claim it here
.
Available Formats
Download as PDF or read online on Scribd
Carousel Previous
Carousel Next
Download
Save
Save Web Services Development with Delphi(1) For Later
0%
0% found this document useful, undefined
0%
, undefined
Embed
Share
Print
Report
Download now
Download
You are on page 1
/ 665
Search
Fullscreen
—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.
You might also like
Delphi Chandler
PDF
No ratings yet
Delphi Chandler
60 pages
(Ebook) Borland - Internet Programming With Delphi (Marco Cantu) PDF
PDF
No ratings yet
(Ebook) Borland - Internet Programming With Delphi (Marco Cantu) PDF
14 pages
WAGW Installation Guide
PDF
No ratings yet
WAGW Installation Guide
31 pages
CodeCharge Studio4 Quick Start Tutorials
PDF
100% (1)
CodeCharge Studio4 Quick Start Tutorials
162 pages
Delphi XE5 ClientRest Google Drive
PDF
100% (1)
Delphi XE5 ClientRest Google Drive
18 pages
Internet Programming With Delphi
PDF
100% (1)
Internet Programming With Delphi
14 pages
Be Delphi Live Bindings
PDF
100% (1)
Be Delphi Live Bindings
25 pages
Send Email With Attachment Delphi Indy PDF
PDF
100% (1)
Send Email With Attachment Delphi Indy PDF
2 pages
Blaise 93 UK
PDF
100% (1)
Blaise 93 UK
102 pages
Using Assembler Language in Delphi
PDF
100% (2)
Using Assembler Language in Delphi
44 pages
CRM Total Cost of Ownership Analysis PDF
PDF
No ratings yet
CRM Total Cost of Ownership Analysis PDF
11 pages
Resume - Alya Alifia Anwar Jaya
PDF
No ratings yet
Resume - Alya Alifia Anwar Jaya
2 pages
Mengirim Email Dengan Delphi
PDF
No ratings yet
Mengirim Email Dengan Delphi
7 pages
The Delphi IDE Open Tools API Version 1.1 PDF
PDF
100% (1)
The Delphi IDE Open Tools API Version 1.1 PDF
160 pages
Analysis of Debtor Information System Development Through Sistem Layanan Informasi Keuangan (Slik) Using Method Model View Controler (MVC)
PDF
No ratings yet
Analysis of Debtor Information System Development Through Sistem Layanan Informasi Keuangan (Slik) Using Method Model View Controler (MVC)
6 pages
Invoice: Invoice For Invoice Number Due Date
PDF
No ratings yet
Invoice: Invoice For Invoice Number Due Date
1 page
Delphi JSON Viewer
PDF
100% (1)
Delphi JSON Viewer
17 pages
Code Block Basics: February 01, 1991 - Spence, Rick
PDF
No ratings yet
Code Block Basics: February 01, 1991 - Spence, Rick
6 pages
C Sharp 4 Delphi Developers
PDF
No ratings yet
C Sharp 4 Delphi Developers
18 pages
Ws Starthere PDF
PDF
No ratings yet
Ws Starthere PDF
4 pages
Blaise Pascal Magazine 85: Special Edition: DELPHI 25 Year
PDF
No ratings yet
Blaise Pascal Magazine 85: Special Edition: DELPHI 25 Year
40 pages
Identifikasi Risiko
PDF
No ratings yet
Identifikasi Risiko
41 pages
MCS98xx - DOS Driver Installation Guide
PDF
No ratings yet
MCS98xx - DOS Driver Installation Guide
4 pages
GLScene Beginner's Guide
PDF
No ratings yet
GLScene Beginner's Guide
36 pages
Clipper Network
PDF
No ratings yet
Clipper Network
7 pages
The Adapter Architecture For Distributed Web Services Using XML Components
PDF
No ratings yet
The Adapter Architecture For Distributed Web Services Using XML Components
8 pages
Distributed App Remoting
PDF
No ratings yet
Distributed App Remoting
754 pages
9907cd PDF
PDF
No ratings yet
9907cd PDF
35 pages
Power Builder 8 Getting Started
PDF
100% (5)
Power Builder 8 Getting Started
304 pages
Untitled
PDF
No ratings yet
Untitled
296 pages
Using The REST Client Library To Access REST-based Web Services
PDF
No ratings yet
Using The REST Client Library To Access REST-based Web Services
6 pages
Workshop Jogja 03 04 2019
PDF
No ratings yet
Workshop Jogja 03 04 2019
25 pages
Parallel Port Programming With Delphi
PDF
No ratings yet
Parallel Port Programming With Delphi
4 pages
Dot NET Enterprise Architecture
PDF
No ratings yet
Dot NET Enterprise Architecture
23 pages
Writing A Metro Like Window For Our Applications With Delphi
PDF
No ratings yet
Writing A Metro Like Window For Our Applications With Delphi
22 pages
TR0120 Delphi Script Reference
PDF
No ratings yet
TR0120 Delphi Script Reference
38 pages
Exception Handling in Multi-Layered Systems
PDF
No ratings yet
Exception Handling in Multi-Layered Systems
36 pages
Delphi 3 The Activex Foundry
PDF
No ratings yet
Delphi 3 The Activex Foundry
58 pages
Delphi - The Road To Delphi - A Blog About Programming
PDF
No ratings yet
Delphi - The Road To Delphi - A Blog About Programming
22 pages
The Persistence of Objects PDF
PDF
No ratings yet
The Persistence of Objects PDF
40 pages
Tehnik Decexpress Cxgrid
PDF
No ratings yet
Tehnik Decexpress Cxgrid
66 pages
Internet Programming With Delphi (Marco Cantu)
PDF
No ratings yet
Internet Programming With Delphi (Marco Cantu)
14 pages
Indy in Depth Eng
PDF
No ratings yet
Indy in Depth Eng
124 pages
Unit Testing in Delphi by Nick Hodges 110214
PDF
No ratings yet
Unit Testing in Delphi by Nick Hodges 110214
36 pages
Power Builder 7
PDF
No ratings yet
Power Builder 7
403 pages
Mobile Tutorials en PDF
PDF
No ratings yet
Mobile Tutorials en PDF
415 pages
Top 10 Tricks For Delphi and C++Builder VCL Database Developers by Cary Jensen
PDF
No ratings yet
Top 10 Tricks For Delphi and C++Builder VCL Database Developers by Cary Jensen
25 pages
Cracking Delphi Programs
PDF
No ratings yet
Cracking Delphi Programs
11 pages
Csharp Programming
PDF
No ratings yet
Csharp Programming
57 pages
Power Builder 9 Power Script Reference 1 (Manual)
PDF
100% (1)
Power Builder 9 Power Script Reference 1 (Manual)
320 pages
E Learning Series Win Mac Development Coursebook Lesson1
PDF
No ratings yet
E Learning Series Win Mac Development Coursebook Lesson1
41 pages
PB Application Techniques
PDF
No ratings yet
PB Application Techniques
784 pages
Vdocuments - MX - Delphi Orm Soa MVC SQL Nosql Json Rest Mormot PDF
PDF
No ratings yet
Vdocuments - MX - Delphi Orm Soa MVC SQL Nosql Json Rest Mormot PDF
207 pages
Rest Servers in Delphi Xe Using Datasnap
PDF
No ratings yet
Rest Servers in Delphi Xe Using Datasnap
66 pages
The CA Clipper Conversion Process
PDF
No ratings yet
The CA Clipper Conversion Process
6 pages
Delphi 2010 REST WP Marco Cantu 0911
PDF
No ratings yet
Delphi 2010 REST WP Marco Cantu 0911
54 pages
Getting Full Speed With Delphi
PDF
No ratings yet
Getting Full Speed With Delphi
25 pages
Delphi Com API Do Windows
PDF
No ratings yet
Delphi Com API Do Windows
21 pages
Developing Enterprise Applications
PDF
No ratings yet
Developing Enterprise Applications
448 pages
Mcs9865 Dos Um Ver1.1
PDF
No ratings yet
Mcs9865 Dos Um Ver1.1
6 pages
Mcs9865 Linux Um Ver1.1
PDF
No ratings yet
Mcs9865 Linux Um Ver1.1
8 pages
WinXP Driver Installation Guide
PDF
No ratings yet
WinXP Driver Installation Guide
4 pages
Internet Programming With DELPHI
PDF
No ratings yet
Internet Programming With DELPHI
3 pages
MCS9865 Win98/ME User Manual
PDF
No ratings yet
MCS9865 Win98/ME User Manual
11 pages
Installaware: For Windows Installer, Native Code, DRM, Virtualization
PDF
No ratings yet
Installaware: For Windows Installer, Native Code, DRM, Virtualization
43 pages