ON THE COVER
4
Dynamic Delphi
27
OP Tech
FEATURES
REVIEWS
11
29
PocketStudio 1.1
33
DBISAM 3.08
36
ExpressQuantumTreeList Suite
Greater Delphi
16
On Language
22
Delphi at Work
DEPARTMENTS
2
Delphi Tools
39
Delphi
T O O L S
New Products
and Solutions
Book Picks
The Book of SAX
W. Scott Means
and Michael A. Bodie
No Starch Press
ISBN: 0-201-70359-9
Cover Price: US$49.99
(1,122 pages, CD-ROM)
www.aw.com/cseng
Delphi
T O O L S
New Products
and Solutions
Book Picks
XML Programming:
Web Applications and
Web Services with JSP and ASP
Alexander Nakhimovsky
and Tom Myers
Apress
ISBN: 1-59059-003-1
Cover Price: US$44.95
(555 pages)
www.apress.com
Inside XSLT
Steven Holzner
New Riders
ISBN: 0-7357-1136-4
Cover Price: US$49.99
(616 pages)
www.newriders.com
Dynamic Delphi
COM Components / Interfaces / Delphi 3-6
By Malcolm Matthews
f you develop COM components, sooner or later you will need to update them.
Either the business requirements will change, or youll overlook something in the
original design. However, interfaces are supposed to be immutable, so how do you
change them?
This article looks at several techniques for
extending the functionality of Delphi COM
components, examining some of the benefits and
drawbacks of each. Although this article shows
each step of the process, it assumes youre familiar
with developing COM components with Delphi,
so many of the basic details will be skipped.
Example Code
Since were dealing with enhancing existing components, we need a starting point. For the purposes
of this article we will consider a simple calculator
component, COMReuseDemo.Calculator, with an
ICalculator interface implemented by TCalculator,
as follows:
Dynamic Delphi
ICalculator = interface(IDispatch)
function Add(Value: Double): Double; safecall;
function Subtract(Value: Double): Double; safecall;
function Multiply(Value: Double): Double; safecall;
function Divide(Value: Double): Double; safecall;
property Accumulator: Double
read Get_Accumulator write Set_Accumulator;
end;
TCalculator = class(TAutoObject, ICalculator)
...
end;
The code for this is in Original\COMReuseDemo.dpr in the accompanying zip file (available for download; see the end of this article for
details). All of the example COM components have a test bed application in the same directory. Warning! These components are for demonstration purposes and any resemblance to real code is purely accidental.
The use of this calculator is quite simple. Set the value of the accumulator, then perform a function on it. For example:
Is the new functionality logically separate from the existing functionality? Interface design is beyond the scope of this article, but the
general rule is that small, highly focused interfaces are more re-useable and therefore better. So if the new methods support separate
functionality from the original interface, they should be part of a new
interface. For our example, we might create IScienceCalc:
var
fltAnswer : Double;
begin
Calculator.Accumulator := 5.0;
fltAnswer := Calculator.Add(1.5);
end;
IScienceCalc = interface(IDispatch)
function Sin: Double; safecall;
end;
As is often the case in the real world, the arguments for this division
arent always clear cut. The alternative is to create a new interface that
extends the original by inheriting from it. By convention, this will
have the same name with an additional, identifying number. So we
now have ICalculator2:
ICalculator2 = interface(ICalculator)
function Sin: Double; safecall;
end;
Dynamic Delphi
The main difference is that with ICalculator2
we can access all of the original methods, and
the new ones, from a single interface reference:
var
MyCalc: ICalculator2;
begin
// Default interface for this is now
// ICalculator2.
MyCalc := CoCalcualtor.Create;
MyCalc.Accumulator := 5.0;
fltAnswer := MyCalc.Add(1.5);
fltAnswer := MyCalc.Sin;
end;
var
MyCalc : ICalculator;
MySciCalc : IScienceCalc;
begin
MyCalc := CoScienceCalc.Create;
MyCalc.Accumulator := 5.0;
fltAnswer := MyCalc.Add(1.5);
MySciCalc := MyCalc as IScienceCalc;
fltAnswer := MySciCalc.Sin;
end;
While this requires more effort in the client application, the potential
benefits of well-designed, generic, small interfaces are often worth it.
One thing to bear in mind at this stage is that scripting clients can
only use the default interface of a component. So if scripts need to
be able to access the new and old functionality, youll need to create
a new interface that inherits from the old one.
These two influences are mutually exclusive, so the decision must
be made early on as to which way to go. One approach is to have
two sets of interfaces: one enormous Swiss Army knife of an interface for scripting clients, and a set of small re-useable interfaces
for other early-binding clients.
Implementation
With the new interface design decided, we now need to consider how
to implement it. Both the new interface and the inherited interface
can be implemented within the existing CoClass for ICalculator
or the CoClass can be subclassed and the new functionality implemented there. These options are shown in Figure 2.
From a simple class design perspective, separating the old and new
functionality by subclassing the CoClass seems attractive. For the
new interface, IScienceCalc, this is the case.
However, for the inherited interface, ICalculator2, creating a
subclass for the new functionality may cause problems during later
development. This is best explained with an example. Assume you
implement ICalculator2 with a subclass: TCalculator2. Then, another
change is required, this time to the parameters being passed for the
Add method. For example:
6 September 2002 Delphi Informant Magazine
Dynamic Delphi
interface instead of the old one. The final
step is to add the code (and of course
update the test application so you can
prove it works).
Remember, all of the functionality of the
COM component is available through the
new interface, so theres no need to change
interfaces within the client. Its possible to set
the component up so that the ICalculator2
methods all call different methods in the
underlying component from their ICalculator
equivalents by using method resolution clauses,
but that topic is beyond this article (see Calc2\
COMReuseDemo.dpr for a copy of this code).
Containment
With containment, the new component implements both the old
and the new interfaces, but any calls made on the old interface
just result in a call delegating the method to the original component. This is shown in Figure 6.
The new component needs to create an instance of the original
component when its created. It then just passes the calls on. This
does require us to write code for each of the original methods,
but they only need to be one-line methods; they simply call the
original method.
The power of this approach is that we have complete control, so
we can modify the behavior of some or all of the methods. This is
just like modifying the behavior of a virtual method in a Delphi
object. So we can do pre- and post-processing, or replace the original
behavior completely.
Implementing Containment
To implement this, create a new component as before, and create
the new interface. Then you use the Type Library editor to mark the
component as implementing the old interface in the same way that
ICalculator2 was added to Calculator earlier. Upon refreshing the
implementation youll find all the methods for both interfaces. A
Dynamic Delphi
TScienceCalc = class(TAutoObject, ICalculator, IScienceCalc)
private
InternalCalculator : ICalculator;
protected
{ ICalculator }
function Add(Value: Double): Double; safecall;
function Divide(Value: Double): Double; safecall;
function Get_Accumulator: Double; safecall;
function Multiply(Value: Double): Double; safecall;
function Subtract(Value: Double): Double; safecall;
procedure Set_Accumulator(Value: Double); safecall;
{ IScienceCalc }
function Sin: Double; safecall;
public
procedure Initialize; override;
end;
All of the ICalculator methods can now simply delegate the call to the
contained component thus:
function TScienceCalc.Add(Value: Double): Double;
begin
Result := InternalCalculator.Add(Value);
end;
You can add any additional code you require, either before or
after this call, or even replace it completely, depending on your
requirements. Once all the other methods have been set up, you
can compile, register, and test the component again. Remember
that as with the new interface implemented by the subclassed
object, the clients will need to change the interface thats being
used to switch from ICalculator to IScienceCalc functionality, and
its the new ScienceCalc component that must be created. (It is
also possible to use containment to implement an inherited interface if a single point of access is required.)
Aggregation
As you can see, containment is relatively straightforward to set up, but
does require code for each method in the original interface. If no additional functionality has been added, this is a bit of a waste of our time.
Aggregation is a specialized case of containment. In it, the original
component is created inside the new one as with containment. But
this time, the interface that is exposed to the outside world is the
interface of the original component (as shown in Figure 8). Not too
surprisingly, this does require a bit of work to set up correctly.
8 September 2002 Delphi Informant Magazine
Aggregation has rules that must be obeyed by both outer and inner
components for it to work correctly. The goal of all of those rules is
that the client application must not be able to tell that an aggregated
component is handling one of the interfaces. Externally the component should look like one that does all of the work itself.
For this to be the case, the IUnknown methods (QueryInterface,
AddRef, and Release) need special handling. Note that these are the
IUnknown methods, not the IUnknown interface itself. Every interface inherits ultimately from IUnknown, so every interface includes
the three IUnknown methods. (Each interface is a separate vTable,
so its possible for methods in one interface to call different code to
similar methods in another interface.)
There are three main rules that govern implementing the IUnknown
QueryInterface method on a COM object:
1) Components must have identity.
2) The set of interfaces on a component instance must be static.
3) It must be possible to query successfully for any interface on a
component from any other interface.
If the inner components interface is used in the normal way, all three
of these rules will be broken. In our example, asking for the IUnknown
interface from ICalculator will return the inner IUnknown, while from
the IScienceCalc interface we will get the ScienceCalc IUnknown interface. These two are supposed to be the same (identity).
Likewise, if we try to get from IScienceCalc to ICalculator it will work
(since TScienceCalc knows it supports both interfaces), but if we try to go
the other way it will not work (TCalculator doesnt know anything about
IScienceCalc). There are also problems with reference counting.
The solution to this is for the IUnknown methods for the aggregated interface belonging to the inner component (ICalculator) to
delegate to the IUnknown interface of the outer component. For
this to be possible, the inner component must know when it is
being aggregated and how to delegate.
Note: The inner components IUnknown interface itself must still
work in the usual way for the outer component (ScienceCalc) to work
with it. So TCalculator must deal with the IUnknown methods in one
of two different ways, depending on which interface theyre called on.
Implementing Aggregation
Aggregation is a fairly advanced topic to get to grips with. Fortunately, TComObject has all the necessary functionality built in.
Dynamic Delphi
This wont work in this case, since the inner component needs to be
informed that its being aggregated. To do this you will need to use the
COM subsystem API call CoCreateInstance directly (CoCalculator.Create
calls this ultimately anyway), as shown in Figure 11.
CLASS_Calculator is the CLSID of the COM component to be
aggregated (a Type Library editor-generated constant in this case).
MyUnknown is the IUnknown interface of the controlling or outer
component (it must be IUnknown). CLSCTX_INPROC_SERVER or
CLSCTX_LOCAL_SERVER just requests either a local (out of process), or in-process component. IUnknown is the interface you want to
get back (this also must be IUnknown in this case; its the only interface
the outer component can use to control the inner one). FInnerUnk is a
private member variable to hold the returned ICalculator interface.
CoCreateInstance is wrapped in an OLECheck call since it returns
an HResult. This saves having to check the return code. OLECheck
will raise an exception if an error is returned.
Now for the code to link everything up, Delphi does this using
the implements keyword and a property. You need to create a
property of type ICalculator and mark this as implementing the
ICalculator interface. Any calls on this interface will then be
Dynamic Delphi
routed directly to the interface returned by the property accessor.
So you need to create the property and provide a Get method to
return the ICalculator interface of the internal Calculator component. Its easiest to see all of this in the completed unit, shown in
Listing One.
And thats all there is to it! Dont be tempted to optimize the
repeated conversion of FInnerUnk to ICalculator by storing a reference to the Calculator components ICalculator interface within
the TScienceCalc class. This is because the reference counting for
that interface actually operates on the ScienceCalc component,
so it will be self-referencing and will never be destroyed. The
Calculators IUnknown interface operates normally, so the reference counting operates on the Calculator component.
Conclusion
As with many design issues, theres no single correct answer to the
question: How do I enhance my COM component when interfaces are supposed to be immutable? However, we have looked at
the main options and seen that none of them (with the possible
exception of aggregation) is difficult to implement. You can even
cheat and change the existing interface. Just remember that the
whole point of immutability is for backwards compatibility. The
important thing is to assess your own circumstances, and try to
decide which option will be best.
The six example projects (with test beds) referenced in this article are
available on the Delphi Informant Magazine Complete Works CD
located in INFORM\2002\SEP\DI200209MM.
Malcolm is principal consultant with Dunstan Thomas Limited and has many years
experience designing and implementing client/server and distributed database
applications using both Microsoft and Borland technologies. As well as being a
Borland Certified Delphi Consultant, he is a Microsoft Certified Software Developer
(MCSD). Malcolm has consulted for clients throughout Europe and is currently
focused on developing Web-based systems using Delphi, MTS, and XML. He spends
his time away from computers being jumped on by the kids. You can reach him at
malcolmm@fish.co.uk.
Greater Delphi
XML Transformations / DataSnap / Delphi 6 Enterprise
XML Transformations
Part II: To DataSnap and Back
elphi 6 Enterprise includes a set of components and a supporting utility that permit you
to easily convert between almost any type of XML file and the special XML file format
required to populate a ClientDataSet. The components are referred to as the XML transformation components, and the utility is the XML Mapper.
Together, these tools permit you to use the standard TDataSet interface to work with XML data,
regardless of its source. Working with XML in this
manner is much easier than using a programmatic
interface, such as the Document Object Model
(DOM), or Simple API for XML (SAX).
Last month, I demonstrated how to use XML
Mapper to create the transformation files required
by the XML transform components. In this second
and final article in this series, well complete the
process by using the XML transformation files
created last month to transform XML to and
from the special XML data packet format used by
ClientDataSet components.
Greater Delphi
ClientDataSet. This event handler also assigns the fully-qualified
file name for the two transformation files created in last months
article to the XMLTransformFile property of the two XMLTransform
components that appear on the main form. Figure 2 shows this
complete OnCreate event handler.
As you can see in Figure 2, after extracting the name of the
application directory, Memo1 and Memo2 are loaded with the
contents of the xmldata.xml and demo.xml files, respectively.
Next, the Text properties of Memo1 and Memo2 are formatted
using the FormatXMLData method, which is declared in the
XMLDoc unit of Delphi 6 Enterprise (the same unit in which the
TXMLDocument class is declared). FormatXMLData adds carriage
returns and indentation to an XML string,
making it easier to read (the XML data packet
created by a ClientDataSet is not formatted for
readability).
Figure 4: The main form after both transformations have been executed.
Greater Delphi
1) Begin by creating a new project in Delphi 6
Enterprise.
2) Place a DBNavigator on the main form,
aligned to alTop, and a DBGrid, aligned to
alClient. These components are found on
the Data Controls page of the Component
palette.
3) Next, place a DataSource, a ClientDataSet,
and an XMLTransformProvider from
the Data Access page of the Component
palette. Set the DataSource properties of the
DBNavigator and DBGrid to DataSouce1.
4) Now set the DataSet property of
DataSouce1 to ClientDataSet1, and the
ProviderName property of ClientDataSet1 to
XMLTransformProvider1.
5) Using the Object Inspector, expand the
Figure 5: Setting the ClientDataSets Active property to True at design time.
TransformRead subcomponent property
of the XMLTransformProvider and
set its TransformationFile property to
the file ToDp.xtr located in the XML
Transformation Demo project directory.
Likewise, expand the TransformWrite
property and set its TransformationFile
property to ToXml.xtr. Finally, set the
XMLFile property to xmldata.xml.
6) Now set the ClientDataSets Active property
to True to see it load the file xmldata.xml
through the ToDp.xtr transformation. Your
main form should now look like that shown
in Figure 5.
7) If you were to run this project now, you
would be able to use the ClientDataSet to
edit the data it loaded from the XML file.
However, to save changes you make to data
Figure 6: Posting a new record.
using a ClientDataSet that loads its data
using a provider, you must explicitly call ApplyUpdates.
an IDOMDocument implementing object, or an XML file name,
Therefore, add a MainMenu to this project. Double-click
respectively. (An IDOMDocument implementing object can be
the MainMenu component to open the Menu Designer,
obtained by reading the DOMDocument property of an active
and add a single menu item. Set the Caption property of
TXMLDocument instance.)
this menu item to Apply Updates. Now add the following
OnClick event handler for this menu item:
When these properties are used, you read the Data property of
procedure TForm1.ApplyUpdates1Click(Sender: TObject);
the XMLTransform instead of calling the TransformXML method.
begin
Also, instead of setting the TransformationFile property to the
ClientDataSet1.ApplyUpdates(-1);
.xtr file containing the transformation, you can alternatively set
end;
the TransformDocument property to point to an IDOMDocument
implementing object that references the transformation.
8) Run the project. Add and post a new record to the
ClientDataSet, like that shown in Figure 6. Before closing
Using XMLTransformProvider
the project, select Apply Updates from the displayed menu
The XMLTransform component is simple and easy to use.
to save your changes. (If you fail to post the new record,
However, most developers dont use the XMLTransform component
calling ApplyUpdates wont do anything.) After closing the
directly. Instead, they typically use the XMLTransformProvider
project, display the xmldata.xml file using your favorite
component, which encapsulates two XMLTransform instances. The
XML viewer (Internet Explorer version 5 and later includes
XMLTransformProvider is used with a ClientDataSet to load data from,
a nice XML viewer). As you can see in Figure 7, the new
and save changes back to, an external XML file. Importantly, because of
record appears in the XML file.
the transformations, the external XML doesnt need to match the special
XML format required by the ClientDataSet.
Transforming XML from a DataSnap Server
DataSnap, formerly known as MIDAS, is Borlands multi-tier
Using the XMLTransformProvider is easy to demonstrate using
framework for building distributed database applications. In a
the xmldata.xml file and the two transformations, ToDp.xtr and
typical DataSnap application, end users work with a DataSnap
ToXml.xtr, employed in the preceding example. The following
client application, which communicates to a DataSnap server
steps demonstrate how you do this:
using one of a variety of network protocols, including DCOM,
13 September 2002 Delphi Informant Magazine
Greater Delphi
TCP/IP, and HTTP. The client application
obtains data from a DataSetProvider on a
remote data module of the DataSnap server,
loading that data into a ClientDataSet.
Using XML transformations, you can
convert the data obtained from a DataSnap
server into an XML format. There are two
ways of doing this. The first mechanism is
to load the data obtained from a DataSnap
server into a ClientDataSet as is normally
done in a DataSnap client, and then use an
XMLTransform component, converting the
XML obtained through the ClientDataSets
XMLData property using an appropriate
transformation file.
The second mechanism makes use of the
Figure 7: The new record in the XML file.
XMLTransformClient component. This
component takes the place of a ClientDataSet.
Specifically, you use an XMLTransformationClient on the
DataSnap client to connect to a DataSetProvider on a DataSnap
server, converting the retrieved data into XML using a
transformation file.
I prefer the first mechanism. Specifically, in most DataSnap
client applications youll probably want to permit your end
users to review or edit the data before saving it to an XML
file. In those cases, the ClientDataSet provides you with these
capabilities without any additional work. By comparison, an
XMLTransformClient is limited to generating XML through a
transformation file based on the data obtained from a DataSnap
server. If you want to view or edit this data, you have to resort to
using an XMLDocument component, a third-party XML parser,
or transform the data for use in a ClientDataSet. On the other
hand, if all your application needs to do is to obtain data from a
DataSnap server and store it in XML, the XMLTransformClient
provides you with all you need.
InterBase must be installed and running to use this server.) Load and
run this server once before attempting to run the client application.
The XMLTransformClient component is found in the project located
in the client directory under XMLTransformClient. The main form
for this project is shown in Figure 8.
Greater Delphi
component. Figure 10 shows how this form will look once the
project is running.
Summary
The XML Mapper utility creates transformation files that
can be used with Delphis XML transform components to
convert between XML files and the special XML format used
by the ClientDataSet. The XMLTransform component is
the primary transformation component, permitting you to
easily convert between XML formats. The other two XML
transform components, the XMLTransformProvider and the
XMLTransformClient, encapsulate one or more XMLTransform
instances. Of these two, the XMLTransformProvider is the most
useful, permitting you to effortlessly view and edit XML files
using the TDataSet interface.
The files referenced in this article are available on the Delphi
Informant Magazine Complete Works CD located in INFORM\
2002\SEP\DI200209CJ.
On Language
Variants / Object Pascal / Delphi 2-6
By Bill Todd
Versatile Variants
Dont Avoid This Useful Type
ave you ever needed to store a value in a variable, but you werent going to know the
type of the value until run time? Or have you ever wanted to pass the contents of a
binary file to a function in a COM server? Variants can do both of these things and more.
Variants are useful because a variant variable can
hold almost any type of data, and the data type
can change from moment to moment as your
application runs. A variant can hold any type of
data except structured data types and pointers.
Put another way, a variant cannot hold records,
sets, static arrays, files, classes, class references, or
pointers. Any other data type will work, including dynamic arrays, COM objects, and CORBA
objects. Like any other variable, a variant can hold
a single value. But variants also can hold arrays,
and the arrays can be dynamically created and
resized. They also can be asymmetrical. Variants are
particularly useful in database applications, because
they can store the null state.
On Language
procedure TfrmVarSpeed.btnVariantClick(Sender: TObject);
var
W, X, Y, Z: Variant;
I: Integer;
Start, Stop: DWORD;
begin
W := 2;
X := 2;
Y := 0;
Start := GetTickCount;
for I := 1 to 10000000 do
Y := Y + (W * X);
Z := Y;
Stop := GetTickCount;
lblVariant.Caption := IntToStr(Stop - Start);
end;
Figure 3:
The Convert sample
application.
On Language
const
varEmpty = $0000;
varNull = $0001;
varSmallint = $0002;
varInteger = $0003;
varSingle = $0004;
varDouble = $0005;
varCurrency = $0006;
varDate = $0007;
varOleStr = $0008;
varDispatch = $0009;
varError = $000A;
varBoolean = $000B;
varVariant = $000C;
varUnknown = $000D;
varShortInt = $0010;
varByte = $0011;
varWord = $0012;
varLongWord = $0013;
varInt64 = $0014;
varStrArg = $0048;
varString = $0100;
varAny = $0101;
varTypeMask = $0FFF;
varArray = $2000;
varByRef = $4000;
If the varArray and varByRef bits of VType arent set, the rest of the
TVarData record contains the variables value. If varArray is set,
the variant contains a pointer to a TVarArray record that contains
information about the array and a pointer to the array data. Figure 7
shows the TVarArray record.
If the varByRef bit is set, the variant contains a pointer to a value of
the type specified by VType. Although you arent likely to want to
do so, you can access the value of the variant directly by using the
TVarData type as follows:
if VarType(V) and varTypeMask = varInteger then
I := TVarData(V).VInteger;
This code checks to see if V is empty. If not, the code sets it to empty
using the VarClear function. The following code does exactly the
same thing using the Unassigned constant:
if not VarIsEmpty(V) then V := Unassigned;
Note that when working with variants, the terms empty and unassigned mean exactly the same thing. The VarIsClear function works
exactly like VarIsEmpty. If you use an empty variant in an expression,
Delphi will raise an exception.
Variants can also be null. This is particularly useful in database applications, because a variant variable can store the null state, or the value of a
field in a database table. This means you can write the following code:
if not VarIsNull(V) then V := Null;
The VarIsNull function returns True if the variant is null, and the
Null constant lets you set a variant to null. If you use a null variant in
an expression, the result will be null. To see how variants work with
database data, see the code in Figure 8. This is the code from the Run
buttons OnClick event handler in the Database sample application
that accompanies this article. The code uses the sample Customer
table in the DBDemos database.
The first three lines of code all retrieve the value from the TaxRate
field in the current record and assign the value to the variable E of
type Extended. What isnt obvious is that all three ways of accessing
the field value return the value as a variant. The problem with this is
that the field value could be null, and the Extended data type cannot
On Language
procedure CopyToArray(var Rec: Variant; DataSet: TDataSet);
var
I: Integer;
begin
// Create a variant array whose number of elements equals
// the number of fields in DataSet.
Rec := VarArrayCreate(
[0, DataSet.FieldCount-1], varVariant);
// Copy the values to the array.
for I := 0 to VarArrayHighBound(Rec, 1) do
Rec[I] := DataSet.Fields[I].Value;
end;
procedure CopyFromArray(var Rec: Variant;
DataSet: TDataSet);
var
I: Integer;
begin
// Copy the values from the array to the fields of DataSet.
for I := 0 to DataSet.FieldCount - 1 do
DataSet.Fields[I].Value := Rec[I];
end;
store the null state. This means that if TaxRate is null, all three of
these statements will cause an EInvalidVariantTypeCast exception.
The next line uses the AsFloat property to return the field value
and assign it to the Extended variable E. This will not cause an
error if TaxRate is null because the AsFloat property converts the
null to zero. All of the numeric As... properties work this way. As
long as you dont need to know that the field was null and not
zero this is a good solution.
The next three lines of code are identical to the first three, except that
the variable V, on the left side of the assignment operator, is a variant.
Because variants can store the null state, all of these statements will run
without error even if TaxRate is null.
If you need to copy the field values from a record in a table to another
record, a variant array makes it easy (see Figure 9). The CopyToArray
method takes a variant and a TDataSet as parameters. The first statement
calls the VarArrayCreate function to create a variant array of variants with
one element for each field in the DataSet. The for loop copies the values
from each field to its corresponding array element.
Unless you want to encounter exceptions at run time, you never should
access a field value in a database table as a variant and assign it to a statically typed variable. If you want to convert nulls to zeroes, or to a null
string as appropriate, use the As... properties to access the field value. If
you need to know if the field value was null, always use variant variables.
On Language
type
TPersonRec = record
FirstName: string[20];
LastName: string[30];
Age: Integer;
end;
TDataArray = array[0..1000000] of Byte;
PDataArray = ^TDataArray;
procedure TfrmBinary.btnPassClick(Sender: TObject);
var
Person: TPersonRec;
V: Variant;
P: PDataArray;
begin
Person.FirstName := 'John';
Person.LastName := 'Doe';
Person.Age := 25;
// Create a variant array of bytes
// the same size as the Person record.
V := VarArrayCreate([0, SizeOf(Person) - 1], varByte);
// Lock the array.
P := VarArrayLock(V);
// Copy the record to the array.
Move(Person, P^, SizeOf(Person));
// Unlock the array;
VarArrayUnlock(V);
// Pass the data.
ShowPerson(V);
end;
procedure TfrmBinary.ShowPerson(V: Variant);
var
Person: TPersonRec;
P: PDataArray;
begin
// Lock the array and get a pointer to its data.
P := VarArrayLock(V);
// Copy the data from the array to the Person record.
Move(P^, Person, SizeOf(Person));
// Unlock the array.
VarArrayUnlock(V);
// Display the fields of the Person record.
ShowMessage(Person.FirstName + ' ' + Person.LastName +
' - Age: ' + IntToStr(Person.Age));
end;
Passing Parameters
Using a variant as a parameter in a function call lets you vary the
type and number of values passed to the function. You also can use a
variant array of bytes to pass any type of data as a parameter. This can
be particularly useful in COM applications, because it allows you to
pass data types that COM doesnt support, such as Pascal records.
Figure 12 shows an example of passing a Pascal record in a variant
array of bytes. The code starts with the type declarations from the
implementation section of the main form in the Binary sample
application. The first type, TPersonRec, is the Pascal record that
will be passed as a parameter in a method call; the second type
declaration, TDataArray, is an array of bytes; and the third type,
PDataArray, is a pointer to an array of type TDataArray.
The first method in Figure 12 is the OnClick event handler of the
Pass Data button. This method starts by assigning values to the
FirstName, LastName, and Age fields of the Person record declared
at the beginning of the method as type TPersonRec. Next, the
method calls VarArrayCreate to create a variant array of bytes
whose size is the size of Person.
Next, the variant array, V, is locked to get a pointer to the arrays
data. Then the Move procedure is called to copy the data from
Person to the array. Move takes three parameters. The first is the
source location in memory, the second is the destination location
in memory, and the third is the number of bytes to copy. In this
example, the source is the Person record variable, the destination
is the array to which P points, and the number of bytes to copy is
the size of Person.
The Move procedure is very handy and very fast, but its also very
dangerous. Make sure you have the correct source, destination, and
size, because Move performs no error checking, so its easy to overwrite
an area of memory you dont intend to change. Once the data has been
copied to the array, the array is unlocked, and the ShowPerson method
is called and passes the array, V, as its parameter.
The ShowPerson method also declares a Person variable of the
TPersonRec type and a pointer, P, of the PDataArray type. It
contains just four lines of code. The first locks the variant array
parameter, V; the second copies the data from the array to Person;
On Language
type
TFloatArray = array of Double;
...
procedure TfrmVarDyn.btnVarDynClick(Sender: TObject);
var
V: Variant;
F: TFloatArray;
begin
V := VarArrayCreate([0, 2], varDouble);
V[0] := 1.1;
V[1] := 2.2;
V[2] := 3.3;
DynArrayFromVariant(
Pointer(F), V, TypeInfo(TFloatArray));
ShowMessage(FloatToStr(F[2]));
end;
the third unlocks the variant array; and the fourth displays the
contents of the Person record.
Using this same technique, you can pass anything you can load into
memory as a parameter in a method call. This also works for a call
from a COM client to a method in a COM server. Examples include
the contents of a text or binary file, a JPEG or bitmap image, and
anything else that can be loaded into memory as a stream of bytes.
You can easily move data between dynamic arrays and variant
arrays using the DynArrayFromVariant (shown in Figure 13) and
DynArrayToVariant procedures. These procedures are very handy
if you need to pass the contents of a dynamic array across a COM
Conclusion
Variants are a powerful tool for storing all types of data and
passing them as parameters. You can store single values or arrays of
values, and the array elements can be of different types. Variants
are extremely useful for passing all types of data across a COM
connection, as well.
The demonstration projects referenced in this article are available
on the Delphi Informant Magazine Complete Works CD located in
INFORM\2002\SEP\DI200209BT.
Delphi at Work
XML / Interfaces / Delphi 6
By Keith Wood
CreateDocument
Producer
DocumentReady
Consumer
Delphi at Work
{ Definition for user of XML document in DOM format. }
IXMLConsumer = interface
['{917863FF-96D1-40F9-9868-926D9C299068}']
{ Pass DOM document to be used and start processing it. }
procedure DocumentReady(Document: IDOMDocument);
end;
{ Definition for generator of XML document in DOM format. }
IXMLProducer = interface
['{B53AF472-4B85-4F98-98B4-48501C81AE6A}']
{ Generate new XML document and pass it to consumer. }
procedure CreateDocument;
function GetConsumer: IXMLConsumer;
procedure SetConsumer(const Value: IXMLConsumer);
{ The consumer makes use of the new document. }
property Consumer: IXMLConsumer
read GetConsumer write SetConsumer;
end;
By defining the producer and consumer requirements as interfaces, you can add them to any existing object, and you can create
new objects that implement them. In fact, by having the one
component provide both interfaces, you can string together a
series of XML handlers and have them each do their things before
passing the updated DOM on to the next step.
A building block can help you write such components.
TXMLBuildingBlock extends TComponent and implements both
interfaces (see Figure 3). Making it a component lets you add these
building blocks to applications visually, and set their properties
interactively. The properties are defined as protected, which enables
subclasses to reuse their abilities and make them available to the
user simply by promoting their visibility. Although this class isnt
declared as abstract, it should be extended before use.
As a document producer, this basic building block does nothing. That
leaves subclasses to override the CreateDocument method as necessary.
However, the DocumentReady method provides key functionality.
The method delegates the actual handling of the DOM document to
the new ProcessDocument method before passing the updated version
on to the consumer attached to this object. Subclasses are expected
to override the ProcessDocument method instead of DocumentReady
directly. The default processing of a document does nothing and just
returns the reference the document passed in.
The NotifyConsumer method is responsible for sending the DOM along
the chain. It ensures a consumer has been defined before passing the
modified document along. The standard IXMLConsumer processing calls
it automatically, but subclasses may invoke it directly to achieve the same
effect. All of this basic handling is shown in the code in Figure 4.
Other common processing requirements are included, which makes
them available to all subclasses. Building blocks often need to create new
DOMs, so the NewDocument method takes the name of the main element
and returns a DOM document with such an element attached. Many
building blocks add a new node to an existing document. To make this
easier to configure, the basic component adds the TagName property and
the OnTagCreate event. The latter should be invoked whenever a tag with
the given name is created, so the user has a chance to amend the resulting
element (perhaps by adding attributes). Use the DoOnTagCreate method to
trigger the event, as the NewDocument method does automatically.
In Delphi 6, a published property may be defined as an interface type,
with the Object Inspector locating appropriate implementers within
23 September 2002 Delphi Informant Magazine
the form. To make this work properly, however, some additional code
is required within the body of the component. When setting such a
property, you need to call the ReferenceInterface method before saving
the new value to release the previous reference, and you need to call it
again afterward to notify others of the new value (see Figure 5). You
should also override the Notification method to handle deletions of
attached components. Within it, check whether the given component
is the implementer of your interface reference, and set that reference
back to nil if it is.
Delphi at Work
{ Do nothing; implemented as needed in subclasses. }
procedure TXMLBuildingBlock.CreateDocument;
begin
end;
{ Process document according to this class; then pass
it on to any consumer. }
procedure TXMLBuildingBlock.DocumentReady(
Document: IDOMDocument);
begin
Document := ProcessDocument(Document);
NotifyConsumer(Document);
end;
{ Pass completed document on to any register consumer. }
procedure TXMLBuildingBlock.NotifyConsumer(
Document: IDOMDocument);
begin
if Assigned(FConsumer) then
FConsumer.DocumentReady(Document);
end;
{ Do nothing; overridden in subclasses. }
function TXMLBuildingBlock.ProcessDocument(
const Document: IDOMDocument): IDOMDocument;
begin
Result := Document;
end;
Transformations
One common task in working with XML documents is to apply an XSL
transformation to it. A transformation is another XML document containing tags that describe how to convert an incoming document into
another format. The output from the transformation could be straight
text, HTML, or even another XML document.
The TXBBTransform class encapsulates the transformation abilities. The
class extends TXMLBuildingBlock, promotes the Consumer property to
published status, and adds properties to indicate the source of the XSL
document (see Figure 8). The source properties parallel those found in
Delphi at Work
{ Write out DOM to given file or stream. Set one of
FileName or Stream before using this component. }
TXBBWriter = class(TXMLBuildingBlock)
private
FFileName: TFileName;
FStream: TStream;
protected
function ProcessDocument(const Document: IDOMDocument):
IDOMDocument; override;
public
constructor Create(AOwner: TComponent;
const FileName: TFileName); reintroduce; overload;
constructor Create(AOwner: TComponent;
const Stream: TStream); reintroduce; overload;
published
property Consumer;
property FileName: TFileName
read FFileName write FFileName;
property Stream: TStream read FStream write FStream;
end;
{ Write the document out to the specified destination. }
function TXBBWriter.ProcessDocument(
const Document: IDOMDocument): IDOMDocument;
begin
if (Filename = '') and not Assigned(Stream) then
raise EXBBException.Create(
'No filename or stream specified');
with Document as IDOMPersist do
if Assigned(Stream) then
SaveToStream(Stream)
else
Save(Filename);
Result := Document;
end;
Linking Blocks
These three building blocks are enough for a simple application.
Together, they let you load an existing XML document into a DOM,
apply a transformation to that DOM, and write out the resulting
structure to another file. Because the classes descend from TComponent
and the properties are published, you can drop these items onto your
form and set them up at design time. Then, you only need to call
the CreateDocument method of the parser to start the whole process.
Alternatively, you can modify the property values at run time before
initiating the DOM processing, as shown below:
{ Initialise and link the components... }
xbbParser.Consumer
:= xbbTransform;
xbbParser.XMLSource
:= 'c:\temp\movie-watcher.xml';
xbbTransform.Consumer := xbbWriter;
xbbTransform.XSLSource := 'c:\temp\movies-only.xsl';
xbbWriter.FileName
:= 'c:\temp\output.xml';
{ ...and start the whole process. }
xbbParser.CreateDocument;
Delphi at Work
The XML building blocks are designed to fulfill this requirement. They
are defined through two simple interfaces, allowing them to be implemented in any number of existing or new classes. One interface describes
objects that generate new DOMs, and the other describes objects that
read or update existing DOMs. By declaring a basic component that
implements both interfaces, you have the basis for chains of objects that
create a DOM and then operate on it in turn. With a little thought and
care, these objects can be designed to be reusable in a number of different
situations, promoting their reuse and stability.
Ive presented an implementation of the two interfaces that serves
as the basis for building-block components. From this, building blocks are created to generate a DOM from an existing XML
document as text (TXBBParser), to write out an altered DOM as
text (TXBBWriter), and to apply an XSL transformation to a DOM
(TXBBTransform). Next month, youll see how the building-block
concept is extended to components that create DOMs from other
sources and that modify the DOM on the way through.
Figure 9: Using XML building blocks to transform a document.
References
Conclusion
XML documents provide a standard way of defining data and the
relationships between them, making it easier to transfer information between applications and platforms. The DOM specification
supplies a standard way of dealing with XML documents as an object
hierarchy, both for navigating existing documents and for creating
new ones. However, it would be useful to have an additional generic
layer on top of the DOM to provide commonly used functionality in
a plug-and-play manner.
The files referenced in this article are available on the Delphi Informant
Magazine Complete Works CD located in INFORM\2002\SEP\
DI200209KW.
Keith Wood hails from Brisbane, Australia. He is a consultant using Delphi and Java. He
started using Borlands products with Turbo Pascal on a CP/M machine. His book, Delphi
Developers Guide to XML (Wordware, 2002) covers many aspects of XML from a Delphi
point of view. You can reach him via e-mail at kbwood@compuserve.com.
OP Tech
StringReplace / Optimization / Delphi 4-6
By Peter Morris
Optimizing StringReplace
Part II: Step-by-Step Redesign
ast month, we saw that even Borland can produce a flawed routine specifically,
the StringReplace function. We also examined the Boyer-Moore string-searching algorithm, which does the same job much more efficiently. We know that StringReplace is slow.
Now its time to find out why, and to replace it with a better, faster function of our own.
Borlands implementation of StringReplace is shown
in Figure 1. There are some obvious flaws that make
this routine slower than it should be. In a non-casesensitive replacement, the entire source string is
converted to upper case, causing read and write access
to every character before you even start. But thats a
minor point compared with some other problems.
As with the string-counting routine discussed last
month, StringReplace keeps a copy of your source
string in a temporary variable so it can use Delete
OP Tech
examples of such a situation Ive come across. I would suggest following these guidelines:
1) Dont convert the source string to uppercase. This requires read
and write operations for each character in the string, and the string
could be very large. Instead, convert each character to uppercase as
required. Because youre not reading every character when using the
Boyer-Moore technique, this will save time.
2) Use a faster search algorithm. Although it doesnt make a massive
improvement to the overall performance of the routine, it does allow
you to implement a FindNext method easily.
3) Dont keep reassigning Result. If you start off with a memory buffer
thats probably large enough to store your result in the first place,
you can just add to the buffer until you finish or run out of buffer
space (at which point you allocate another larger buffer and copy
your Result to that instead).
4) Finally, resize the memory buffer to the exact number of bytes
you wrote to it, just to ensure you dont have garbage attached to
the end of your string.
All of these guidelines are implemented in the custom BMStringReplace
function, shown in Listing One (available for download). Lets step
through the function:
1) Calculate the length of the source string, old pattern, and new pattern. Theres no point in calling a subroutine to discover these values
when you can just store them in a local variable.
2) If either the source or find string is null there will be no changes, so
you can set the result to the source string and exit the function.
3) Store a pointer to the old pattern (the find string) and the start of
S (the source string). These are used when asking BMPCharPos or
BMPCharPosNoCase to find the next occurrence of a substring.
4) Try to guess how big the resulting string will be in a worst-case scenario. If the NewPattern string is smaller than the OldPattern, then
at worst the result will be the same size as the source string,
i.e. if OldPattern does not occur once within the source string. If the
NewPattern is larger than the OldPattern, assume the source string
consists only of OldPattern repeated many times, i.e. the worst case.
5) Replacing a small piece of text, such as <!Comments!>, with a
large piece of text would cause your estimation of the size of the
result to be very large. If the estimated size is more than 10 times
larger than the source, or if your calculation of the estimated size
causes an integer overflow, you should assume that the size of the
result will be the same as the size of the source string. If this is
the case, increase the size of the result buffer by a factor (Ive used
1.5) each time the buffer is exhausted.
6) Build a jump table (discussed last month). Depending on
whether the search is case-sensitive, youll use MakeBMTable
or MakeBMTableNoCase.
7) Set a local variable to point to your Boyer-Moore search routine.
Depending on whether the search is case-sensitive, it will point to
BMPCharPos or BMPCharPosNoCase. This is done simply because
its much quicker to call a routine directly than it is to have an if
statement within a loop. Every little bit helps.
8) You enter one of two possible loops. If you are replacing OldPattern
with an empty string, you dont need to worry about copying anything
from NewPattern, and you can eliminate a few lines of code within
each loop. (Ill cover the process of replacing a substring with another
rather than simply deleting it.)
9) The next occurrence of the OldPattern is found in the source string.
All characters up to this occurrence are copied into the Result buffer,
and the pointer to the start of the Result buffer is incremented.
10) The contents of NewPattern are copied to the Result buffer, and the
pointer to the start of the Result buffer is incremented.
11) The pointer to the start of the source string is incremented past the
28 September 2002 Delphi Informant Magazine
current occurrence, so the next call to your find routine will return
the occurrence after it. Incrementing a pointer to the start helps you
avoid having to delete part of the source string each time.
12) Repeat step 9 until no more occurrences are found.
13) Resize your Result buffer to the number of bytes written, plus the
number of characters left at the end of your source string. Resizing
your Result buffer actually causes a copy of the entire Result string,
but a single copy is relatively instant.
14) Finally, copy the trailing characters of the source string to the
end of Result.
If you were to use AnsiStrPos instead of AnsiPos, you would be able to use
a similar technique. With only small changes to the StringReplace function, you would be able to improve the speed dramatically while keeping
the same parameters and functionality. There are really only two changes
I will make: 1) try to predict the size of the result to avoid too many
buffer reallocations; and 2) use PChars to point to the start of strings and
increment those pointers rather than deleting text from the beginning of
the strings. An implementation of such a function, named AnsiReplace, is
available for download (again, see end of article for details).
Conclusion
In this series, weve learned how poorly one standard Delphi function
can perform when put under stress. We also explored and implemented
a version of the Boyer-Moore searching routine. Finally, we learned why
StringReplace performs so badly, and wrote a replacement.
The osFastStrings.pas unit and demonstration project referenced in this article
are available on the Delphi Informant Magazine Complete Works CD
located in INFORM\2002\SEP\DI200209PM.
Peter Morris is 26 years old and married, with two children. He works from home
as a computer programmer for Insite Technologies Ltd., where he writes a wide
variety of non-standard-looking Windows applications. He loved breaking away
from the Windows GUI standards. Now, he writes special graphical effects and
animated components. You can reach Peter at MrPMorris@hotmail.com.
PocketStudio 1.1
The PalmOS IDE for Pascal
Well Mr. Leahey, we like your suggestions and have decided that Thoughtsmithy is just
the company to provide for our development needs. This could easily turn into two or
three years worth of work, so we want to make sure your company is well taken care
of. Do you need any equipment new notebook computers, software upgrades? Well
take care of all that. How about a new car? We dont want you putting lots of miles on
yours. Better yet, well provide a driver and valet for you. We have two empty office
spaces your development team could use. Ones on the coast, the other is in the mountains; which would you prefer? Oh, by the way, well need a PalmOS version of this
software for our traveling teams. You can do that right?
Gulp. Why yes; of course we can.
Good, we wouldnt want that little detail to spoil
this contract, would we?
Pricing
The first thing you may notice is PocketStudios ridiculously low
price. Metrowerks CodeWarrior 8.0 for Palm is currently going
for US$499 (with an enterprise edition available for US$1,299).
Pocket Technologies immediately wins this round by pricing
PocketStudio Standard at US$59.99 and PocketStudio Professional
at US$199.99. Granted, Metrowerks has been at this longer
(version 8), but PocketStudio offers a comparable feature list. And
when you add the language issue (CodeWarrior is C/C++ based)
this becomes a huge plus in the PocketStudio column.
IDE
I have mixed feelings about the PocketStudio IDE (see Figure 1).
On first glance it should be obvious that a good amount of work
has gone into making this environment look and feel like home
to the Delphi developer. Many of the windows with which we
are familiar are present; many keystrokes are duplicated (@ for
instance, will toggle between the code editor and the resource
designer).
If you look a little closer, however, youll see my first reservation
about this interface. The marketing materials for PocketStudio suggest that a Delphi developer will feel at home in the IDE; were
also encouraged to use our existing Delphi skills to be productive in
PocketStudio. Have a look at Figures 2 and 3 the toolbars from
Delphi and PocketStudio respectively.
Pocket Technologies has chosen to use different glyphs in their toolbuttons. The two toolbars have seven commands in common: can
you spot them? This may seem like a trivial issue, especially if youre
not a toolbar-minded person, but I contend that its a very big issue
for any developers who, like me, depend on their toolbars. I think
30 September 2002 Delphi Informant Magazine
That being said, there are some nice enhancements to the PocketStudio IDE that are smart. For instance, the Editor+ technology
offers the ability to collapse and expand method implementations.
There are many features that will be familiar to Delphi programmers, such as Code Completion and various wizards to automate
project and code creation. The Resource Designer is also well done,
with easy access to all the resources in a given form (see Figure 4).
On the whole, Delphi developers will feel at home in the PocketStudio IDE, but there are some exceptions, and developers should
be forewarned not to expect PocketStudio to be Delphi; its not.
That will be even more evident when you begin writing code...
Language
Lets get this out of the way early: PocketStudios flavor of Pascal is
not Object Pascal as used by Delphi. It is not at all object oriented;
there is no VCL or inheritance, there are no interfaces or cool
third-party component sets and you wont be writing any custom
components yourself any time soon.
Does that mean you cant be productive using it? Absolutely not!
The preceding list merely embodies the points that most people
seem surprised by when trying PocketStudio; Pascal without all
those things is still Pascal and you know Pascal. Unless you
know C++, youre going to be more comfortable in PocketStudio
than you will be in CodeWarrior.
Another thing to be aware of is that PocketStudio forces you to
know the PalmOS API more thoroughly than Delphi does the
Win32 API. Its possible to write working applications in Delphi
without ever writing a Win32 API call. With PocketStudio, youll
be writing PalmOS API calls, so be prepared. The reason for all this
is that the Delphi architects have done a marvelous job of encapsulating the Win32 API in the VCL. Its not that your application
isnt calling the API; its just that those calls are typically buried
in the VCL classes. With PocketStudio, we are, for the most part,
missing that layer of abstraction.
However, Pocket Technologies is doing something exciting that is changing some of this. Theyve created something called PSLibraries units
that wrap common PalmOS API calls and provide routines that are more
Delphi-like. Theyre still not objects, but youll find routines that seem
familiar to you. For instance, the PSList unit assists you in managing
TList objects by providing routines such as Add, IndexOf, Remove, Count,
etc. If youre taken aback that this functionality is even being discussed,
then you get the point. The convenience to which you are accustomed
in Delphi is a product of the robust classes in the VCL; but there are no
classes in PocketStudios Pascal.
Let me make it clear that this is not a failing on the part of Pocket Technologies; the PalmOS is not object oriented, and theyve done a marvelous job of trying to make object-like behavior appear where there are no
objects. For instance, in the PSList unit I mentioned, the Insert routine
(which inserts an item into a list) contains 28 lines of code. Thats 28
lines you wont have to write every time you instead call PSLine.Insert.
The best part about the PSLibraries is that Pocket Technologies is
continuing to write more. At the time of this writing, an update to
Extensibility
An area where PocketStudio really shines is in extensibility. It was
obviously not lost on the developers that one of the major strong
points of Delphi has been its open architecture, and by providing
PocketScript, they allow the addition of XML-based wizards into the
IDE. As PocketStudio gains acceptance, expect to see more and more
plug-ins based on this architecture.
Figure 4: PocketStudios Resource Designer.
Components
PocketStudio comes
standard with 17 components, which are the
standard Palm UI controls:
Figure 5: One of my applications runbuttons, lists, checkboxes,
ning in POSE as a Handspring Treo.
labels, sliders, tables, and
even a graffiti state indicator
among other things. There is also a control called GADGET, which
enables the developer to create a custom UI widget. These controls comprise all you need to create standard PalmOS applications. If you need to
supply a different UI element, youll have to either use a shared library, or
get very familiar with the PalmOS API.
Documentation
We come to what I feel is the Achilles heel of the current version
of PocketStudio. As good as this product seems to be, and as much
potential as it seems to have for opening up the world of PalmOS
development for Pascal developers, it also stands to drive off as many
customers as it gains simply by its lack of quality documentation.
Like many developers, I learned how to program using Delphi 1.0
under the venerable tutelage of 1. The first version of Delphi,
31 September 2002 Delphi Informant Magazine
Debugging
PocketStudio offers integrated debugging in tandem with POSE
(the PalmOS Emulator). POSE creates a virtual PalmOS device on
your PC, allowing you to test applications without the overhead of
installing your application on a physical unit (see Figure 5). Plus, by
downloading various ROM images, you can test your application
against a variety of devices. The debugger offers some of the niceties
youre familiar with in Delphis debugger, such as Tool Tip evaluation, local variables watch, and a registers window.
Quality of Effort
Make no mistake; this first version of PocketStudio is a landmark effort
in quality and power. The drawbacks Ive listed in no way diminish
the value of PocketStudio. This PalmOS development tool should be
considered a serious contender for best PalmOS development environment, regardless of your language background. And if youre a Delphi
developer, you really have no viable reason not to choose this as your
Conclusion
Can a Delphi developer use his or her skills to more quickly
develop PalmOS applications using PocketStudio? The answer is
Yes. The developer must understand, however, that this product
is not Delphi for PalmOS. He or she will be disappointed. Ive
seen postings on various newsgroups from people who seemed
to think they would be porting their existing Delphi projects
to PocketStudio and have a working PalmOS application in no
time. There needs to be an awareness: PocketStudio is a powerful PalmOS development environment. And its the one I suggest
heartily. But a developer will have to know the platform for which
they are developing. PalmOS development is completely different
from Windows development, and unless a developer gets a good
grounding in the PalmOS API, he or she is in for a rough time.
Use PocketStudio; as a Delphi programmer, its the one you need for
PalmOS development. Just go into the process with your eyes open.
Robert is a Delphi developer who has been writing code since he discovered AppleBasic on his Apple II+. He now provides custom creative solutions and Delphi tool
training via his company, Thoughtsmithy (http://www.thoughtsmithy.com). He is
working on his Masters degree in Music Theory at the University of North Texas and
currently resides in Texas with his wife and daughters.
DBISAM 3.08
Powerful BDE Replacement in a Small Package
ith version 3, DBISAM maintains its place as the premier BDE replacement, and
just for grins adds client/server capabilities to its list of features.
Features
There isnt room here for an in-depth discussion of DBISAMs
features (theyre numerous and robust), and many of them have
been discussed before in other reviews of the product. Instead, I
encourage you to download the trial version and read the documentation. However, we do have room for an overview of the
products main components.
Once installed, the four data access components added to Delphi
are TDBISAMTable, TDBISAMQuery, TDBISAMDatabase, and
TDBISAMSession. These components are largely direct replacements
for Delphis standard BDE components, i.e. Table, Query, Database,
and Session. All of the standard functionality youre familiar with
is available in the DBISAM components; each of the components
behaves in a similar manner to their BDE counterparts. However,
they also include many DBISAM-specific enhancements. Because of
the similarities, converting an existing BDE application to DBISAM
is very simple; in most cases, a simple one-to-one replacement of
components does the trick. And with the available BDE Transfer utility, you can take your existing BDE-based database and convert the
data to DBISAM tables, getting your legacy BDE application off the
ground with DBISAM in no time.
Distribution Dreams
When you develop an application using DBISAM, you can include
the DBISAM engine either by compiling it directly into your application, or by providing it as a run-time package. If you choose to
deploy it as a package, you need only distribute one BPL file. This is
quite refreshing (and enviable) if youre used to distributing BDE- or
ADO-based applications. If you deploy with the engine compiled
into your application, youve no distribution issues whatsoever; your
database engine is part of your executable.
Test Bench
I have two sets of favorite test data. One database is a set of master/
detail tables (M/D/D/D/D) in which the lowest detail table contains
1,000,000 records. The other reveals my passion for the game of
baseball; its a very large database containing all the statistics for every
baseball player thats ever been in the major leagues over 15,000
players resulting in over 400,000 records of statistics. These two
databases provide a good test bench since the million-record database
is large but doesnt represent real-world data, and the baseball data is
smaller but is more realistic, requiring SQL statements with several
joins and calculations to provide meaningful data.
34 September 2002 Delphi Informant Magazine
Conclusion
DBISAM has been my favorite desktop database for some time.
Now, with the affordable and high-quality client/server feature set,
it may begin to bump aside the big players in the multi-tier arena as
well. While DBISAM did not take top honors in my time trials, Ill
reiterate that when its respectable performance is combined with
its other great features power, compactness, ease of distribution,
great support and documentation and good company track record
it easily rises to the top of any database engine list.
Robert is a Delphi developer who has been writing code since he discovered AppleBasic on his Apple II+. He now provides custom creative solutions and Delphi tool
training via his company, Thoughtsmithy (http://www.thoughtsmithy.com). He is
working on his Masters degree in Music Theory at the University of North Texas and
currently resides in Texas with his wife and daughters.
ExpressQuantumTreeList Suite
Powerful, Flexible Data Presentation Components
eveloper Express ExpressQuantumGrid took top honors as Best VCL Component in the
recent Delphi Informant Magazine 2002 Readers Choice Awards. That probably didnt
surprise you; it won last year as well. What may surprise you, however, is that lurking behind
the notoriety of its big brother is another very powerful tool from Developer Express which
may appeal to those on a budget: the ExpressQuantumTreeList Suite (EQTreeList for short).
First, if you have the ExpressQuantumGrid Suite
(EQGrid for short), the components that make
up EQTreeList are going to sound familiar. Thats
because you already have them. Of the components in EQTreeList, the QuantumTreeList and
the ExpressEditors Library are part of the Standard edition of EQGrid (for US$299.99), while
these components and the QuantumDBTreeList
are found in the Professional edition of EQGrid
(for US$349.99). However, what Im reviewing
here is EQTreeList (for US$199.99) which basically contains everything in EQGrid Pro, except
for the famous QuantumGrid control. Confused?
Check out the feature matrix in Figure 5.
TdxTreeList
TdxTreeList is one serious data presentation component. This thing takes no prisoners and other
tree controls should fear meeting it in a dark alley.
What have Win32 developers been doing in any
sort of hierarchical display application since the
advent of Windows 95? Dropping a TreeView and
Figure 1: A TreeList control in action. Hank Aaron wasnt a bad player, eh?
A career .928 OPS? Wow.
36 September 2002 Delphi Informant Magazine
Column Types
Each column in the TreeList control can be one of 18
different types (20 for the DBTreeList). This column
type determines, among other things, what sort of inplace editor will be used to edit a value in the column.
The column types are as follows:
Standard. Generic text display type and base class
to all column types.
Mask. Uses a mask (similar to Delphis
TMaskEdit) to format displayed data.
Button. Displays a customizable button in the
column which your users can click to edit the
value. This fires events to which you can respond.
Date. Displays a drop-down calendar for editing.
Check. Displays a check box editor.
Image. This confusingly named column
actually uses a drop-down list, which can
contain images and text (like an extended
combo box) as an editor.
Spin. Uses a spin button for editing of
sequential values.
Pick. Uses a drop-down list editor much like a
combo box.
TdxDBTreeList
As impressive as the
TreeList control is,
you havent seen
anything until youve
seen the data-aware
version work its
magic on your datasets. TdxDBTreeList
features all of the
same columns, and
adds two more:
Lookup. Displays
a drop-down
lookup list as an
in-place editor.
Extended
Lookup. Displays
a TdxDBGrid
as a drop-down
lookup editor.
ExpressEditors
A great set of components in their own right, the ExpressEditors
that are part of the suite seem almost an afterthought. But
these controls shouldnt be overlooked. You have available, as
stand-alone Delphi components, controls that are essentially the
same as the in-place editors you find in your TreeList cells; any
functionality you like can be found in the corresponding standalone editor. Youll find data-aware and non-data-aware versions
of dxEdit, dxMaskEdit, dxMemo, dxDateEdit, dxButtonEdit,
dxCheckEdit, dxImageEdit, dxSpinEdit, dxPickEdit, dxCalcEdit,
dxHyperLinkEdit, dxTimeEdit, dxCurrencyEdit, dxGraphicEdit,
Price
EQTreeList
EQGrid
Standard
EQGrid
Professional
US$199.99
US$299.99
US$349.99
QuantumGrid
QuantumTreeList
QuantumDBTreeList
ExpressEditors
Conclusion
EQTreeList is a great, affordable alternative to the EQGrid. Obviously, you should consider going all out and buying EQGrid Professional, but if budget is an issue and you dont need the extra features
offered by the EQGrid, you can get a great deal of bang for the
buck in EQTreeList. The feature/price matrix in Figure 5 provides
a comparison of the available products. The components found in
EQTreeList offer exceptional capabilities for minimal effort on the
part of the developer. Creating applications that are beyond the
no-frills nature of a quick-and-dirty Delphi application becomes a
simpler matter with ExpressQuantumTreeList Suite.
Robert is a Delphi developer who has been writing code since he discovered AppleBasic on his Apple II+. He now provides custom creative solutions and Delphi tool
training via his company, Thoughtsmithy (http://www.thoughtsmithy.com). He is
working on his Masters degree in Music Theory at the University of North Texas and
currently resides in Texas with his wife and daughters.
File | New
Directions / Commentary
f you have a bookshelf close to your development computer, take a moment to examine it. If its anything like
mine, it will include a number of newer books and a handful of old favorites. Well discuss both vintages in this
column, beginning with the newer books. First well examine new editions of established Delphi classics, then more
specialized new books. Well conclude by dusting off a few oldies but goodies that should be a part of the library
of every Delphi developer who wants to master the more advanced topics.
Perennial Classics. If youve read my earlier columns on this subject,
you know my choices as the two Delphi classics two books that have
had editions for almost every new version of Delphi and that have been
consistently at the top of the annual readers poll. Of course Im talking
about Mastering Delphi 6 by Marco Cant (SYBEX, ISBN: 0-78212874-2) and Delphi 6 Developers Guide by Steve Teixeira and Xavier
Pacheco (SAMS, ISBN: 0-672-32115-7). I reviewed earlier editions
of each of these outstanding works last year and also discussed them in
an earlier column. In this column I will summarize some of the main
features with emphasis on newer features.
As I mentioned, I consider Cants work to be better suited for a lessexperienced developer with its clear explanations of basic principles
and techniques, its tour of Delphis IDE, and its introduction to
Object Pascal. But it also includes more advanced topics, such as
working with dynamic-link libraries, and a short section on shell
programming. Like Delphi itself, it is especially strong in its coverage
of database and Internet development topics, including InterBase,
DataSnap, WebBroker, XML, and SOAP. It also includes all the
expected basic topics, and chapters on component writing.
Steve Teixeira and Xavier Pacheco take a similar approach to Cants,
beginning with a thorough introduction to the Object Pascal language. This work puts greater emphasis on cross-platform development (with Delphi and Kylix) and includes a discussion of creating
CLX components. It includes a much more extended discussion of
shell programming and multithreading. It ends with a chapter on
programming wireless devices. As a bonus, the CD includes chapters
from the previous edition not included in this volume.
Tomes and More. The two-volume Tomes of Delphi 3 by John Ayers, et.
al. was an acknowledged milestone in Delphi book publishing. These
were the first serious excursions exposing API-level programming with
Delphi. Considering all of the changes in the world of Windows development in the last several years, an update to these important works was
needed. It has now arrived! Earlier this year Ayers The Tomes of Delphi:
Win32 Core API, Windows 2000 Edition (Wordware, ISBN: 1-55622750-7) appeared. It included most of the earlier Core volume, and many
topics from the Graphical API volume.
A second, new Tomes of Delphi volume on the Win32 Shell API (Wordware, ISBN: 1-55622-749-3) will be available by the time you read this.
I had a chance to examine a pre-publication version while writing this
column. With emphasis on the Windows 2000 operating system, it
greatly expands the Shell API coverage found in the original Graphical
API volume. All the features of the earlier Tomes volumes are included,
39 September 2002 Delphi Informant Magazine
with at least one important addition those functions specific to Windows NT and/or Windows 2000 are indicated in the text.
One of the important new technologies supported in Delphi is XML.
Keith Woods Delphi Developers Guide to XML (Wordware, ISBN:
1-55622-812-0) deals exclusively with this topic in a comprehensive
way. All of the major XML-related technologies are covered, including
DOM and SAX. In many instances Wood discusses alternate Delphi
solutions to working with particular parts of the XML family. For example, after an excellent introduction to the Document Object Model
(DOM) followed by Microsofts implementation of it, he provides
two more chapters that introduce specific Delphi solutions: CUESofts
Document Object Model and the Open XML Document Object
Model, both of which are comprised of native Delphi Objects. If youre
working with XML in Delphi you should find this work invaluable.
Oldies but Goodies. My list of favorite oldies is similar to the Essential Delphi Library of Advanced Works I included in my last Delphi
book column a year and a half ago. Unfortunately, many of the books
are now out of print and difficult to find. Nevertheless, I recommend
adding any or all of them to your library. If you write experts, or work
in other ways with Delphi internals (e.g. RTTI), then you need both
of Ray Lischners early books, Secrets of Delphi 2 (Waite Group, ISBN:
1-57169-026-3) and Hidden Paths of Delphi 3 (Informant Press, ISBN:
0-9657366-0-1). The first deals with a variety of advanced topics, and
the second concentrates on the Open Tools API.
If you write components, Ray Konopkas seminal treatise on this
discipline, Creating Custom Delphi 3 Components (Coriolis, ISBN:
1-88357-747-0) is the fundamental book you must include in your
library. For information on other older and more recent Delphi
books I recommend you read my last column on the subject, Delphi
Book Wrap-up 2000 (in the November 2000 issue), available online
at http://www.DelphiZine.com. Until next time...
Alan C. Moore, Ph.D.
Alan Moore is a professor at Kentucky State University, where he teaches music theory and
humanities. He was named Distinguished Professor for 2001-2002. He has been named the
Project JEDI Director for 2002-2003. He has developed education-related applications with
the Borland languages for more than 15 years. Hes the author of The Tomes of Delphi:
Win32 Multimedia API (Wordware Publishing, 2000) and co-author (with John Penman)
of an upcoming book in the Tomes series on Communications APIs. He also has published
a number of articles in various technical journals. Using Delphi, he specializes in writing
custom components and implementing multimedia capabilities in applications, particularly
sound and music. You can reach Alan on the Internet at acmdoc@aol.com.