Delphi Informant 95 2001
Delphi Informant 95 2001
Do the Strand
Delphi 2 Multithreading
ON THE COVER
9 Do the Strand — Joseph C. Fung
35 Dynamic Delphi — Andrew Wozniewicz
The 32-bit version of Windows features true multitasking through
It’s the final installment of a four-part series on DLLs. Mr Wozniewicz
multithreading, a technology that can enhance the performance of
concludes by discussing: dynamically loading and releasing a DLL, using
your Windows 95 and Windows NT applications. Mr Fung explains
dynamically loaded DLLs, and accessing data in a library. He even pro-
how to implement multithreading capabilities into your Delphi 2
vides a DLL summary for your quick reference.
programs using clear, workable examples.
FEATURES REVIEWS
15 Informant Spotlight — Kevin Bluck
40 Memory Monitor for Delphi
Most programmers use Paradox tables for their Delphi database
Product review by Robert Vivrette
applications. Depending on the application, however, Local InterBase
Resource leaks are the bane of Windows programming.
may be the better tool. To help you decide which product best suits
Fortunately, there’s now a tool to help Delphi developers
your needs, Mr Bluck provides a comprehensive analysis and
stop the bleeding. It’s named MemMonD and our own
comparison of Paradox and InterBase.
Mr Vivrette puts it through its paces.
20 DBNavigator — Cary Jensen, Ph.D.
43 Developing Custom Delphi Components
In addition to ranges, DataSets, and SQL queries, Delphi 2 provides
Book review by Richard Wagner
filtering capabilities. Dr Jensen introduces the cast of characters —
TDBDataSet’s TTable, TQuery, and TStoredProc —you’ll need to
become familiar with to help your users “drill down” into their data DEPARTMENTS
with Delphi 2. 2 Editorial by Jerry Coffey
23 From the Palette — Ray Konopka 3 Delphi Tools
If you’re interested in creating Delphi components, Mr Konopka’s article 6 Newsline
is a great place to start. His TRzAddress component appears simple. Peel 44 File | New by Richard Wagner
back its layers, however, and you’ll see that this well-built control
demonstrates the effective encapsulation of other components.
love that one. It has all the elements of a great belly laugh. It’s short, topical, and has that cruel
I edge a joke needs to really draw blood. Cruel because it rings true — as any VB-to-Delphi convert
can attest.
The tone was only slightly more seri- The important thing is that there is a
ous at the First Annual Delphi happy, healthy, and still burgeoning
Informant Readers Choice awards din- third-party market for Delphi. This is
ner, where representatives of the best especially remarkable given the fact
third-party Delphi tools were present- that Delphi is just over a year old.
ed with lovely tokens of your affec- Product of the Year was a runaway
tion. However, there is no affectionate with Woll2Woll Software’s InfoPower
term for the award itself — a hand- taking top honors. Other winners
some Lucite tower encapsulating the included Pacheco and Teixeira’s Delphi
DI “Big D” and its happy electron Developer’s Guide for Best Delphi
satellites (a dire warning of a radia- Book, and Successware International’s
tion leak or the first image in an “Our Apollo Rock-E-T for Best Delphi
Friend the Atom” slideshow, depend- Add-In (see the April DI for all the
ing on your mood). No candidate winners). Zack Urlocker, Director of Delphi
jumps to the forefront. For example, a Product Management, and Delphi cre-
“Dimmy” (from Delphi Informant And despite the fact the DI Readers ator, Anders Hejlsberg arriving at the
Delphi Informant Readers Choice
Magazine) doesn’t sound like some- Choice Awards weren’t for them, the
Awards. They’d just left the Jolt Cola
thing you’d really like to win. three top members of the Delphi devel- awards where Delphi won for
Obviously there’s work to be done in opment team — Delphi Chief Technical Excellence.
this area. Or perhaps I should heed Architect, Anders Hejlsberg; Director
today’s chic admonition and simply of Delphi Product Management, Zack
“not go there.” Urlocker; and Director of Delphi home an industry award for them-
Development, Gary Whizin — were selves, and then looked on as the
kind enough to attend the festivities. community they created was honored
for its achievements. ’Course the faji-
Actually, our little affair must have tas and margaritas didn’t hurt.
been a bit of a let down for the three-
some, since earlier that evening they’d Thanks for reading,
accepted the Jolt Cola award for
Technical Excellence, beating an
impressive array of competitors
including white-hot Java, Symantec
C++, and Visual Basic (okay, so most
of the competitors were impressive). Jerry Coffey, Editor-in-Chief
version 3.1, a database of syn- can add custom word cate- US$1,499 (CAN$1,899). The Win16
onyms and a software library gories and synonyms. and Win32 SDKs are available bundled
to access the database. ThesDB is available in for US$699 (CAN$879).
Developers can use ThesDB three forms: the Win16 Contact: Wintertree Software Inc.,
to add thesaurus and syn- SDK for 16-bit Windows 69 Beddington Ave., Nepean, Ontario,
onym-finding capabilities to applications; the Win32 Canada, K2J 3N4
their 16- and 32- bit applica- SDK for 32-bit develop- Phone: (800) 340-8803 or
tions. The thesaurus dialog ment; and the Source SDK (613) 825-6271
box permits keyword searches, which includes the ANSI-C Fax: (613) 825-5521
user-thesaurus maintenance, source code to the ThesDB CIS Forum: GO WINSDK
and synonym selection. The engine and related software. Web Site: http://fox.nstn.ca/~wsi/
initial keyword and selected The Source SDK is suitable
synonym can be set and for applications developed
retrieved by the application, on non-Windows plat-
replacing the selected word forms, and includes Win16
with a synonym. ThesDB also and Win32 SDKs. All SDK
suggests replacements for mis- types include a 50-page
spelled or unknown words. programmer’s guide and a
The new version includes license to distribute applica-
50,000 synonyms in over tions royalty-free.
3,100 word categories in the A demonstration version of
American English main the- ThesDB is available from
saurus. Word categories in the Wintertree’s CompuServe
main thesaurus are classified forum and Web page.
By Joseph C. Fung
Do the Strand
An Introduction to Multithreading with Delphi 2
Do the Strand-o, when you feel low
It’s the new way, that’s why we say
Do the Strand
— Roxy Music, “Do the Strand”
For Your Pleasure ...
Essentially, whenever a thread makes a call to a pro- ReturnValue The value returned by a thread.
tected 16-bit API function, any other thread Suspended A Boolean variable that lets you set/get the thread’s
suspended state.
attempting to call any other 16-bit API function
waits until the first thread yields to Windows or Terminated A read-only Boolean that indicates if the thread
should terminate.
releases control. If an application doesn’t yield, or
ThreadID The thread’s ID.
stops processing window messages for a long time,
all these other applications will be tied up. Figure 1: TThread class properties.
Figure 5: The default unit created for the new TThread type.
Creating a Thread
To create a new TThread, you declare a TThread class, a
variable or member variable of this class, and then call its
Create constructor. A constructor of the TThread type has
the following syntax:
The code fragment in Figure 6 shows how your code The five main priority levels are:
might appear, and demonstrates how you can update a 1) tpLowest
component’s property from within a thread. A method 2) tpLower
named UpdateDisplay is implemented for the TMyThread 3) tpNormal
class. Within this method, a Label component’s Caption 4) tpHigher
property is updated. 5) tpHighest
Terminating a Thread This next example illustrates how you can open new
There are two recommended ways to terminate a thread forms, each owning its own worker thread. The lengthy
when using the TThread class: you can call Exit from the task in this example is represented by iterating through a
TThread’s Execute method, or you can call the TThread’s long loop and updating a track bar to show the progress.
Terminate method.
When you run the example in project WORK.DPR, the
Calling Exit to Terminate a Thread. The bulk of the code Worker Thread Examples form appears. Each time you
responsible for performing your background task resides in press the New Thread button on this form, a new child
the Execute method. When this code finishes running, it form is created with its own thread, and immediately
should exit the Execute method. This can happen implicit- begins its work. Each new form has a unique caption
ly as the last action of the method or if you call Exit. that identifies the “worker” (Worker 1, Worker 2, etc.).
When Execute finishes, the thread terminates and the
TThread class takes care of any cleanup. Each form also contains a Suspend/Resume button that
allows you to suspend and resume the thread. Finally, if
Calling Terminate from Another Thread. A second way to you close the form before the thread is finished, the
terminate a thread is to call the thread’s Terminate method. form closes and the thread terminates.
Typically, you do this when you want to terminate a specif-
ic thread from the main thread or from another thread. Figure 7 depicts the main form surrounded by two
worker forms. Listing One (on page 14) shows the .PAS
Terminate does not actually terminate the thread, but file for this example.
sets the Terminated property to
True. It’s then up to your code to
check the value of Terminated
from inside the thread’s methods,
and to act appropriately to exit the
Execute method. If you do not
have any code that does this, call-
ing Terminate is ineffective.
The demonstration project referenced in this article is uses WorkForm, Windows, SysUtils;
available on the Delphi Informant Works CD located in
INFORM \JUNE \96 \DI9606JF. { TWorkerThread }
procedure TWorkerThread.UpdateDisplay;
begin
with (FOwnerForm as TWorkerForm) do begin
with WorkProgressBar do
end.
By Kevin Bluck
f you’re like me, when you first bought Delphi you didn’t pay much
I attention to the Local InterBase Server bundled in the package. Most
likely, even if you were learning to build database applications, you
amused yourself for weeks using nothing but Paradox tables. In fact, like
me, you may have came from a Paradox background.
The products are so strongly associated that ly impossible. Worst of all, some operations are
many people have had difficulty making the slower with InterBase than with Paradox. It
distinction. And rumors that Delphi would quickly becomes apparent that InterBase isn’t
replace Paradox compounded the problem. automatically better than Paradox.
Although it should be clear by now that this
isn’t the case, most developers still use Paradox The idea that InterBase isn’t better than
tables for their Delphi database development. Paradox is absolutely true. At least, it’s not
In short, I doubt many bought Delphi to get always better. The two products are significant-
their hands on the Local InterBase Server. ly different, and are intended to serve in differ-
ent situations. The only thing they really have
Piqued Curiosity in common is that they both store data in
But, programmers are a naturally curious lot, tables. They diverge rapidly from that point.
and many of them eventually began poking
at Local InterBase. At first glance, it doesn’t Each is a system with strengths and weak-
seem that different from Paradox. You access nesses. The trick is deciding which is appro-
it using an alias, just as Paradox, and priate for a particular application. And once
although they have different names, the field made, that decision fundamentally affects the
types are similar. You can even create subsequent development effort.
InterBase tables using the Database Desktop.
You use the same TTable and TQuery com- Paradox Is File Based
ponents that are used for Paradox tables. In Paradox is a file-based database system. The
short, the Borland Database Engine (BDE) data files contain data records that have a defi-
interface creates a convincing illusion that nite order. In other words, record number 106
InterBase tables behave like Paradox tables. will always be the same record until it’s physi-
cally moved within the file, perhaps as a result
Except InterBase is supposed to be better some- of a sorting operation. Even more importantly,
how. It’s an industrial-strength RDBMS, so it it will always follow record 105 and precede
must be bigger and faster that Paradox, right? record 107, until that order is explicitly
changed. This allows the records to be easily
For many developers, though, disillusionment navigated by a cursor, since it’s possible to iden-
soon sets in. After creating InterBase tables with tify a record by its position within a table with-
the Database Desktop, they discover they can’t out having to reference the data it contains.
casually change field definitions by simply
restructuring as they can with Paradox tables. This explicit physical ordering of records has
All searches and indexes are case-sensitive, some advantages. Moving back and forth
unlike Paradox. Defining primary and foreign through the data file is a simple matter, and
keys seems easy, but changing them seems near- the records are easily refreshed when the cur-
For example, your automated teller machine (ATM) performs A trigger is short for triggered procedure. It’s a stored proce-
database transactions. Whenever you withdraw cash, two dure that is not explicitly called by an application, but is exe-
operations must be performed for the bank to properly cuted in response to a data action, such as inserting a new
account for its assets: The balance of your account must be record. Triggers allow you to perform extremely complex data
reduced, and the balance of cash on hand must also be validation, and are guaranteed to execute within the same
reduced by the same amount. Obviously, the preferred situa- transaction that performed the triggering operation. If any
tion is for both operations to succeed, but if the power goes operation fails, all changes made by triggers associated with
off in the middle of the operation, it’s absolutely not accept- that operation are also rolled back.
able for one account to be updated and not the other —
both operations must fail to maintain the proper accounting. InterBase supports stored procedures that return result
sets, which can be treated exactly as read-only tables, as
Transaction processing allows this to happen. The operations well as triggers that simply perform data transformations
in a transaction are not permanent until the whole transac- and don’t return any results. It supports essentially unlim-
tion is committed. Until that time, it may be rolled back to ited numbers of triggers for each table, which can occur
the starting point. A rollback can be explicitly triggered using before or after inserts, updates, and deletes. If more than
the Rollback method, or it can occur automatically when a one trigger is associated with an operation, their order of
system failure occurs. execution can be specified. Triggers can make changes that
execute other triggers, in a chain-reaction fashion, but all
InterBase fully supports transactions. In fact, all operations such cascading actions are still contained within a single
occur within the context of a transaction. In the absence of transaction.
explicit programmer control, the BDE automatically “wraps”
every operation in its own transaction. For example, every Paradox does not support either of these concepts. All data
time you post a record, a transaction is started and committed processing must be done at the client. Each application must
immediately after the post. Using the TDatabase component, contain the same code to maintain the data, and each appli-
you can explicitly control a single transaction and have it cation must be modified if the method of handling data
encompass as many operations as you like. must be changed.
However, the BDE does not fully support InterBase’s transac- There is no guarantee that an operation will be completed
tion capabilities. TDatabase methods can only be used against once it’s started. For example, cascading the delete of a
a single InterBase database, and only one transaction may master record to its detail records can fail in midstream,
exist at a time for each BDE alias. InterBase itself supports leaving details undeleted. If this cascade were implemented
multiple simultaneous transactions per connection, and a as a trigger in InterBase, either all or none of the records
transaction can also encompass more than one database, but would be deleted. Furthermore, the code to cascade the
the BDE doesn’t surface these abilities. You’ll have to make delete must be written into each different application that
calls to the InterBase API to use these features. uses the Paradox data. Using InterBase, it only needs to be
written once in a trigger. The application simply deletes
Paradox doesn’t support transactions. Whenever a record is post- the master record and InterBase takes care of deleting the
ed, the changes are permanently written to the table. It requires detail records.
another edit to manually change it back if a rollback is desired.
In addition, the system will not guarantee that a group of opera- Making the Choice
tions will all either succeed or all fail. It’s possible to simulate Choosing between Paradox and InterBase has important
some of this capability through some tricky programming and implications for your project. Accordingly, it’s essential to
temporary tables, but eventually the records must be modified know what is important for your situation. Figure 1 shows
one at a time in a batch, which leaves a window for failure. And some general principles that can help you make a decision.
there’s no way you can program a Paradox application to recover
from a system failure such as a power outage or disk crash. These are guidelines, not rules. Most of them assume a net-
work is involved. If you are contemplating a single-user system,
Triggers and Procedures Paradox is usually the best choice. The Local InterBase Server
A stored procedure is a piece of code that is stored in the can be deployed as a single-user system, but without concur-
database along with the data. It allows the server to per- rency issues, many InterBase features don’t apply. If the ability
form complex manipulation of data entirely on the server. to browse data is important, Paradox is also a good choice.
Conclusion
It’s important to remember that Paradox and InterBase are
substantially different systems, even though the BDE
attempts to make them look similar. It’s a seductive, but dan-
gerous idea that converting an application from one to the
other involves nothing more than changing an alias. They
require significantly different design concepts, and a design
that is efficient with one will likely not be with the other.
Selecting which system to use is a crucial decision that must
be made at the start of a project. It will profoundly impact
your subsequent development effort. ∆
Strainless Filtering
Delphi 2’s New and Powerful Filtering Capabilities
By Ray Konopka
Components &
Sub-Components
Encapsulating Multiple Controls
Ray Konopka is the author of Developing Custom Delphi Components, published property DataSource : TDataSource
by The Coriolis Group. Ray is also the founder of Raize Software Solutions, Inc., read GetDataSource write SetDataSource;
supplier of Delphi consulting services. Ray can be reached at [email protected]
or [email protected]. property FirstNameField : string
index 1 read GetField write SetField;
TempLbl := CreateLabel('Last Name'); { When CreateWnd is called, the Items list of FCbxState
with TempLbl do SetBounds(182, 8, Width, Height); is cleared. Therefore, the contents of the FStateList
TempLbl.Alignment := taRightJustify; are copied back into FCbxState. }
FCbxState.Items.Assign(FStateList);
FEdtLastName := CreateEdit; end;
FEdtLastName.SetBounds(240, 4, 137, 20);
procedure TRzAddress.CreateStateList;
TempLbl := CreateLabel('Street'); begin
with TempLbl do SetBounds(0, 36, Width, Height); FStateList := TStringList.Create;
FEdtStreet := CreateEdit; FStateList.Add('AK');
FEdtStreet.SetBounds(67, 32, 310, 20); FStateList.Add('AL');
FStateList.Add('AR');
TempLbl := CreateLabel('City'); FStateList.Add('AZ');
with TempLbl do SetBounds(0, 64, Width, Height); FStateList.Add('CA');
FEdtCity := CreateEdit; FStateList.Add('CO');
FEdtCity.SetBounds(67, 60, 121, 20); FStateList.Add('CT');
FStateList.Add('DC');
TempLbl := CreateLabel('State'); FStateList.Add('DE');
with TempLbl do SetBounds(200, 64, Width, Height); FStateList.Add('FL');
TempLbl.Alignment := taRightJustify; FStateList.Add('GA');
FCbxState := CreateCombo; FStateList.Add('HI');
FCbxState.SetBounds(240, 60, 50, 20); FStateList.Add('IA');
FStateList.Add('ID');
TempLbl := CreateLabel('Zip'); FStateList.Add('IL');
with TempLbl do SetBounds(300, 64, Width, Height); FStateList.Add('IN');
TempLbl.Alignment := taRightJustify; FStateList.Add('KS');
FEdtZip := CreateEdit; FStateList.Add('KY');
FEdtZip.SetBounds(326, 60, 51, 20); FStateList.Add('LA');
FStateList.Add('MA');
CreateStateList; FStateList.Add('MD');
FStateList.Add('ME');
Width := 382; FStateList.Add('MI');
Height := 86; FStateList.Add('MN');
end; { = TRzAddress.Create = } FStateList.Add('MO');
FStateList.Add('MS');
destructor TRzAddress.Destroy; FStateList.Add('MT');
begin FStateList.Add('NC');
FStateList.Free; FStateList.Add('ND');
inherited Destroy; FStateList.Add('NE');
end; FStateList.Add('NH');
FStateList.Add('NJ');
By Keith Wood
Talking to Yourself
A Look at Recursion in Object Pascal
e’ve all seen those cereal boxes that have pictures of someone holding
W the same cereal box, which has another picture of someone, etc. This is
an example of recursion, and it’s a powerful tool for the programmer as well.
Recursion arises when something is defined mixed up during recursion since we are call-
in terms of itself. Pascal allows for recursive ing the same function? No — Pascal provides
functions and procedures that are well suited separate variables each time the function is
for particular programming problems. To called. They are only valid within that partic-
show how it all works, this article looks at ular instance of the function, and retain their
recursion in general, and presents a recursive values across any other recursive calls. Of
function, two recursive plotting procedures, course this does have the effect that the space
and a binary tree class. available for variables (the “stack”) is slowly
eaten away as we go deeper into the recursive
Recursion calls. If this continues indefinitely, the space
Normal processing in a program is iterative. is eventually exhausted and an error occurs.
This means that part of the algorithm may
be executed in a for, while, or repeat loop. Factorials
In recursive processing, the entire algorithm The factorial function in mathematics is a
is re-executed from the beginning on a small- classic recursion example. This function
er part of the problem. computes the product of all the integers
between one and a given positive value (the
Two things are necessary for using recursion in result gets very large, very quickly). It’s
programming. First, the problem must be denoted by the exclamation mark ( ! ) and
defined in terms of itself; second, it must have can be defined by the following:
a terminating condition. Since the portion of
the problem being solved at each level of recur- 1! = 1
sion is smaller than the previous step, eventual- n! = n * (n - 1)!
ly we get to a very small or easy problem for
which we can immediately provide the answer In other words, one factorial is equal to one,
(the terminating condition). This answer can while n factorial — where n is any positive
then be passed back up the levels for further integer greater than one — is equal to the
manipulation before obtaining the final result. value n times the factorial of one less than n.
The first part of the definition is the termi-
For particular types of problems, recursive nating condition and the second part is the
algorithms can provide a solution in a few recursive call.
steps that would be extremely complex using
an iterative algorithm. To implement this in Pascal we define a
function (see Figure 1) that takes one integer
Within a function or procedure we make use parameter and returns a long integer
of values held in variables. Won’t these get (remember that it grows very quickly). We
{ Termination condition }
if Value = 1 then
Result := 1
else
{ Call function again with smaller value }
Result := Value * Factorial(Value - 1);
end;
need to check that a valid value has been entered initially: it Figure 4: Definition of the tree structure.
cannot be zero or negative. If an incorrect value is found then
an exception is raised to notify the program of the problem. Plotting Trees
A routine that allows us to plot images of trees based on the
With a valid value, we check for the terminating condition structure is shown in Figure 4. A tree consists of two branches
(in this case the value being equal to one), stop the recursion, from the one base point. These can have different lengths, a
and return the predefined result if this is so. If we are not at and b, and can be inclined at different angles, α (alpha) and
the terminating condition, we need to call the function again β (beta), from the vertical.
with the next lowest value, and compute the product with
the current value to obtain the final result. To complete the figure, we then plot the same structure at the
end of each branch on a reduced scale, using the direction of
When called with a value that branch as the new “vertical.” This sort of tree is effectively
Value Result
greater than one, the function a fractal, with parts of it being repeated at an ever smaller scale.
5 5 * 4! halts its execution at the
4 4 * 3! recursive call until the The algorithm for this can be stated as:
3 3 * 2! required value is returned
from the next level down. If the length of the main branch is below a predefined
2 2 * 1! threshold (the terminating condition) then exit the proce-
Once the terminating condi-
1 1 tion is reached, the results are dure without doing anything. Otherwise, given a point
passed back through the pre- and a direction (the “vertical”), plot the two branches of
2 2*1=2
ceding levels building up the the tree. The first is to the left of vertical by a specified
3 3*2=6 final result as it goes. Figure 2 angle and with a given length. The second is to the right
4 4 * 6 = 24 is a picture of this process. of vertical by a, possibly, different angle and with a length
reduced from the first by a given amount. Then call the
5 5 * 24 = 120
To see this in “real life,” we procedure recursively for each new endpoint and its corre-
Figure 2: Steps in computing 5! can put a breakpoint on the sponding angle, but with a reduced main branch length.
result of the terminating
condition (in this case, Result := 1) and then re-execute the This process is captured in the code in Figure 5. We must
program. When it stops, display the call sequence with the pass numerous parameters to it, being the starting point, the
menu command View | Call Stack. The recursive function calls current direction of the vertical (in degrees), the two angles
and their parameters are shown in the Call Stack window. by which the branches are offset, the length of the main
branch and the amounts by which to reduce this to produce
The factorial function the length of the secondary branch, and the length of the
is implemented in the main branch for the next level.
FACT1.PAS unit that
accompanies this arti- We must delve into some trigonometry to calculate the end-
cle. The project, FAC- points for the branches. The mod operator is used to ensure
TRIAL.DPR (see that the angles being used remain in the range of 0 to 359,
Figure 3), allows you preventing any possibility of overflow. Angles are defined in
to select an input degrees, since this is easier for us to use, but must be convert-
value for the factorial Figure 3: Our example project, ed to radians before Object Pascal can use them. Radians are
FACTRIAL.DPR.
function between 0 used in all of Pascal’s trigonometric functions, and are calcu-
and 12 (this being the limit for a long integer result). Note lated based on 180 degrees being equal to Pi radians. The
that using zero results in an exception being raised; this has conversion is done using a predefined value to reduce the
been left to show what happens with invalid input. amount of computation required in the plotting process.
{ Terminating condition }
if Length < MinLength.Value then
begin { Draw the line segment }
FractalImage.Canvas.MoveTo(FromPoint.X, FromPoint.Y);
FractalImage.Canvas.LineTo(ToPoint.X, ToPoint.Y);
Exit;
end;
Figure 8: The recursive procedure for plotting the Von Koch Note that these pointers may be empty. The first node in the
snowflake curve. tree is referred to as the root, and the tree is usually depicted as
growing down from the root, with node values increasing to
of calculation required. These are Root3 which is the square the right (see Figure 10). Those nodes at the bottom of the tree
root of three and Degrees30 which is 30 degrees expressed with no descendants are referred to as leaf nodes. Note that the
as radians. Also note that we are making four recursive calls root is the only means of access to the entire tree. All process-
from this procedure, one for each component line segment. ing must start from here.
This procedure can be found in the SNOWFLK1.PAS unit. As you can see, each node and its descendants form a sub-
The sample project, SNOWFLAK.DPR, allows the maxi- tree within the main tree. This gives us the self-definition
mum segment length to be set and uses this as the threshold required for recursion, with the leaf nodes or empty point-
value when plotting the curve (see Figure 9). The form ers being the terminating conditions. The processes that
should plot different figures for values of 5, 10, 30, and 90. we must be able to perform on a binary tree include
Note that some of the other values may produce slightly inserting a new value, removing an existing value, check-
incomplete curves because of the closeness of the segment ing whether a value is present, and processing all the
lengths to the threshold value. It also allows for the segments nodes in order.
If they are not equal then we need to determine which of the Pruning
node’s sub-trees to search to find it. If the value is less than Inserting an object into the tree is fairly straightforward, but
the current node then we look in the left sub-tree, otherwise removing objects is much more complicated. As before, we
{ Recursive function to remove an object from the tree { Recursive procedure to replace with maximum
and return a flag showing whether it was there } in left sub-tree }
function TBinaryTree.RemoveFromTree(obj: TBinaryTreeObject; procedure ReplaceWithMax(var ptrRep: PBinaryTreeNode);
var ptr: PBinaryTreeNode): Boolean; begin
begin { Terminating condition - found maximum }
if ptr = nil then { Terminating condition - not there } if ptrRep^.Right = nil then
Result := False begin
{ Terminating condition - remove } ptr := ptrRep; { Adjust pointers }
else if obj.IsEqualTo(ptr^.NodeObject) then ptrRep := ptrRep^.Left;
begin ptr^.Left := ptrRemove^.Left;
ReorganiseInTree(ptr); ptr^.Right := ptrRemove^.Right;
Result := True; end
end else { Recursive call to next level }
{ Check in appropriate half of tree } ReplaceWithMax(ptrRep^.Right);
else if obj.IsLessThan(ptr^.NodeObject) then end;
Result := RemoveFromTree(obj, ptr^.Left)
else begin
Result := RemoveFromTree(obj, ptr^.Right); { Remember current node for later disposal }
end; ptrRemove := ptr;
if (ptr^.Left = nil) and (ptr^.Right = nil) then
{ Procedure to reorganise the tree from this { Terminating condition - no more branches }
point - deleting current node } ptr := nil
procedure TBinaryTree.ReorganiseInTree( { Terminating condition - move right tree up }
var ptr: PBinaryTreeNode); else if ptr^.Left = nil then
var ptr := ptr^.Right
ptrRemove: PBinaryTreeNode; { Terminating condition - move left tree up }
else if ptr^.Right = nil then
{ Recursive procedure to replace with minimum ptr := ptr^.Left
in right sub-tree } else
procedure ReplaceWithMin(var ptrRep: PBinaryTreeNode); { Replace current object with min in right branch
begin or max in left branch - this is done randomly
{ Terminating condition - found minimum } to reduce distortions to the tree structure }
if ptrRep^.Left = nil then begin
begin if Random(2) = 0 then
ptr := ptrRep; { Adjust pointers } ReplaceWithMin(ptr^.Right)
ptrRep := ptrRep^.Right; else
ptr^.Left := ptrRemove^.Left; ReplaceWithMax(ptr^.Left);
ptr^.Right := ptrRemove^.Right; end;
end { Free object at node to be deleted }
else { Recursive call to next level } ptrRemove^.NodeObject.Free;
ReplaceWithMin(ptrRep^.Left); { And destroy the node }
end; Dispose(ptrRemove);
end;
By Andrew J. Wozniewicz
DLLs: Part IV
Wrapping Up Dynamic Link Libraries in Delphi
Dynamically Loading a DLL — nated strings are not and may run up to 64KB
LoadLibrary characters. A PChar is essentially a null-termi-
The keys to explicitly loading a DLL are a nated string (the issue of pointers deliberately
pair of standard Windows API functions that is avoided here) for all intents and purposes.
are always used in conjunction: LoadLibrary
and GetProcAddress. First, you must issue a Null-terminated strings are native to the
call to LoadLibrary, which is declared in the Windows API, but Delphi shields you from
WinProcs unit, as follows: them most of the time. When you need to
make a direct call to an API subroutine,
function LoadLibrary(
however, there is no “protection” — you are
LibFileName: PChar): THandle;
dealing with Windows directly and must use
You can use this function by passing it a the data types that Windows expects.
string value, for example:
A null-terminated string is an array of charac-
var
ters. Unlike Pascal strings, which have the
ALibrary: THandle;
begin length byte at the beginning, null-terminated
... strings do not explicitly store their lengths, but
ALibrary := LoadLibrary('dllfirst.dll');
instead mark their ends with the null-character
...
end (ASCII 0) — hence, their name. A null-termi-
nated string can be much longer than a Pascal
This example, however, hides a number of dif- string, up to the maximum limit of 64KB.
ficulties and issues that arise when you attempt
to use the LoadLibrary function. The first issue The LoadLibrary function expects you to
is making sure the WinProcs unit is in the uses pass the file name of the library as a null-ter-
clause of the unit or module in which you minated string. The example in this section
intend to use LoadLibrary. The second issue is looks deceptively simple because it hides the
that the function takes a single parameter, fact that the string passed as the actual para-
LibFileName, which is of type PChar. meter is a null-terminated string:
Note that you must provide the complete file name in a call Figure 1: Explicitly loading a DLL via a LoadLibrary call.
to the LoadLibrary function. At a minimum, this involves
providing the file name and the extension. Because file name After you have the name of the library file to load, you can
extensions other than the default .DLL are possible, no proceed with the conversion to a null-terminated string. The
default extension is assumed. You have to explicitly supply ABuffer variable serves as the storage for the file name string
the extension, if any, even if it is DLL. after the conversion occurs. The StrPCopy subroutine con-
verts between a Pascal string and a null-terminated string.
Fortunately, you don’t have to worry too much about null- The first parameter to StrPCopy is the destination buffer
terminated strings to make the LoadLibrary call. Just remem- where the null-terminated equivalent will be stored. The sec-
ber that it’s not a straightforward Pascal string. Typically, you ond parameter is the Pascal string you want to convert.
must translate between a String variable in which you likely
will have the file name of the library stored, and what the After the conversion, you are ready to call LoadLibrary. The
LoadLibrary function requires. return value of this function is assigned to the variable
ALibrary, declared as a THandle. (We’ll discuss this more
Calling LoadLibrary later.) In a nutshell, it’s a “token” through which you can refer
Without getting into too much detail, Figure 1 illustrates the to the library after it has been loaded. You will need to check
steps to issue the LoadLibrary call. This code shows how to the value of the returned token, however, because a value
use the LoadLibrary function in a generic situation, when the below the predefined HINSTANCE_ERROR indicates an
file name of the library to use for the call is stored as a Pascal error condition. The last three lines of code determine if the
string, as it typically would be. call to LoadLibrary was successful.
First, the uses clause ensures that the required subroutines are The result of the LoadLibrary function call is a value of type
visible. You need WinProcs to access the LoadLibrary function, THandle. This value is a token, or an “abstract value,” that
and SysUtils to access StrPCopy (the conversion routine that enables you to identify the library to Windows after it has
translates between a Pascal string and a null-terminated string). been loaded successfully. It’s important to realize that, as long
You also need WinTypes to use the HINSTANCE_ERROR as the LoadLibrary call was successful, the numeric value of
constant defined there when checking for the result of the the handle it returns is of no importance to you directly. You
LoadLibrary call. simply supply whatever value LoadLibrary returned to other
functions, such as GetProcAddress, that require it.
Then, the necessary variables are declared:
The only time you need to actually look at the value
var returned by LoadLibrary is directly after the call, because
AFileName: string;
ABuffer: array[0..255] of Char;
the return value may indicate an error condition. You can
ALibrary: THandle; check whether a call to LoadLibrary was successful by com-
paring it with a predefined constant declared in the
AFileName is the String variable in which you store the file name WinTypes unit: HINSTANCE_ERROR. If LoadLibrary
originally. Here, for simplicity, the AFileName variable gets its returns a value that is less than or equal to this predefined
value from a straightforward constant assignment. However, this constant, the call succeeded in actually loading the library,
can be replaced by a more elaborate scheme, such as getting the and it’s not safe to use any of the functions that need the
value from a user, reading it from an .INI file, and so on. library to be loaded, such as GetProcAddress.
The key to successfully using the subroutines in the library S := StripStr(' Teach Yourself Delphi Now! ');
library ExtStr;
The exports clause lists the subroutines actually exported
var from a DLL. All the exported subroutines must have
AString: string; been declared with the export directive.
function GetValue: string; export;
The most convenient way of accessing the subroutines
begin inside a DLL is by creating an implicit import unit,
Result := AString; declaring the headers of the subroutines, and binding
end;
them to the corresponding routines inside a DLL via the
procedure SetValue(AValue: string); export; external directive.
begin There are many ways of binding the subroutines declared
AString := AValue;
end;
as external to the actual subroutines implemented inside a
DLL. The subroutines can be bound by their declared or
exports assumed name, or by an ordinal number.
GetValue index 11,
SetValue index 12;
A LoadLibrary standard API function can be used to
provide a greater degree of control over when a partic-
begin ular DLL is loaded. There must be a matching call to
end.
FreeLibrary for each invocation of the LoadLibrary
Figure 3: Exporting a string variable from a DLL via a procedural function.
interface. Before you can use the subroutines inside a library explic-
itly loaded with a call to LoadLibrary, you must retrieve
be useful sometimes, but often is unwanted, unexpected,
their addresses via a call to GetProcAddress.
and may be disastrous. Writing multi-user servers as DLLs
You can store the values retrieved by GetProcAddress in
is a tricky issue that is beyond the scope of this series.
procedural-type variables, which can later be used to call
the subroutines.
The library presented in Figure 3 indirectly exports a string
You cannot export data elements directly from a DLL. To
variable, AString, by providing two access subroutines:
make data available to applications using a DLL, you
GetValue and SetValue. The code accomplishes the goal of
must provide a procedural interface, consisting of a
exporting a data element from a library module. The data
GetXXXX function and a SetXXXX procedure, to retrieve
element “exported” by the DLL, AString, is declared early.
and change the value of a particular variable in question.
The AString variable is not visible outside the module, however.
This discussion of DLLs has only scratched the surface of the
To enable applications using the library to obtain and change
issues involved. Be sure to consult other references when you
the value of the variable, two access subroutines are defined:
are considering making heavy use of dynamic linking. The
The GetValue function, to retrieve the current value of
examples given here are just a foundation for knowledge.
the variable.
The SetValue procedure, to change the value of the variable.
Now go build some DLLs! ∆
These subroutines actually are exported via an exports
This article was adapted from material for Teach Yourself
clause, and the client applications can operate effectively on
Delphi in 21 Days [SAMS, 1995], by Andrew Wozniewicz
the value of the variable without “seeing” the variable direct-
and Namir Shammas.
ly. This is similar to the concept of access methods for a class
property. In both cases, the access subroutines shield the
The example DLLFIRST.DLL and its associated files are
using code from directly manipulating the value, and may
available on the Delphi Informant Works CD located in
introduce side-effects, as well as validation and checking.
INFORM \JUNE \96 \DI9606AW.
Conclusion
This series has discussed the following issues:
Like application modules, DLLs are executable modules,
but they are not directly executable. Andrew J. Wozniewicz is president and founder of Optimax Development
Corporation (http: //www.webcom.com/~optimax), a Chicago-based consultan-
DLLs make it possible for many running applications, or cy specializing in Delphi and Windows custom application development, object-
many instances of the same application, to share code and oriented analysis, and design. He has been a consultant since 1987, developing
binary resources. primarily in Pascal, C, and C++. A speaker at international conferences, and an
DLLs are created in Delphi by replacing the keyword early and vocal advocate of component-based development, he has contributed
program with the keyword library in the main Delphi articles to major computer industry publications. Andrew can be contacted on
CompuServe at 75020,3617 and on the Internet at [email protected].
project file, and making some additional changes to the
standard project file generated by Delphi.
The export directive makes a subroutine exportable —
capable of being exported by a DLL and used by an
application external to that DLL.
By Robert Vivrette
One significant problem is resource leak- the state of each of these memory heaps.
age. All of you who have worked with Fortunately, Windows 95 takes great
Windows 3.x or Windows for Work- strides to alleviate much of the basic
groups are familiar with the problem. A restrictions on the use of these memory
program uses pieces of memory from two segments. A determined, ill-behaved pro-
small supplies called the GDI and USER gram, however, can still wreak havoc.
heaps. Each of these is limited to 64KB,
so when a program takes some of this As useful as these resource monitoring pro-
memory and doesn’t give it back, less is grams are, they are — after all — merely
available for the next application. monitors. They do nothing to help uncover
Eventually, enough badly behaved pro- the programming flaw that generated the
grams bring Windows to its knees, forc- leak in the first place. In addition, they
ing the user to restart the machine. typically only monitor the relatively small
GDI and USER heaps. Leaks relating to
This has given rise to a multitude of the global memory pool are much more
resource monitoring programs that display difficult to track.
Using MemMonD is about as simple as it gets. Before Most of you will recognize the
testing, the application needs to be compiled with the problem here. Aside from the
Map file: Detailed radio button selected on the Linker fact that the program does
page of the Project Options dialog box (see Figure 1). nothing with the TBitmap, it
This setting tells the Delphi compiler to generate a map also doesn’t release it from
file (*.MAP) during the linking stage that shows (among memory. The object was
other things) memory addresses for the various pieces of Figure 2: A simple “leaky” instantiated, telling Delphi to
code in the program. demonstration program. create various internal struc-
tures for the TBitmap, yet it is
never removed from memory. Delphi has no way of flag-
ging this type of logic error. My use of the TBitmap is syn-
tactically correct — as far as it goes.
To see how
MemMonD
helps in this sit-
uation, let’s run
this sample
program while
MemMonD is
watching. After
launching
MemMonD
and selecting
Figure 1: Delphi’s Project Options dialog box. the program to
monitor, you’ll
Then it’s a simple matter of firing up MemMonD, selecting see something Figure 3: MemMonD at work, displaying
the application to test, and running that application. Your similar to each unit’s address.
program will run normally with MemMonD sitting in the Figure 3. The
background watching what your program is doing. (Its main list box shows all the units that have been compiled
window even has shifting eyes to indicate that it’s at work.) into the example project. (Note that the user has com-
plete control over what memory issues will be reported.
Exercise all portions of your test program, then shut it This is just one “mode.”)
down. MemMonD will prepare a report that contains infor-
mation about global memory and stack consumption, and a After running the sample program, clicking on the but-
list of pointers that were never freed — each with a reference ton (and creating the TBitmap), and then quitting,
to the line of source code where the pointer was allocated. MemMonD will generate a report that uncovers our
problem (Figure 4). As you can see, MemMonD noticed
It will also catch one of the more difficult bugs to track there were 2 pointers to memory, accessing a total of 44
down in a Delphi program — namely, the use of the bytes, that were not released. It also indicates that these
FreeMem procedure with a different size parameter from pointers were allocated in the UNIT1.PAS file on line
that used when the GetMem or AllocMem procedure was 29. If we go back to the source for our demonstration
used to reserve the memory. application, we see the following on line 29:
Bitmap1 := TBitmap.Create;
To demonstrate MemMonD’s principal capabilities, let’s
look at a simple example.
In this case MemMonD has spotted the culprit as being the
creation of a bitmap that was never released. To remedy this
An Example
problem, the programmer would then need to include a:
The example application performs one simple task — and
performs it incorrectly. It presents a form with a single Bitmap1.Free;