You are on page 1of 44

F O R D E L P H I, L A Z A R U S, A N D P A S C A L

R E L A T E D L A N G U A G E S / A N D R O I D,
I O S, M A C , W I N D O W S & L I N U X
P R I N T E D, P D F, & O N L I N E V I E W

DX

BLAISE PASCAL MAGAZINE 61

RTTI FOR STARTERS PART 1 (RUNTIME TYPE INFORMATION)


BY MICHAEL VAN CANNEYT
CALLING LINUX COMMANDS FROM DELPHI
BY CRAIG CHAPMA
CREATING A LINUX DAEMON (SERVICE) IN DELPHI
BY CRAIG CHAPMAN
LINUX APPLICATION DEVELOPMENT
CREATING A TODO LIST WITH FIRE DAC MEMTABLE
BY DETLEF OVERBEEK
BLUETOOTH CONTROLLED ROBOT
WITH VISUINO AND DELPHI PART 2 - DELPHI
BY BOIAN MITOV
KBMMW ORM (OBJECT RELATIONAL MODEL)
BY KIM BO MADSEN

PRINTED ISSUE PRICE € 15,00

DOWNLOAD ISSUE PRICE € 5,00


POCKET EDITION THE FAMOUS
Printed in full color.
A fully indexed PDF book LIBRARY STICK
is included.

GET THE BOOK INCLUDING NEWEST TECHNIQUE USB 3 /


THE NEWEST LIBRARY STICK 16 GB. LIBRARY OF THE
INCLUDING 1YEAR DOWNLOAD MAGAZINE UPDATED FROM
FOR 75 EUROS + SHIPPING ISSUE 1 TO ISSUE 59

COMBINATION: 3 FOR 1
BOOK INCLUDING THE LIBRARY STICK EXCL. SHIPPING
INCLUDING 1YEAR DOWNLOAD FOR FREE 75€
http://www.blaisepascal.eu/subscribers/UK/UK_CD_DVD_USB_Department.html

2 Issue Nr 8 2016 BLAISE PASCAL MAGAZINE


BLAISE PASCAL MAGAZINE 61
D E L P H I, L A Z A R U S, S M A R T M O B I L E S T U D I O,
A N D P A S C A L R E L A T E D L A N G U A G E S
F O R A N D R O I D, I O S, M A C, W I N D O W S & L I N U X

CONTENTS
ARTICLES
RTTI FOR STARTERS PART 1 (RUNTIME TYPE INFORMATION) PAGE 5
BY MICHAEL VAN CANNEYT
CALLING LINUX COMMANDS FROM DELPHI PAGE 14
BY CRAIG CHAPMA
CREATING A LINUX DAEMON (SERVICE) IN DELPHI PAGE 16
BY CRAIG CHAPMAN
LINUX APPLICATION DEVELOPMENT PAGE 17
CREATING A TODO LIST WITH FIRE DAC MEMTABLE PAGE 19
BY DETLEF OVERBEEK
BLUETOOTH CONTROLLED ROBOT WITH VISUINO AND DELPHI PAGE 29
PART 2 - DELPHI
BY BOIAN MITOV
KBMMW ORM (OBJECT RELATIONAL MODEL) PAGE 37
BY KIM BO MADSEN

V
X CL
M
F

FNC

V
X CL
M
F

FNC

PASCON 2017
19 SEPTEMBER

ADVERTISERS
ANDROID LOGCAT FREE TOOL COMPONENTS4DEVELOPERS PAGE 27
BARNSTEN PAGE 11/12/13
PASCON 2017 DELPHI & PASCAL EVENTS PAGE 18
COMPONENTS4DEVELOPERS PAGE 44
SPECIAL OFFER PAGE 2
VISUINO PAGE 28

Publisher: Foundation for Supporting the Pascal Programming Language


in collaboration with the Dutch Pascal User Group (Pascal Gebruikers Groep)
© Stichting Ondersteuning Programmeertaal Pascal

Issue Nr 3 2017 BLAISE PASCAL MAGAZINE 3


Stephen Ball Peter Bijlsma -Editor Dmitry Boyarintsev
http://delphiaball.co.uk peter @ blaisepascal.eu dmitry.living @ gmail.com
@DelphiABall

Michaël Van Canneyt, Marco Cantù David Dirkse


michael @ freepascal.org www.marcocantu.com www.davdata.nl
marco.cantu @ gmail.com E-mail: David @ davdata.nl

Benno Evers Bruno Fierens Primož Gabrijelčič


b.evers www.tmssoftware.com www.primoz @ gabrijelcic.org
@ everscustomtechnology.nl bruno.fierens @ tmssoftware.com

Fikret Hasovic Cary Jensen Peter Johnson


fhasovic @ yahoo.com www.jensendatasystems.com http://delphidabbler.com
http://caryjensen.blogspot.nl delphidabbler@gmail.com

Max Kleiner John Kuiper Wagner R. Landgraf


www.softwareschule.ch john_kuiper @ kpnmail.nl wagner @ tmssoftware.com
max @ kleiner.com

Kim Madsen Andrea Magni Boian


Kim Madsen
Mitov
kbm @ components4developers.com www.andreamagni.eu mitov
www.component4developers
@ mitov.com
andrea.magni @ gmail.com
www.andreamagni.eu/wp
Olaf MONIEN Paul Nauta PLM Solution Architect Jeremy North
olaf@developer-experts.net CyberNautics jeremy.north @ gmail.com
paul.nauta@cybernautics.nl
Detlef Overbeek - Editor in Chief Howard Page Clark Heiko Rompel
www.blaisepascal.eu hdpc @ talktalk.net info@rompelsoft.de
editor @ blaisepascal.eu

Wim Van Ingen Schenau -Editor Peter van der Sman Rik Smit
wisone @ xs4all.nl sman @ prisman.nl rik @ blaisepascal.eu
www.romplesoft.de

Bob Swart B.J. Rao Daniele Teti


www.eBob42.com contact@intricad.com www.danieleteti.it
Bob @ eBob42.com d.teti @ bittime.it

Anton Vogelaar Siegfried Zuhr


ajv @ vogelaar-electronics.com siegfried @ zuhr.nl

Editor - in - chief
Detlef D. Overbeek, Netherlands Tel.: +31 (0)30 890.66.44 / Mobile: +31 (0)6 21.23.62.68
News and Press Releases email only to editor@blaisepascal.eu

Editors
Peter Bijlsma, W. (Wim) van Ingen Schenau, Rik Smit,
Correctors
Howard Page-Clark, James D. Duff
Trademarks
All trademarks used are acknowledged as the property of their respective owners.
Caveat Whilst we endeavour to ensure that what is published in the magazine is correct, we cannot accept responsibility for any errors or omissions.
If you notice something which may be incorrect, please contact the Editor and we will publish a correction where relevant.
Subscriptions ( 2013 prices )
1: Printed version: subscription € 85.-- Incl. VAT 6 % (including code, programs and printed magazine,
10 issues per year excluding postage).
2: Electronic - non printed subscription € 50.-- Incl. VAT 21% (including code, programs and download magazine)

Subscriptions can be taken out online at www.blaisepascal.eu or by written order, or by sending an email to office@blaisepascal.eu
Subscriptions can start at any date. All issues published in the calendar year of the subscription will be sent as well.
Subscriptions run 365 days. Subscriptions will not be prolonged without notice. Receipt of payment will be sent by email.
Subscriptions can be paid by sending the payment to:
ABN AMRO Bank Account no. 44 19 60 863 or by credit card: Paypal
Name: Pro Pascal Foundation-Foundation for Supporting the Pascal Programming Language (Stichting Ondersteuning Programeertaal Pascal)
IBAN: NL82 ABNA 0441960863 BIC ABNANL2A VAT no.: 81 42 54 147 (Stichting Programmeertaal Pascal)
Subscription department Edelstenenbaan 21 / 3402 XA IJsselstein, The Netherlands / Tel.: + 31 (0) 30 890.66.44 / Mobile: + 31 (0) 6 21.23.62.68
office@blaisepascal.eu

Copyright notice
All material published in Blaise Pascal is copyright © SOPP Stichting Ondersteuning Programeertaal Pascal unless otherwise noted and may
not be copied, distributed or republished without written permission. Authors agree that code associated with their articles will be made
available to subscribers after publication by placing it on the website of the PGG for download, and that articles and code will be placed on
distributable data storage media. Use of program listings by subscribers for research and study purposes is allowed, but not for commercial
purposes. Commercial use of program listings and code is prohibited without the written permission of the author.

4 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE


RTTI FOR STARTERS PART 1 PAGE 1/ 6
(RUNTIME TYPE INFORMATION) BY MICHAËL VAN CANNEYT
starter expert
DX it will know that
Delphi & Lazarus 1. M indeed has a property MyProperty
Abstract 2. An integer value can be assigned to MyProperty.
The RTTI is a part of Delphi since version 1 of 3. it must call SetM to actually set the value.
Delphi. What is it, what does it do and what
can you do with it ? A gentle introduction... However, at run-time, the compiler is not available to
check such things.
INTRODUCTION
The RTTI is a very important part of the Object Yet, when a Delphi program is run, the definition of a
form is read from the form file and used to construct
Pascal language. It has been around as long as
the form. This is thanks to the presence of RTTI:
Delphi is around, because Delphi as we know it The presence of RTTI makes it possible to create
today, would not be able to exist without RTTI: arbitrary objects and assign values to the available
the IDE's Form designer is built on top of RTTI. properties.
Since the first version of Delphi, the RTTI When looking at a form file (to see it, hit Alt-F12).
has been expanded to include much more Things like the following can be seen:
information than in the original version.
Meanwhile it can be used to e.g. (Exempli gratia) object BSet: TButton
Left = 8
call arbitrary methods (e.g. in the SOAP Top = 80
implementation), arbitrary attributes can be Width = 90
created, and the way it is accessed has evolved Height = 25
(the RTTI unit). Caption = 'Set property'
Other languages also offer RTTI equivalents: Default = True
TabOrder = 2
Java and C# have introspection and reflection,
OnClick = BSetClick
which corresponds to RTTI in Delphi. end
In Javascript, the feature is more or less built-
in in the language.
(When done, hit Alt-F12 again to see the actual form)
At run-time, when the form must be created, the above
THE NEED FOR RTTI information is read, and the form is constructed.
Formally, RTTI stands for Run-Time Type This is achieved using RTTI: when the above
Information. As the name indicates, information is read (using the streaming mechanism),
it is information about the used types in a Delphi things like
program, which is available at run-time. Here Run- Height = 25
are translated to the equivalent of Pascal statements
Time is meant as opposed to Compile-Time. like.
Since Object Pascal is a strongly typed language, it is Bset:= TButton.Create(Self)
only natural that while compiling, the compiler has all BSet.Height:=25;
information available about a property of a class. The compiler is not available at runtime to evaluate
This means that, given the declaration the above instruction. Instead, the information
contained in the RTTI in the binary is queried to
TMyClass = class know how to create the button, and how to
private apply 25 to the Height property of the button.
FMyProperty: Integer; In essence, RTTI is a large table
procedure SetM(AValue: Integer); containing all the classes used in a
Public program, and for each class, a table
Property MyProperty : Integer Read FMyProperty Write SetM; with all the published fields,
end; properties and methods of the class,
procedure TMyClass.SetM(AValue: Integer); and how to access them.
begin The information in these tables is then
if FMyProperty=AValue then Exit; consulted to create the button, and set its
FMyProperty:=AValue; height to 25.
end;

the compiler can do a lot of things when


it encounters code as:
M:=MyClass.Create;
M.MyProperty:=3;

Issue Nr 3 2017 BLAISE PASCAL MAGAZINE 5


RTTI FOR STARTERS PART 1 (RUNTIME TYPE INFORMATION) PAGE 2/ 6

MAKING USE OF BASIC RTTI A SIMPLE EXAMPLE


If constructing forms at run-time was the only use To demonstrate the use of RTTI, we'll make a small
for RTTI, then the makers of Delphi need not have program that allows to enter a property name, and a
bothered: it would have been perfectly possible to value.
let the IDE create a (private ) method which was The program will then attempt to set a form property
simply full of pascal code like: with the given name, using the provided value.
There are 2 edit boxes: one for the name, one for the
BSet:=TButton.Create(Self)
BSet.Height:=25; value.
BSet.Left:=8; A button Set property can be pushed to actually set
BSet.Top:=80; the property.
BSet.Width:=90; The first version of the form uses the typinfo unit.
BSet.Height:=25; The OnClick handler of the button looks as
BSet.Caption:='Set property'; follows:
procedure TMainForm.BSetClick(Sender: TObject);
This method could be called by the constructor, begin
SetPropValue(Self,EName.Text,EValue.Text);
and this would construct the form.
end;
Indeed, this would be faster than the current method
which involves parsing a form file, looking up names
This is of course very simple.
in RTTI etc.
The SetPropValue function is simplicity itself.
In fact, IDE plugins exist that do exactly this
(e.g. GExperts contains such a plugin). procedure SetPropValue(Instance: TObject;
const PropName: string;
const Value: Variant); overload;
Then why use RTTI anyway ? The reason is in the
additional flexibility this offers. If the form was
constructed using compiled code, there would be no
possibility to change the definition, once the binary is It takes 3 arguments.
shipped to the users: the definition is compiled in the An instance of a class , a property name and the
binary. (Binary code, the digital representation of text and value (as a variant).
data) It will look up the name of the property, check the
But since the form definition is stored in text in the type, and attempts to convert the variant to the
resources, it is possible to change the text, or even load appropriate type for the property. If all checks out, the
2 form definitions, one after the other. property is set.
This is in fact what is done if a form is localized: the
original definition is loaded using RTTI, and then it is Using the RTTI unit, a little more work is needed.
altered using a localized definition of the form; (not This unit lacks the ability to convert a variant to the
only the texts can be changed, but also control positions etc.) appropriate type, so this must be done manually.
Whenever classes of the RTTI unit are used, a context
There exist many components that use the RTTI to TRTTIContext record must be used. This record
save and store form positions (and in fact, any property of contains the necessary methods to query the RTTI.
any component) at runtime. For our example, the GetType method must be used
The JVCL's TJvFormPlacement class, for to retrieve the type information of the form.
example. This is an object of type TRTTIType. That object can
The Inspex package from Raize Software also makes then be used to get the type information of the
use of the RTTI to allow you to alter the properties of property whose name was entered: for this,
classes at runtime. the GetProperty method of TRTTIType must be
There are 2 units in the RTL (Run-Time Library )that used.
offer access to the RTTI. It will result in a object of type TRTTIProperty
The original TypeInfo unit (which is procedural and Once the property information is available, the actual
very low-level), and the more modern, object-oriented property can be set:
RTTI unit which uses classes and generics.
Both offer the same information, and which one to use
is largely a matter of preference.

6 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE


RTTI FOR STARTERS PART 1 (RUNTIME TYPE INFORMATION) PAGE 3/ 6

procedure TMainForm.BSetClick(Sender: TObject); GETTING PROPERTY LISTS


It is not very interesting to be able to set only properties of
Var the form, so we'll first extend the program to fill a combobox
Ctx : TRTTIContext; with the available components in the form.
RT : TRTTIType; Additionally, letting the user guess the names of available
P : TRttiProperty; properties is also not very convenient, so we'll present the
AValue : String; user with a list of properties of the selected component.
Showing a list of available components can be done without
begin RTTI, using the methods of TComponent is sufficient
AValue:=EValue.Text; (although RTTI could be used to get the design-time components
Ctx:=TRTTIContext.Create; of the form). We'll store a reference to the component in the
try Items.Objects property of the combobox.
RT:=Ctx.GetType(ClassInfo);
P:=rt.GetProperty(EName.Text); procedure TMainForm.FormCreate(Sender: TObject);
if P.IsWritable then begin
case P.PropertyType.TypeKind of FillCBComponents;
tkInteger : P.SetValue(Self,StrToInt(AValue)); end;
tkEnumeration :
P.SetValue(Self,StrToInt(AValue)); procedure TMainForm.FillCBComponents;
tkUString : P.SetValue(Self,AValue);
Var
end;
C : TComponent;
finally
S : String;
Ctx.free;
end; begin
end; S:=CBComponent.Text;
With CBComponent,CBComponent.Items do
To set the property, its type must be known: the RTTI begin
unit does not offer functionality to automatically beginUpdate;
convert from a string to the property's actual type. try
Clear;
The type of the property is available in the Sorted:=False;
AddObject(Name,Self);
P.PropertyType.TypeKind property:
for C in self do
this is an enumerated type which contains an AddObject(C.Name, C);
element for all possible pascal types. Sorted:=True;
CBComponent.ItemIndex:=
The code above checks for a few common types, CBComponent.Items.IndexOf(S);
but in actual code, all possible type kinds should be finally
checked (and an exception raised if it is not supported). EndUpdate;
The SetPropValue does all this automatically. end;
The result of this code can be seen in figures 1 and 2. end;
end;

The BSet OnClick handler is now refactored *


(see next page) so it will first retrieve the selected
component, and then calls the SetProperty
routine, which simply contains our previous code,
changed to accept 3 arguments: component, property
name and value
procedure TMainForm.BSetClick(Sender: TObject);
Var
IDX : Integer;
C : TComponent;

begin
IDX:=CBCOmponent.ItemIndex;
if (IDX=-1) then
Raise Exception.Create(
'Select a component first');
C:=CBCOmponent.Items.Objects[IDX] as TComponent;
SetProperty(C,EName.Text,EValue.Text);
end;
Figure 1/2 : Setting a property

Issue Nr 3 2017 BLAISE PASCAL MAGAZINE 7


RTTI FOR STARTERS PART 1 (RUNTIME TYPE INFORMATION) PAGE 4/ 6

procedure TMainForm.ShowProperties(C : TComponent);

begin
With CBName,Items do
begin
try
BeginUpdate;
Clear;
Sorted:=False;
if Assigned(C) then
GetPropertyNames(C,CBName.Items);
Sorted:=True;
finally
EndUpdate;
end;
end;
end;

Using the typinfo unit, the routine to get the


The result looks as in the figures 3/4 above property names of the selected component,
looks as this
Code refactoring procedure TMainForm.GetPropertyNames(
is the process of restructuring existing computer code C : TComponent;AList : TStrings);
—changing the factoring—without changing its external Var
behavior. P : PPropList;
Refactoring improves nonfunctional attributes of the I,N : Integer;
software. Advantages include improved code readability and begin
reduced complexity; these can improve source-code N:=TypInfo.GetPropList(C,P);
maintainability and create a more expressive internal try
architecture or object model to improve extensibility. I:=0;
Typically, refactoring applies a series of standardised While I<N do
basic micro-refactorings, each of which is (usually) a tiny begin
change in a computer program's source code that either AList.Add(P^[I].Name);
preserves the behaviour of the software, or at least does not Inc(I);
modify its conformance to functional requirements. end;
Many development environments provide automated finally
support for performing the mechanical aspects of these basic FreeMem(P);
refactorings. If done extremely well, code refactoring may end;
also resolve hidden, dormant, or undiscovered computer bugs end;
or vulnerabilities in the system by simplifying the underlying
logic and eliminating unnecessary levels of complexity.
The GetPropList function of the TypInfo unit
Now for the more interesting part: returns a list of all properties of an object (or a class).
The result is a count of properties, and a pointer to an
Once the component is selected, we present to the
array of PPropInfo pointers (P).
user a list of published properties that the selected
component has. When finished with this array, it must be freed.
The varPPropInfo points to a TPropInfo
procedure TMainForm.CBComponentChange(Sender: TObject);
record. This is one of the central records in
Var
IDX : Integer; the TypInfo unit:
C : TComponent; TPropInfo = packed record
begin PropType: PPTypeInfo;
IDX:=CBCOmponent.ItemIndex; GetProc: Pointer;
if (IDX=-1) then SetProc: Pointer;
C:=Nil StoredProc: Pointer;
else Index: Integer;
C:=CBCOmponent.Items.Objects[IDX] as TComponent;
ShowProperties(C); Default: Integer;
end; NameIndex: SmallInt;
Name: TSymbolName;
end;

8 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE


RTTI FOR STARTERS PART 1 (RUNTIME TYPE INFORMATION) PAGE 5/ 6

The PropType pointer points to the type Var


information of the property's type. I : Integer;
The Name is the name of the property, which is what begin
For I:=0 to ControlCount-1 do
interests us: this is used to add the name of the
Controls[i].Enabled:=False;
property to the list of property names. end;
The other fields tell us how the property is accessed
(getter,setter etc.), and whether has a default value.
Using the RTTI unit, the routine to get the property
names is quite simple as well:
procedure TMainForm.GetPropertyNames(C : TComponent;AList : TStrings);

Var
Ctx : TRTTIContext;
P : TRttiProperty;

begin
Ctx:=TRTTIContext.Create;
try
For P in ctx.GetType(C.ClassInfo).GetProperties do
AList.Add(P.Name);
finally
Ctx.free;
end;
end;

The CTX.GetType call is the same as used in the code This can be done because all controls on the form
to set a property name: it returns a TRTTIType descend from TControl and they all inherit the
descendent. Enabled property.
The GetProperties method of the TRTTIType
class returns an array of TRttiProperty instances: But imagine another scenario:
one for each property in the class. only Data-Aware controls must be disabled.
This array is traversed, and the names of each of the Data-Aware controls have been created as descendants
properties is added to the list. of regular controls which implement Data-related
The resulting code results in a form that looks as in properties (DataField and DataSource).
figure  5/6.  That means that there is no property at the TControl
level which can be checked to see if the property is
data-aware.
Without RTTI, the only method to achieve this would
have been to check the type of the control:
Var
I : Integer;
C : TControl;
begin
For I:=0 to ControlCount-1 do
begin
C:=Controls[i];
if (C is TDBText)
or (C is TDBGrid)
// check all other types
or (C is TDBEdit) then
C.Enabled:=False;
end;

Figure 5/6 :
Selecting a property of a selected component It is clear that this is error-proof and slow.
With RTTI, we can do this a lot easier.
SETTING A KNOWN PROPERTY ON SEVERAL When the Data-Aware controls were made, care was
CLASSES taken that they all use the same property names to
The code demonstrated till now was informative, link to a datasource and indicate a field
but hardly practical. Let's turn to a more practical (DataField and DataSource).
example. Using this fact, we can just check whether the control
To disable all controls of a form, their 'enabled' has the Datasource property to know if it is data
property can be set to false. this can be done with a aware, and disable it. Using the typinfo unit, this
simple loop: is done as follows: (next page)

Issue Nr 3 2017 BLAISE PASCAL MAGAZINE 9


RTTI FOR STARTERS PART 1 (RUNTIME TYPE INFORMATION) PAGE 6/ 6

Var Conclusion
I : Integer; The RTTI of Delphi is a powerful tool. For simple
C : TControl; projects, it's mostly hidden in the form streaming
code, but as projects become more advanced, it can
begin be useful to know how RTTI functions and what can
For I:=0 to ControlCount-1 do be done with it. In this article, a start has been
begin made, showing some simple things like how to set
C:=Controls[i]; an arbitrary property using RTTI, how to get a list of
if isPublishedProp(C,'DataSource') then properties, and how to check if a class has a certain
C.Enabled:=False; property. RTTI can do many more things, such as
end;
examine attributes or invoke arbitrary methods.
end;
These topics will be treated in a future article.

The isPublishedProp function of the TypInfo


unit checks if a class (or class instance) has the indicated
property.
To do this with the RTTI unit, the following code can be
used:
Var
I : Integer;
C : TControl;
Ctx : TRTTIContext;
P : TRttiProperty;

begin
Ctx:=TRTTIContext.Create;
try
For I:=0 to ControlCount-1 do
begin
C:=Controls[i];
for P in Ctx.GetType(C.ClassInfo).GetDeclaredProperties do
if SameText(P.Name,'DataSource') then
begin
C.Enabled:=False;
Break;
end;
end;
finally
Ctx.free;
end;
end;

10 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE


• development
CLIENT DATASETS TO-DO LIST PART 1 tools • training
PAGE 8/8
barnsten •• components
consultancy • hands-on workshops
• support

GREAT MAY 2017 DEAL


PROFESSIONAL VERSION
BUY ONE, GET ONE HALF PRICE!
Deal 1:
Ready to upgrade your team to Delphi 10.2 Tokyo Professional?
Upgrade your licenses from XE7 or later now and get your second license
with a discount of 50%

50%
off

Deal 2:
Expanding your Delphi development team using Delphi Professional?
Buy now your additional licenses and benefit from a discount of 50% on your
second license
Learn more at: http://embt.co/2pEO09O
THIS OFFER IS ALSO VALID ON C++BUILDER AND RAD STUDIO
PROFESSIONAL LICENSES

www.barnsten.com .
11 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE
• development tools • training
barnsten • consultancy
• components
• hands-on workshops
• support

GREAT MAY 2017 DEAL


ENTERPRISE/ARCHITECT VERSION
Buy Delphi 10.2 Tokyo Enterprise or Architect Edition and get the
complete RAD STUDIO version.
RAD Studio includes both Delphi and C++Builder!

DX RX CX
The Ultimate APPlication
Development Platform for
Windows 10, Mac, Mobile and IoT

This offer is valid for upgrades and new user licenses.

Learn more at: http://embt.co/2qERJn1

www.barnsten.com .
12 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE
• development tools • training
barnsten • consultancy
• components
• hands-on workshops
• support

We invite you for a 100%technical


Delphi/C++Builder seminar.
During this seminar we use the latest techniques and the new Delphi 10.2
Tokyo Enterprise version. You will learn everything about the 2017 features and
the possibilities the Tokyo version is offering. You will get up to speed with the
support for Linux, RAD Server, Beacons and much more.

We based the content of the sessions on the most frequently asked questions
we receive from developers:
1. How can I give my application a modern look and feel and how can I
optimize the underlaying code.
2. How can I use RAD Server for developing / migrating my multi-tier
applications.
3. What is the best way to develop cross platform.
4. How can I add Linux support in my Development environment.

May 17 - Event in France:


In France the sessions are presented to you in Paris
from 09:30-17:30 hrs.
http://embt.co/2qEzmOS

May 18 - Event in Belgium:


In Belgium the sessions are presented to you in Edegem
from 13:30-17:30 hrs
http://embt.co/2p5aHSA

May 19 - Event in The Netherlands:


In The Netherlands the sessions are presented in Bussum from
09:00-17:00 hrs.
http://embt.co/2pXNK8x

www.barnsten.com .
13 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE
CALLING LINUX COMMANDS FROM DELPHI PAGE 1/2
WORK
BY CRAIGWITH WINEHQ,
CHAPMAN WHAT IS IT? PAGE 3/4
BEFORE YOU CAN MAKE USE *NOTE* You should see the
of the following articles you must at least have complete video.
installed Delphi’s version Tokyo and follow the In this video I am assuming you are
installing requirements. These requirements already configured to deploy an
are mentioned on page 17. application to Linux and understand
how to launch it from the Linux command line.
INTRODUCTION: If this is not the case, see my earlier posts
One of the great features of Linux is that you can do
covering these subjects:
just about anything from the command line.
If we’re able to gain access to command line http://chapmanworld.com/2017/02/28/
instructions from our Delphi applications, this will embarcadero-delphi-linux-bootcamp/
give us a very powerful API for the system.
In this video I’m going to show you how to access http://chapmanworld.com/2016/12/29/con
the Linux command line from your Delphi figure-delphi-and-redhat-or-ubuntu-
applications. for-linux-development/

See the Video at: http://chapmanworld.com/2017/04/06/calling-linux-commands-from-delphi/

14 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE


CALLING LINUX COMMANDS FROM DELPHI PAGE 2/2
program myls;
{$APPTYPE CONSOLE}
THE COMPLETE CODE OF THE PROJECT: {$R *.res}

uses
System.SysUtils,
Posix.Base,
Posix.Fcntl;

type
TStreamHandle = pointer;

/// <summary>
/// Man Page: http://man7.org/linux/man-pages/man3/popen.3.html
/// </summary>
function popen(const command: MarshaledAString;
const _type: MarshaledAString):
TStreamHandle;
cdecl; external libc name _PU + 'popen';
/// <summary>
/// Man Page: http://man7.org/linux/man-pages/man3/pclose.3p.html
/// </summary>
function pclose(filehandle: TStreamHandle): int32;
cdecl;
external libc name _PU + 'pclose';
/// <summary>
/// Man Page: http://man7.org/linux/man-pages/man3/fgets.3p.html
/// </summary>
function fgets(buffer: pointer; size: int32;
Stream: TStreamHAndle): pointer;
cdecl;
external libc name _PU + 'fgets';
/// <summary>
/// Utility function to return a buffer of ASCII-Z data as a string.
/// </summary>
function BufferToString( Buffer: pointer;
MaxSize: uint32 ): string;
var cursor: ^uint8; EndOfBuffer: nativeuint;
begin
Result := '';
if not assigned(Buffer) then begin
exit;
end;
cursor := Buffer;
EndOfBuffer := NativeUint(cursor) + MaxSize;
while (NativeUint(cursor)<EndOfBuffer)
and (cursor^<>0) do begin
Result := Result + chr(cursor^);
cursor := pointer( succ(NativeUInt(cursor)) );
end;
end;

var Handle: TStreamHandle;


Data: array[0..511] of uint8;
begin
try
Handle := popen('/bin/ls -lart','r');
try
while fgets(@data[0],Sizeof(Data),Handle)<>nil do
begin
Write(BufferToString(@Data[0],sizeof(Data)));
end;
finally
pclose(Handle);
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

Issue Nr 3 2017 BLAISE PASCAL MAGAZINE 15


CREATING A LINUX DAEMON (SERVICE) IN DELPHI. PAGE 1/2
BY CRAIG CHAPMAN ·
program Project1;
BEFORE YOU CAN MAKE USE
of the following articles you must at least have {$APPTYPE CONSOLE}
installed Delphi’s version Tokyo and follow the
{$R *.res}
installing requirements. These requirements
are mentioned on page 17 uses
System.SysUtils,
INTRODUCTION: Posix.Unistd;
With the introduction of the Linux target for
Delphi, a wide range of possibilities are opened begin
up to Delphi developers, to create Linux server try
if fork()<>0 then begin
applications. Unfortunately there are currently exit;
a limited number of project types available end;
from the RAD Studio IDE, and those do not while true do sleep(100);
include creating a service (or Daemon as it’s except
called in the Linux world). on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Under the Linux operating system, a Daemon is
simply an executable which continues running while
ignoring the standard input and output streams. Now, when you attempt to run this application
That-is, they do not accept input from the keyboard, from the command line, it will immediately exit.
and they do not present data to the screen (except in It will appear as though the application did nothing.
special circumstances). So an application which simply However, issue the instruction: ps -e
continues to run in the background without using the
standard input and output, is a Daemon. and you should see something like this:

One other property of a Daemon


application is that it runs in the
background. Applications run
from the terminal window in Linux
are provided with the standard
input and output streams, and
therefore the terminal becomes
unusable until the application
terminates. To demonstrate, create a
new command line application and set its source You’ll notice that “Project1” is actually still running,
code to read as follows… it’s simply been moved to the background.
As a side note, you can kill the application by making a
program Project1;
{$APPTYPE CONSOLE} note of its process ID (in my case 16898 from the
screenshot) and issuing the appropriate kill
{$R *.res} instruction: kill 16898

uses You could now replace the infinite while loop with
System.SysUtils; whatever application logic you need within your
daemon.
begin
try HOW DOES THIS WORK?
while true do sleep(100);
except The key to this working is the “fork()” method, which
on E: Exception do can be found in the unit “Posix.Unistd”. The fork
Writeln(E.ClassName, ': ', E.Message); method is a part of the standard posix API, who’s
end; documentation may be found here:
end. https://linux.die.net/man/3/fork

What fork does is to create a copy of which-ever


If you run this application from the command line,
process calls it, in our case Project1.
you’ll find that it enters into an infinite loop (the while
So at the moment fork is called, our application
loop), and you do not regain control of the terminal
execution either continues in the originating process,
window until you issue [CTRL] + [C] to abort the
or it continues in the copy process.
application.
Let’s now turn this application into a Daemon. Alter
your source code to read as follows…

16 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE


CREATING A LINUX DAEMON (SERVICE) IN DELPHI. PAGE 2/2

The return value of the call to fork tells us which INSTALLING LINUX SDK
instance of the process we are executing in. A return To install Linux SDK on Ubuntu,
first you have to add the Repository.
value of zero tells us that we are executing in the child 1. Right-click your desktop and select
process (the copy), and any other value tells us that we Open Terminal.
are executing in the parent process (the originator). In 2. To add a repository, type sudo
fact, when the return of fork is not zero, it is the process add-apt-repository ppa:
ID of the newly created child process. We simply call ubuntu-sdk-team/ppa in the terminal.
‘exit’ to exit the application when we determine that we 3. Press Enter. Now, your repository is added.
4. To install Linux SDK , type sudo apt install
are in the parent process, allowing the child copy to
ubuntu-sdk in the terminal.
proceed on to the infinite while loop. 5. Press Enter and wait until the SKD is
Conclusion installed.
At this point you are able to create daemon applications. It may take a few minutes to complete.
You may wish to research the Linux (posix) API’s in a little
more detail to see how to handle ‘signals’ which may be TO INSTALL LINUX SDK ON RED HAT:
used to gracefully terminate your application, rather than 1. Right-click your desktop and select
using the brute force termination of a kill instruction. You Open Terminal.
might also want some way to communicate with your 2. Type yum groupinstall 'Development Tools'
daemon process. There are many ways to communicate in the terminal.
with the process, but possibly the most familiar to a Delphi 3. Press Enter and wait until the SKD is
developer would be to use TCP/IP. Turning your service installed. It may take a few minutes to
into a TCP/IP server would allow you to send data and complete.
instructions to it.
PREPARING YOUR LINUX MACHINE
Regardless of what you decide to do with your daemon, I To create Linux applications, you need to add
hope you found this brief instructional useful. a virtual machine PAServer:
1. Find the LinuxPAServer19.0.tar.gz file
in the following location:
C:\Program Files(x86)\Embarcadero\
Thanks for reading! Studio\19.0\PAServer
2. Unpack the LinuxPAServer19.0.tar.gz
file.

LINUX APPLICATION DEVELOPMENT To use the System.zLib uni t on Red Hat:


1. Install the zLib-devel library to your
RAD Studio allows you to develop applications RHEL machine with the following
for the target Linux platform. Using RAD command: sudo yum install zlib-devel
Studio installed on Windows machine, you 2. Update the local SDK cache.
can create a 64-bit Linux application and
deploy it to the Linux machine. Developing To use the System.zLib unit on Ubuntu:
Linux applications is similar to creating 1. Install the zLib-devel library to your
Ubuntu machine with the following
Android apps in RAD Studio. However, for
command:
Linux, you cannot create the visual VCL and sudo apt-get install zlib1g-dev
FireMonkey applications. Here is the list of 2. Update the local SDK cache.
apps that are available for the Linux platform:
CREATING A CONNECTION PROFILE
• Console Application 1. Right-click the folder where you have
• EMS Package (RAD Server) unpacked the LinuxPAServer19.0.tar.gz file,
• DataSnap and then click Open in Terminal.
• DataSnap WebBroker 2. Enter ./paserver in terminal.
• WebBroker Then, press Enter.
• Dynamic-link Libraries 3. On the Tools > Options > Environment
• DUnitX Project Options > Connection Profile Manager page,
click the Add button. The Create a
PREREQUISITES
Connection Profile wizard opens.
To develop Linux applications, you need to have
4. On the Profile information page,
the RAD Studio installed on the Windows PC as
enter the following:
well as a machine with the Linux platform
> In the Profile name field, enter the
(can be a virtual machine).
needed name.
PREPARING YOUR LINUX > In the Platform field, click the 64-bit
DEVELOPMENT ENVIRONMENT Linux platform.
To prepare your development > Click Next.
system (PC) and Linux machine for 5. On the Remote machine information page,
application development with RAD Studio: enter the following:
1. Install RAD Studi o on your Windows PC > In the Remote machine field, enter the
2. Install Linux SDK IP address or Machine name.
3. Prepare your Linux machine > Click Test Connection to check if the
4. Create a Connection profile values are valid.
5. Add the installed SDK to the RAD Studio 6. Click Finish.
6. Run PAServer on LINUX machine

Issue Nr 3 2017 BLAISE PASCAL MAGAZINE 17


LINUX APPLICATION DEVELOPMENT PAGINA 2/2

ADDING THE INSTALLED SDK TO RAD STUDIO RUNNING PASERVER ON LINUX


1. On the Tools > Options > Environment MACHINE
Options > SDK Manager page, To run the PAServer on your Linux
click the Add button. machine:
> Open the terminal, and then run
2. In the Add a New SDK dialog, PAServer in the terminal.
enter the following:
Developing Your Application
> In the Select a platform field, 1. Select the project type for your Delphi
click 64-bit Linux. application.
> In the Select a profile to connect, · File > New > Other > Delphi Projects:
select a created profile from the · Console Application
drop-down list. · DUnitX Project
> In the Select an SDK version field, · Dynamic-link Library
the name of the SDK that you just · Package
installed will automatically appear. · File > New > Other > Delphi Projects >
If you have not installed SDK previously, DataSnap Server:
you can do it on this step. · DataSnap Server
If you have already installed SDK, · DataSnap WebBroker
move on to the next step. · File > New > Other > Delphi Projects >
WebBroker > WebBroker Application

V
X CL
M
F

FNC

V
X CL
M
F

FNC

PASCON 2017
19 SEPTEMBER

18 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE


CREATING A TODO LIST WITH FIRE DAC MEMTABLE PAGE 1/9
BY DETLEF OVERBEEK
starter expert DX Delphi

CREATING A TODO LIST USING THE


FIREDAC EQUIVALENT OF THE CLIENT
DATASET: TFDMEMTABLE.
memtable
This is the second article in this series to find out
what the differences are and eventual advantages.
We noticed that there are a lot of TClientDataset-like
tables. First of all the main use of TFDMEMTABLE:
it can be used as an in-memory table and is therefore
very easy to use in smaller applications
to help you build your programs.

TFDMEMTABLE USABILITY
Whether you’re looking for a single-file, single-
user database or if you need to store some
application specific data but don't want to use a
larger equivalent or use something like the
registry or even an Ini-File, this sort of Data
registering table is just what you need:
it’s easy to handle and maintain.
You do not need to invest in database
knowledge to be able to do what you want:
register some details, make them protected
(binary) – if necessary, or quickly and easily create a
list of files, articles or even save pictures.
Of course there are lots of other useful
things to do. In the first episode I explained the
basics for creating a DataSet, so we can repeat
this way of working, though there are some
differences in the FireDac MemTable that may
lead to faster data access and display.
It might be of interest that even a larger
database could be designed by using this as a
model design.
One of the great features is FireDAC’s ability to
use SQL. That is really quite an improvement To find the FDMemTable we are looking for select
relative to ClientDataSets: you can only filter the FireDAC tab or simply type in the Tool
them and from experience I noticed that then it is Palette-search box “memtable”. If you want to see
difficult to get the result you want. what is available on the FireDAC-tabs: first choose
If you want to have a deep dive into more FireDAC.
knowledge about this you could try Cary
Jensen: he's writing a book about FireDac and
you probably can study that. He also created a
video about this subject:
https://www.youtube.com/
watch?v=iNgHJakYWkU
Since we are interested in the basics of
this all: here you see the vast number of
components that are available
for FireDAC as a whole.
I think the founder and author Dmitry
Arefiev has done a great Job. (He even made it
possible to leave the BDE and update your BDE
projects to the future).

19 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE


CREATING A TODO LIST WITH FIRE DAC MEMTABLE PAGE 2/9

As in the first project of the


ClientDataSet simply add two
FDMemTables. Right-click on the
memtable and a list will appear very
similar to what we had before.
Now because I started with a copy
of the first project to stick to the original
build, I would like to reuse
the work I had done already: the
ClientDataSet still has the content
and structure of the fields etc. It might
not be necessary to create the fields
again and maybe even resolve the data.
So I open the Fields Editor and try to
find the fields by right-clicking on the
fields editor and selecting “add field” or
“add all fields”. That creates an error
message: (See Figure 3 at the bottom)

Figure 2: Figure 4: Opening the fields


FIREDAC editor, which of course is empty
Overview

Figure 3: Error message

20 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE


CREATING A TODO LIST WITH FIRE DAC MEMTABLE PAGE 3/9

--------------------------- Clone cursor is a very special function:


Error It does what it says and will clone a complete DataSet
---------------------------
[FireDAC][Comp][DS]-206. Cannot open dataset [FDMemTable3]. and even more.
A DataTable or a DataView must be supplied. Hint: if that is With TClientDataSet you can add, delete, change
TFDMemTable, use CreateDataSet or CloneCursor to open dataset. records and then process TClientDataSet.Delta.
---------------------------
OK That is possible with TFDMemTable as well.
--------------------------- TFDMemTable has similar functionality, but it
follows the Cached Updates model.
Figure 5: Readable content of Figure 3 - When the CachedUpdates property value is True,
HINT You can extract the content of messages by MemTable will record changes in the updates log.
clicking Ctrl+C To post updates to a database, you have to use
ApplyUpdates.
To merge the change log,
you have to call CommitUpdates

You can find the description of these


and other methods in the FireDAC
help files and examples in the
FireDAC\Samples\Comp
Layer\TFDMemTable folder.

Figure 6: Opening the New Fields


editor, which of course is empty

New Field responds with the New Field Editor, but


then we would need to create each field and I would
rather have a copy of at least the structure of the Fields.
So we must supply a DataSet, or use CloneCursor
to open the DataSet. CloneCursor so far is useless
since there is nothing in the DataSet.
We also could try to load from file. Let’s try to load
from file. The files are available since this project is
only a renamed copy of the first project
CDS_FD.dproj .

Figure 7: Right click -try the option


load from file

Figure 8: the path to the


So let’s open the file. Too bad! That’s not possible: an TODOlistUTF.xml
error message occurs again. The File cannot be read
with the FDMemTable.

Figure 9: The file cannot be read.

Issue Nr 3 2017 BLAISE PASCAL MAGAZINE


CREATING A TODO LIST WITH FIRE DAC MEMTABLE PAGE 4/9

The option of assigning a DataSet is still open.


Let’s try that.
I chose the ClientDataset1, and that is the way
to do it: the structure is read and extracted, now we can
add all fields. See the result at image 10.

Figure 10: assign the dataset gives result.


The structure of the chosen Dataset is read

Figure 11: Choose Edit DataSet

Figure 12: It Opens

Figure 13: Click the plus to add a new


record

22 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE


CREATING A TODO LIST WITH FIRE DAC MEMTABLE PAGE 5/9

Now that both FDMemTables have their structure I opened the DataEditor and added a line with
(of course both equal) we must go into details. some trial data. Now before we go any further we
Already here is a difference from the must run the program:
ClientDataSet: You can now edit the DataSet We want to make sure that ClientDataSet1 will
which is great. contain all the data.
Here you can (without running your project) add To take care of that we need to run the application and
your real data, which means you don’t have to then push the Button “VIEW ARCHIVE ON/OFF” .
run the program and test it. After that Click the button “RESTORE FROM ARCHIVE”
You can simply do it here. That is a great advantage. to make sure you haven't lost any details.
If the field sizes you have created are too big The Dataset has now all the content and all we need to
you can widen the editor and then correct the do is write that to the FDMemTable.
field’s width, so you can view all the fields you There is as we know no other solution than to
have created, and can make the width of the add all the content, because we could not read the
editor much smaller. Just try. ClientDataSet file where it all was registered.
Of course you can also clear all content. We will have to create a procedure for that and
Close the editor and we will find out what this after that save the file into the new files we are
looks like in your project. going to create for the FDMemTable.

Figure 14: Save your data

Figure 15: Example of data in the toDo-List

Issue Nr 3 2017 BLAISE PASCAL MAGAZINE 23


CREATING A TODO LIST WITH FIRE DAC MEMTABLE PAGE 6/9

Figure 16: All the “DONE” items are moved


to archive

First of all we need to set and create the file:


Right click on the MFDMemTable and the List will appear again. Choose “Save to file” for the FDMemTable1,
set the name to
ToDoList_FD_UTF.xml
and for the FDMemTable2 use the name
ToDoList_FD_BackUpUTF.xml. Now we have created the files.
To make use of the files:
The file is read in the OnCreate of the form and the in the OnClose is set. Because we need to use the
ClientDataSet we will have to add these files and remove them later when you do not need them any more.
See the following procedures.
procedure TF_TODO.FormCreate(Sender: TObject);
begin
F_Todo.Height:= 604;
// Client DataSet eventually to be removed
ClientDataSet1.LoadFromFile(ExtractFilePath(Application.exename)+'ToDoListUTF.xml');
ClientDataSet2.LoadFromFile(ExtractFilePath(Application.exename)+'ToDoListBackUpUTF.xml');
// FDMemTable
FDMemTable1.LoadFromFile(ExtractFilePath(Application.exename)+'ToDoList_FD_UTF.xml');
FDMemTable2.LoadFromFile(ExtractFilePath(Application.exename)+'ToDoList_FD_BackUpUTF.xml');

DateTimePicker1.DateTime := (Now);
DateTimePicker2.DateTime := (Now)
end;

procedure TF_TODO.FormClose(Sender: TObject; var Action: TCloseAction);


begin
// Client DataSet eventually to be removed
ClientDataSet1.MergeChangeLog;
ClientDataSet1.SaveToFile(ExtractFilePath(Application.exename) + 'ToDoListUTF.xml');
ClientDataSet2.MergeChangeLog;
ClientDataSet2.SaveToFile(ExtractFilePath(Application.exename) + 'ToDoListBackUpUTF.xml');
// FDMemTable
FDMemTable1.MergeChangeLog;
FDMemTable1.SaveToFile(ExtractFilePath(Application.exename) + 'ToDoList_FD_UTF.xml');
FDMemTable2.MergeChangeLog;
FDMemTable2.SaveToFile(ExtractFilePath(Application.exename) + 'ToDoList_FD_BackUpUTF.xml');
end;

24 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE


CREATING A TODO LIST WITH FIRE DAC MEMTABLE PAGE 7/9

After running the program. I found out that even as Figure 17: The storage does not work with
this format
the file specification is given as xml files,
it creates an error: Invalid binary storage

To make sure the other file extensions work I have Figure 18: This is the actual list of the file
tried to save and load them each separately. formats that actualy are available but do
Again an invalid binary storage when I chose not work. Probably an unsoved item
.json. I didn’t find any documentation on this. So the
upshot is that you can only use .fds . (A binary file).
This means that the file extension for saveto and read
must be changed for the OnCreate:
FDMemTable1.LoadFromFile(ExtractFilePath(Application.exename)+'ToDoList_FD_UTF.fds');
FDMemTable2.LoadFromFile(ExtractFilePath(Application.exename)+'ToDoList_FD_BackUpUTF.fds');

and for the OnClose:


FDMemTable1.MergeChangeLog;
FDMemTable1.SaveToFile(ExtractFilePath(Application.exename) + 'ToDoList_FD_UTF.fds');
FDMemTable2.MergeChangeLog;
FDMemTable2.SaveToFile(ExtractFilePath(Application.exename) + 'ToDoList_FD_BackUpUTF.fds');

I tried to find out how the auto -increment fields in


FireDac are handled:
I created an AI field of the type Auto
Increment. Usually this can become quite tricky
and therefore I looked into what Embarcadero says
about this.

FireDAC automatically recognizes a column of an


auto-incrementing data type, and defines it to
dtIntXxx, [caAutoInc, caReadOnly,
caAllowNull]. This leads to TField setup:

TField.DataType = ftAutoInc
(TFDAutoIncField) when dtInt32 or
dtUInt32;
otherwise one of the numeric ftXxxx data types;
TField.Required = False;
TField.ReadOnly = True;
TField.ProviderFlags = [pfInWhere],
or [pfInWhere, pfInKey] when the column is
part of a primary key.

FireDAC automatically recognizes limited sets of the


Firebird auto-incrementing columns, and sets them
to dtIntXxx, [caAutoInc, caAllowNull] when:
extended metadata is enabled;
a table has a BEFORE INSERT trigger;
the trigger depends on a single column and a
single generator. This column is recognized as auto-
incrementing.
So we do not have to take care of this mechanism for
this project.

Issue Nr 3 2017 BLAISE PASCAL MAGAZINE 25


CREATING A TODO LIST WITH FIRE DAC MEMTABLE PAGE 8/9

Figure 19: The test is done to collect records from the


clientdataset
Now we need to copy the records from the
ClientDataset to the FDMemtable.
The solution is rather simple: we need that only once
until it’s done and therefore give no extra
attention like design etc. I have added a Button1
and an extra navigator.
See figure at the top: Figure19.
Here is the Code for it:
procedure TF_TODO.Button1Click(Sender: TObject);
var i: integer;
begin
ClientDataSet1.First;
for i := 0 to ClientDataSet1.recordCount - 1 do
begin
FDMemTable1.Append;
FDMemTable1.FieldByName('Subject').AsString := ClientDataSet1.FieldByName('Subject').AsString ;
FDMemTable1.FieldByName('Done').AsString := ClientDataSet1.FieldByName('Done').AsString ;
FDMemTable1.FieldByName('Begin').AsDateTime := ClientDataSet1.FieldByName('Begin').AsDateTime ;
FDMemTable1.FieldByName('End').AsDateTime := ClientDataSet1.FieldByName('End').AsDateTime ;
FDMemTable1.FieldByName('Priority').AsString := ClientDataSet1.FieldByName('Priority').Text ;
FDMemTable1.FieldByName('Description').AsString :=
ClientDataSet1.FieldByName('Description').AsString;
ClientDataSet1.next
end;

end;

Issue Nr 3 2017 BLAISE PASCAL MAGAZINE 26


CREATING A TODO LIST WITH FIRE DAC MEMTABLE PAGE 9/9

Now we have the data inserted to the new Dataset. In the next article I will give you the details
That means we can have a look at how to about KBMMW.
create SQL statements on the DataSet. I made contact with Kim Madsen and he told me
Before you do that you should delete all the testing the following:
items like Button1 and the extra grid. Eventually you
can exclude the procedure of the button and remove the You can use both filter and SQL.
ClientDataSets etc. But be cautious, make a copy of You would use TkbmMemSQL if you want to query
your project!!! using SQL.
Simply deleting the ClientDataSets is an option. In FPC which does not have any filter TDataset
The IDE will warn you many times, and then you can mechanism, kbmMemTable automatically uses
alter the statements of whatever is wrong. SQL syntax (WHERE clause) for filtering
If you want to do it carefully you could iterate through You can do regular SQL queries on kbmMemTable.
the Code and call F3, so you will find all the instances It doesn’t support joins though, but otherwise
without having them deleted. most other SELECT, INSERT, DELETE and UPDATE
statements
NOW TO THE PART OF THE SQL STATEMENTS.
I have tried to use the local SQL.
But you have to install SQLLite for it and there is no
way to use the SQLLite on the FDMemTable itself.
You have to create or use a real - even if it is only small -
Database. You can query that one and put the results
into your FDMemtable
So we are left with completing the filter method .
And I have said enough about that in the first project.
So the SQL on your FDMemTable does not work.
You can import from a database but not the other way
around.

COMPONENTS
DEVELOPERS 4 Android logcat tool
People developing for Android know that checking it’s logfiles is a must, debugging any new
application. One can do it directly using adb.exe with the logcat command, but it outputs data to the
console and is generally not really very useful due to the typical high amount of data produced by it.
Android Studio contains a log viewer application, but alas that requires installation of the complete
Android Studio, which perhaps is overkill to check the contents of the log.
Some 3rdparty options also exists. Unfortunately they typically require Java or .Net to be available, and
most of them are trials or limited commercials.
So when nothing exists that fulfills the requirements, the option is to make it yourself.
Thats what I did. May I present kbmLogCat!

su
sb
sc
r
F ibe at h
R ttps:
E //com
E pon
e
T nts4de
O velop
O ers.w
L ordpre
s. s
27
Issue Nr 2 2017 BLAISE PASCAL MAGAZINE
co 27
Issue Nr 3 2017 BLAISE PASCAL MAGAZINE
m
/
28 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE
BLUETOOTH CONTROLLED ROBOT WITH VISUINO AND DELPH PAGE 1 / 8
PART 2 - DELPHI BY BOIAN MITOV
starter expert
- + We will need 4 buttons to control the movement
of the robot. In the search box of the “Tool
In the previous article, I showed you how you Palette” type “button”. Select the TButton
can use Visuino to program Elegoo Robot to be component:
controlled over Bluetooth Classic. In this article
I will show you how you can use Delphi to write
Android app to control the Robot. The App
should also work on iOS, MAC OS, and Windows
systems with Bluetooth.
Start RAD Studio, and create a new Multi-Device
Application Delphi. Select Blank application:

Drop 4 Buttons on the form, and arrange them as


shown on the picture. Set their captions to
“Forward”, “Left”, “Right” and “Back”.
Select the 4 components.

-
+
First we need to add a TBluetooth to connect to
the “HC-06” module. In the search box of the “Tool
Palette” type “blue”. Select the TBluetooth
component:

In the Object Inspector set the Enabled property


for all of them to “False”:

And drop it on the form. In the Object Inspector


set the Enabled property to True:

29
BLUETOOTH CONTROLLED ROBOT WITH VISUINO AND DELPHI - +
PART 2 - DELPHI PAGE 2 / 8

We will add a label to display a message while In the event handler call
connecting to the HC-06 module. “Bluetooth1.DiscoverDevices” and specify some
In the search box of the “Tool Palette” type time interval, as example 5 seconds:
“label”. Select the TLabel component:
procedure TForm1.FormCreate(Sender: TObject);
begin
Bluetooth1.DiscoverDevices( 5000 );
end;

When we connect we will use a socket. We will


need to declare an FSocket field in the Tform:
TForm1 = class(TForm)


private
FSocket : TBluetoothSocket;
Drop the Label on the form, and set the value …
of the “Text” property to “Connecting”: …

Next we need to write the code to handle the


discovered devices. Switch to the Form Editor and
select the Bluetooth1 component.
In the “Object Inspector” switch to the Events tab.
Select the OnDiscoveryEnd and double click on
the value editor to generate event handler:

In the event handler write the following code:


procedure TForm1.Bluetooth1DiscoveryEnd(const Sender: TObject;
const ADeviceList: TBluetoothDeviceList);
Double click on the form to generate var ADevice : TBluetoothDevice; AService : TBluetoothService;
OnCreate event handler: begin
for ADevice in ADeviceList do
begin
if( ADevice.DeviceName = 'HC-06' ) then
begin
Bluetooth1.Pair( ADevice );
for AService in ADevice.LastServiceList do
begin
FSocket := ADevice.CreateClientSocket( AService.UUID, False );
if( FSocket <> NIL ) then
begin
FSocket.Connect();
Label1.Visible := False;
Button1.Enabled := True;
Button2.Enabled := True;
Button3.Enabled := True;
Button4.Enabled := True;
Break;
end;
end;
Break;
Double click on the form to generate end;
OnCreate event handler: end;
end;

30 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE


BLUETOOTH CONTROLLED ROBOT WITH VISUINO AND DELPHI - +
PART 2 - DELPHI PAGE 3 / 8
First we need to iterate trough the discovered
devices: for ADevice in ADeviceList do In the event handler write the following
code to send the “f” character through the socket:
and check if the ADevice is the “HC-06” module:
if( ADevice.DeviceName = 'HC-06' ) then procedure TForm1.Button1MouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Single);
If this is the device, we will pair with it:
Bluetooth1.Pair( ADevice ); begin
FSocket.SendData( TEncoding.UTF8.GetBytes( 'f' ));
and will iterate trough the services to try to end;
create a socket:
for AService in ADevice.LastServiceList do
begin
Switch to the Form Editor. In the “Object
FSocket := ADevice.CreateClientSocket(
AService.UUID, False ); Inspector” select the OnMouseUp and double click
if( FSocket <> NIL ) then on the value editor to generate event handler:

If we can create a socket, we will connect, hide the


Label1, and enable the buttons, then exit the
FSocket.Connect(); services iteration loop:
Label1.Visible := False;
The next step is
Button1.Enabled := True;
Button2.Enabled := True; not needed for
Button3.Enabled := True; Android apps,
Button4.Enabled := True; but since the code
Break; In the event handler write the following code to
is cross-platform
send the “F” character through the socket:
we will do it.
Switch to the Form Editor and select the procedure TForm1.Button1MouseUp(Sender: Tobject;
Button: TMouseButton; Shift: TShiftState; X, Y: Single);
Form. In the “Object Inspector” select the begin
OnDestroy and double click on the value FSocket.SendData( TEncoding.UTF8.GetBytes( 'F' ));
editor to generate event handler: end;

In the event handler,


Switch to the Form Editor and select the Button2
call FreeAndNil(
(Left) component. In the “Object Inspector” select
FSocket ) to free
the OnMouseDown and double click on the value
and set to NIL
editor to generate event handler:
the FSocket:
procedure TForm1.FormDestroy(
Sender: TObject);
begin
FreeAndNil( FSocket );
end;

Now we can start writing the code for each of


the buttons. For each button we will send one
lower case character when the button is
pressed and the same character in upper case
when it is released. For the 4 directions we will
use “f”, “l”, “r”, and “b” characters. Switch to the
Form Editor and select the Button1 (Forward)
component. In the “Object Inspector” select the
OnMouseDown and double click on the value
editor to generate event handler:

In the event handler write the following code to


send the “l” character through the socket:
procedure TForm1.Button2MouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
FSocket.SendData( TEncoding.UTF8.GetBytes( 'l' ));
end;

Issue Nr 3 31
BLUETOOTH CONTROLLED ROBOT WITH VISUINO AND DELPHI - +
PART 2 - DELPHI PAGE 4 / 8

Switch to the Form Editor. In the “Object Inspector” In the event handler write the following code to
select the OnMouseUp and double click on the value send the “r” character through the socket:
editor to generate event handler: procedure TForm1.Button3MouseDown(Sender: TObject;
Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
begin
FSocket.SendData( TEncoding.UTF8.GetBytes( 'r' ));
end;

Switch to the Form Editor. In the “Object Inspector”


select the OnMouseUp and double click on the value
editor to generate event handler:

In the event handler write the following code to send the


“L” character through the socket:
procedure TForm1.Button2MouseUp(Sender: TObject; Button:
TMouseButton; Shift: TShiftState; X, Y: Single);
begin
FSocket.SendData( TEncoding.UTF8.GetBytes( 'L' ));
end;

Switch to the Form Editor and select the Button3 In the event handler write the following code to
(Right) component. In the “Object Inspector” select send the “R” character through the socket:
the OnMouseDown and double click on the value procedure TForm1.Button3MouseUp(Sender: Tobject;
editor to generate event handler: Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
begin
FSocket.SendData( TEncoding.UTF8.GetBytes( 'R' ));
end;

Switch to the Form Editor and select the Button4


(Back) component. In the “Object Inspector” select
the OnMouseDown and double click on the value
editor to generate event handler:

32 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE


BLUETOOTH CONTROLLED ROBOT WITH VISUINO AND DELPHI - +
PART 2 - DELPHI PAGE 5 / 8
In the event handler write the following code to
send the “b” character through the socket:
procedure TForm1.Button4MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
begin
FSocket.SendData( TEncoding.UTF8.GetBytes( 'b' ));
end;

Switch to the Form Editor. In the “Object In the RAD Studio IDE press the Ctrl+Shift+F11 on
Inspector” select the OnMouseUp and double click the keyboard to open the Project Options dialog.
on the value editor to generate event handler: In the “Project Options Dialog” select the “User
Permissions”. For target select “All configurations
– Android platform”. Set the “Bluetooth”, and
“Bluetooth admin” permissions to True:

In the event handler write the following code to


send the “B” character through the socket:

procedure TForm1.Button4MouseUp(Sender: TObject; Button: TMouseButton;


Shift: TShiftState; X, Y: Single);
begin
FSocket.SendData( TEncoding.UTF8.GetBytes( 'B' ));
end;

Issue Nr 3 2017 BLAISE PASCAL MAGAZINE 33


BLUETOOTH CONTROLLED ROBOT WITH VISUINO AND DELPHI - +
PART 2 - DELPHI PAGE 6 / 8

Now you can compile and deploy the App to the


Android phone. Before you run the app on the
phone, make sure the Robot is turned on, so the
And after few seconds if everything goes well, the
HC-06 will be powered, and the phone can connect
“Connecting” message will disappear, and the
to it. In case the Bluetooth is not enabled, you may
buttons will be enabled:
need to allow it:

The app will try to connect to the Robot: Now you can use the 4 buttons to drive the robot.
Pressing the Forward button will tell the robot to
move forward:

34 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE


BLUETOOTH CONTROLLED ROBOT WITH VISUINO AND DELPHI - +
PART 2 - DELPHI PAGE 7 / 8
Pressing the Left button will tell
Here is the complete Delphi source code to control the Robot:
the robot to turn Left:
unit Unit1;

interface

uses
System.SysUtils, System.Types, System.UITypes, System.Classes,
System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics,
FMX.Dialogs, System.Bluetooth, System.Bluetooth.Components,
FMX.Controls.Presentation, FMX.StdCtrls;

type
TForm1 = class(TForm)
Bluetooth1: TBluetooth;
Button1: TButton;
Button2: TButton;
Button4: TButton;
Button3: TButton;
Label1: TLabel;
procedure FormCreate(Sender: TObject);
procedure Bluetooth1DiscoveryEnd(const Sender: TObject;
const ADeviceList: TBluetoothDeviceList);
Pressing the Right button will tell procedure FormDestroy(Sender: TObject);
the robot to turn Right: procedure Button1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
procedure Button1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
procedure Button2MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
procedure Button2MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
procedure Button3MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
procedure Button3MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
procedure Button4MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
procedure Button4MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
private
FSocket : TBluetoothSocket;

end;

var
Form1: TForm1;
And pressing the Back button will
tell the robot to move backward: implementation

{$R *.fmx}

Issue Nr 3 2017 BLAISE PASCAL MAGAZINE 35


BLUETOOTH CONTROLLED ROBOT WITH VISUINO AND DELPHI - +
PART 2 - DELPHI PAGE 8 / 8
procedure TForm1.Bluetooth1DiscoveryEnd(
const Sender: TObject;
const ADeviceList: TBluetoothDeviceList); procedure TForm1.Button2MouseUp(
var Sender: TObject; Button: TMouseButton;
ADevice : TBluetoothDevice; Shift: TShiftState; X, Y: Single);
AService : TBluetoothService; begin
begin FSocket.SendData( TEncoding.UTF8.GetBytes( 'L' ));
for ADevice in ADeviceList do end;
begin
procedure TForm1.Button3MouseDown(
if( ADevice.DeviceName = 'HC-06' ) then Sender: TObject; Button: TMouseButton;
begin Shift: TShiftState; X, Y: Single);
Bluetooth1.Pair( ADevice ); begin
for AService in ADevice.LastServiceList do FSocket.SendData( TEncoding.UTF8.GetBytes( 'r' ));
begin
end;
FSocket := ADevice.CreateClientSocket(
AService.UUID, False ); procedure TForm1.Button3MouseUp(
if( FSocket <> NIL ) then Sender: TObject; Button: TMouseButton;
begin Shift: TShiftState; X, Y: Single);
FSocket.Connect(); begin
Label1.Visible := False; FSocket.SendData( TEncoding.UTF8.GetBytes( 'R' ));
Button1.Enabled := True;
end;
Button2.Enabled := True;
Button3.Enabled := True; procedure TForm1.Button4MouseDown(
Button4.Enabled := True; Sender: TObject; Button: TMouseButton;
Break; Shift: TShiftState; X, Y: Single);
end; begin
end; FSocket.SendData( TEncoding.UTF8.GetBytes( 'b' ));
Break; end;
end; procedure TForm1.Button4MouseUp(
end; Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
end; begin
FSocket.SendData( TEncoding.UTF8.GetBytes( 'B' ));
procedure TForm1.Button1MouseDown( end;
Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single); procedure TForm1.FormCreate(Sender: TObject);
begin begin
FSocket.SendData( TEncoding.UTF8.GetBytes( 'f' )); Bluetooth1.DiscoverDevices( 5000 );
end; end;

procedure TForm1.Button1MouseUp( procedure TForm1.FormDestroy(Sender: TObject);


Sender: TObject; Button: TMouseButton; begin
Shift: TShiftState; X, Y: Single); FreeAndNil( FSocket );
begin end;
FSocket.SendData( TEncoding.UTF8.GetBytes( 'F' ));
end; CONCLUSION
In this Article I showed you how you can use
procedure TForm1.Button2MouseDown( Delphi to write an Android app to control a Robot
Sender: TObject; Button: TMouseButton; over Bluetooth Classic. In the following articles I
Shift: TShiftState; X, Y: Single); will show you how you can develop and control
begin more complex robots with the help of Visuino and
FSocket.SendData( TEncoding.UTF8.GetBytes( 'l' )); Delphi, and how to add Computer Vision and
end; Artificial Intelligence to your Robots.

36 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE


KBMMW ORM (OBJECT RELATIONAL MODEL) PAGE 1/7
BY KIM BO MADSEN
Digging for w ORMs
have of performance reasons.
kbmMW ORM and SQL
rewriting explained in
kbmMW_Field tells the framework that the
beautiful combination following property or field is to be persisted, and under
with kbmMW what data storage field name it will be persisted.
Smart services and Further it describes how that data storage field should
kbmMW Smart clients. be, type, size, precision and more.
The ID property has more information attached to
it. The framework is being told that this field should be
the primary field (a primary field should always be
declared), and that the fields value is automatically
generated upon storage in the database.
In this case the value will contain a so called short
GUID*, which is a shortened version of a regular
GUID.
I’ve promised to release v5 with a new very cool
The field refers to a generator. kbmMW ORM
feature (in beta), which I’ll explain a bit about now:
supports a number of generators:
kbmMW ORM (object relational model). • GUID – Returns a GUID formatted as a regular
Whats an ORM? It can be interpreted in multiple GUID
ways. In our way we interpret it as a way to persist, { 123e4567-e89b-12d3-a456-426655440000 }
query and manipulate objects to and from a data • SHORTGUID – Returns a short GUID where
storage of some sort. braces and dashes are missing:
We have focused more on the object persistence 123E4567E89B12D3A456426655440000
part of the ORM, than on the automatic relational • SEQUENCE – Returns next unique number from
management of master/detail scenarios. There are a sequence. Provide name of sequencer in sequence
good and bad things about fully automatic relational property and optional sequencestart property
cascade type storage and data manipulation. In some (not supported by all databases!)
cases the bad things outweighs the good, and • DATETIME – Returns a date time value,
sometimes visa versa. formatted according to the dateFormat property.
*A universally unique identifier (UUID) is a 128-bit
SHOW ME THE CODE! number used to identify information in computer systems.
The term globally unique identifier (GUID) is also used.
Well.. it’s simple… create an object, tell the framework
When generated according to the standard methods, UUIDs
to persist the object and you have stored your data. It are for practical purposes unique, without depending for
sounds too easy doesn’t it? It really_is_easy. their uniqueness on a central registration authority or
This is an ordinary class, for a TPerson. coordination between the parties generating them, unlike
This example contains some funny types, like most other numbering schemes. While the probability that a
kbmMWNullable, but thats just a convenience that UUID will be duplicated is not zero, it is so close to zero as
to be negligible.
makes some things even easier. However you could
Thus, anyone can create a UUID and use it to identify
choose to use plain simple regular native strings and something with near certainty that the identifier does not
integer and other native types. duplicate one that has already been, or will be, created to
What you may also notice are some custom attributes identify something else. Information labeled with UUIDs by
named kbmMW_Table and kbmMW_Field. independent parties can therefore be later combined into a
kbmMW_Table tells the kbmMW framework single database, or transmitted on the same channel, without
needing to resolve conflicts between identifiers.
something about how objects of this type are to be
Adoption of UUIDs and GUIDs is widespread, with many
stored in data storage. It includes the desired table name computing platforms providing support for generating them,
and optional additional indexes that would be nice to and for parsing their textual representation.
type
[kbmMW_Table('name:person, index:{name:i1,field:name,descending:false},
index:{name:i2,unique:true,fields:[{name:name,descending:true},{name:age}]')]
TPerson = class
public
FID:kbmMWNullable;
FName:kbmMWNullable;
FAddress:kbmMWNullable;
FAge:kbmMWNullable;
published
[kbmMW_Field('name:id, primary:true, generator:shortGuid',ftString,40)]
property ID:kbmMWNullable read FID write FID;
[kbmMW_Field('name:name',ftWideString,50)]
property FullName:kbmMWNullable read FName write FName;
[kbmMW_Field('name:address',ftWideString,50)]
property Address:kbmMWNullable read FAddress write FAddress;
[kbmMW_Field('name:age',ftInteger)]
property Age:kbmMWNullable read FAge write FAge;
end;
initialization
kbmMWRegisterKnownClasses([TPerson,TObjectList]);
end.
Issue Nr 3 2017 BLAISE PASCAL MAGAZINE COMPONENTS
DEVELOPERS 4 37
KBMMW ORM (OBJECT RELATIONAL MODEL) PAGE 2/7 COMPONENTS
DEVELOPERS 4
In addition the field type can be set to ftAutoinc, CreateTable can take an array of classes, if
just like any regular TDataset based databases. In multiple tables should be created in one go.
this case the database will generate a unique number, You can test if a table (or tables) already exists
which the framework will pickup automatically and using
return into the persisted object. if not orm.ExistsTable(TPerson) then
....
So how do we get around to actually persist
anything?
CreateTable will only create tables if they are
First we create an ORM instance, linking it to a
missing, but using ExistsTable may make it easier
connection pool. The connection pool is responsible for
for you, to know when you can prepopulate newly
the actual connection to the data storage.
created table with whatever bootstrap information you
orm:TkbmMWORM; like to put into them from start.
... You can use DeleteTable(TPerson) to delete the
orm:=TkbmMWORM.Create(kbmMWSQLiteConnectio table and its data, and you can use PurgeTable
nPool1);
to leave the table, but remove its contents.
In this case we have chosen a kbmMWSQLite How do we put some data into the table?
connection pool, but most databases and connection We do that by instantiating instances of TPerson like
pools are supported. I will write a bit more about that this p:=TPerson.Create;
later. p.FullName:='Hans Hansen';
The connection pool and a metadata component p.Address:='Hans Home Address 1';
matching it (kbmMW will automatically attempt to find p.Age:=65;
the right one if none has been setup) was put on the orm.Persist(p);
form at design time.
After Persist has run successfully, you
will find that p.ID has a value, even
though we didn’t put one originally.
If an object do not have a value for the
field that was marked as primary, then
that object is understood as a new
object.
Thus in the future (for as long as we
keep the instance pointed to by p) alive,
kbmMW ORM will know that future
persisting of it means that the existing
record in the table will be updated
rather than a new added.
To add more records, simply create
One single thing you need to setup on the connection more instances and persist them.
pool, is the database name, and if you want the system Remember that you own the TPerson instances that
to automatically create the SQLite database if none you create, and can do with them what you want,
exists, then also set its SQLiteOptions property to which also means that you are responsible for freeing
contain mwsloAutoCreateDB , and set its MetaData them again when you no longer need them.
to point on the kbmMWSQLiteMetaData1 component. Freeing the instance will not affect what has been
The metadata component is actually not mandatory persisted.
to place on the form, and is only shown for If you make changes in the object and want those
completeness. kbmMW will automatically generate a changes persisted, simply call orm.Persist(p);
metadata component instance that match the used on the object again.
connection pool, if none has been provided. If you want to delete an object from persistence,
As you may be able to see from the screendump (if you do orm.Delete(p);
you have been using kbmMW before), is that this form is
part of a simple n-tier server ORM sample which is You still have the p instance in memory, but it is now
included with v5. gone from storage.
Let’s look at the code again… we now want the ORM If you have a collection or an array of TPeople
to automatically create all relevant tables in the data you can Persist and Delete those the same way as
storage. already shown.
I’m talking about tables here, because that’s what Persisting data is no fun, unless you can retrieve
most people use for storing data, but it could also be them again at some point.
other types of storage, which have other concepts for kbmMW ORM supports a vast number of ways to
storage than tables. As long as a connection pool, with search and retrieve data via the various Query
accompanying metadata exists for that storage, it will methods.
work fine with kbmMW ORM. Let’s say you want to retrieve an object with a
Having the table created is as simple as this specific primary key
orm.CreateTable(TPerson); var
p:TPerson;
...
38 COMPONENTS
DEVELOPERS 4 p:=orm.Query<TPerson>('123e4567e89b12d3a456426655440000');
KBMMW ORM (OBJECT RELATIONAL MODEL) PAGE 3/7 COMPONENTS
DEVELOPERS 4
Now p will either be nil or point to a matching instance There is an exception to that, which is shown further
of TPerson. You own this object instance and may down.
free it when you want. For example if you want to get all deleted persons
Or if you know there is only 0 or 1 record in the from the dataset, to let the ORM actually delete those:
table, you can simply do p:=orm.Query<TPerson>; var
p:TObjectList<TPerson>;
Even if there are more records, only the first one will be ...
returned to you. It should obviously be used with p:=orm.ListFromDataset<TPerson>(ds,[usDeleted]);
common sense because it does request all records from orm.Delete(p);
the data storage, and they may come in any order. o.Free;
If you want a complete list of all persons known by
the data storage var If you want to know if data has changed (been inserted or
p:TObjectList<TPerson>; modified) in an object, you can
... if orm.HasChanged(p) then
p:=orm.QueryList<TPerson>; ...

Now… this is all nice and easy.. but what if I want to This is here where kbmMWNullable and
search for all people with a specific age? TkbmMWDateTime comes into play. Regular native
var types like string, integer etc. can’t hold the value Null,
p:TObjectList<TPerson>; and they also can’t hold any information about if their
... value has been changed. But if you wrap the types in
p:=orm.QueryList<TPerson>(['age'],[65]); kbmMWNullable, or use TkbmMWDateTime instead
of TDateTime, you suddenly have those abilities
Now only people of the age 65 is returned. which the framework can take advantage of.
Looking at the FullName property in our
var
p:TObjectList<TPerson>; TPerson object, you can see that it was wrapped in
... kbmMWNullable. That means we at any time can
p:=orm.QueryList<TPerson>(['age'],[65],mwoqoGT); request information about if it’s null, or modified like
this: (next page)
Now only people older than 65 is returned. If you only if p.FullName.IsNull then
wanted one of them you could have used ...
Query<TPerson> … instead of QueryList. if p.FullName.Modified then
You can specify multiple fields and values in your ...
search this way, but all are controlled by the given And we can clear its modified flag anytime if we need to
operator (default mwoqoEQ = equal). p.FullName.Modified:=False;
If you just wanted a count
And we can set its value to null
var i:integer;
p.FullName.Clear;
...
i:=orm.QueryCount<TPerson>(['age'],[65],mwoqoGT);
By default, all properties are marked as non modified
You can use various aggregates, like QueryCount, when they have been returned as a result of one of the
QuerySum, QueryAvg, QueryMin, QueryMax Query methods.
and QueryStdDev or the generic QueryAggregate This way, Persist and other UpdateIfChanged
method. Or if you really want the data as a TDataset / InsertIfChanged methods, will only resolve data
instead, you can do back to the data storage if any changes have been made
var to the data in the object. If you are using regular non
ds:TDataset; wrapped properties, then kbmMW ORM can only
... assume that the data may have changed, and it will
ds:=orm.QueryDataset<TPerson>(['age'],[65],mwoqoGT); attempt to update all provided objects regardless.
Most of the time, you really want to make more
This will return a dataset to, which you can use as any complex searches when querying for data. We have
other dataset. The dataset is however not directly chosen to build in a uniform SQL based query language
connected with the data storage behind, so changes in into kbmMW ORM which is not only easy to understand
the dataset needs to be either resolved using kbmMW’s for most, but is automatically translated on the fly, to
regular resolving mechanism (which is nice, but not the query language used by the data storage.
ORM), or by converting the dataset back to a set of
var
objects you can persist. o:TObjectList<TPerson>;
The field name given is the name your property is ...
known as. So if you would have searched on a name, o:=orm.QueryList<TPerson>('SELECT * FROM
you would have specified ‘FullName’ as the field name, uData.TPerson WHERE age>65 ORDER BY
rather than ‘name’ as it’s given in the data storage. FullName'));
With kbmMW ORM you must generally always refer
to the object’s property names.

Issue Nr 3 2017 BLAISE PASCAL MAGAZINE COMPONENTS


DEVELOPERS 4 39
KBMMW ORM (OBJECT RELATIONAL MODEL) PAGE 4/7 COMPONENTS
DEVELOPERS 4
Notice that you select from uData.TPerson. If a database specific metadata component (which takes
That is a so called qualified class name, which means care of the rewriting of SQL) do not exist, then kbmMW
that there is no doubt about which exact TPerson you ORM will attempt to fall back to a generic SQL
want to search on. You can have multiple TPerson metadata component, which will work in many cases.
classes defined in multiple units and that’s perfectly However if there is a special query you want to use,
legal. While querying on them, just remember always which kbmMW ORM SQL do not directly support, you can
to prefix the unit name. send it raw, provided your orm instance will allow it.
Also notice the ORDER BY part. It refers to To allow for custom native query statements, make
FullName which is the property in the TPerson class. sure that
The query parser also supports joins. But remember orm.QueryMode:=mwoqmNative or mwoqmMixed.
that only the fields that match the requested object type
will be returned. It is default mwoqmMW.
Setting it to native, prevents kbmMW ORM to rewrite
var
o:TPersonAccount; anything, and all query statements are considered
... native.
o:=orm.Query<TPersonAccount>('SELECT p.id, Setting it to mixed, will make kbmMW ORM assume
name, value FROM uData.TPerson p, that the query statements are kbmMW ORM SQL , but
uData.TAccount a WHERE a.PID=p.ID'); allow for you to put a # in front of a statement to send a
native statement in a query.
To have all data returned, you will have to have define
var
a TPersonAccount class somewhere which contains o:TObjectList<TPerson>;
minimum the fields ID, Name and Value. Excess ...
fields in the query will be silently ignored. o:=orm.QueryList(TPerson,'#SELECT * FROM Person');
[kbmMW_VirtualTable]
TPersonAccount = class Notice that the table name given, must match
private the table name in the data storage.
FID:string;
FName:kbmMWNullable<string>; BUT WHAT ABOUT N-TIER SCENARIOS?
FValue:kbmMWNullable<double>;
public I started out with telling about that this article was
[kbmMW_Field('name:id')] based on an n-tier ORM sample, so it makes sense
property ID:string read FID write FID; to show the remaining parts that enables a client to
query for people, update changes to them and
[kbmMW_Field('name:name')] delete them.
property Name:kbmMWNullable<string> read FName kbmMW v5 was from outset designed to be
write FName; the easiest to use n-tier product ever.
With it came the new smart service and smart
[kbmMW_Field('name:value')] clients, and what else makes sense than to use those
property Value:kbmMWNullable<double> read FValue
along with the ORM.
write FValue;
This makes it possible to create both a server and a
end;
client, requiring only a few lines of business code
almost totally eliminating boiler plate code (i.e. code
Notice that this class has been given the attribute
that does nothing for your business logic, but is required
kbmMW_VirtualTable. A virtual table is a table that
to glue things together).
do not exist in the data storage, and as such if you try to
Thus we will create a smart service with 3
CreateTable(TPersonAccount) an exception
methods, StorePersons, DeletePersons
will be thrown telling you that its a virtual table that is
and GetPersons. As we have put the orm
only meant for transient uses.
instance on the main form (Form1) we refer to
Also notice that in this case we have defined ID as a
that from here.
regular native string instead of wrapping it with
kbmMWNullable. As we will always have a value in
the ID property, we can safely choose not to use
kbmMWNullable, but it’s perfectly fine to wrap even
this one with kbmMWNullable to follow a simple rule
of thumb and not having to judge for each property
type.

The kbmMW ORM SQL syntax is fairly complete, and


sticking to it allow you to easily move your application
between the different database types supported by
kbmMW, in particular SQLite, Oracle, MSSQL,
PostgreSQL, MySQL and Interbase/Firebird. Also
many other databases are supported.

40 COMPONENTS
DEVELOPERS 4 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE
KBMMW ORM (OBJECT RELATIONAL MODEL) PAGE 5/7 COMPONENTS
DEVELOPERS 4
[kbmMW_Service('name:ORMDEMO')]
TkbmMWCustomService2 = class(TkbmMWCustomSmartService)
public
// This lists stores changes (but not removed items) in the list of persons.
[kbmMW_Method]
procedure StorePersons(const APersons:TObjectList<TPerson>);
// This lists deletes persons in the list.
[kbmMW_Method]
procedure DeletePersons(const APersons:TObjectList<TPerson>);
// This method returns a list of persons.
[kbmMW_Method]
function GetPersons:TObjectList<TPerson>;
end;

implementation

uses
Unit1;

{$R *.dfm}

procedure TkbmMWCustomService2.StorePersons(const APersons:TObjectList<TPerson>);


begin // The APersons list is owned by this function, and must be manually freed.
Form1.orm.Persist(APersons);
APersons.Free;
end;

procedure TkbmMWCustomService2.DeletePersons(const APersons:TObjectList<TPerson>);


begin // The APersons list is owned by this function, and must be manually freed.
Form1.orm.Delete(APersons);
APersons.Free;
end;

function TkbmMWCustomService2.GetPersons:TObjectList<TPerson>;
begin // Return all persons (people).
Result:=Form1.orm.QueryList<TPerson>;
end;

initialization
TkbmMWRTTI.EnableRTTI(TkbmMWCustomService2);

This is really really simple isn’t it? Only business logic code and nothing else.
In the FormCreate event handler of the main form Form1, where we have also put the orm creation, we
add // Register all services automatically.
// Services will only be autoregistered if they have a kbmMW_Service attribute.
server.AutoRegisterServices;

In the event handler of the Listen button on Form1 we write server.Active:=not server.Active;
That’s all!
NOW TO THE CLIENT.

Issue Nr 3 2017 BLAISE PASCAL MAGAZINE COMPONENTS


DEVELOPERS 4 41
KBMMW ORM (OBJECT RELATIONAL MODEL) PAGE 6/7 COMPONENTS
DEVELOPERS 4
The client has a connect button, a button requesting a list
of TPerson and a button to store changes made to
TPerson data.
In addition it contains a string grid and a
mtPersons kbmMemTable which are live bound
together along with the navigator.
Embarcadero’s live binding is in some ways nice, but
it is not terribly flexible, so it will only bind to native
object properties and fields. In our case we have
kbmMWNullable wrapped properties, which are
simple to use in code, but which are not understood by
livebinding.
This can be circumvented in a number of ways.
One is to add a number of alternative properties to the
TPerson object, which exposes the same data, but as
native types.
This could look like this
[kbmMW_Table('name:person, index:{name:i1,field:name,descending:false},
index:{name:i2,unique:true,fields:[{name:name,descending:true},{name:age}]')]
TPerson = class
private
FID:kbmMWNullable<string>;
FName:kbmMWNullable<string>;
FAddress:kbmMWNullable<string>;
FAge:kbmMWNullable<integer>;

function GetID:string;
procedure SetID(AValue:string);
function GetName:string;
procedure SetName(AValue:string);
function GetAddress:string;
procedure SetAddress(AValue:string);
function GetAge:integer;
procedure SetAge(AValue:integer);
published
[kbmMW_Field('name:id, primary:true, generator:shortGuid',ftString,40)]
property ID:kbmMWNullable<string> read FID write FID;

[kbmMW_Field('name:name',ftWideString,50)]
property FullName:kbmMWNullable<string> read FName write FName;

[kbmMW_Field('name:address',ftWideString,50)]
property Address:kbmMWNullable<string> read FAddress write FAddress;

[kbmMW_Field('name:age',ftInteger)]
property Age:kbmMWNullable<integer> read FAge write FAge;

{ One way of livebinding to kbmMWNullable or kbmMWDateTime fields is to add an alternative access to them.
LiveBinding only supports basic data types. Remember to kbmMW_Ignore the properties to tell kbmMW not to attempt to
stream them.
As kbmMWNullable and TkbmMWDateTime values retain information about nullability and modification,
it may be preferred to use these types over regular string, int, TDateTime etc. types.
If the number of properties of such types is large, writing many alternative setters and getters may become tedious.
Instead you can (as this sample shows in unit1.pas), convert the objects to a dataset (a proxy) which is easy to databind with.}

[kbmMW_Ignore]
property LBID:string read GetID write SetID;

[kbmMW_Ignore]
property LBName:string read GetName write SetName;

[kbmMW_Ignore]
property LBAddress:string read GetAddress write SetAddress;

[kbmMW_Ignore]
property LBAge:integer read GetAge write SetAge;
end;

42 COMPONENTS
DEVELOPERS 4 Issue Nr 3 2017 BLAISE PASCAL MAGAZINE
KBMMW ORM (OBJECT RELATIONAL MODEL) PAGE 7/7 COMPONENTS
DEVELOPERS 4
Notice that the extra properties are not wrapped with And in the Get Persons buttons event handler put
kbmMWNullable. They have also been marked with orm.ToDataset<TPerson>(mtPersons,GetPersons,true);
the attribute kbmMW_Ignore, to tell the kbmMW
Now the grid is populated with data, and you can work
framework that they are not to be marshalled
the data in any way you want. As the data is available
(serialized).
in a kbmMemTable , sorting, searching , filtering and
Their only purpose is to allow for easy object live
other things are very easy to do.
binding and is basically boiler plate code that we want
At some point, the data may have been modified,
to avoid.
and it should be sent to the server for updating.
There is another way that require less typing, and
In the Store Persons buttons event handler put
converting the objects to a dataset and back again
when needed. var
kbmMW ORM provides easy support for that. persons:TObjectList<TPerson>;
begin
In this case, we have already put a mtPerson
persons:=orm.ListFromDataset<TPerson>(mtPersons,[
kbmMemTable on the form at designtime, to make it
usDeleted]);
easy to do designtime live binding. if persons.Count>0 then
All we need is to populate it with some data. This DeletePersons(persons)
happens in the Get persons buttons eventhandler. else
But first we need to prepare the smart client. persons.Free;
This can happen in the Connect buttons event handler. persons:=orm.ListFromDataset<TPerson>(mtPersons);
// A form field StorePersons(persons);
c:IkbmMWSmartClient; end;
...
// Connect button's event handler
c:=TkbmMWSmartRemoteClientFactory.GetClient(Transport,'ORMDEMO');

Then let us make a few methods to get and send data.


function
TForm1.GetPersons:TObjectList<TPerson>;
begin This first sends a list of deleted TPerson’s to the
// Request a list of persons. server’s DeletePersons method (if any were deleted),
Result:=Use.AsObject<TObjectList<TPerson>>(c.S and then a list of remaining data. In fact that list could
ervice.GetPersons); also be limited to only inserted or modified data by
end; adding the argument [usModified,usInserted]
to the ListFromDataset method call. But right now
procedure we just ship all what is visible.
TForm1.StorePersons(APersons:TObjectList<TPer
What happens on the server, is that it will know
son>);
which fields was changed and only update the records
begin
// Store a list of persons. that are relevant . Remember this requires that all
c.Service.StorePersons(Use.Arg(APersons)); fields in the TPerson object are either wrapped with
end; kbmMWNullable or are of type TkbmMWDateTime.

procedure The combination of smart services, smart clients,


TForm1.DeletePersons(APersons:TObjectList<TPe advanced object marshalling, and the new ORM
rson>);
begin
really makes it extremely easy to build multi tier
// Store a list of persons. applications in a true RAD way!
c.Service.DeletePersons(Use.Arg(APersons));
end;
Happy wORMing!

COMPONENTS
DEVELOPERS 4 43
Digging for wORMs
kbmMW Object Relational Model
kbmMW Smart services
kbmMW Smart clients

KBMMW PROFESSIONAL AND ENTERPRISE EDITION V. 5.01.00 RELEASED!


- RAD Studio 10.2 Tokyo support including Linux support
(in beta). - Native improved XSD importer
- Huge number of new features and improvements! for generating marshal
- New Smart services and clients for very easy able Delphi objects from XML schemas.
publication of functionality and use from clients - High speed, unified database access
and REST aware systems without any boilerplate code. (35+ supported database APIs) with connection
- New ORM OPF (Object Relational Model Object pooling, metadata and data caching on all tiers
Persistence Framework) to easy storage and retrieval - Multi head access to the application server,
of objects from/to databases. via REST/AJAX, native binary, Publish/Subscribe, SOAP,
- New high quality random functions. XML, RTMP from web browsers, embedded devices,
- New high quality pronouncable password linked application servers, PCs, mobile devices, Java
generators. systems and many more clients
- New support for YAML, BSON, Messagepack - Full FastCGI hosting support.
in addition to JSON and XML. Host PHP/Ruby/Perl/Python applications in kbmMW!
- New Object Notation framework which JSON, YAML, - Native AMQP support ( Advanced Message Queuing
BSON and Messagepack is directly based on, Protocol) with AMQP 0.91 client side gateway
making very easy conversion between these formats and support and sample.
also XML which now also supports the object notation - Fully end 2 end secure brandable Remote Desktop
framework. with near REALTIME HD video, 8 monitor support,
- Lots of new object marshalling improvements, texture detection, compression and clipboard sharing.
including support for marshalling native Delphi objects - Bundled kbmMemTable Professional
to and from YAML, BSON and Messagepack in addition to which is the fastest and most feature rich in
JSON and XML. memory table for Embarcadero products.
- New LogFormatter support making it possible to
customize actual logoutput format.
- CORS support in REST/HTML services. kbmMemTable is the fastest and most feature rich in
- High performance HTTPSys transport for Windows. memory table for Embarcadero products.
- Focus on central performance improvements. - Easily supports large datasets with millions of records
- Pre XE2 compilers no longer officially supported. - Easy data streaming support
- Bug fixes - Optional to use native SQL engine
- Multimonitor remote desktop V5 (VCL and FMX) - Supports nested transactions and undo
- RAD Studio and Delphi XE2 to 10.2 Tokyo support - Native and fast build in M/D, aggregation /grouping,
- Win32, Win64, Linux64, Android, IOS 32, IOS 64 range selection features
and OSX client and server support! - Advanced indexing features for extreme performance
- Native PHP, Java, OCX, ANSI C, C#,
Apache Flex client support!
- High performance LZ4 and Jpeg compression
- Native high performance 100% developer defined app
server with support for loadbalancing and failover

COMPONENTS
DEVELOPERS 4 DX
EESB, SOA,MoM, EAI TOOLS FOR INTELLIGENT SOLUTIONS. kbmMW IS THE PREMIERE N-TIER PRODUCT FOR DELPHI /
C++BUILDER BDS DEVELOPMENT FRAMEWORK FOR WIN 32 / 64, .NET AND LINUX WITH CLIENTS RESIDING ON WIN32 / 64, .NET, LINUX, UNIX
MAINFRAMES, MINIS, EMBEDDED DEVICES, SMART PHONES AND TABLETS.

You might also like