Copyright: Copyright ©1995 - Prime Time Programming
Citation: Copyright ©1995 - Prime Time Programming


Contents
  The Unofficial Newsletter of Delphi Users - Issue #13 - May 1996

I bet you thought I had been hit by a bus or something! Well, I do appologize for skipping a month on everyone, but as I write this, I am 24 hours away from a 3,100 mile move from California to North Carolina. My wife and I will be driving the whole way and it will take about 6 days. Needless to say, preparing for this move has occupied a vast majority of my time over the last two months, and hence the delay of the newsletter. In addition, this issue is a bit small, because I had to rush to get anything out at all before I left. I will be working at home in North Carolina and will be able to manage my time much more efficiently for future issues. In fact, my plan is to expand the size and capabilities of UNDU as well, so please don't think that the small size of this issue marks any kind of trend...

All issues of UNDU can be obtained from a few sources. The first is the usual location on the Borland Delphi forum of CompuServe (GO DELPHI) in the "Delphi IDE" file section. In addition, the Informant forum on CompuServe (GO ICGFORUM) will also house the issues. Lastly, Informant communications has agreed to give UNDU a internet home on their web site (http://www.informant.com). They will keep a link on the home page to direct you to where the issues are. I will be converting all issues from Windows HLP files to HTML pages so the newsletters can be read on line. Informant will also have an FTP site that will house the HLP files themselves, for those of you who chose to download read them later.

You may also have noticed that I am changing the naming scheme for the newsletters. All future issues will be named DNMMYY where MM is the month and YY is the year. This issue is then named DN0596 for the May, 96 issue.

In light of my desire to expand on UNDU, I will be needing additional articles. If you have anything that you think the Delphi public can benefit from, whether it is a custom component, some tip or technique, or an article on some aspect of Delphi, I would love to see it. UNDU covers a fairly broad spectrum from very basic beginner discussions, to some of the more advanced techniques, so anything is welcome. Remember: It is the readers that makes UNDU work, not me!

- Robert

Using Quick Reports in Delphi 2.0

Core Concepts With Delphi - Sorting

Product Announcement - TIFIMG

The Delphi Information Connection

Using Resource-Only DLL's

Where To Find UNDU

Index of Past Issues


Delphi Information Connection
  The Unofficial Newsletter of Delphi Users - Issue #13 - May 1996

The Delphi Information Connection - http://www.delphi32.com

A week ago I was asked by the publisher of The Unofficial Delphi Newsletter to write an article about my own Delphi Web Site. I began wondering how to go about the task of doing so since I did not want to sound like I was bragging or letting all of the comments that I have received already blow my head up larger than the Goodyear blimp. This is the results of that article.

In January the small town of Pendleton, Oregon began offering Internet services to the community, something that had been going on around the larger cities for a few years now. After signing up for service with a provider I sat out to design a web site. The result was The Delphi Information Connection.

We designed The Delphi Information Connection as a place for users of Borland's Delphi to locate related information and to find tools and components for use with it. The information that is found at our site includes links to other Delphi related sites, components for downloading, lists of Delphi User Groups and Newsgroups, Delphi related Bulletin Boards Systems (yes, they still exist) and now a Live Chat area for Netscape 2.0 For Windows users.

In most cases, information is submitted by visitors using a form or email. They tell us where on the Internet a component or link is located and we verify and update the pages that the information pertains to. There are times that me or my wife (Susan Bernard) go browsing the web for information to include.

Currently the pages at the site receive 3000+ hits per day (combined total). The site has been running since February 16th, 1996, so we felt this was a good-sign that we had done something right. On February 26th, 1996 I registered the domain name of delphi32.com, although the current URL for the site is still located at http://www.ucinet.com/~dchamber

Oregon Trail Internet located in Eastern Oregon will house the new location of the site. The move is planned for the 1st of April but then again we have been working on the move since March 1st so don't quote me on it.

The new location will provide FTP access, a chat server for non-Netscape users, additional storage space to house components from authors that do not already have them located on the Web. We are also adding Excite's Search Engine for searching the site and a database system to house other information such as email addresses or component vendor information. We are also working on housing other Delphi programmer pages on the site.

There is a lot more in store for The Delphi Information Connection, all I can say is "Check the site often" as you never know what we have done or are working on and information is added daily to the site. If you are a Delphi programmer that has written components, want to share your knowledge or are looking for a particular file, stop on by and check us out. Authors that write small articles can also have there work published on our Tips and Tricks or Programming Example pages.

If you haven't already stopped by for a visit then please check it out. If you would like further information you can contact me via email at

dbernard@oregontrail.net

Copyright 1996 by David Bernard - All Rights Reserved!

Return to Front Page


Index of Past Issues
  The Unofficial Newsletter of Delphi Users - Issue #13 - May 1996

Index of Past Issues

Below is a complete index of all principle articles in past issues of the Unofficial Newsletter of Delphi Users. Provided that you have the prior issues in the same directory as this issue, you can click on any of these hotspots to go directly to that article. To return to the index, you can click on the Back button, or you can use the History list. Once you jump to one of these issues, you can navigate through the issue as you would normally, but you will need to go to the History list to get back to this index. There will be an updated index included in all future issues of UNDU.

Issue #1 - March 15, 1995

What You Can Do

Component Design

Currency Edit Component

Sample Application

The Bug Hunter Report

About The Editor

SpeedBar And The ComponentPalette

Resource Name Case Sensitivity

Lockups While Linking

Saving Files In The Image Editor

File Peek Application

Issue #2 - April 1, 1995

Books On The Way

Making A Splash Screen

Linking Lockup Revisited

Problem With The CurrEdit Component

Return Value of the ExtractFileExt Function

When Things Go Wrong

Zoom Panel Component

Issue #3 - May 1, 1995

Articles

Books

Connecting To Microsoft Access

Cooking Up Components

Copying Records in a Table

CurrEdit Modifications by Bob Osborn

CurrEdit Modifications by Massimo Ottavini

CurrEdit Modifications by Thorsten Suhr

Creating A Floating Palette

What's Hidden In Delphi's About Box?

Modifications To CurrEdit

Periodicals

Progress Bar Bug

Publications Available

Real Type Property Bug

TIni File Example

Tips & Tricks

Unit Ordering Bug

When Things Go Wrong

Issue #4 - May 24, 1995

Cooking Up Components

Food For Thought - Custom Cursors

Why Are Delphi EXE's So Big?

Passing An Event

Publications Available

Running From A CD

Starting Off Minimized

StatusBar Component

TDBGrid Bug

Tips & Tricks

When Things Go Wrong

Issue #5 - June 26, 1995

Connecting To A Database

Cooking Up Components

DateEdit Component

Delphi Power Toolkit

Faster String Loading

Font Viewer

Image Editor Bugs

Internet Addresses

Loading A Bitmap

Object Alignment Bug

Second Helping - Custom Cursors

StrToTime Function Bug

The Aquarium

Tips & Tricks

What's New

When Things Go Wrong

Issue #6 - July 25, 1995

A Call For Standards

Borland Visual Solutions Pack - Review

Changing a Minimized Applications Title

Component Create - Review

Counting Components On A Form

Cooking Up Components

Debug Box Component

Dynamic Connections To A DLL

Finding A Component By Name

Something Completely Unrelated - TVHost

Status Bar Component

The Loaded Method

Tips & Tricks

What's In Print

Issue #7 - August 31, 1995

ChartFX Article

Component Cookbook

Compression Shareware Component

Corrected DebugBox Source

Crystal Reports - Review

DBase On The Fly

Debug Box Article

Faster String Loading

Formula One - Review

Gupta SQL Windows

Header Converter

Light Lib Press Release

Limiting Form Size

OLE Amigos!

Product Announcements

Product Reviews

Sending Messages

Study Group Schedule

The Beginners Corner

Tips & Tricks

Wallpaper

What's In Print

Issue #8 - October 10, 1995

Annotating A Help System

Core Concepts In Delphi

Creating DLL's

Delphi Articles Recently Printed

Delphi Informant Special Offers

Delphi World Tour

Getting A List Of All Running Programs

How To Use Code Examples

Keyboard Macros in the IDE

The Beginners Corner

Tips & Tricks

Using Delphi To Perform QuickSorts

Issue #9 - November 9, 1995

Using Integer Fields to Store Multiple Data Elements in Tables

Core Concepts In Delphi

Delphi Internet Sites

Book Review - Developing Windows Apps Using Delphi

Object Constructors

QSort Component

The Component Cookbook

TSlideBar Component

TCurrEdit Component

The Delphi Magazine

Tips & Tricks

Using Sample Applications

Issue #10 - December 12, 1995

A Directory Stack Component

A Little Help With PChars

An Extended FileListBox Component

Application Size & Icon Tip

DBImage Discussion

Drag & Drop from File Manager

Modifying the Resource Gauge in TStatusBar

Playing Wave Files from a Resource

Review of Orpheus and ASync Professional

The Component Cookbook

Tips & Tricks

UNDU Readers Choice Awards

Using Integer Fields to Store Multiple Data Elements in Tables

Where To Find UNDU

Issue #11 - January 18th, 1996

Core Concepts With Delphi - Part I

Core Concepts With Delphi - Part II

Dynamic Delegation

Data-Aware DateEdit Component

ExtFileListBox Component

DBExtender Product Announcement

Dynamic Form Creation

Finding Run-Time Errors

Selecting Objects in the Delphi IDE

The Beginners Corner

The Delphi Magazine

Top Ten Tips For Delphi

The Component Cookbook

Tips & Tricks

The UNDU Awards

Where To Find UNDU

Issue #12 - Feburary 23rd, 1996

The Beginners Corner

Delphi Projects

Marketing Your Components

An LED Component

A 3D Progress Bar

Common Strings Functions

Checking if your application is running already

AutoRepeat for SpeedButtons

Form and Component Creation Tip

Detecting a CD-ROM Drive

Drawing Metafiles in Delphi

Shazam Review

Product Announcement - Dr. Bob's Delphi Experts

Book Review - Instant Delphi Programming

Tips & Tricks

The Component Cookbook

Where To Find UNDU

Issue #13 - May, 1996 - (This Issue)

Core Concepts - Sorting

Delphi Information Connection

Creating Resource-Only DLL's

Quick Reports

TIFIMG Product Announcement

Where To Find UNDU

Return to Front Page


Where To Find UNDU
Typically, each issue of the newsletter is posted to three locations. The first is the Borland Delphi forum on CompuServe (GO DELPHI) in the "Delphi IDE" file section. If you want the issue as soon as it comes out, then this is the place to look. I also put the issue in the Informant Communications forum (GO ICGFORUM) in the "Delphi 3rd Party" file section at the same time. Lastly, all issues are being converted to web pages and stored on the Informant Communications web site located at http://www.informant.com


Quick Reports
  The Unofficial Newsletter of Delphi Users - Issue #13 - May 1996

Quick Reports

by Cary Jensen

Report generation in Delphi 1.0 was provided by ReportSmith, which, although being a strong report generator, failed to meet the needs of many Delphi developers. The large footprint of the ReportSmith runtime made it unsuitable for many types of applications. Fortunately, third-party developers where quick to fill the need, offering VCL-based report generators that permitted reports to be compiled into the EXE. Among the notable offerings were ReportPrinter, from Nevrona Designs, Ace Reporter, from SCT, and QuickReport, from QSD AS.

Borland, apparently heeding the call by developers for additional flexibility in a reporting solution, and has included QuickReport in Delphi 2. (A 16-bit shareware version is available for download. See http://www.qsd.no/products/quickrep for more information.) QuickReport is a set of components that permit you to transform a form into a report generator, allowing you to print and/or preview a variety of report styles based on native Delphi DataSources and DataSets. As a result, QuickReport represents a significant tools for Delphi developers. This article provides you with an introduction to creating a basic QuickReport report.

There are six essential components you use to create a report with QuickReport. At a minimum, you will need:

1.	A form

2.	A QuickReport component

3.	One DataSource component

4.	One DataSet descendants (a Table, a Query, or a StoredProc component)

5.	A QRBand component

6.	One or more of the QuickReport-printable components (The most basic of the QuickReport-printable components are QRLabel, which is used for printing a static text label, and QRDBLabel, which is used for printing data fields from a DataSet.)

You use the Form component as a container in which the QuickReport is built. In most cases, you will use one Form for each QuickReport report in your application. It is possible, although less common, to make one form as the basis for multiple reports.

The QuickReport component, found on the QReport page of the component palette, is the central component for any QuickReport report. Conceptually, you can think of a QuickReport component as changing a Form into a report. The QuickReport component links, by way of its DataSource property, to the required DataSource component. This DataSource, in turn, must point to the required DataSet. The data in this DataSet is the main source of data for the report.

Most QuickReport reports contain more than one QRBand component. The QRBand is a container in which you place the printable elements of the report. As mentioned previously, these include the QRLabel and QRDBLabel components.

How the printable elements you place in a QRBand are displayed depends primarily on the BandType property of the QRBand. If individual records from the QuickReport’s DataSource are being printed, at least one QRBand component in the report will have a BandType of rbDetail. The printable elements within this QRBand are printed for every record in a DataSet. Since most reports also include a page header and footer, you will likely include at two additional bands in your report, one with its BandType set to rbPageHeader and another with its BandType set to rbPageFooter.

Creating a Simple Report Example

The following steps show you how to create a simple QuickReport report:

1.	Create a new project.

2.	On the main form of the project place a DataSource, a Table, a QuickReport, and three QRBand components. The default Align property for the QRBand is alTop, so the three QRBands will automatically move to the top of the form.

3.	Set the DataSet property of the DataSource to Table1. For the Table component, set its DatabaseName property to DBDEMOS, its TableName property to CUSTOMER.DB, and its Active property to True.

4.	Set the QuickReport’s DataSource property to DataSource1.

5.	Set the top QRBand’s BandType property to rbPageHeader, the second to rbDetail, and the third to rbPageFooter.

6.	Place one QRLabel and one QRDBLabel in the middle QRBand. Set the QRLabel’s Caption property to Company Name. For the QRDBLabel, set its DataSource property to DataSource1, and its DataField property to Company. Since Table1 is active, as soon as you have set the DataField property of the QRDBLabel component to Company, the company name associated with the first record in the CUSTOMER.DB table will appear in this component. The form should now look like the following:

Using a QuickReport Report

To permit a user to preview or print a QuickReport report, at a minimum you must explicitly call the QuickReport Preview or Print method. But that is the absolute minimum. In most cases, you will  include several additional steps to set it up properly. For example, you will generally never display to the user the form on which the QuickReport component appears. Instead, you will call the QuickReport’s Preview or Print methods from another form.

Use the following steps to demonstrate printing or previewing at runtime with the report created in the preceding section:

1.	From the Delphi main menu, select File|New Form.

2.	On this new form, place two buttons. Change the Caption property of Button1 to Preview Report, and change the Caption property of Button2 to Print Report.

3.	Double-click the Preview Report button to create an OnClick event handler for it. In this event handler enter the following code:

procedure TForm2.Button1Click(Sender: TObject);

begin

  Form1.QuickReport1.Preview;

end;

4.	Double-click the Print Report button, and enter the following code into the automatically created OnClick event handler:

procedure TForm2.Button2Click(Sender: TObject);

begin

  Form1.QuickReport1.Print;

end;

5.	While in unit2, add the following uses clause to the implementation section:

uses Unit1;

6.	Make this new form, Form2, the Main form. Do this by selecting Project|Options (Delphi 2) or Options|Project Options (Delphi 1.0) to display the Project Options dialog box. From the Forms page of this dialog box, select Form2 from the Main Form drop-down menu.

7.	Run the form. Now, when you click the Preview Report button, the default QuickReport previewer dialog box will appear. When you click the Print Report button, the QuickReport will be sent to the printer.

Quick Reports Source

This article is based in part on material appearing in the chapter Reporting with QuickReport in Delphi 2, from Delphi In Depth (Obsorne/McGraw-Hill, 1996. ISBN 0-07-882211-4. US $42.95). For more information about this book, see http://gramercy.ios.com/~jdsi/did.html.

About the Author

Cary Jensen is President of Jensen Data Systems, Inc., a Houston-based database development company. He is Contributing Editor and columnist for Delphi Informant Magazine, and Coordinating Editor and co-author of Delphi In Depth (Osborne/McGraw-Hill, 1996).

Return to Front Page


TiffImg
  The Unofficial Newsletter of Delphi Users - Issue #13 - May 1996

Product Announcement - TIFIMG

By  COMMUNICONSULTANTS/ Kent Phelps

TIFIMG is a Delphi component which reads, decompresses, displays, roatates and otherwise manipulates images, especially TIFF (G4, G32D, G31D, LZW, PACKBITS, UNCOMPRESSED), GIF, DCX BMP, JPEG, WMF. Fast Decompression and rotating are supported (1-3 sec range). Images can be cropped, and the cropped area can be enlarged, saved and/or printed. A Pan window function is supported which allows easy scrolling through large engineering drawings and maps. Tiff Tags can be read, and expansion of 200 x 100 fax images can be accomplished. It allows Text annotation directly onto images, and can provide a floating MAGNIFYING GLASS over an image. MultiPage images (TIFF, DCX) are supported.

Price is $45US plus $4-5 shipping depending on what area of the world you're in. 32bit versions add $10. DCU Source Code + enhanced support is available for anyone purchasing 10 copies. All purchasers get free minor upgrades, discounts on major upgrades and new products.

DELPHI Add-ons to TIFIMG are (or will be) :

TIFIMG32	32 bit version - available May '96

CCITT.DLL	Ability to read raw CCITT images (not packaged by TIFF) such as BITFAX,

Winfax PRO, and Images created with other, especially older, Imaging systems.

available late april '96

 Advanced Image Manipulation : contrast, dither, skew, scale, convert - available July '96

OTHER PRODUCTS:

SCANNER TWAIN component  - simple and cheap TWAIN component - flatbed scanners available now; ADF feeder scanners (hopefully) available June '96

BARCODE - 	read a barcode from a scanned image - Code 3 of 9 available now, code 2/5, codeabar, code 128 available August '96

TIFIMG and it's cousins are products of

COMMUNICONSULTANTS/ Kent Phelps
P.O. 440
Conifer, CO 80433 USA

Compuserve 74020,1513 or Internet : communi@qadas.com

Return to Front Page


Quick Reports Source
  The Unofficial Newsletter of Delphi Users - Issue #13 - May 1996

program Demo1;

uses

  Forms,

  demo1u1 in 'demo1u1.pas' {Form1},

  demo1u2 in 'demo1u2.pas' {Form2};

{$R *.RES}

begin

  Application.Initialize;

  Application.CreateForm(TForm2, Form2);

  Application.CreateForm(TForm1, Form1);

  Application.Run;

end.

/////////////////////////////////////////////////////////////////////////////

unit demo1u1;

interface

uses

  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

  Quickrep, StdCtrls, ExtCtrls, DB, DBTables;

type

  TForm1 = class(TForm)

    DataSource1: TDataSource;

    Table1: TTable;

    QuickReport1: TQuickReport;

    QRBand1: TQRBand;

    QRLabel1: TQRLabel;

    QRDBText1: TQRDBText;

  private

    { Private declarations }

  public

    { Public declarations }

  end;

var

  Form1: TForm1;

implementation

{$R *.DFM}

end.

/////////////////////////////////////////////////////////////////////////////

unit demo1u2;

interface

uses

  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

  StdCtrls;

type

  TForm2 = class(TForm)

    Button1: TButton;

    Button2: TButton;

    procedure Button1Click(Sender: TObject);

    procedure Button2Click(Sender: TObject);

  private

    { Private declarations }

  public

    { Public declarations }

  end;

var

  Form2: TForm2;

implementation

{$R *.DFM}

uses demo1u1;

procedure TForm2.Button1Click(Sender: TObject);

begin

  Form1 := TForm1.Create(Application);

  Form1.QuickReport1.Preview;

  Form1.Release;

end;

procedure TForm2.Button2Click(Sender: TObject);

begin

  Form1 := TForm1.Create(Application);

  Form1.QuickReport1.Print;

  Form1.Release;

end;

end.

Return to Article

Return to Front Page


Core Concepts
  The Unofficial Newsletter of Delphi Users - Issue #13 - May 1996

Core Concepts With Delphi - Sorting

by Alan G. Labouseur - CIS: 70312,2726

Good day, and welcome spring, and the next chapter of Core Concepts.  When we last got together it was snowing by the bucketful as we discussed sub-programs and variable scope.  I had planned to follow up with a discussion of parameter passing.  The subject of parameter passing logically follows subprograms, because they are what you pass the parameters to.  But I'm going to save that for next time.  Instead, we're going to focus on one of the algorithmic steps used in the example program from last time.  Recall if you will the starting example for the mailing list program which is roughly expressed in pseudo-code:

Program MailingLabels

Begin

  ask the user for the name of the list

  read that list into memory

  ask the user how the list should be sorted: name or zip code

  sort the list

  ask the user about where to send the output: screen or printer

  print the labels

End.

The fourth step is to "sort the list".  What does that mean?  Well, later in the same article I presented a more refined outline of the program which included a procedure entitled "SortList".  In it we asked the user about how they wanted the data sorted and then called PleaseWait.  The next line of code read "{ code to sort the list }".  Not much help there.  So this is the topic for today: Sorting.

You can sort anything that's in a list.  There are many ways to represent a list: an array, a chain of pointers, an instance of Delphi's TStringList object.  For the purposes of this discussion and demonstration, we'll be considering the conventional one-dimensional array.  There are several well known and well established ways to sort data.  We'll look at two of them.  First, the easy-to-understand but slow way: the Swap Sort.  Second is the harder-to-understand but fast way: the QuickSort.

The "Swap Sort"

Let's think about what we would do if we had to sort something, like numbered lottery balls for instance.  We could line them all up (randomly, in a sandbox so that they don't roll away) and then start putting them in order.  How would we put them in order?  We could scan the balls and find the lowest one and place it in the first position.  We then look for the second lowest ball and put it in its place.  Doing this for every ball in the sandbox will yield us a sorted array of balls.  Great!  We can program the computer do to the same thing, minus the sandbox.

Let's write a Delphi program to compare each array element to all others and put it in its proper ordered place.  To do this we need to iterate through the array.  A for loop is needed here.

for i := 1 to ArraySize do begin { ArraySize is a constant integer }

end; { i }

This allows us to iterate through the array one element at a time.  While doing this, we want to compare each current ("i-th") element to all others in the array.

for i := 1 to ArraySize  do begin { ArraySize is a constant integer }

   compare current element to all other elements

end; { i }

We need a second, nested for loop here to compare the current (i-th) element to all others in the array.

for i := 1 to ArraySize do begin { ArraySize is a constant integer }

   for j := i+1 to ArraySize do begin

   end; { j }

end; { i }

Look carefully at the "for j" loop.  We start j at i+1 each time.  This is so that we don't waste time comparing the i-th element to itself.  Also, since we know that at the end of each pass through the i loop, the i-th element will have in it the proper value, we don't need to recompare the past i elements in subsequent iterations.  Thus, we always start the inner j loop at element i+1.

The last step here is to implement the compare and swap.  We'll look at the i-th and j-th elements and swap them if necessary.

for i := 1 to ArraySize do begin

   for j := i+1 to ArraySize do begin

      if MyArray[i] > MyArray[j] then begin

         temp := MyArray[i];

         MyArray[i] := MyArray[j];

         MyArray[j] := temp;

      end; { if }

   end; { j }

end; { i }

And that's all there is to it.  I have incorporated this code into a demonstration program.  Here it is:

program Swapsort;

uses

  WinCrt;

const

  ArraySize = 9;

type

  TMyArray = array[1..ArraySize] of integer;

var

  MyArray : TMyArray;

  i 	: integer;

Procedure SwapSortEm;

var

  temp, i, j : integer;

Begin

   { Compare every element in the array ... }

   for i := 1 to ArraySize do begin

      { ... to every other element in the array ... }

      for j := i+1 to ArraySize do begin

         if MyArray[i] > MyArray[j] then begin

            { ... and swap them if they're out of order. }

            temp := MyArray[i];

            MyArray[i] := MyArray[j];

            MyArray[j] := temp;

         end; { if }

      end; { j }

   end; { i }

End;  { Procedure SwapSortEm }

Begin  { Program SwapSortCrt }

   Randomize;   { Initialize the random number function. }

   writeln('Before . . .');

   for i := 1 to ArraySize do begin

      MyArray[i] := trunc(random(1000))+1;

      writeln(i, ': ', MyArray[i]);

   end; { for }

   SwapSortEm;

   writeln('After . . .');

   for i := 1 to ArraySize do begin

      writeln(i, ': ', MyArray[i]);

   end; { for }

End.  { Program SwapSortCrt }

The swap sort is quick to program and it works quite well.  There is one major problem, however.  It can take a really long time to run if we're sorting a large list.  The example program sorts only nine values, so it doesn't matter.  What if we were sorting 9,000,000 values?  Now speed counts.  The reason that the swap sort is so slow is that it must compare all elements (n) to roughly all other elements (n) all the time.  This means that there are roughly n x n compares.  For a small list, as in the example program, that's roughly 9 x 9 = 81 compares.  No biggie.  But 9,000,000 x 9,000,000 is a different story.  There must be a faster, more efficient way.  Thankfully, there is.

The "QuickSort"

Sorting large big arrays can be a big problem.  What's a proven method to solve big problems?  Divide and conquer.  Break the large problem into smaller component problems.  Solve all of the small problems and you've solved the big one.  That's just what a certain computer scientist in the early 1960's was thinking when he came up with the idea for a quick sort.  He called it, not surprisingly, the QuickSort.

This divide and conquer algorithm was developed by Anthony Hoare in the early 1960's.  In essence, we take an array and select an element whose value will be the "pivot" value.  Then divide the array into two parts: those elements whose values are less than the pivot value, and those elements whose values are greater than the pivot value.  Divide the large array into two smaller sub-arrays.  Now recursively sort the two sub-arrays.

That last line is the killer.  It's through the wonder of recursion that QuickSort works its magic.  What's recursion?  Well, aside from being another article topic, recursion is when a subprogram calls itself, kind of like a circular definition in a dictionary:

"Drums" - noun; percussive instruments played by hitting the drum head with a stick.  See Conga.

"Conga" - noun; special drum played with the palm of your hand.  See drums.

In terms of the QuickSort, we are using it to divide the array into two smaller arrays, and then calling QuickSort on each smaller array.  QuickSort will then process both smaller arrays in turn, dividing each into two smaller-still arrays, and calling itself recursively again.  This happens until all small arrays are sorted, which means that the original, large array is sorted and we're done.

So, after the first pass we have an array partitioned into two parts.  All we know is that the values in the lower part are all less than those in the upper part.  By applying the QuickSort  process to each of these parts, we'll get an array with two parts of two parts each.  Then they'll sort two parts, and they'll sort two parts, and so on, and so on . . . until we have an array that is completely sorted.  Cool, eh?  We'll know when the array is completely sorted by watching the boundaries on the subarrays.  When they meet, we're done.

Here's some Object Pascal code to demonstrate.  It sorts an array of integers.  You can modify this code to sort almost any type of array.  Actually, there are several areas for improvement on this code.  You could write a swap routine rather than using in-line code as I have.  (Although in-line code has the advantage of being faster, and if you're sorting a huge array it makes a difference.)  You could write data-type-specific compare functions which you pass as parameters to the QuickSort routine so that a single QuickSort engine can drive QuickSort for all data types.  And more.  But here's the general idea:

Program QuickSortCrt;

uses

  WinCrt;

const

  ArraySize = 9;

type

  TMyArray = array[1..ArraySize] of integer;

var

  MyArray : TMyArray;

  i : integer;

Procedure QuickSort(lowerPos, upperPos: integer);

   var temp, i, middlePos, pivotValue : integer;

Begin

   { check that the lower position is less than the upper position }

   if lowerPos < upperPos then begin

      { Select a pivot value }

      pivotValue := MyArray[lowerPos];

      { default to the middle position to the lower position }

      middlePos := lowerPos;

      { partition the array about the pivot value }

      for i := lowerPos+1 to upperPos do begin

         if MyArray[i] < pivotValue then begin

            { bump up the middle position }

            inc(middlePos);

            { swap this element to the "lower" part of the array }

            temp := MyArray[middlePos];

            MyArray[middlePos] := MyArray[i];

            MyArray[i] := temp;

         end; { if }

      end; { for }

      { place the pivot value in the middle to finish the partitioning }

      temp := MyArray[lowerPos];

      MyArray[lowerPos] := MyArray[middlePos];

      MyArray[middlePos] := temp;

      { Finally, recursively call QuickSort on the two parititioned halves.}

      QuickSort(lowerPos, middlePos-1);

      QuickSort(middlePos+1, upperPos);

   { else

      the lower position has reached or exceeded the upper position,

      so we're done.  This case terminates the tail-end recursion. }

   end;  { if }

End;  { Procedure QuickSort }

Begin  { Program QuickSortCrt }

   Randomize;   { Initialize the random number function. }

   writeln('Before . . .');

   for i := 1 to ArraySize do begin

      MyArray[i] := trunc(random(1000))+1;

      writeln(i, ': ', MyArray[i]);

   end; { for }

   QuickSort(1, ArraySize);

   writeln('After . . .');

   for i := 1 to ArraySize do begin

      writeln(i, ': ', MyArray[i]);

   end; { for }

End. { Program QuickSortCrt }

Although QuickSort is harder to understand, and therefore harder to program, it works extremely well and it quite efficient.  Where the swap sort would take roughly n x n compares to sort the array, QuickSort requires only roughly n log n.  So where a swap sort on 10,000 elements would require roughly 100,000,000 compares, QuickSort needs only 132,877!  Pretty cool, eh?  Now that 9,000,000 element array is within our sortable reach! Remember that although these examples deal only with arrays, you can use the same methodologies on lists and pointer chains, and many other data types you encounter.

I hope this has been helpful in providing more core information upon which we can build in the future.  Feel free, encouraged even, to e-mail me with any comments, questions, or suggestions.  Thank you and goodnight.

About the Author

Alan G. Labouseur is Vice President and co-owner of AlphaPoint Systems, Inc., a custom programming consultancy located in Brewster, NY.  Alan has a Masters degree in Computer Science and has been developing custom software for ten years.  He is currently working on Clipper and Delphi applications for clients in the NY-NJ-CT area.  You can reach Alan through e-mail at AGL007@IX.NETCOM.COM or 70312,2726 here on CompuServe.

Return to Front Page


DLL's
  The Unofficial Newsletter of Delphi Users - Issue #13 - May 1996

Building Resource-Only DLLs in Delphi

Dr. Adrian Bottoms - CIS: 100435,2330

This article describes a method of building Dynamic Link Libraries (DLL) using Delphi 1.0 (minor modifications need only be made to port this to Delphi 2.0) A previous article by Maga WiseCarver in UNDU Issue #8, October 10th 1995 describes building DLLs containing code using Delphi.

I previously described a method for playing wave files from resources compiled into the executable code (UNDU Issue #10, December 12th 1995). This overcame some problems with the DOS filing systems as described but also increased the compile/link time for Delphi each time I made a change to the program. I decided that what I needed was a way of building a DLL that contained only resources.

Initially I couldn't see how to do this with Delphi so I resorted to using C. The following code is the fragment needed to create a stub DLL which can be inserted into a DLL_STUB project. If this is built as a Windows DLL it creates a stub DLL which is about 2.8Kb long.

    /*

    **

    **  This code provides a stub for a DLL without anything usefull

    **  inside it. Its purpose is to exist to provide a stub DLL which

    **  can be used by the resource compiler to build a resource only DLL.

    **

    */

    #include <windows.h>

    int FAR PASCAL LibMain (HANDLE hInstance,

                            WORD wDataSeg,

                            WORD wHeapSize,

                            LPSTR lpszCmdLine) {

        if (wHeapSize > 0) UnlockData (0) ;

        return 1 ;

        }

I used this to make up my resource-only DLLs in the following way. First, place a copy of DLL_Stub.DLL somewhere convenient. Then create a resource compiler source file. For example WAVES.RC contains:

A       WAVE    WAVES\A.WAV

I       WAVE    WAVES\I.WAV

U       WAVE    WAVES\U.WAV

E       WAVE    WAVES\E.WAV

O       WAVE    WAVES\O.WAV

BA      WAVE    WAVES\BA.WAV

BE      WAVE    WAVES\BE.WAV

BI      WAVE    WAVES\BI.WAV

BO      WAVE    WAVES\BO.WAV

BU      WAVE    WAVES\BU.WAV

GA      WAVE    WAVES\GA.WAV

GE      WAVE    WAVES\GE.WAV

GI      WAVE    WAVES\GI.WAV

GO      WAVE    WAVES\GO.WAV

GU      WAVE    WAVES\GU.WAV

Now create a suitable batch file along the lines of:

@ECHO OFF

REM	MAKE_DLL.BAT

REM	If no %1 then do the lot

if [%1] == [] goto HIRAGANA

REM	Force the command line parameter to uppercase

set path_save=%path%

path %1

set param %path%

path %path_save%

set path_save=

    	if [param] == [HIRAGANA] goto HIRAGANA

    	if [param] == [KATAKANA] goto KATAKANA

    	if [param] == [WAVES]    goto WAVES

goto NoP1

:HIRAGANA

    	set module=HIRAGANA.DLL

    	copy ..\dll_stub\dll_stub.dll HIRAGANA.DLL

    	brcc HIRAGANA

    	if ERRORLEVEL 1 goto ERROR

    	rlink HIRAGANA.RES HIRAGANA.DLL

    	if ERRORLEVEL 1 goto ERROR

    	if NOT [%1] == [] goto EXIT

:KATAKANA

    	set module=KATAKANA.DLL

    	copy ..\dll_stub\dll_stub.dll KATAKANA.DLL

    	brcc KATAKANA

    	if ERRORLEVEL 1 goto ERROR

    	rlink KATAKANA.RES KATAKANA.DLL

    	if ERRORLEVEL 1 goto ERROR

    	if NOT [%1] == [] goto EXIT

:WAVES

    	set module=WAVES.DLL

    	copy ..\dll_stub\dll_stub.dll WAVES.DLL

    	brcc WAVES

    	if ERRORLEVEL 1 goto ERROR

    	rlink WAVES.RES WAVES.DLL

    	if ERRORLEVEL 1 goto ERROR

    	if NOT [%1] == [] goto EXIT

    	REM All done. Bail out.

    	goto EXIT

:NoP1

ECHO	Usage:  Make_DLL [hiragana|katakana|waves]

goto exit

:ERROR

    	ECHO    MAKE_DLL Error in %module% !!!!!!!!

:EXIT

Note that this uses the Borland Resource Compiler (BRCC) and The Borland Resource Linker (RLINK) supplied with Delphi in \DELPHI\BIN (which are in my PATH).

The only remaining thing to do then is to access the resources in these DLLs from Delphi. I'll cover how that's done after I've shown you how to use Delphi to build a resource-only DLL. To build a resource-only DLL using Delphi follow the following scheme:

1.	Use a text editor to create your resource file source code; for example THING.RC. Compile it with the Borland Resource Compiler:

\DELPHI\BIN\BRCC THING

WARNING. Do NOT use the same name as the name of your resource-only DLL as Delphi creates its own project resource file with that name and in a straight fight with Delphi you will loose!

2.	Chose File|New Project to create a new project containing Unit1 and Form1

3.	Chose File|Remove File. Remove Unit1 & Form1 from the Project. Don't bother to save changes to the project.

4.	Chose File|New Unit. Creates Unit1.

5.	Chose View|Project Source. Edit the Unit1 as follows:

a. Change "Program" to "Library"

b. Remove "Forms" from the Uses clause

c. Delete the line containing "{$R *.RES}"

d. Remove all code between the BEGIN and END

6.	Click on the "Unit1" tab to open it for editing and after "Implementation" insert:

{$R THING.RES}	{ Or whatever your resource file is called }

7.	Press F9 to compile and link the DLL.

Note that if you are using BITMAP, CURSOR or ICON resources you can create the resource file (.RES) by using the supplied Delphi image editor. I don't know at the moment whether version 2 of Delphi has a more extended resource editor; I'm still waiting for delivery!

It is interesting that if you omit the {$R THING.DLL} from the unit you will end up with a stub DLL as produced above by C. However the stub produced by Delphi is about half the size of the Visual C one. Less run-time overhead I presume.

Now that you own a resource-only DLL you will need to know how to use it. This is fairly straighforwards and boils down to two issues, firstly getting the DLL to be loaded and secondly, accessing the resources in the DLL. DLLs are loaded using the following code segement:

UNIT Main;

.

.

.

VAR                                         { Global data                      }

    BMPDLLName      : Array [0..9] OF Char; { Name of Bitmap DLL               }

    BMPHandle       : THandle;              { Handle of Kana bitmap DLL        }

.

.

.

PROCEDURE TMainForm.KanaMenuItmClick;

    {   Common handler for KatakanaMenuItmClick and HiraganaMenuItmClick.      }

    {   This code carries out the common operations to set (or swap to)        }

    {   the selected representation.                                           }

VAR

    Temp    : Array [0..255] OF Char;

BEGIN

    { Load the bitmap DLL for the right kana                                   }

    IF BMPHandle <> 0 THEN BEGIN                { If it's already loaded ...   }

        FreeLibrary(BMPHandle);                 { Unload it                    }

        BMPHandle := 0;                         { Flag not loaded              }

    END;

    StrCopy(Temp, 'C:\DELPHI\KANA\');

    StrCat(Temp, BMPDLLName);

    StrCat(Temp, '.DLL');

    BMPHandle := LoadLibrary(Temp);

    IF BMPHandle < 32 THEN BEGIN

        MessageDlg('Bitmap DLL load failure: ' + BMPDLLName, mtError, [mbOK] , 0);

        MainForm.Close;

    END;

END;

.

.

.

END.

This code segment loads the appropriate DLL (for this application!). It is important to remember the handle that is returned by the LoadLibrary routine since you will need this later. The Windows API routines which load and access resources all use, as their first parameter, the handle of the instance to load from. When accessing a resource from your loaded DLL you will need to specify the handle returned by the LoadLibrary call. For example:

PROCEDURE TMainForm.ShowKana;

    {   Show the bitmap image of the kana that's been selected.

    {   This is done by loading the image from the resources

    {   built into the loaded DLLs.

VAR

    KanaName    : Array [0..24] OF Char;     { Ctring type string }

BEGIN

    { Resource name is kana name plus "H" or "K" for hiragana or katakana }

    StrCopy(KanaName, KanaList[KNum]);    { Get the name of this kana }

    StrCat(KanaName, KanaSet);            { Add the "H" or "K" }

    KanaBmp.Handle := LoadBitmap(BMPHandle, KanaName);

    KanaImage.Picture.Graphic := KanaBmp;

    DeleteObject(KanaBmp.Handle);

    KanaImage.Visible := TRUE;

    KanaImage.Repaint;

END;

It is your responsibility to unload the DLL before your program terminates otherwise ot will be left lying around in memory until you reboot Windows. For example my program contains the following:

PROCEDURE TMainForm.FormDestroy(Sender: TObject);

BEGIN

    {Unload any loaded DLLs that we may have left lying around.}

    IF DLLHandle >= 32 THEN FreeLibrary(DLLHandle);

    IF BMPHandle >= 32 THEN FreeLibrary(BMPHandle);

    IF WAVHandle >= 32 THEN FreeLibrary(WAVHandle);

END;

Resource-only DLLs are useful tools, the standard application being the internationalisation of software. You can place all of your messages in the string resource and have a different DLL for each language you wish to support. In this way your code becomes independant of the language your users are working with.

Another use is to implement another instance of the axiom I trot out whenever I can: "All problems in computing are soluable by another level of indirection!". Carefull design of the data contained in a resource-only DLL allows you to develop a general purpose driver program which picks up its configuration from the data contained in a DLL.

For example I have been reworking my Japanese kana tutor to handle other languages with different representation from roman. All of the data structures to support, for example, Thai, Japanese, Cyrillic, Korean etc. are present in a representation specific DLL. The driver program scans the directory for DLLs when it starts up and builds the items on a "Language" menu at run time. To add support for another language I just create the DLL and add it to the directory.

I am indebted to Bob Gillett for his help in figuring out how to get Delphi to produce resource-only DLLs.

Return to Front Page