Professional Documents
Culture Documents
Blaise31UKTotal Free PDF
Blaise31UKTotal Free PDF
BLAISE PASCAL
PASCAL MAGAZINE
MAGAZINE
D E L P H I,
A N D P A S C AL
A N D R O I D,
31/32
L A Z A R U S,
I O S,
O X Y G E N E,
R E L A T E D
M A C,
S M A R T,
L A N G U A G E S
W I N D O W S & L I N U X
5 / 6 2013
Publisher: Foundation for Supporting the Pascal Programming Language
in collaboration with the Dutch Pascal User Group (Pascal Gebruikers Groep)
© Stichting Ondersteuning Programmeertaal Pascal
BLAISE
BLAISE PASCAL
PASCAL MAGAZINE
MAGAZINE
D E L P H I,
P A S C AL
I O S, M A C,
31/32
L A Z A R U S,
R E L A T E D
O X Y G E N E, S M A R T, A N D
L A N G U A G E S
W I N D O W S & L I N U X
A N D R O I D,
maXbox Editors
Peter Bijlsma, W. (Wim) van Ingen Schenau, Rik Smit
The maXbox Pure Code Page Page 77
By Max Kleiner Correctors
Interview with Ray Konopka Page 93 Howard Page-Clark, James D. Duff
Programming Bitmap Rotation Page 98 Trademarks
By David Dirkse All trademarks used are acknowledged as the property
Introduction to Model, View and View Model (MVVM) of their respective owners.
and the Caliburn Micro for Caveat Whilst we endeavour to ensure that what is
Delphi framework Page 102 published in the magazine is correct, we cannot accept
By Jeroen Pluimers responsibility for any errors or omissions. If you notice
kbmFMX for XE5 (android) Page 113 something which may be incorrect, please contact the
By Fikret Hasovic Editor and we will publish a correction where relevant.
Subscriptions ( 2014 prices )
1: Printed version: subscription € 60.-- Incl. VAT 6%
(including code, programs and printed magazine, 6
issues per year excluding postage).
2: Electronic - non printed subscription € 45.--
Incl. VAT 21% (including code, programs and
2 COMPONENTS
DEVELOPERS 4 Nr 5 / 6 2013 BLAISE PASCAL MAGAZINE
From The Editor
6 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Book Review: Coding in Delphi (Continuation 3)
Introduction
Over the years, I’ve spoken in front of a lot of Delphi What you will find are ways to make your code much
developers. The one thing that I notice is that there are a lot cleaner, much more powerful, and way easier to maintain.
more of you Delphi guys than the average Delphi guy This book is all about the cool, new code you can write
thinks. There are Delphi folks everywhere. Also, I have with Delphi. It won’t matter whether you are building a
noticed that a lot of Delphi developers are “behind”. That VCL or an FM application. I’ve titled it “Coding in Delphi”
is, they are either using an older version of Delphi, or they because I want to make it a book that shows you simple
aren’t using or aren’t even aware of all the features in the examples of how to use powerful features – that, is about
newer versions of Delphi that they are using. the code. These language features are indeed advanced
Something I like to do when I’m in front of folks is features – they are new relative to, say, the case statement
ask a few questions about what people are doing. I’m – and thus many of you are beginners to them. By the end
always saddened that the response to questions like “Who of this book, you won’t be.
is doing unit testing?” or “Who is taking advantage of The approach I’m taking for this book is to try to
Generics?” distill things down to the very basics. The examples will be
is pretty meager. very simple but the explanations deeper. I believe that if
This is particularly true for the language features and the you can understand the basic idea in a simple example, it is
run-time library. It’s quite easy to move forward with an but a small leap to using those ideas in more complex code.
older code base, utilizing the new features in the IDE and There is no point in giving complex examples
adding new things to your app using the new high level when you can get the main thrust using fundamental
frameworks and components that come in the newer implementations that illustrate advanced ideas.
versions. Between the code in this book and in the samples online
For example, you might have been developing an (https://bitbucket.org/NickHodges/codinginde
application since Delphi 3, moving forward through lphi²) you can learn all the precepts and then begin
various versions. Along the way, you may have added applying them to your own code. In other words, nothing
some DataSnap functionality, started using the Code fancy here, just the basics – it is then up to you to use these
Insight features in the IDE, and when you moved to XE2, ideas in your own code.
you start poking around with FireMonkey. This book is not done – it’s instead a living document.
But it’s fairly easy to ignore the new language features that Since it is self-published on a platform that makes iteration
come along with those new versions. very easy, I plan on having frequent releases to fix typos
For instance, two powerful language features were added (which will, I’m sure, sneak through despite my best efforts),
in Delphi 2009: generics and anonymous methods. improve examples and descriptions, and keep up with
Both are features that enable the development of really cool technology changes. Owners of the PDF should get
code and frameworks. notifications of new versions automatically.
But if you didn’t understand or feel the need for them,
then it was pretty easy to simply not use them.
If you are reading a paper version of this book, I’m sorry I
You can still do all kinds of great stuff in Delphi without
can’t deliver fixes to your hard-copy – perhaps
them, but with them, well, you can write some
some day that will be possible.
really beautiful, testable, and amazing code.
The book will be done when you guys say it is done.
For instance, a relatively new framework that
Maybe, it will never be done because Delphi keeps
exists only because of these new language features is the
growing and expanding. I guess we’ll see.
Spring Framework for Delphi, or Spring4D, as I’ll refer to it
As a result, I’m totally open to feedback – please feel free to
in this book. Spring4D is a feature rich framework that
contact me at nickhodges@gmail.com
provides a number of interesting services, including a wide
with suggestions corrections, and improvements.
variety of collections that leverage generics, an Inversion of
Please join the Google Plus group for the book.³
Control container, encryption support, and much more.
I may even add whole new chapters.
I view Spring4Dsolid as much a part of the Delphi
Thanks for your purchase – this book was a labor of love,
RTL as SysUtils is. Using Spring4D in your code will make
so every sale is icing on the cake.
many, many things easier and cleaner. But many Delphi
developers don’t know this yet. Nick Hodges
If the above is familiar, this book is for you: Gilbertsville, PA
The Delphi developer that hasn’t quite yet made the leap
over into the cool and amazing things that you can do with ²https://bitbucket.org/NickHodges
the latest versions of the Delphi language.
This book is all about introducing these new
language features and some of the intriguing things you
can do with them. It will take a look at these new language
features, and then expand into some of the open source
Coding in Delphi
W
e assume you are developing a public DLL.
So you will have a .dll file, you will have the consider taking a shortcut through somebody's backyard or
header files (at least *.h and *.pas), and you will going the wrong way down a one-way street.
have documentation. In the same way that an experienced chess player doesn't
The header files (or headers) form a set of source files even consider illegal options when deciding his next move,
containing structure and function declarations used in the an experienced programmer doesn't even consider
API for your DLL. violating the following basic rules without explicit
Typically they contain no implementation. permission in the documentation which allows
The headers should be available in several languages. contravening the rule:
As a rule, this means the language used to create the DLL • Everything not defined is undefined.
(in our case - Delphi), C (as standard) and perhaps additional This may be a tautology, but it is a useful one.
languages (such as Basic, etc.). Many of the rules below are just special cases
All these header files are equivalent to each other, of this rule.
• All parameters must be valid.
representing only translation of the API from one language
The contract for a function applies only when the caller
to another.
adheres to the conditions, and one of the conditions is
The more languages you include the better.
that the parameters are actually what they claim to be.
If you don't provide header files for a particular language,
This is a special case of the "everything not defined is
then developers using that language will be unable to use
undefined" rule.
your DLL, (unless they are able to translate your header files
o Pointers are not nil unless explicitly
from a language you provide (Delphi or C) into their language).
permitted otherwise.
This means that failing to offer headers for a particular
o Pointers actually point to what they
language is usually a big enough obstacle that developers
purport to point to.
in that language will not use your DLL, although it is not
If a function accepts a pointer to a
an absolute block to such usage. From this perspective
CRITICAL_SECTION, then you must pass a pointer
COM looks more attractive (the API description is stored in
which points to a valid CRITICAL_SECTION.
type libraries in the universal TLB format).
8 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Designing an API: common mistakes (Continuation 1)
o Pointers must be properly aligned. • Input buffers:
Pointer alignment is a fundamental o A function is permitted to read from the full extent
architectural requirement, yet something many of the buffer provided by the caller, even if the
people overlook, having been pampered by a entire buffer is not required to determine the result.
processor architecture that is very forgiving of • Output buffers:
alignment errors.
o An output buffer cannot overlap an input buffer
o The caller has the right to use the
or another output buffer.
memory being pointed to.
This means no pointers to memory o A function is permitted to write to the full extent
that has been freed or to memory that the caller of the buffer provided by the caller, even if not
does not have control over. all of the buffer is required to hold the result.
o All buffers are as big as the declared o If a function needs only part of a buffer to hold
(or implied) size. the result of a function call, the contents of
If you pass a pointer to a buffer and say that it the unused portion of the buffer are undefined.
is ten bytes in length, then the buffer really o If a function fails and the documentation does not
needs to be ten bytes in length (or more). specify the buffer contents on failure,
o Handles refer to valid objects that then the contents of the output buffer are
have not been destroyed. undefined.
If a function wants a window handle, This is a special case of the
then you must pass a valid window handle. "everything not defined is undefined" rule.
o Note that COM imposes its own rules
• All parameters are stable.
on output buffers. COM requires that all output
o You cannot change a parameter
buffers be in a marshallable state even on failure.
while the function call is in progress.
o If you pass a pointer, the pointed-to memory For objects that require nontrivial marshalling
will not be modified by another thread for the (interface pointers and BSTR/WideStrings being the
duration of the call. most common examples), this means that the output
o You cannot free the pointed-to memory either. pointer must be nil on failure.
• The correct number of parameters is passed with the (Remember, every statement here is a basic ground rule, not an
correct calling convention. absolute inescapable fact. Assume every sentence here is prefaced
This is another special case of the "everything not with "In the absence of indications to the contrary". If the caller
defined is undefined" rule. and callee have agreed on an exception to the rule, then that
o Thank goodness, modern compilers exception applies.)
refuse to pass the wrong number of parameters,
though you would be surprised how many Error:
people manage to sneak the wrong number of Providing no dedicated initialize
parameters past the compiler anyway, and finalize functions
usually by devious casting. The first mistake you can make as an API developer is not
o When invoking a method on an object, to provide standalone functions to initialize and finalize
the Self parameter is the object. Again, your DLL, but instead use the DLL_PROCESS_ATTACH
this is something modern compilers handle and DLL_PROCESS_DETACH notifications from the
automatically, though people using COM DllMain callback-function.
from C (and yes they exist) have to pass
DllMain is a special function in the DLL,
the Self parameter manually,
called automatically by the system in response to certain
and occasionally they mess up.
events. Among those events are DLL_PROCESS_ATTACH
·• Function parameter lifetime: and DLL_PROCESS_DETACH
o The called function can use the parameters - these are notifications about the loading and unloading of
during the execution of the function. your DLL.
o The called function cannot use the parameters If you are using Delphi, then the initialization section
once the function has returned. Of course, if the and the finalization section of Pascal units in the DLL are
caller and the callee have agreed on a means of executed in response to DLL_PROCESS_ATTACH
extending the lifetime, and DLL_PROCESS_DETACH, which the system sends to
then those agreed rules apply. the DllMain function of your DLL.
§ The lifetime of a parameter that is a Of course, you do not see this process, it is happening
pointer to a COM object can be extended
under the hood of the RTL (language support library).
bythe use of the IUnknown.AddRef
You just see that when the DLL is loaded, all units are
method.
§ Many functions are passed parameters initialized, and when it is unloaded, they are finalized.
with the express intent that they be used
after the function returns.
It is then the caller's responsibility
to ensure that the lifetime of the parameter
is at least as long as the function needs it.
For example, if you register a callback
function, then the callback function needs
to be valid until you deregister
the callback function.
10 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Designing an API: common mistakes (Continuation 3)
Error: What is the purpose of a shared memory manager?
Using language-specific data types In a sense, a shared memory manager is a "hack".
or other language-specific constructs It is a quick and dirty way to solve the problem of
Obviously if you create an API to be used from various exchanging dynamic data between modules.
programs each written in a different language, then you Never use a shared memory manager at the beginning of
cannot use structures that exist only in your language. a new DLL project.
For example, string (as well as AnsiString, UnicodeString, A shared memory manager is a means of backward
ShortString, String[number]), array of (dynamic and open compatibility, but does not fit with new code.
arrays), TObject (i.e. any objects), TForm (and components), If you need to exchange objects (including exceptions),
etc. strings or other language-specific constructs you have to
use BPL-packages, not DLLs.
If you use a data type (or language construct) that has no If you have used a DLL, then you must not use Delphi-
counterpart in other languages, then code in this very specific types and, therefore, must not use the shared
different language simply will not be able to use your API. memory manager.
It would have to emulate the language constructs of your Hence the comment at the beginning of the earlier section
language. about the forbidden use of UnicodeStrings (and so on) in
DLLs.
So, what can be used in an API? The following:
• integer types You can easily transfer data of varying size if you follow
(Integer, Cardinal, Int64, UInt64, NativeInt, NativeUInt, the guidance above; and you already know you should not
Byte, Word, etc. - with the exception of Currency); use Delphi-specific types in a DLL.
• real types Therefore, there is no need to use a shared memory
(Single and Double - except Extended, Real, Real48 manager at all (either alone or using run-time packages).
and Comp);
• static arrays Error:
(array[number .. number] of) of the acceptable types; Failing to protect each exported function with
• set, enumerated and subrange-types a try/except block
(with some reservations - see below; The explanation above should have made it clear that you
it is preferable to replace them with integer types); cannot pass any objects between modules.
• character types An exception is also an object (in Delphi).
(AnsiChar and WideChar, but not Char); Adding two plus two, we realize that you cannot throw an
• strings exception in one module and then catch it in another.
(only in the form of WideString; strings The other module has no idea how to work with an object
as PWideChar - allowed, but not recommended;
that was created in a different programming language.
PAnsiChar is valid only for ASCII-strings;
PChar strictly prohibited;
This means, of course, that you have to think carefully how
ANSI-string is prohibited);
you will report errors (as mentioned above). Here I am
• Boolean type
talking only about the particular case of an exception.
(BOOL, but not Boolean; ByteBool, WordBool and
Because an exception cannot (that is: should not) leave the
LongBool are acceptable, but not recommended);
module, you must implement this rule religiously: put a
• interfaces which use and operate
try/except block to catch all exceptions around the code of
with acceptable types only;
• records with fields of acceptable types; each exported function.
• pointers to data of acceptable types;
• untyped pointers; Note:
• data types from the Winapi.Windows.pas unit • The function can be exported explicitly (“exports
(or a similar unit for non-Windows platforms); function-name”) or implicitly (e.g. as a callback-
• data types from other system headers function or other function which returns a pointer to
(they are located in the \Source\ RTL\Win of your Delphi; the calling code).
replace "Win" path with OSX, iOS, etc. Both options must be wrapped in a try/except block.
- for other platforms). • "Catch all exceptions," of course, does not mean that
you wrap the function in an empty try/except block.
Error: You must not turn off all exceptions. You have to
Using a shared memory manager catch them (yes, catch them all) and transform rather
and/or packages than suppress them.
Any shared memory manager You must convert each exception to something
(such as ShareMem, FastShareMem, SimpleShareMem, etc.) prescribed by your protocol (perhaps an error code, an
is a language-specific facility which does not exist in other HRESULT, or whatever).
languages. So (as in the previous section) you should never
use any of them in your API. The same applies to run-time Note also that if you use the method recommended above
packages (.bpl packages). This package concept exists only (interfaces with safecall) then this issue is automatically
in Delphi (and C++ Builder). covered for you. The safecall calling convention assures
you that every method you write will be wrapped by a
hidden try-except block through compiler "magic";
and no exception escapes your module.
14 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Designing an API: common mistakes (Continuation 7)
You have changed the interface, but you also have Gotcha:
changed the IID, so everything should be OK, right? Function return values
Functions or methods that return an interface (as in the
Actually - no. previous paragraph) present a problem for the extension.
Of course, in the beginning it is a convenient solution:
The IGraphicImage interface depends on the you can call the function "normally" and even hook them
IColorInfo interface. When you change the into chains like this:
IColorInfo interface, you implicitly changed the
Control.GetPicture.GetImage.GetColorInfo.GetBackgroundColor
IGraphicImage.GetColorInfo method
- because its return value has now changed to become However, this state of affairs will exist only in the very first
another: IColorInfo interface version v2.0. version of the system. As you begin to develop the system,
you will begin to create new methods and new interfaces.
Look at the following code, written with headers v2.0: In the not too distant future you'll have plenty of advanced
interfaces; and base interfaces that were originally in the
procedure AdjustGraphicColorInfo (pgi:
IGraphicImage; const clrOld, clrNew: TColorRef); program, at the time of its birth will provide only trivially
var uninteresting functions.
pci: IColorInfo;
begin Overall, very often the caller will need the newer interfaces
pci: = pgi.GetColorCount (pci);
rather than the original ones.
pci.AdjustColor (clrOld, clrNew);
end; What does this mean? It means that almost all the code has to
call the original function to get the original interface, and then
If this code is run on v1.0, the call request a new one (via Supports/QueryInterface) and only then
IGraphicImage.GetColorCount returns IColorInfo use the new interface.
version v1.0, and this version has no The result is not so comfortable, and even more uncomfortable
IColorInfo.AdjustColor method. is the fact we now have a triple calls (original/old + conversion +
desired/new).
But you still call it.
Result: you skip to the end of the method table and call the
Let us look again at the previous point:
trash that lies behind it.
the modification of one interface makes it necessary to make
copies of all the interfaces that use it as a return value
Quick fix - change IID for IGraphicImage,
- even if they themselves do not change.
to take account of changes in IColorInfo:
type The best solution for both cases is that the callee code indicates
IGraphicImage = interface to the called function which interface it wants
{UVW} // <- a new GUID - the new or the old.
... This can be done, of course, by specifying the IID:
function GetColorInfo: IColorInfo;
type
safecall;
IGraphicImage = interface
end; {XYZ}
...
This code update path is very time-consuming because you procedure GetColorInfo
have to keep track of all references to the variable interface. (const AIID: TGUID; out AColorInfo);
safecall;
end;
Moreover, you cannot just change the GUID
- you have to create a second interface IGraphicImage Note that now you cannot use the result of the function, as the
with a new GUID and manage the two interfaces (even result has to have a specific type (of course it does not have it -
though they are identical up to the return value). we should return interfaces of different types), that's why we use
the raw data output parameter.
When you have several of these changes and the use of a
large tree, the situation quickly gets out of control with Then, you can write code like this:
endless cloning of interfaces for every sneeze.
var
Image: IGraphicImage;
We will look at the correct solution to this problem in the
ColorInfo: IColorInfoV1;
next paragraph. begin
...
Image.GetColorInfo(IColorInfoV1, ColorInfo);
Color := ColorInfo.GetBackgroundColor;
...
var
Image: IGraphicImage;
ColorInfo: IColorInfoV2;
begin
...
Image.GetColorInfo(IColorInfoV2, ColorInfo); //
throw a "no interface" exception, if you run on the V1
ColorInfo.AdjustColor(OldColor, NewColor);
...
However, what was said in the previous paragraph is still It turns out that even though the object implements the
in force: in terms of versioning interfaces, it is better to use interface it does not tell "outside" that it implements it.
constructs like:
procedure Test1(const IID: TGUID; out Rslt);
safecall;
16 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Designing an API: common mistakes (Continuation 9)
That is, if two interfaces are connected by inheritance, Gotcha:
the mere inclusion of the child interface into the list of Polymorphism and implementation
interfaces implemented by a class does not ensure the of interfaces
inclusion of an ancestor/parent interface in this list. When your object descends from a class its polymorphic
In other words, in order to be implemented automatically behaviour is achieved by virtual means. But when you use
by a class, you must ensure that this interface has appeared interfaces, all the methods of the interface are already virtual (by
at least once in the line "list interfaces" for the class (not definition).
necessarily in this class, it can be in an ancestor, but it must Therefore, there is no need to use virtual methods to implement
appear somewhere). interfaces (though virtual methods may be required for other reasons
The presence of a child interface is not enough. - for example, to inherit functionality).
Note that the code:
For example:
type type
ISomeInterface = interface ISomeInterfaceV1 = interface
['{A80A78ED-5836-49C4-B6C2-11F531103FE7}'] ['{C25F72B0-0BC9-470D-8F43-6F331473C83C}']
procedure A; procedure A;
end; procedure B;
end;
IAnotherInterface = interface
['{EBDD52A1-489B-4564-998E-09FCCF923F48}'] TObj1 = class(TInterfacedObject, ISomeInterfaceV1)
procedure B; protected
end; procedure A;
procedure B;
TObj1 = class(TInterfacedObject, ISomeInterface) end;
protected
procedure A;
TObj2 = class(TObj1, ISomeInterfaceV1)
end;
protected
TObj2 = class(TObj1, IAnotherInterface) procedure B;
protected end;
procedure B;
end; procedure TForm1.Button1Click(Sender: TObject);
var
procedure TForm1.Button1Click(Sender: TObject); SI: ISomeInterfaceV1;
var begin
SI1: ISomeInterface; SI := TObj2.Create;
SI2: IAnotherInterface; SI.A; // calls TObj1.A
begin SI.B; // calls TObj2.B
SI2 := TObj2.Create;
end;
Supports(SI2, ISomeInterface, SI1);
Assert(Assigned(SI1));
SI1.A; Note that specifying ISomeInterfaceV1 for TObj2 means
end;
that the method TObj2.B will implement ISomeInterfaceV1.B.
The key point here is - just specify the interface. Please note
and
that:
type • Method B does not have to be virtual
ISomeInterfaceV1 = interface • ISomeInterfaceV1 interface for TObj2 is assembled
['{A80A78ED-5836-49C4-B6C2-11F531103FE7}']
procedure A; "piece by piece": the method B is taken from the TObj2,
end; but the method A is taken from TObj1. This is a standard way
of working with interfaces and class inheritance.
ISomeInterfaceV2 = interface (ISomeInterfaceV1)
['{EBDD52A1-489B-4564-998E-09FCCF923F48}']
procedure B; However, as has been said, sometimes you may
end; want to use this code:
TObj1 = class (TInterfacedObject, ISomeInterfaceV1)
protected
procedure A; type
end; ISomeInterfaceV1 = interface
['{C25F72B0-0BC9-470D-8F43-6F331473C83C}']
TObj2 = class (TObj1, ISomeInterfaceV2) procedure A;
protected procedure B;
procedure B; end;
end;
TObj1 = class(TInterfacedObject, ISomeInterfaceV1)
procedure TForm1.Button1Click (Sender: TObject); protected
var procedure A; virtual;
SI1: ISomeInterfaceV1; procedure B; virtual;
SI2: ISomeInterfaceV2; end;
begin
SI2 := TObj2.Create; TObj2 = class (TObj1)
Supports(SI2, ISomeInterfaceV1, SI1); protected
Assert(Assigned(SI1)); procedure B; override;
SI1.A; end;
end;
destructor TMyObject.Destroy;
Obj.DoSomething(TSomething.Create); begin
if FNeedSave then
What will happen? Save;
The const modifier tells the compiler that it should call inherited;
end;
_AddRef and _Release on the interface.
It does not look very scary, does it?
On the other hand, we are creating a new object. The object just saves itself before it is destroyed.
What is the reference count of the newly created object?
It is equal to zero. But the Save method might look something like this:
The counter is incremented by _AddRef when the object is
used (for example, when the interface is assigned to a variable). function TMyObject.Save: HRESULT;
var
We have created an object with the counter set to 0, spstm: IStream;
spows: IObjectWithSite;
and passed it to a method that does not change the begin
reference count. Result := GetSaveStream(spstm);
As a result, the reference count never drops to 0 (simply if SUCCEEDED(hr) then
because it never rises from the ground), and, hence, begin
Supports(spstm, IObjectWithSite, spows);
no destructor of the object is called. As a result, if Assigned(spows) then
we get a leak for this object. spows.SetSite(Self);
Result := SaveToStream(spstm);
The solution is to use a variable: if Assigned(spows) then
spows.SetSite(nil);
end;
var
end;
Arg: ISomething;
begin
Arg: = TSomething.Create; By itself, it looks fine. We get a stream and save Self in it,
Obj.DoSomething (Arg); further establishing the context information (site) - just in
end; case the stream needs additional information.
18 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Designing an API: common mistakes (Continuation 11)
But this simple code combined with the fact that it is function TMyObject._AddRef: Integer;
running from a destructor, gives us a recipe for disaster. begin
Assert (FRefCount >= 0);
Look what happens: Result := InterlockedIncrement(FRefCount);
end;
1. The _Release method decrements
the reference count to zero and deletes Self. function TMyObject._Release: Integer;
begin
2. The destructor tries to save the object. Result := InterlockedDecrement(FRefCount);
3. The Save method wants to save into if Result = 0 then
the stream and sets Self as the context. Destroy;
This grows the reference count from zero to one. end;
4. The SaveToStream method saves procedure TMyObject.BeforeDestruction;
the object to the stream. begin
5. The Save method clears the thread's context. if RefCount <> 0 then
This reduces the reference count of our System.Error(reInvalidPtr);
FRefCount := -1;
object back to zero.
end;
6. Therefore, the _Release method calls
the destructor of the object a second time. Note:
such a check is not present in TInterfacedObject.
The destruction of the object a second time leads to full- TInterfacedObject allows your code to run and call
scale chaos. If you are lucky, the crash inside the recursive the destructor twice.
destruction will be able to identify its source; but if you are This check will help you to easily catch "cases of
unlucky, it may cause damage to the heap, which will mysterious double calls to the destructor of the object."
remain undetected for some time, after which you'll be But when you identify the problem, then what do you do
scratching your head. with it?
Here's one recipe:
Therefore, as a minimum, you should insert an Assert call http://blogs.msdn.com/b/oldnewthing/archive
into your _AddRef method, to ensure that you do not /2005/09/28/474855.aspx
increase the reference count from zero during the
execution of a destructor:
20 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Programming with the leap motion By Michaël Van Canneyt
on the Mac OS including Update of the code for Linux and Windows
starter expert Delphi 7 and later
Lazarus 1 and later
When first started, the Lazarus IDE will prompt for the
location of the fpc compiler and the sources. For a default
installation, this is /usr/local/bin/fpc and
/usr/local/share/fpcsrc, respectively.
MOTION
Once installed and started, the IDE is ready for use.
Introduction
The Leap Motion works on all major platforms: Windows,
Mac OS and Linux. So does Lazarus. The workings of the
Leap Motion controller on Linux and Windows were easily
verified, as the component was developed on that
platform. To check whether the component also works on
Mac OS X, Lazarus was installed on a Mac (Macbook Pro,
running OS X Lion 10.7.5) and compilation of the leap
component and one of the demo applications is tested.
Installation of Lazarus
While cross-compilation is commonplace these days, there
is nothing like native development.
So, installing Lazarus on the Mac is the firststep. This can
be easily done: From the Lazarus website, 3 disk image files
need to be downloaded:
fpc-2.6.2.intel-macosx.dmg
The Free Pascal compiler.
The IDE calls the free pascal compiler Pointables
when it needs to compile code. on the move
fpcsrc-2.6.2-i386-macosx.dmg
The Free Pascal Sources.
The IDE needs this to provide code insight.
lazarus-1.0.14-20131116-i386-macosx.dmg
The actual Lazarus IDE.
The version numbers may change as FPC and Lazarus
evolve.
Under Mac
22 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Programming with the leap motion on the Mac OS
including Update of the code for Linux and Windows (continuation 2)
Verifying the LazLeap package
To check whether everything works as it should 3
packages must be installed:
• laz_synapse
The low-level TCP/IP socket support on which
the websocket support builds.
• bauglirwebsocket
The websocket implementation needed to query
the leap motion service.
• lazleap
The actual lazarus component.
The tap demo has been improved with a new ’magnetism’ It will contain the following Gestures
setting. The tap movement is very sensitive: while tapping, Tapping
the tip of the finger (used to select a button) moves. • (Clicking)
The effect may be that the actual button that is under the Open Gesture
finger cursor on the moment the tap gesture is received, • (a gesture that follows your own design andevents)
is not the button for which the tap was intended: Cirkels (2D Swipe)
• (Swiping - for quick rotation)
• (Dragging for precise movement)
Left to right (or vice versa)
The downward movement of the finger • (Swiping - for quick movement with number of lines to
during the tap may switch the focus to the run predefineable)
button below the intended. • (Swiping - for precision)
To prevent this from happening, a kind of Top or down (or vice versa)
magnetism is introduced: magnetism ’shrinks’ the surface • (Swiping - for quick movement with number of lines to
of a control, making it more difficult to be selected. run predefineable)
It is in fact the number of pixels that the cursor must • (Swiping - for precision)
be inside the actual border, before the control is selected as 3D Swipe
the new focused control. • (Swiping - for quick rotation)
The focus sticks to the previously selected control, • (Dragging for precise movement)
unless the finger cursor is really centered on the new for this one needs to have a 3D Picture
control, hence the name ’magnetism’.
We have already added sound to the gesture.
This simple trick makes the tap demo a lot more easy to
We want to add sound reaction and (snapping
handle, as some experimenting will confirm. with fingers) and verbal commands (English). We
will start with this in the coming months...
Conclusion
In the case of the Leap Motion, Lazarus truly lives
up to it’s motto: Write once, compile anywhere.
After verifying that the leap motion controller
works on all platforms, it is time to start work on
designing some components that help to drive the
user interface with the Leap.
As well as sharing with the author “... the passion of a few in the programming
community for good documentation...”, it is good to see the following comment within the
Foreword section that is applicable to both today's learners and experienced developers,
now that their targets have gone beyond the single Windows Operating System : "A
maturing Lazarus has also helped slowly to convince developers for MacOS, Linux and
more recently Android and other platforms that Pascal can truly serve their needs."
In the opening chapter, the following is quoted to explain the contents and objectives of the book.
"This is a tutorial guide rather than a reference book.
When you've worked your way through the following chapters you should be able to understand how
to use Lazarus to tackle increasingly complex programming challenges, and have learned
not only how to use the Lazarus IDE and the Pascal language, but how to go about finding out what
you still need to learn." Below is the list of chapters, each one containing several topics, and
finishing off with either Review Questions or Review Exercises to keep the reader awake. A couple of
sub-topic lists are also included to show the levels of detail provided within those chapters.
1 Starting to program
2 Lazarus and Pascal
3 Types, variables, constants and assignments
a. Pascal types, b. Ordinal types, c. The boolean type,
d. Enumerated types, e. Type conversion, f. Typecasts,
g. Variables, h. Initialised variables, i. Assignment: placing a value in a variable,
j. Extended numerical assignment operators,
k. Constants and literal values, l. A program example: simple_types,
m. Typed constants, n. Pointers, o. Review Questions
4 Structured types
5 Expressions and operators
6 Pascal statements
7 Routines: functions and procedures
8 Class: An elaborate type
9 Polymorphism
10 Units, GUI programs and the IDE
11 Display Controls
a. TLabel, b. exploring TLabel properties, c. TStaticText,
d. TBevel and TDividerBevel, e. TListBox, f. TStatusBar,
g. Further options, h. Review Questions
12 GUI Edit controls
13 Lazarus GUI projects
14 Component Containers
15 Non-visual GUI Support classes
16 Files and Errors
17 Working within known limits
18 Algorithms and Unit tests
19 Debugging techniques
20 Further resources
LEARN TO PROGRAM
USING COMPONENTS
24
96
LAZARUS 4 Nr Nr
5 /56/ 2013 BLAISE PASCAL MAGAZINE
ISBN 978-94-90968-04-5
DEVELOPERS
NEW PRINTED BOOK:
LEARN TO PROGRAM
USING LAZARUS
PUBLISHED BY
Detlef Overbeek, Editor in Chief of Blaise Pascal As computational power diversified and
Magazine, recently asked the question... progressively became less expensive it opened up
"Can Pascal somehow take part in the desktop 3D printing from the lab to industry. The PC
3D printing scene?" presented the means, the technology.
Perception vs Proof But what built the explosive and wide spread
awareness about 3D printing was not technology
The quick answer? That would be a "No". alone. It was actually a simple conversation that
did. A conversation between a few who wanted to
That train has left the station. Most all software make a particular technology more accessible. They
related elements in that process have been claimed made the first step and that conversation moved on
by others. Not one of them uses Pascal. Its like the to millions. It brought people together and created
popular kids are having a party and now some of an entire community. A mix of technological
the other kids want to join in as well. It does not development and social bonding. Democratized
matter how much technology, ingenuity and party development.
spirit the other kids may bring. Its not going to
happen. The Value of Technology
Technology itself has little value. At least not on its
But that is too easy. “No” is not an answer. It has own. It is still people that get things done.
little meaning here. It is too blunt. Moreover, it Pascal, like so many, is simply a language.
actually does not answer the question. It avoids it. Its technologies are the existing compilers,
We could think of a million reasons why something frameworks, components and IDE's that allow you
will not work. Typically, we do. But the idea here is to develop in that language. That's what makes it
to find an answer about what would work, or at accessible and applicable. Yet, the actual value and
least what might work. Glass half full, not half success of a language resides in and is proportional
empty. to a community that supports it.
In addition, as people of science and technology we Looking back 15 years ago Pascal was at an all time
don't like to think in terms of yes and no, good and high. And, for good reason. It made DOS and
bad or black and white. Those types of answers are Windows development extremely easy, versatile
too polarized. It is a too simplistic way of thinking. and fast. More importantly, it was not just building
It ignores all the shades of gray. Instead, we tend a community but communities, plural. Apart from
to rather observe the properties and patterns of developing business applications it was the
something. Discover how these might be used to standard in education. It was also the standard on
serve some purpose or task. Creating something the DIY (do it yourself) front. It made things clear
new. We think about truth. If we did not then what and its programing-correct structure was building
we create won't usually work in the real world. It's good programming practices for all. Unfortunately
that simple. for reasons other than its technology it was not
setting standards. Rather, it was forced to follow
But there is also a kind of a problem here. Having a the standard settings of others.
scientific or technical background means that most
tend to be more inclined to search for technical Many of the technological benefits of Pascal exist
solutions as answers. That's fine. Yet, at this point, today. And, a lot more. The Delphi and Lazarus FPC
these only serve as tactics. Actually, we should be solutions allow developers to go farther, wider and
searching for something else. A strategy is needed. deeper than ever before. More than most any other
Something that defines the situation and the language.
problem so that we may understand what "can" be
done and what "needs" to be done first. The Pascal is best? There is no such thing as best.
solution tactics, the technology, will follow later. What is best depends on how something fits an
application. Yet, Pascal is making it progressively
So what might be the answer? less warranted to use something as messy and slow
In part, Pascal might take part in the 3D printing as java, something as cumbersome and tedious as
scene. But in order to do so, Pascal needs to create xcode and it allows easy entry for Windows
its own party. Claim a location, a platform and build developers to step into linux development.
its own entry point. Something in alignment with its But the problem is not the technology. Its is not a
merit that is also worthy of conversation and lack of accessibility nor applicability. It is, typically,
collaboration. Something that uses technology to a lack of awareness. Pascal is powerful, but people
allow accessibility. And, most importantly, build the need to know about it to make it truly powerful.
social bonding between people.
Democratize Development,
How to emphasize this? Elaborate? 3D printing, for Centralize Awareness
instance, is not new. It has been around for some “Change” has become more of a constant than an
time. A long time, in fact. Yet, the success of one exception these days. And, that brings along entry
thing is typically the result of the success of points, opportunities. Opportunities for Pascal to
another thing. For 3D printing it was the PC. enter into and create its own party.
26 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Perception versus proof:To 3d Print or Not to print 3D Printing
The 3D printing scene is not immune to change a More Tangible Idea
either. It is not something static.
New developments are arising all the time as these What does 3D printing have to do with
technologies diversify and their applications widen, Pascal? Well...not much really. That is
deepen and demand grows. unless Pascal somehow starts finding
Other platforms are also on the rise such as mobile its way into developing solutions here.
which may allow new solutions for 3D printing. 3D printing is an exciting and
emerging manufacturing technology.
Mobile would seem to be an attractive an obvious
One that Pascal could take part in.
choice. Jump on the bandwagon.
A great stepping stone for Pascal and 3D printing.
In terms of software development,
In many ways that can be argued to be true. Pascal provides all the solution.
Yet, this is a very frontal approach. But how that might eventually happen
Pascal may make headlines here, even build some will depend on you. With that in mind,
awareness. But there is a lot more involved in the incentive here is to present a very
capturing and sustaining a conversation that builds brief but in-depth and critical
communities. overview about 3D printing.
Something that sheds light on how it
There are platforms other than mobile that are
essentially works, what it can do as
finding a lot of market acceleration.
They are moving fast. More importantly,
well as what it can't do (yet).
these may offer more alignment with Pascal and
a foundation to bring to light its true merit.
This towards having Pascal not only capture but
build and sustain new communities.
Things like the Arduino, BeagleBoard and, in
particular, the Raspberry Pi.
Technology and its merit aside, the significance What this example illustrates is that while the 3D
here is the main stream "awareness" that this printed shape of an object may suffice for some
brings about. And, that by itself is a revolution. applications there are many more applications
How we convert an idea into tangible and functional where it won't. This does not mean that 3D printing
form. technology is not applicable for real world products.
It simply means that the technology to induce
It typically takes about 30 years before a really new certain properties in 3D printed products is not
idea can move from concept to culture. Something there yet. There are also many other factors
that moves into our minds and effects the way we involved in manufacturing. What about production
do things and the ways we think about how to cost and output?
make them.
With that said lets move on and get an insight
In other words, such "awareness" can push and about 3D printing technologies.
even catapult the development of a new technology.
We have just witnessed it before with the PC
industry. 3D printing is now laying down another
infrastructure. One that allows these technologies
diversify and be applied to an ever wider range of
uses.
But recent changes in design approaches and, in While it may seem otherwise, most 3D printing
particular, the resins involved are making a technologies are not available to the average
difference. The development and diversity of photo consumer due to cost, complexity and safety
cure resins and suppliers are creating a better fit for issues.
this process on our desktop. This does not mean that they won't one day take
part as a household appliance and be as easy to
The process relies on a photo cure resin which is use as a glorified coffee maker.
selectively and acutely cured to form layers. The We are just not there yet. 3D printing is still in its
resin in a SLA process is a liquid which typically infancy. With that in mind it should be apparent
cures under UV light. Lasers and other UV light that there is room for improvement.
sources may be used. After each layer is cured A lot of room. And, that means that there are
another layer is added in order to build the object. opportunities. Opportunities for Pascal to
provide solution. To appreciate this we need to
The SLA process appears to be the next candidate gain a better idea on the processes involved.
in the desktop 3D printing scene. Most systems rely
on the use of DLP projectors (Digital Light While 3D printing technologies may differ in their
Processing) to illuminate the resin. These are fast. operation and control the basic processing is more
Laser scanning systems however offer other or less the same. These include:
important benefits as well.
Pre-Process – Main Process – Post-Process
Pascal and 3D Printing The Pre-Process stage relates to processing the 3D
While new suppliers of desktop 3D printers for data and preparing it for the printer to be printed in
home use seem to appear every other week the the Main-Process.
majority are based on the same technique (FDM).
They are variations of the same thing.
Different sizes and aspect ratio's, some may be fast
or accurate or have some combination of these
aspects. There are exceptions, but, typically, they
are all based on the same technique.
And, that means that
there are opportunities.
Opportunities for Pascal to provide
30 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Depending on the technique, the Post-Process stage For the average user it may turn out to be an
relates to things like cleaning, assembling or impossible task to perform, especially these days.
post-curing of the printed material after the In fact, even when it comes to 3D modeled data
Main-Process is completed. drawn in a graphics or CAD application, things can
take time to prepare and involve a lot of work.
In particular it is the Pre-Processing and Desktop 3D printing, certainly for home use, is still
Main-Processing stages were the magic equally an art as it is a technology.
happens and where Pascal can provide
solutions. The Main-Processing stage pertains to The block diagram provides a birds-eye view of the
the control of the machine, the printer. typical Pre-Process elements of 3D printing data
preparation. The 3D scan data process flow has also
If you are new to 3D printing then you may be been included to illustrate what's typically involved.
inclined to think about the “washer and dryer”
solution. The 3D scan and 3D print solution. This is Solid Model
a scenario where a 3D scanner is used to scan in The 3D data used for printing must be a solid
some usually trivial (typically broken) part and then model, a closed vessel. It must be leak-proof.
that part is reproduced through 3D printing. Think of this as modeling your house for
A perfectly reasonable idea. 3D printing.
And, this line of thought is correct. It is certainly You may have modeled the front side but what
where we are headed. about the back side and all in between. Modeling
only one “open” side would not make sense to a 3D
But, in practice, and for most applications there is a printer.
lot more involved in the process. In many cases this A solid model is that which fully describes the
approach is simply too impractical, too difficult. model in 3D space from all angles. Anything less
3D scanning can be (very) complex. than that is in the strictest sense not a 3D model,
In many ways it is still an art. it’s not solid. It is certainly possible to force close a
This is certainly true when high levels of accuracy model, assuming that these closed sides are not of
are needed. Also, preparing the data for 3D printing interest.
can be equally, if not more, complex.
Sure, there are exceptions. But these are usually
not the rule.
MERGE
ADD
3D SCAN SOLID MODEL SIZE, SLICE
DECIMATE INTEGRITY ORIENTATE SUPPORT
DATA MODEL CHECK MATERIAL LAYERS
SMOOTH G-CODE
3D
EDIT
PRINTER
3D
GRAPHICS
CAD
POST PROCESS
G-Code
CNC, NC, Numerical control. CAM. 3D printers fall
back on this old but certainly not outdated machine
code called G-Code.
A standard in the manufacturing industry.
G-Code is simply lines of “move to” type
instructions with added control and auxiliary control
information such as speed, compensation rules etc.
This data is what is sent to the printer to control it
in the Main Process.
34 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Programming the Microsoft Kinect in Pascal (Continuation 2)
NUI_IMAGE_TYPE_COLOR a color image. The second parameter is a pointer to a
NUI_IMAGE_TYPE_COLOR_INFRARED an infrared image NUI_SKELETON_FRAME structure. On return, it points to a
NUI_IMAGE_TYPE_COLOR_RAW_BAYER record that describes the tracked skeletons.
a Raw Bayer color image (RGB) It is described as follows
NUI_IMAGE_TYPE_COLOR_RAW_YUV
a YUV color image; no conversion to RGB32. NUI_SKELETON_FRAME = record
NUI_IMAGE_TYPE_COLOR_YUV liTimeStamp: int64;
a YUV color image, converted to RGB32 dwFrameNumber,
NUI_IMAGE_TYPE_DEPTH a depth image. dwFlags: DWORD;
NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX vFloorClipPlane,
a depth image with player index encoded in the map. vNormalToGravity: Vector4;
SkeletonData : array[0..5] of NUI_SKELETON_DATA;
Various streams can be opened to capture data from the end;
same device, but the capture of depth images must be
enabled when initializing the device. The interesting data is the last structure, an array of 6
The resolution of the image can be specified in the NUI_SKELETON_DATA records. The limit of 6 skeletons is
eResolution parameter, which can have one of the values hardcoded: the kinect tracks at most 6 players (A constant
exists which describes this limit: NUI_SKELETON_COUNT).
NUI_IMAGE_RESOLUTION_80x60, The 6 elements of the array are always present, even if
NUI_IMAGE_RESOLUTION_320x240,
less skeletons have actually been detected:
NUI_IMAGE_RESOLUTION_640x480 or
Each skeleton is described by the following record:
NUI_IMAGE_RESOLUTION_1280x960.
NUI_SKELETON_DATA = record
The dwImageFrameFlags parameter can be used to
eTrackingState: NUI_SKELETON_TRACKING_STATE;
specify some flags when capturing images, it accepts the
dwTrackingID,
same values as used in the dwEnrollmentIndex,
NuiImageStreamSetImageFrameFlags function. dwUserIndex: DWORD;
The hNextFrameEvent parameter is the handle of the Position: Vector4;
event that must be triggered when a new frame is ready.
SkeletonPositions: array[0..19] of Vector4;
Finally, the phStreamHandle is the handle of the image eSkeletonPositionTrackingState: array[0..19] of
stream that must be used in the NUI_SKELETON_POSITION_TRACKING_STATE;
NuiImageStreamGetNextFrame calls to read the image. dwQualityFlags: DWORD;
The NuiImageStreamSetImageFrameFlags method can be end;
used to modify the flags passed in the
dwImageFrameFlags parameter to The eTrackingState field describes whether the record
NuiImageStreamOpen: actually describes a skeleton.
The exact meaning of these parameters can be found in the If the last 3 bits are nonzero, then the pixel is considered
NUI API documentation on MSDN. part of a player’s body. The player index can be used for
example to color the corresponding pixels in a player-
Interpreting depth image data specific color.
If a depth image is requested, the INuiSensor’s method
NuiImageStreamGetNextFrame can be used to retrieve Putting everything together
the actual depth image. It is declared as follows: After the long description of the Kinect (Natural User
Function NuiImageStreamGetNextFrame(hStream:THandle; Interface) API, a small demonstration application will clarify
dwMillisecondsToWait : DWORD; things a bit.
pImageFrame : PNUI_IMAGE_FRAME) : HRESULT;
The hStream handle is an image stream handle created using the The sample application:
NuiImageStreamOpen function. Similar to the • Connects to the first found Kinect sensor.
NuiSkeletonGetNextFrame function, the • Requests and displays skeleton frames and a depth
dwMillisecondsToWait specifies a timeout, in case the image image stream.
is not yet ready. • Uses events to get a notification when the next frames
On return, the location pointed to by pImageFrame will be filled
are ready.
with a NUI_IMAGE_FRAME record:
• Displays the depth image with a specific color for all
NUI_IMAGE_FRAME = record players, and superimposes on that, for the first
liTimeStamp : int64; detected player, shapes representing the hands and head.
dwFrameNumber: DWORD; • Allows to set/get the camera elevation angle.
eImageType : NUI_IMAGE_TYPE;
eResolution : NUI_IMAGE_RESOLUTION;
pFrameTexture: INuiFrameTexture; The program is written in Lazarus, but it should work
dwFrameFlags : DWORD; equally well in Delphi. It is a simple form, with 2 panels,
ViewArea : NUI_IMAGE_VIEW_AREA; some controls, and 3 shapes on it. The OnCreate event
end; handler is used to initialize some variables and connect
The pFrameTexture field contains an INuiFrameTexture to the kinect:
interface that can be used to examine the actual frame data:
procedure TMainForm.FormCreate(Sender: TObject);
INuiFrameTexture = interface(IUnknown) begin
['{13ea17f5-ff2e-4670-9ee5-1297a6e880d1}'] FESkeleton:=INVALID_HANDLE_VALUE;
Function BufferLen: integer; FEDepth:=INVALID_HANDLE_VALUE;
Function Pitch: integer; FSDepth:=INVALID_HANDLE_VALUE;
Function LockRect(Level: UINT; LoadNuiLibrary;
pLockedRect: PNUI_LOCKED_RECT; TBAngle.Min:=NUI_CAMERA_ELEVATION_MINIMUM;
TBAngle.Max:=NUI_CAMERA_ELEVATION_MAXIMUM;
pRect: PRECT;
if not InitKinect then
Flags: DWORD ): HRESULT; ShowMessage('Could not initialize kinect!');
Function GetLevelDesc(Level : UINT; For I:=1 to 6 do
out desc : NUI_SURFACE_DESC): HRESULT; FPlayerColors[i]:=clWhite;
Function UnlockRect(Level: UINT): HRESULT; end;
end;
36 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Programming the Microsoft Kinect in Pascal (Continuation 4)
The variables FESkeleton and FEDepth are the events NuiImageResolutionToSize(ImageResolution, w, h);
used to receive notifications when the skeleton and depth ClientWidth := w;
ClientHeight := h;
frames are ready. The FSDepth variable will contain a
FBDepth:= TBitmap.Create;
handle for the depth image stream. FBDepth.Width := w;
The range of the camera’s elevation angle is determined by FBDepth.Height:= h;
the NUI_CAMERA_ELEVATION_MINIMUM and Result:=true;
NUI_CAMERA_ELEVATION_MAXIMUM constants if Not Failed
(-27 and 27, respectively), these values are used to initialize a (FKinect.NuiCameraElevationGetAngle(@A))
then TBAngle.Position:= A;
track bar control which can be used to set the angle of the end;
Kinect’s camera. Lastly, an array of colors is initialized to
The last statements retrieve the elevation angle of the
show the players on the depth map.
kinect’s camera, and initialize a trackbar (TBAngle) with the
After loading the Kinect library, the InitKinect function
current position of the camera.
is called to actually initialize everything:
The TEventDispatcherThread is a thread descendant (in
function TMainForm.InitKinect (EnableNear :
Boolean = False): boolean; the EventDispatcherThread unit) which simply loops
var w,h : DWord; C,i : integer; and waits for kinect events. When a kinect event is detected,
NS : INuiSensor; E : Int64; a windows WM_USER message is sent to the main form.
begin
Result:=false;
FKinect := nil; This is one way of handling the events, another way would
if Failed(NuiGetSensorCount(C)) then exit; be to use the OnIdle event of the application, or a timer, to
I:=0;
While (FKinect=Nil) and (i<C) do check for new events. Instead of sending a message,
begin it is also possible to use the Synchronize or Queue methods
if Not Failed(NuiCreateSensorByIndex(i,NS))
then to let the main thread respond to the arrival of new data.
if (NS.NuiStatus=S_OK) then FKinect:= NS; The thread’s Execute method looks very simple:
Inc(I); procedure TEventDispatcherThread.Execute;
end; begin
if not Assigned(FKinect) then exit; if (FHWnd=INVALID_HANDLE_VALUE) or
if Failed(FKinect.NuiInitialize(NUIOptions)) (FESkeleton=INVALID_HANDLE_VALUE)
then then exit;
begin
FKinect:=Nil; While not terminated do begin
Exit; if (WaitForSingleObject(FESkeleton,50)=
end; WAIT_OBJECT_0)
then
This code is pretty straightforward, it requests the number begin
of kinect devices, and connects to the first available one. SendMessage(FHWnd,WM_USER,MsgSkeleton,0);
If none was detected, it exits. The first available sensor is ResetEvent(FESkeleton);
then initialized, the NUIOptions constant is defined as: end;
if (WaitForSingleObject(FEDepth,50)=
Const
WAIT_OBJECT_0)
NUIOptions = then
NUI_INITIALIZE_FLAG_USES_SKELETON or begin
NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX; SendMessage(FHWnd,WM_USER,MsgDepth,0);
After the kinect was initialized, an event handle is created, ResetEvent(FEDepth);
and used to enable skeleton tracking: end;
end;
FESkeleton:= CreateEvent(nil,True,False,nil); end;
FKinect.NuiSkeletonTrackingEnable( If an event is received on either of the 2 handles,
FESkeleton,SkeletonOptions); a WM_USER message is sent to the main form, which will
then take appropriate action. The message parameters are
SkeletonOptions is a constant requesting seated support defined as constants. Note that the event is reset after the
and near range. The next thing to do is request a depth message is sent.
image, again using an event handle to get notifications: Reacting on the messages is done by implementing a
message handler method in the form for the WM_USER
FEDepth:= CreateEvent(nil,true,false,nil);
if Failed(FKinect.NuiImageStreamOpen( message:
ImageOptions, ImageResolution,0,2, procedure TMainForm.eventDispatcher(var msg:
FEDepth, FSDepth)) then TMessage);
Exit; begin
if EnableNear then if (msg.WParam=msgSkeleton)
if then OnNewSkeletonFrame
Failed(FKinect.NuiImageStreamSetImageFrameFlags else if (msg.WParam=msgDepth)
(FSDepth,ImageStreamOptions)) Exit; then OnNewDepthFrame;
DoTick(msg.WParam=msgDepth);
If all went well, a thread can be set up to check for events end;
on the FESkeleton and FEDEpth The message handler method simply examines the message
FTEvents:= parameter and calls the appropriate method to deal with
TEventDispatcherThread.CreateDispatcher(Handle, the message. After that it executes a tick, which collects and
FESkeleton, displays some statistics.
FEDEpth); The actual work is done in OnNewSkeletonFrame and
Lastly, the size of the depth image is used to create a OnNewDepthFrame. The first one is responsible for
bitmap (which will be used to draw the depth image) and set drawing the head and hand joints in the skeleton frame.
the width and height of the form: FEDEpth. It examines the received data and positions 3 shapes on the
form:
Nr 5 / 2013 BLAISE PASCAL MAGAZINE COMPONENTS
DEVELOPERS 4 37
Programming the Microsoft Kinect in Pascal (Continuation 5)
procedure TMainForm.OnNewSkeletonFrame; procedure TMainForm.OnNewDepthFrame;
var i : integer; fr : NUI_SKELETON_FRAME; Const
PSD : PNUI_SKELETON_DATA; DS = NUI_IMAGE_PLAYER_INDEX_SHIFT;
tsp : NUI_TRANSFORM_SMOOTH_PARAMETERS; MINS = NUI_IMAGE_DEPTH_MINIMUM_NEAR_MODE shr DS;
begin MAXS = NUI_IMAGE_DEPTH_MAXIMUM_NEAR_MODE shr DS;
FillChar(fr,sizeof(NUI_SKELETON_FRAME),0); var
if Failed( IFDepth : NUI_IMAGE_FRAME;
FKinect.NuiSkeletonGetNextFrame(0,@fr)) FT : INuiFrameTexture; lck : NUI_LOCKED_RECT;
then Exit; depth : pword; CD : Word; p : byte;
PSD:= Nil; x, y : integer; w, h, GS : cardinal;
I := 0; C : TCanvas;
begin
While (PSD=Nil)and (i<NUI_SKELETON_COUNT) do if (FSDepth=INVALID_HANDLE_VALUE) then Exit;
begin if Failed(
if (fr.SkeletonData[i].eTrackingState = FKinect.NuiImageStreamGetNextFrame(
NUI_SKELETON_TRACKED) then FSDepth,0,@IFDepth)) then Exit;
PSD:= @fr.SkeletonData[i]; NuiImageResolutionToSize(
Inc(I); IFDepth.eResolution, w, h);
end;
if Not Assigned(PSD) then Exit; The above code retrieves the image from the kinect and calculates
This code fetches the next NUI_SKELETON_FRAME a width and heigh with it. The next step is to retrieve the image
structure from the kinect, and initializes data from the INuiFrameTexture interface:
a pointer to the first skeleton (PSD). The following step is
’smoothing out’ the received try
coordinates: FT:=IFDepth.pFrameTexture;
if not assigned(FT) then Exit;
With tsp do begin if Failed(FT.LockRect(0,@lck,nil,0))
fCorrection := 0.3; then Exit;
fJitterRadius:= 1.0; try
fMaxDeviationRadius:= 0.5; if lck.Pitch<>(2*w) then Exit;
fPrediction := 0.4; depth:=lck.pBits;
fSmoothing := 0.7; The following steps transfer are a loop over the depth
end; data, transferring it as a grayscale to the bitmap.
if Failed(FKinect.NuiTransformSmooth(@fr,@tsp))
The depths that have a player index in them are
then Exit;
transferred to the bitmap using the player’s color.
And finally, the 3 shapes are positioned
If there is no player index, the depth value is
ShowJoint(SHead,PSD,NUI_SKELETON_POSITION_HEAD); transformed to a grayscale value ranging from 0 to
ShowJoint(SLeft,PSD,NUI_SKELETON_POSITION_HAND_LEFT);
ShowJoint(SRight,PSD,NUI_SKELETON_POSITION_HAND_RIGHT); 255. Note that the bitmap canvas is locked for better
end; performance:
The ShowJoint will check if the requested joint position (the C:=FBDepth.Canvas;
third parameter) was tracked, and if so, position the shape so C.Lock;
it is centered on this position. for y:=0 to h-1 do
To position a shape on the depth bitmap, the skeleton for x:=0 to w-1 do
coordinates must be transformed to an X,Y position on the begin
depth image. This can be done with the aid of the CD:=Depth^;
NuiTransformSkeletonToDepthImage function: P:=(CD and NUI_IMAGE_PLAYER_INDEX_MASK);
if (P<>0) then
Procedure NuiTransformSkeletonToDepthImage(
C.Pixels[X,Y]:=FPlayerColors[p]
vPoint : TVector4;
out fDepthX : single; else if (CD>=NUI_IMAGE_DEPTH_MINIMUM_NEAR_MODE)
out fDepthY : single; and
eResolution : NUI_IMAGE_RESOLUTION); (CD<=NUI_IMAGE_DEPTH_MAXIMUM_NEAR_MODE) then
begin
GS:=Round(((CD shr ds) - MINS) / MAXS * 255);
It receives a position vector, and a resolution. It returns an X,Y GS:=GS and $FF;
coordinate which is a coordinate on a depth bitmap GS:=GS or (GS shl 8) or (GS shl 16);
corresponding to the given resulution. C.Pixels[X,Y]:=GS;
All this is used in the ShowJoint function: end
else
Procedure TMainForm.ShowJoint(S : TShape; PSD : C.Pixels[X,Y]:=clBLack;
PNUI_SKELETON_DATA; HI : Integer); Inc(depth);
Var end;
x, y : single; C.Unlock;
begin
S.Visible:= Lastly, the retrieved depth image data is released,
PSD^.eSkeletonPositionTrackingState[HI]= and the bitmap is drawn on the panel:
NUI_SKELETON_POSITION_TRACKED;
if S.Visible then finally
begin FT.UnlockRect(0);
NuiTransformSkeletonToDepthImage( end;
PSD^.SkeletonPositions[HI],x,y, finally FKinect.NuiImageStreamReleaseFrame(
NUI_IMAGE_RESOLUTION_640x480); SDepth,@IFDepth);
S.Left:=Round(x)-(S.Width div 2); end;
S.Top:=Round(y)-(S.Height div 2); PImage.Canvas.Draw(0,0,FBDepth);
end; For X:=0 to PImage.ControlCount-1 do
end; if PImage.Controls[x] is TShape then
Finally, the depth image must be rendered: for this, the depth PImage.Controls[x].Repaint;
image needs to be interpreted and transferred to a bitmap, and end;
then the bitmap is drawn on a panel:
38 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Programming the Microsoft Kinect in Pascal (Continuation 6)
Finally, to make sure the shapes representing the head and Note that the trackbar position is reversed; it is positioned
hands are properly shown, they are repainted. vertically, with the minimum value (-27) at the top, and the
The form contains some logic to display a tick and average maximum value (27) at the bottom of the trackbar.
frames per second count, and to set the colors of the Everything put together, the running program results in a
players. This logic is not relevant to the operation of the figure like figure below.
kinect.
Conclusion
However, the routine to set the camera’s elevation angle
The kinect is a device which is one way of
needs some explanation: Setting the elevation angle of the
implementing a Natural User Interface: use the
camera takes some time: it is a mechanical operation,
human body to control the computer. While it is
involving a small motor inside the kinect. Setting a new
position while the previous position was not yet originally aimed at gaming, there may be
specialized uses for this device outside the
established, will result in an error. It is therefor important
not to send commands too often or too much. The gaming industry. For a more fine-grained control
following code attempts to do that: of the computer, the resolution of the Kinect’s
procedure TMainForm.TBAngleChange(Sender: TObject); skeleton detection is not fine enough:
Var A : Longint; it cannot detect individual fingers of the hand.
begin This gap may be better filled by the Leap Motion
If TBAngle.Position=FLastPosition then Exit; device.
If Not Failed
Both devices are available to Object Pascal
(FKinect.NuiCameraElevationGetAngle(@A))
then begin programmers, and there are certainly Object
if (A<>-TBAngle.Position) then Pascal game programmers that will consider the
A:=-TBAngle.Position; ability to use the kinect a nice addition to their
begin
if Failed
(FKinect.NuiCameraElevationSetAngle(A))
then ShowMessage(Format(SErrSetAngle,[A]));
FLastPosition:=-A;
end;
end
else
ShowMessage(SErrGetAngle);
end;
Figure 8: Turning
Figure 5: SmartMS-design
procedure TGameOfLifeEngine.SetArea(X,Y:Integer);
var Index: Integer;
begin
FNumCellsX := X;
FNumCellsY := Y;
FArea.SetLength(FNumCellsY + 2);
// include two wrap-around lines
for Index := 0 to FArea.Length - 1 do
FArea[Index].SetLength(FNumCellsX + 2);
// include two wrap-around columns
end;
FastReport
(and the devices that support them) continues to move ahead people will wait for quite some updates to engage again?
on their own pace of innovation and release schedules. We have our public roadmap that list some of the work we
This makes it harder to synchronize with all of them on a are doing with general time frames - for example
specific schedule. We must also work to support the C++Builder iOS and Android support this Winter.
has managed to create a Beta
release cycles of our R&D efforts and also the platforms ThereVersion for parts
are so many moving lazarus.
in the IDEs, compilers,
If you buy the full source codeFM,(for
themselves. RTL andDelphi) you
device/platform will
support thatbe able
it is not easy to
It was easier in the past, when we were a Windows predict when we will release updates, new versions and
to install the first even
only development product, to follow the cycles of
working
new products.
version.
Windows releases. We have allready tested it.
48 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
For example, we had originally planned to have Delphi for David I
iOS support in XE3, but we needed more time to complete I see no dangers anywhere. We are providing capabilities
the ARM compiler and mobile language enhancements, for all developers whether they are mature, youngsters or
so these were released when ready in April of this year. newbies. We are the only native code, optimizing compiler,
rapid application prototyping developer tools company on
Our first Android support was ready in the summer, the planet that offers one code base,
so it fit our "traditional" fall release. Some developers will multi-device targeting that is on all of the major platforms
remember the early years of Turbo Pascal and Borland Windows, OS X, iOS and Android.
Pascal where we released versions sometimes in
Springtime, sometimes when a new release of DOS or We give you a wide range of RTL functions and
Windows appeared, and even Turbo Pascal for the Mac components to improve your ability to build apps for the
that was released separately on its own schedule. platforms and at the same time we allow you to go right
down to the operating system and the hardware if you
Some of our customers have waited for releases that need to.
had capabilities they are interested in or need.
Other developers keep up with every release. We have been doing these things since the days of DOS
Some developers are still using very old Windows only (Turbo Vision) and Windows (Object Windows, VCL and now
versions of Delphi (3, 5, 6, 7, 2007, etc). FM). We know how to abstract the OS and hardware
If Microsoft dropped 32-bit from Windows, I would expect without keeping developers out of those spaces.
that most Delphi developers would move forward to use Everything, the APIs, devices, sensors, operating system
the 64-bit Delphi compiler. and other hardware features are available for your
programs.
We are very proud that our products are so good, that
they can be used for years and support multiple versions of We have millions of seasoned professionals and new
the platforms including Windows, OSX, iOS and Android generations of young developers using our products
versions. around the world..
Since we don't time out the releases that developers
purchase, it is their choice when to move forward. This year, South Africa has chosen Delphi as the
We continue to move forward and new and existing standard programming language and product for their
developers are coming with us. At the same time, we will High Schools computer classes. This means that a new
continue to help developers understand how to use the generation of South African students will learn object
current and latest capabilities for development. programming, component based development, event
programming and rapid application prototyping in high
Our DocWiki online documentation system and our school and will be well prepared for college and industry.
community/partner ecosystem still have all of the
information going back multiple versions, so developers Editor
can get the information they need to be successful Will you go there?
regardless of what version they are using.
David Intersimone :
We will keep innovating; continue to support the latest I have been to South Africa one time before and am
platforms and development technologies across all of the planning on going again sometime early next year.
platforms: Windows, OS X, iOS and Android. If someone I look forward to my return visit to South Africa.
is doing Windows only development on an older Windows
version, they can keep using the latest versions products. Thanks David!
Editor:
Is there no danger that youngsters or newbies will just go
for the new possibilities especially in the so called new
markets like the far (or closeby) east?
50 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
A simple superscript text editor (Continuation 1)
procedure setplaintext; procedure kbLeft;
begin begin
with form1 do activecontrol := edit1; if cursX > 1 then
clearcursor; //verwijder cursor uit paintbox begin
plaintext := true; dec(cursX);
end; if cursX - bias < 1 then dec(bias);
setmodeF;
procedure setsupertext; end;
begin
end;
form1.activecontrol := nil;
clearcursor; //must be painted later If the cursor exits the box at the left side, the bias must be
plaintext := false; adjusted to keep the cursor inside.
end;
To paint superscript text in the paintbox, setmodeF sets variabele upmode true or false, which takes
in design time it's properties are set to care of the vertical cursor position. In this case, the right
width 660
height 30
character position (F = forward) is copied.
font "courier new", procedure kbBack handles the backspace:
font size 12
font style bold procedure kbBack;
which yields a fixed character width of 11 pixels. var i : byte;
The formulabox may display 60 characters at the time. begin
if cursX > 1 then
However, a formula may be much longer,(up to 250
begin
characters) , in which case part of the formula is outside the for i := cursX-1 to textlength-1 do
box. The number of characters left outside the box is formtext[i] :=
var bias : byte; formtext[i+1];
Also the position of the cursor has to be remembered formtext[textlength].ch := #0;
var cursX : byte; dec(textlength);
Accuracy is important: dec(cursX);
cursX has the index of the character where it is placed if cursX - bias < 1 then dec(bias);
before in the text. Far left of the paintbox is position 1. setmodeB;
end;
end;
Translation of superscript- to plain text means inserting ^(
when the vertical cursor position changes from low to hi
and inserting ) when to position changes to low again.
Bits 0 and 1 of variabele m encode the current position (bit
0) and previous position (bit 1), where low = 0 and hi = 1.
m = 1 decodes as the transition from low to hi, m = 2
translates for hi to low.
A case statement investigates m.
The value of the bias has to be added for the final cursor
position. Then, the cursor may be in the lower or upper Opposite, when translating plain text to superscript text,
position which is remembered by the text must be scanned for ^(
var upmode : boolean = false; to detect a low to hi transition. Then the number of
For each character in de string, the position (up, down) parenthesis must be counted : +1 for ( and -1 for ).
must be recorded. Occurrence of a ) together with a zero count detects a hi
The text has a special dataformat to facilitate this: to low transition.
const charwidth = 11;
maxchardisplay = 60; //characters displayed in A mousedown event in the paintbox calls
paintbox
maxchar = 250; //max length of text
Formulabox.mousedown(...) which delivers the (x,y)
type TFormText = record coordinates. These are used to calculate the cursorpositie
ch : char; //character code in de superscript text:
up : boolean; //position
var hh : byte;
end;
begin
var formtext : array[1..maxchar] of TFormText; hh := bias + ((x+4) div charwidth) + 1;
textlength : byte = 0; //number of characters in text if hh > textlength + 1
then hh := textlength + 1;
Procedure showformula paints the complete line of cursX := hh;
text. For each character if cursX <= textlength
procedure paintchar(n) is called to paint character n then upmode := formtext[cursX].up
of the text. Please look at the source code for details. else upmode := false;
The specific procedures for cursor control are small but Please refer to the source code for more details.
tricky because accurate counting is needed. This concludes the description of this small editor project.
Below is procedure kbLeft to advance the cursor 1 place to
the left.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE COMPONENTS
DEVELOPERS 4 51
Interview with Gwan Tan - better office
So I did my final thesis with that faculty working on my
Gwan Tan is the owner of “better office”.
own project for a Chinese consumer word processor
We met a long time ago and had several times
very interesting discussions – especially after although I speak no Chinese.
meetings of first time Delphi presentations.
Editor
Because we want to present very interesting
Was there any follow up for this Chinese consumer?
people we meet and their companies using Delphi
and Pascal we want to know their opinion about
Gwan
the market, developments and critical annotations.
Unfortunately not.
At the end of my project 1989-1990 China had the 'incident'
at the Tiananmen Square.
That caused China to close down for a number of years.
As the targets were the consumers in China any further
investment was considered too risky. So the project 'died'
after demonstrating the first prototype.
Editor
So international situations CAN change the personal
history. Now you have your own company?
Editor Gwan
Could you give some details about you and your company? Well, I started my first company with two friends just after
my study in 1989/1990.
Gwan That company was called Co-Assist and we did a lot of
Well, I'm 54 years old. I started programming at university work with Turbo Pascal but later mostly with Paradox
(TUE) in 1977. There we learned languages like ALGOL60 (Borland's database program at the time).
and APL and programming on punch cards for a
In 1997 I left Co-Assist and founded “Sibylle IT”.
Burroughs mainframe. Later in the study (electrical
A number of years ago I also started a cooperation with a
engineering) I learned Pascal especially Turbo Pascal.
German company called better office.
Turbo Pascal was actually the first program I bought
That cooperation led to founding the company better
legally because of the amount of handbooks coming with
office benelux and is a cooperation between better
it. Copying them at the time was almost as expensive as
office (Germany), Sibylle IT and PSO (Jeroen
buying the program.
Pluimers'company).
Editor
Editor
What does TUE mean ?
How did this cooperation start?
It is unusual that people have interest beyond the border –
Gwan
other countries.
Technical University Eindhoven (at that time it was the THE
Technische Hogeschool EIndhoven).
Gwan
Well it started actually at a BorCon (Borland Conference) in
Editor
the USA. I met the owners/directors of better office and on
The technical part of it, was that of any influence?
personal level we had a click.
Gwan At the time I had no direct interest in doing any work in
Electrical engineering is only taught at technical Germany. But as I got an invitation for a Christmas Party, I
universities. decided to go to Oldenburg (city in the north of Germany near
At technical Universities you can achieve the engineers title Bremen).
(Ir.) At general universities you can become a Master of So the contact was continued on German soil. With these
Science, (M Sc) Docterandus in Dutch (Drs.) first contacts I met other German developers (and
But electrical engineering is mostly an applied science. publishers) and I was invited to the first German Delphi
The more theoretical study is physics. Conference (EKON).
I always liked the applied sciences more than theory. During the years I got more and more involved with
In the beginning I wanted to specialize in data German developers and slowly got some clients there too.
communications. But during a training period in the USA As the German market was getting more and more
I got to my hands on the first Apple Macintosh and found interesting and there seemed to be good opportunities for
programming a much more interesting area, Dutch developers I sought a more structured approach to
especially with user interfaces and databases. the German market leading to better office benelux.
Back at university I continued in that area,
Editor
also as at the time there was no department of Information
So now I understood you are mainly working in Germany.
Technology.
What special fields of interest?
That area was divided between the mathematical
department and the electro-technical department (faculty of
Digital Systems).
52 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Interview met Gwan Tan - better office (Continuation 1)
Gwan Gwan
In the past years our main projects are in Germany What do you mean with combine with? We have done
although we still have an old client with sites in the projects were we have Pascal clients with Java middleware
Netherlands, Ireland, France, Germany and Thailand. etc. Is that what you want to know?
In Germany we specialize on ERP support programs.
We have a production planning system running in Editor
cooperation with the clients SAP system. Yes. And the databases?
We also have a system running with another client that
handles the sales departments, warehouse and distribution Gwan
of the clients main German factory. As for databases we mostly use InterBase or Firebird if we
have the freedom of choice. Often we have to connect and
Editor develop with MySQL, SQL Server or Oracle. Basically we
so you're mainly doing business with larger contractors? know InterBase and Firebird best but we are extending our
Is there any specializing like mobile or is that a new field? knowledge and experience in MySQL and SQL Server as
often clients expect us to work with these databases.
Gwan
We work most of all directly for a client. Editor
As our programs are often connected with the backbone Do you have a project that you are especially proud of?
systems of the client we have to cooperate with their other
contractors. Gwan
In the future one can foresee much more use of tablets, Depends on what to be proud of. Technically I'm very
handhelds and smartphones even within the factory. proud of the first production planning system we
The information people need during a production is not developed for a customer in Mainz.
confined to a working place and therefore to a single On a more general level I'm very proud of 'having
computer. survived' so far in the ERP-project just across the border.
The people need to have their info with them like they That project was partly challenged for technical reasons
have their wireless company phones. but even more for organizational reasons.
Within a warehouse it a already more obvious and the At the start the requirements seemed to be quite good
sales departments especially those sales persons in the defined but as the project went on it became clearer the
field need to be connected with the main company system. requirements were not as complete as expected.
So we are looking the area of mobile computing and are So much more interaction with the users was necessary to
therefore very interested in the (future) extensions of find out what had to be developed.
Delphi and Pascal in general. In the meantime especially top management put a lot of
At present our clients are not ready for mobile computing. pressure on getting the program(s) up and running.
They are in the field of IT mostly conservative, Especially Edwin van der Kraan, my main developer in
needing proven technology and do not want to be this project, had a very hard time.
pioneers. But now after 2.5 years the system is up and running and
So we have some time to develop applications that will get stable. So I'm proud of the project and especially proud of
their interest growing. Edwin.
Editor Editor
So you -up to now- are actually VCL users? What are fields you would especially point at for reasons
of learning and avoiding eventual upcoming problems?
Gwan How do you respond to pressure without getting squeezed?
Yes, in our German projects we are using VCL mostly.
Especially Jeroen is investigating/playing with the newer Gwan
extensions. Pressure is at almost all projects present. But sometimes
the pressure is very high. To keep the pressure bearable
Editor one has to keep the targets of the project well defined and
I got the impression that you are doing websites as well? plan sufficient moments where especially management can
What languages do you use for this? Intraweb maybe or see the progress made and the direction of the project.
php or... Depending on the part of the project and the pressure of
management one has to plan those moments more often.
Gwan But do not forget to explain to management that such
Websites are not our main area. It is mostly our German moments cost time and therefore will slow down the
colleagues who work in that area. project. These milestones should be useful for checking the
They use mostly “php” as far as I know. I have to admit progress and direction of the project.
that I'm not right person to ask about that area.
Editor
Editor I think planning and explaining the plans are the most
If you work with Pascal what other languages you have to difficult and therefore very often underestimated subjects
combine with? for a project. Do you have any special advice or helpful
ideas for developers?
54 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Interview met Gwan Tan - better office (Continuation 3)
Editor Gwan
What's your special quality as company (in Delphi?) I have seen a demo of the Leap Motion in Leipzig at the
towards companies? Delphi Tage. I was quite impressed by its possibilities. It
does need some work to get a decent version out but I can
Gwan see some great opportunities with it.
As mentioned earlier we develop applications with I will have to think some more about itt as I have to find
databases, mostly Delphi and InterBase but sometimes possible uses for my customers. It might be interesting to
with Visual Studio and other databases like MySQL, SQL develop some managerial/marketing applications with it for
Server and Firebird. Although we have developed a instance to manipulate the views of graphs etc.
number of ERP-related applications we also have done a lot
of work for social housing corporations. Editor
We developed modules connecting with the main What's your idea of 3d printing? We want to make it
administration systems of those corporations. available for Delphi and are working hard to get that done...
We also created several applications that handle a lot of
statistical/market-oriented information and make that Gwan
information available to the corporation. I find 3D printing very interesting. I even thought about
I prefer not to give names of customers although with getting me one just to try it. Except from finding the time to
social housing corporations it would be possible. 'play' with it I did not have an immediate idea for something
Ok, there are many corporations we worked for. to print that I could use. I will first have to spend some time
to think about what I would like to print with it before
Often it is easier to mention the cities they are at as that is
mostly their working area. searching for the best 3D-printer to do the job. I am certain
The main cities are: Maastricht, Nijmegen, Eindhoven, that 3D-printing will become more and more important in
Nieuwegein, Almelo, Enschede, Ede, Dantumadeel, the future.
Woudenberg and Raamsdonks Veer. I probably have The price has gone down a lot and the capabilities of those
'cheap' printers are improving a lot. To be able to develop
forgotten several corporations but this should give an idea
applications with Delphi for it would be great as I expect
Editor customers to ask for those kinds of applications in the near
How do you feel about the upcoming updates of versions in future. So if you have something for Delphi applications
Delphi every 6 mays? with 3D-printers let me know. I would be very interested in
The next XE6 will probably be released in April... trying it.
Gwan Editor
I can understand Embarcadero needs to get a better cash Sure! In this issue we have started our first articles about
flow in order to keep investments in development possible this. There is even a possibility of creating the so called 4th
but I do not believe the market will accept updates to be dimension: Interactive layer printing...Like printing a layer
released that often. Especially if they charge the normal that is able to react towards light or temperature. One could
cost. Developers (and their bosses) will not go for each even print layers that enable special things like creating an
update and will only update if one really needs to update. interactive object with printed chips on it...
Also if updates are released that often most updates will be
actually just patches. They should be free anyway and Gwan
made available as soon as possible. So if Embarcadero is The future capabilities of 3D-printing are going to be
really going for a release schedule of once every 6 months enormous. Already some creative minds have found new
and charges for each update their standard prices I expect ways to use 3D-printing. I wonder what will be done with it
part of the market to turn away from the product. in the future. So your articles will give everyone the chance
How large that part will be is hard to predict but as the to become part of that future.
Delphi market is already small any part will hurt. I do think that that is one of the main reasons for magazines,
conferences and fora etc. to be reading and discussing the
Editor future not just what can be done today but also what might
Did you ever consider the other Pascal? FPC- combined be possible in 5 or 10 years....
with Lazarus? Have you ever tried that?
Gwan
I have not tried Lazarus yet but especially if Embarcadero
is moving to a 6 months update schedule Lazarus will be a
possible alternative. I'm sure I will have a look at Lazarus
in the next 3-6 months if only to see what it can and how
much work it would be to switch from Delphi to Lazarus.
For now I would not switch to Lazarus but it depends very
much on Embarcadero and their path for the future.
Editor
I have two final questions:
We have been busy creating the new extra userinterface,
not for everything useful but can be the Leap Motion...
Have you heard about that or seen something?
If there is a classification to make for what geo services are To get started with using Google maps, drop the TMS
being used, we'd divide these in following categories: TWebGMaps component on the form and add the code:
pti := pt.Add;
pti.Latitude := 51.2;
pti.Longitude := 4.37;
pti := pt.Add;
pti.Latitude := 51.05;
pti.Longitude := 3.7;
pti := pt.Add;
pti.Latitude := 50.85;
pti.Longitude := 4.35;
pti := pt.Add;
pti.Latitude := 51.2;
pti.Longitude := 4.37;
webgmaps1.Polylines.Add(false,false,false,nil,pt,clred,255,2,true,100);
pt.Free;
end;
Geolocation
Geolocation is the name used for all kinds of techniques • Wi-Fi service based: Similar as with cell phone based
that provide information about the location of a device. geolocation, Wi-Fi based geolocation could be an option
These days, most mobile devices have a GPS built-in and when a device is connected via a Wi-Fi access point and
this can return the longitude and latitude of the device the location of the Wi-Fi access point is known.
immediately. From Delphi XE4, the non-visual component An example of a service that collects information on the
TLocationSensor is provided that allows you to get this position of Wi-Fi access points is
information. Using TLocationSensor is easy. http://www.skyhookwireless.com
Set LocationSensor.Active = true and via the For mobile devices, typically a fallback mechanism is
event OnLocationChanged, the position is returned. used. First, there is a check if a GPS exists.
The accuracy of determining this location is around 10 When not, it can try to see if it can find a position based
metres typically. When no GPS is available, we must resort on the IP address, the connected cell phone access point or
to different techniques. These techniques can be: Wi-Fi access point. This is the mechanism that is built-in
• ISP IP address based: many ISPs have a database of these days in any HTML5 compliant browser. This allows
what IP address range is being used in what area. Services web applications to determine where the device is located
exist that gather this information that can be used to that connects to it. This is known in the HTML5 standard
retrieve location information based on an IP address. as HTML5 Geolocation API.
• Cell phone based: when a mobile device is connected
to a cell phone access point, the position of the cell phone To make it easy for desktop applications to determine as
access point is known and thus also the area the signal of good as possible the location of a machine, TMS software
this cell phone access point covers. There are also services offers a component TAdvIPLocation that uses the
that collect this information and make it accessible. FreeGEOIP service. To make it easy for IntraWeb web
OpenCellID is an example. applications, we have a component
See: http://www.opencellid.org/cell/map TTIWIPhoneGeolocation that uses the HTML5
Geolocation API to determine the location.
if AdvIPLocation1.GetIPLocation then
begin
webgmaps1.MapOptions.DefaultLatitude :=
AdvIPLocation1.IPInfo.Latitude;
webgmaps1.MapOptions.DefaultLongitude :=
AdvIPLocation1.IPInfo.Longitude;
webgmaps1.Launch;
memo1.Lines.Add(AdvIPLocation1.IPInfo.ZIPCode + ' ' +
AdvIPLocation1.IPInfo.City);
memo1.Lines.Add(AdvIPLocation1.IPInfo.CountryName);
end;
This code snippet uses the non-visual component Next, when we click on a category in the listbox, we
TAdvIPLocation to obtain the location based on the IP perform a query of the top 10 points of interests nearby the
address of the machine and shows this location on a map computer location (obtained with TAdvIPLocation) and fill
and adds the location city name, ZIP code and country in a the listbox with this info:
memo.
procedure TForm1.ListBox1Click(Sender: TObject);
var
GEO POI services id: string;
Geo POI services is the name of services that provide point i: integer;
of interest information at a specific location. This includes la,lo:double;
things as railway stations, museums, restaurants, sports begin
infrastructure etc... Typically, a service can provide a list of id := listbox1.Items[listbox1.ItemIndex];
points of interest that matches a requested category and a
specific location. It can then offer information such as id := copy(id,pos('/',id)+1, 255);
address, description, recommendations, opening-hours of
listbox2.Items.Clear;
the points of interest. Many services exist that offer this
kind of information but the main suppliers with the biggest AdvIPLocation1.GetIPLocation;
amount of information are FourSquare, Google Place, Bing orgla := AdvIPLocation1.IPInfo.Latitude;
Spatial Data Services, Factual... orglo := AdvIPLocation1.IPInfo.Longitude;
As FourSquare is one of the leading services, TMS software
has a component TAdvFourSquare that makes using this // This fills the AdvFourSquare Venues
service very easy. Typically, all we need to do is specify a collection with points of interest:
location, i.e. longitude & latitude, specify category of AdvFourSquare1.GetNearbyVenues(
points of interest we're interested in and possibly also a orgla,orglo,'','',id);
radius. The service then returns a list of points of interests
of which we can query a description, photo, etc.. // Add the summary line to a listbox
for i := 0 to advfoursquare1.Venues.Count - 1
do
To illustrate this, we'll use the component begin
TAdvFourSquare, available in the TMS Cloud Pack. To listbox2.Items.Add(
start using this component, it is necessary to first obtain a AdvFourSquare1.Venues[i].Summary);
(free) FourSquare application key and secret. You can end;
register for this at https://developer.foursquare.com end;
First we obtain the different categories and subcategories
of points of interests that FourSquare has and fill a listbox
with this:
var
i,j: integer;
id: string;
begin
AdvFourSquare1.App.Key := FourSquare_AppKey;
AdvFourSquare1.App.Secret := FourSquare_AppSecret;
AdvFourSquare1.GetCategories;
for i := 0 to advfoursquare1.Categories.Count - 1 do
begin
listbox1.Items.Add(AdvFourSquare1.Categories[i].Summary);
for j := 0 to AdvFourSquare1.Categories[i].SubCategories.Count - 1 do
begin
listbox1.Items.Add(AdvFourSquare1.Categories[i].SubCategories[j].Summary +
'/'+AdvFourSquare1.Categories[i].SubCategories[j].ID);
end;
end;
end;
Routes
A final important part in useful geo information based In this example, we use the TWebGMaps component as well
services we can consume from Delphi applications, is as the TWebGMapsDirectionList. TWebGMapsDirectionList
getting routing or directions information to travel from a is a component especially designed to visualize HTML
location A to a location B. Again, the three major suppliers formatted directions information returned by Google.
of these services are Google with the Google Directions Getting directions is as simple as calling
API, Microsoft with Bing Routes API and the Openroute WebGMaps.GetDirections with start and end address. Here
service (http://www.openrouteservice.org/). Such service we obtain directions information from San Francisco center
typically works in following way: we make a request to the Embarcadero offices in Scotts Valley
based on two locations, either specified as two sets of
longitude/latitude of two sets of addresses, the start
address and end address. The service then returns one
route or a set of routes that can be used to travel from start
point to end point. Note that some services also support
waypoints, i.e. points between the start point and end
point the route must go along. A route is typically
returned as a series of textual descriptions of the route to
follow. Each part of the route that is described is called a
leg. A set of routes can be returned when alternative routes
exist. Along the textual description, typically also polygon
data is returned and this polygon data can be used to
visualize the route on a map.
var
from_address, to_address: string;
begin
from_address := 'San Francisco';
to_address :=
'5617 Scotts Valley Dr #200, Scotts Valley, CA';
webgmaps1.GetDirections(from_address,to_address);
type type
TForm1 = class(TForm) TCallback = function (FoundData: TData): BOOL;
Button1: TButton; cdecl;
Memo1: TMemo;
Edit1: TEdit; function RegisterCallback (Callback: TCallback):
Integer; cdecl;
// Adapter
strict private As you can see, there are no user-parameter, and the only
class function
InternalEnumWindowsCallback(Wnd: HWND; lpData: argument for callback-function represents the actual data
LPARAM): Bool; stdcall; static; for the function.
Memo1.Lines.Add(Format('%8x:%s', [AWnd,
WndText]));
end;
Result := True;
end;
66 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Correcting a bad API design (Continuation 2)
The correct solution: dynamic code Creating a master template
However, an experienced programmer can offer First step: write the following dummy code:
guaranteed working version, "adding" user-parameter to
the existing "bad" API. type
This is not an impossible task as it may seem, but the TRealCallbackFunc = function(FoundData: TData;
Data: Pointer): BOOL; cdecl;
solution is not trivial.
The essence of the idea is that the user-parameter can be
function InternalCallback(FoundData: TData):
replaced by the actual function callback that will be unique BOOL; cdecl;
to each use. Thus, the call will not be identified by the var
parameter, but rather to which callback function is called. RealCallback: TRealCallbackFunc;
begin
Statement of the problem RealCallback: = Pointer ($ 12345678);
Of course, in a pre-compiled file there cannot be an Result: = RealCallback (
arbitrary number of functions for arbitrary user- FoundData, Pointer ($ 87654321));
end;
arguments. That is why this solution requires generating
code on the fly (in run-time). procedure TForm1.Button1Click (Sender: TObject);
Fortunately, it's not too difficult task because you can use begin
the services of the Delphi compiler to generate the InternalCallback (0);
template. What's more - you may not even know end;
assembler. But you need to have some idea of the memory
architecture in Windows. Here: InternalCallback - this is a callback-function, the
prototype of which is fully consistent with API.
So, suppose we have the following: TRealCallbackFunc – is a modified callback-function
with desired prototype, which is different from the API
type only by the presence of an additional parameter: it is our
TData = Integer; // just as an example, it could be
user-parameter. Although the prototype of RealCallback
// anything: a pointer, record, etc.
TCallback = can be arbitrary, but for ease of debugging, it is desirable
function(FoundData: TData): BOOL; cdecl; that he would be as similar to InternalCallback as possible.
TRegisterCallbackFunc = function(Callback: However, you can also put a user-parameter to be the first
TCallback): Integer; cdecl; parameter and change the call model from
TUnregisterCallbackFunc =
procedure(Callback: TCallback); cdecl; cdecl/stdcall to register - it will allow you to skip
var intermediate adapter in the future, if you ever want to
RegisterCallback: TRegisterCallbackFunc; make this callback-function a member of the class. In this
// imported from a DLL case, the Self will be transferred in user-parameter - thus
UnregisterCallback: TUnregisterCallbackFunc;
the signature of callback-function binary matches the
// imported from a DLL
signature of the conventional method of the class.
This is our "bad" API. Since the API is usually located in a We should just call RealCallback via fixed pointer,
separate DLL, then I made an example of a function- passing a fixed pointer as the user-parameter. The values of
variable, rather than a normal function. $12345678 and $87654321 are chosen for the simple reason
So our challenge: to add support of the user-parameter to that it will be easy to spot them in the native code. You can
the API. use any other "magic" values. Surely, in the case of 64-bit
Note: not always "bad" API is the result of developer mistake. code you need to use 16 bytes magic values instead of 8
Sometimes this kind of design is dictated by technical byte, for example: $1234567890ABCDEF and
constraints. $FEDCBA0987654321.
For example, the function SetWindowsHookEx can set The RealCallback function is not called directly, but
hooks both locally (in the current thread) and globally (for all indirectly - through a RealCallback variable. I will explain
processes in the system). below why it is done.
You simply cannot have a user-parameter with the design: So, add a direct call to InternalCallback in your code:
because the user-parameter during installation of the hook
procedure TForm1.Button1Click(Sender: TObject);
will not make sense at the time of the callback call - as the
begin
hook is called from another process. InternalCallback(0);
However, if you want to install a local trap in the same // here: TData = Integer (in this example),
process, the user-argument would be quite useful. // you can pass any value
For example, if you want to keep track of window end;
messages for some of your windows.
In this case, the hook will be limited only to your main Warning: do not run this code! This code will result in an
thread, but you may have many windows, so threadvar exception being thrown (Access Violation), because it calls
cannot help you. "something" via "trash" address. Instead, set a breakpoint
on the call of InternalCallback in Button1Click. Run
the project, click the button, stop on a breakpoint, go to the
function InternalCallback (via F7 / Step into, but do not use F8
/ Step over) and open CPU-debugger (Ctrl + Alt + C or View /
Debug Windows / CPU View). You will see the following
code:
Nr 5 / 2013 BLAISE PASCAL MAGAZINE COMPONENTS
DEVELOPERS 4 67
Correcting a bad API design (Continuation 3)
You can see that all of the code consists of four lines: begin · Address of the called function
(aka - the prologue), assigning the function to variable, (RealCallback): $12345678
the call, end (aka - the epilogue). Each line is labeled with · The value of user-parameter: $87654321
comment (in bold font), which shows the location in the
module and the code line itself. Lines below the comment All other code is static and it does not depend on anything,
is a machine (native) code that was compiled from that line. i.e. it will be exactly the same in all cases: for any user-
Do not worry, you absolutely do not need to arguments for any callback-functions.
understand what it says! All you need – is to determine the This means that if we want to generate the functions
beginning and end of the function. It is easy to do: start of themselves, such as InternalCallback, we can just
the function is at the very top and marked by a blue line. copy all the code and simply substitute two numbers: the
The end of the function is clearly in line Unit1.pas.43: end; address of the function and user-argument.
- i.e. you need to take another 6 lines after it - up to a line
Unit1.pas.46: begin - line which starts some other function. Address of the function and user-parameter is easy to spot
in machine code, since we used the magic value of
Note: If you do know assembler, then you know that the $12345678 and $87654321.
function ends in a line with a ret command (return control),
and the next line is just a garbage filler. Note:
Select the code with your mouse and copy it somewhere this is why I used the design with indirect function call
via the clipboard. Again, you do not need to run this code! instead of direct call: because in this design the call is
If you do not know assembly language, then all you need defined as "call the function at that address."
to know: There is clearly "this address" present - which means that it
The first column is the address of the instruction. can be easily changed. If the call was direct, the machine
These addresses belong to your exe (after all it is the exe code code were said to "call a function that lies in N bytes before
that we have called from Button1Click) and we are not this instruction". Altering such address would be much
interested in these. The second column: hex-code of more difficult.
machine code. That is, this is the machine code in a pure That is all that relates to parsing the template.
form, which will be executed by the processor (CPU) Please note that we did not use any special knowledge,
– and this is where we are concerned. apart from the ability to use a debugger and some common
The third column - is the assembly code corresponding to sense.
machine code. The great thing about this listing - we do Then proceed to the coding. First, you need to copy our
not need the assembly code, we need only native code. template into a byte array for later use in the program code.
Now I'll explain why... Now look: this function has only
two values of the variables:
68 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Correcting a bad API design (Continuation 4)
To do this, I first wrote out the entire machine code This weird code says that a CallbackTemplate pointer (and
(second column): any string - is a pointer, for a static array you'd have to take a
55 pointer explicitly via @) should be interpreted not as a string,
8BEC but as a function of the type TCallback.
83C4F8 So this function, therefore, should be called. Here is a
C745F878563412 // 12345678 longer version of the same code:
6821436587 // 87654321
8B4508
procedure TForm1.Button1Click (Sender: TObject);
50 var
FF55F8 Template: AnsiString;
83C408 Ptr: Pointer;
8945FC CB: TCallback;
8B45FC begin
59 Template := CallbackTemplate;
59 Ptr := Pointer(Template);
5D CB := TCallback(Ptr);
C3 CB(0);
end;
Remember, you do not need to write these codes manually
- select text within CPU-debugger with the mouse and use Set a breakpoint to the function call (in any form - long or
Ctrl + C - this will copy the selected lines to the clipboard. short), run the program, click, stop up on the breakpoint.
Then, go to the code editor and paste the text from the Do not press F7!
clipboard, and then remove the four comment lines, As we have no source code for the function encoded in
as well as the first and last columns, leaving only native CallbackTemplate, the debugger will try to execute the
code. whole function in a single pass - which will lead to Access
Note that x86 is little-endian architecture - which means Violation, as both of our pointer ($12345678 and
that the numbers "are written in reverse." $87654321) point to trash. Instead, call the CPU debugger
Of course, you do not have to change the byte order. (Ctrl + Alt + C or View / Debug Windows / CPU View), and
hit the F7 key few times - until you will be transferred to
After that, I removed the line breaks, combining all the the familiar code:
machine code in a long stream of bytes:
558BEC83C4F8C745F87856341268214365878B450850FF55F8 005B5C70 55 push ebp
83C4088945FC8B45FC59595DC3 005B5C71 8BEC mov ebp, esp
005B5C73 83C4F8 add esp, - $ 08
After that I put in the #$ every two characters and 005B5C76 C745F878563412 mov [ebp-$ 08], $
designed this constant as a string: 12345678
005B5C7D push $ 6821436587 87654321
const 005B5C82 8B4508 mov eax, [ebp + $ 08]
CallbackTemplate: RawByteString = 005B5C85 50 push eax
005B5C86 FF55F8 call dword ptr [ebp-$ 08]
#$55#$8B#$EC#$83#$C4#$F8#$C7#$45#$F8#$78#$56#$34# 005B5C89 83C408 add esp, $ 08
$12#$68#$21 + 005B5C8C 8945FC mov [ebp-$ 04], eax
005B5C8F 8B45FC mov eax, [ebp-$ 04]
#$43#$65#$87#$8B#$45#$08#$50#$FF#$55#$F8#$83#$C4#
005B5C92 59 pop ecx
$08#$89#$45 +
#$FC#$8B#$45#$FC#$59#$59#$5D#$C3; 005B5C93 59 pop ecx
005B5C94 5D pop ebp
005B5C95 C3 ret
Why is the string?
Well, it's easier and shorter than the declaring array of
Make sure that this piece is exactly the same as the source
bytes: no need to insert spaces, commas, specify the
code from the screenshot above. If there is a match, then
dimension of the array.
you've done everything correctly, the template is ready. If
One-byte string, no encoding (RawByteString - it is
not - correct and be more careful in the future.
AnsiString in older versions of Delphi) - so this string is
The code to call RealCallback and InternalCallback can be
actually an array of bytes.
removed. We have our hands on the machine code - which
Now it would be a good idea to check that we have not
is what we needed.
made a mistake.
Change the button click handler as follows:
Note:
of course, the machine code is specific to the target CPU.
procedure TForm1.Button1Click (Sender: TObject);
begin Machine code for x86-32 will not run on x86-64 and ARM
TCallback(Pointer(CallbackTemplate))(0); (and vice versa). That is why, if you are writing cross-
end; platform code, you will need to repeat the operation for
each target platform and create a constant for each
platform. For example, for x86-64, we get:
const
CallbackTemplate: RawByteString =
{$IFDEF CPUX86}
#$55#$8B#$EC#$83#$C4#$F8#$C7#$45#$F8#$78#$56#$34#$12#$68#$21#$43#$65 +
#$87#$8B#$45#$08#$50#$FF#$55#$F8#$83#$C4#$08#$89#$45#$FC#$8B#$45#$FC + #$59#$59#$5D#$C3;
{$ENDIF}
{$IFDEF CPUX64}
#$55#$48#$83#$EC#$30#$48#$8B#$EC#$89#$4D#$40#$48#$B8#$EF#$CD#$AB#$90 +
#$78#$56#$34#$12#$48#$89#$45#$20#$8B#$4D#$40#$48#$BA#$21#$43#$65#$87 +
#$BA#$DC#$FE#$00#$FF#$55#$20#$89#$45#$2C#$8B#$45#$2C#$48#$8D#$65#$30 + #$5D#$C3;
{$ENDIF}
Note:
you can do it even for ARM (iOS and Android), but for that you need to run your code on a real device, not in the
emulator (which uses x86). Unfortunately, I have no iOS or Android devices, so I cannot give you an example with native
ARM code.
70 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Correcting a bad API design (Continuation 6)
unit FixCallbacks;
interface
implementation
uses Windows;
{$IFDEF CPU386}
{$DEFINE CPU32}
{$ENDIF}
{$IFDEF CPUX64}
{$DEFINE CPU64}
{$ENDIF}
{$IFDEF CPUARM}
{$DEFINE CPU32}
{$ENDIF}
var OrgPtr: Pointer; OrgSize: Cardinal; Ptr: PPointer; ActualTemplate: RawByteString; Dummy: Cardinal;
begin
Result := nil; ActualTemplate := ATemplate;
OrgSize := Length(ATemplate);
OrgPtr := VirtualAlloc(nil, OrgSize, MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE);
if not Assigned(OrgPtr) then Exit;
try
Ptr := OrgPtr;
StrReplace(ActualTemplate,
{$IFDEF CPU32}$12345678{$ENDIF}{$IFDEF CPU64}$1234567890ABCDEF{$ENDIF},
NativeUInt(ACallback));
StrReplace(ActualTemplate,
{$IFDEF CPU32}$87654321{$ENDIF}{$IFDEF CPU64}$FEDCBA0987654321{$ENDIF},
NativeUInt(AUserParam));
VirtualFree(ATemplate, 0, MEM_RELEASE);
end;
end.
// ...
interface
// ...
type
TData = Integer; // remains the same
implementation
uses
FixCallbacks; // "magic" functions
// ...
Result := True;
end;
72 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Correcting a bad API design (Continuation 8)
Voila! Da Magics! Win64
Unit1.pas.34:
A practical example begin
00000000006990B0 55 push rbp
Please note that the AllocTemplate and DisposeTemplate 00000000006990B1 4883EC30 sub rsp,$30
functions are universal and do not depend on your code. 00000000006990B5 488BEC mov rbp,rsp
00000000006990B8 894D40 mov
To illustrate the versatility of this - let's write code to [rbp+$40],ecx
monitor window messages using SetWindowsHookEx. 00000000006990BB 48895548 mov
[rbp+$48],rdx
We will use the WH_GETMESSAGE hooks. To do this, we
00000000006990BF 4C894550 mov
need to create a template first. Write this code: [rbp+$50],r8
Unit1.pas.35: GetMsgProc := Pointer({$IFDEF
type CPUX86}$12345678{$ENDIF}{$IFDEF
TGetMsgProc = function(Self: Pointer; code: CPUX64}$1234567890ABCDEF{$ENDIF});
00000000006990C3 48B8EFCDAB9078563412 mov
Integer; _wParam: WPARAM; _lParam: LPARAM):
rax,$1234567890abcdef
LRESULT; 00000000006990CD 48894520 mov
[rbp+$20],rax
function InternalGetMsgProc(code: Integer; Unit1.pas.36: Result :=
_wParam: WPARAM; _lParam: LPARAM): LRESULT; GetMsgProc(Pointer({$IFDEF
stdcall; CPUX86}$87654321{$ENDIF}{$IFDEF
var CPUX64}$FEDCBA0987654321{$ENDIF}), code, _wParam,
GetMsgProc: TGetMsgProc; _lParam);
begin 00000000006990D1 48B92143658709BADCFE mov
GetMsgProc := Pointer({$IFDEF rcx,$fedcba0987654321
00000000006990DB 8B5540 mov
CPUX86}$12345678{$ENDIF}{$IFDEF edx,[rbp+$40]
CPUX64}$1234567890ABCDEF{$ENDIF}); 00000000006990DE 4C8B4548 mov
Result := GetMsgProc(Pointer({$IFDEF r8,[rbp+$48]
CPUX86}$87654321{$ENDIF}{$IFDEF 00000000006990E2 4C8B4D50 mov
CPUX64}$FEDCBA0987654321{$ENDIF}), code, r9,[rbp+$50]
_wParam, _lParam); 00000000006990E6 FF5520 call qword ptr
end; [rbp+$20]
00000000006990E9 48894528 mov
[rbp+$28],rax
procedure TForm1.Button1Click(Sender: TObject); Unit1.pas.37: end;
begin 00000000006990ED 488B4528 mov
InternalGetMsgProc(0, 0, 0); rax,[rbp+$28]
end; 00000000006990F1 488D6530 lea
rsp,[rbp+$30]
00000000006990F5 5D pop rbp
We get such an assembly listing for this code: 00000000006990F6 C3 ret
Win32
Unit1.pas.34: begin Cut the machine code and make it into a constant:
005B5C64 55 push ebp const
005B5C65 8BEC mov ebp,esp GetMsgProcTemplate: RawByteString =
005B5C67 83C4F8 add esp,-$08 {$IFDEF CPUX86}
Unit1.pas.35: GetMsgProc := Pointer({$IFDEF
CPUX86}$12345678{$ENDIF}{$IFDEF #$55#$8B#$EC#$83#$C4#$F8#$C7#$45#$F8#$78#$56#$34#
CPUX64}$1234567890ABCDEF{$ENDIF});
$12#$8B#$45#$10#$50#$8B#$4D +
005B5C6A C745F878563412 mov [ebp-$08],$12345678
Unit1.pas.36: Result :=
GetMsgProc(Pointer({$IFDEF #$0C#$8B#$55#$08#$B8#$21#$43#$65#$87#$FF#$55#$F8#
CPUX86}$87654321{$ENDIF}{$IFDEF $89#$45#$FC#$8B#$45#$FC#$59 +
CPUX64}$FEDCBA0987654321{$ENDIF}), code, _wParam, #$59#$5D#$C2#$0C#$00;
_lParam); {$ENDIF}
005B5C71 8B4510 mov eax,[ebp+$10] {$IFDEF CPUX64}
005B5C74 50 push eax
005B5C75 8B4D0C mov ecx,[ebp+$0c]
#$55#$48#$83#$EC#$30#$48#$8B#$EC#$89#$4D#$40#$48#
005B5C78 8B5508 mov edx,[ebp+$08]
005B5C7B B821436587 mov eax,$87654321 $89#$55#$48#$4C#$89#$45#$50 +
005B5C80 FF55F8 call dword ptr [ebp-
$08] #$48#$B8#$EF#$CD#$AB#$90#$78#$56#$34#$12#$48#$89#
005B5C83 8945FC mov [ebp-$04],eax $45#$20#$48#$B9#$21#$43#$65 +
Unit1.pas.37: end;
005B5C86 8B45FC mov eax,[ebp-$04] #$87#$09#$BA#$DC#$FE#$8B#$55#$40#$4C#$8B#$45#$48#
005B5C89 59 pop ecx $4C#$8B#$4D#$50#$FF#$55#$20 +
005B5C8A 59 pop ecx
005B5C8B 5D pop ebp #$48#$89#$45#$28#$48#$8B#$45#$28#$48#$8D#$65#$30#
005B5C8C C20C00 ret $000c $5D#$C3;
{$ENDIF}
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
Edit1: TEdit;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FHook: Pointer;
FHookHandle: THandle;
function GetMsgProc(code: Integer; _wParam: WPARAM; _lParam: LPARAM): LRESULT;
procedure InstallHook;
procedure UninstallHook;
end;
var
Form1: TForm1;
implementation
uses
FixCallbacks;
{$R *.dfm}
// Determinates if this window message is designated to our form or any of its sub-windows
function FindParent(AWnd: HWND; const ATarget, AException: HWND): Boolean;
begin
Result := False;
while AWnd <> 0 do
begin
if AWnd = AException then
Break;
if AWnd = ATarget then
begin
Result := True;
Break;
end;
AWnd := GetParent(AWnd);
end;
end;
var
Msg: PMsg;
begin
Msg := PMsg(_lParam);
// Is this a message for our form or its components (excluding the Memo)?
if (code = HC_ACTION) and FindParent(Msg.hwnd, Handle, Memo1.Handle) then
begin
// If yes – then log the window message
Memo1.Lines.Add(Format('Wnd: %d, Message: %d, wParam: %d, lParam: %d', [Msg.HWnd, Msg.message,
Msg.wParam, Msg.lParam]));
end;
74 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Correcting a bad API design (Continuation 10)
procedure TForm1.InstallHook;
const
GetMsgProcTemplate: RawByteString =
{$IFDEF CPUX86}
#$55#$8B#$EC#$83#$C4#$F8#$C7#$45#$F8#$78#$56#$34#$12#$8B#$45#$10#$50#$8B#$4D +
#$0C#$8B#$55#$08#$B8#$21#$43#$65#$87#$FF#$55#$F8#$89#$45#$FC#$8B#$45#$FC#$59 +
#$59#$5D#$C2#$0C#$00;
{$ENDIF}
{$IFDEF CPUX64}
#$55#$48#$83#$EC#$30#$48#$8B#$EC#$89#$4D#$40#$48#$89#$55#$48#$4C#$89#$45#$50 +
#$48#$B8#$EF#$CD#$AB#$90#$78#$56#$34#$12#$48#$89#$45#$20#$48#$B9#$21#$43#$65 +
#$87#$09#$BA#$DC#$FE#$8B#$55#$40#$4C#$8B#$45#$48#$4C#$8B#$4D#$50#$FF#$55#$20 +
#$48#$89#$45#$28#$48#$8B#$45#$28#$48#$8D#$65#$30#$5D#$C3;
{$ENDIF}
type
TGetMsgProc = function(code: Integer; _wParam: WPARAM; _lParam: LPARAM): LRESULT of object;
var
T: TGetMsgProc;
M: TMethod;
begin
UninstallHook;
T := GetMsgProc;
M := TMethod(T);
FHook := AllocTemplate(GetMsgProcTemplate, M.Code, M.Data);
Win32Check(Assigned(FHook));
FHookHandle := SetWindowsHookEx(WH_GETMESSAGE, FHook, HInstance, GetCurrentThreadId);
Win32Check(FHookHandle <> 0);
end;
procedure TForm1.UninstallHook;
begin
if FHookHandle <> 0 then
begin
UnhookWindowsHookEx(FHookHandle);
FHookHandle := 0;
end;
if Assigned(FHook) then
begin
DisposeTemplate(FHook);
FHook := nil;
end;
end;
end.
Features
http://sourceforge.net/projects/maxbox
Pure Code By Max Kleiner
Coding with maXbox maXbox
Now let's take a look at the code of this project. Our first line
is
01 program Motion_HTTPServer_Arduino41_RGB_LED;
If you can't find the two files try also the zip-file loaded
from:
http://www.softwareschule.ch/examples/305_w
ebserver_arduino3ibz_rgb_led.txt
Nr 5 / 2013 BLAISE PASCAL MAGAZINE COMPONENTS
DEVELOPERS 4 77
Coding with maXbox (Continuation 1) maXbox
Although you can find plenty to complain about in this This problem might not be identified in the testing process,
code, it’s really not that bad. It’s compact and simple and since the average user installs to the default drive of the
easy to understand. However, within this code it is easy to archive and directory and testing might not include the
see the power of scripting because it's agile and high option of changing the installation directory.
available but you can't hide the functionality. By the way you find all info concerning a script or app in
If a maXbox script or app is programmed with the default menu /Program/Information/…
host standard, it is always started relative to the path
where the maXbox3.exe as the host itself is:
playMP3(ExePath+'examples\maxbox.mp3');
78 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Coding with maXbox (Continuation 2) maXbox
Another solution to prevent hard coded literals is a We go on with the boot loader and his functionality.
constant or the call of a user dialog. An indirect reference, maXbox and the script loader system has default folders
such as a variable inside the program called 'FileName', which organize files logically on the hard disk.
could be expanded by accessing a "select browse for file" You can also copy all 6 folders and root files to a folder on
dialog window, and the program code would not have to your USB-stick that stores the same content in your
be changed whwn the file is moved. maXbox installation folder and it will start from the stick!
procedure GetMediaData(self: TObject);
begin 23.05.2013 <DIR> docs
if PromptForFileName(selectFile, 23.05.2013 <DIR> examples
09.05.2013 <DIR> exercices
'Media files (*.mp3)|*.mp3|*.mpg)|*.mpg', '',
09.05.2012 <DIR> crypt
'Select your mX3 media file Directory', 12.05.2013 <DIR> source
'D:\kleiner2005\download', False) 17.03.2013 <DIR> web
then begin 29.03.2013 97'370 bds_delphi.dci
// Display this full file/path value 11.12.2007 254'464 dbxint30.dll
11.11.2012 580'096 dmath.dll
However it is advisable for programmers and developers 09.04.2013 5'426 firstdemo3.txt
not to fix the installation path of a program or hard code 28.11.2010 3'866 firstdemo3.uc
some resources, since the default installation path is 27.10.2005 103'424 income.dll
different in different natural languages, and different 07.11.2010 138 maildef.ini
07.02.2013 10'544 maxbootscript_.txt
computers may be configured differently. It is a common 21.10.2011 59'060 maxbox.mp3
assumption that all computers running Win have the 02.01.2009 71'807 maxbox.png
primary hard disk labelled as drive C:\, 11.05.2013 11'887'616 maxbox3.exe
11.05.2013 5'133'220 maxbox3clx
but this is not the case.
12.05.2013 994 maxboxdef.ini
As you will see the configuration of maXbox is possible 03.12.2012 12'503 maxboxerrorlog.txt
with a boot loader script and a simple ini-file too. 12.05.2013 42'773 maxboxnews.htm
Extensions are possible with the Open Tools API and 12.05.2013 2'309'571 maxbox_functions_all.pdf
21.04.2012 9'533 maxdefine.inc
a small CLI (Command Line Interface). 14.11.2005 383'488 midas.dll
10.12.2012 17'202 pas_includebox.inc
12.05.2013 36'854 readmefirst_maxbox3.txt
Tutorials (for downloading to the harddisk) 11.10.2010 135'168 TIFFRead.dll
You can read or download tutorials 1 till 26 from the
program: ž /Help/Tutorials/: When you start the box a boot script is loaded.
from the Internet: Within the boot script, you can perform common source
http://sourceforge.net/apps/ control tasks, such as file check in, check out, and of course
maXbox
mediawiki/maxbox/ change IDE settings and synchronization of your current
Tutorial 00 Function-Coding (Blix the Programmer) version. This is a script where you can put all the global
Tutorial 01 Procedural-Coding
settings and styles of the IDE of maXbox, for example:
Tutorial 02 OO-Programming
Tutorial 03 Modular Coding with maxForm1 do begin
Tutorial 04 UML Use Case Coding caption:= caption +'Boot Loader Script
Tutorial 05 Internet Coding maxbootscript.txt';
Tutorial 06 Network Coding color:= clteal;
Tutorial 07 Game Graphics Coding IntfNavigator1Click(self);
Tutorial 08 Operating System Coding
tbtnCompile.caption:= 'Compile!';
Tutorial 09 Database Coding
tbtnUsecase.caption:= 'UML UC';
Tutorial 10 Statistic Coding
maxform1.ShellStyle1Click(self);
Tutorial 11 Forms Coding
memo2.font.size:= 16;
Tutorial 12 SQL DB Coding
Tutorial 13 Crypto Coding Info1Click(self);
Tutorial 14 Parallel Coding end;
Tutorial 15 Serial RS232 Coding Writeln('BOOTSCRIPT ' +BOOTSCRIPT+ ' loaded')
Tutorial 16 Event Driven Coding
When you want to see a complete copy of that file, look at:
Tutorial 17 Web Server Coding
Tutorial 18 Arduino System Coding
07.02.2013 10'544 maxbootscript_.txt
Tutorial 18_3 Arduino RGB LED Coding
Tutorial 19 WinCOM /Arduino Coding
Tutorial 20 Regular Expressions RegEx When you delete the underscore in the filename to
Tutorial 21 Android Coding (coming 2014) maxbootscript.txt the system performs next time when you
Tutorial 22 Services Coding load maXbox and presents you with a different view. This
Tutorial 23 Real Time Systems boot script results in the picture below for example. The
Tutorial 24 Clean Code
trick of renaming the file has a simple explanation. The ini-
Tutorial 25 maXbox Configuration
Tutorial 26 Socket Programming with TCP file default to load the boot script is YES so it can be easier
Tutorial 27 XML & TreeView (coming 2014) to rename the file instead of change the ini-file to set to YES,
Tutorial 28 Closures (coming 2014) cause of missing permissions, testing or so:
Tutorial 29 UML Scripting (coming 2014) BOOTSCRIPT=Y
Tutorial 30 Web of Things (coming 2014) Maybe you want to change the colour or the caption of a
button or a frame; you can do this by accessing the Open
Tools API of the object maxForm1.
<<extended>>
Start maXbox
Load Boot
Script <<include>> bds_delphi.dci
(templates)
Actor HEX
in the BOX
Parse Ini
File
maxbootscript.txt
maxboxdef.ini
PRE PROCESSING
Compile Script
maxboxdef.ini
include file
<<include>>
Run App
Byte Code
80 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Coding with maXbox (Continuation 4) maXbox
• An advantage of using S_ShellExecute or
In this section we deal with multiple instances of maXbox
and its creation. You can create multiple instances of the ShellExecute3 is the no wait condition.
same app to execute multitask code, just type <F4>. For
example, you can launch a new instance within a script of In this article we show 4 steps to build a configuration:
the box in response to some user action, allowing each 1. First building block is the use of a
script to perform the expected response. boot script maxbootscript.txt.
2. Second we jump to the template file bds_delphi.dci
ExecuteShell(ExePath+'maxbox3.exe','"' 3. Third the Ini-file maxboxdef.ini environment
+ExePath+'examples\'+script+'"'); and code settings.
4. Forth we set an include file in our script, like
S_ShellExecute(ExePath+'maxbox3.exe', pas_includebox.inc to call external functions of
ExePath+'examples\'+script,secmdopen); a unit, it's very powerful, but tough to built.
There's no good way to launch one application (maXbox) Ok we have finished the boot script what about a template
with multiple scripts in it. file. The code template file bds_delphi.dci
Maybe with OLE Automation in that sense you open office stands for code completion templates.
programs (word, excel) or other external shell objects. In the Code Editor, type an object, class, structure or a
CreateOleObject creates a single uninitialized object of pattern name followed by <Ctrl J> to display the object.
the class specified by the ClassName parameter. But it's not a full code completion where you get after the
dot (.) a list of types, properties, methods, and events, if
ClassName specifies the string representation of the you are using the Delphi or C# languages.
Class ID (CLSID). It's more a template copy of your building blocks.
CreateOleObject is used to create an object of a In the menu /Debug/Code Completion List you get
specified type when the CLSID is known and when the the defaults, here is an extract:
object is on a local or in-proc server.
Only the objects that are not part of an aggregate are [cases | case statement |
Borland.EditOptions.Pascal]
created using CreateOleObject.
case | of
• Try the example of OLE Objects
: ;
318_excel_export3.TXT and the tutorial 19.
: ;
end;
An external script or a least a second one could also be a [try | try except |
test case to compare the behaviour. Borland.EditOptions.Pascal]
try |
Each test case and test project is reusable and rerun able, except
and can be automated through the use of shell scripts or end;
console commands.
Figure 4:
Each of the Read routines takes three parameters. The first parameter (Form in
our example) identifies the section of the ini file. The second parameter identifies
the value you want to read, and the third is a default value in case the section or
value doesn't exist in the ini file.
84 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Coding with maXbox (Continuation 8) maXbox
Let's do the last step with an include file. Under Include Files, list the files you
want to include in a script. A script can include a script from a file or from
another unit. To include script from a file, use the following code statement:
305_indy_elizahttpserver.TXT
{$I ..\maxbox3\examples\305_eliza_engine.INC}
If you need to specify additional compiler options, you can invoke the compiler from the
command line with the Command Line Interface (CLI).
• As you know, there's a simple test to run the CLI out of the box with a
ShellExecute() or a similar RunFile() Command.
ShellExecute3(ExePath+'maxbox3.exe',ExePath+'examples\'
+ascript,secmdopen);
ShellExecute3(ExePath+'maxbox3.exe',
ExePath+'examples\003_pas_motion.txt',secmdopen);
A simple CLI is more relevant today than ever for scripting, and modern shell
implementations such as maXbox or PowerShell have a lot to bring to the table.
procedure TForm1_FormCreateShowRunDialog;
var ShellApplication: Variant;
begin
ShellApplication:= CreateOleObject('Shell.Application');
ShellApplication.FileRun;
end;
86 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Coding with maXbox (Continuation 10) maXbox
The Macro
You can set the macros like #host: in your header or
elsewhere in a line, but not two or more on the same line
when it expands with content:
Let's have a look at the demo 369_macro_demo.txt
{********************************************
* Project : Macro Demo
* App Name: #file:369_macro_demo.txt
* Purpose : Demonstrates the functions of macros in header
* Date : 21/09/2010 - 14:56 - #date:01.06.2013 16:38:20
* #path E:\maxbox\maxbox3\examples\
* #file 369_macro_demo.txt
* #perf-50:0:4.484
* History : translate/implement to maXbox June 2013, #name@max
* : system demo for mX3, enhanced with macros, #locs:149
*********************************************
All macros are marked with red. One of my favour is #locs means
lines of code and you get always the certainty if something has
changed by the numbers of line.
So the editor has a programmatic macro system which allows the pre
compiler to be extended by user code I would say user tags.
Below an internal extract from the help file All Functions List
maxbox_functions_all.pdf:
//----------------------------------------------------------------------------
10181: //**************mX4 Macro Tags **************************
10182: //----------------------------------------------------------------------10183:
10184: #name, i#date, i#host, i#path, i#file, i#head, i#sign, i#teach
10185:
10186: SearchAndCopy(memo1.lines, '#name', getUserNameWin, 11);
10187: SearchAndCopy(memo1.lines, '#date', datetimetoStr(now), 11);
10188: SearchAndCopy(memo1.lines, '#host', getComputernameWin, 11);
10189: SearchAndCopy(memo1.lines, '#path', fpath, 11);
10190: SearchAndCopy(memo1.lines, '#file', fname, 11);
10191: SearchAndCopy(memo1.lines, '#locs', intToStr(getCodeEnd), 11);
10192: SearchAndCopy(memo1.lines, '#perf', perftime, 11);
10193: SearchAndCopy(memo1.lines, '#head',Format('%s: %s: %s %s ',
10194: [getUserNameWin, getComputernameWin, datetimetoStr(now), Act_Filename]),11);
10195: SearchAndCopy(memo1.lines, '#sign',Format('%s: %s: %s ',
[getUserNameWin, getComputernameWin, datetimetoStr(now)]), 11);
10196: SearchAndCopy(memo1.lines, '#tech',Format('perf: %s threads: %d %s %s',
[perftime, numprocessthreads, getIPAddress(getComputerNameWin), timetoStr(time)]), 11);
<<extended>>
Start maXbox
Parse Ini
File Macro
Macro Actor
Macro
Macro
maxbootscript.txt
Macros
’#name’, get_UserNameWin, 11)
’#date’, datetimetoStr(now), 11)
’#host’, get_ComputernameWin, 11)
’#path’, fpath, internal func 11)
’#file’, fname,internal func 11)
maxboxdef.ini ’#locs’, intToStr(getCodeEnd), 11)
’#perf’,perftime, internal func 11)
’#head’, Format(’%S: %S: %S: %S:)
Compile Script
Some DLL or Lib
PRE PROCESSING include file
<<include>>
Run App
Byte Code
• Some macros produce simple combinations of one liner tags but at least they replace
the content by reference in contrary to templates which just copy a content by value.
88 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Coding with maXbox (Continuation 12) maXbox
Build your own IDE
At last we go back to the magic boot script which will be the key to modify the IDE
especially with the inbuilt SynEdit API (since V3.9.8.9). What does it mean? It means
you can change or rebuild your IDE not just by fixed options or settings but also in a
programmatic way in your boot script without compilation! Imagine you want to set a
vertical red line on the gutter to the left:
//memo1.Gutter.BorderColor:= clred; //---> reflection to box!
//memo1.Gutter.ShowLineNumbers:= true; //---> reflection to box!
You simply put the line above on the boot script and make sure the ini file has it set to Yes.
BOOTSCRIPT=Y //enabling load a boot script
In combination with the Open Tools API you can tweak the GUI with new or change
buttons, events and behaviour for example:
if extractFileName(maxform1.appname) = '370_synedit.txt'
then
begin
Options:= +[eoShowSpecialChars];
ActiveLineColor:= clyellow;
maxform1.tbtnUseCase.caption:= 'SynScriptUC';
maxform1.ShellStyle1Click(self)
end
else ActiveLineColor:= clgreen;
maxform1.memo1.Options:= +[eoShowSpecialChars];
maxform1.memo1. ActiveLineColor:= clyellow;
More examples at 370_synedit.txt and all the other changeable properties or
methods you find at the bottom of the help file <All Functions List>
maxbox_functions_all.pdf
10197: //--------------------------------------------------------------------------
10198: //**************mX4 Public Tools API *******************
10199: //----------------------------------------------------------------
//--------------------------------------------------------------------------
10702: file : unit uPSI_fMain.pas; OTAP Open Tools API Catalog
10703: // Those functions concern the editor and pre-processor, all of the IDE
10704: Example: Call it with maxform1.Info1Click(self)
10705: Note: Call all Methods with maxForm1., e.g.:
10706: maxForm1.ShellStyle1Click(self);
You can also enhance the API with functions like the example above GetEnvironmentString:
function getEnvironmentString2: string;
var list: TStringList; i: Integer;
begin
list:= TStringList.Create;
try GetEnvironmentVars(list, False);
for i:= 0 to list.Count-1 do
result:= result + list[i]+#13#10;
finally list.Free;
end;
end;
The Open Tools API is a collection of classes and functions of SynEdit and VCL components for
extending and enhancing the design and your editor environment. Unlike other development
tools, you use maXbox (Delphi) to extend maXbox. You don't need to learn a new scripting
language because PascalScript works for you.
The Open Tools API puts you in control; reshape maXbox to match your needs.
I mean an elegant and efficient application has some script features.
90 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Coding with maXbox (Continuation 14) maXbox
Feedback: max@kleiner.com About the Author:
Max Kleiner
Links of maXbox and DelphiWebStart:
The professional environment of Max Kleiner is in the
range OOP, UML and system design, including as a
http://www.softwareschule.ch/maxbox.htm trainer, developer and publisher. His focus is on IT
http://sourceforge.net/projects/maxbox security, navigation and simulators that need the power
of the Delphi compiler. As a teacher in a Fach Hochschule
http://sourceforge.net/apps/mediawiki/maxbox/
(University of Applied Sciences) and on behalf of a firm
http://sourceforge.net/projects/delphiwebstart also microcontroller and the Web of Things have been
added. The book "Patterns konkret" (published in 2003)
is still relevant with the Clean Code initiative.
The CodeSite Logging System gives developers deeper insight into how their code is executing, which
enables them to locate problems more quickly and ensure their application is running correctly.
CodeSite's logging classes let developers capture all kinds of information while their code executes and
then send that information to a live display or to a log file. Furthermore, both styles of logging, live
logging and file logging, can be performed locally or remotely.
A key element to CodeSite's effectiveness is that unlike message boxes and inspecting variables on
breakpoints, CodeSite messages are not transient. The resulting log of messages provides valuable
information for locating problem areas in your code.
Raize Components is a user interface design system for Borland Delphi and Borland C++Builder. At its
center is a collection of more than 125 general-purpose native VCL controls. Built on a foundation of
technology first created more than eight years ago, these high-quality components give developers
unsurpassed power and flexibility without sacrificing ease-of-use.
In addition to the core set of controls, Raize Components includes more than 100 component designers
focused on simplifying user interface development. Now more than ever, developers use Raize
Components to build sophisticated user interfaces in less time with less effort.
Raize Components comes with complete source code for all components, packages, and design editors at
no additional charge. Documentation is provided through an extensive context-sensitive online help
system. Raize Components also features One-Step Installation, Automatic Help Integration, and Dynamic
Component Registration.
DropMaster is a set of 4 native VCL controls for use in Delphi and C++Builder. While the VCL components
included with Delphi and C++Builder permit drag and drop between windows in the same application,
DropMaster allows developers to add support for drag and drop between applications. The drag and drop
can be between the developer's new application and existing applications such as the Microsoft Office
suite, a web browser, etc., or between two custom-written applications.
DropMaster also comes with a collection of more than 40 example applications, which demonstrate the
features of the DropMaster components in real-world situations. They also represent the results of
extensive research into the drag and drop behavior of many popular commercial applications.
Inspex is an advanced set of native VCL grid controls specifically designed for inspecting objects and
other data types in your programs. From the light-weight TIxItemListEditor for editing lists of name-value
pairs to the advanced TIxObjectInspector for inspecting all published properties of objects and
components, there is an inspector control in the Inspex collection that will meet your needs.
ScratchPad is a general-purpose text editor with features typically found in programming editors. For
instance, you can edit multiple files at the same time in a sleek tabbed interface. ScratchPad also
supports syntax highlighting a variety of file types including, AutoCorrect, Keyboard Templates, and
Bookmarks.
http://www.raize.com/DevTools/Products.asp
Nr 5 / 2013 BLAISE PASCAL MAGAZINE 97
Programming Bitmap Rotation By David Dirkse
Editor: David wrote this article especilaly Rotation takes place between a source and a destination
for us because we needed it for our Leap bitmap. In coarse mode, the source bitmap is scanned pixel
Motion Development. It was so much by pixel and projected on the destination bitmap.
interesting that we decided to publish the Therefore, not every pixel of the destination bitmap may be
basic Delphi code and handling... covered.
In medium mode, the pixels of the destination bitmap are
This article describes a Delphi project for scanned and their value is loaded from the source bitmap.
bitmap rotation. It does it in three different This insures that all pixels of the destination bitmap are
qualities: Simple, Good and Excellent covered.
Result for the Bitmap. In fine mode, the scanning is the same as in medium mode,
but each pixel is divided in 9 equal parts.
There are 3 units:
- unit1: exerciser to test the rotation procedures Parts may cover different pixels in the source map, the
- rotation_unit : procedures for bitmap rotation proportions are summed for the final color of the
- clock_unit : time measurement procedures destination pixel.
Exerciser Programming
The form has buttons for loading and saving bitmaps. The programmer has to create both the source and the
Also 3 modes of rotation are selectable destination bitmap. Before a rotation may take place, the
rotation_unit has to be informed about the names of the
- coarse: fast but less accurate bitmaps. This is done by a call to
- medium: somewhat slower but destination bitmap is fully
procedure setmaps(sourcemap,destination map)
covered
- fine: slow, but with soft edges (under construction)
Setmaps sets the pixelformat of both bitmaps to 32 bit.
Also, the dimensions of the destination bitmap are adjusted
Bitmaps are displayed in paintbox1.
to accomodate all possible rotations of the source map.
Moving the mousepointer over the paintbox with
leftmousebutton pressed,
Hereafter, images may be drawn in the source map and
causes the picture to rotate in the selected mode. procedure coarserotate(deg : word)
Below is an example of medium mode rotation: procedure mediumrotate(deg: word)
procedure finerotate(deg: word)
may be called.
deg is the rotation angle in degrees from 0..360
Rotation is clockwise, so, for a left rotation of 90 degrees,
270 degrees must be specified.
98 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Programming Bitmap Rotation (Continuation 1)
The source bitmap is painted in red.
Black pixels of the destination map are scanned, the color is
obtained from to corresponding red pixel of the source
map.
Rotation calculation
Now we describe how the position on the source map is
calculated given position (x,y) of the destination map.
(x,y) is regarded as the vector addition of (x,0) and (0,y), so
the x and the y vectors.
These vectors are rotated separately, then the resulting
vectors are added to obtain the rotated (x,y) position.
tx = xtx + ytx
ty = - xty + yty
For the other quadrants, the signs of xtx, ytx, xty, yty
Figure 5: Rotation of 30 degrees may change.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE COMPONENTS
DEVELOPERS 4 99
Programming Bitmap Rotation (Continuation 2)
Addressing the pixels in the bitmaps Also an adjustment must be made to position the center of
Of course, Delphi property pixels[x,y] may be used to a bitmap over (0,0), the coordinate system origin.
change pixelvalues.
However, this way is incredibly slow. scx is the center x pixel of the source map
Also, the scanline[y] property, which returns the memory scy is the center y pixel of the source map
pointer of the first pixel in row y, is very slow. dcxy is the x and y center of the destination map
Better is to calculate the pointer to a certain pixel. Now, pixel (x,y) of the source map is addressed by
Because the bitmaps are in 32 bit format we first define
trunctx := trunc(tx); //tx is floating point format
type PDW = ^dword; truncty := trunc(ty); //...
ttx := scx + trunctx; //add center
PDW is a pointer to a 32 bit unsigned variable. tty := scy + truncty;
PS := PSBM - tty*Slinestep + (ttx shl 2); //pointer to
Address calculations are done with variables in source pixel
dword ( = cardinal) format. pix := PDW(PS)^;
First, the pointer to pixel [0,0] must be obtained.
For this, scanline[0] is used by the setmaps procedure. pix receives the color value of the source map pixel.
scanline[1] returns the pointer to pixel [0,1]. For quadrant 1, the pointer to the destination map is
scanline[1] - scanline[0] gives the pointer difference
between two rows. Ybase1 := PDBM - (dcxy + y)*Dlinestep;
PD := Ybase1 + ((dcxy+x) shl 2);
(note: row 1 pointer is smaller then the row 0 pointer). PDW(PD)^ := pix;
In the rotation procedure
For more details, I refer to the source code.
PSBM = scanline[0] for the source map
PDBM = scanline[0] for the destination map
Slinestep = scanline[0] - scanline[1] for the source map
Dlinestep = scanline[0] - scanline[1] for the destination map
Figure 7: Raw (coarse) - see the pixelerrors causing a pattern of bad information - or no information
Quick and dirty: see the measurement buttons
100 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Programming Bitmap Rotation (Continuation 3)
Figure 8: Medium - lesser pixelerrors better result but the calculation takes a lot more time
Figure 9: Fine - see the smooth picture no information loss - but the calculation takes ten times as much
BUSINESS
FACING
"organize the mess", allows you to unit test not only the ACCEPTANCE
TESTS
business logic but also the interaction logic in an early
stage, and makes it easier to switch user interfaces when
aiming for cross-platform versions of your applications.
The really cool thing is that you do not have to start
TECHNOLOGY
with MVVM from scratch. You can apply the ideas from
FACING
MVVM into existing applications as well and evolve them UNIT TESTS INTEGRATION END TO END
TESTS TESTS
into a fully fledged MVVM application.
It means MVVM gives you a lot of freedom on which I
will write more at a later stage. Let’s first focus on the
introduction though. GRANUARITY / SCOPE
My encounter with MVVM was in the .NET and later in Binding the 3rd object
the Delphi world with a self written framework similar to The question is: how do you bind View, Model and the 3rd
what Malcolm Grooves presented at CodeRage 7. object? That highly depends on what kind (or even
Back then I wasn't too impressed with what I achieved. flavour) of 3rd object architecture you use: MVC, MVP,
Even though I was used to naming conventions, most of MVVM, et cetera. A few of them are here:
the things were still manual labour. Too much tedious
manual labour.
Later I bumped into Caliburn for .NET, but at that VIEW MODEL
time I was working with applications that hadn't been
maintained since around 2005 in a way that was hard to
move to MVVM. Last year, I was really glad to see DSharp
was having an MVVM presentation model, and even more
happy that it was being revamped into something very 1 PRESENTER
VIEW = MODEL
VIEW MODEL
or, if you are lucky, like:
3
VIEW MODEL VIEW MODEL
THIRD OBJECT
VIEW MODEL
5
102 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Steps in MindScape AppView Step - By - Step (Introduction 1)
Caliburn sticks to the last one: the View binds to the View Model, MVVM: the View Model
and the View Model to the Model. Updates come When starting with MVVM, the term View Model wasn't making
from the Model to the View Model and from the View Model to much sense to me at first. I was missing the steering layer from the
the View. There is no binding between the View and Model at all. controller or presenter in MVC and MVP. And I'm not the only one,
This setup allows you to change any of the 3 layers and be very as you can see from this MVVM Wikipedia excerpt:
flexible.
The term "View model" is a major cause of confusion in
The traditional 3rd layer in Delphi is to separate user interface into
understanding the pattern when compared to the more
forms, business logic into data modules and
widely implemented MVC or MVP patterns. The role of the
your data into some form of storage. This has worked for 15 years, controller or presenter of the other patterns has been
but still needs a lot of plumbing and it is hard substituted with the framework binder (e.g., XAML) and
to change the UI as the binding is usually tied to the VCL view model as mediator and/or converter of the model to
platform. the binder.
with these class In MVVM the steering role is taken over by the framework,
and sequence and the View Model focuses a lot more on (testable!) logic
diagrams: than the controller in MVC or the presenter in MVP.
MVVM favours these concepts:
• Decoupling
• Composition over inheritance
• Test driven development
• Patterns, conventions, ...
Note that the vast majority of those projects use a DVCS for
version control like Git or Mercurial, so that is another learning
opportunity for many Delphi developers. Caliburn Micro for
.NET was our origin. Back in 2010, Rob Eisenberg gave a very
influential speech titled Build Your Own MVVM Framework,
which led to wide adoption of Caliburn Micro in the .NET world.
The Delphi implementation is mimicked after it, and now even a
JavaScript library is: Durandal (also the name of a sword). His
speech has since then been published on Channel 9 and well
worth viewing as lots of the Delphi implementation works in a
similar way.
Caliburn Micro for Delphi is part of DSharp
Currently, Caliburn Micro for Delphi is in alpha stage. It is hosted
at the DSharp repository from Stefan Glienke.
Stefan has done most of the core DSharp work and a lot of
Patterns
Spring4D work (both DSharp and Caliburn depend on Spring4D).
In 2004, the - instantly famous - Gang of Four (abbreviated to
Most of the Caliburn specific work has been done by Marko
GoF) published the book Design Patterns: Elements of Reusable
Vončina.
Object-Oriented Software. The book is another catalog, this time
Internally, all these frameworks heavily depend on these Delphi
on patterns, with a common vocabulary about recipes for
features:
developing new code. The Gang of Four consists of Erich Gamma, interfaces - attributes - generics - RTTI
Richard Helm, Ralph Johnson and John Vlissides. They expose many classes and interfaces (attributes are based on
Martin Fowler on the GoF book in relation to the 3rd object: classes). Applications based on Caliburn will use interfaces and
„In my view the Gang of Four is the best book ever written on attributes a lot.
object-oriented design - possibly of any style of design“. This book Make sure you read the Coding in Delphi book by Nick Hodges if
has been enormously influential. you feel unfamiliar with them. Actually: just read the book
The 3rd object is “just” a (relatively) new way of using patterns. anyway. I learned a lot while reading it!
104 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Steps in MindScape AppView Step - By - Step (Continuation 1)
The goal is to write an application using MVVM VCL project
that: Name the form AppView, the form unit AppViewForm and clean
is based on patterns and conventions by using RTTI up the uses list, private and public sections:
- is driven by the View Model (not the View)
- has the user interaction logic in the Model View unit AppViewForm;
- can have the View Model interface
- be unit tested in an early stage uses Forms;
- has the data storage in a Model type
- can have the Model TAppView = class(TForm)
- be unit tested in an early stage as well runs in Delphi end;
XE or higher builds warning and hint free. var
AppView: TAppView;
During the steps you will see some failures that will be implementation
fixed in subsequent steps. That is deliberate: it is nice
having working demos but in practice you will bump into {$R *.dfm}
things, so it is good to see the things you might bump
into upfront and how easy it is to solve them. end.
The projects are based on the Mindscape HQ
MVVM demos that use the Caliburn Micro for .NET
Name the application MindScape_AppViewVCL:
framework. Though Delphi is different than .NET,
the ideas in the framework are kept the same as much
program MindScape_AppViewVCL_Step00;
as possible. The demos will teach you where things differ
most. uses Forms,
So the Delphi application will eventually look similar to AppViewForm in 'AppViewForm.pas' {AppView};
this Caliburn Micro for .NET based WPF application: {$R *.res}
it can add, double and increment, all from a View Model begin
automagically updating the View. In fact the Delphi ReportMemoryLeaksOnShutdown := True;
application will also add a Model that is persistent in an Application.Initialize();
INI file! Application.MainFormOnTaskbar := True;
Application.CreateForm(TAppView, AppView);
Application.Run();
end.
106 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Steps in MindScape AppView Step - By - Step (Continuation 3)
Step 2: It almost is, and indeed it is the first step. And you get
making Caliburn recognize your form another error that too is part of the learning experience:
as a valid View Exception EResolveException
No component was registered for the service type:
IAppViewModel
108 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Steps in MindScape AppView Step - By - Step (Continuation 5)
Step 05: Step 06:
adding a Count property to the View Model fixing the binding.
and control to the View The previous step told about the importance of the
One of the Caliburn conventions is that if you have a interceptor classes in the DSharp.Bindings.VCLControls
control in the View with the same name as a property in unit. And that is exactly the reason why at run-time you got
the View Model, then they will automatically be bound Count into the caption of the TEdit:
together. it wasn't bound to the integer value of Count in the View
So lets start with adding a TEdit control called Count Model, as the interceptor classes could not do their
on the View, and notice that Delphi automatically work. The reason is that Delphi does not see them as they
extends the uses list for you: are obscured by the units that expose the actual control.
uses
DSharp.Bindings.VCLControls, The lesson is easy:
Classes, always make sure that units like
Controls, DSharp.Bindings.VCLControls that have
StdCtrls; interceptor classes are always the last in
type the uses list.
TAppView = class(TForm)
Count: TEdit; So the solution is very simple, modify the uses list from
end; uses
Now add the Count property to the View Model: DSharp.Bindings.VCLControls, Classes,
Controls, StdCtrls;
TAppViewModel = class(TScreen, IAppViewModel)
strict private
FCount: Integer; into
strict protected
function GetCount(): Integer; virtual; uses
procedure SetCount(const Value: Integer); Classes, Controls, StdCtrls,
virtual; DSharp.Bindings.VCLControls;
public Now run and enjoy the results of this step that was very
constructor Create(); override;
property Count: Integer read GetCount easy to perform, but had a high impact.
write SetCount;
end;
and have it backed by get and set methods
function TAppViewModel.GetCount(): Integer;
begin
Result := FCount;
end;
procedure TAppViewModel.SetCount(const Value:
Integer);
begin
if Count <> Value then
begin
FCount := Value; Step 07:
NotifyOfPropertyChange('Count'); add buttons to increment or decrement
end; the count
end
Here you will see that the Caliburn convention of naming
The DSharp.Bindings.VCLControls have class controls not only holds for properties in the View Model,
interceptors adding notification to most controls that ship but also for methods.
with Delphi, the View Model must also notifications. Lets start with the View Model: add two public methods
The above SetCount implementation shows this for here named DecrementCount and
properties; we will see later that this also can hold for IncrementCount
methods procedure TAppViewModel.DecrementCount;
Now let’s run the application and see if design-time begin
Count := Count - 1;
gets bound on run-time: end;
procedure TAppViewModel.IncrementCount;
begin
Count := Count + 1;
end;
Now add two buttons with the same name in the View:
type
TAppView = class(TForm)
Count: TEdit;
IncrementCount: TButton;
DecrementCount: TButton;
end;
You see that it doesn't, and that's what the next step
will fix.
const
MinimumCount = -10;
MaximumCount = +10;
Adding unit tests
Now add these public properties to the View Model: I like having unit tests around the boundary cases, and to
have a certain symmetry. So these are the published test
property CanDecrementCount: Boolean
methods added:
read GetCanDecrementCount;
property CanIncrementCount: Boolean procedure Test_DecrementCount_MaximumCount();
read GetCanIncrementCount; procedure Test_DecrementCount_MaximumCount_Minus1();
and have them backed by get methods: procedure Test_DecrementCount_MaximumCount_Plus1();
procedure Test_DecrementCount_MinimumCount();
function TAppViewModel.GetCanDecrementCount(): procedure Test_DecrementCount_MinimumCount_Minus1();
Boolean; procedure Test_DecrementCount_MinimumCount_Plus1();
begin procedure Test_DisplayName();
Result := Count > MinimumCount; procedure Test_IncrementCount_MaximumCount();
end; procedure Test_IncrementCount_MaximumCount_Minus1();
procedure Test_IncrementCount_MaximumCount_Plus1();
function TAppViewModel.GetCanIncrementCount(): procedure Test_IncrementCount_MinimumCount();
Boolean; procedure Test_IncrementCount_MinimumCount_Minus1();
begin procedure Test_IncrementCount_MinimumCount_Plus1();
Result := Count < MaximumCount;
end with implementations like these:
It is always a good idea to make the View Model robust so procedure TAppViewModelTestCase.
that it can withstand unwanted calls. So update Test_DecrementCount_MaximumCount();
the DecrementCount and IncrementCount so they begin
AppViewModel.Count := MaximumCount;
throw an exception when they cannot perform their AppViewModel.DecrementCount();
respective action: end;
procedure TAppViewModel.DecrementCount;
begin procedure TAppViewModelTestCase.
if not CanDecrementCount then Test_DecrementCount_MaximumCount_Minus1();
raise EInvalidOperation.Create( begin
'not CanDecrementCount'); AppViewModel.Count := MaximumCount-1;
Count := Count - 1; AppViewModel.DecrementCount();
end; end;
procedure DecrementCount();
function GetCount(): Integer;
procedure IncrementCount();
procedure SetCount(const Value: Integer);
property Count: Integer read GetCount
write SetCount;
end;
Even after the interface change, you will get some test errors
when running the unit test, but that is fine: the next step will
fix those.
The important thing to remember here is:
by using MVVM you can test your View Model
independent of your UI in an early stage.
Step 9:
ensuring the unit test results make sense
Of the failing methods, these fail in a sort of expected way:
procedure Test_DecrementCount_MinimumCount();
procedure Test_IncrementCount_MaximumCount();
procedure Test_DecrementCount_MinimumCount_Minus1();
procedure Test_IncrementCount_MaximumCount_Plus1();
procedure Test_DecrementCount_MinimumCount();
procedure Test_IncrementCount_MaximumCount();
112 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
kbmFMX for XE5 By Fikret Hasovic
starter expert Delphi XE5
I'll explain here how to create an android application with embedded kbmMW
application server, so you can fully utilize all kbmMW power!
We start from scratch with creating FireMonkey Mobile Application :
COMPONENTS
DEVELOPERS 4
After selecting the folder, the Delphi designer will open, and here is the main window with the mobile application unit created:
So, the new form in design mode will look like this:
114 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
kbmFMX for XE5 (Continuation 2)
COMPONENTS
DEVELOPERS 4
Also, Delphi XE5 will create the following code to handle gestures and device keys:
procedure TTabbedwithNavigationForm.FormKeyUp(Sender: TObject; var Key: Word;
var KeyChar: Char; Shift: TShiftState);
begin
if Key = vkHardwareBack then
begin
if (TabControl1.ActiveTab = TabItem1) and (TabControl2.ActiveTab = TabItem6) then
begin
ChangeTabAction2.Tab := TabItem5;
ChangeTabAction2.ExecuteTarget(Self);
ChangeTabAction2.Tab := TabItem6;
Key := 0;
end;
end;
end;
sgiRight:
begin
if TabControl1.ActiveTab <> TabControl1.Tabs[0] then
TabControl1.ActiveTab := TabControl1.Tabs[TabControl1.TabIndex-1];
Handled := True;
end;
end;
{$ENDIF}
end;
I will use the SQLite database to store my photo album together with some notes or cooments about them.
Here is SQL to create table in SQLite database:
CREATE TABLE "main"."Images" ("ImageID" INTEGER PRIMARY KEY AUTOINCREMENT
NOT NULL UNIQUE , "Image" BLOB, "Comment" VARCHAR)
For the purpose of creating an SQLite database, you can use the excellent SQLite Manager,
which is actually an add-on for Firefox, but you can use any other tool you prefer:
Now, to use SQLite db in your app, you add the kbmMWSQLiteConnectionPool component:
116 COMPONENTS
DEVELOPERS 4 Nr 5 / 2013 BLAISE PASCAL MAGAZINE
kbmFMX for XE5 (Continuation 4)
On
COMPONENTS
DEVELOPERS 4
http://www.components4programmers.com/produ
cts/kbmmw/download/sampleprojects.htm
you can find the LocalServer downloadable sample,
explaining how to use kbmMW™ based server embedded
in client application.
I have used the same schema here, only used SQLite
specific connectivity instead of BDE in sample.
So, after adding required components on main form, this is
how it looks like now:
end;
Before we try to run it, don't forget to add following code to OnCreate method of main form:
kbmMWServer1.RegisterService(TkbmMWInventoryService,false);
kbmMWServer1.RegisterServiceByName('KBMMW_QUERY',TTestQuery,false);
kbmMWServer1.Active := True;
You can go without Inventory service, but you might want to test it here, by creating some test procedure, just use standard kbmMW
code... Since kbmFMX components are ReadOnly, I have used a standard Memo component to enter note/comment for pictures, so I
needed some code like this:
Application source code and compiled aplication dpk will be available for download from BlaisePascal, so let me try to run app and
show few screenshots here... Don't forget to add needed files for deployment, using Deployment tool:
Delphi will add the launcher related files, but you need to Here is one more:
add some files, in this case SQLite database, or to add code
to create it from your application.
I have added db file here, and RemotePath should be
assets\internal\ so the client application will be able
to connect to.
OK, here are a few screenshots from my Samsung Galaxy
Tab 2 7“ P3110:
kbmMW is the premier high performance, high Supports Delphi/C++Builder/RAD Studio 2009 to
functionality multi tier product for serious system XE5 (32bit, 64bit and OSX where applicable).
development. kbmMW for XE5 includes full support for Android
and IOS (client and server).
- Native high performance 100% developer
kbmMemTable is the fastest and most feature rich
defined application server with support for in memory table for Embarcadero products.
loadbalancing and failover
- Native high performance JSON and XML - Easily supports large datasets
(DOM and SAX) for easy integration with - Easy data streaming support
external systems - Optional to use native SQL engine
- Native support for RTTI assisted object - Supports nested transactions and undo
marshalling to and from XML/JSON - Native and fast build in
aggregation/grouping features
- High speed, unified database access
- Advanced indexing features
(35+ supported database APIs) with connection
for extreme performance
pooling, metadata and data caching on all tiers
- Multi head access to the application server, Warning!
via AJAX, native binary, Publish/Subscribe, SOAP,
kbmMemTable and kbmMW
XML, RTMP from web browsers, embedded
devices, linked application servers, PCs, mobile are highly addictive!
devices, Java systems and many more clients Once used, and you are hooked for life!