delphi informant magazine vol 6 no 1

Upload: sharkfinmike

Post on 04-Jun-2018

259 views

Category:

Documents


2 download

TRANSCRIPT

  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    1/34

    Cover Art By: Art hur Dugoni

    ON THE COVER6 Delphi at Work Embedded Forms John SimoniniMr Simonini shares a technique for embedding forms as the pages of atabbed notebook a technique that facilitates discrete business logic,team development, memory management, and code reuse.

    FEATURES10 Distributed DelphiRequest Threads Michael J. LeaverMr Leaver describes a resource-sharing methodology for Delphi 4/5

    Client/Server Suite using DCOM, and without using TransactionProcessing Monitors, such as MTS or BEAs Tuxedo.

    14 Sound + VisionA Multimedia Assembly Line: Part II Alan C. Moore, Ph.D.Dr Moore wraps up his two-part description of how to create a Delphisound expert that builds sound-enabled components, including adetailed look at the code-generating engine.

    18 In DevelopmentControl Panel Applets Peter J. RosarioControl Panel applets are the small programs that are visible in, andrun from, Windows Control Panel. Mr Rosario describes why youd writeone, and demonstrates how to do it with Delphi.

    22 Dynamic DelphiRun-time ActiveX Ron LoewySure, its easy to incorporate an ActiveX component with your Delphiapplication at design time, but Mr Loewy takes it one big step further byallowing run-time integration.

    REVIEWS29 Orpheus 3Product Review by Alan C. Moore Ph.D.

    DEPARTMENTS

    2 Delphi Tools5 Delphi News33 File | New by Alan C. Moore, Ph.D.

    January 2000, Volume 6, Number 1

    1 January 2000 Delphi Informant Magazine

    http://00index.pdf/
  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    2/342 January 2000 Delphi Informant Magazine

    DelphiT O O L S

    New Products

    and Solutions

    Excel Software Announces QuickCRC 1.2Excel Software announced

    QuickCRC 1.2for object-ori-ented software modeling onWindows or Macintosh com-puters. Version 1.2 increases themodel capacity to support thou-sands of object classes, hundredsof diagrams, and long names for

    classes, attributes, and opera-tions. QuickCRC 1.2 supportsthe Delphi, C++, and Java re-engineering features available inWinTranslator 2.0, which gen-erates class models or CRCcards from source code.

    QuickCRC uses a diagramworkspace for creating cardand scenario objects. A cardrepresents the properties of aclass, including its name,description, superclasses, sub-classes, attributes, responsibili-

    ties, and object collaborations.A scenario represents a designmechanism defined as a seriesof steps involving communi-cating objects. Scenarios canreference cards and other sce-narios. As information isentered or changed for a cardor scenario object, it is instant-ly synchronized throughout

    the model. Separate diagramspartition large models intosubject areas. The contentsview allows a designer to navi-gate between diagrams. Thegenerated inheritance graphconcisely illustrates the classinheritance structure.

    A text representation can begenerated from information in aCRC model. This informationcan be used as a coding specifi-cation, transferred to otherapplications, or used to generate

    a new model. Design informa-tion can be exported to theMacA&D or WinA&D model-ing tools for detailed design andcode generation.

    QuickCRC for Windows runson Windows 95, 98, and NT.Models are binary-compatible

    between platforms.

    Excel SoftwarePrice: US$295Phone: (515) 752-5359Web Site: http://www.excelsoftware.com

    Vista Software Releases Apollo Client/Server 5.0Vista Software announced

    the release ofApollo

    Client/Server 5.0 for Delphidevelopers. Based on theApollo product (the companysnative VCL replacement forthe BDE) and new 32-bit SDEdatabase technology, thisclient/server database engineoffers features Apollo usershave been requesting, includ-ing minimizing network trafficby offloading data processingonto the server. ApolloClient/Server is compatible

    with Delphi 4 and 5 andC++Builder 3 and 4.Apollo Client/Server is TCP/IP-

    based, which allows developers toconnect client applications to anycomputer addressable by an IPaddress, from a local intranetTCP/IP network, to a Web siteon the Internet. ApolloClient/Server 5.0 includes sup-port for developing non-client/server applications.

    Users deploy the Apollo Server

    application on a networked server

    running Windows 95, 98, NT, or2000. Basic Apollo server configu-ration requires only that develop-ers tell the server which directoriescontain their database files. Usersthen use the TApolloDataSetandTApolloConnection components intheir client applications to con-nect to the Apollo Server andaccess those registered databases.Apollo Client/Server 5.0 fea-

    tures unlimited concurrent

    client connections per server,

    support for local and client/serv-er application development,support for server-side storedprocedures, user security, trans-action support, triggers, com-plete Delphi source for all com-ponents included, and more.

    Vista SoftwarePrice: US$679; upgrade pricing is available.Phone: (800) 653-2949Web Site: http://www.vistasoftware.com

    Tiriss Announces CB4 Tables Version 1.01

    Tiriss announced version 1.01of CB4 Tables, its Delphi wrap-per for using SequitersCodeBase instead of the BDE.CB4 Tables is a set of compo-nents that can be used any-where you want to use dBASEIV, FoxPro, or Clipper tables,but dont want to use the BDE.

    CB4 Tables TTablereplace-ment has almost all functions aTTablehas, and is completelycompatible with TTable. CB4

    Tables major advances includeno BDE install, but a (small)DLL sold by SequiterSoftware; compatibility withthe BDE; compatibility withall standard Database compo-nents; and support for Delphi3 and 4.

    TirissPrice: US$35; US$49 with source.E-Mail: [email protected] Site: http://www.tiriss.com

    http://www.excelsoftware.com/http://www.vistasoftware.com/http://www.tiriss.com/http://www.tiriss.com/http://www.vistasoftware.com/http://www.excelsoftware.com/
  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    3/343 January 2000 Delphi Informant Magazine

    DelphiT O O L S

    New Products

    and Solutions

    Vista Software Launches Apollo 5.0Vista Software announced therelease ofApollo 5.0, the compa-nys native VCL replacement forthe Borland Database Engine.

    Some of the new Apollo 5.0features include Delphi 5 sup-port (and continued support forDelphi 3 and 4 and C++Builder

    3 and 4); the TApolloEnvcom-ponent, which helps isolate

    global/environmental settingsfor all TApolloDataSetcontrols;a comprehensive online help file(specific to new TApolloDataSet,TApolloEnv, andTApolloDatabasecomponentarchitecture); updated sampleprograms; improved support for

    Woll2Wolls InfoPower compo-nents; new properties and meth-

    ods for greater power and flexi-bility; and complete Delphisource code for TApolloDataSet,TApolloEnv, andTApolloDatabasecomponents.

    Vista SoftwarePrice: US$379; upgrade pricing is available.

    Phone: (800) 653-2949Web Site: http://www.vistasoftware.com

    ASTA Technology Group Announces ASTA 2.0ASTA Technology Group

    announced the release ofASTA2.0, the companys multi-tierdevelopment tool for producingInternet applications. Delphi 5support has also been initiatedwith the release.

    In addition to improved per-formance and numerous featureenhancements, ASTA 2.0 intro-duces support for pure multi-tier programming, includingsupport for business objects andJava clients.

    New server-side featuresinclude an AstaProvider, whichoffers advanced SQL generationon the server, with events thatcan fire before each update,insert, or delete statement. TheASTA Business ObjectsManager introduces a smartmans method of working with

    distributed objects. Define serv-er-side methods along withparams and they will becomeaccessible to client-side work atdesign time. Support for Javaclients has also been added withan event for communicationwith Java clients.

    Robust ASTA servers run inthree ASTA threading models all threading is handledinternally and automatically byASTA. ASTAs client-sideenhancements includeAutomatic Client Updates, sup-port for Remote Directories(Internet-ready file dialogboxes), an extended suitcasemodel, a progress bar for visual

    feedback on socket reads, andthe ability to clone datasets andtheir edits.

    ASTA Technology GroupPrice: Component Suite, US$249; EntrySuite, US$399 (includes Component Suiteand one server license).Phone: (800) 699-6395 or(785) 539-3731Web Site: http://www.astatech.com

    Quiksoft Announces EasyMail Objects Version 5.0Quiksoft Corp. released ver-

    sion 5.0 of EasyMail Objects,a set of e-mail COM objectsused by software developers toe-mail-enable their applica-tions. Applications utilizingEasyMail Objects can send,retrieve, view, compose, andprint Internet e-mail. EasyMailObjects is ideal for developersusing Delphi, Visual Basic,C++, ASP, and other develop-ment systems supportingCOM objects.

    Version 5.0 enables the devel-oper to create and retrieve richHTML messages, including

    embedded items, such asimages, sounds, and videoclips.

    The updated IMAP4 Objectenables applications to down-load a messages list of attach-ments so it can screen outmessages containing large orunwanted items. The SMTPObject supports ESMTPsLogin and encryptedCRAM-MD5 authenticationschemes. The new version alsoallows developers to increaseperformance by using callbackfunctions instead of eachobjects standard COM events.

    The objects are Y2K-compat-ible and scale to suit multi-user environments, such asWeb servers. The objects sup-port SMTP, POP3, IMAP4,ESMTP, APOP, HTML, RTF,plain text, file attachments,MIME, Base64, UUencode,Quoted Printable, customizedheaders, and optional NT inte-gration.

    Quiksoft Corp.Price: From US$399 per developer.Phone: (800) 509-8700 or(610) 544-3139Web Site: http://www.easymailobjects.com

    http://www.vistasoftware.com/http://www.astatech.com/http://www.easymailobjects.com/http://www.easymailobjects.com/http://www.easymailobjects.com/http://www.astatech.com/http://www.vistasoftware.com/
  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    4/344 January 2000 Delphi Informant Magazine

    DelphiT O O L S

    New Products

    and Solutions

    Greg Lief Offers G.L.A.D. ComponentsGreg Liefis offering G.L.A.D.

    (Greg Liefs Assorted Delphi)components, a collection of over60 Delphi components andproperty editors. The suite

    includes three user-friendly fil-tering components(TGLFilterDialog,TGLQBE, andTGLQBE2);TGLHTMLTable,which converts any dataset to a

    formatted HTML table;TGLXLS, which converts anydataset to Microsoft Excel for-mat);TGLPrintGrid, and more.G.L.A.D. registration includes

    all Delphi source code; supportfor Delphi 1 through 5 andC++Builder 1 through 3; a com-

    prehensive help file; unlimitedminor updates; and access toGreg Liefs Delphi KnowledgeBase, an online searchable collec-tion of Gregs favorite Delphi tips,techniques, and examples.

    Greg LiefPrice: US$69Phone: (877) REGSOFTWeb Site: http://www.greglief.com/delphi/components.shtml

    Objective Software Releases ABC for Delphi Version 5Objective Software Technology

    Pty Ltd. announced version 5 ofits visual component library,

    ABC for Delphi.ABC is a complete user

    interface toolkit, with over 200components to enhance allaspects of user interface design,data presentation, and applica-tion support. Componentsinclude toolbars and custommenus; DB tree views;advanced DB navigation, excep-tion handling, and help andhint editing; rich text editing;button bars; animation andtransition effects; and more.A new Dataset Adapter

    Framework adds support forADO, InterBase, MIDAS, andcommon third-party datasets.This framework isolates ABCcomponents from underlyingdataset functionality, reducing

    the interface to a single point of

    adaptation. Developers will findit easier to use ABC in Delphi5, with a new componentbrowser, ABC property catego-ry, and enhanced componentand property editors.

    Objective Software TechnologyPty Ltd.Price: From US$149E-Mail: [email protected] Site: http://www.obsof.com

    TUS Announces LocoMotionTisfoon Ulterior Systems

    (TUS) announced the release ofLocoMotion, a set of nativeDelphi VCL components formotion control and factoryautomation.

    The set of 13 componentscontrol servo motors, digitaland analog IO, and specialized

    components for linked axes,two-dimensional gantry sup-port, and analog input filters.

    Included are built-in formsfor user configurable parame-ters, including PID filter para-meters for servo motors, portnumber, and others. Theseforms have optional operator-level and maintenance-levelpasswords and complete onlinedocumentation. Additionally,there are built-in diagnostics

    forms with background statusupdates for each device.

    Currently, the componentsonly work with the MotionEngineering PCX/DSP cardunder the Windows NT operat-ing system.

    Tisfoon Ulterior SystemsPrice: US$4,995 (includes source andunlimited e-mail support).Phone: (919) 881-8322Web Site: http://www.Tisfoon.com

    The Tomes of Delphi:

    Win32 Database

    Developers Guide

    Warren Rachele

    Wordware Publishing, Inc.

    ISBN: 1-55622-663-2

    Price: US$39.95

    (365 pages, CD-ROM)

    Web Site: http://www.

    wordware.com

    Wise Introduces Wise for Windows InstallerWise Solutions, Inc., workingclosely with Microsoft Corp.,

    announced Wise for WindowsInstaller, an application installerthat enables application develop-ers to create installation programscompatible with Microsofts newWindows Installer technology.

    Microsofts Windows Installerwas originally designed forWindows 2000 (formerlyWindows NT 5.0). However,both the development environ-ment and the installations createdwill support Windows NT 4.0,

    and Windows 95/98. Designed toreduce the administrative require-

    ments of managing Windowsworkstations, the technology playsa key role in Microsofts ZeroAdministration initiative forWindows. The new Wise forWindows Installer enables devel-opers to create installations thatmeet the new Microsoft standard.

    Wise Solutions, Inc.Price: US$795Phone: (800) 554-8565Web Site: http://www.wisesolutions.com

    SSNet Releases NeoSecureSSNet, Inc. announced the

    release of NeoSecure, whichprovides Windows-based soft-ware authors and proprietarycontent providers with a sys-tem to control and monitorproduct distribution andpiracy.

    NeoSecure is available as aplug-in (for embedding insoftware source code) and as ashell, providing protection to

    content files, such as music,video, data, and others.

    Both forms of NeoSecureoffer a variety of features, suchas automatic e-mail notifica-tion of product installs andregistrations; the ability to pre-vent multiple uses of a serialnumber; the ability to instant-ly disable a given serial num-ber, thereby disabling allcopies using that number; and

    a backdoor into everyinstalled copy for news andother special announcements.

    SSNet, Inc.Price: NeoSecure Plug-in, US$200(includes US$50 setup fee and 100 keys);NeoSecure Shell, US$300 (includes US$150setup fee and 100 keys).Phone: (303) 722-5240Web Site: http://softwaresolutions.net/neosecure/index.htm

    http://www.greglief.com/delphi/components.shtmlhttp://www.greglief.com/delphi/components.shtmlhttp://www.greglief.com/delphi/components.shtmlhttp://www.obsof.com/http://www.tisfoon.com/http://www.wordware.com/http://www.wordware.com/http://www.wisesolutions.com/http://softwaresolutions.net/neosecure/index.htmhttp://softwaresolutions.net/neosecure/index.htmhttp://softwaresolutions.net/neosecure/index.htmhttp://www.wordware.com/http://www.wordware.com/http://softwaresolutions.net/neosecure/index.htmhttp://www.tisfoon.com/http://www.wisesolutions.com/http://www.obsof.com/http://www.greglief.com/delphi/components.shtml
  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    5/345 January 2000 Delphi Informant Magazine

    NewsL I N E

    Janua ry 2 00 0

    Inprise Announces JBuilder 3 Enterprise Solaris EditionScotts Valley, CA Inprise

    Corp. announced JBuilder 3Enterprise Solaris Edition, mak-ing the companys enterprise

    development tools for the Javaplatform available for the Solarisoperating environment. JBuilder3 Enterprise is a comprehensive

    visual development tool for cre-ating Java-platform-based appli-cations and applets that can alsoinclude JavaServer Pages andJava Servlets technologies,JavaBeans and EnterpriseJavaBeans technologies, and dis-tributed CORBA applications

    for the Java 2 platform.JBuilder 3 Enterprise SolarisEdition also includes supportfor the Java 2 Platform,Enterprise Edition (J2EE).JBuilder 3 Enterprise Solaris

    Edition is available on the Webat http://www.borland.com.JBuilder 3 Enterprise SolarisEdition has an estimated streetprice (ESP) of US$2,499 fornew users. Current owners ofany Borland Client/Server orEnterprise product can purchase

    the product for an ESP ofUS$1,699. Current owners ofany Borland Professional prod-uct can purchase the product foran ESP of US$2,199.

    Inprise to Support C, C++, and DelphiDevelopment on Linux

    Scotts Valley, CA Inprise

    Corp. announced it is developinga Linux application developmentenvironment that will support C,C++, and Delphi development.The project, code-named Kylix,is set for release this year.

    Project Kylix is planned to be aLinux component-based develop-ment environment for two-wayvisual development of graphicaluser interface (GUI), Internet,database, and server applications.Plans are for Project Kylix to bepowered by a new high-speed

    native C/C++/Delphi compilerfor Linux and will implement aLinux verison of the BorlandVCL (Visual ComponentLibrary) architecture. The

    Borland VCL for Linux will be

    designed to speed native Linuxapplication development and sim-plify the porting of Delphi andC++Builder applications betweenWindows and Linux.

    The Project Kylix design wasinfluenced by the results fromthe Borland Linux DeveloperSurvey, conducted in July 1999.The results indicate that devel-opers are seeking RAD, databaseenablement, and GUI design.The Project Kylix developmentenvironment is planned to sup-

    port major Linux distributions,including Red Hat Linux andthe forthcoming Corel LINUX.

    To learn more, visit Inprise athttp://www.inprise.com.

    Inprise Previews Products to Support Enterprise ApplicationsNew York, NY Inprise

    Corp. announced the betarelease of the next version of itsInprise Application Server, aswell as the latest releases ofAppCenter and VisiBroker.

    Inprise Application Servercombines the benefits of EJBand CORBA. With VisiBroker,customers can create e-businessapplications that can handlethe high volumes of transac-tions required for doing busi-ness on the Web. The InpriseApplication Server also pro-

    vides comprehensive supportfor the Java 2 Platform,Enterprise Edition (J2EE).

    Inprise AppCenter isdesigned specifically to managedistributed applications, allow-

    ing customers to manage fromthe application level ratherthan from the hardware per-spective. It provides a software-based centralized managementand control for distributedapplications, running on mul-tiple hardware and softwareplatforms across multiple loca-

    tions. This new version ofInprise AppCenter will supportthe management of EJB appli-cations and will be optimizedfor the Inprise ApplicationServer.

    Inprises VisiBroker isdesigned to facilitate the devel-opment and deployment of dis-tributed enterprise applicationsbased on industry standardsthat are scalable, flexible, andeasily maintained.

    For more information, visithttp://www.inprise.com.

    Inprise Centralizes European Customer Support Operation

    Scotts Valley, CA InpriseCorp. announced it will central-ize its European customer sup-port operation in Amsterdam toprovide round-the-clock cus-tomer support throughoutEurope. The announcement fol-lows the expansion of InprisesEuropean Professional ServicesOrganization (PSO) to providea center of excellence inCORBA and Application Serverconsulting skills.

    Inprise is establishing aWorldwide Customer SupportGroup with three major sup-

    port centers around the globe:Scotts Valley, United States;Singapore; and Amsterdam, theNetherlands. It will operate as aseparate entity from the PSOto ensure that customers haveaccess to a dedicated expertresource at all times. CharlesOdinot will head the Europeanoperations.

    The group will provide sup-port across the whole range ofInprises Borland developmenttools and enterprise integra-tion and database products,and will service customers in

    Europe and worldwide.Customers will be able to

    access customer supportthrough local call numbers in allmajor countries, and customerqueries will be routed and han-dled directly through theAmsterdam center. Support ser-vices will also be provided via e-mail and through the InpriseWeb site.

    For more information, includ-ing customer service phonenumbers worldwide, visithttp://www.inprise.com orhttp://www.inprise-europe.com .

    Inprise Licenses VisiBrokerCORBA Technology to HPInprise Corp. announced a

    worldwide technology licensingagreement with Hewlett-Packard

    Company covering InprisesVisiBroker CORBA object request

    broker. HP plans to integrateVisiBroker in its Smart Internet

    Usage (SIU) 2.0 solution forInternet Service Providers.

    Financial terms of the deal were

    not disclosed.SIU 2.0 gives service providersand enterprise-network managers

    a consolidated, Internet data-mediation platform for billing,

    tracking, and data mining of sub-scriber utilization of network

    resources and services.Inprises VisiBroker for Java andVisiBroker for C++ CORBA

    object request brokers aredesigned to facilitate the devel-opment and deployment of dis-tributed enterprise applicationsthat are scalable, flexible, and

    easily maintained.

    http://www.borland.com/http://www.borland.com/http://www.inprise.com/http://www.inprise.com/http://www.inprise.com/http://www.inprise.com/http://www.inprise-europe.com/http://www.inprise-europe.com/http://www.inprise.com/http://www.inprise.com/http://www.inprise.com/http://www.borland.com/
  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    6/346 January 2000 Delphi Informant Magazine

    Delphi at Work

    Tabbed Notebook / OOP / Memory Management / Visual Inheritance

    By John Simonini

    Embedded FormsPutting Visual Inheritance to Work

    Atabbed notebook presents information to the user in an organized fashion (see

    Figure 1). Unfortunately, this interface complicates the coding effort by mixing page

    navigation logic with business application logic. Every page presented to the user intro-

    duces more business application logic concentrated into one place making mainte-

    nance issues a growing concern.

    Embedding forms in the pages of a notebook is atechnique that allows individual forms to containdiscrete business logic, while another totally sepa-rate form contains the page navigation and com-mon function logic. By dividing the logic intomanageable units of page navigation and userinterface, the following advantages are realized:

    Improved team development efforts.Improved memory management.Improved code reuse.

    Embedding forms will give the user the percep-tion of working with one form containing a

    notebook full of controls. Only the developerrealizes that each page in the notebook is a sepa-rate form, allowing for improved team develop-ment efforts. Two descendant classes of TFormare created to accomplish the effect:TEmbeddedNotebookto manage page navigation,and TEmbeddedForm to handle user interface.

    (All source code referenced in this article is avail-able for download; see end of article for details.)

    TEmbeddedForm is a pure virtual descendant ofTForm and is used to represent each page in thenotebook. The abstract methods withinTEmbeddedForm provide the functionality neededto support generic behaviors, such as save, cancel,and print, invoked from TEmbeddedNotebook.TEmbeddedNotebookis an abstract descendant ofTForm that contains a TPageControl and severalTBitButton objects used to manage a collection ofTEmbeddedForm objects.

    To embed a form within a TPageControl, followthese steps (seeFigure 2):

    Eliminate the embedded forms border.Set the embedded forms parent to the cur-rent page.Fit the embedded form to the page.Show the embedded form modeless.

    Embedding a form occurs only when the userselects a tab on the notebook. No embedded formis loaded into memory until selected by the user,resulting in improved memory management.

    Divide and ConquerTabbed notebooks divide user interface elements intological groups to make understanding easier for theuser. Embedded forms do the same for developers,delivering on the promise of improved team develop-ment. Dividing or structuring code into logicalgroups is a key factor in writing maintainable code.

    The TEmbeddedForm class has five abstract methods:InitializeForm, Print, SaveForm, CancelForm, andCloseForm; and one property, CanPrint(seeFigure3). These form the functional interface between theclasses TEmbeddedForm and TEmbeddedNotebook.Figure 1: The example tabbed notebook application at run time.

  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    7/347 January 2000 Delphi Informant Magazine

    InitializeForm is a procedure invoked every time the user selects apage. InitializeForm handles any setup logic, such as performing cal-culations or queries before displaying the form.

    Printis a procedure that will execute whatever method of printingthe developer has selected. The CanPrintproperty is used by theTEmbeddedNotebook to disable the users ability to invoke the printmethod when no print functionality is needed.

    SaveForm is a function that returns True if the save was successful.SaveForm is invoked when a user selects Save. If SaveForm returnsFalse, the page is focused and the user should be prompted to fixthe condition that is causing the save to fail (it could be that theembedded form is failing an edit or information is missing).

    CancelForm is a function that returns True if the cancel was success-ful. CancelForm is invoked when a user selects Cancel. In the eventCancelForm returns False, the page is focused and the user should beprompted to fix the condition that is causing the cancel to fail.

    CloseForm is a function that returns True if the user has handled allpending changes. CloseForm, as can be seen from the supplied sourcecode, is best utilized by calling CanCloseQuery, passing in a Booleanvariable. Nominally, CloseForm should be used to check for anypending changes. If changes exist, the user should be prompted forthe disposition of these changes.

    The most effective way to implement TEmbeddedForm is to sub-class an intermediate class. The intermediate class will implement

    the generic rules for this project, such as behaviors for SaveForm,CancelForm, and CloseForm. Sub-classing all the other embeddedforms to be used from the intermediate class will improve codereuse. However, for simplicitys sake, this is not the route I chose touse with the supplied demonstration project.

    Implementing the class TEmbeddedNotebook is as simple as overrid-ing one abstract method, FindEmbeddedForm, which returns aTEmbeddedForm (seeFigure 4). Taking the time to look over thesource for TEmbeddedNotebook reveals quite a bit of code that han-dles the drudgery of housekeeping.

    Under the HoodTEmbeddedNotebook is the real workhorse here. It manages a TList

    of TEmbeddedForm objects, as well as all the generic processing forthe TEmbeddedForm objects. TEmbeddedNotebook delivers improvedmemory management and improved code reuse. The central focus ofTEmbeddedNotebook is the TListof TEmbeddedForm objects namedFormList. TEmbeddedNotebookhandles these tasks:

    It initializes FormList;displays and hides embedded forms on page turns; andinvokes generic processing, such as save, cancel, close, and print.

    As previously mentioned,FormListis a TListof TEmbeddedForms. Torealize improved memory management, the embedded forms wont becreated until theyre requested by the user clicking on their respectivepage. To this end, FormListis initialized and loaded with nil pointers,

    one for each TTabSheeton the TPageControl(see Figure 5). The reasonfor loading nil pointers is that retrieving TEmbeddedForms fromFormListis dependent upon an exact correlation between the pageindex of the selected page and the corresponding TEmbeddedForm.

    Showing a TEmbeddedForm occurs when TEmbeddedNotebookrespondsto the TPageControls OnChangeevent. GetForm is the function respon-sible for returning the correct TEmbeddedForm (seeFigure 6).

    The page index of the active form is used as the index intoFormList. If the pointer is assigned, then TEmbeddedForm isreturned. Otherwise, the page index of the active page is passed to

    // Size, position, initialize, and show the form

    // associated with the page.

    procedure TFormEmbeddedNoteBook.ShowForm(Form: TEmbededForm);

    begin

    Form.BorderStyle := bsNone; // Eliminate border.

    // Set Parent as the active page.

    Form.Parent := PageControl1.ActivePage;

    Form.Align := alClient; // Make form fit the page.

    Form.InititalizeForm; // Display form.

    SetButtons(Form); // Call to set print btns.

    Form.SetFocus;

    end;

    Figure 2: Embedding one form within another.

    Delphi at Work

    // Embedded form pure virtual class definition.

    TEmbeddedForm = class(TForm)protected

    FCanPrint: Boolean; // True if form has print capability.

    public

    property CanPrint: Boolean read FCanPrint default False;

    // Housekeeping logic.

    procedure InitializeForm; virtual; abstract;

    procedure Print; virtual; abstract; // Print form.

    // Save data changes.

    function SaveForm: Boolean; virtual; abstract;

    // Cancel data changes.

    function CancelForm: Boolean; virtual; abstract;

    // Close the form.

    function CloseForm: Boolean; virtual; abstract;

    end;

    Figure 3: The TEmbeddedForm class definition.

    // Return embedded form based on the notebook page passed.

    function TfrmDiDemo.FindEmbeddedForm(aPage: Integer):

    TEmbeddedForm;

    begin

    case aPage of

    efCustList: Result := TfrmCustomer.Create(Self);

    efOrders: Result := TefOrders.Create(Self);

    efOrderItm: Result := TefOrderItems.Create(Self);

    else

    Result := nil;

    end;

    end;

    Figure 4: Loading an embedded form.

    // Set up the PageControl to contain embedded forms.

    // Create a TList to hold the TEmbeddedForm Objects.procedure TFormEmbeddedNoteBook.InitializePageControl(

    Sender: TObject);

    var

    Index: Integer;

    begin

    // Turn to the first page.

    PageControl1.ActivePage := PageControl1.Pages[0];

    SetPages; // Set Visible property for tabsheets.

    FormList := TList.Create; // List of available forms.

    // Create placeholder for each tab.

    for Index := 0 to PageControl1.PageCount -1 do

    FormList.Add(nil); // Empty placeholders.

    PageControl1Change(Sender); // Show 1st form.

    end;

    Figure 5: Setup for an embedded form list.

  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    8/348 January 2000 Delphi Informant Magazine

    the GetEmbeddedForm function, which creates and returns thecorrect TEmbeddedForm (seeFigure 7).

    Hiding a TEmbeddedForm is accomplished by responding to theTPageControls OnChanging event. The OnChangingevent has avariable Boolean parameter,AllowChange. As a result of callingTEmbeddedForms CloseForm function, a Boolean will be returnedthats used to set the state of AllowChange. The recommended

    practice is for TEmbeddedForm to perform its final editing andpost all data changes here. If TEmbeddedForm fails an edit or apost, a message should be displayed to the user explaining how tocorrect the error, and a Boolean with a value of False is returned.This prevents the user from turning to another page until the erroris corrected or the user cancels all pending changes.

    Generic processing comes in two flavors: processing that occurs toall embedded forms currently loaded (ProcessAllForms) and process-ing that occurs only to the embedded form currently displayed(GetForm). Save, cancel, and close, by default behavior, fall into theOccurs to all forms flavor of processing. ProcessAllFormsis themethod responsible for iterating through the list of loaded forms. Its

    a method that accepts a procedural variable that points to a methodthat accepts a TEmbeddedForm as a parameter (seeFigure 8).

    Printing falls into the other flavor of processing, which simplyinvokes the selected embedded forms Printmethod (seeFigure 9).

    Safety FirstTo ensure that changes the user has made are handled correctly whenthe TEmbeddedNotebookis closed, generic processing similar to Occursto all forms is used. Because data integrity is of paramount importance,the user cannot be allowed to close the TEmbeddedNotebookuntil allchanges have been saved or canceled by the user.

    The following is the chain of events for ensuring the integrity of thechanges the user has made:

    Handle TEmbeddedNotebooks OnCloseQueryevent.Invoke the loaded embedded forms CloseForm function.Set the value of TEmbeddedNotebooks CanClosevariable with thenet result of all the embedded forms SaveForm function calls.

    When TEmbeddedNotebooks OnCloseQueryevent is fired, itinvokes CloseEmbeddedNotebook(seeFigure 10), which invokesClearFormList(see Figure 11). ClearFormListiterates thoughFormList, invoking the CloseForm function for all loadedTEmbeddedForms. If CloseForm returns True, TEmbeddedNotebookfrees that TEmbeddedForm. Otherwise, the corresponding page isfocused and TEmbeddedNotebooks CanClosevariable is set toFalse, allowing the user to remedy the situation and continue.

    Great care has been taken to ensure the user cannot close theTEmbeddedNotebookwithout disposing of their changes. Save andCancel buttons have also been provided for the users convenience.

    // Return selected form if available;

    // otherwise raise exception.

    function TFormEmbeddedNoteBook.GetForm: TEmbeddedForm;

    var

    PageNum: Integer;

    begin

    PageNum := PageControl1.ActivePage.PageIndex;

    if notAssigned(FormList.Items[PageNum]) then

    try // If form doesn't exist.

    FormList.Delete(PageNum); // Clear nil placeholder.

    // Insert new form.

    FormList.Insert(PageNum, GetEmbeddedForm(PageNum));

    except

    on E: EFormNotFound do // New form creation failed.

    // Replace nil placeholder.

    FormList.Insert(PageNum, nil);

    end;

    // Return contents of FormList.

    Result := TEmbededForm(FormList.Items[PageNum]);

    end;

    Figure 6: Retrieving the selected embedded form.

    // Call a routine to return the embedded form

    // for a given page.

    function TFormEmbeddedNotebook.GetEmbeddedForm(

    PageNum: Integer): TEmbeddedForm;

    begin

    Result := FindEmbeddedForm(PageNum);if Result = nil then

    Raise EFormNotFound.Create('Form Not Found');

    end;

    Figure 7: Driving logic for requesting embedded forms.

    Delphi at Work

    // Process each EmbeddedForm within FormList, with the

    // function provided as the parameter aFunction.

    procedure TFormEmbeddedNoteBook.ProcessAllForms(

    aFunction: TFunctType);

    var

    Index: Integer;

    begin

    // For all the forms in the list...

    for Index := FormList.Count -1 downto 0 do

    // If form has been assigned...

    ifAssigned(FormList.Items[Index]) then

    // Call the passed method with the form.

    aFunction(TEmbeddedForm(FormList.Items[Index]));

    end;

    Figure 8: Driving logic for generic processing of all loadedembedded forms.

    // Print a screen dump report.

    procedure TFormEmbeddedNoteBook.btnPrintClick(

    Sender: TObject);

    begin

    GetForm.Print;

    end;

    Figure 9:An example of specific processing of a selectedembedded form.

    // Close all embedded forms. If all forms are closed and

    // destroyed, the result is True, otherwise it's False.function TFormEmbeddedNoteBook.CloseEmbeddedNotebook:

    Boolean;

    var

    Index: Integer;

    begin

    try

    Screen.Cursor := crHourglass;

    Result := True;

    Index := ClearFormList;

    if Index > -1 then

    begin

    Result := False;

    PageControl1.ActivePage.PageIndex := Index;

    Exit;

    end

    else

    FormList.Free;

    finally

    Screen.Cursor := crDefault;

    end;

    end;

    Figure 10: Ensure all modifications have been processed beforethe embedded notebook is closed.

  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    9/349 January 2000 Delphi Informant Magazine

    The Save and Cancel buttons Enabledproperty is set using a user-defined Windows message WM_REMOTEDSCHANGED. Settingthe lparm parameter of TMessagewith a 1 or 0, depending on thestate of the data or the presence of updates, communicates the state

    of the data (seeFigure 12). The definitions needed forWM_REMOTEDSCHANGED are in the TYPES.PAS unit.

    Polly Want a Cracker?Polymorphism allows a descendant class to operate differentlythan its parent class for the same method call. For any frameworkto be usable, it must allow for its default behaviors to bechanged. In nature, this is called evolution. Object-orienteddesign should always allow for the evolution and maturation ofclasses to adapt to changing business rules.

    Although every attempt has been made to default to usable behaviors,No plan ever survives contact with the enemy. Examples of defaultbehavior are the processing for save, cancel, and close functions,

    which assume the user wants to operate on all pages generically.

    The two most likely behaviors that will need to be changed are:Changing tabs forces the user to resolve data changes (save or can-cel); and, function button clicks process all pages or a single page.

    At the initial design of embedded forms, certain assumptions weremade. The first assumption was that the information contained onthe embedded forms was interrelated. Assuming the information wasinterrelated, the design stance was to force the user to either save orcancel all pending changes when they select a new tab. The secondassumption was that when the user clicked Save or Cancel, the actionwould take effect in all loaded forms.

    Altering the behavior of the embedded notebook when pages areturned can be accomplished in the following manner. In theTPageControls OnChangeevent, the CanChangeTabsfunction isinvoked. CanChangeTabsreturns the result from calling theTEmbeddedForms Closefunction. By overriding this behavior toreturn True, the pages of the notebook can be changed without hav-ing to resolve pending data changes.

    Altering the function button behaviors of TEmbeddedNotebook can

    be accomplished in the following manner. When theSave

    orCancel

    button is clicked, ProcessAllForms is invoked passing the SaveorCancelmethods, respectively. This behavior can be changed by over-riding the btnSaveClickand btnCancelClickevent handlers. Bothdata state buttons, btnSaveand btnCancel, invoke ProcessAllChangesby passing a procedural variable. If GetFormwere called in its place,the result would be the currently focused TEmbeddedForm, and itsSaveForm or CancelForm function could be invoked.

    Its very relevant to point out that, because TEmbeddedForm is a purevirtual class, it can very nicely be implemented as an interface.Implementing this scheme as an interface will allow pre-existingforms to be leveraged as embedded forms. (For more informationregarding interfaces, see Cary Jensens article Interfaces: An

    Introduction in theApril, 1998Delphi Informant Magazine.)

    ConclusionEmbedded forms provide the following business advantages as statedin the introduction of this article: improved team development byallowing multiple developers to work simultaneously on what theend user perceives as one form; improved memory management byonly loading those embedded forms users have selected; and,improved code reuse through the housekeeping and data integritycode supplied in the base class of TEmbeddedNotebook.

    The embedded forms described in this article were designed as ateam development technique, and as such they came to life through

    the joint efforts of a team. I would like to credit and thank the fol-lowing people for all their efforts in making embedded forms a suc-cessful technique:

    Anil Thomas for his implementation of the procedural versionof embedded forms.Mike Carkitto for his immeasurable help in implementing theobject-oriented version of embedded forms.Ed Hauswirth, my friend and mentor, and the person whoproved to me, much to my chagrin, that C++ is not the finalword in PC development.

    The files referenced in this article are available on the DelphiInformant Magazine Complete Works CD located inINFORM\00\JAN\DI200001JS.

    Delphi at Work

    // This routine calls the ProcessAllForms with CloseForm

    // method of all embedded forms. If succesful, the form is

    // freed and removed from the FormList.

    function TFormEmbeddedNoteBook.ClearFormList: Integer;

    var

    Index: Integer;

    begin

    Result := -1; // Set result to all deleted.

    ProcessAllForms(CloseForm); // Close all forms.

    // For all the forms in the list.

    for Index := FormList.Count -1 downto 0 do

    // If any forms are still loaded...ifAssigned(FormList.Items[Index]) then begin

    // Form cannot be closed, return the index.

    Result := Index;

    Exit; // Exit the for loop.

    end;

    end;

    Figure 11: Clean up of loaded embedded forms.

    // Light data-handling function buttons.

    procedure TFormEmbeddedNoteBook.SetDataButtons(

    State: Boolean);

    begin

    btnSave.Enabled := State;

    btnCancel.Enabled := State;

    end;

    // Handle buttons to notify user of the state of the data.

    procedure TFormEmbeddedNoteBook.DSChanged(

    var Message: TMessage);

    begin

    SetDataButtons(Message.lParam = wmpTrue);

    end;

    Figure 12: Data state-handling routines.

    John Simonini lives in the beautiful Pocono Mountains of Pennsylvania, with hislovely wife, two adorable sons, and two goofy dogs. John is a Senior Consultantin the employ of Source Technology Corp., and can be contacted by e-mail [email protected] or [email protected].

    http://9804.pdf/http://9804.pdf/http://9804.pdf/http://9804.pdf/http://9804.pdf/http://9804.pdf/
  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    10/3410 January 2000 Delphi Informant Magazine

    Distributed Delphi

    Multi-threading / DCOM / Database / Delphi 4, 5

    By Michael J. Leaver

    Request ThreadsOne Solution to Resource-sharing Problems

    In an ideal world, a server system would have unlimited resources available to an

    unlimited number of clients. That world doesnt exist just yet. For example, a database

    server can only support a limited number of client connections. The database, in this

    case, is the limiting resource.

    One way around this problem is the use ofTransaction Processing Monitors, e.g. MicrosoftsTransaction Server (MTS) or BEAs Tuxedo. Thisprovides for three-tier distributed processing, withthe TP Monitor taking care of resource sharing andother important things, such as transactions and dataintegrity. Indeed, Delphi 4/5 allows for easy creationof MTS objects, but MTS has its drawbacks and isnot the answer for all situations. Other TP Monitorsmay be overkill for a small system, or simply tooexpensive in licensing, development, and supportcosts. Theres also a steep learning curve to consider.

    This article describes a resource-sharing methodimplemented using Delphi 4 Client/Server Suiteand Microsofts Distributed Component ObjectModel (DCOM), with the resource being a data-base (the techniques apply equally well to Delphi5). The method could equally be applied to anymodern programming language and operating sys-tem that supports threads and remote procedurecalls. The resource need not be a database; itcould be a file server, printer, etc. The methoddescribed in this article is based on one used with-in a workflow-imaging trade finance productcalled Prexim, developed by China Systems.

    One-to-one ConnectionsIts a simple task to create a multi-threaded DCOMserver using Delphi 4 Client/Server Suite. Such a

    server is capableof providing ser-vices to a poten-tially unlimitednumber ofclients, with thebasic limitingfactors beingmemory andprocessing power.

    Servers are never that simple, however. For exam-ple, they usually rely on services provided by otherservers, such as a relational database server.Connections to these underlying servers are gener-ally expensive in terms of resource usage, andmust, therefore, be used sparingly. It isnt practicalfor each thread of a server to own a dedicatedconnection to another database server. For exam-ple, if there are 100 clients connected to a server,there will be 100 corresponding connections tothe underlying database server (see Figure 1).

    This is clearly impractical, and in many cases

    impossible. Its a huge waste of resources, as thosedatabase connections are unlikely to be used allthe time by all the clients. A better solution wouldbe for clients to share these precious resources.

    ThreadsHow do you share a connection to a databaseserver? A simple DCOM server would create adedicated connection to the database server foreach client connection. Whats needed is a pool ofconnections that all clients can share. This sharingmust be transparent to the clients who need notbe aware of such complexities of implementation.

    One solution is for the DCOM server to open anumber of connections to the database serverwhen it starts. The number of connections may beconfigurable and fixed once the DCOM serverstarts. Preferably, it should be dynamic andincrease or decrease on the fly depending uponthe load, i.e. load balancing.

    Now when a client connects to the DCOM server, itdoesnt create a new connection to the database serv-er. Instead it asks one of the threads created earlier todo the processing and return the results, which inturn will be passed back to the client (seeFigure 2).Figure 1: One-to-one connections.

  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    11/3411 January 2000 Delphi Informant Magazine

    There are two types of threads being discussed here. When a clientconnects to a multi-threaded DCOM server, the server spawns athread. This thread, which well call a client thread, is responsible

    for servicing that client. Therefore, each client connection to aDCOM server has its own client thread. Well call the threads thatare spawned when the server initializes and created before any clientsconnect to the server request threads. Its these limited requestthreads that are shared by the potentially unlimited client threads.

    The problem now is how to communicate with those request threads.We need an efficient method of inter-thread communication.

    CommunicationTo get straight to the point, one answer is to use good-old-fash-ioned First-In-First-Out (FIFO) queues. This avoids starvation,because no priorities are involved. Because were dealing withthreads, synchronization of access to these queues is critical to

    avoid difficult-to-find data corruption bugs. Using mutexobjects, semaphores, or many of the other objects available inmodern operating systems can solve this. Luckily for us, Delphiprovides a simple interface to these potentially difficult-to-useoperating system objects.

    Communication between these threads works in the following order.The DCOM server starts and spawns one or more request threads.Each request thread has its own connection to the database server,and when a client connects to the DCOM server, a client thread isspawned. The client thread cant talk directly to the database serverbecause it has no connection to it. For the client to talk to the data-base server, it must ask a request thread to perform that task on its

    behalf. The client thread does this by creating an inbound requestrecord, and placing it onto the inbound request queue.

    A request record describes what processing the client thread wantsthe request thread to perform. The client now waits for the result(as we will discuss later, it need not block on the wait). One ofthe request threads takes the item out of the inbound queue andperforms the task, which involves talking to the database servervia the connection the request thread owns, e.g. the BDE.

    The results are placed into an outbound request record and putonto the outbound request queue. An outbound request recordcontains the results of the processing, and any other relevant infor-mation, such as error messages. The client thread, which has beenwaiting for the result, takes the outbound request record off theoutbound queue. After any further processing, the results arereturned to the calling client (seeFigure 3).

    Spawning Request ThreadsWhen the DCOM server starts, it must spawn one or more requestthread for the connections to the database server. More threads givemore throughput, but increase memory use. Also, at some pointthe throughput from having more threads decreases. Ideally, some

    method of load balancing should be used, but thats beyond thescope of this article.

    The reqint.pas unit contains a function named RequestThreadsStartthat must be called by the DCOM server when it starts. Thisfunction initializes synchronization objects and spawns therequired number of request threads. It allows for initializationfailure of one or more request threads, i.e. five requests may beasked for, but, perhaps, only two request threads can actually becreated. The minimum number required is specified as an argu-ment to the function.

    The first thing RequestThreadsStart does is create the inbound and

    outbound queues, as well as the semaphores used to coordinate read-ing from and writing to those queues. It would be very inefficient topoll the queues waiting for new records to arrive, so a more efficientmethod using semaphores is used instead.

    Making RequestsThe client threads must create a request record detailing what isrequired of the request threads. This record must then be placedonto the inbound queue.Method1 andMethod2demonstrate howsimple this is (seeFigure 4). An inbound request record is filled withthe request details. Theng_RequestInterface.RequestSyncis called tosend the request to the request threads and wait for the result.

    The RequestSyncfunction is an interface for RequestIn and

    RequestOut, where RequestIn passes the request onto the requestthreads, and RequestOutchecks to see if the results are availablefor collection. IfMethod1 orMethod2wanted to make an asyn-chronous request, they would call RequestIn and RequestOutsepa-rately without calling upon RequestSync.

    How does a client thread know which results to collect from the out-bound queue? By taking results with the correct result handle. When arequest is made, its given a unique result handle that is simply a uniquenumber (unique for each server process), the current date and time, anda random string of characters. The date and time are stored in the resulthandle, so the server knows if a result has timed out. A random string ofcharacters is placed on the end to reduce the chance that another client

    Figure 2: Request thread connections.

    Figure 3:A diagram of the communication between the clientand request threads.

    Distributed Delphi

  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    12/3412 January 2000 Delphi Informant Magazine

    will try to steal anothers results. Remember that the result handle maybe used asynchronously at a later point by the client, so it makes senseto make it a single string instead of multiple strings.

    As explained earlier, semaphores are used to signal both clientthreads that results are available for collection, and request threadsthat a request has been made. g_SemInQis the inbound semaphore,andg_semOutQis the outbound semaphore.

    Processing RequestsTRequestThread.Execute contains a simple loop that shows whateach request thread does. First it takes the first request record it canget from the inbound queue (via the function TakeFromQueue).Next it processes the item, places the results onto the outboundqueue, and frees the memory used by the inbound request, as itsno longer required (seeFigure 5).

    Collecting ResultsThe TRequestThread.RequestOutmember function is used to collect theresults from the request threads. Supplied with a request handle (previ-ously created when the client thread made a request via RequestIn) anda place to store the results, it first checks to see if the request has timedout. If a request is over a certain age, that requests results cant be col-lected because they will have been garbage collected and removed fromthe outbound queue. If the results werent purged, the outbound queuewould eventually grow too large if the client threads didnt collect theirresults. Remember that if an asynchronous request had been made, theclient may be on another machine. The client program may exit beforecollecting the results, or the network connection may go down. Thereare many reasons why results may never get collected. Deciding if aresult may not get collected isnt possible.

    Having resolved that the request isnt too old, the outbound queue sem-aphore (g_SemOutQ) is checked. If its signaled, then there are results inthe queue, else the queue is empty and hence the results arent availableyet. The queue is searched for a matching request that hasnt been han-dled, i.e. another thread hasnt already taken the results. Because the listis locked, there is no need for mutexes or other thread synchronizationmethods when its searched. Only one request thread can search theoutbound queue at a time. A result cant be taken from the queue dur-ing the search, so its handled state is flagged as being True.

    If the result was found, it can then be removed from the queue andreturned to the client thread. When no result is found, the out-

    bound queue semaphore must be re-signaled to indicatethat there are still pending results. Without re-signaling,other request threads would assume the queue is empty.

    AsynchronousImagine an extension to Delphi that allowed asynchro-nous function calls without the programmer needing toworry about how its actually implemented.Figure 6shows that the asynchronous function can be called as per

    any other function with no special considerations.

    Control returns to the procedure before the function hascompleted and returned its result. The procedure wouldonly block waiting for the result if it tried to use one ofthe values returned, or set by the asynchronous function,e.g.AsyncReturn or out_arg. Wouldnt that be a very use-ful extension to any language?

    With request threads, its possible to do this now within aprocess and outside of the process. For example, asynchronous COMcalls are possible with request threads. The client would call theCOM method as usual, but instead of getting its result, the clientwould get a unique handle. At a later point, the client would call the

    COM method again with the handle supplied earlier and would nowget the results back. The client who originally made the call need notsupply the handle. Another client process on another machine couldcollect the results as long as it used the correct handle.

    // Take out of g_InQ and place items in g_OutQ.

    while not Self.Terminated do begin

    inq := TakeFromQueue;

    if Self.Terminated then

    Break;

    // Process it.

    outq := ProcessItem(inq);

    if outq = nil then

    Continue;

    // Add it to the out Q.

    AddToQueue(inq^.request_in.rhandle, outq);

    Dispose(inq);end;

    Figure 5:TRequestThread.Execute showing what each requestthread does.

    Figure 4: The example DCOM server displayed in the Type Library editor.

    Distributed Delphi

    functionAsyncExample(const in_arg: Integer;

    var out_arg: string): Boolean; Async;

    begin

    // Do some processing. Set the value of out_arg.

    // Return some result.

    Result := True;

    end;

    procedure UsingTheExample;

    var

    AsyncReturn: Boolean;

    SomeString: string;

    begin

    // Call our async function.

    AsyncReturn := AsyncExample(123, SomeString);

    // At this point we can continue doing something

    // else while that function does whatever it does.

    // Let's get the result.

    ifAsyncReturn then

    AnotherString:= 'The string result is:' + SomeString

    else

    AnotherString:= 'Error';

    end;

    Figure 6: Calling an asynchronous function with no changein method.

  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    13/3413 January 2000 Delphi Informant Magazine

    Unfortunately, the client must be aware that the call is asynchro-nous and must store the handle. There is also the possibility thatthe client will never collect the result, so some garbage collectionmust be performed periodically within the server. Security mustalso be taken into consideration so that clients dont try to stealresults meant for others.

    ImprovementsThere are many ways to improve upon the simple method shown

    in this article, and there are still many more things that haventbeen described.

    First, it must be said that the example code isnt what it should beand can clearly be improved upon. Delphi is an object-orientedlanguage, but the example code given doesnt make much use of itsobject-orientation. A generic request threads class would be a verypowerful class indeed.

    Request threads can span across multiple calls. For example, a clientthread may need to make multiple calls to the same request threadbecause that request thread has state, e.g. a cursor connection to thedatabase server, or perhaps even an open database transaction. Thisis possible within request threads simply by locking a request thread

    to one client thread via a unique handle. This handle can be passedeach time the client thread needs to use a specific thread. Locking athread doesnt necessarily mean other client threads cant use it whileits idle. For example, if the request thread is locked because it has acursor open, it doesnt mean other client threads cant use it to readother tables as long as the cursor isnt affected. It all gets complicatedand other issues arise, such as unlocking threads, orphaned requestthreads, etc. For that reason, its not described in this article.

    Because requests are defined within records and are used withinqueues, it allows for easy recording of requests for later playback. Forexample, a database transaction could be performed by recordingrequests and storing them in a list instead of sending them to

    request threads one by one. Later, the entire list could be sent to arequest thread for immediate processing as a single block of requests.The block of requests could be processed within one database trans-action within the request thread.

    The example code also doesnt show how to ensure that the DCOMserver has completed initialization before client calls are accepted. Ifa client call is received before the queues have been set up, for exam-ple, then problems may occur. Request records may need to containa large amount of data, as they need to support all types of requests.To reduce memory usage, variant records should be used, or perhapseven variant variables.

    Request threads beg for tuning parameters, e.g. TTL (time-to-live)

    values for request in the queues, time-out values when waiting forresults, optimal number of threads to use, etc. This hasnt been givenin the example code, but its not difficult to add. Whats more difficultis automatically creating the best values for a specific implementation.

    ConclusionRequest threads allow a simple way to make asynchronous calls toshared resources. Delphi allows easy creation of such a methodbecause of its strong support for COM, threading, and access to theWin32 API.

    The source code, which is available for download (see end of article fordetails) includes a demonstration COM out-of-process server and a

    client test program. To enable threading on the server side, and so allow-ing the server to process multiple client calls simultaneously, theBorland-supplied source code thrddcf.pas has been employed. Withoutthis, the clients requests would be queued by the server and processedserially, one by one. This file may be found in the directoryDemos\Midas\Pooler. Because of the multi-threaded nature of the serv-er, it cant be used with Windows 95. It should be used on WindowsNT only, whereas the client program can be used on Windows 95 orNT. Before using the client program, the server must be registered. This

    can be done by executing the server with the command line argument/regserver (or more easily from the Delphi IDE).

    The files referenced in this article are available on the DelphiInformant Magazine Complete Works CD located inINFORM\00\JAN\DI200001ML.

    Distributed Delphi

    Michael J. Leaver is the Product Manager for the China Systems Prexim system.China Systems is a world leader in trade finance technology. Prexim is a workflowimaging solution for trade finance processing written using Delphi 4 C/S. Michaelcan be reached via e-mail at [email protected].

  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    14/3414 January 2000 Delphi Informant Magazine

    Sound + Vision

    Multimedia / Experts / Tool Services / Delphi 1-5

    By Alan C. Moore, Ph.D.

    A Multimedia Assembly LinePart II: Generating a Component

    Last month we began building a Delphi sound expert (Wizard) that produces multime-dia components. Based on a simpler expert, SndExprt 1.0, it adds sound-playing(.WAV file) capabilities to any component. We also dealt with the expert structure and

    the user interface. Before we take a detailed look at the code-generating engine, lets

    review that structure.

    The sound-generating property, SoundPlayEvents,and its subproperties are arranged in a hierarchy.The main property added to each component,SoundPlayEvents, contains a series of properties,XSoundPlayEvent, where X is the name of one ofthe sound events supported. These properties,such as ClickSoundPlayEvent,EnterSoundPlayEvent, and ExitSoundPlayEvent,contain their own subproperties. Those that man-age the playing of sounds are shown inFigure 1.

    With SoundSource, the flags infdwSound

    (SND_ALIAS, SND_FILENAME, andSND_RESOURCE) determine how the Filenameproperty will be interpreted as a file name, a

    resource identifier, or an alias for a system event.Filename is a string holding the name of the file,resource, or other location. All of these subproper-ties are included in the ?SoundPlayEvent property,where ? is the name of one of the sound events.

    How do we program our expert to generate acomponent with this new composite property?Youll notice we use several types and data struc-tures from the unit, SndTypes.pas, introduced inlast months installment. Now lets explore some ofthe details.

    Programming the Expert EngineIn writing an expert, its engine is where the realprogramming generally takes place. Its also where Ihad to write most of the new code for the updatedexpert. In both the original and revised experts, Ifound it helpful to have a prototype of the type ofcomponent the expert would generate to which wecould refer. That prototype included all the newfunctionality and properties weve been discussing.

    After youve made all your choices and clickedFinish, the code for your new component is gen-erated (CompGen1.pas) and opened in the

    Delphi IDE editor. There you can look at thecode, edit it, or save it. Lets concentrate on thecomponent-building code. Here is the mostimportant routine in the engine:

    procedureWriteALine(StringIn: string);

    begin

    TempMemoStr.Add(StringIn);

    end;

    TempMemoStris a StringList that collects all ofthe code to be saved in a file. Because we callthis method for every line of generated code, theFigure 1: Subproperties of XSoundPlayEvent.

    Property Description

    SoundSource Type of sound source to which the FileName parame-ter points; enumerated type: TSoundSource (ssAlias,ssAlias_ID, ssFilename, ssMemory, ssResource, ssNone).The 16-bit version with sndPlaySound: (ssFilename,ssMemory). Its associated with PlaySounds fdwSoundflags in fdwSound (SND_ALIAS, SND_FILENAME, andSND_RESOURCE).

    Filename A string associated with PlaySounds first parameter,lpszSound, a pointer to a null-terminated string that

    specifies the sound to play.Yield A Boolean property that determines whether the

    requested sound can interrupt a sound thats playing.hmodValue Of type HMODULE. Associated with the second para-

    meter (hmod) of the PlaySound function. Its the han-dle of the executable file (DLL or EXE) that containsthe resource to be loaded. Unless SND_RESOURCE isspecified, this parameter must be set to nil.

    ?SoundPlayOption Of type T?SoundPlayOption, where the ? repre-sents one of the event classes, e.g. Click, Enter, orExit. All ways of playing a .WAV file (synchronously,asynchronously, in a loop) are supported.

  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    15/3415 January 2000 Delphi Informant Magazine

    CompGen1.pas unit is now too large to include in its entirety.Instead, well highlight some of the more interesting routines.Compared to the earlier version, the WriteTypesmethod isexpanded considerably. It writes the support types and a specificSound Option Type for each enabled event. In theWriteSoundOptions method, lines such as the following read thedata collected from running the expert, and produce the propertypes by using strings associated with the event to name them:

    if spPlayReturn in

    SoundFactorsArray[j].SoundPlayOptions then

    AddSoundOption(EventPrefixes[j] + 'spPlayReturn');

    The EventPrefixesarray stores prefixes for each event with an appro-priate name: ck, dd, and do are prefixes for the Click,DragDrop, and DragOverevents, respectively.

    There are other significant changes in this new version that arenecessary to create the new property structure in the target com-ponents created. In the previous version, we simply added twoproperties to each generated component. Here we add just one,but its a monster with multiple class properties, each of whichhas a number of subproperties.Figure 2 shows the new property

    from a newly produced component in the Object Inspector witha few of these subproperties shown.

    The new Sound Option Types we just discussed provide the basis forthe most important subproperties used under each event subproperty.The work of writing the main component class and the host of eventsubclasses is done in the WriteNewClassesprocedure. There are severalnested procedures within this method, including WriteMainClass,which writes the new component class, and WriteSoundClasses, whichwrites a new class for each Sound Event. Lets examine that procedure,as it demonstrates several interesting issues. Pay particular attention tothe nested procedure within WriteSoundClasses, WriteSoundClass. This

    latter procedure, shown in

    Listing One (on page 17),writes each of the individualsound event classes.

    As you can probably guess,this one procedure writes thebulk of the new propertywith all of its subproperties.The variable or changingportions of this code dependupon a number of arrays ofstrings: BasicEventNames,which includes the sameevent names used with the

    check boxes (Click,DragDrop, etc.);EventNames, which is thename of the actual methodused to fire the event(DoEnterinstead of Enter,for example); and a smallerarray, ParametersArray,which contains the stringsused for parameters whenthe inherited methods arecalled in the overriddenevent methods.

    In this code, and in code we examined last month, EventMax isused a great deal. This constant represents the number of eventssupported minus one, because all of the iterations are zero-based.Its used in most of these arrays, as well as in most iterations.By using this constant, it will be easy to change this code byadding or deleting supported events. For example, take a lookat WriteConstructors. A procedure nested within it,WriteSoundClassesConstructors, iterates through all possible eventsand writes a constructor for each one the user has selected.

    Similarly, the WriteEventHandlersprocedure writes event handlersfor those events.

    The code in Listing One may seem like a lot of code for producing aseries of rather simple classes, but it does more than just write all ofthese classes. Lets start with the individual classes. First, we need todetermine which events will be sound-enabled by iterating throughthe check-box flags with:

    for j := 0 to EventMax do

    if EventCheckBoxArray[j]= ecbsChecked then

    ...

    Then we build an array that contains information on which events

    are sound enabled. We use the NewClassesvariable to monitor howmany are actually used. As we iterate through all of the possibleevents, we build that array:

    NewClassArray[NewClasses] := BasicEventNames[j];

    BasicEventNamescontains the basic names of the enabled events.We use this array later to write the implementation code. The codethat follows simply writes the remainder of the class declaration.When we reach the last line, we call a nested procedure with:

    WriteSoundPlayEventsClass;

    This procedure writes another class the main class that contains allof the individual sound-enabled events and their subproperties. Withthis procedure, we make good use of the NewClassArrayto create aproperty for each sound-enabled event. Figure 3 shows the generatedcode for this class.

    Youll note that we have just one event the Clickevent. We couldjust as easily have three or 12. We must create one more class thecomponent class. Some of the information for this class comes fromthe first page of our expert (seelast months article),its class name,and its ancestor class. Other than that, the code is identical for anycomponent created with this expert. Figure 4 shows a main compo-nents class declaration.

    Upon casual examination, this code is deceptively simple. Keep inmind, however, the many subproperties within theSoundPlayEvents property we just discussed. If you enable many

    Figure 2: Main property with afew of these subproperties in anew component shown in theObject Inspector.

    TSoundPlayEvents = class(TPersistent)

    private

    FClickSoundPlayEvent : TClickSoundPlayEvent;

    public

    constructor Create(AOwner: TComponent);

    published

    property ClickSoundPlayEvent : TClickSoundPlayEvent

    read FClickSoundPlayEvent write FClickSoundPlayEvent;

    end;

    Figure 3: The TSoundPlayEvents class in a generated component.

    Sound + Vision

    http://9912cd.pdf/http://9912cd.pdf/http://9912cd.pdf/http://9912cd.pdf/
  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    16/3416 January 2000 Delphi Informant Magazine

    events and then view all of the details, it could take up most ofthe space in the Object Inspector (again, refer toFigure 2).

    Next, we need to create constructors for these classes. Thatchore is rather straightforward, so well skip a detailed discussionof it. One issue worth mentioning is that we need a way tohandle the options on our pop-up dialog box concerning defaults(.WAV files or Yield). In the individual event classes, we handlethat situation with the following code, using data collected in

    our expert:

    if SoundFactorsArray[j].WavFileDefault then

    WriteALine(' FSoundFile := ''' +

    SoundFactorsArray[j].DefaultWavFile + ''';')

    else

    WriteALine(' FSoundFile := ''*.wav'';');

    WriteALine(' FSoundSource := ssFilename;');

    if SoundFactorsArray[j].Yield then

    WriteALine(' FYield := True;')

    else

    WriteALine(' FYield := False;');

    Even more interesting to examine is the method that actually doesthe work, in this case the Clickmethod (seeFigure 5).

    There are various steps involved here. First, we use the local function,GetfdwSoundValue, to determine the sound source type (SoundSource),and return the corresponding flag. We then plug that flag into the lastparameter of the PlaySoundfunction along with other appropriateflags. Unless we use the SND_RESOURCE flag, the hmodValueis setto zero. One option (that we dont enable here) concerns defaultsounds: snd_NoDefault is always set. Of course, we could add an addi-tional subproperty. That will have to wait for the next version.

    ConclusionWe havent investigated every detail of the code-generating engine.However, the other procedures use similar approaches and thesesame string arrays to accomplish their purpose. This version repre-sents a major enhancement of the previous one. Weve extended theexpert to handle other standard events besides the Clickevent.Unfortunately, we havent provided a way to handle events thatmight apply to just a few components, let alone new events we writeourselves. This would be difficult to accomplish.

    It would also be nice to have a property editor to use with our newSoundsEventsproperty. Again, this would be tricky, because eachproperty will have its own character with a different list of subclass-es, as well as a different list of properties within each subclass. Still,

    there might be an appropriate and elegant means of doing this. Iplan to continue to update this expert, so be sure to let me knowwhat youd like to see. In the meantime, enjoy using the manysound-enabled components youll create using this tool.

    The files referenced in this article are available on the DelphiInformant Magazine Complete Works CD located inINFORM\00\JAN\DI200001AM.

    TSoundButton = class(TBitBtn)

    private

    FSoundPlayEvents : TSoundPlayEvents;

    public

    constructor Create(AOwner: TComponent); override;

    procedure Click; override;

    published

    property SoundPlayEvents : TSoundPlayEvents

    read FSoundPlayEvents write FSoundPlayEvents;

    end;

    Figure 4:A new components main class declaration.

    Sound + Vision

    procedure TSoundButton.Click;

    var

    fdwSoundValue : DWORD;

    AhmodValue : HMODULE;

    AFileName : array [0..255] of Char;

    function GetfdwSoundValue(

    ASoundPlayEvent: TSoundPlayEvent): DWORD;

    begin

    case ASoundPlayEvent.SoundSource of

    ssFilename: Result := SND_FILENAME;

    ssMemory: Result := SND_MEMORY;

    ssAlias: Result := SND_ALIAS;

    ssAliasID: Result := SND_ALIAS_ID;

    ssResouce: Result := SND_RESOURCE;

    end;

    end; { GetfdwSoundValue }

    begin

    with SoundPlayEvents do begin

    fdwSoundValue := GetfdwSoundValue(ClickSoundPlayEvent);

    if (fdwSoundValue = SND_RESOURCE) then

    AhmodValue := ClickSoundPlayEvent.hmodValue

    else

    AhmodValue := 0;

    StrPCopy(AFileName, ClickSoundPlayEvent.SoundFile);

    if ClickSoundPlayEvent.yield then

    fdwSoundValue := (fdwSoundValue OR SND_NOSTOP);

    case ClickSoundPlayEvent.ClickSoundPlayOption of

    ckspPlayReturn:

    PlaySound(AFileName, AhmodValue, fdwSoundValue or

    snd_Async or snd_NoDefault);

    ckspPlayNoReturn:

    PlaySound(AFileName, AhmodValue, fdwSoundValue or

    snd_Sync or snd_NoDefault);

    ckspPlayNoSound: ;

    ckspPlayContinuous:

    PlaySound(AFileName, AhmodValue, fdwSoundValue or

    snd_Async or snd_Loop or snd_NoDefault);

    ckspEndSoundPlay:

    PlaySound(nil, AhmodValue, fdwSoundValue or

    snd_Async or snd_NoDefault);

    end; { case }

    end;

    inherited Click;

    end;

    Figure 5:An event handler override to play a sound.

    Alan Moore is a Professor of Music at Kentucky State University, specializing in musiccomposition and music theory. He has been developing education-related applica-tions with the Borland languages for more than 10 years. He has published a num-ber of articles in various technical journals. Using Delphi, he specializes in writingcustom components and implementing multimedia capabilities in applications, par-ticularly sound and music. You can reach Alan on the Internet at [email protected].

  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    17/3417 January 2000 Delphi Informant Magazine

    Begin Listing One WriteSoundClasses routineprocedureWriteSoundClasses;

    var

    j, NewClasses : Integer;

    NewClassArray : array [0..EventMax] of string;

    procedureWriteSoundPlayEventsClass;

    var

    i: Integer;

    begin

    WriteALine(' TSoundPlayEvents = class(TPersistent)');

    WriteALine(' private');

    for i := 0 to (NewClasses-1) do

    WriteALine(' F' + NewClassArray[i] +

    'SoundPlayEvent : T' + NewClassArray[i] +

    'SoundPlayEvent;');

    WriteALine('public');

    WriteALine(' constructor Create(AOwner: TComponent);');

    WriteALine(' published');

    for i := 0 to (NewClasses-1) do begin

    WriteALine(' property ' + NewClassArray[i] +

    'SoundPlayEvent : T' + NewClassArray[i] +

    'SoundPlayEvent read F' +

    NewClassArray[i] + 'SoundPlayEvent');

    WriteALine(' write F' + NewClassArray[i] +

    'SoundPlayEvent;');

    end;

    WriteALine('end;');WriteALine('');

    end; { WriteSoundPlayEventsClass }

    begin { WriteSoundClasses }

    NewClasses := 0;

    for j := 0 to EventMax do

    if EventCheckBoxArray[j]= ecbsChecked then

    begin

    NewClassArray[NewClasses] := BasicEventNames[j];

    inc(NewClasses);

    WriteALine(' T' + BasicEventNames[j] +

    'SoundPlayEvent = class(TSoundPlayEvent)');

    WriteALine(' private');

    WriteALine(' FSoundFile : TFileName;');

    WriteALine(' FSoundSource : TSoundSource;');

    WriteALine(' FYield : Boolean;');

    WriteALine(' F' + BasicEventNames[j] +

    'SoundPlayOption : T' +

    BasicEventNames[j] +'SoundPlayOption;');

    WriteALine(' public');

    WriteALine(

    ' constructor Create(AOwner: TComponent);');

    WriteALine(' published');

    WriteALine(' property SoundFile: TFileName ' +

    'read FSoundFile write FSoundFile;');

    WriteALine(' property SoundSource: TSoundSource ' +

    'read FSoundSource write FSoundSource ' +

    'default ssFilename;');

    WriteALine(' property Yield : Boolean ' +

    'read FYield write FYield default False;');

    WriteALine(' property ' + BasicEventNames[j] +

    'SoundPlayOption : T' BasicEventNames[j] +

    'SoundPlayOption read F' + BasicEventNames[j] +'SoundPlayOption write F' + BasicEventNames[j] +

    'SoundPlayOption;');

    WriteALine('end;');

    WriteALine('');

    end;

    WriteSoundPlayEventsClass;

    end;

    End Listing One

    Sound + Vision

  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    18/3418 January 2000 Delphi Informant Magazine

    In DevelopmentControl Panel Apps / DLLs / Configuration Programs / Delphi 2-5

    By Peter J. Rosario

    Control Panel AppletsIntegrating Configuration Programs with Windows

    Control Panel applets are the small programs that are visible in, and run from,Windows Control Panel. They are typically used to configure hardware, the operat-ing system, utility programs, and application software. This article shows you how to cre-

    ate and install your own Control Panel applet.

    Why create your own custom Control Panelapplet? Many programs you develop require con-figuration. You probably store the configurationparameters in an .INI file, the registry, or a data-base. Some programmers add code to their mainprograms to allow users to display, change, andsave configuration parameters, perhaps making itaccessible through an Options menu choice.

    However, there are many reasons why you shouldconsider placing this code in a separate Delphiproject. Placing the configuration code in a sepa-

    rate Delphi project not only makes it more modu-lar, and thus easier to debug, it also makes it moreamenable to parallel development within a teamof programmers. The separate Delphi project willalso exhibit high cohesion and low coupling.

    By placing the configuration code in a separateDelphi project, you can more easily prevent end-user access to the code. You may want just anadministrator to be able to change the configura-tion parameters. If this is the case, you couldinstall the compiled project on just the adminis-trators machine or, if it is on a file server, youcould modify the files execute permission so that

    only administrators can execute it.

    Placing the configuration code in a separateDelphi project allows you to convert it to aControl Panel applet, making it appear more pro-

    fessional and integrated with Windows.Users and administrators are used to look-ing in Control Panel when they want toconfigure something. Why should it be anydifferent when it comes to your program?

    A Control Panel applet is a special .DLLthat resides in the Windows system directo-

    ry. In this article, well discuss implementing asimple dialog box run from an .EXE, convertingthe dialog box to a .DLL, and converting the.DLL into a special .DLL with the file extension.CPL. (All three projects are available for down-load; see end of article for details.)

    Simple Dialog Box ExecutableThe first Delphi project builds an executable and usesonly one unit/form: AlMnDlg.pas/AlMnDlg.dfm(see Figure 1). The project uses an icon that is differ-ent from the default Delphi torch-and-flame icon.

    The forms name isMainDlg, its BorderStylepropertyis set to bsDialog, and it contains two buttons. EachbuttonsModalResultproperty is set to somethingother than mrNone. When were done, this dialogbox will be what appears when you activate theapplet from the Control Panel.

    Dynamic Link LibraryThe second Delphi project builds a .DLL and usesthe same form as the first project. It also adds a sec-ond unit, AlMain.pas, that implements the Executeprocedure. The units interface section contains theExecuteprocedures header so it can be used outsidethe unit. The procedure uses the stdcall calling con-

    vention, because it will be exported from the .DLL.Executesimply creates the dialog box, shows itmodally, and destroys it. Its definition is shown here:

    procedure Execute; stdcall;

    begin

    AlMnDlg.MainDlg :=

    AlMnDlg.TMainDlg.Create(nil);

    try

    AlMnDlg.MainDlg.ShowModal;

    finally

    AlMnDlg.MainDlg.Free;

    AlMnDlg.MainDlg := nil;

    end;

    end;

    Figure 1: The Control Panel appletsmain dialog box.

  • 8/13/2019 Delphi Informant Magazine Vol 6 No 1

    19/3419 January 2000 Delphi Informant Magazine

    It was easy enough to test the first Delphi projects compiled exe-cutable, Applet.exe. All we had to do was run it. To test the secondDelphi projects compiled executable, Applet.DLL, well have tobuild a program whose sole purpose is to exercise the .DLL. This isdone in AlDriver.dpr. It contains a single form namedMainDlgthat resides in ADMain.pas/ADMain.dfm, and has a single Executebutton (seeFigure 2).

    The buttons OnClickevent handler calls the Executeprocedure

    exported by Applet.DLL:

    procedure TMainDlg.ExecuteButtonClick(

    Sender: System.TObject);

    begin

    ADMain.Execute;

    end;

    For the Applet DLLs Executeprocedure to be visible in theADMain unit, it must be imported from the .DLL. Once itsimported, it appears to the rest of the code to actually exist in theunit at the location of the importstatement. (This explains whythe fully qualified procedure name,ADMain.Execute, works.) Theprocedure is statically imported using the external directive:

    procedure Execute; external 'Applet.DLL';

    Because no path is specified, Windows uses an algorithm tosearch for the .DLL. One of the directories searched is theWindows system directory. Another is the directory from whichthe application loaded. In fact, that directory is searched first,and is the preferred directory to use. The search algorithm isdocumented in win32.hlp (on the Indextab, search onLoadLibrary).

    Its simple to go into AlDriver.dprs Project | Options menu choice,change to the Directories/Conditionals page, and type the directory

    path to where Applet.DLL is located in theOutput directory

    combobox. If you do so in your driver project, it will automatically besaved in the same directory as the .DLL whenever your driver exe-cutable is built.

    To test Applet.DLL, place AlDriver.exe into Applet.DLLs direc-tory if its not already there. Run AlDriver.exe and click theExecute button. You should see the original dialog box on screen.Because its shown modally, and both buttons have modal resultsset to something other than mrNone, clicking either one of themcloses (and frees) the dialog box. When youre done testing the.DLL, close AlDriver.exe.

    Congratulations! You now know how to place a form in a .DLL.

    Only a few more steps are required to convert the .DLL into aControl Panel applet.

    Control Panel AppletTo test the second Delphi project, we had to build a driver pro-gram to load the .DLL, import a subroutine, and execute the sub-routine. To test the final Delphi project (the one that builds theControl Panel applet), we wont have to build a driver program.Why? Windows itself will be the driver program. This controllingapplication is usually Control Panel itself, or the Control Panelfolder in Windows Explorer. Like our driver program, the con-trolling application will have to load the applet, import a subrou-tine, and execute the subroutine.

    To load the Control Panel applet, thecontrolling application must first findit. The simplest way to allow the con-trolling application to find the appletis to copy it to the Windows systemdirectory. How does it distinguishbetween Control Panel applet .DLLsand regular .DLLs? Control Panelapplet .DLLs have the extension .CPL

    not .DLL. You can make your pro-ject automatical ly use the .CPL extension instead of the .DLLextension. To do so, select Project | Options. On the Applicationpage, type cpl in the Target file extension edit box.

    To import a subroutine from a .DLL, the controlling applicationmust use the subroutines name or index. Windows uses thename, and looks for a function named CPlAppletwith the follow-ing signature:

    LONG APIENTRY CPlApplet(

    HWND hwndCPl, // Handle to Control Panel window.

    UINT uMsg, // Message.

    LONG lParam1, // First message parameter.

    LONG lParam2 // Second message parameter.

    );

    The code is shown in C because its from the Microsoft Help filewin32.hlp (refer to the References section at the end of this articlefor more information). The Object Pascal equivalent is:

    function CPlApplet(hwndCPl: Windows.THandle;

    uMsg: Windows.DWORD; lParam1, lParam2: System.Longint):

    System.Longint; stdcall;

    This function header is declared in cpl.pas. If you have theProfessional or Client/Server versions of Delphi, you have access

    to the source for the cpl.pas unit. You dont need it to createControl Panel applets, but its heavily commented, and thereforeprovides good documentation.

    Unlike our Executeprocedure, the CPlAppletfunction is called manytimes and performs multiple functions, depending on what parame-ter values are passed. The table inFigure 3 shows the possible valuesfor the uMsgparameter. (The information found in this table comesmostly from win32.hlp.)

    Each CPL_XXX constant is defined in the CPL unit. Theexample projects CPlAppletfunction uses these constants (seeFigure 4).

    As the description for CPL_GETCOUNT indicates, itspossible to implement multiple Control Panel applets (i.e.dialog boxes) per .CPL. The example project, however, imple-ments only one.

    After you tell Windows how many dialog boxes your .CPLimplements, it calls the CPlAppletfunction again with uMsgequal to CPL_INQUIRE once for each dialog box. The lParam1parameter tells you which dialog box the function call is for. Itwill be numbered from 0 to NumberOfDialogBoxes- 1. Becausethe example project only implements one applet, the CPlAppletfunction will only be called once so it doesnt handle the caseswhere lParam1 is other than 0.

    Figure 2: The driverexecutables main dia-log box.

    In Development

  • 8/13/2019 Delphi Info