the persistence of objects.pdf

Upload: jd

Post on 04-Jun-2018

230 views

Category:

Documents


0 download

TRANSCRIPT

  • 8/14/2019 The Persistence of Objects.pdf

    1/401 October 1997 Delphi Informant

    October 1997, Volume 3, Number 10

    The Persistenceof ObjectsPresenting a Generic,

    Registry-Aware Component

    Cover Art By: Louis Lillegard

    ON THE COVER

    5 The Persistence of Objects Robert VivretteUsers hate to perform boring tasks over and over again. And so doprogrammers! Learn to use Delphis RTTI capabilities to create generic,object-oriented, registry-aware components without drowning indrudgery yourself.

    FEATURES

    14 Informant Spotlight

    Down to the Metal Jay ColeHeres an exotic one. Tired of C/C++ programmers claiming suprema-cy in low-level programming? Would you believe using assembly to cre-ate a dynamic (run-time) compiler?

    22 Greater Delphi

    Whats Multi-GenerationalArchitecture, Anyway? Bill ToddThis fifth and final installment explains what really sets InterBase apart,by exposing the problems its structure was designed to solve. Feel lucky?Grab a wad of cash, and get ready for ATM roulette.

    25 DBNavigator

    Trying on TeeChart Cary Jensen, Ph.D.Theres more to Delphi 3 than its marquee players. Among theunsung is TeeChart, which builds charts at either design time or run

    time, from any data available to your application. Heres how.

    30 At Your FingertipsSearching Multiple Directories, etc. John P. GambrellThis months tips include the finer points of the FileSearchfunction and the importance of good parenting.

    REVIEWS

    32 The Birth of RaptorProduct Review by Alan Moore, Ph.D.

    37 Hidden Paths of Delphi 3Book Review by Alan Moore, Ph.D.

    37 Special Edition Using Delphi 2 and

    Special Edition Using Delphi 3Book Review by Alan Moore, Ph.D.

    DEPARTMENTS

    2 Delphi Tools4 Newsline40 File | New by Richard Wagner

    http://97index.pdf/
  • 8/14/2019 The Persistence of Objects.pdf

    2/402 October 1997 Delphi Informant

    DelphiT O O L S

    New Products

    and Solutions

    SPIS Ltd, makers of theTCompress Component Suitefor Delphi and C++Builder,has released TCompLHA 2.0and TCompress ComponentSet 3.5.TCompLHA supports

    Delphi 1, 2, and 3, as well asC++Builder. It helps createand manage archives compati-ble with the freeware LHArcand LHA utilities archives

    which can also be used by util-ities including WinZip.

    One-step methods such asScan, Compress, Expand, Delete,and Verifysimplify archivemanagement. Key propertiessuch asArchiveName,CompressionMethod, Confirm,and FilesToProcessprovide

    control over how files areprocessed, including wildcardexpansion and user interfacecustomization. TCompLHAalso offers segmented (multi-disk) archive processing.Additional features such asfull archive verify and safemode processing enableTCompLHA to be used incommunications applicationsfor the Internet.

    TCompLHA ships with ademonstration, sourceexamples, and Help files.

    SPIS Ltd also announced anew version of their com-pression components forDelphi 1, 2, and 3, andC++Builder. In additionto new Delphi 3 andC++Builder support,TCompress 3.5 adds a com-pression method, a rich text

    BLOb compression compo-nent, and new utility func-tions for more flexiblearchive management.

    TCompress 3.5 providesnative components for thecreation of multi-file com-pressed archives, as well asdatabase, file, resource, andin-memory compressionusing streams. Two com-

    pression methods (RLE andLZH) are built in, withhooks for adding customcompression formats.

    TCompress 3.5 alsoincludes drop-and-playcomponents for automaticdatabase BLOb, image, richtext, and memo compres-sion, based on the DelphiVCLs TDBMemo,TDBRichText, and

    TDBImagecomponents.TCompress 3.5 includes ademonstration, source code,examples, and Help files.

    SPIS LtdPrice: TCompLHA 2.0 is US$65 forregistration; US$40 for TCompLHA com-ponent source. TCompress ComponentSet 3.5 is US$65 for registration;US$40 for TCompress componentsource; and US$34 for source of the

    compressed DB controls.Fax: +64-3-384-5138E-Mail: [email protected] Site: http://www.spis.co.nz

    TCompLHA 2.0 and TCompress Component Set 3.5 Ship from SPIS Ltd

    Design Systems announcedRemember This, a VCL com-ponent collection that allowsdevelopers to add options andsettings to screens in Delphiapplications.

    With Remember This, devel-opers can modify any dialogbox to remember a usersfavorite settings, to see whenany component on a form hasbeen modified, and to rollback those modifications.

    Remember This also providessimpler access to Windows.INI files and the registry.Similar products require

    retrofitting existing applica-tions into their framework, butRemember This works with

    new or existing applications.Remember This uses

    Delphis Run-Time TypeInformation (RTTI) to auto-matically handle any compo-nent. It relies on a unique and

    extensible StateSaver technolo-gy to customize the saving ofall Delphi standard controls.

    Remember This is availablein a Delphi edition; aC++Builder edition isscheduled for release in the3rd quarter of 1997. Bothversions include online Help,sample applications, unlimitedsupport, and a 60-day money-back guarantee. A trial ver-sion is available from DesignSystems Web site.

    Design SystemsPrice: The developers kit of the Delphior C++Builder version is US$119;with source code its US$179. A bun-dle of both versions is US$139; thebundle with source code is US$199.

    Phone: (508) 888-4964E-Mail: [email protected] Site: http://www.-dsgnsystms.com

    Design Systems New VCL Component Collection Adds Time-Saving Features

  • 8/14/2019 The Persistence of Objects.pdf

    3/403 October 1997 Delphi Informant

    DelphiT O O L S

    New Products

    and Solutions

    TurboPowerSoftware Co. hasreleasedAbbrevia 1.0,enabling Delphi pro-grammers to accessand manipulatePKZIP-compatible

    files.Abbrevia can create

    ZIP files; it includesaccess to advanced fea-tures such as disk span-ning, self-extractingarchives, ZIP-file com-ments, and passwordprotection. With diskspanning, large ZIPfiles can be automati-cally spread across a series of

    floppy disks. A user receiv-ing a self-extracting ZIP filecan expand its contents byexecuting a small Windowsprogram bound with theZIP file.

    In addition, Abbreviaincludes visual componentsthat enable programmers tooffer access to ZIP filesgraphically in their pro-grams. It also has API-level

    routines for compressing and

    decompressing streams of

    data on-the-fly.Unlike other libraries for

    handling ZIP files, Abbreviacomponents are written inDelphi, and will compiledirectly into an application.

    Abbrevia requires no addi-tional files be shipped withan application that uses itscomponents. This productships with documentation,online Help, free technical

    support, and a 60-day

    money-back guarantee.TurboPower also

    announced the availabil-ity of free Delphi 3upgrades for owners ofLockBox, OnGuard,Orpheus, and SysTools.

    These upgrade patchescan be downloaded fromhttp://www.turbopower.-com/updates.

    In addition to thepatches, TurboPowerhas released a list of thechanges necessary touse its flagship prod-uct, Async Professional,

    with Delphi 3. Freeupdates for other Turbo-

    Power products, includingFlashFiler, will be avail-able soon.

    TurboPower Software Co.Price: Abbrevia 1.0 (supports 16- and32-bit development with Delphi 1, 2,and 3), US$199. Current TurboPowercustomers receive a 20 percent discount.Phone: (800) 333-4160 or(719) 260-9639E-Mail: [email protected]

    Web Site: http://www.turbopower.com

    ISBN: 0-672-31114-3

    Price: US$29.99 (599 pages)

    Phone: (800) 545-5914 or(317) 581-5801

    Teach Yourself Delphi 3

    in 14 Days

    Dan Osier, et al.

    Sams Publishing

    LMD Innovative ofSiegen, Germany hasreleased LMD-Tools 3.0, acomponent package of over100 native Delphi VCLcomponents and routines.System, multimedia, visual,data sensitive, and dialogcomponents are available.

    Version 3 adds new com-

    ponents, such as a text edi-tor with RTF support,dockable toolbars, 10 newbutton classes, and uniquehandling of data containers.

    Any component in the setcan integrate with Delphi1, 2, or 3.About 20 components in

    the set can be used tomanipulate standard systembehavior (system menu,caption bars, explodingeffects for forms, appear-ance of hints, timerpool forsharing timers, and more).

    LMD-Tools features datacontainer supportingresource files, form data, orcustom formats (includingcompression). Several com-ponents can be linked tothese containers to share

    resources, and to make anapplication smaller andfaster.

    It also offers standarddialog boxes for immediateuse, multimedia compo-nents (e.g. for playing.WAV files, creating screen-savers, or supporting joy-sticks), and a design-timehelper for hiding non-visualcomponents on a form.

    Trial and demonstrationversions are available fromthe LMD Innovative

    Web site.

    LMD InnovativePrice: Standard Edition, US$99;Professional Edition, US$169.Phone: +49-271-355489E-Mail: [email protected] Site: http: //www.lmd.de

    LMD Innovative Ships LMD-Tools 3.0 for Delphi

    TurboPower Launches Abbrevia; Releases Free Delphi 3 Upgrades

  • 8/14/2019 The Persistence of Objects.pdf

    4/404 October 1997 Delphi Informant

    NewsL I N E

    Oc t ober 1997

    Borland Launches AS/400Client/Server Developer

    Program with IBM

    Borland announced a developer-relations and marketing program in

    conjunction with IBMs AS/400Division. A three-part partnershipprogram, AS/400 ADI assists

    Borland and IBM AS/400 developersin supporting, registering, and mar-keting their client/server applications

    on the IBM AS/400 platform.As part of the partnership, Borlandand IBM are hosting the ADI Web

    site that includes technical informa-tion on Borlands client/server

    development tools, as well as howto deliver applications to the

    AS/400. Developers interested inregistering their applications on the

    ADI Web site may send an e-mail [email protected], or visit

    http://www.borland.com/-

    borland400/.Borland and the IBM AS/400

    Division also plan to launch a world-wide series of technical seminars inmore than 15 cities on client/server

    development for the AS/400.Borlands AS/400 development

    tools include Delphi/400Client/Server Suite and

    C++Builder/400 Client/Server Suite.

    Nashville, TN BorlandsChairman and CEO Delbert

    W. Yocam outlined a newstrategy to establish Borland asthe leading provider of toolsand technologies to help usersbuild corporate InfoNets. An

    extension of their GoldenGate strategy, this InfoNetstrategy combines Borlandsobject-oriented developmenttools, middleware technolo-

    gies, and open architecture toenable developers to createapplications that providecross-platform access andanalysis of data over intranets,extranets, and the Internet.According to Yocam,

    Borland is qualified to deliverthe technologies needed tohelp build next-generationinformation networks. Hebelieves Borland can offer the

    tools and middleware for theinformation application life-cycle, including development,analysis, reporting, deploy-ment, and management ofinformation resources andapplications.

    Yocam noted the keyfoundations of the InfoNetarchitecture includeenterprise-class applica-tion development tools,

    Java/Web integration, andrich information analysis.

    In addition, Yocam said theforces driving the need forInfoNets include: the adop-tion of thin-client, browser-based applications; increas-

    ing growth of Windows NTas an InfoNet applicationserver platform; the maturityof Java as a cross-platformlanguage; the emergence oflow-cost network computersand network PCs; andexpanded deployment ofDecision Support Solutions(DSS) and Online AnalyticProcessing Server (OLAP)applications, as a result of

    the growth of the Internet.

    Borlands Yocam Announces New Business Strategy

    Scotts Valley, CA Borlandsfirst quarter revenues for Fiscal1998 were US$41,970,000,compared to revenues ofUS$38,146,000 for the first

    quarter of the previous fiscalyear. (First quarter revenues

    were up 10 percent overthe first quarter of fiscalyear 1997.)

    Net income for the firstquarter of fiscal year 1998

    was US$79,000, break-evenin terms of earnings pershare.

    This is compared with anet loss of US$21,809,000

    (or US$.60 per share) for thefirst quarter of fiscal 1997.

    The companys cash bal-ance on June 30, 1997 wasUS$76,600,000, comparedto an ending cash balance ofUS$54,400,000 on March31, 1997.

    The increase in cash wasprimarily the result of the

    closing of the privatelyplaced equity financing ofapproximately US$25 mil-lion, and exercises ofemployee stock options of

    about US$1.6 million.When Delbert W. Yocam,

    Chairman and CEO,joined Borland inDecember 1996, hepledged a return toprofitability in fiscal year1998, which began on

    April 1, 1997. To achievethis goal, he cut 30 percentof the workforce, andinitiated new cost-

    reduction programs.

    Borland Returns to Profitability

    Nashville, TN Borlandannounced its JBuilder fami-ly of visual development toolsfor Java. JBuilder Standardand JBuilder Professional areshipping now, and JBuilderClient/Server Suite is sched-uled for release later this year.All three versions of

    JBuilder feature JavaBeancomponent creation, a scal-able database architecture,visual development tools, andthe ability to produce 100%Pure Java applications,applets, and JavaBeans.As part of Borlands Golden

    Gate strategy, JBuilder alsosupports standards such as

    JDK 1.1, JFC, AFC, RMI,CORBA, JDBC, ODBC,and most database servers.

    JBuilder also provides an

    open architecture for addingthird-party tools and

    JavaBean components.JBuilders developer-specific

    features include the BorlandRAD WorkBench, pure Javatwo-way tools, productivity

    wizards, a new applicationbrowser, a graphical debuggerand SmartChecker compiler,as well as Local InterBase andBorland SQL Tools.JBuilder Standard is

    US$99.95. JBuilderProfessional is US$299.95.Owners of other Borlandtools can purchase JBuilderProfessional for US$249.95.Pricing for JBuilderClient/Server Suite wasunavailable at press time. Formore information, callBorland at (800) 233-2444.

    Borland Announces JBuilder Products

    Nashville, TN Borlandhas updated its Enteraintelligent middleware,

    which supports Delphi,ODBC data connectivity,and Gradient/DCE on

    Windows NT.The company also

    announced it has extendedEntera 3.2s platform sup-port with a new port toIBM MVS/Open Edition.

    Borland Entera is an inde-pendent framework for

    building, managing, anddeploying middle-tierclient/server applications.

    The new update to Entera3.2 is available to all regis-tered Borland Entera sup-port subscribers.

    Borland plans to ship anew version of Entera laterthis year.

    For more information,visit Borlands Web site,

    located at http://www.-borland.com.

    Borland Announces Entera Update

  • 8/14/2019 The Persistence of Objects.pdf

    5/405 October 1997 Delphi Informant

    The Persistence of ObjectsMake Your Applications Registry-Aware with aGeneric Object-Oriented Component

    On the CoverDelphi 3

    ByRobert Vivrette

    Y

    our parents taught you to be persistent. Its the only way to get noticed

    these days! Well, persistence isnt just a good quality for humans; computer

    users have come to expect a certain level of persistence in the programs they

    use as well.

    Think about the applications you use everyday. No doubt each has its own problems

    with persistence that annoy you. For exam-ple, I use CompuServe for Windows to man-age my CompuServe mail, and I am alwaysirritated at the number of places within theprogram where I have to perform boringtasks over and over again; when I launch theprogram, it never leaves me where I left off. I

    have to click on the Mail Centerbutton tolook at my list of incoming mail; and when-ever I add attachments to messages, the Opendialog is never looking at the right directory.Virtually every program I use has these kindsof minor shortcomings. Im sure when they

    were developed, saving the existing state ofcertain controls was not considered a priority.In fact, many programmers consider suchprogramming to be drudgery.

    Yet programming an application to be persis-

    tent is a vital piece of your job as an applica-tion developer. Your application might be thegreatest thing since they put soap in Brillopads, but if it requires users to unnecessarilyprovide repetitive input to the program, it

    will quickly become an annoyance. Here arejust a few of the kinds of information that aprogram should save:

    User information (name, address, pass-words, etc.)Sizes and locations of forms (includingthe main application form)States of program optionsHistory lists of file Open and Save dialogs

    Last directory visited by Open and SavedialogsHistory list of recently-accessed docu-ment filesCustom wildcard specifications for fileOpen and Save dialogsTypefaces used in the applicationLast visited tab in tab-sheet controls

    The list is virtually endless, and I could go onwith additional items. Probably the best indi-cator of when you have a problem with per-sistence is to watch a user run your programrepeatedly. How many times did they have toperform the same steps? How many times didthey have to resize a form, or change toanother tab? Every place where there was arepetitive action is a place where you couldsave that state from the previous execution ofthe application.

    The Windows Registry32-bit Windows provides us with an excellentplace to store persistent data. Its an internaldatabase of information managed by Windowscalled the registry. Before the registry existed,application developers stored persistent data in.INI files. While that technique can still beused, its no longer recommended. The registryadds capabilities that dont automatically existin .INI files (such as storing data for differentusers of the same machine). However, somehabits die hard and many application develop-ers still prefer .INI files. For the purpose ofthis article, we are going to be dealing with

  • 8/14/2019 The Persistence of Objects.pdf

    6/406 October 1997 Delphi Informant

    only the registry, but if youre stuck on .INI files (or are devel-oping 16-bit applications) the same principles apply.

    The format of the registry is a tree structure. Each branchof the tree (or key in registry terminology) holds informa-tion about a particular user, application, or piece of hard-

    ware. At the end of these keys, there are Name/Value pairs.This is where persistent data is saved. For example, for theHKEY_CURRENTUSER\ControlPanel\Colors key, thereis a list of key names (e.g. ActiveBorder, ActiveTitle,

    AppWorkspace, etc.) that have color assignments as values.When you open the Control Panel and access the colorsapplet, Windows retrieves these color assignments as yourcolor options.

    This article assumes you have a basic understanding of how theregistry works, so before we go any farther, I must provide this

    warning: The registry holds data essential to the operation ofWindows; if you dont know what youre doing and you acci-dentally corrupt data in the registry, you quite possible mightrender Windows inoperative. Therefore, please make certain youunderstand how the registry functions and make backups of theregistry before doing any experimentation. Windows 95 andNT have a program called RegEdit to manipulate the registrydatabase. The online Help for RegEdit will help you if you havequestions on its use. With that out of the way, lets go on.

    So we want to save persistent application data, and we wantto use the registry to hold that data. How can you get a com-ponent to save pieces of itself (properties, values, etc.) auto-matically? Well, there have been many efforts in the Delphicommunity to create persistent objects, and to understandthe issues a bit more, lets look at some of these along withtheir advantages and disadvantages.

    Subclassed Registry-Aware ComponentsIn this scheme, a developer creates a component that is awareof the registry, and has the ability to save and load data toand from the registry. Say for example that you wanted anEdit component that automatically saved the value of its Textproperty to a certain registry key. Using this technique, you

    would create a new component (descended from a TEdit),and add support for the registry. This support would likelybe in the form of two methods (one each to save and restorethe Textproperty to the registry), as well as some new prop-erty that lets the user define what key to use in the registry.

    The result would be something named, say, TRegistryEdit. Itwould look and behave exactly as an Edit component, butwould have one additional property named, say, RegistryKey.To use the control, a developer simply takes a TRegistryEditcontrol, drops it on the form, provides the name of a key inRegistryKeyand then adds a line of code in the FormCreateand FormDestroymethods to access the registry load and savemethods of the new subclassed control.

    What are the disadvantages of such an approach? There areplenty. The key one is that you have to create a new sub-classed control for every component to which you want to

    add registry support. If you want to add registry support toListBoxes, ComboBoxes, StringGrids, RadioButtons, andCheckBoxes, you will have to create a new subclassed con-trol for each of these and add the same registry support codethat you did for TRegistryEdit. The code you add would beessentially identical, but would of course save the checkedstate of a CheckBox and RadioButton, the list of items in aListBox and ComboBox, etc. However, when you add allthese new controls to your form, youre adding quite a lot ofredundant registry support code. Remember, each sub-classed control you added had its own registry support! Also,your Component palette will have to house all these newregistry-aware components.

    In addition, you still will need to manage (in code) each ofthese controls. Your FormCreatemethod might start tolook like this:

    procedure TForm1.FormCreate(Sender: TObject);

    begin

    Edit1.LoadFromRegistry;

    Edit2.LoadFromRegistry;

    Edit2.LoadFromRegistry;

    Edit2.LoadFromRegistry;

    ListBox1.LoadFromRegistry;

    StringGrid1.LoadFromRegistry;

    CheckBox1.LoadFromRegistry;

    CheckBox2.LoadFromRegistry;

    CheckBox3.LoadFromRegistry;

    CheckBox4.LoadFromRegistry;

    CheckBox5.LoadFromRegistry;

    end;

    Obviously, this adds a lot of maintenance responsibilities whendeveloping the application, and it will be prone to error (particu-larly if youre saving and restoring dozens or hundreds of values).

    Another disadvantage with this approach is that the newregistry-aware control is only going to save data that you asthe component developer have programmed it to save. Formany controls this is obvious: An Edit component saves itsTextproperty, a CheckBox saves its Checkedstate, and so on.But what if someone using your registry-aware control wantsto save the Edit components ShowHintproperty, or theCheckBoxs Enabledproperty? They probably would have totoss your control and make their own subclassed version sav-ing the values they want to save. Obviously, this is not agreat example for promoting code re-use.

    Now, granted this kind of idea could have worked only if theDelphi development team decided to add automatic registrysupport to the component hierarchy. They could have addedsome kind of RegistryKeyproperty in the TPersistentclass (orsomewhere else nearby) and then all components descendingfrom it would have that same basic behavior. However, it

    would have clearly made the VCL a bit more cluttered. Whoknows, they may have not even thought of it. Regardless, itisnt there now so we have to find another solution.

    A Better SolutionLets step back a bit and look at the problem with a broad-er perspective. We want to save data related to controls,

    On the Cover

  • 8/14/2019 The Persistence of Objects.pdf

    7/40

    but as we already learned, we dont want to have to createnew registry-aware versions of each control. This meansthat our improved solution must work with unmodifiedcomponents and should require the absolute minimumamount of maintenance.

    Enter the Persistent Object Manager (ObjMgr). For thoseof you who have used my IniOut Component PropertyManager, the Persistent Object Manager will be familiar.

    Although IniOut is a shareware product, I wanted Delphiprogrammers to get a feel for some of the concepts it uses.

    As a result, I created a completely new component, basedloosely on the ideas used in IniOut. That way, I can provideall the source code necessary to show you how it works inthis article without sabotaging the success of IniOut.

    First, lets go through a brief example of how ObjMgr works.Then we can dig into how its implemented in code.

    Using the Persistent Object ManagerLets say we are writing a simple piece of code that will savethe name of the programs user and some other relateddetails. This form might look something likeFigure 1.

    The first time your application runs, you want to gather thisinformation and save it to the registry. You will probably also

    want the ability for the user to get back to this form to modi-fy or update the information. Granted, you could just createan instance of a TRegistryobject and write out the values

    when the form closes and read them back in when it opensagain. However, the objective here is to not have to write ormanage this kind of code, particularly for each componenton the form.

    Instead, lets add an ObjMgr component to the form. Its anon-visual component, and once you place it on a form anddouble-click it, you will see the property editor shown inFigure 2. This is the project were going to create in this arti-cle. The ListBox on the left shows what component proper-ties youre managing. For each item managed, you will pro-vide data in the controls on the right. We add a new item byclicking on theAdd button. The Component combo boxenables you to pick a control on the current form. Note that

    by dropping down this combo box, we automatically see allthe controls that are present on the form (see Figure 3).

    We will start with the User Name edit box (edtName).When you select that component, the Propertycombo boxis populated with all the supported properties of that con-trol (see Figure 4). In this case, we want to manage the Textproperty. After choosing this property, we provide the reg-istry subkey that this value will be saved as. Lets enterSoftware\Informant\ObjMgrDemo\UserData for this value.Then, we provide the name of this particular key (in this

    Figure 1:A sample form.

    On the Cover

    Figure 2: The property editor.

    Figure 4:Viewing the supported properties.

    7 October 1997 Delphi Informant

    Figure 3:Accessing the controls.

  • 8/14/2019 The Persistence of Objects.pdf

    8/408 October 1997 Delphi Informant

    case, well call it UserName). Lastly, we have a field that allowsus to provide a default value for the property. It wouldntmake sense to provide a default for a user name, so we willleave this blank. After all these steps have been done, theproperty editor would look like Figure 5.

    Now, lets add all the other items we want to manage.Specifically, we would want to save the Textproperties of the

    Address, City/State/Zip, and Stereo Brand Name edit boxes.Also, lets save the Position property of each of the two track

    bars (Volume and Balance). Note that the registry subkeyvalue will be the same for each item (more on this later).

    After specifying each of these, the property editor would looklike Figure 6.

    We dismiss the property editor by clicking OK. Now there is

    only one thing left to do: We need to tell ObjMgr when toread values from the registry and when to save them. TheFormCreateand FormDestroymethods are good spots; all wedo is add a single line of code to each to access the LoadandSavemethods of ObjMgr as follows:

    procedure TForm1.FormCreate(Sender: TObject);

    begin

    ObjMgr1.Load;

    end;

    procedure TForm1.FormDestroy(Sender: TObject);

    begin

    ObjMgr.Save;

    end;

    Now run the application (see Figure 7). See what hap-pened? ObjMgr set up the form as we requested throughits property editor. Because this is the first time the pro-gram has been executed, there is no entry in the registryfor any of this data. Therefore each control was given itsdefault values.

    Now lets enter data in each field (see Figure 8) and closethe application. When the application closed, theFormDestroymethod was triggered, which told ObjMgr tosave its managed properties to the registry. Now if you runRegEdit, youll note that the registry data in Figure 9 hasbeen entered.

    Figure 5:A completed property editor.

    On the Cover

    Figure 6:All the items we want to manage.

    Figure 7: The running application.

    Figure 8: Enter data then close the application.

    Figure 9: The data in RegEdit.

  • 8/14/2019 The Persistence of Objects.pdf

    9/409 October 1997 Delphi Informant

    The location of these keys comes, of course, from the sub-key field data we provided to ObjMgr. Now comes thecool part: Run the program again. See what happened?Because the registry key existed along with all the values

    we provided to ObjMgr, each of the controls has beenrestored to how it was when the form last closed. This isbecause we told ObjMgr to load its values in the formsFormCreatemethod.

    Keep in mind that all we needed to do to accomplish thiswas to add an ObjMgr component to the form, use itsproperty editor to provide some basic information of prop-erties to manage, then add two lines of code (one each inthe FormCreateand FormDestroymethods). We used stockcontrols from Delphis Component palette and turnedthem into persistent objects. Not bad! Furthermore, if you

    were to expand on the form to hold a few more controls,and you wanted to save property values from those con-trols, you only need to access ObjMgrs property editor andadd those items!

    How It WorksNow that weve seen ObjMgr at work, lets check under thehood to see howit works. I wont be covering every aspectof ObjMgr, but rather hitting the highlights. But fear not!If you want to use ObjMgr yourself, the complete sourcecode is available on (see the end of this article for details).

    The key piece of ObjMgr is of course the property editor thatyou see when you double-click on the component (again, seeFigure 2). The whole purpose of this property editor is tomanage the contents of a single TStringsobject. This string listholds the essential bits of information necessary for ObjMgrto save and restore properties from the registry. Although thislist is internal to the component and you never see it directly,a brief discussion of its structure would be beneficial.

    Each item in this string list holds information for a singleproperty being managed. The string is a collection of smallerstrings separated by semicolons. Each string list contains thefollowing items:

    Component NameProperty NameRegistry SubKeyKey NameDefault ValueValid Flag

    In our previous discussion, therefore, the first item weentered into ObjMgr would be stored internally like this:

    edtName;Text;Software\Informant\ObjMgrDemo\UserData;UserName;;1

    First, there is the component name (edtName) then a semi-colon, followed by the property name (Text), another semi-colon, and so on. Towards the end, you see two semicolonstogether. This is where the default is stored, and we didnt pro-vide one for edtName. Last, there is a 1, which is a flag to tellObjMgr whether the item is valid. If the item is valid, its

    included when saving or restoring from the registry. If it isntvalid, its skipped.

    Run-Time Type InformationWith that piece of architectural design behind us, lets moveon. When the ObjMgr is activated, how does it get the list ofcomponents from the form? Well, as it turns out, the creatorsof Delphi knew that component developers would want thisfeature. These capabilities are all collected under one mainconcept, namely what Borland calls RTTI (run-time typeinformation). This is a mechanism in the internal structure ofDelphi that allows you to interrogate any component at runtime to determine its name, properties, methods, etc. RTTI isessential for the design-time pieces of Delphi to function, andObjMgr relies heavily on RTTI to work its magic. If you wantto learn more about RTTI, look through the Delphi unitsTypinfo.pas and Dsgnintf.pas. Both of these files are reason-ably well documented, and many of the procedures men-tioned therein are also referenced in the Delphi Help system.

    So to get the list of components on the form, we need to talk toRTTI. We first do this in the FormShowmethod of the propertyeditors form (see Figure 10). The first step is to add the name ofthe form itself to the Component combo box. This will allow usto access the forms properties. Next, we need a list of all thecomponents on the form. This is done by means of a referenceto the Form Designer, which is defined in Delphis Dsgnintf.pasunit. Basically, this is a way a property editor can obtain a con-nection to the form that is in use. In our case, we use this refer-ence to get access to the forms Componentsarray. We simplyloop through all the elements of this array and add each compo-nent name to the Component combo box.

    Once a component is selected from this list, we need to get alist of its properties to populate the Propertycombo box. Aftera user has chosen a component (actually on its OnChangeevent), we call the UpdatePropertiesCombo method. The firstline of code in this method extracts the selected item from theItems Managed list box (as the compound strings we were talk-ing about earlier). I then use a short function called Parseto

    On the Cover

    procedure TObjMgrDialog.FormShow(Sender: TObject);

    var

    a : Integer;

    begin

    // Add the form name to the list so we

    // can access its properties.

    cmbComponent.Items.Add(TheObjEd.Designer.Form.Name);

    // Populate combo with all components on form.

    with TheObjEd.Designer.Form do

    for a := 0 to ComponentCount-1 do

    cmbComponent.Items.Add(Components[a].Name);

    ValidateAll;

    // Are there items in the items managed list?

    if lbItemsManaged.Items.Count > 0 then

    begin

    // Select the first one.

    lbItemsManaged.ItemIndex := 0;

    // Simulate a click on it to populate other controls.

    lbItemsManagedClick(lbItemsManaged);

    end;

    end;

    Figure 10: The FormShowmethod.

  • 8/14/2019 The Persistence of Objects.pdf

    10/4010 October 1997 Delphi Informant

    extract the individual piece were looking for the name ofthe component, in this case. Referring to the Form Designeragain, we obtain a reference to the component (which can beeither a component on the form or the form itself).

    Next, we use the GetPropListfunction in Delphis Typinfo.pasunit. This function returns all properties matching a certain cri-teria in its class or its ancestors. That criteria is a set of the sup-ported property types ObjMgr can handle (specifically, tkInteger,tkChar, tkString, and tkLString). If you call this function andprovide a nil as the last argument, the function simply returnsthe number of properties for the component. After we have thiscount, we allocate a chunk of memory of the appropriate sizeand then call the function again this time including a point-er to that block of memory. Then its just a matter of scanningthrough this block of data (records of type TPropInfo) andextracting the name of each property we encounter. After that,

    we make sure that we free the memory block; the whole sectionis wrapped in a try..finally construct to make sure this happens.

    Briefly then, whenever a user clicks on an item in the ItemsManaged list box to the left, ObjMgr needs to fill each control onthe right with the appropriate data (if available). Obviously, ifyoure adding a new item to the list, some of the controls willhave no data to display. But after youve entered a few items, you

    will be able to scroll up and down the list and all the controls onthe right would be automatically updated. The effect is much likethat of data-aware controls. In the preceding paragraphs, I dis-cussed how UpdatePropertiesComboworks, but there is actually asimilar Updatemethod for each of the controls on the form. Thisis shown in Figure 11.

    One additional routine I wanted to comment on isNewRegSubKeyCheck. This method is called whenever the userhitsJ in, or exits from, the Registry Sub-Keyfield in theproperty editor. We want the drop-down list to show all thesubkeys entered so far. This routine looks at any entry into thedrop-down combo box and checks to see if it already exists inthe list. If it does, that item is selected. If it doesnt, the newstring is added to the list. This is a good general purpose rou-tine for this kind of drop-down combo box management.

    The UnitsThe code for ObjMgr is split up into two units: Objmgr.pasand Objmgr2.pas. The first unit houses all the behavior of theproperty editor as a whole, i.e. the way the controls work, the

    way it allows you to add and delete items managed, and so on.The second unit, Objmgr2.pas, contains the internal string listof managed properties as well as the registry-support code.

    When you install ObjMgr into the Delphi Component Library,youre adding Objmgr.pas, which references Objmgr2.pas.

    At run time, however, the only file that needs to be included in aproject is Objmgr2.pas. Because that is where the internal stringlist and registry code are, Delphi knows that it doesnt need tolink in all of the design-time material into your run-time exe-cutable. If you looked at the compiled DCU files for each ofthese units, you would see that Objmgr.dcu is about 17KB andObjmgr2.dcu is only 6KB. Because the design-time and run-time material has been segregated in this way, we save the 17KBof Objmgr.dcu that doesnt link into the final executable.

    The Run-Time ImplementationThis brings us to the run-time piece of the code, Objmgr2.pas(shown in its entirety inListing One beginning on page 12).The unit is straightforward. First, there is the definition ofTObjMgr, which descends from TComponent. There is a singleprivate variable, FItems, which is the internal string of itemsthat ObjMgr is managing. Then there are the Loadand Savemethods that do the actual reading and writing of registry data.

    Lets take a look at the Savemethod (the Loadmethod isessentially the same). The first step is to create an instanceof a TRegistryobject, and set its RootKeyproperty toHKEY_CURRENTUSER. ObjMgr would of course be more flex-ible if this could be changed by means of a property, butfor the sake of simplicity, I have hard-coded it here. Aftersetting up the registry object instance, we now need to loopthrough all the items managed. We first verify that theobject is valid by checking the 1 or 0 at the end of eachitem string. If the item isnt valid, its ignored.

    Assuming the item is valid, we next extract each piece of theitem (component name, property name, registry key, etc.).

    We obtain a reference to either the form or a component onthe form, and once we have that component reference, wepass it into the GetPropInfo function (in Delphis Typinfo.pasunit). GetPropInfo returns a pointer to a TPropInfo structure.That structure holds all the basic information necessary togain access to the property and its stored value.

    Now we need to write the data out to the registry. We firstclose any key that might be open and then open the newlyspecified key. The second parameter of the OpenKeymethodis set to True,which tells the TRegistryobject to create thekey if it doesnt exist. Now we simply write out the key withTRegistrys WriteStringmethod. We give it the key name theuser provided and a string value to write out. That stringvalue is actually the return result of a function in this unitcalled GetPropAsString. Its purpose is to return the currentvalue of the specified property as a string value. It is thatvalue that is written to the registry.

    So lets look at GetPropAsString. When we called it, wepassed in a reference to the component we are working

    with, along with the PropInfo record of the specific propertywe are interested in. We first examine the Kindproperty ofPropInfo to see if this property is an integer, char, or string.

    procedure TObjMgrDialog.lbItemsManagedClick(

    Sender: TObject);

    begin

    UpdateComponentsCombo;

    UpdatePropertiesCombo;

    UpdateSubKeyCombo;

    UpdateKeyNameEditBox;

    UpdateDefaultsEditBox;

    end;

    Figure 11: Calling the Update methods when a new item is clicked.

    On the Cover

  • 8/14/2019 The Persistence of Objects.pdf

    11/4011 October 1997 Delphi Informant

    If it is a string, we simply use GetStrProp (also defined inDelphis Typinfo unit) to return the current value of thatstring property. If it is of Char type, we use GetOrdProp andconvert the ordinal value that comes back into a Char value.If it is an Integerproperty, we want to do a little more thansimply return a number. In the event that a user wants tosave off a Coloror Cursorproperty, it wouldnt be veryhandy to require the user to lookup that the color clFuchsiais the number 16711935. Instead, we use some handy func-tions provided with Delphi that will convert a numericvalue to and from either a Coloror Cursor. Then it is simplya matter of determining if we are looking at a TColororTCursorand calling the appropriate conversion routine(ColorToIdentor CursorToIdent both defined inGraphics.pas). Because we add this extra step, we now willhave a registry entry that says that the Colorkey is clFuchsiainstead of the meaningless value 16711935. Also, we canuse the same technique when asking the user for a defaultvalue for such a property. In the Default Value edit box inthe property editor, we can enter clYellow and the proper-ty editor will make the appropriate conversion.

    Some Additional FeaturesThere are some additional features of ObjMgr that I amgoing to mention briefly here. As you can see, the propertyeditor form is resizable. I trapped the WMGetMinMaxInfomessage that Windows processes to tell it the minimumsize the form can be. Also, on the forms Resizemethod, Iput in code that stretches the controls so that they takeadvantage of additional form space. This is particularly use-ful to view long registry key names.

    I also added a component editor to ObjMgr to add the capa-bility of double-clicking on the component to get its propertyeditor up. It also adds a Edit Managed Properties menu itemon the right-click popup menu for the ObjMgr component.

    The Items Managed list box is also different in that it is anowner-draw list box. I wanted to indicate whether a particu-lar item was valid or not, so I added a bitmap resource to theproject that shows either a green check or a red X drawn tothe left of each item to indicate its validity.

    IniOut and Where to Go from HereAs I mentioned before, ObjMgr is based on principles usedin my IniOut utility (yes, I hate the name too, but I amstuck with it now). ObjMgr was created to show how youcan apply these principles of object persistence in yourapplications. However, it clearly falls short in many areas.For one thing, it only supports three property types, i.e.integer, string, and char. These are the easiest to implementand I chose them strictly to limit the focus of this article.

    ObjMgr can be extended to add other property types withvarying degrees of effort. Floating-point numbers wouldprobably be the next easiest to implement. After that

    would come set types, which would be a little more diffi-cult because you will need to manage the individual ele-ments of the set and convert them to and from a string.

    Getting even more difficult would be enumerated types,which would allow you to manage something like a com-ponentsAlign property. When you chose theAlign proper-ty, the Default Value edit box could become a drop-downlist populated with all the valid elements of that enumerat-ed type; in this instance it would hold alBottom, alClient,alLeft, alNone, alRight, and alTop. You would also need toconvert these to and from textual representations (e.g.alClientas a string) into its actual type (alClientas an itemof the type TAlign).

    IniOut takes those extra steps to be a far more comprehen-sive solution. It manages virtually any property type(including floating-point properties, sets, enumerated types,classes, methods, and compound properties types such asFonts, StringLists, and so on. It also allows you to add non-published properties, so you can add variables at run timeto its list of managed properties.

    As a special offer to readers, I have decided to provide a freecopy of the registered version of IniOut to anyDelphiInformant subscriber. The shareware version has a few limita-tions, but the registered version is completely functional andincludes compiled units for all versions of Delphi. The regis-tered version doesnt include the source to IniOut, but it isavailable for a modest fee. For those of you who have previ-ously paid for a registered copy of IniOut, dont fret! Themoney you paid for the registered version will be appliedtoward the cost of the source code version if you wish to getthe IniOut source.

    To get your free registered copy of IniOut, simply send me ane-mail at [email protected]. Include in your e-mail the num-ber directly to the left of your subscription expiration date.The number should look something like C 12345. I willsend you the registered version of IniOut by return e-mail.

    ConclusionWith the Persistent Object Manager, making a Delphi objector application persistent is as easy as dropping a componenton a form. Saving persistent data helps users of your applica-tions be more productive and eliminates annoying repetitivechores or input of data.

    In the past, making objects persistent might have been achore that you as an application developer would rather notdeal with. Now you have no excuse!

    The files referenced in this article are available on the DelphiInformant Works CD located in INFORM\97\OCT\DI9710RV.

    On the Cover

    Robert Vivrette is a contract programmer for Pacific Gas & Electric, and TechnicalEditor for Delphi Informant. He has worked as a game designer and computer con-sultant, and has experience in a number of programming languages. He can bereached via e-mail at [email protected].

  • 8/14/2019 The Persistence of Objects.pdf

    12/4012 October 1997 Delphi Informant

    Begin Listing One Objmgr2.pasunit OBJMGR2;

    interface

    uses

    WinProcs, WinTypes, Messages, SysUtils, Classes,

    Graphics, Controls, Forms, Dialogs, StdCtrls, Buttons,

    TypInfo, DsgnIntF, Registry;

    const// These are the supported property types.

    tkProps = [tkInteger,tkChar,tkString,tkLString];

    ppComponent = 0;

    ppProperty = 1;

    ppSubKey = 2;

    ppName = 3;

    ppDefault = 4;

    ppValid = 5;

    type

    TObjMgr = class(TComponent)

    private

    FItems : TStrings;

    procedure SetItems(Value: TStrings);

    protected

    publicconstructor Create(AOwner: TComponent); override;

    destructor Destroy; override;

    procedure Load;

    procedure Save;

    published

    property Items: TStrings read FItems write SetItems;

    end;

    function Parse(TmpS: string;

    A: Integer; C: Char): string;

    implementation

    function Parse(TmpS: string; A: Integer; C: Char): string;

    var

    SCNum,X : Integer;begin

    Result := '';

    SCNum := 0;

    for X := 1 to Length(TmpS) do

    if TmpS[X] = C then

    Inc(SCNum)

    else

    if SCNum = A then

    Result := Result + TmpS[X];

    Result := Trim(Result);

    end;

    function IsValid(a: string): Boolean;

    begin

    Result := (Parse(a,ppComponent,';')'');

    if not Result thenResult := (Parse(a,ppProperty,';')'');

    if not Result then

    Result := (Parse(a,ppSubKey,';')'');

    if not Result then

    Result := (Parse(a,ppName,';')'');

    end;

    function GetPropAsString(Obj: TObject;

    Info: PPropInfo): string;

    var

    IntVal : LongInt;

    begin

    Result := '';

    with Info^ do

    case PropType^.Kind of

    tkInteger :begin

    // Get the integer value.

    IntVal := LongInt(GetOrdProp(Obj,Info));

    // If TColor or TCursor, convert to text.

    if (PropType^.Name = 'TColor') and

    ColorToIdent(IntVal,Result) then

    else if (PropType^.Name = 'TCursor') and

    CursorToIdent(IntVal,Result) then

    else

    Result := IntToStr(IntVal);

    end;

    tkChar : Result := Chr(GetOrdProp(Obj,Info));

    tkString,tkLString : Result := GetStrProp(Obj,Info);

    end;

    end;

    procedure SetPropFromString(Obj: TObject; Info: PPropInfo;

    Str: string);

    var

    IntVal : LongInt;

    CharVal : Char;

    begin

    try with Info^ do

    case PropType^.Kind of

    tkInteger :

    if Str'' then

    if PropType^.Name = 'TColor' then

    if IdentToColor(Str,IntVal) thenSetOrdProp(Obj,Info,IntVal)

    else

    SetOrdProp(Obj,Info,StrToInt(Str))

    else

    if PropType^.Name = 'TCursor' then

    if IdentToCursor(Str,IntVal) then

    SetOrdProp(Obj,Info,IntVal)

    else

    SetOrdProp(Obj,Info,StrToInt(Str))

    else

    SetOrdProp(Obj,Info,StrToInt(Str));

    tkChar :

    begin

    CharVal := Str[1];

    SetOrdProp(Obj,Info,Ord(CharVal));

    end;tkString,

    tkLString : SetStrProp(Obj,Info,Str);

    end;

    except

    // This catches invalid property assignments.

    end;

    end;

    { TObjMgr - Persistent Object Manager }

    constructor TObjMgr.Create(AOwner: TComponent);

    begin

    inherited Create(AOwner);

    FItems := TStringList.Create;

    TStringList(FItems).OnChange := nil;

    end;

    destructor TObjMgr.Destroy;

    begin

    FItems.Free;

    inherited Destroy;

    end;

    procedure TObjMgr.SetItems(Value: TStrings);

    begin

    FItems.Assign(Value);

    end;

    procedure TObjMgr.Load;

    var

    Reg : TRegistry;

    A : Integer;

    TmpCmp : TComponent;PropInfo : PPropInfo;

    On the Cover

  • 8/14/2019 The Persistence of Objects.pdf

    13/4013 October 1997 Delphi Informant

    S1 : string;

    CmpName : string;

    PrpName : string;

    SubKey : string;

    KeyName : string;

    DefVal :string;

    begin

    Reg := TRegistry.Create;

    try

    Reg.RootKey := HKEY_CURRENT_USER;

    // Loop through all items managed.

    forA := 0 to Items.Count-1 do beginS1 := Items[A];

    // Verify item is valid.

    if not IsValid(S1) then Continue;

    // Extract the individual elements from the item.

    CmpName := Parse(S1,ppComponent,';');

    PrpName := Parse(S1,ppProperty,';');

    SubKey := Parse(S1,ppSubKey,';');

    KeyName := Parse(S1,ppName,';');

    DefVal := Parse(S1,ppDefault,';');

    // Check to see if this is a form.

    if CmpName = (Owner as TForm).Name then

    // Yes - use the form.

    TmpCmp := (Owner as TForm)

    else

    // Find the component on the form.

    TmpCmp := (Owner as TForm).FindComponent(CmpName);// Couldn't find component - go on to next.

    if TmpCmp = nil then Continue;

    // Get the info record on this component.

    PropInfo := GetPropInfo(TmpCmp.ClassInfo,PrpName);

    if PropInfo = nil then Continue;

    try

    Reg.CloseKey;

    // Open the Subkey.

    if Reg.OpenKey(SubKey,False) then

    // Does this key name exist?

    if Reg.ValueExists(KeyName) then

    // Yes - set the property.

    SetPropFromString(TmpCmp,PropInfo,

    Reg.ReadString(KeyName))

    else

    // No it doesn't exist - use the default.SetPropFromString(TmpCmp,PropInfo,DefVal)

    else

    // Couldn't open the key - use the default.

    SetPropFromString(TmpCmp,PropInfo,DefVal);

    except

    end;

    end;

    finally

    Reg.Free;

    end;

    end;

    procedure TObjMgr.Save;

    var

    Reg : TRegistry;

    A : Integer;TmpCmp : TComponent;

    PropInfo : PPropInfo;

    S1 : string;

    CmpName : string;

    PrpName : string;

    SubKey : string;

    KeyName : string;

    begin

    Reg := TRegistry.Create;

    try

    Reg.RootKey := HKEY_CURRENT_USER;

    // Loop through all items managed.

    forA := 0 to Items.Count-1 do begin

    S1 := Items[A];

    // Verify item is valid.

    if not IsValid(S1) then Continue;// Extract the individual elements from the item.

    CmpName := Parse(S1,ppComponent,';');

    PrpName := Parse(S1,ppProperty,';');

    SubKey := Parse(S1,ppSubKey,';');

    KeyName := Parse(S1,ppName,';');

    // Check to see if this is a form.

    if CmpName = (Owner as TForm).Name then

    // Yes - use the form.

    TmpCmp := (Owner as TForm)

    else

    // Find the component on the form.

    TmpCmp := (Owner as TForm).FindComponent(CmpName);

    // Couldn't find component - go on to next.if TmpCmp = nil then Continue;

    // Get the info record on this component.

    PropInfo := GetPropInfo(TmpCmp.ClassInfo,PrpName);

    if PropInfo = nil then Continue;

    try

    Reg.CloseKey;

    if Reg.OpenKey(Subkey,True) then

    Reg.WriteString(

    KeyName,GetPropAsString(TmpCmp,PropInfo));

    except

    end;

    end;

    finally

    Reg.Free;

    end;

    end;

    end.

    End Listing One

    On the Cover

  • 8/14/2019 The Persistence of Objects.pdf

    14/4014 October 1997 Delphi Informant

    Down to the MetalHigh-Performance Computing with Delphi

    Informant SpotlightDelphi 2 / Delphi 3

    ByJay Cole

    Delphi generates incredible code. It can pass procedure parameters in

    registers instead of setting up a stack frame; it uses pointer arithmetic

    instead of array index calculations when it can. All in all, the code Delphi

    generates is at least on a par with any C/C++ compiler out there.

    Are you tired of C/C++ programmers claimingyou cant do low-level programming efficientlyin Delphi? They maintain that only languagessuch as C/C++ are suited for low-level tasks,and that Delphi is great for user-interface pro-gramming and prototyping, but for time-critical, serious applications, only C/C++ orassembler will do. Well, you can out-performeven the craftiest C/C++ programmer ifyou are willing to break a few rules.

    So what can you do when you need evenmore performance? Obviously, first visit thealgorithm used to solve the problem. A welldesigned algorithm can create orders-of-magnitude improvements in performance;the classic example is a bubble sort versus aquick sort. That is exactly what we at OpNekdid with NitroSort: We invented a new sort-ing algorithm that was several times fasterthan the traditional quick sort. But that

    wasnt enough. We couldnt get the perfor-mance numbers we wanted without breakingsome of the cardinal rules of computer sci-ence, and generating some very tight code.(Note: The material in this article applies onlyto Delphi 2/3 running on Windows 95/NT.)

    Creating a Dynamic CompilerRemember back in college when your profes-sor said, Dont ever, ever, ever write self-modifying code? Well, this article puts a bitof a twist on that. It doesnt actually modifyexisting code, but it does generate entirely

    new code, in memory, on-the-fly, based on asmall, embedded language specific to theproblem being addressed. The technique issimple: Allow users to specify what they wantto accomplish through a small languagegeared to the problem at hand.

    In NitroSort, this is a sorting language thatallows the user to specify the file type, recordsize, and sort keys, among other things. Thenthe system allocates a block of executablememory, and compiles the machine codedirectly into the allocated memory block.Finally, a standard function pointer inDelphi is assigned to the allocated memoryblock, and is called as a function pointer.Essentially, this is what Borland did withTurbo Pascal 3.0 when it compiled and ran

    within the DOS environment.

    Sound difficult? After all, who knowsmachine code? Who knows how to write acompiler? How does one debug and test thecode? Actually, it looks more difficult than itis. Surprisingly, all this is fairly easy for mostembedded-language applications. With toolssuch as Visual Parse++ for the parsing andTurbo Debugger for generating the machinecode and debugging compiler output, theprocess is quick and straightforward. (Thisarticles references to the Turbo Debuggerrefer specifically to the Turbo Debugger for

    Win32. It allows assembling and disassem-bling of 32-bit instruction codes. Generally,

  • 8/14/2019 The Persistence of Objects.pdf

    15/4015 October 1997 Delphi Informant

    the more complex and general a language becomes, the moredifficult the compilation and debugging becomes.)

    An Old, Trusty ExampleThis article assumes a basic knowledge of compiler theoryand assembly language. For more information, readCompilers: Principles, Techniques and Toolsby Alfred V.

    Aho, Ravi Sethi, and Jeffrey D. Ullman [Addison-Wesley,1985]. Its the Dragon Book the bible of compilertheory. For 386 assembler, almost any introductory text

    will do. Peter Norton puts out a pretty good book on IntelAssembler; for a full treatment, read Assembly LanguageProgramming for the Intel 80XXX Familyby William B.Giles [Prentice Hall, 1991].

    To illustrate the technique without getting bogged down inthe details, well create a simple version of a classic computer-science homework assignment, The Formula Compiler.

    Our Formula Compiler

    Our example will be very limited; it will do calculations onlyon the IEEE floating-point data type (the Delphi Double),and handle only addition, subtraction, multiplication, anddivision. The compiler will be a simple, recursive-descentparser, with an emphasis on code generation, not parsing.The techniques presented here, however, can be generalizedfor powerful languages. Of course, the compiler will handlethe expressions in correct mathematical precedence.

    Two key elements. Users must be able to specify the formulavia an edit box or some other mechanism at run time. Theyalso need access to internally-held variables in the compiled

    program with which they are interacting. For example, whencreating a graphing function for an X-Y coordinate system,users would need access to the X and Y variables the program

    will be plotting, in order to enter their formulas. They alsoneed a place to enter the formula to be compiled and used bythe graphing function. If users are allowed to enter formulasthat act on internal fields and variables, they would needaccess to the names of those variables. Essentially, the user-defined, run-time formula must interact with the static, compile-time variables. A symbol table manager handles this.

    Who Writes Machine Code, Anyway?

    Take a look at the Turbo Assembler quick reference guide, orany x86 assembler reference. In it are cryptic entries such as:

    OpCode Instruction Clocks Description

    $81 /0 id ADD r/m32, imm32 ... ...

    The OpCodeon the left is the machine code that represents theinstruction (in the Instruction column). If the computers instruc-tion pointer were currently pointing at a memory location con-taining $81, and it was to execute, the CPU would know to per-form an ADD instruction, and the remaining bytes immediatelyafter the $81 would tell it what and how to add. To perform thisoperation, generate a $81 hex followed by the mod r/m and the

    immediate value as a 4-byte integer. The mod r/m is an indicatorto tell whether the operation is to memory, or to a register, and

    what memory location or what register. Its not difficult to gener-ate the machine code by hand, but its tedious.

    Fortunately, theres an easier way. I dont think in, orwrite, machine code, so I cheat. Turbo Debugger has abuilt-in facility that makes this step easy. Load the TurboDebugger, select File | Open, then open any executableprogram (even if it wasnt created in Delphi). If the pro-gram wasnt compiled with Turbo Debugger information,the message Program has no symbol table will be dis-played. Click on the OKbutton if that message boxappears. If the selected program has Turbo Debuggerinformation embedded, the debugger will show sourcecode instead of CPU instructions. In that case, selectView| CPU to switch from source-level to CPU-level debug-ging. A screen similar to Figure 1will appear immediatelyafter opening the file and viewing the CPU window.

    Now, to see how the assembly statement would be represent-ed in machine code, enter it into the debugger:

    ADD EDX, 200

    By pressingA, an input box is displayed with the characterA already in it. Enter the rest of the ADD statement asshown in Figure 2.After pressingJ, the assembly state-ment is transformed into machine code.

    The first column in the CPU window is the instructionpointer for the machine, i.e. the location of the add instruc-tion in memory (:00410000). The next column contains themachine code bytes equivalent to ADD EDX, 00000200.The final column is the assembler statement itself. So, in our

    example, we have:

    :00410000 81C200020000 add edx, 00000200

    The $81 byte is the add instruction we saw earlier in theTurbo Assembler reference. The $C2 byte indicates theEDX register, and $00200000 is the 32-bit value represent-

    Figure 1: Turbo Debugger.

    Informant Spotlight

  • 8/14/2019 The Persistence of Objects.pdf

    16/4016 October 1997 Delphi Informant

    ing the hex number 200. (Remember, on the Intel plat-form, words are stored backwards.)

    How Do We Allocate the Code Space?Now that there is an easy method to quickly access themachine code for any assembly statement, we need an exe-cutable memory block to hold the code to be generated.In Windows 95, you can allocate a block of memory usingGetMem, assign a function pointer to it, and call the func-tion directly. Thats nice, but not very safe. Fortunately,

    Windows NT is a bit more protective. If you tried theWindows 95 approach, a protection violation would beissued under NT. In NT, a memory block must be allocat-ed with the VirtualAlloc function, which contains moredetails as to the memorys exact use. The code in the arti-cle will work in both Windows 95 and NT, and is thesafest code for performing this type of technique. In NT,its necessary to declare the memory as executable, as wellas readable and writeable.

    VirtualAlloc specifies the size of the memory space toallocate, as well as the attributes of the memory block.

    In Figure 3, the memory block is allocated as an exe-cutable, readable, and writeable block of memory.PAGE_EXECUTE_READWRITE is a flag to indicate to

    Windows NT not only to read and write to this memorylocation, but later to actually allow executing it as code. Itreturns a pointer to an executable memory block of sizecodeSize. This pointer to memory can be used just as anyother pointer variable would be used.

    Generating Good CodeSo far, this article has demonstrated translating assemblyinstructions to machine code, and how to allocate an exe-cutable block of memory. The only thing left is to generatecode for the application. Ideally, the run-time formulasshould execute as fast as if they had been compiled directlyinto Delphi. But, instead of being compiled statically, they

    will be dynamic and user-defined well after compilation.

    By examining how Delphi generates a formula, we can sizeup the machine code involved. If the code Delphi generatedisnt efficient, a better algorithm or method can be substi-tuted and typed into the debugger (as shown inFigure 2).For this example, a small console application is created,containing all the operators to be implemented: addition,multiplication, subtraction, and division. The application iscompiled with Turbo Debugger information embedded, andpulled into Turbo Debugger for examination. By doing this,the source code and the associated assembly language gener-ated by Delphi are viewed together. The project options areshown in Figure 4.

    I knew Delphi would do some optimizations over andabove what I would handle in this simple example. So, Iput some floating-point assembly-language code inside anasm block I knew I would need in the code generationprocess. By doing this, I can translate the other neededinstructions at the same time, instead of typing them inseparately. Dont execute this code! It would most likelyresult in a nasty crash. Now, with a basic understanding ofhow Delphi generates the code, an efficient formula parsercan be implemented.

    Before proceeding, make sure the compiler tab has all threeDebugging options checked. With the sample code inFigure 5, the machine code needed to implement the nextpart of our formula parser is displayed. First, compile thisprogram; go into Turbo Debugger and select File | Load forthe executable. Then selectView | CPU and begin steppingthrough the code until the call to Eval in Figure 5. At thispoint, use7 to step into the function. A display similarto Figure 6will appear.

    fCodeBuff :=

    VirtualAlloc(nil, codeSize, MEM_COMMIT + MEM_RESERVE,

    PAGE_EXECUTE_READWRITE);

    Figure 3:Virtual allocation of a code page.

    Figure 2: The instruction entry screen.

    Informant Spotlight

    Figure 4: The Project Options dialog box for our test program.

  • 8/14/2019 The Persistence of Objects.pdf

    17/4017 October 1997 Delphi Informant

    Here, Delphigenerates somepretty simplecode. It loadsthe EAX register

    with the addressof the variableA. It uses a com-bination ofFLD, FSTP,FADD, FDIV,FMUL, andFSUB. Theseoperators loadvalues on thefloating-pointstack from amemory point-er, pop the val-ues off the stackto a memoryaddress, andperform binaryarithmetic oper-ations on the

    floating-point stack. Most assembly language texts will give acomplete treatment of these floating-point operators. Now allthe information is present to generate a user-defined function inmachine code.

    Each function in Delphi has function entry and exit code. Allthe entry and exit code information can simply be copied direct-ly to the formula compiler. All Delphi functions must adhere tothis standard calling methodology as long as they are not stdcalltype functions. Delphi subtracts eight from the stack pointer,and loads the EAX register with the address of A upon entry intothis function. It then performs several floating-point stack opera-tions, and finally, moves the result to the ESP stack, as well aspushing the result on to the floating-point stack, the code after:

    Result := a+b

    With the value on the floating-point stack, the function resultcould easily be used in an expression instead of just an assign-ment. Finally, it pops off two four-byte integers, so the stack

    frame is returned to the original setting before calling. Theuser-defined procedure generated in the formula compiler willneed to mimic the entry and exit code of the function, and,based on the user formula, generate machine code similar tothat in Figure 6,between the entry and exit code.

    Take one of the instructions as an example of how to use themachine code:

    DD0560164200 fld qword ptr [00421660]

    The first two bytes are the FLD (Load Real) instruction

    ($DD followed by $05). The remaining bytes are the addressof the Double variable push onto the floating-point stack. To

    create this instruction in memory, write out the first two

    bytes for the instruction, then the address of the variable topush. When the machines instruction pointer (IP) comes tothat instruction ($DD,$05), it will push the value containedat that memory location to the floating-point stack, just as ifDelphi had generated it.

    So, all the building blocks necessary to generate user-defined code are now in place. The only thing thatremains is the formula parser and code generator. The for-mula compilers code model will push the left side, thenthe right side of each expression to the floating-pointstack. It will perform the arithmetic operation on the top

    two stack elements and push that result back to the floating-point stack. For example, in the expression x + y, the valuefor x, then the value for y, would be pushed onto thestack. The + operation would be performed on the valuesfor x and y, and the resulting value would be pushed backto the floating-point stack. This is a traditional post-fixstack execution model. Its not always the most effectivecode, but because the floating-point processor on aPentium takes one or three clock cycles to perform theseinstructions, the code will be quick. Optimization is out-side the scope of this article, so its just brute force fornow. Even this brute-force method, however, would out-

    perform the cleverest interpreted scheme.

    The Code Generator

    Code generation is the simplest of the two remaining sections.Machine code must be output and space reserved for the userconstants and variables. The symbol table is key to sharingcompile-time variables with the users run-time formula.

    The symbol table entries are basic. Pass in the name and addressof the compile-time variable. Those variables are then ready tobe processed against a user-entered formula. The compiler hasmemory-address knowledge, through the symbol table, of the

    variables inside the compiled program, and therefore can accessand set the values in those variables. Figure 7 shows the class

    program ConsoleApp;

    uses

    Forms;

    {$R *.RES}

    var

    a,b,c,d, tmpResult : Double;

    function Eval : Double;

    begin

    b := a * b + c * d;

    asm

    FMULP ST(1), ST

    FDIVP ST(1), ST

    FADDP ST(1), ST

    FSUBP ST(1), ST

    end;

    Result := a + b;

    end;

    begin

    b := 12.445;

    c := 13.345;

    d := 445014.112;

    tmpResult := Eval;

    WriteLn(tmpResult);

    end.

    Figure 5: The sample code.

    Informant Spotlight

    Figure 6: Mixed view of source and generated code.

  • 8/14/2019 The Persistence of Objects.pdf

    18/4018 October 1997 Delphi Informant

    definition of the code generator. Basically, the constructorreports the combined size of the code and data space. In parsingthe formula, the compiler will begin to generate bytes, words,and quad-words to the code space, and sub-allocate data space

    from the block of memory allocated for the machine code. Bycalling the Gen... functions, the machine code is generated asthe expression is parsed. When done with this step, only theexit code and return must be generated before the code is readyto execute as a standard Delphi function.

    TheAllocDataSpacefunction allocates a block of numBytessizefrom the end of the code buffer. The code is generated from thebeginning of the block to the end. The data is allocated fromthe end of the block to the beginning. As long as these twopointers dont overlap, there is enough space to generate codeand data. If the program runs out of space, the Gen... functionsreturn False. Of course, it would be nice to have some simpleerror recovery functions. Its not difficult, but its left as a prob-lem for the reader. The Create constructor uses the VirtualAllocfunction, described previously, to allocate an executable memo-ry block. Finally, calls to GetFunctionAddress get the pointer tothe memory block containing the generated machine code, so afunction pointer can be assigned to the newly generated code,and subsequently be called and executed. The implementationcode is shown in Listing Two, beginning on page 20.

    The Parser and ScannerA parser and scanner could be written from scratch, but it turnsout the Dragon Book provides an adequate parser and scanner.The only remaining task is to integrate the code generation andthe data-space allocation. The parser takes the in-fix formula,parses it, then produces post-fix code. Post-fix code pushes bothoperands to a stack, then generates the binary operator to act onthe stack elements. In other words, the operator is evaluatedafter (post) the operands. Except for a few changes to make theparser/lexer more efficient and Delphi-izing the code, most ofthe original Dragon Book code for parsing and lexing isretained. Instead of outputting a character as in the DragonBook, the equivalent machine code is generated.

    Due to space limitations, the lexer is greatly simplified. In reality,a tool like Lex, Yacc, or Visual Parse++ would be used to put

    together a real grammar, parser, and lexical analyzer. But, for thisexample, a quick and dirty, recursive-descent parser is best forillustration. Note the glaring omission of almost all error-check-ing and recovery code. This is also left as a problem for the read-er, as there is not sufficient space to include it. Full source codefor this article is available (see end of article for details).

    Its best to explain this overall process through an example.Take the expression A+B. The parser would push the value in

    A, then the value for B to the floating-point stack. The + oper-ator generates an FADDP ST(1), ST that takes the top twovalues off the floating-point stack, adds them, then pushes theresultant value back to the floating-point stack for the nextoperation. The entry and exit code for the function is the sameas the code generated by Delphi. The actual code generated forthe mathematical operators is quite small. In fact, most of thecode is spent in the mundane task of lexing the input streaminto recognizable tokens. This grammar is rudimentary; itdoesnt handle the negate operator or the power operator, letalone mathematical functions such as Sine, Cosine, etc., butthis article is not about how to write grammars.

    Running the FormulaTo execute the code, a function pointer is needed to assign tothe memory. This particular function pointer must point to afunction that returns a Double:

    type

    TDoubleFunction = function : Double;

    var

    funcPtr : TDoubleFunction;

    tmpDouble : Double;

    Now that the types are declared, all that remains is assign-ment and execution. To assign the function pointer, cast thepointer returned by the GetFunctionPointermethod to typeTDoubleFunction:

    FuncPtr := TFunctionType(codeGenObj.GetFunctionPointer);

    And execute the function:

    tmpDouble := FuncPtr;

    Now if the run-time formula will have access to internalvariables, it must use the previous symbol table functionsto set them (before the compilation process). A symboltable consists of each variable name and its address. Rightafter the parser is created, but before the users formula iscompiled and executed, add the internal variables and theirrespective addresses to the symbol table:

    aParser := TParser.Create;

    aParser.AddSymbol('X', @x);

    aParser.AddSymbol('Y', @y);

    if aParser.ParseFormula('x+y * 2');

    ...

    Because this example uses the variablesx andy, and uses theaddress of the variables (not the values), the values dont need

    TCodeGenerator = class(TObject)

    fCodeBuff, fCodeBuffEnd,

    fDataBuffPtr, fCurrCodeBuffOfs : Pointer;

    fCodeBuffSize : Integer;

    public

    constructor Create(const codeBuffSize : Integer);

    destructor Destroy; override;

    function GenBytes(

    const bytes : array of Byte) : Boolean;

    function GenWords(

    const words : array ofWord) : Boolean;

    function GenQuads(

    const quads : array of Cardinal) : Boolean;

    functionAllocDataSpace(numBytes : Longint) : Pointer;

    function GetFunctionAddress : Pointer;

    end;

    Figure 7: The code generator interface.

    Informant Spotlight

  • 8/14/2019 The Persistence of Objects.pdf

    19/4019 October 1997 Delphi Informant

    to be initialized in any other way than just setting them inthe Pascal code. For example:

    for x := 1 to 100 do begin

    for y := 1 to 100 do begin

    tmpDouble := FuncPtr;

    end;

    end;

    Because the compiler is accessingx andy via their address in the

    function routine, the assignment in the Delphifor

    loop is suffi-cient to evaluate the formula versus the current values inx andy.

    Reviewing the CodeOnce the code is generated, take a look at it. When theTestFormParser program is loaded into the Turbo Debuggerand run, the form in Figure 8will be displayed. Step into thecode and see the results. First, enter:

    x*x + y

    into the formula input box. Next, set a breakpoint at thisstatement:

    tDouble := funcPtr;

    This code is located in the OnClickevent for the Calculatebutton in the inner-most loop. By setting the breakpointhere, the debugger will stop short of the newly generatedroutine. Now, run the program by pressing the Calculatebutton. The debugger will break at the line above. SelectView | CPU in Turbo Debugger and switch to mixed-modesource and assembly. Press7 to step into the generatedcode. A screen similar to that shown in Figure 9will appear.

    First is the generated function entry code, ADD ESP,FFFFFFF8. Next is the push of the variableX twice and a

    call to FMULP, followed by a push of the Y variable and acall to FADDP. Finally, the resultant value is pushed to thestack, and the function exit code is executed. The RETfunction returns to the function call in the OnClickevent.It is now an official compiler! It has also produced somepretty efficient code not quite to the level of Delphi,but close.

    Where to Go from HereOkay, now that the basic techniques have been illustrated,how useful is this? Whenever an application needs user-dynamic interaction and speed, this technique provides a

    powerful solution. Compiled code will typically yield threeto five times the speed advantage over an interpreted version.

    Dynamic compilation is a great encapsulation tool, and yieldsdramatic performance and flexibility advantages to users. Its sim-ple language interface means that application programmers dontneed to understand any of the implementation details. Just thesimple formula compiler we have built can be embedded intoany other application that needs user-defined formulas.

    As you investigate this technique, youll notice that you haveto handle problems such as general register allocation and

    stack and temporary name handling, as well as relative jumpaddressing and near/long jumps, among others. These arenot difficult topics (save register allocation), but they do taketime to implement and are certainly beyond the scope ofa magazine article. In fact, when I do technical interviews, Iuse the forward and backward jump references as an inter-view question to see the thought processes of potential jobcandidates. It is not a trivial problem, but certainly solvable

    within a few days of coding.

    My recommendation is to start small, with the formula parser.Begin to add small functions as the applications needs expand.

    These compiler tools and objects will evolve out of necessity.Soon, all the tools necessary to generate even a full language

    Figure 8: The user input screen.

    Figure 9: The generated code.

    Informant Spotlight

  • 8/14/2019 The Persistence of Objects.pdf

    20/4020 October 1997 Delphi Informant

    compiler such as Delphi or C/C++ will be available. It takessome time, but small steps add up quickly.

    The files referenced in this article are available on the DelphiInformant Works CD located in INFORM\97\OCT\DI9710JC.

    Informant Spotlight

    Jay Cole is the owner of OpNek Research, a consulting and Financial Markets firmthat consults for many Fortune 500 companies. OpNek also produces the high-speed sort utility NitroSort. For more information on OpNek or NitroSort, visithttp://www.nitrosort.com.

    Begin Listing Two Code Generator Implementationimplementation

    uses SysUtils, Dialogs;

    const

    cTokToStr : array[TTokenType] of string[50] =

    ('+', '-', '*', '/', 'Number', 'None', 'Error',

    'Id', '(', ')', 'Done');

    type

    TRefObj = class(TObject)

    public

    fAddr : Pointer;

    end;

    constructor TParser.Create;

    begin

    // Create the symbol table and an 8KB

    // code/data space for our formula.

    fSymbolTable := TStringList.Create;fCodeGen := TCodeGenerator.Create(8192);

    end;

    destructor TParser.Destroy;

    begin

    fSymbolTable.Free;

    fCodeGen.Free;

    end;

    function TParser.LookUp(tokenStr : string;

    var addrVal : Pointer) : Boolean;

    var

    ndx : Integer;

    begin

    ndx := fSymbolTable.IndexOf(tokenStr);

    if (ndx >= 0) then beginaddrVal := (fSymbolTable.Objects[ndx] as TRefObj).fAddr;

    Result := True;

    end else begin

    Result := False;

    addrVal := nil;

    end;

    end;

    procedure TParser.AddSymbol(sym : string; addr : Pointer);

    var refObj : TRefObj;

    begin

    refObj := TRefObj.Create;

    refObj.fAddr := addr;

    fSymbolTable.AddObject(UpperCase(sym), refObj);

    end;

    procedure TParser.DeleteSymbol(sym : string);

    begin

    fSymbolTable.Delete(

    fSymbolTable.IndexOf(UpperCase(sym)));

    end;

    function TParser.GetChar : char;

    begin

    if (fCurrPos

  • 8/14/2019 The Persistence of Objects.pdf

    21/4021 October 1997 Delphi Informant

    Informant Spotlight

    Chr(0) : Result := ttDone;

    else

    Result := ttNone;

    Break;

    end;

    end;

    procedure TParser.SyntaxError(anErrorStr : string);

    begin

    fError := True;

    MessageDlg(anErrorStr, mtError, [mbOk], 0);

    end;

    function TParser.ParseFormula(aFormula : string) :

    Boolean;

    begin

    // Generate the entry code.

    fError := False;

    fInputStr := aFormula;

    fCurrPos := 1;

    fCodeGen.GenBytes([$83, $c4, $f8]); // ADD ESP,

    $FFFFFFF8

    lookAhead := Lexan;

    if (lookAhead ttDone) then Expr;

    // Generate the exit code. We must have space to store

    // result, and that space is the ESP position that we// backed up. We also need to leave the result on the

    // floating-point stack.

    fCodeGen.GenBytes([$dd,$1c,$24]); // FSTP QWORD PTR

    [ESP]

    fCodeGen.GenBytes([$9B]); // WAIT

    fCodeGen.GenBytes([$dd,$04,$24]); // FLD QWORD PTR

    [ESP]

    // Pop off the 8 stack bytes and return.

    fCodeGen.GenBytes([$59]); // POP ECX

    fCodeGen.GenBytes([$5a]); // POP EDX

    fCodeGen.GenBytes([$c3]); // RET

    Result := not fError;

    end;

    procedure TParser.Expr;

    begin

    Term;

    while (True) do begin

    if (lookAhead = ttAdd) then begin

    Match(ttAdd); Term;

    // Generate Add code.

    fCodeGen.GenBytes([$de,$c1]); // FADDP ST(1), ST

    end else if (lookAhead = ttSubtract) then begin

    Match(ttSubtract); Term;

    // Generate Subtract Code.

    fCodeGen.GenBytes([$de,$e9]); // FSUBP ST(1), ST

    end else

    Break;

    end;

    end;

    procedure TParser.Term;

    begin

    Factor;

    while (True) do begin

    if (lookAhead = ttMultiply) then begin

    Match(ttMultiply); Factor;

    // Generate the multiply code.

    fCodeGen.GenBytes([$de,$c9]); // FMULP ST(1), ST

    end else if (lookAhead = ttDivide) then begin

    Match(ttDivide); Factor;

    // Generate the divide code.

    fCodeGen.GenBytes([$de,$f9]); // FDIVP ST(1), ST

    end else

    Break;

    end;end;

    procedure TParser.Factor;

    var

    tmpPtr : ^double;

    begin

    if (lookAhead = ttLParen) then begin

    Match(ttLParen); Expr;

    Match(ttRParen);

    end else if (lookAhead = ttNumber) then begin

    // Add the number to the data space and then generate

    // the load code for that number.

    tmpPtr := fCodeGen.AllocDataSpace(SizeOf(fFloatVal));

    tmpPtr^ := fFloatVal;fCodeGen.GenBytes([$dd,$05]);

    // FLD QWORD PTR [tmpPtr]

    fCodeGen.GenQuads([Cardinal(tmpPtr)]);

    Match(ttNumber);

    end else if (lookAhead = ttId) then begin

    // Get the ID's address. Generate the push for the

    // value held in the Id address space.

    fCodeGen.GenBytes([$dd,$05]);

    // FLD QWORD PTR [@id]

    fCodeGen.GenQuads([Cardinal(fAddrVal)]);

    Match(ttId);

    end else begin

    // We have a syntax error. Let the user know.

    SyntaxError('Unrecognized token at ' +

    IntToStr(fCurrPos));

    end;end;

    procedure TParser.Match(t : TTokenType);

    begin

    if (lookAhead = t) then

    lookAhead := Lexan

    else begin

    // Syntax Error

    SyntaxError('Expecting '+cTokToStr[t]);

    end;

    end;

    procedure TParser.GetProcAddress(

    var funcPtr : TDoubleFunction);

    var

    ptr : Pointer;begin

    ptr := fCodeGen.GetFunctionPointer;

    funcPtr := TDoubleFunction(ptr);

    end;

    End Listing Two

  • 8/14/2019 The Persistence of Objects.pdf

    22/4022 October 1997 Delphi Informant

    Whats Multi-GenerationalArchitecture, Anyway?Inside InterBase: Part V

    Greater DelphiInterBase / Delphi

    ByBill Todd

    This final installment of the InterBase series exposes what really sets InterBaseapart from other database servers: its multi-generational, orversioning,architecture. Most new technologies are developed to solve a problem posed bya previous technology, and the InterBase versioning architecture is no exception.

    Before exploring versioning, its important to look at transaction processing (the

    previous technology, in this case), how it works, and why you need it.

    The Complete TransactionA transaction is a logical unit of work thatconsists of one or more changes to oneor more tables in a database that must allsucceed or fail as a unit. Why do you needtransactions? Heres the example thatappears in every database text: You go to

    your ATM to transfer $1,000 from your sav-ings account to your checking account. Todo this, the software must make twochanges to the database. It must decrease thebalance in your savings account, andincrease the balance in your checkingaccount. A problem arises if the databasesoftware decreases the balance in your sav-ings account, but crashes before it increasesthe balance in your checking account. Atthis point, you have lost $1,000.

    You want both changes to succeed, or nei-ther; thats what transaction processingensures. The software would start a transac-tion, reduce the balance in your savingsaccount, increase the balance in your check-ing account, then committhe transaction.Committing makes all the transactionschanges a permanent part of the database.Up to the moment the transaction is com-mitted, you can roll it back and undo thechanges. If the database server crashes beforethe transaction is committed, the transac-

    tion automatically will be rolled back whenthe server restarts.

    Dwelling in IsolationWhen you work with transactions, picking atransaction-isolation levelis critical. It controlshow or if it sees changes made byother transactions. The TransIsolation proper-ty of Delphis Database component lets youchoose one of three levels.

    The first isolation level is tiDirtyRead. If youchoose this isolation level, your transactioncan see every change made to the databaseby other transactions including thosethat havent yet committed. This is undesir-able, because it allows your transaction toread a value that may yet be rolled back,rendering that value incorrect. Thats whymany servers dont support dirty reads.

    The second isolation level is tiReadCommitted,which allows your transaction to see only thosevalues written to the database by transactionsthat have committed. This ensures that the val-ues used have become a permanent part of thedatabase. However, it doesnt protect yourtransaction from reading a value, then havinganother transaction change the value again,rendering it incorrect.

    The third and most restrictive isolation levelis tiRepeatableRead. With repeatable read,your transaction is guaranteed that a value

    will remain constant, no matter how manytimes its read from the database. Another

  • 8/14/2019 The Persistence of Objects.pdf

    23/4023 October 1997 Delphi Inf