BLAISE BLAISE PASCAL PASCAL MAGAZINE MAGAZINE 31/32 D E L P H I, L A Z A R U S, O X Y G E N E, S M A R T, A N D P A S C A L R E L A T E D L A N G U A G E S A N D R O I D, I O S, M A C , W I N D O W S &
L I N U X
TWO ISSUES IN ONE Book Review: Coding in Delphi By Jim Duff Book:
Coding Delphi Author: Nick Hodges Designing an API: common mistakes By Alexander Alexeev Newest Leap developments Michael Van Canneyt 3D Printing By Bj Rao Kinect ?! By Michael Van Canneyt Smart Mobile Studio 2.0 By Primož Gabrijelčič Interview with David I: plans about updating buying etc A simple superscript text editor By David Dirkse Interview with Gwan Tan - better office Using GEO services in Delphi applications with TMS components
By Bruno Fierens Correcting a bad API design: By Alexander Alexeev
maXbox
The maXbox Pure Code By Max Kleiner Interview with Ray Konopka Programming Bitmap Rotation By David Dirkse Introduction to Model, View and View Model (MVVM) and the Caliburn Micro for Delphi framework By Jeroen Pluimers kbmFMX for XE5 (android) By Fikret Hasovic
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 31/32 D E L P H I, L A Z A R U S, O X Y G E N E, S M A R T, A N D P A S C A L R E L A T E D L A N G U A G E S A N D R O I D, I O S, M A C , W I N D O W S & L I N U X
CONTENTS
ISSN 1876-0589 Royal Library -Netherlands Koninklijke Bibliotheek Den Haag
Articles Editorial Book Review: Coding in Delphi
Editor - in - chief
Page 3 Page 4
Detlef D. Overbeek, Netherlands Tel:+31(0)30 890.66.44/Mobile +31(0)6 21.23.62.68
By Jim Duff Book: Coding Delphi Author: Nick Hodges Designing an API: common mistakes
News and Press Releases email only to
[email protected] Authors
Page 8
By Alexander Alexeev Newest Leap developments
Page 21
Michael Van Canneyt 3D PrintingBy Bj Rao Kinect ?! By Michael Van Canneyt Smart Mobile Studio 2.0
A Alexander Alexeev B C D F G H J L K M N O P S
Page 26 Page 33 Page 41
By Primož Gabrijel čič Interview with David I: plans about updating buying etc A simple superscript text editor
Page 48 Page 50
By David Dirkse Interview with Gwan Tan - better office Page 52 Using GEO services in Delphi applications with TMS components Page 57
By Bruno Fierens Correcting a bad API design:
Page 65
By Alexander Alexeev
maXbox
Editors
The maXbox Pure Code Page
Peter Bijlsma, W. (Wim) van Ingen Schenau, Rik Smit
Page 77
Correctors
By Max Kleiner Interview with Ray Konopka Programming Bitmap Rotation
Howard Page-Clark, James D. Duff
Page 93 Page 98
Trademarks
By David Dirkse Introduction to Model, View and View Model (MVVM) and the Caliburn Micro for Delphi framework Page 102
By Jeroen Pluimers kbmFMX for XE5 (android)
Page 113
By Fikret Hasovic
maXbox
All trademarks used are acknowledged as the property of their respective owners.
Caveat Whilst we endeavour to ensure that what is
published in the magazine is correct, we cannot accept responsibility for any errors or omissions. If you notice something which may be incorrect, please contact the Editor and we will publish a correction where relevant. Subscriptions ( 2014 prices ) 1: Printed version: subscription € 60.-- I ncl. 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 download magazine) Subscriptions can be taken out online at
www.blaisepascal.eu or by written order, or by sending an email to
Advertisers
[email protected]
Barnsten BetterOffice Components 4 Developers ITDevCon Lazarus the complete guide Learnto program
Pag Page Page Pag Page Page
20 25 120 39 19 24
maXbox Raize Software Smart Mobile Studio
Page Pag Page
77 92 40 / 46
Subscriptions can start at any date. All issues published in the calendar year of the subscription will be sent as well.
Subscriptions run 365 days. Subscriptions will not be prolonged without notice. Receipt of payment will be sent by email. Subscriptions can be paid by sending the payment to:
ABN AMRO Bank Account no. 44 19 60 863 or by credit card: Paypal or TakeTwo Name: Pro Pascal Foundation-Foundation for Supporting the Pascal Programming Language (Stichting Ondersteuning Programeertaal Pascal)
Copyright notice All material published in Blaise Pascal is copyright © SOPP Stichting Ondersteuning Programeertaal Pascal unless otherwise noted and may not be copied, distributed or republished without written permission. Authors agree that code associated with their articles will be made available to subscribers after publication by placing it on the website of the PGG for download, and that articles and code will be placed on distributable data storage media. Use of program listings by subscribers for research and study purposes is allowed, but not for commercial purposes. Commercial use of program listings and code is prohibited without the written permission of the author.
2
Peter Bijlsma, Michaël Van Canneyt, Marco Cantù, David Dirkse, Daniele Teti Bruno Fierens Primož Gabrijelčič, Fikret Hasovic Cary Jensen Wagner R. Landgraf, Sergey Lyubeznyy Max Kleiner Kim Madsen, Felipe Monteiro de Cavalho Jeremy North, Tim Opsteeg, Inoussa Ouedraogo Howard Page-Clark, Jeroen Pluimers Rik Smit, Bob Swart,
COMPONENTS DEVELOPERS
4
IBAN: NL82 ABNA 0441960863 BIC ABNANL2A VAT no.: 81 42 54 147
(Stichting Programmeertaal Pascal)
Subscription department Edelstenenbaan 21 / 3402 XA IJsselstein, The Netherlands / Tel.: + 31 (0) 30 890.66.44 / Mobile: + 31 (0) 6 21.23.62.68
[email protected]
Nr 5 / 6 2013 BLAISE PASCAL MAGAZINE
From The Editor
F
irst of all a Happy New Year to you all. This is a season of dark days, so I especially like the light from our Christmas tree which adds to the warm ambience of those holiday opportunities to relaxing with your family and friends, times that are enhanced with little gifts and delicious meals... 2013 was supposed to have been the end of the world – and though it was not the worst of years in the political sense, yet world crises seem not to be over, even if some of them are lessening in severity… The immense horns Mr Snowden placed on us made us realize that our governments do not play by the rules. Ordinary mortals can do little beyond despairing at the invasion of privacy and taking what steps we can to combat it. Politicians must battle this erosion of individual's rights, and I think in the long run the battle will be won. Simply because it has to be. I think the sheer quantity of garbage the intelligence community is accumulating will collapse upon the perpetrators. It will take some time to reach critical mass; but in the world of programming we already have a solution for this: simply remove it by deletion ( Garbage Collection)!
This year we will see 3D printing take off, and of course Leap Motion as a human-computer interface used increasingly. For Leap Motion we are developing a version that will run on ARM cpus: so we will be able to port Leap Motion software to the Raspberry-Pi and set up some new tools. This year we plan to offer our new group of Components for Leap Motion on several platforms.
I wrote a chapter about the history of computing for our Learning Pascal book which has turned out to be a lengthy story: about 70 pages. Naturally I had to
find out something about theabout founding Pascal and write a few lines him. father of In Cologne recently I met Max Kleiner and we discussed the srcins of Pascal, and he told me Professor Wirth was still alive, and so I thought it would be interesting to interview the author of Pascal: Professor Dr. Niklaus Wirth. He will be 80 years old next month. So we will visit him shortly to do an interview. Mr. Wirth has agreed to this and you will be able to read the outcome of our conversation in the next issue. In his honour we will start a new project: PEP – PASCAL EDUCATION PROGRAM. We will launch So many complain about deception. But who knows what PEP for the first time on 15th February, since that is the truth is? We all lie now and then, because we do not his birthday. In line with this educational theme we want to damage our relationship to someone; and I think it plan in future issues to reserve about 4 pages for in the end humans do not always want a black-and-white Pascal beginners, explaining the fundamentals of difference between Yes and No, we also prefer a gray zone. programming, and creating basic examples especially We find this even in scientific theory (although that is denied for those new to programming. as well). Young people (12 to 18 years of age) and students String theory is a very good example of this: it should be impossible for an entity to exist at two distinct will be offered a free Blaise magazine subscription so they can receive information without needing to pay. places at the same time. And when you cast your mind back over history, and look at longer epochs, you will see Details will be available at our website. that there is a pendulum effect: things move back and Many of you always prefer Delphi's help as it was in forth. Sometimes there is a slight improvement before the Delphi 5, 6 and 7. Here is some good news: we are cycle starts all over again… shortly releasing The Helper Component. The universe as a whole seems to work on this principle. This is a component that presents help in the way we Some claim we will find people who can write programs think help should be organized. We plan to announce that will protect us from any intrusion or decryption. the first trial version in the next issue: meantime you Don’t believe them… can help us by letting us know your gripes and wishes for an improved IDE help system. You would do better to spend your money on the newly
developing future like the adventure of the Leap Motion or even 3D Printing, and be creative ( as you probably have always been ) in writing new programs with new code. People outside the programming world may possibilities not understand thisPascal need world to innovate, new in our offers.or the many No Operating System is excluded any more. Pascal is not only doing good it is getting back where it belongs: at the top of the range of available languages, because of its enormous potential in education, for solving problems, in facilitating Rapid Application Development, in bringing the “write-once-run-anywhere” dream closer for cross-platform programmers. As the user group that publishes this Magazine we will always try to help you advance to the next step.
There are many plans for 2014: The next Pascon conference to follow the great success of last summer's event; The new Leap component – Experimenting with Kinect Many articles about Android development using Pascal, and new opportunities for that OS… It’s going to be a very busy year. Very Exciting!
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
3
Book Review: Coding in Delphi By Jim Duff Developers at all levels will be able to use the information contained within this book. It doesn't matter if you're relatively new to Delphi or programming or you've got many years of experience. If there is one common trait I've found among software developers, it is their desire to learn. Learning a new technique or figuring out how to apply that technique to one's unique situation is part of the excitement of being a software developer." Now, coming back down to the details of what's in the book, Nick's next chapter, being the Introduction, yetbook. another flag that waves your desire to readisthe Once more, I provide some partly quoted sentences that illustrate the book's high value for developers. “..noticed that a lot of Delphi developers are “behind”.
That is, they are either using an older version of Delphi, or they aren't using or aren't even aware of all the features in the newer versions of Delphi that they are using." "For instance, two powerful language features were added in Delphi 2009: generics and anonymous methods. Both are features that enable the development of really cool code and frameworks" The content of the book contains the following chapters: Book: Coding Delphi Author: Nick Hodges
There are a couple of ways to preview what a technical software book is all about before one goes ahead to use it. The first quick act is to look at the list of contents and then flick through some of the chapters to preview the wording, code samples and pictorial views. The next step is to analyse the knowledge and validity of the author to make sure that the contents and sample code are going to give you the correct, accurate and productive assistance for your development design and coding. As a reviewer, the above second step is an important one in order to provide the magazine readers with a brief and positive (or negative - not in this case) assessment of the book. So, here we go with a summary of the first two write ups, namely Forward, by Allen Bauer, Embarcadero's Chief Scientist, and Introduction by author Nick Hodges. In Allen's Forward chapter, he provides a write up of Nick's background beginning with "Nick holds the honor of producing one of, if not the first non-Borland built component. In order to learn about Delphi component building, he built TSmiley." Looks like that must have worked OK as Nick became a member of Borland-Embarcadero. And near the end of Allen's summary, he made the following link between the positive aspects of the book and how such value would provide a beneficial effect for developers. 4
COMPONENTS DEVELOPERS
4
Forward Introduction Acknowledgements Frameworks used in Coding in Delphi 1 Exceptions and Exception Handling 2 Using Interfaces 3 Understanding Generics 4 Understanding Anonymous Methods 5 Delphi Collections 6 Enumerators in Delphi 7 IEnumerable 8 Run-time Type Information 9 Attributes 10 Using TVirtualInterface 11 Introduction to Dependency Injection 12 A Deeper Look at Dependency Injection 13 Unit Testing 14 Testing with an Isolation Framework Appendix A: Resources Appendix B: My Delphi Story
The list of chapters shows that the book is aimed at experienced Delphi developers rather than being a beginner's guide. Having already mentioned the Forward and Introduction items above, one more item to mention in support of the qualified author is the final Appendix - My Delphi Story. This is the third part that once more gives the reader the confidence in going ahead to take in the technical aspects of the book.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Book Review: Coding in Delphi (Continuation 1) So, finally getting into the technical documentation, an introductory chapter Frameworks used in Coding in Delphi provides "...a number of open source frameworks to facilitate the coding techniques that are discussed herein. I refer to them here at the beginning of the book to enable you to retrieve the code and get them set up before we begin." Now, we are ready to go, and a major influence of this book is having this subject matter as the first main chapter:
If I could teach a new programmer one thing it would be this: Program to an interface, not an implementation.
This sample quote that soon follows the above one, is provided to further confirm the reasonings provided throughout the book, in line with the sample code and instructions. "All through this book, I'll talk a lot about decoupling
1. Exceptions and Exception Handling Introduction your code and why that is really good. But a definition here is probably a good idea. Structured Exception Handling How Not to Use Exceptions Code can be said to be decoupled when your classes Don't Eat Exceptions are designed in such a way that they don't depend on Don't Generally Trap Exceptions the concrete implementations of other classes. Don't Go Looking For Exceptions Two classes are loosely coupled when they know as Don't Use the Exception Handling System as a Generic Signaling System little about each other as possible, the dependencies How to Use Exceptions Properly between them are as thin as possible, and the Use Exceptions to Allow Your Code to Flow Without the communication lines between them are as simple as Interruption of Error Handling Code Application Writers Should Trap Exceptions possible." Trap only specific exceptions A code sample and part of the associated instructions Component Writers and Library Code Writers Raise that provide associated reasonings are now given as Exceptions the final quoted sample in this review. Raise Your Own Custom Exceptions Let Your Users See Exception Messages Feel Free To Provide Good Exception Messages type Provide Two Versions Of A Library Routine IName = interface Conclusion ['{671FDA43-BD31-417C-9F9D-83BA5156D5AD}' ] fu n c t i o n FirstName: s t r i n; g
This provides not only the ' how' together with sample code, but the reasoning behind having it as a fundamental method of coding. It reminds one of the basic input-process-output of computer processing; if these steps don't work, then the hardware/software doesn't work properly and/or stops working - full stop. Part of his final comments in the Conclusion provides the 'why': "It's quite easy to fall into the trap of using exception handling improperly. ... Use exceptions wisely, and you'll be able to product robust, clean code." 2. Using Interfaces
Introduction Decoupling What are Interfaces? Interfaces Everywhere A Simple Example Implementing an Interface Some Further Things to Note Interface Inheritance Other Things to Think About About TInterfacedObject How to Actually Use Interfaces Why Should You Be Using Interfaces?
end; fu n c t i o n LastName:
st r i n; g
" Note that the declaration of the interface has a Globally Unique Identifier (GUID) right after the initial declaration. This GUID is used by the compiler to identify this interface. You can use an interface without the GUID, but much of the RTL and most frameworks that take advantage of interfaces will require a GUID be present. (You can generate a GUID any time you want in the IDE by typing CTRL+SHIFT+G)” The remaining chapters follow in a logical sequence, each with code samples plus the 'how' and 'why' to use them.
i h lp e D ni g ni do C
This chapter has the only photo of the author, so it just goes to show how important he believes the following advice is.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
5
Book Review: Coding in Delphi (Continuation 2) Nick held the position of Product Manager for a while then managed a portion of the Delphi R&D team. Nick was never afraid to challenge assumptions and actively stand up for the Delphi developer community. Even though Nick’s position didn’t require him to write code, he didn’t let that stop him. He was always looking for ways to keep his programming skills as sharp as possible. As new features were developed, Nick was always right there to give them a workout. To this day there is a large amount of code written by The 'book' reviewed has been the pdf version and Nick that remains a key portion of the overall there are several links in Appendix A such as Unit regression and unit-testing process. Testing, Projects, Good Stuff etc. The following Nick discovered that some of the best ways to link is also provide at the start of the book: learn about new code is to test that new code. It is not without irony that this process requires code to be "Find out what other people are saying about the written. book by clicking on this link to search for this It stands to reason that Nick would end up here; hashtag on Twitter: https://twitter.com/search?q=#codingindelphi writing a book with a single focus on the code. That is the engine that drives all the other revolutionary features of Delphi. Reviewed by Jim Duff Without the code, there would be no “Visual” in the ADUG Melbourne Member Visual Component Library (VCL). In fact, Delphi has always been about getting the The following lines are taken out the book. developer to their code as quickly as possible. The VCL and the newer FireMonkey component Forward frameworks make the use and construction of UI, I first met Nick during a Delphi 1 pre-launch “ boot-camp” that was held at the brand new Borland database, connection and others as simple as Summary Hopefully, the selected quotations from the book have provided the reader with positive views of the author's ability to explain the 'how' and 'why' to advance your coding capabilities. Nick has also given a good background of his own and Delphi's history, from the early days to the present, to show how they and we readers can advance our development capabilities as the technical environment is accelerating in this day and age.
campus in Scotts Valley, California. We had invited a cadre of developers, authors and long-term luminaries to get some in-depth training and presentations directly from the development team and product management. Enthusiastic and exuberant are adjectives that don’t fully characterize my first impressions of him. He was never afraid of asking questions and absorbed the information quickly.
possible. Its ultimate goal is to allow the developer to focus on their task, which is to produce an application that solves a specific problem. It is the code that is written in between those UI, database and connection components that make up the application. Developers at all levels will be able to use the information contained within this book. It doesn’t matter if you’re relatively new to Delphi or programming or you’ve got many years of experience. If there is one common trait I’ve found I cannot talk about Nick without also discussing among software developers, it is their desire to learn. TSmiley. Inquisitive and curious, Learning a new technique or figuring out how to Nick embraced Delphi without reservation. apply that technique to one’s unique situation is part To that end, Nick wasn’t satisfied with what Delphi did, but was just as keen on learning about how it did of the excitement of being a software developer. it. To that end, Nick holds the honor of producing one This is right up there with the thrill experienced when some thought, idea or concept comes to life of, if not the first non-Borland built component. In order to learn about Delphi component building, he built TSmiley. In this one simple component all the core aspects of using Delphi’s Pascal language to extend and enhance the VCL framework were demonstrated. You see, Delphi component building is all about the code. I had the pleasure of working closely with Nick during his time at Borland and then Embarcadero.
6
COMPONENTS DEVELOPERS
4
within the computer.rarely Whendo a developer their running application, they thinksees about the few moments they spent arranging some controls on the User Interface. They feel a sense of pride about that one difficult and thorny problem they solved through the clever use of code. At the end of the day, to a developer, it is really all about the code. Allen Bauer
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 developers. The one thing that I notice is that there are a lot more of you Delphi guys than the average Delphi guy thinks. There are Delphi folks everywhere. Also, I have noticed that a lot of Delphi developers are “behind”. That is, they are either using an older version of Delphi, or they aren’t using or aren’t even aware of all the features in the newer versions of Delphi that they are using. Something I like to do when I’m in front of folks is ask a few questions about what people are doing. I’m always saddened that the response to questions like “Who is doing unit testing?” or “Who is taking advantage of
What you will find are ways to make your code much cleaner, much more powerful, and way easier to maintain. This book is all about the cool, new code you can write with Delphi. It won’t matter whether you are building a VCL or an FM application. I’ve titled it “Coding in Delphi” because I want to make it a book that shows you simple examples of how to use powerful features – that, is about the code. These language features are indeed advanced features – they are new relative to, say, the case statement – and thus many of you are beginners to them. By the end of this book, you won’t be. The approach I’m taking for this book is to try to
Generics?” is pretty meager. This is particularly true for the language features and the run-time library. It’s quite easy to move forward with an older code base, utilizing the new features in the IDE and adding new things to your app using the new high level frameworks and components that come in the newer versions. For example, you might have been developing an application since Delphi 3, moving forward through various versions. Along the way, you may have added some DataSnap functionality, started using the Code Insight features in the IDE, and when you moved to XE2, you start poking around with FireMonkey. But it’s fairly easy to ignore the new language features that come along with those new versions. For instance, two powerful language features were added in Delphi 2009: generics and anonymous methods. Both are features that enable the development of really cool
distill thingsbut down the very basics. TheIexamples will very simple theto explanations deeper. believe that if be you can understand the basic idea in a simple example, it is but a small leap to using those ideas in more complex code. There is no point in giving complex examples when you can get the main thrust using fundamental implementations that illustrate advanced ideas. Between the code in this book and in the samples online (https://bitbucket.org/NickHodges/codinginde lphi²) you can learn all the precepts and then begin applying them to your own code. In other words, nothing fancy here, just the basics – it is then up to you to use these ideas in your own code. This book is not done – it’s instead a living document. Since it is self-published on a platform that makes iteration very easy, I plan on having frequent releases to fix typos (which will, I’m sure, sneak through despite my best efforts ), improve examples and descriptions, and keep up with technology changes. Owners of the PDF should get
code But ifand youframeworks. didn’t understand or feel the need for them, then it was pretty easy to simply not use them. You can still do all kinds of great stuff in Delphi without them, but with them, well, you can write some really beautiful, testable, and amazing code. For instance, a relatively new framework that exists only because of these new language features is the Spring Framework for Delphi, or Spring4D, as I’ll refer to it in this book. Spring4D is a feature rich framework that provides a number of interesting services, including a wide variety of collections that leverage generics, an Inversion of Control container, encryption support, and much more. I view Spring4Dsolid as much a part of the Delphi RTL as SysUtils is. Using Spring4D in your code will make many, many things easier and cleaner. But many Delphi developers don’t know this yet. If the above is familiar, this book is for you: The Delphi developer that hasn’t quite yet made the leap over into the cool and amazing things that you can do with 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 frameworks that have sprung up ( no pun intended) as a result. It will then show you techniques that you can use to write SOLID¹, clean, testable code. You won’t find much of anything about the IDE or the higher level frameworks here. Screen shots will be few but code examples many. You won’t find anything about how to build better user interfaces or fancy components.
notifications of new versions automatically. If you are reading a paper version of this book, I’m sorry I can’t deliver fixes to your hard-copy – perhaps some day that will be possible. The book will be done when you guys say it is done. Maybe, it will never be done because Delphi keeps growing and expanding. I guess we’ll see. As a result, I’m totally open to feedback – please feel free to contact me at
[email protected] with suggestions corrections, and improvements. Please join the Google Plus group for the book.³ I may even add whole new chapters. Thanks for your purchase – this book was a labor of love, so every sale is icing on the cake. Nick Hodges Gilbertsville, PA
²https://bitbucket.org/NickHodges
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
i h lp e D ni g ni do C
COMPONENTS DEVELOPERS
4
7
Designing an API: common mistakes By Alexander Alexeev starter
expert
When you want to create a public component for use by several potential applications, you can register it as a COM object. (Component Object Model (COM) is a binary-
interface standard for software components introduced by Microsoft in 1993) Because COM was designed to simplify exactly this task, creating a publicly offered component as a COM object is an obvious way to do it. However there are drawbacks to development using COM. Firstly COM technology exists only on Windows, and secondly it has a steep learning curve - which means that you need to learn many things before you can develop your public Alsobegin if youto are only developing a component. simple component COM may be overkill. Consequently a developer may avoid the COM route, choosing to implement his component as a simple DLL. When you create a new DLL you need to decide: - what functions you want it to export; - what arguments the functions will have; - how you want to transfer data, and other issues. Taken together, we call this the API of your DLL it( is
also termed a protocol or contract ). The API (Application Programming Interface) is a set of rules (the contract), which you as developer of the DLL and the user (as caller of your DLL ) agree to follow in order to understand each other and to interact successfully. All DLL services are provided only under this contract.
You do not have to do anything beyond distributing a .tlb file (which can also be embedded in the .dll itself). If the 'foreign' language can work with COM, then it can import information from the TLB and generate appropriate header files for itself. A TLB file is a binary file which has been created and edited by a TLB editor (or generated by an IDE). It is also possible to "compile" a TLB file from a text description - an IDL file (.idl or .ridl). If you are smart enough, you can "borrow" this feature from COM. Your documentation is a textual description of the DLL API written by the DLL developer for other developers who will use the DLL ( i.e. it is one-sided). Of course, you can also document internal details for yourself; but that is not the concern of this article. So, you should provide documentation which at least contains a formal description of the API, listing of all the functions, methods, interfaces, and data types, along with explanations of "how" and "why" (the so-called Reference). Additionally, the documentation may include an informal description of the code-development process (a guide, a how-to, etc.). In the simplest cases, the documentation is written directly in the header files as comments, but more often it is provided as a separate file (or files) in chm, html, or pdf format. Unwritten rules
This article describes typical mistakes, features and pitfalls developers encounter as they develop a public DLL API. In
(This section is based on
general, this article serves as a kind of check list for you. You can compare your code with this list and find out how good it is, and if it contains any of the mistakes typically found in such DLLs.
There are some basic ground rules that apply to all programming, which are so obvious that most documentation and books do not bother explaining them (because these rules should have been internalized by practitioners of the art to the point where they don't need to be expressed). A driver planning what route to take wouldn't even consider taking a shortcut through somebody's backyard or going the wrong way down a one-way street. In the same way that an experienced chess player doesn't even consider illegal options when deciding his next move, an experienced programmer doesn't even consider violating the following basic rules without explicit permission in the documentation which allows contravening the rule: • Everything not defined is undefined. This may be a tautology, but it is a useful one. Many of the rules below are just special cases of this rule.
W
e assume you are developing a public DLL. So you will have a .dll file, you will have the header files (at least *.h and *.pas ), and you will have documentation. The header files (or headers) form a set of source files containing structure and function declarations used in the API for your DLL. Typically they contain no implementation. The headers should be available in several languages. As a rule, this means the language used to create the DLL (in our case - Delphi ), C (as standard) and perhaps additional languages (such as Basic, etc.). All these header files are equivalent to each other,
http://blogs.msdn.com/b/oldnewthing/archive/ 2006/03/20/555511.aspx)
representing only translation of the API from one language • to another. The more languages you include the better. If you don't provide header files for a particular language, then developers using that language will be unable to use your DLL, (unless they are able to translate your header files from a language you provide (Delphi or C) into their language). This means that failing to offer headers for a particular language is usually a big enough obstacle that developers in that language will not use your DLL, although it is not an absolute block to such usage. From this perspective COM looks more attractive ( the API description is stored in type libraries in the universal TLB format).
8
COMPONENTS DEVELOPERS
4
All parameters be valid. The contract formust a function applies only when the caller adheres to the conditions, and one of the conditions is that the parameters are actually what they claim to be. This is a special case of the "everything not defined is undefined" rule. o Pointers are not nil unless explicitly permitted otherwise. o Pointers actually point to what they purport to point to. If a function accepts a pointer to a CRITICAL_SECTION, then you must pass a pointer which points to a valid CRITICAL_SECTION.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Designing an API: common mistakes (Continuation 1) o
o
o
o
Pointers must be properly aligned. Pointer alignment is a fundamental architectural requirement, yet something many people overlook, having been pampered by a processor architecture that is very forgiving of alignment errors. The caller has the right to use the memory being pointed to. This means no pointers to memory that has been freed or to memory that the caller does not have control over. All buffers are as big as the declared (or implied) size. If you pass a pointer to a buffer and say that it is ten bytes in length, then the buffer really needs to be ten bytes in length ( or more). Handles refer to valid objects that have not been destroyed. If a function wants a window handle, then you must pass a valid window handle.
•
All parameters are stable. o You cannot change a parameter while the function call is in progress. o If you pass a pointer, the pointed-to memory will not be modified by another thread for the duration of the call. o You cannot free the pointed-to memory either.
•
The correct number of parameters is passed with the correct calling convention. This is another special case of the "everything not defined is undefined" rule. o Thank goodness, modern compilers refuse to pass the wrong number of parameters, though you would be surprised how many people manage to sneak the wrong number of parameters past the compiler anyway, usually by devious casting. o When invoking a method on an object, the Self parameter is the object. Again, this is something modern compilers handle automatically, though people using COM from C (and yes they exist) have to pass the Self parameter manually, and occasionally they mess up.
•
•
o
o
§
If a function fails and the documentation does not specify the buffer contents on failure, then the contents of the output buffer are undefined. This is a special case of the "everything not defined is undefined" rule. Note that COM imposes its own rules on output buffers. COM requires that all output buffers be in a marshallable state even on failure. For objects that require nontrivial marshalling (interface pointers and BSTR/WideStrings being the most common examples), this means that the output pointer must be nil on failure.
(Remember, every statement here is a basic ground rule, not an absolute inescapable fact. Assume every sentence here is prefaced with "In the absence of indications to the contrary". If the caller and callee have agreed on an exception to the rule, then that exception applies.)
·• Function parameter lifetime: o The called function can use the parameters during the execution of the function. o The called function cannot use the parameters once the function has returned. Of course, if the caller and the callee have agreed on a means of extending the lifetime, then those agreed rules apply.
§
Input buffers: o A function is permitted to read from the full extent of the buffer provided by the caller, even if the entire buffer is not required to determine the result. Output buffers: o An output buffer cannot overlap an input buffer or another output buffer. o A function is permitted to write to the full extent of the buffer provided by the caller, even if not all of the buffer is required to hold the result. o If a function needs only part of a buffer to hold the result of a function call, the contents of the unused portion of the buffer are undefined.
The lifetime of a parameter is a pointer to a COM object canthat be extended bythe use of the IUnknown.AddRef method. Many functions are passed parameters 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.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Error: Providing no dedicated initialize and finalize functions
The first mistake you can make as an API developer is not to provide standalone functions to initialize and finalize your DLL, but instead use the DLL_PROCESS_ATTACH and DLL_PROCESS_DETACH notifications from the DllMain callback-function. DllMain is a special function in the DLL, called automatically by the system in response to certain events. Among those events are DLL_PROCESS_ATTACH and DLL_PROCESS_DETACH - these are notifications about the loading and unloading of your DLL. If you are using Delphi, then the initialization section and the finalization section of Pascal units in the DLL are executed in response to DLL_PROCESS_ATTACH and DLL_PROCESS_DETACH, which the system sends to the DllMain function of your DLL. Of course, you do not see this process, it is happening under the hood of the RTL ( language support library). You just see that when the DLL is loaded, all units are initialized, and when it is unloaded, they are finalized.
COMPONENTS DEVELOPERS
4
9
Designing an API: common mistakes (Continuation 2) and some of the functions even report: ...in case of success, the function returns the size of the data, or 0 if there is no data. In the case of error, http://blogs.msdn.com/b/oleglv/archive/2003 the function returns 0. To determine the exact cause, /10/24/56141.aspx). you should call GetLastError, There are very few actions that you can take in DllMain which returns ERROR_SUCCESS for a successful call, with guaranteed safety. or an error code. For example, loading libraries, starting threads, What a nightmare! thread synchronization, COM calls, even calls to other libraries (except kernel32) – performing any of these actions Once again, we encounter two function calls rather than inside DllMain may lead to blocking ( deadlock/hang/freeze). one, not to mention the complexity of the extensibility ( to use our own error codes) and the inability to receive more When you realize that developers usually do not context data about the error. consider whether the code they put in the initialization What is the problem here? DllMain is no ordinary callback-function. It is invoked by a very specific point (
and finalization sections of their units is truly admissible there, you also realize that relying on such behaviour (automatic initialization of units from DllMain ) is not the best design solution. That's why your DLL must export the two functions like Init and Done, which will contain the actions that you otherwise would have inserted in the initialization and finalization sections. Whoever loads your DLL should immediately import and call the Init function. Later he should also call Done just before unloading your DLL. Error: Using ancient techniques for memory management and error handling
In any API, there are two very important aspects: how you pass data of arbitrary (dynamic) size, and how you report any errors from API function calls. A typical WinAPI function contains this logic: The caller must call a function with
lpData = nil and cbSize = 0, whereupon the function returns an ERROR_INSUFFICIENT_BUFFER error, and cbSize will contain the amount of memory in
bytes required to store all the data. The caller can then allocate sufficient memory and call the function again, passing to lpData a pointer to a block of data, and passing the size of the block to cbSize. This approach is complex in itself ( calling the function twice instead of once), and imagine how it would work for frequently changing data. What happens if the data increases in size between your two calls? Then the second call will return an ERROR_INSUFFICIENT_BUFFER error again, and again you will need to re-allocate more memory and repeat the call. That is - to reliably call the function - you have to write a loop. Why do most of the WinAPI functions use such a terribly complicated method? Answer: history. When these functions were first designed there were no modern de-facto standards; moreover the Windows developers sacrificed convenience for the sake of microoptimizations http://blogs.msdn.com/b/oldnewthing/ archive/2004/08/23/218837.aspx
Similarly, a typical WinAPI function reports that it ...returns True on success and False on failure. The cause of the error can be obtained by calling GetLastError.
10
COMPONENTS DEVELOPERS
4
Accordingly, many people when designing their own API see how it is done in the operating system and imagine they should do the same. "Well, if that is how Microsoft does it, then we will copy them because (perhaps) it is correct and necessary to do it that way."
However, they fail to realize that today's WinAPI was created a long, long time ago. A huge number of functions began their life in 16-bit code. These functions were designed for requirements that simply do not exist today. There is no need to use such ancient and uncomfortable approaches. Instead use a modern approach. Here (in descending order of preference) is how you can transfer varyingly sized data without sacrificing the convenience of a call: • [Special case, only for strings] BSTR/WideString. •• •
The interface with athe lpDatafor and cbSizememory properties. A DLL can export function freeing which the DLL itself allocated. You can use system memory management (most often: HeapAlloc/HeapFree).
Here is a list for error handling ( in descending order of preference): • COM style: HRESULT + ICreateErrorInfo. Delphi may additionally take advantage of the "magic" safecall call model. The function returns HRESULT. • • Functions return a sign of success/failure, the error code is obtained from GetLastError. Similar to the previous item, but implementing your • own error code function. Moreover, there is no need to use the naked functions because today we have interfaces. Interfaces offer solid advantages: • Automatic memory management - no problems with the allocation / release. Delphi gives you a safecall calling convention • and virtual methods - for customizing the compiler "magic". Simplified versioning, because an interface • can be uniquely identified by a GUID (aka IID). • Data is grouped with methods for handling this data. Performance does not suffer (a lot). •
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Designing an API: common mistakes (Continuation 3) Error: Using language-specific data types or other language-specific constructs
Obviously if you create an API to be used from various programs each written in a different language, then you cannot use structures that exist only in your language. For example, string (as well as AnsiString, UnicodeString, ShortString, String[number]), array of (dynamic and open arrays), TObject (i.e. any objects), TForm (and components), etc. If you use a data type (or language construct) that has no counterpart in other languages, then code in this very different language simply will not be able to use your API. It would have to emulate the language constructs of your language. So, what can be used in an API? The following: • integer types (Integer, Cardinal, Int64, UInt64, NativeInt, NativeUInt, Byte, Word, etc. - with the exception of Currency); • real types (Single and Double - except Extended, Real, Real48 and Comp); • static arrays (array[number .. number] of ) of the acceptable types; • set, enumerated and subrange-types (with some reservations - see below; it is preferable to replace them with integer types); • character types (AnsiChar and WideChar, but not Char); • strings (only in the form of WideString; strings as PWideChar - allowed, but not recommended; PAnsiChar is valid only for ASCII-strings; PChar strictly prohibited; ANSI-string is prohibited); • Boolean type (BOOL, but not Boolean; ByteBool, WordBool and LongBool are acceptable, but not recommended); • interfaces which use and operate with acceptable types only; • records with fields of acceptable types; • pointers to data of acceptable types; • untyped pointers; • data types from the Winapi.Windows.pas unit (or a similar unit for non-Windows platforms ); • data types from other system headers (they are located in the \Source\ RTL\Win of your Delphi; replace "Win" path with OSX, iOS, etc. - for other platforms).
What is the purpose of a shared memory manager? In a sense, a shared memory manager is a "hack". It is a quick and dirty way to solve the problem of exchanging dynamic data between modules. Never use a shared memory manager at the beginning of a new DLL project. A shared memory manager is a means of backward compatibility, but does not fit with new code. If you need to exchange objects ( including exceptions), strings or other language-specific constructs you have to use BPL-packages, not DLLs. If you have used a DLL, then you must not use Delphispecific types and, therefore, must not use the shared memory manager. Hence the comment at the beginning of the earlier section about the forbidden use of UnicodeStrings ( and so on) in DLLs. You can easily transfer data of varying size if you follow the guidance above; and you already know you should not use Delphi-specific types in a DLL. Therefore, there is no need to use a shared memory manager at all (either alone or using run-time packages). Error: Failing to protect each exported function with a try/except block
The explanation above should have made it clear that you cannot pass any objects between modules. An exception is also an object (in Delphi). Adding two plus two, we realize that you cannot throw an exception in one module and then catch it in another. The other module has no idea how to work with an object that was created in a different programming language. This means, of course, that you have to think carefully how you will report errors (as mentioned above). Here I am talking only about the particular case of an exception. Because an exception cannot (that is: should not) leave the module, you must implement this rule religiously: put a try/except block to catch all exceptions around the code of each exported function. Note: • The function can be exported explicitly ( “exports
•
Error: Using a shared memory manager and/or packages
function-name”) or implicitly ( e.g. as a callbackfunction or other function which returns a pointer to the calling code). Both options must be wrapped in a try/except block. "Catch all exceptions," of course, does not mean that you wrap the function in an empty try/except block. You must turn offthem all exceptions. You have to catch themnot (yes, catch all ) and transform rather than suppress them. You must convert each exception to something prescribed by your protocol (perhaps an error code, an HRESULT, or whatever).
Any shared memory manager (such as ShareMem, FastShareMem, SimpleShareMem, etc.) is a language-specific facility which does not exist in other 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 (interfaces with safecall) then this issue is automatically packages (.bpl packages). This package concept exists only covered for you. The safecall calling convention assures in Delphi (and C++ Builder). you that every method you write will be wrapped by a hidden try-except block through compiler "magic"; and no exception escapes your module.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
11
Designing an API: common mistakes (Continuation 4) Error: Using ANSI-encoding(s)
Gotcha: Enumerated types
Support for Unicode appeared in the Windows API in 1996 (in Windows NT 4), and in 2000 Unicode support came to the client OS ( Windows 2000). Although Windows 95 did contain Unicode functions, there was no full support. The mobile OS market was Unicode only from the start (Windows CE, 1996) through the PocketPC, Windows Mobile, and up to Windows Phone - all these systems support exclusively Unicode, but not ANSI. That is, for more than 13 years Unicode has been the default choice in all Windows versions.
An enumerated type is a convenient way to declare casetypes (rather than using integers ). What is the problem here? Look at this code:
But WideString was used - despite undeniable advantages: there arehardly no problems with theitsexchange of strings between modules, auto-conversion to string and back again, built-in support for Unicode, built-in pointer length. Why the avoidance of WideString? Probably because PWideChar is sufficient for easy transfer of data inside the called function, and returning data from called functions was required much less frequently.
own code. But since you are sharing it with other code, such a change in the size of the data will be a complete surprise to the other code. Overwriting (data corruption) and Access
type TTest =(
,T1 ,T 2 )T;3
var T: TTest ;
Question: What is the size of the variable T in bytes? This is an important question because size affects the For more than 13 years the ANSI API Windows functions position of the fields in records, when passing arguments have been nothing more than stubs that do nothing beyond to functions, and in much other code. converting the string encoding before invoking UnicodeThe answer is that in general you do not know the size. variants of themselves. It depends on the compiler and its settings. By default, it is 1 byte for Delphi. However, another compiler and/or Support for Unicode in Delphi has been present since setting may result in 4 bytes. Delphi 3 - as part of the COM support (that is from 1997). Although until 2008 (Delphi 2009), the entire language Here is another question: since this type occupies 1 byte in support library (RTL) and the component library (VCL) Delphi, it can hold up to 255 values. worked in ANSI. But what if your enumerated type has more than 255 values? However, in spite of the wide open opportunity to use Answer: then the variable will occupy 2 bytes. Unicode when constructing their own APIs most Delphi developers even since 1997 (over 16 years), have not Do you see where this is leading? hesitated to use the "familiar types" - that is, at best a PChar Suppose you have used 50 values in version 1 of your (equivalent to PAnsiChar on the systems of that time ), and at DLL, so the fields of this type occupied 1 byte. worst – a string with the shared memory manager. In version 2 you have extended the possible values up to Of course, those who were smart used PAnsiChar, 300 - and the field now occupies 2 bytes. and PWideChar (since 2000). It would not matter if we used this type only inside our
To sum up: always use Unicode in your API for strings - even if you are using Delphi 7 and work with ANSI-strings inside: it does not matter. In 2013, the API must be Unicode. It is not 1995.
Violations are the result. Note:
in fact the problem arises even with far less than 300 elements. It is sufficient that the type has a 300-th element: type TTest = (
,T1 ,T 2 ,T 3 T=4
); 300
var T: TTest ;
begin
WriteLn ( (SizeOf )); T // shows 2 Well, what about the ANSI-adapters (stubs) to Unicodefunctions: are they necessary? No. Remember why they are there in the WinAPI: as a means of backward compatibility You can solve this problem in two ways: 1. You can place the compiler directive {$Z4} for legacy code which is not familiar with Unicode. 4}) at the beginning of each or {$MINENUMSIZE This era ended in 2000 with the release of Windows 2000. (header file. There is no need to create a means of This will cause the compiler to make every enumerated backward compatibility for something which type 4 bytes in size, even if you do not need so much. never existed in the first place.
No code was using your 2013 API functions with ANSI – so there is no need to add ANSI-adapters to your API.
2.
Note that if you are using the recommendations in the preceding paragraphs you have already covered this issue. By now, you should be using WideString strings ( aka BSTR) to pass string data between modules; or, in extreme cases, PWideChar.
It is possible that the second method is preferable because it clearly answers the question: What are the numerical values of the type's elements?
You can use LongWord ( or Cardinal) and a set of numeric constants ( const T1 = 0 ).
You should not use PChar and PAnsiChar.
12
COMPONENTS DEVELOPERS
4
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Designing an API: common mistakes (Continuation 5) Gotcha: Records
The uncertainty about the size of enumerated types also applies to records: type TTestRec = record A: Byte ; B: Int64 ;
end;
What is the size of this record? 9? 10? 12? 16? The amount of unused space between fields ( filler bytes) also depends on the compiler and its settings. Overall, I would recommend using interfaces instead of records when possible. If you need to use a record: either insert the directive {$A8} ({$ALIGN 8}) to the beginning of each header file, or use the keyword packed for the record. The latter may be preferable - because the alignment rules in Delphi might be different from the alignment rules in another language (for instance consider the case of problems similar to this bug: http://qc.embarcadero.com/wc/ qcmain.aspx? d = 75838 ).
For this purpose, a callback function is provided with a socalled user-argument: either a pointer or an integer ( such as Native(U)Int, but not (U)Int ), which are not used by the API itself and transparently passed directly to the callbackfunction. Or (in rare cases), it can be any value that uniquely identifies the callback function call. For example, the system function SetTimer has idEvent, while EnumWindows function has lpData. These parameters are not used by the functions and are simply passed directly to the callback-function unchanged. That is why we can use these parameters to pass arbitrary data. If you do not implement user-parameters in your API, then the calling code cannot associate a callback-function with the data. We will look at this issue in more detail in the next article.
Gotcha: Sets
We can ask the same question about sets: how many bytes do they occupy? Here, however, everything is more complicated, because a set can take up to 32 bytes, and there is no compiler directive to control the size of sets. Overall, the set is a syntactic convenience for dealing with flags. So instead of sets you can use an integer type ( again: LongWord or Cardinal) and a set of numeric constants, combining them with OR for inclusion in the "set" and checking their presence in the "set" with AND. Error: Failing to provide user-arguments in callback-functions A callback-function is a piece of executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at some convenient time.
For example, if you want to find all the windows on your desktop, you can use EnumWindows: function MyEnumFunc ( : Wnd; HWND :lpData ) :LPARAM Bool ; stdcall; begin / / This is called once for each window in the system end; procedure TForm1 . Button1Click ( : Sender ); begin EnumWindows (@ MyEnumFunc ,) ; 0 end;
TObject
Since the callback function normally performs the same task as the code that sets it, it turns out that the two pieces of code are working with the same data. Consequently, the data from the code setting the callback must somehow be passed to the callback function.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Gotcha: Mixing manual and automatic control of an entity's lifetime
In general, you should try to use automatic control of a lifetime. You have less chance to screw up, because the compiler emits code to keep track of your objects and there is less (fallible) human work to do. But in any case, there will always be places where you want manual control of a lifetime. The junction of these two control mechanisms is what can cause problems. Look at this code: type ISomething = interface procedure DoSomething;
end; TSomething = (
class , TComponent ) procedure DoSomething ; end;
ISomething
var Comp : TSomething ;
function GetSomething : ISomething ; begin Result := Comp ; end; begin Comp :=
TSomething Create . ( ) ; nil
try GetSomething. DoSomething;
finally FreeAndNil( Comp);
end; end;
As you know, TComponent does not use automatic reference counting and you must control its lifetime manually. The problem in the above code is in the line GetSomething.DoSomething. A temporary (hidden) variable of the interface type is created (for storing the result of the GetSomething call), which is cleared in the last line ( at “end;”) - after the object has been released.
COMPONENTS DEVELOPERS
4
13
Designing an API: common mistakes (Continuation 6) Of course, this will never invoke the destructor for TComponent (TComponent uses manual control and does not respond to reference counting), but cleaning up is still necessary. Clean reference counting means calling the _Release method – a method of an already deleted object. Which will lead to an Access Violation. Note: Access Violations are not always raised for such e rrors – due to the “caching” behaviour of the memory manager. Which makes such errors especially dangerous.
A similar problem can be seen in this code: begin := Lib
LoadLibrary (...); Win32Check (
); 0
try Func : = GetProcAddress (Lib , . . . ) ; Intf Func := (...); // ... some action with Intf Intf := nil; finally FreeLibrary( Lib); end; end;
Between the LoadLibrary and FreeLibrary calls there may be temporary variables created that hold references to interfaces from the DLL. Therefore, even if we have cleared all clearly visible references before unloading DLL, hidden variables will be released after unloading DLL and thus will call already unloaded code (hello, another Access Violation). Of course, we (the developers) do not have an eagle eye to find all the places where the compiler wants to create hidden variables, so that we can convert such hidden variables into explicit variables and explicitly free them. Let me remind you that the solution would be to use the fact that all temporary hidden variables are released at the time of exit from the procedure. Therefore, we must clearly distinguish between code that works with manual and automatic control: TComp procedure DoDangerousStuff ( : )Comp ; begin // ... some action with the Comp, including the use of types with automatic control end;
begin : = Comp try
TSomething . ( Create ); nil
DoDangerousStuff( Comp);
Gotcha: Identifying interfaces
Interfaces are different from other data types. They have two levels of identification. On the programming language level an interface is identified by an identifier name (such as IUnknown, IApplication etc.) and is no different in this aspect from any other data type in Delphi. Two interfaces with the same declaration but with different type identifiers are considered to be different data types by the compiler. On the other hand, the interfaces may also be identified not only at the level of programming language, but at run-time (by the machine code) – via meta-information: the interface's GUID (IID). Two completely different declarations, but with the same IID will be considered to be identical by run-time machine code. Gotcha: The immutability of interfaces Once you have published an interface ( "published"
means you publicly release a version of the DLL with this interface definition), you cannot change it (either its IID or its structure) because the interface is used by third party code. Changing the interface declaration will break the other (third party's) code. Instead of changing the interface, you need to create a new interface with a new GUID. You should create a new independent interface (preferably and usually) or inherit from your old interface ( allowed). Gotcha: Expanding interfaces
Look at this code: type IColorInfo = interface
{ABC} function GetBackgroundColor : TColorRef ; safecall; ... end; IGraphicImage = interface
{XYZ} ... fu n ct i o n GetColorInfo : IColorInfo ; s a fe ca l l; end;
Suppose you want to add a new method to interface IColorInfo:
finally FreeAndNil( Comp);
end; end;
type IColorInfo = interface
procedure(DoDangerousStuff : ); Lib HMODULE begin // ... some action with Lib, including the use of types with automatic control end; begin := Lib
LoadLibrary (...); Win32Check ( ); 0
{DEF} // <- new GUID function GetBackgroundColor : TColorRef ; safecall; ... procedure AdjustColor ( constclrOld , clrNew : TColorRef ); safecall; // <- new method end; IGraphicImage = interface
{XYZ} ... fu n ct i o n GetColorInfo : IColorInfo ; s a fe ca l l; end;
try DoDangerousStuff( Lib);
finally FreeLibrary( Lib);
end; end;
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 changed the IID, so everything should be OK, right? Actually - no.
The IGraphicImage interface depends on the IColorInfo interface. When you change the IColorInfo interface, you implicitly changed the IGraphicImage.GetColorInfo method - because its return value has now changed to become another: IColorInfo interface version v2.0. Look at the following code, written with headers v2.0: procedure AdjustGraphicColorInfo (pgi : IGraphicImage ; const clrOld , clrNew :TColorRef ); var pci : IColorInfo ; begin pci =:. pgi GetColorCount ( ); pci clrNew .pci AdjustColor ( , clrOld ); end;
If this code is run on v1.0, the call IGraphicImage.GetColorCount returns IColorInfo version v1.0, and this version has no IColorInfo.AdjustColor method.
But you still call it. Result: you skip to the end of the method table and call the trash that lies behind it. Quick fix - change IID for IGraphicImage, to take account of changes in IColorInfo: type IGraphicImage = interface
{UVW} // <- a new GUID ... function GetColorInfo : IColorInfo ; safecall; end;
Gotcha: Function return values
Functions or methods that return an interface ( as in the previous paragraph) present a problem for the extension. Of course, in the beginning it is a convenient solution: you can call the function "normally" and even hook them into chains like this: Control.. GetPicture GetImage .GetColorInfo .GetBackgroundColor
However, this state of affairs will exist only in the very first version of the system. As you begin to develop the system, you will begin to create new methods and new interfaces. In the not too distant future you'll have plenty of advanced interfaces; and base interfaces that were srcinally in the program, at the time of its birth will provide only trivially uninteresting functions. Overall, very often the caller will need the newer interfaces rather than the srcinal ones. What does this mean? It means that almost all the code has to call the srcinal function to get the srcinal interface, and then request a new one ( via Supports/QueryInterface) and only then use the new interface. The result is not so comfortable, and even more uncomfortable is the fact we now have a triple calls (srcinal/old + conversion + desired/new). Let us look again at the previous point: the modification of one interface makes it necessary to make copies of all the interfaces that use it as a return value - even if they themselves do not change. The best solution for both cases is that the callee code indicates to the called function which interface it wants - the new or the old. This can be done, of course, by specifying the IID: type IGraphicImage = interface
{XYZ} ... procedure GetColorInfo (co n st AIID : TGUID ; safecall; end;
This code update path is very time-consuming because you have to keep track of all references to the variable interface. Moreover, you cannot just change the GUID - you have to create a second interface IGraphicImage with a new GUID and manage the two interfaces ( even though they are identical up to the return value ). When you have several of these changes and the use of a large tree, the situation quickly gets out of control with endless cloning of interfaces for every sneeze.
o u t AColorInfo );
Note that now you cannot use the result of the function, as the result has to have a specific type ( of course it does not have it we should return interfaces of different types), that's why we use the raw data output parameter. Then, you can write code like this: var
We will look at the correct solution to this problem in the next paragraph.
Image : IGraphicImage ; ; ColorInfo : IColorInfoV1 begin ... . Image GetColorInfo ( IColorInfoV1 , ) ; ColorInfo : = Color . ColorInfo GetBackgroundColor ; ... var Image : IGraphicImage ; ColorInfo : IColorInfoV2 ; begin ... IColorInfoV2 . Image GetColorInfo ( , ) ; ColorInfo throw a "no interface" exception, if you run on the V1 ColorInfo.AdjustColor (OldColor , NewColor ); ...
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
//
15
Designing an API: common mistakes (Continuation 8) You do not need to work directly with the IID: Delphi will automatically substitute the IID for the interface name.
Error: Interfaces without an IID
Every interface in your API must be declared with a GUID (interface identifier - IID ). You may be tempted to skip the IID for interfaces that are returned from functions explicitly, without request by IID. But, as we saw above, you need to design your API in such way that you have no functions that return an interface via Result - because it is extremely difficult to further expand the system. Therefore, all of your interfaces must always have an IID.
Error: Returning complex types via Result
A good, general rule of thumb is this: if you need to return something more complex than an integer type (including auto-managed types: interfaces and WideString) – then you should always use the out-parameter instead of the result of the function. Then you avoid bugs in Delphi like this:
http://qc.embarcadero.com/wc/qcmain.aspx?d=75838 ,
and it seems to be a similar problem with real data type,
Error: Missing interface declarations when declaring the implementing class
but I could wrong. I think that be Delphi and MS C++ disagree over which stack (CPU or Math CPU) should be used to return a real result from the function, but I am not 100% sure about this, since I failed to find a link to the bug report.
As you probably know already, there are two ways to implement interfaces in a class: 1. Automatically. You simply declare The problem in all these cases is that Delphi and C++ differ TMyClass = class(the base class, the list of interfaces). in their interpretation of the calling convention model with Once you have declared interface support, regard to returning complex types. there is nothing more to do. Delphi's documentation indicates that the following code: 2. Manually. You override the virtual class method QueryInterface, fu n ct i o n Test: IInterface ; st d ca l; l analyse the parameters and then construct is interpreted as: and return the interface. fu n ct i o n Test: IInterface ; st d ca l; l while MS C++ literally follows the syntax and returns the You would think that with the automatic method, interface directly (EAX for x86-32). we would surely have no problems; but look at the following Thus, instead of declaring functions like this (for example): code (the key points are noted in comments ): fu n ct i o n Test1 : IInterface ;
type
st d ca l l;
ISomeInterfaceV1 = interface
f fu un nc ct ti io on n Test2 Test3 : : WideString TSomeRecord;; s st td dc ca al ll l; ;
['{A80A78ED-5836-49C4-B6C2-11F531103FE7}' ] procedure A ; end;
Always use either this form ( “out” can be replaced by “var” for better performance): procedure ( Test1 procedure ( Test2 procedure ( Test3
out : Rslt out : Rslt : outRslt
// ISomeInterfaceV2 inherited from ISomeInterfaceV1 ['{EBDD52A1-489B-4564-998E-09FCCF923F48}' ] procedure B ; end; =
or this one: fu n ct i o n Test1 : IInterface ; s a fe ca l l; fu n ct i o n Test2 : WideString ; s a fe ca l l; fu n ct i o n Test3 : TSomeRecord ; sa f e ca l l;
The latter is valid for the simple reason that such code is equivalent to: fu n ct i o n ( Test1 stdcall; fu n ct i o n ( Test2 stdcall; fu n ct i o(n Test3 stdcall;
ISomeInterfaceV2 = interface(ISomeInterfaceV1 )
IInterface ); ; stdcall WideString ); ; stdcall TSomeRecord ); ; stdcall
o :u tRslt
IInterface ):
;HRESULT
o :u tRslt
WideString ):
;HRESULT
o:u tRslt
TSomeRecord ):
; HRESULT
Please note that in our case we have removed this problem, since we agreed to use the safecall calling convention. However, what was said in the previous paragraph is still in force: in terms of versioning interfaces, it is better to use constructs like:
(TObj class TInterfacedObject , )ISomeInterfaceV2 // List ISomeInterfaceV2, but not ISomeInterfaceV1 protected
procedure A; // necessary because object implements ISomeInterfaceV1. Otherwise - a compilation error procedure B; end; procedure.TForm1 Button1Click ( Sender : ) ; TObject var SI1 : ISomeInterfaceV1 ; SI2 : ISomeInterfaceV2 ; begin SI2 := . TObj Create ; ( Supports , SI2 ISomeInterfaceV1 , ); SI1 Assert Assigned SI1 ( ( )); // Fails, SI1 = nil (Supports call returned False) SI1. A; end;
It turns out that even though the object implements the interface it does not tell "outside" that it implements it.
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, the mere inclusion of the child interface into the list of interfaces implemented by a class does not ensure the inclusion of an ancestor/parent interface in this list. In other words, in order to be implemented automatically by a class, you must ensure that this interface has appeared at least once in the line "list interfaces" for the class ( not necessarily in this class, it can be in an ancestor, but it must appear somewhere). The presence of a child interface is not enough.
Gotcha: Polymorphism and implementation of interfaces
When your object descends from a class its polymorphic behaviour is achieved by virtual means. But when you use interfaces, all the methods of the interface are already virtual ( by definition). Therefore, there is no need to use virtual methods to implement interfaces (though virtual methods may be required for other reasons - for example, to inherit functionality).
Note that the code:
For example: type
type ISomeInterface = interface
ISomeInterfaceV1 = interface [ '{C25F72B0-0BC9-470D-8F43-6F331473C83C}' ] procedure A ; procedure B ;
['{A80A78ED-5836-49C4-B6C2-11F531103FE7}' ] procedure A ; end;
end;
IAnotherInterface = interface
['{EBDD52A1-489B-4564-998E-09FCCF923F48}' ] procedure B ; end; = (TObj1 class TInterfacedObject , ) protected procedure A; end; =
TObj2 (
TObj1 class , protected procedure B; end;
=
and type ISomeInterfaceV1 = interface
['{A80A78ED-5836-49C4-B6C2-11F531103FE7}' ] procedure A ; end; ISomeInterfaceV2 = interface (ISomeInterfaceV1 )
['{EBDD52A1-489B-4564-998E-09FCCF923F48}' ] procedure B ; end;
TObj2 (
class , TObj1
TObj2 ( class , TObj1 protected procedure B;
ISomeInterfaceV1 )
end; procedure.TForm1 Button1Click ( : Sender ) ; TObject var SI : ISomeInterfaceV1 ; begin : =SI .TObj2 Create ; SI .A ; // calls TObj1.A SI.B ; // calls TObj2.B end;
SI1
= (TObj1 class TInterfacedObject , ) protected procedure A; end;
ISomeInterfaceV1
procedure A; procedure B ; end;
IAnotherInterface )
SI2 : = , TObj2 . SI2 Create ; (Supports ISomeInterface , ); Assert ( Assigned ( ) )SI1 ; SI1 .A ; end;
class TInterfacedObject , )
protected
ISomeInterface
( Sender : ) ; TObject procedure.TForm1 Button1Click var SI1 : ISomeInterface ; SI2 : IAnotherInterface ; begin
=
= ( TObj1
Note that specifying ISomeInterfaceV1 for TObj2 means that the method TObj2.B will implement ISomeInterfaceV1.B. The key point here is - just specify the interface. Please note that: • Method B does not have to be virtual • ISomeInterfaceV1 interface for TObj2 is assembled "piece by piece": the method B is taken from the TObj2, but the method A is taken from TObj1. This is a standard way of working with interfaces and class inheritance. However, as has been said, sometimes you may want to use this code:
ISomeInterfaceV1
type ISomeInterfaceV1 = interface
['{C25F72B0-0BC9-470D-8F43-6F331473C83C}' ] procedure A ;
ISomeInterfaceV2 )
B
protected procedure B; end; procedure TForm1 . Button1Click ( : Sender ); var SI1 : ISomeInterfaceV1 ; SI2 : ISomeInterfaceV2 ; begin SI2 : = TObj2 . Create ; SI2 ISomeInterfaceV1 SI1 ( Supports , , ); Assert ( Assigned ( ) )SI1 ; SI1 .A ; end;
procedure ; end ; TObject
= ( TObj1
class TInterfacedObject , )
ISomeInterfaceV1
protected
procedure A; procedure B ; end;
virtual ; virtual ;
TObj2 = class (TObj1 ) protected procedure B; override ;
end;
are not a problem.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
17
Designing an API: common mistakes (Continuation 10) procedure.TForm1 Button1Click ( Sender : ) ; TObject var SI : ISomeInterfaceV1 ; begin : =SI .TObj2 Create ; SI .A ; // calls TObj1.A SI.B ; // calls TObj2.B end;
Note:
of course, you can "solve" this problem by removing const from the parameter declaration, but you need to understand that providing a well-formed argument is the task of the caller, not the callee.
In general, guided by the rule "Give way to a fool", I would recommend not to use the const modifier for the The effect of this code is the same as in previous code parameters of interface types, and, of course, not to use the example, but the internal structure will be a bit different. constructor directly when passing arguments to such I will only note that because of this difference, it is highly functions. recommended to follow this rule: if your object implements an interface, and this is its only task, then your Error: code (and third-party code) should not use an object of this Double freeing of an interface class - it should use the interface only. Destructors of classes that implement interfaces are very fragile methods. If you try to do too much - you may be in trouble. For example, if your destructor passes a reference Error: to itself to other functions, these functions may decide to Non-obvious feature of reference counting recall your _AddRef and _Release during their work. Look (constructor const-parameter) at this code: Suppose you have a function/method with an interface type parameter that is declared as const: ( :constAArg) ; ISomething procedure DoSomething
and suppose that you pass to the interface the following argument: Obj.DoSomething (TSomething .Create );
What will happen? The const modifier tells the compiler that it should call _AddRef and _Release on the interface.
On the other hand, we are creating a new object. What is the reference count of the newly created object? It is equal to zero. The counter is incremented by _AddRef when the object is used (for example, when the interface is assigned to a variable). We have created an object with the counter set to 0, and passed it to a method that does not change the reference count. As a result, the reference count never drops to 0 ( simply because it never rises from the ground), and, hence, no destructor of the object is called. As a result, we get a leak for this object. The solution is to use a variable: var Arg: ISomething ;
function TMyObject ._ :Release; Integer begin = : Result InterlockedDecrement ( ) ; FRefCount i f Result = 0 t h e n Destroy; end; destructor TMyObject. Destroy; begin i f FNeedSave t he n Save; inherited; end;
It does not look very scary, does it? The object just saves itself before it is destroyed. But the Save method might look something like this: function TMyObject.Save : HRESULT ; var spstm : IStream ; spows : IObjectWithSite ; begin : = Result GetSaveStream ( ); spstm i f SUCCEEDED( hr) t he n begin Supports(spstm , IObjectWithSite, spows ); i f Assigned( spows) t he n spows. SetSite( Self) ; : = Result SaveToStream ( ); spstm i f Assigned( spows) t he n spows. SetSite(nil) ; end; end;
begin
By itself,establishing it looks fine. We get a stream and save in it, further the context information (site)Self - just in case the stream needs additional information.
TSomething := Arg . ; Create Obj . DoSomething ( ); Arg
end;
The introduction of variable causes the reference count to change, and eventually results in a call to the destructor, because now the reference count drops to zero when the Arg variable goes out of scope.
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 running from a destructor, gives us a recipe for disaster. Look what happens: := 1. 2. 3.
4. 5.
6.
The _Release method decrements the reference count to zero and deletes Self. The destructor tries to save the object. := The Save method wants to save into the stream and sets Self as the context. This grows the reference count from zero to one. The SaveToStream method saves the object to the stream. The Save method clears the thread's context. This reduces the reference count of our object back to zero. Therefore, the _Release method calls the destructor of the object a second time.
The destruction of the object a second time leads to fullscale chaos. If you are lucky, the crash inside the recursive destruction will be able to identify its source; but if you are unlucky, it may cause damage to the heap, which will remain undetected for some time, after which you'll be scratching your head. Therefore, as a minimum, you should insert an Assert call into your _AddRef method, to ensure that you do not increase the reference count from zero during the execution of a destructor:
function TMyObject ._ : AddRef; Integer begin Assert ( FRefCount > =) ; 0 Result InterlockedIncrement ( ); FRefCount end; function TMyObject ._ :Release; Integer begin Result InterlockedDecrement ( ); FRefCount i f Result = 0 t he n Destroy; end; procedure TMyObject. BeforeDestruction; begin i f RefCount <> 0 t he n System . Error( reInvalidPtr) ; FRefCount := - ; 1 end;
Note: such a check is not present in TInterfacedObject. TInterfacedObject allows your code to run and call the destructor twice. This check will help you to easily catch "cases of mysterious double calls to the destructor of the object." But when you identify the problem, then what do you do with it? Here's one recipe: http://blogs.msdn.com/b/oldnewthing/archive /2005/09/28/474855.aspx
For our subscibers we have a special offer
The new LIB STICK version 2014 has arrived: all 30 issues on one usb stick, including the latest version of Lazarus Portable and Lazarus for Win / Lin /Mac Price: € 30 + postage € 5 Only for subscribers, otherwise you will have to become a subscriber. Non subscribers are not eligible If you take out a subscription for 1 year or more - download or printed you can have the subscription including download and the new LIB STICK version 2014 (8 GB) for € 50 + extra postage € 5 for the stick The NEW PRINTED version of Learn to program using Lazarus is now available: ISBN 978-94-90968-04-5 As Sewn Hardcover: € 35 + Postage € 22,50 As PDF File € 35 also (included) ready for IPad and Android Pad. for more info see page 24 and 25 The PDF book contains the new history of computing and a download of the latest version of Lazarus installer
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
19
http://www.barnsten.com/smashing-deal Or call: +31 (0)23 542 22 27
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
Each of these disk images contain an .pkg file with the same name, which can be installed by ctrl-click-ing it and selecting ’Open’ (simply double-clicking will not necessarily work) from the menu that pops up. This will start an installation of the package. It is best to install the packages in the order listed here. If the packages are installed in the default order and on default locations, a ’Lazarus’ application will appear in the list of applications. 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. Once installed and started, the IDE is ready for use.
MOTION
Using the Leap Motion on Mac OS with Lazarus Lazarus is a cross platform IDE supporting (among others) Windows, Linux and Mac OS. The Leap Motion works on all of these OS-es. While not initially developed on the Mac, the intention was that the lazarus components for the Leap Motion should be usable on all platforms that the Leap supports, and this includes Mac OS.
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 when it needs to compile code.
Pointables 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.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
21
Programming with the leap motion on the Mac OS including Update of the code for Linux and Windows (continuation 1) Under Windows
Magnetism can be expanded or minimized for testing purposes Delay for viewing individual image changes for testing purposes
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. These three packages can be downloaded and copied anywhere: After opening and compiling them, the lazarus IDE will remember where they are and will correctly resolve all references to them. All 3 packages compiled out-of the box, with the exception of synapse: there the synaser unit gave an error. Since it is not needed for TC/IP support, it can simply be removed from the package. Compiling the fingers and tap demo
After the packages have been compiled, the demo programs are next. The demo programs do not really rely on platform-specific things, and indeed, they compile without a glitch, the running tapdemo is shown e.g. in figure figure 1 on page 3.
We will develop a complete set of components usable for Delphi aswel Lazarus.
The tap demo has been improved with a new ’magnetism’ setting. The tap movement is very sensitive: while tapping, the tip of the finger (used to select a button) moves.
It will contain the following Gestures
The effect may be that the actual button that is under the finger cursor on the moment the tap gesture is received, is not the button for which the tap was intended:
Open Gesture • (a gesture that follows your own design andevents) Cirkels (2D Swipe) • (Swiping - for quick rotation) • (Dragging for precise movement) Left to right (or vice versa) • (Swiping - for quick movement with number of lines to
The downward movement of the finger during the tap may switch the focus to the button below the intended. To prevent this from happening, a kind of magnetism is introduced: magnetism ’shrinks’ the surface of a control, making it more difficult to be selected. It is in fact the number of pixels that the cursor must be inside the actual border, before the control is selected as the new focused control. The focus sticks to the previously selected control, unless the finger cursor is really centered on the new control, hence the name ’magnetism’. This simple trickexperimenting makes the tap will demo a lot more easy to handle, as some confirm. 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.
Tapping • (Clicking)
run predefineable) • (Swiping - for precision) Top or down (or vice versa) • (Swiping - for quick movement with number of lines to run predefineable) • (Swiping - for precision) 3D Swipe • (Swiping - for quick rotation) • (Dragging for precise movement) for this one needs to have a 3D Picture We have already added sound to the gesture. We want to add sound reaction and (snapping with fingers) and verbal commands (English). We will start with this in the coming months...
You can download the latest code from your subscription page.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
23
PUBLISHED BY
W W W . B L A I S E P A S C A L . E U
D E L P H I, L A Z A R U S, S M A R T M O B I L E S T U D I O OXYGENE AND PASCAL RELATED LANGUAGES
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 2 3
4 5 6 7 8 9 10 11
12 13 14 15 16 17 18 19 20
Starting to program Lazarus and Pascal 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 Structured types Expressions and operators Pascal statements Routines: functions and procedures Class: An elaborate type Polymorphism Units, GUI programs and the IDE Display Controls a. TLabel, b. exploring TLabel properties, c. TStaticText, d. TBevel and TDividerBevel, e. TListBox, f. TStatusBar, g. Further options, h. Review Questions GUI Edit controls Lazarus GUI projects Component Containers Non-visual GUI Support classes Files and Errors Working within known limits Algorithms and Unit tests Debugging techniques Further resources
LEARN TO PROGRAM Nr Nr 5 /56/ 2013 2013 BLAISE PASCAL MAGAZINE USINGCOMPONENTS LAZARUS ISBN 978-94-90968-04-5 4 24 96
DEVELOPERS
NEW PRINTED BOOK: LEARN TO PROGRAM USING LAZARUS
PUBLISHED BY
W W W . B L A I S E P A S C A L . E U
D E L P H I, L A Z A R U S, S M A R T M O B I L E S T U D I O OXYGENE AND PASCAL RELATED LANGUAGES
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
25
To 3d Print or Not to 3d print By BJ Rao Detlef Overbeek, Editor in Chief of Blaise Pascal Magazine, recently asked the question... "Can Pascal somehow take part in the desktop 3D printing scene?"
As computational power diversified and progressively became less expensive it opened up 3D printing from the lab to industry. The PC presented the means, the technology.
Perception vs Proof
But what built the explosive and wide spread awareness about 3D printing was not technology alone. It was actually a simple conversation that did. A conversation between a few who wanted to make a particular technology more accessible. They made the first step and that conversation moved on to millions. It brought people together and created
The quick answer? That would be a "No". That train has left the station. Most all software related elements in that process have been claimed by others. Not one of them uses Pascal. Its like the popular having a party somenot of the otherkids kidsare want to join in as and well.now It does matter how much technology, ingenuity and party spirit the other kids may bring. Its not going to happen.
an entire community. A mix of technological development and social bonding. Democratized development.
But that is too easy. “No” is not an answer. It has little meaning here. It is too blunt. Moreover, it actually does not answer the question. It avoids it. We could think of a million reasons why something will not work. Typically, we do. But the idea here is to find an answer about what would work, or at least what might work. Glass half full, not half empty.
The Value of Technology Technology itself has little value. At least not on its own. It is still people that get things done. Pascal, like so many, is simply a language. Its technologies are the existing compilers, frameworks, components and IDE's that allow you to develop in that language. That's what makes it accessible and applicable. Yet, the actual value and success of a language resides in and is proportional to a community that supports it.
In addition, as people of science and technology we don't like to think in terms of yes and no, good and bad or black and white. Those types of answers are too polarized. It is a too simplistic way of thinking. It ignores all the shades of gray. Instead, we tend
Looking back 15 years ago Pascal was at an all time high. And, for good reason. It made DOS and Windows development extremely easy, versatile and fast. More importantly, it was not just building a community but communities, plural. Apart from
to rather observe the properties and patterns of something. Discover how these might be used to serve some purpose or task. Creating something new. We think about truth. If we did not then what we create won't usually work in the real world. It's that simple.
developing business applications it was the standard in education. It was also the standard on the DIY (do it yourself) front. It made things clear and its programing-correct structure was building good programming practices for all. Unfortunately for reasons other than its technology it was not setting standards. Rather, it was forced to follow the standard settings of others.
But there is also a kind of a problem here. Having a scientific or technical background means that most tend to be more inclined to search for technical solutions as answers. That's fine. Yet, at this point, these only serve as tactics. Actually, we should be searching for something else. A strategy is needed. Something that defines the situation and the problem so that we may understand what "can" be done and what "needs" to be done first. The solution tactics, the technology, will follow later.
Many of the technological benefits of Pascal exist today. And, a lot more. The Delphi and Lazarus FPC solutions allow developers to go farther, wider and deeper than ever before. More than most any other language.
Pascal is best? There is no such thing as best. What is best depends on how something fits an application. Yet, Pascal is making it progressively less warranted to use something as messy and slow So what might be the answer? as java, something as cumbersome and tedious as In part, Pascal might take part in the 3D printing 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 lack of accessibility nor applicability. It is, typically, merit that is also worthy of conversation and a lack of awareness. Pascal is powerful, but people collaboration. Something that uses technology to 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 “Change” has become more of a constant than an instance, is not new. It has been around for some exception these days. And, that brings along entry time. A long time, in fact. Yet, the success of one points, opportunities. Opportunities for Pascal to thing is typically the result of the success of enter into and create its own party. another thing. For 3D printing it was the PC.
26
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Perception versus proof:To 3d Print or Not to print
The 3D printing scene is not immune to change either. It is not something static. New developments are arising all the time as these technologies diversify and their applications widen, deepen and demand grows. Other platforms are also on the rise such as mobile which may allow new solutions for 3D printing. Mobile would seem to be an attractive an obvious choice. Jump on the bandwagon. A great stepping stone for Pascal and 3D printing. In many ways that can be argued to be true. Yet, this is a very frontal approach. Pascal may make headlines here, even build some awareness. But there is a lot more involved in capturing and sustaining a conversation that builds communities. There are platforms other than mobile that are finding a lot of market acceleration. They are moving fast. More importantly, 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.
3D Printing a More Tangible Idea What does 3D printing have to do with Pascal? Well...not much really. That is unless Pascal somehow starts finding its way into developing solutions here. 3D printing is an exciting and emerging manufacturing technology. One that Pascal could take part in. In terms of software development, Pascal provides all the solution. But how that might eventually happen will depend on you. With that in mind, the incentive here is to present a very brief but in-depth and critical overview about 3D printing. Something that sheds light on how it essentially works, what it can do as well as what it can't do (yet).
Sure, the user base of these platforms is significantly smaller than mobile. But this user base may also be significantly more important to Pascal. Why? The proportion of their developer base is second to none! Nearly all users are also developers to some level. Their age range is wide starting from the junior all the way to the senior. Also, these devices are found progressively more in the education system. This inherently instills democratized development innovation. These devices permit, for many, the first encounter with what goes on behind the screen and with programming. Shouldn't Pascal be one of those first encounters? Bottom-Up. They form the root, rather than just the branches and the leaves. Most importantly, that which offers solution here can impact, build and sustain awareness in the most profound and fundamental of ways. Pascal offers the means, the solution. It's up to you to exercise it and build awareness. Get a Return on Your Investment.
Misprint? Why write another article about 3D printing? The internet already offers an abundance of information on the subject. Mountains, in fact. Everyone has been talking and writing about it. And, companies like MakerBot, Ultimaker and several others are building a vast knowledge base in the peer production realm. All True. But there may be more to be said...and said again.
These days we can print out 3D objects in plastic, metal, ceramics and even organic material. We can print in just about any shape we can imagine. All this suggests that we will soon be able to print out trains, planes, automobiles and even complex body parts. We also hear claims like; “We will print our own phones” and “Conventional manufacturing techniques will soon be rendered obsolete”. 3D printing is the holy grail to manufacturing and the distribution of products. Well...yes and no. Talk is cheap and the web has a way of taking that to the next level. The internet flattens things out. Sure, truth has a way of eventually rising above it all. Only the most robust of ideas will move up through the ranks and prevail. Moving from perception to proof is what brings value to something. It, at the very least, makes it more tangible to talk about. But that process takes time.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
27
3D Printing a More Tangible Idea In the mean time how does one separate what is real from what is rumor or just plain wrong? At this point 3D printing offers the ability to print out the "shape" of an object, in a certain material and within certain accuracy. For many types of products that may be enough to get the job done. Yet, for most, there is typically a lot more involved in the manufacturing of functional products.
Claims overshoot reality, jump out of the box, and things can become confusing. That's fine, its part of the process of innovation to imagine that what we don't have or can't be done, yet.
While 3D printing technologies are rapidly advancing and diversifying in applications, at this point, we are pretty good at printing out only the shape of an object and doing so within a certain degree of accuracy. Again, there is typically a lot The impact of 3D printing on industry is undeniable. more involved in manufacturing functional Just as it was more than 25 years ago. But the real products. revolution here is not only the technology. The real revolution is more about accessibility awareness. More specifically, awareness about and how we make things and how we think about making things. A Revolution to Shape Ideas and Culture Rapid prototyping, free form manufacturing, additive manufacturing and 3D printing. You could dispute the differences but they, more or less, boil down to the same concept.
Engines of revolution. Labeled with words like "disruptive" and "revolution", additive manufacturing, or 3D printing technologies as they are referred to these days, are actually not that new. The concept is very old. In fact, many of the patents related to the latest core technologies over the past 25 years have expired or will soon expire. What is actually new here is the recent accessibility of these technologies to the masses. At least to some level. Specifically, low cost 3D printers for home use and 3D print service providers. You can now own a 3D printer or 3D print service providers can provide the latest and greatest technologies without having to actually own the machine. In any case, 3D printing is now more tangible to us in our hands, minds and in society.
Hammering Things Out Use a hammer to drive a nail into a piece of wood. Depending on your aim you will hit the nail with hammer without bending the nail. In most cases, not a problem. A trivial, age-old process that most anyone has done before. Now lets do the same with 3D metal printed products. But don't be surprised if the nail and/or even the hammer breaks or even shatters on impact.
3D printers can print a metal hammer and a nail. But creating functional products involves more than just creating the shape of an object. There are very specific technologies involved in the manufacturing of something even as trivial as a nail. Nails are typically made of rolled, cold-drawn metal. The process involves rolling and stretching the nail metal and aligning its metal crystals in such a way that allows it to become more rigid, springy, tough or otherwise less brittle. The truth of the matter is, you never really hit the nail head on. But due to its forging it is forgiving and springs back in most cases. In the worst case it bends but won't break, let alone shatter. Think about that for a moment the next time you hear about 3D printed firearms.
Technology and its merit aside, the significance here is the main stream "awareness" that this brings about. And, that by itself is a revolution. How we convert an idea into tangible and functional form.
What this example illustrates is that while the 3D printed shape of an object may suffice for some applications there are many more applications where it won't. This does not mean that 3D printing 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 like any revolution, what is real and what is rumor become intermixed as things move toward a critical mass. Belief and perception typically precede objective observation and proof.
28
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
The Basic Idea The idea, the core concept behind 3D printing is actually very simple. Traditional manufacturing techniques rely on removing material until the desired shape and accuracy of some material has been attained. You know, like a chisel to sculpture wood or a lathe to cut cylindrical shapes. 3D printing or additive manufacturing takes the opposite approach. Instead of removing material it adds material. It builds it up until the desired object shape and accuracy has been attained. Bottom-Up instead of Top-Down.
The working materials for the FDM process are widely available, inexpensive and the resulting prints can be durable with little to no postprocessing. The concept and workings of the FDM process also require only a simple, relatively low cost design setup to get started. Together these factors imply accessibility. Accessibility for all. The process involves the deposition of molten plastic on a flat bed using an extruder. The extruder is typically positioned using an XY linear motion system. In this manner layers are
Think of 3D printing as using Lego stacked and positioned to build up an bricks object.orThe bricks are like building blocks connected together. It should be evident that the smaller the bricks, or building blocks, the more accurate the resulting object shape will be. Its that simple.
built and a 3D shape is formed.
built up of many infinitely thin cross-sections or layers. If we precisely stack up these layers we again have the object shape of the apple. The same approach applies to 3D printing. You are taking a complex 3D shape and dividing it into simple layers which are much less difficult to manufacture.
process is repeated with a new sheet layer above the previous.
LOM Surprisingly, not much focus is given to this technique despite its merit. Laminated Object Manufacturing involves the use of sheet material such as paper, plastic, metal foils or fabrics. By using a sheet material the layer thickness is But what makes additive manufacturing so important? There are several reasons. Probably the normalized, defined and is thereby easy to control. Also the process allows selective coloring when, most significant is that additive manufacturing for instance, paper is used. The printed products techniques are much less constrained in what they can make in terms of object shape. Virtually any 3D are very durable and resemble wood (in the case of paper). object shape can be produced within the working volume of a 3D printing system. In other words, Typically a role of sheet material is unwound flat 3D printing offers the freedom to create just about over the working area of the printer. A laser or any object shape you can imagine in a single knife cuts the 2D layer profile as well as a grid of operating session. cuts around the object. The grid is to allow the release of the model after printing. A layer of Cut an apple in two. The cut surfaces are crossadhesive is applied after a layer is cut and the sections of the apple. Think of the apple as being
Creating layers involves slicing a 3D modeled or 3D scan of an object into a multitude of thin sections. Each layer is then printed out and stacked on the previous layer until the full 3D object shape is printed. The thinner the layer the more smooth and accurate the object shape will be.
The LOM apparatus can be relatively simple in construction and easy to scale in terms of design. The materials used are usually non-toxic, low cost and easy to handle making the LOM process an attractive choice for the prosumer and small business.
The only limitation is the cutting mechanism. The laser used to cut the sheets at high speeds is relatively costly and can be dangerous. Using a knife to cut the paper may be less precise and The building blocks or base materials used to make slower. Moreover, paper is stronger and more abrasive than one may think. A knife, at the very a cross-section layer can be of various format. least, would need to be made of some carbide, The format obviously depends on the printing technique. Formats such as sheets or beads layered widea or diamond for any long term use. onto one and other, powders connected together SLS/SLM using adhesives or welding or even liquids (resins) which are photo-cured are employed. These techniques offer some truly spectacular 3D print solutions. In particular, the production of metal products. There are several variations of this FDM technique. What they have in common is the use of The Fused Deposition Modeling technique (FDM) powders as the base material format. was probably the most responsible in forming the entry point for the 3D printing revolution/hype in The focus here is Selective Laser Sintering/Melting. recent years. At least a good part of it. It's by no means the best 3D printing technique. But it offers Powders are fused/melted together in a powder bed to create layers, typically using a high power accessibility to a very large audience. laser. After each layer a new layer of powder is brushed over and the process repeats. The powder is brushed away from the print after it is completed.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
29
Other powder based approaches exist. For instance, an inkjet type dispenser dispenses an adhesive in a metal powder bed which selectively binds a metal powder together in order to build layers. This “green” product is then sintered in order for the adhesive to burn out and allow the metal powder to fuse into a solid. It should be clear that powder handling is a difficult and messy process. These technologies can be complex to manage and handle. Also, when working with metal powders, elevated temperatures and lasers there are many risks involved. SLA The historic Stereo Lithography process, patented in the late 80s, formed the foundation of 3D printing as we know it today. The technique has gone through a long process of development and it has some serious merit to offer.
But, traditionally, the technique also had some serious limitations as a desktop or home use device. Apart from the printing device itself, probably the most apparent is the fact that it relies on photo-cure resins to build 3D models. The process is sound, it works. In fact, it works very well. But the resins involved are typically toxic, messy and costly with limited shelve life. All in all, too cumbersome, complex and costly for many let alone the average consumer. But recent changes in design approaches and, in particular, the resins involved are making a difference. The development and diversity of photo cure resins and suppliers are creating a better fit for this process on our desktop. The process relies on a photo cure resin which is selectively and acutely cured to form layers. The resin in a SLA process is a liquid which typically cures under UV light. Lasers and other UV light sources may be used. After each layer is cured another layer is added in order to build the object. The SLA process appears to be the next candidate in the desktop 3D printing scene. Most systems rely on the use of DLP projectors (Digital Light Processing) to illuminate the resin. These are fast. Laser scanning systems however offer other important benefits as well. Pascal and 3D Printing While new suppliers of desktop 3D printers for home use seem to appear every other week the 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.
One of the reasons that so many suppliers now exist is due the high demand for these machines. True. But in part, the demand is the result of the explosive awareness built by the democratized open development itself. The conversation just gets bigger, creating its own demand. A marketing feedback system like the ringing of a microphone that is held too close to a speaker box. In addition, the democratized open development has made the technology ( software and hardware) so incredibly accessible to all. You don't need real engineering skills to create a product and become a supplier. This is great for Joe DIY (Do It Yourself) but also leaves a whole lot to be desired for most others. Turn-key? Not really. In many cases, you get what you pay for. And, while the obsession with “low-cost” can bring forward the greatest innovation it can also deprive the development process from making good design decisions. All in all, becoming a supplier is as easy as creating your own linux distro. You don't really need Linux skills for that. If you intend to get a 3D printer, get the facts first. Find out who is behind it all. Cost i s important but not absolute. 3D printing is not just software development, it's electronics and mechanical engineering combined. Debugging is a whole new ball-game here.
While it may seem otherwise, most 3D printing technologies are not available to the average consumer due to cost, complexity and safety issues. This does not mean that they won't one day take part as a household appliance and be as easy to use as a glorified coffee maker. We are just not there yet. 3D printing is still in its infancy. With that in mind it should be apparent that there is room for improvement. A lot of room. And, that means that there are opportunities. Opportunities for Pascal to provide solution. To appreciate this we need to gain a better idea on the processes involved. While 3D printing technologies may differ in their operation and control the basic processing is more or less the same. These include: Pre-Process – Main Post-Process The Pre-Process stageProcess relates to– processing the 3D data and preparing it for the printer to be printed in the Main-Process.
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 impossible task to perform, especially these days. relates to things like cleaning, assembling or In fact, even when it comes to 3D modeled data post-curing of the printed material after the drawn in a graphics or CAD application, things can Main-Process is completed. take time to prepare and involve a lot of work. Desktop 3D printing, certainly for home use, is still In particular it is the Pre-Processing and equally an art as it is a technology. Main-Processing stages were the magic happens and where Pascal can provide The block diagram provides a birds-eye view of the solutions. The Main-Processing stage pertains to typical Pre-Process elements of 3D printing data the control of the machine, the printer. preparation. The 3D scan data process flow has also been included to illustrate what's typically involved. If you are new to 3D printing then you may be inclined to think about the “washer and dryer” solution. The 3D scan and 3D print solution. This is a scenario where a 3D scanner is used to scan in some usually trivial ( typically broken) part and then that part is reproduced through 3D printing. A perfectly reasonable idea. And, this line of thought is correct. It is certainly where we are headed.
Solid Model The 3D data used for printing must be a solid model, a closed vessel. It must be leak-proof. Think of this as modeling your house for 3D printing. You may have modeled the front side but what 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 model in 3D space from all angles. Anything less approach is simply too impractical, too difficult. than that is in the strictest sense not a 3D model, 3D scanning can be (very) complex. it’s not solid. It is certainly possible to force close a In many ways it is still an art . model, assuming that these closed sides are not of This is certainly true when high levels of accuracy 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
3D SCAN DATA
DECIMATE
SOLID MODEL
MODEL INTEGRITY CHECK
SIZE, ORIENTATE
ADD SUPPORT MATERIAL
SLICE LAYERS
SMOOTH
G-CODE
EDIT
3D PRINTER
3D GRAPHICS
CAD
POST PROCESS Nr 5 / 2013 BLAISE PASCAL MAGAZINE
31
There are many different 3D file data formats. The standard for 3D printing is the STL file format. Binary versions are popular but text (ascii) is also available. The reasoning for ascii type format was that this allowed 3D print operators to manually examine the data if needed. The STL file format is popular but it is far from efficient. To maintain some form of readability the file construct includes an incredible amount of redundant information. In particular for 3D scan data this means that incredibly large file sizes are not uncommon. Fortunately other file format constructs are on the
The Final Layer 3D printing has been around for some time. Still, things are actually just getting started. The concept is sound and proven. And, as the awareness grows so also will these technologies diversify. Opportunities will always be around the corner. Its important to find entry points. Things that you can claim and offer solution in.
rise.
collective view of 3D printing is becoming more realistic and tangible as the hype debris settles and our experience grows.
Object Orientation and Size The accuracy of 3D printers is typically not the same in all 3 directions. The layer thickness (Z) may even be a constant (LOM, for instance) while the X axis and Y axis may allow for much higher accuracy. The minimum wall thickness of your 3D model may also be limited. All in all, the orientation in which you print your model may be directly related to the desired quality and even feasibility of your print. Support Material Overhang. A 3D model may have a shape that extends over its base foot print. Depending on the 3D print technology, support material for the overhang may be required. The construct of this support material may be very important. In most cases it should not extend print time too much. It should use minimal material (cost) while allowing maximum effectiveness. It should also allow easy and quick removal with minimal surface damage to the 3D print. Slicing Creating layers means slicing up the 3D model into 2D (2.5D) cross-sections. The minimal permitted thickness of the layers will depend on the 3D printing machine capabilities. The greater thickness of the layers the less the 3D printing model will resemble that of the srcinal 3D data. The thinner the thickness of the layers the more time it will take to print.
More importantly, more of us are now more aware of what's involved in 3D printing. What it can do, what it can't do and how it should be done. Our
At this point we print using bits and pieces to create layers but a time will come when we print in particles and molecules to create structures. Real or 3D printed? What's the difference? That will be a revolution like no other seen before. Dr. Richard Feymann http://en.wikipedia.org/wiki/There%27s_Ple nty_of_Room_at_the_Bottom Molecular Imprints http://www.molecularimprints.com/ ASML http://www.asml.com/asml/show.do?ctx=427 Foresight Institute http://www.foresight.org/ Dr. Eric Drexler http://en.wikipedia.org/wiki/K._Eric_Drexl er 3D Printing and Grey Goo http://heywhatsthebigidea.net/3d-printingand-grey-goo/
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.
32
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
KINECT
KINECT
KINECT
Programming the Microsoft Kinect in Pascal By Michaël Van Canneyt starter
expert
Delphi, Lazarus
FreePascalshipsasingleunit libkinect10 which combines all definitions of the Delphi units. The names, structures and interfaces should be identical. Both sets of units load the library dynamically. Loading and unloading the library must be done through the following functions: F u n ct i o n LoadNuiLibrary ( C o n stFilename : string = LibKinect ) : Integer ; Procedure UnloadNuiLibrary;
Figure 1: The kinect for XBox 360
In February 2012 we started to do research on the Kinect and its relatives. Now finally we have the first results: The Kinect is a device created by Microsoft to enable NUI (Natural User Interface) for X-Box and Windows. It tracks the movement of the human body, and provides a stereoscopic image of whatever is located in front of the camera. The C++ API for this device can be used in Pascal Introduction
Some years ago, Microsoft introduced the Kinect for its game console XBox: a small camera-like device that sits on top of the TV and registers players and their movements. The Kinect does for complete human bodies what the Leap Motion (introduced in an earlier article - issue Nr. 30 , page 47 ) does for hands: it can track the position and movement of the human body (called skeleton tracking), and provides a stereoscopic image ( depth map) of whatever is located in front of the camera. In addition, the Kinect) can be used as a microphone ( it accepts voice commands andalso simply as a webcam. A picture of the device can be seen in figure 1 at the top of this page.
When loading the library, the filename is optional and when none is specified, the default kinect10.dll is used. The free Pascal version of the units uses a reference counting mechanism, which means that UnloadNuiLibrary must be called as much as LoadNuiLibrary was called. The kinect library does not need to be initialized: once it is loaded, it is ready for use. The library exposes a few global functions, and some interfaces. One of these interfaces is INuiSensor, representing the Kinect camera. It has the following signature: (for brevity, the arguments of the methods have been omitted) INuiSensor = interface(IUnknown )
[ '{1f5e088c-a8c7-41d3-9957-209677a13e85} ' ] Function NuiInitialize ( dwFlags : ): DWORD HRESULT; Procedure NuiShutdown; Function NuiSetFrameEndEvent () : ;HRESULT Function NuiImageStreamOpen () : ;HRESULT Function NuiImageStreamSetImageFrameFlags () : HRESULT; Function NuiImageStreamGetImageFrameFlags () : HRESULT; Function NuiImageStreamGetNextFrame () : ; HRESULT Function NuiImageStreamReleaseFrame () : ; HRESULT Function NuiImageGetColorPixelCoordinates\ FromDepthPixel () : HRESULT ; Function NuiImageGetColorPixelCoordinates\ FromDepthPixelAtResolution () : HRESULT ; Function NuiImageGetColorPixelCoordinate\ FrameFromDepthPixelFrameAtResolution() : HRESULT; Function NuiCameraElevationSetAngle () : ; HRESULT Function NuiCameraElevationGetAngle () : ; HRESULT Function NuiSkeletonTrackingEnable () : ; HRESULT Function NuiSkeletonTrackingDisable : HRESULT ; Function NuiSkeletonSetTrackedSkeletons () : HRESULT; Function NuiSkeletonGetNextFrame () : ; HRESULT Function NuiTransformSmooth () : ;HRESULT Function NuiInstanceIndex : integer ; Function NuiDeviceConnectionId : PWideString ; Function NuiUniqueId : PWideString ; Function NuiAudioArrayId : PWideString ; Function NuiStatus : HRESULT ; Function NuiInitializationFlags : DWORD ;
Since end 2011, the Kinect SDK is also available for Windows PCs, and a C# (or .NET) and C++ API is available. The C# interface is more elaborate than the C++ interface, but the C++ interface is usable in all programming languages, including, as it turns out, Object Pascal. The SDK can be downloaded for free from the MSDN developer website, and version 1.7 was used for this article (a new version is scheduled for Quarter 1 2014 ). It contains some libraries ( both for .NET and native development), which need to be distributed with an application that wants to connect to the Kinect. In particular, the kinect10.dll must be distributed. The Microsoft Kinect C++ SDK provides roughly the same functionality as the open-source OpenNI and OpenCV libraries, but has a much more simplified API than the latter libraries. end; This article shows how to use the API for a simple Not all of these methods will be discussed here, just the skeleton-tracking application: discussing the complete ones needed to track a skeleton and view the depth map. SDK is beyond the scope of a single article. The kinect library can be used with multile kinect devices. The library therefore exposes some of the Pascal Headers - The kinect API ISensor methods as global functions: The C++ SDK headers have been translated to Pascal. if only a single kinect is connected to the computer, then A version for Delphi is available on Google code: these global functions can be used to control the kinect. http://code.google.com/p/kinect-sdk-delphi/ Other than that no interface is used, the methods are The Delphi units are called the same, and the same procedures must be followed. NuiAPI,NuiSensor, NuiImageCamera, SuiSensor Since the operating method is the same, in this article the and NuiSkeleton. more general approach using interfaces is used.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
33
Programming the Microsoft Kinect in Pascal (Continuation 1) Detecting a kinect device
Once the kinect library is loaded, an interface to a kinect device must be retrieved. This can be done using 2 global functions: F u n ct i o n NuiGetSensorCount ( : o u tCount) : integer HRESULT; F u n ct i o n NuiCreateSensorByIndex ( I n d e x : integer ; out ppNuiSensor : INuiSensor ): ; HRESULT
The NuiGetSensorCount function returns the number of connected Kinect devices. The NuiCreateSensorByIndex function then creates a INuiSensor interface for the Index-th device. Both functions return a HRESULT value: that means that the result of the function can be checked using the windows’ unit Failed function. The usable function result is always returned in out parameters. Once a device has been detected, and an interface to the device was returned, the device must be initialized. When the device is no longer needed, the device can be shut down. These operations can be performed using the following methods of the INuiSensor interface: Function NuiInitialize ( :dwFlags ): Procedure NuiShutdown ;
DWORD ;
HRESULT
When initializing the device, the device needs to be told what kind of processing it should do: • calculate depth image, • track player skeletons.
That means that the calling application needs to set up several event handles, one for each kind of data stream it wishes to receive. These event handles must then be passed on to the data stream initialization functions. For the demo application, 2 streams will be examined: the depth map and the skeleton tracking data. Both streams are provided through memory blocks that must be requested through some methods of the INuiSensor interface. The skeleton tracking stream is initialized ( or stopped) through the following functions: Function NuiSkeletonTrackingEnable ( hNextFrameEvent: THandle ; dwFlags : DWORD ) : HRESULT ; Function NuiSkeletonTrackingDisable : HRESULT ;
The first parameter to NuiSkeletonTrackingEnable is the handle used to report the presence of a new skeleton frame. The second parameter determines how the tracking data is calculated and returned: NUI_SKELETON_TRACKING_FLAG_SUPPRESS_NO_FRAME_DATA
When set, the NuiSkeletonGetNextFrame method will not return a E_NUI_FRAME_NO_DATA error when no data is present, instead the call will block until data is present or the timeout is reached. NUI_SKELETON_TRACKING_FLAG_TITLE_SETS_TRACKED _SKELETONS
When set, the detected players are not really tracked. NuiSkeletonSetTrackedSkeletons
Since each stepnot in processing takes CPU that time,will not be it is important to request processing used anyway. This is specified in the dwFlags option to the NuiInitialize function. The flags are an OR-ed combination of the following constants:
The must be used to select the players that should be fully tracked.
NUI_INITIALIZE_FLAG_USES_AUDIO Request audio data. NUI_INITIALIZE_FLAG_USES_COLOR Request color data. NUI_INITIALIZE_FLAG_USES_DEPTH Request depth data.
This means that the 10 lower-body joints of each skeleton are not tracked, resulting in less calculations ( the default is to track the whole body, 21 joints).
NUI_SKELETON_TRACKING_FLAG_ENABLE_SEATED_SUPPORT
enables seated skeleton tracking.
When tracking a person seated in front of a computer, this option can be used to reduce calculation time. NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX The depth image stream, as well as other image streams, Request depth data with a player index. are initialized through the following functions: NUI_INITIALIZE_FLAG_USES_SKELETON Function NuiImageStreamOpen( Request skeleton tracking eImageType : NUI_IMAGE_TYPE ; eResolution : NUI_IMAGE_RESOLUTION ; For the purpose of this article, only the last 2 will be used. dwImageFrameFlags : DWORD ; The difference between the dwFrameLimit : DWORD ; hNextFrameEvent : THandle ; NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX THandle HRESULT out phStreamHandle : ) : ; NUI_INITIALIZE_FLAG_USES_DEPTH Function NuiImageStreamSetImageFrameFlags(
and
is that the former encodes a player index in the depth map: hStream : THandle ; the depths are returned as word-sized values, and the 3 last dwImageFrameFlags : DWORD ) : HRESULT ; bits of the word are used to encode a player index The NuiImageStreamOpen function opens an image (meaning that at most 7 players can be used ) stream. Which images the stream returns is specified through the eImageType parameter, which can have one Reading data from the device of the following values: The kinect API provides several data streams: video, audio, depth map, skeleton data. The API uses event handles to report the presence of data in one of these streams.
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. NUI_IMAGE_TYPE_COLOR_INFRARED an infrared image NUI_IMAGE_TYPE_COLOR_RAW_BAYER a Raw Bayer color image (RGB) NUI_IMAGE_TYPE_COLOR_RAW_YUV a YUV color image; no conversion to RGB32 . NUI_IMAGE_TYPE_COLOR_YUV a YUV color image, converted to RGB32 NUI_IMAGE_TYPE_DEPTH a depth image. NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX a depth image with player index encoded in the map.
The second parameter is a pointer to a NUI_SKELETON_FRAME structure. On return, it points to a record that describes the tracked skeletons. It is described as follows
Various streams can be opened to capture data from the same device, but the capture of depth images must be
end;
NUI_SKELETON_FRAME = record liTimeStamp : int64 ; dwFrameNumber, dwFlags : DWORD ; vFloorClipPlane, vNormalToGravity : Vector4 ; SkeletonData : [ a]r r a y 0 . . 5
o fNUI_SKELETON_DATA ;
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. The dwImageFrameFlags parameter can be used to specify some flags when capturing images, it accepts the same values as used in the NuiImageStreamSetImageFrameFlags function. The hNextFrameEvent parameter is the handle of the event that must be triggered when a new frame is ready. Finally, the phStreamHandle is the handle of the image stream that must be used in the NuiImageStreamGetNextFrame calls to read the image. The NuiImageStreamSetImageFrameFlags method can be used to modify the flags passed in the dwImageFrameFlags parameter to NuiImageStreamOpen:
NUI_IMAGE_STREAM_FLAG_DISTINCT_OVERFLOW _DEPTH_VALUES is undocumented. NUI_IMAGE_STREAM_FLAG_ENABLE_NEAR_MODE Enable near mode. (enable depth detection close to the camera) NUI_IMAGE_STREAM_FLAG_SUPPRESS_NO_FRAME_DATA
NUI_SKELETON_DATA = record eTrackingState : NUI_SKELETON_TRACKING_STATE ; dwTrackingID, dwEnrollmentIndex, dwUserIndex : DWORD ; Position : Vector4 ; SkeletonPositions : a r r a [y 0 . .]1 9 o fVector4 ; eSkeletonPositionTrackingState : a r r a [y 0 . .]1 9 NUI_SKELETON_POSITION_TRACKING_STATE; dwQualityFlags : DWORD ;
of
end;
The eTrackingState field describes whether the record actually describes a skeleton. It can have one of the following values: NUI_SKELETON_NOT_TRACKED The record does not describe a tracked skeleton. NUI_SKELETON_POSITION_ONLY The record describes a skeleton whose position is tracked. NUI_SKELETON_TRACKED record describes a fully tracked skeleton.
When set, the NuiImageStreamGetNextFrame method will not return an E_NUI_FRAME_NO_DATA error when no To determine the skeletons, the eTrackingState field of data is present, instead the call will block until data is each record in the SkeletonData array of present or the timeout is reached. NUI_SKELETON_FRAME must be checked. If it contains NUI_IMAGE_STREAM_FLAG_TOO_FAR_IS_NONZERO NUI_SKELETON_TRACKED, it is a usable record. is undocumented. For each skeleton, 20 joints are tracked. These joints are described in the SkeletonPositions After the image stream and skeleton stream have been set and eSkeletonPositionTrackingState arrays. up, frames can be read by watching the event handles. For each of the 20 joints, a constant is defined, for example: In the example program later on, this will be done in a separate thread. Interpreting skeleton frame data
When a skeleton frame is ready, it can be fetched with the following method of INuiSensor: Function NuiSkeletonGetNextFrame( dwMillisecondsToWait : DWORD ; pSkeletonFrame : PNUI_SKELETON_FRAME ) : HRESULT ; The first parameter is a timeout: if no frame is ready within the specified time, the call returns with an error condition. When an event handle is used to signal the completion of a frame, then the call should return at once.
NUI_SKELETON_POSITION_HEAD, NUI_SKELETON_POSITION_HAND_LEFT, NUI_SKELETON_POSITION_HAND_RIGHT.
Each of these constants is an index in the SkeletonPositions and eSkeletonPositionTrackingState arrays. The eSkeletonPositionTrackingState array determines which of the SkeletonPositions
elements contains a valid position vector. An element in the array can have one of the following values:
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
35
Programming the Microsoft Kinect in Pascal (Continuation 3) NUI_SKELETON_POSITION_NOT_TRACKED The array element does not contain valid data. NUI_SKELETON_POSITION_INFERRED The position is calculated from other data. NUI_SKELETON_POSITION_TRACKED The position is tracked.
The last 2 values mean that the element with the same array index in the SkeletonPositions array, contains a valid joint position. The skeleton tracking mechanism may result in ’jittery’ data. The results are vectors, and the positions will appear to have some random Brownian-like motion. The INuiSensor interface offers the NuiTransformSmooth function to deal with this:
For the depth image, the data comes in the form of an array of word-sized values. The byte size of the array is reported using BufferLen, the length of a single scan line can be retrieved with the Pitch method. The actual array can be retrieved with LockRect. Since the array is managed by the kinect driver, it is locked when it is retrieved. It must be unlocked using the UnlockRect call when it is no longer needed. The data array is described by the following record NUI_LOCKED_RECT = record Pitch : integer ; size : integer ; pBits : pointer ;
Function NuiTransformSmooth(
end ; Pitch and Size correspond to the BufferLen and Where
pSkeletonFrame : PNUI_SKELETON_FRAME ; const pSmoothingParams : PNUI_TRANSFORM_SMOOTH_PARAMETERS ) : HRESULT ;
This function will attempt to reduce the randomness by applying a transformation on the received coordinates. The transformation is controlled by the following NUI_TRANSFORM_SMOOTH_PARAMETERS record: NUI_TRANSFORM_SMOOTH_PARAMETERS = record fSmoothing, fCorrection, fPrediction, fJitterRadius, fMaxDeviationRadius : single ;
end;
The exact meaning of these parameters can be found in the NUI API documentation on MSDN. Interpreting depth image data
If a depth image is requested, the INuiSensor’s NuiImageStreamGetNextFrame can be usedmethod to retrieve the actual depth image. It is declared as follows: FunctionNuiImageStreamGetNextFrame(hStream:THandle; dwMillisecondsToWait : DWORD ; pImageFrame : PNUI_IMAGE_FRAME ) : HRESULT ; The hStream handle is an image stream handle created using the NuiImageStreamOpen function. Similar to the NuiSkeletonGetNextFrame function, the dwMillisecondsToWait specifies a timeout, in case the image is not yet ready. On return, the location pointed to by pImageFrame will be filled with a NUI_IMAGE_FRAME record: NUI_IMAGE_FRAME = record liTimeStamp : int64; dwFrameNumber : DWORD ; eImageType : NUI_IMAGE_TYPE; eResolution : NUI_IMAGE_RESOLUTION; pFrameTexture : INuiFrameTexture ; dwFrameFlags : DWORD; ViewArea : NUI_IMAGE_VIEW_AREA;
end;
The pFrameTexture field contains an INuiFrameTexture interface that can be used to examine the actual frame data: INuiFrameTexture = interface(IUnknown ) ['{13ea17f5-ff2e-4670-9ee5-1297a6e880d1}'] Function BufferLen: integer ; Function Pitch : integer ; Function LockRect(Level : UINT ; pLockedRect : PNUI_LOCKED_RECT ; pRect: PRECT ; Flags : DWORD ): HRESULT ; Function GetLevelDesc (Level : UINT ; : out desc NUI_SURFACE_DESC ): ; HRESULT ( : Level ): UINT ; HRESULT Function UnlockRect end; 36 COMPONENTS DEVELOPERS
4
Pitch methods of the INuiFrameTexture interface. The pBits pointer points to the actual array. Each element in the array is aWord value between NUI_IMAGE_DEPTH_MINIMUM_NEAR_MODE and NUI_IMAGE_DEPTH_MAXIMUM_NEAR_MODE if near mode is enabled. In normal mode, the minimum and maximum values are NUI_IMAGE_DEPTH_MINIMUM and NUI_IMAGE_DEPTH_MAXIMUM. When NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX was used when creating the stream, the depth image’s word values are shifted, and the last 3 bits are used to encode a player index. (3 is the value of NUI_IMAGE_PLAYER_INDEX_SHIFT).
If the last 3 bits are nonzero, then the pixel is considered part of a player’s body. The player index can be used for example to color the corresponding pixels in a playerspecific color. Putting everything together
After the long description of the Kinect ( Natural User Interface) API, a small demonstration application will clarify things a bit. The sample application: • Connects to the first found Kinect sensor. • Requests and displays skeleton frames and a depth image stream. • Uses events to get a notification when the next frames are ready. • Displays the depth image with a specific color for all players, and superimposes on that, for the first detected player, shapes representing the hands and head. • Allows to set/get the camera elevation angle.
The program is written in Lazarus, but it should work equally well in Delphi. It is a simple form, with 2 panels, some controls, and 3 shapes on it. The OnCreate event handler is used to initialize some variables and connect to the kinect: procedure TMainForm . FormCreate ( : Sender ) ; TObject begin FESkeleton :=INVALID_HANDLE_VALUE ; FEDepth:=INVALID_HANDLE_VALUE ; FSDepth:=INVALID_HANDLE_VALUE ; LoadNuiLibrary; TBAngle.Min : =NUI_CAMERA_ELEVATION_MINIMUM ; TBAngle.Max : =NUI_CAMERA_ELEVATION_MAXIMUM ; i f n o t InitKinect t h e n ShowMessage ( 'Could not initialize kinect! ' ) ; F o r I := 1t o6 d o FPlayerColors [i ] : =clWhite ; end;
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Programming the Microsoft Kinect in Pascal (Continuation 4) NuiImageResolutionToSize (ImageResolution),, ;w h The variables FESkeleton and FEDepth are the events ClientWidth := w; used to receive notifications when the skeleton and depth 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 ; i f N o t Failed NUI_CAMERA_ELEVATION_MAXIMUM constants (FKinect.NuiCameraElevationGetAngle (@A )) (-27 and 27, respectively), these values are used to initialize 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 EnableNear function . ):InitKinect ( :; Boolean TMainForm boolean = False 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 ; i f( Failed NuiGetSensorCount ( )) C ; t he nexit I:=0 ; While ( FKinect = Nil ) ( and < i) C do
begin i f N o t Failed( NuiCreateSensorByIndex( i, NS) ) then i f (NS .NuiStatus =S_OK ) t h e nFKinect : = ; NS Inc(I ); end; i f n o t Assigned (FKinect ) t h e nexit ; ( if Failed ( FKinect. NuiInitialize( NUIOptions) ) then begin FKinect:=Nil; Exit; end;
This code is pretty straightforward, it requests the number of kinect devices, and connects to the first available one. If none was detected, it exits. The first available sensor is then initialized, the NUIOptions constant is defined as: Const NUIOptions = NUI_INITIALIZE_FLAG_USES_SKELETON or NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX;
After the kinect was initialized, an event handle is created, and used to enable skeleton tracking: FESkeleton := CreateEvent ( n,i l True , False , FKinect. NuiSkeletonTrackingEnable( FESkeleton, SkeletonOptions);
n)i;l
SkeletonOptions is a constant requesting seated support and near range. The next thing to do is request a depth image, again using an event handle to get notifications: FEDepth:= CreateEvent ( n,i l true , false , n)i;l if Failed(FKinect.NuiImageStreamOpen ( ImageOptions , ImageResolution , 0, 2, FEDepth, FSDepth )) then Exit; i f EnableNear t h e n
if Failed(FKinect.NuiImageStreamSetImageFrameFlags
(FSDepth,ImageStreamOptions )) ; Exit
If all went well, a thread can be set up to check for events on the FESkeleton and FEDEpth FTEvents:= TEventDispatcherThread. CreateDispatcher( Handle, FESkeleton, FEDEpth);
Lastly, the size of the depth image is used to create a bitmap (which will be used to draw the depth image ) and set the width and height of the form: FEDEpth.
This is one way of handling the events, another way would be to use the OnIdle event of the application, or a timer, to check for new events. Instead of sending a message, it is also possible to use the Synchronize or Queue methods to let the main thread respond to the arrival of new data. The thread’s Execute method looks very simple: procedure TEventDispatcherThread . Execute; begin i f FHWnd INVALID_HANDLE_VALUE = or ) (= FESkeleton INVALID_HANDLE_VALUE) then exit; While not terminated do begin i f (WaitForSingleObject( FESkeleton,5 0) = WAIT_OBJECT_0) then begin SendMessage( FHWnd, WM_USER, MsgSkeleton,0 ) ; ResetEvent( FESkeleton); end; ( i f WaitForSingleObject FEDepth (,5 0 )= WAIT_OBJECT_0) then begin SendMessage( FHWnd, WM_USER, MsgDepth,0 ) ; ResetEvent( FEDepth); end; end; end;
If an event is received on either of the 2 handles, a WM_USER message is sent to the main form, which will then take appropriate action. The message parameters are defined as constants. Note that the event is reset after the message is sent. Reacting on the messages is done by implementing a message handler method in the form for the WM_USER message: procedure TMainForm . eventDispatcher ( : TMessage); begin ( if msg WParam . msgSkeleton = )
varmsg
then OnNewSkeletonFrame e l se i f (msg .WParam =msgDepth) then OnNewDepthFrame; DoTick( msg. WParam= msgDepth) ; end;
The message handler method simply examines the message parameter and calls the appropriate method to deal with the message. After that it executes a tick, which collects and displays some statistics. The actual work is done in OnNewSkeletonFrame and OnNewDepthFrame. The first one is responsible for drawing the head and hand joints in the skeleton frame. 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; :var i integer ; : fr NUI_SKELETON_FRAME ; PSD : PNUI_SKELETON_DATA ; tsp : NUI_TRANSFORM_SMOOTH_PARAMETERS ; = begin = FillChar( fr, sizeof( NUI_SKELETON_FRAME) ,0 ) ; if Failed( FKinect.NuiSkeletonGetNextFrame (0 , @fr ) ) then Exit; PSD := Nil; I := 0 ;
:
While ( PSD Nil =and)(i < NUI_SKELETON_COUNT ) do begin if (fr.SkeletonData [i ] .eTrackingState = NUI_SKELETON_TRACKED) then PSD :@= . fr SkeletonData [ ]i; Inc ( I); end; i f N o t Assigned (PSD ) t he nExit ;
This code fetches the next NUI_SKELETON_FRAME structure from the kinect, and initializes a pointer to the first skeleton (PSD). The following step is ’smoothing out’ the received coordinates: With tsp do begin fCorrection := 0.3; fJitterRadius:= 1.0;
procedure TMainForm. OnNewDepthFrame; Const DS = NUI_IMAGE_PLAYER_INDEX_SHIFT ; MINS NUI_IMAGE_DEPTH_MINIMUM_NEAR_MODE DS shr ; MAXS NUI_IMAGE_DEPTH_MAXIMUM_NEAR_MODE shr ; DS var IFDepth : NUI_IMAGE_FRAME ; FT INuiFrameTexture ; : lck NUI_LOCKED_RECT ; depth : pword ; C: D Word ; p: byte ; x, y : integer ;w ,h , GS cardinal : ; C : TCanvas ; begin ( i f FSDepth = INVALID_HANDLE_VALUE ) ; t he n Exit if Failed ( FKinect. NuiImageStreamGetNextFrame( FSDepth,0 , @IFDepth ) ) t he nExit; NuiImageResolutionToSize( IFDepth.eResolution ,,)w; h
The above code retrieves the image from the kinect and calculates a width and heigh with it. The next step is to retrieve the image data from the INuiFrameTexture interface: try FT: =IFDepth .pFrameTexture ; i f n o t assigned (FT ) t h e nExit ; i f Failed (. FT LockRect ( , @ 0 ,lck ,n i)l) 0 then Exit;
try i .f lck ( * ) 2w t h e;nExit depth : =lck .pBits ;
fMaxDeviationRadius:= 0.5; fPrediction := 0.4; fSmoothing := 0.7;
The following steps transfer are a loop over the depth data, transferring it as a grayscale to the bitmap. The depths that have a player index in them are 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; if Failed ( FKinect . NuiTransformSmooth (@ then Exit;
fr ,@
tsp ))
end The;ShowJoint will check if the requested joint position ( the
third parameter) was tracked, and if so, position the shape so it is centered on this position. To position a shape on the depth bitmap, the skeleton coordinates must be transformed to an X,Y position on the depth image. This can be done with the aid of the NuiTransformSkeletonToDepthImage function: Procedure NuiTransformSkeletonToDepthImage( vPoint : TVector4 ; out fDepthX : single ; out fDepthY : single ; eResolution : NUI_IMAGE_RESOLUTION );
It receives a position vector, and a resolution. It returns an X,Y coordinate which is a coordinate on a depth bitmap corresponding to the given resulution. All this is used in the ShowJoint function: Procedure TMainForm . ShowJoint (: S ; TShape : PSD PNUI_SKELETON_DATA ; HI : Integer ); Var x, y : single ; begin S. Visible:= PSD^ .eSkeletonPositionTrackingState [HI ] = NUI_SKELETON_POSITION_TRACKED; i f S .Visible t he n begin NuiTransformSkeletonToDepthImage( PSD ^ .SkeletonPositions [ HI ]x , y, , NUI_IMAGE_RESOLUTION_640x480); S.Left : =Round (x ) - S ( Width . d i v )2; S.Top : =Round (y ) - S ( Height . d i v )2; end; end; Finally, the depth image must be rendered: for this, the depth image needs to be interpreted and transferred to a bitmap, and then the bitmap is drawn on a panel:
38
COMPONENTS DEVELOPERS
4
performance:
C: =FBDepth .Canvas ; C. Lock ; fo r: =y 0t -o h 1d o fo r x: = 0t o w- 1d o
begin CD: =Depth ^ ; P: = (CD and NUI_IMAGE_PLAYER_INDEX_MASK ) ; i f ( P < >0 ) t he n C.Pixels [X ,Y ] : =FPlayerColors p [ ] (e l s e i f CD NUI_IMAGE_DEPTH_MINIMUM_NEAR_MODE >=
and (CD < =NUI_IMAGE_DEPTH_MAXIMUM_NEAR_MODE ) then begin GS : =Round ( (CD ( s hrds ) -MINS ) / MAXS * 255 ); GS:=GS a n d $F F; GS:GS = o rGS ( s hl 8 ) oGS r( s hl )1; 6 C.Pixels [X ,Y ] : = GS ; end else C.Pixels [X ,Y ] : =clBLack ; Inc( depth); end; C. Unlock ;
Lastly, the retrieved depth image data is released, and the bitmap is drawn on the panel: finally FT. UnlockRect(0 ) ; end; finally FKinect. NuiImageStreamReleaseFrame( SDepth, @IFDepth ) ; end; PImage .Canvas .Draw (0 ,0 ,FBDepth ) ; F :o =r X 0 t.o PImage ControlCount 1 do i f PImage.Controls [x ] i s TShape t he n PImage.Controls [x ] .Repaint ; end;
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
)
Programming the Microsoft Kinect in Pascal (Continuation 6) Finally, to make sure the shapes representing the head and hands are properly shown, they are repainted. The form contains some logic to display a tick and average frames per second count, and to set the colors of the players. This logic is not relevant to the operation of the kinect. However, the routine to set the camera’s elevation angle needs some explanation: Setting the elevation angle of the camera takes some time: it is a mechanical operation, involving a small motor inside the kinect. Setting a new position while the previous position was not yet established, will result in an error. It is therefor important not to send commands too often or too much. The
Note that the trackbar position is reversed; it is positioned vertically, with the minimum value (-27) at the top, and the maximum value (27) at the bottom of the trackbar. Everything put together, the running program results in a figure like figure below.
following code attempts to do that: TBAngleChange TObject procedure TMainForm . ( : Sender ); Var A : Longint ; begin I f TBAngle .Position =FLastPosition t he nExit ; I f N o t Failed (FKinect.NuiCameraElevationGetAngle ( @A ) ) then begin i f (A < > -TBAngle .Position ) t he n A: = -TBAngle .Position ; begin if Failed (FKinect. NuiCameraElevationSetAngle( A) ) then ShowMessage (Format (SErrSetAngle , [A ] ) ) ; FLastPosition : = -A ; end; end else ShowMessage( SErrGetAngle); end;
Conclusion The kinect is a device which is one way of implementing a Natural User Interface: use the human body to control the computer. While it is srcinally aimed at gaming, there may be specialized uses for this device outside the gaming industry. For a more fine-grained control
of the computer, the resolution of the Kinect’s skeleton detection is not fine enough: it cannot detect individual fingers of the hand. This gap may be better filled by the Leap Motion device. Both devices are available to Object Pascal programmers, and there are certainly Object Pascal game programmers that will consider the ability to use the kinect a nice addition to their
Figure 2: The demo program in action
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
39
The license is now free for students! Smart Mobile Studio now offers a free educational licence for students in school or university or who are members of an after-school coding club.
30
Nr 4 / 2013 BLAISE PASCAL MAGAZINE www.SmartMobileStudio.com
Smart Mobile Studio 2.0 By Primož Gabrijelčič starter
expert
SMART
A year and a half after the srcinal release of Smart Mobile Studio 1.0 I'm proud to present the first major upgrade – Smart Mobile Studio 2.0. A lot of things have happened since the srcinal release. The team is now larger, counting five developers. Lennart, the srcinal father of the “Pascal for the Internet” concept, has accepted new challenges, and I have stepped in as a programming manager for the Smart team. This article is therefore not an objective outsider view,
Smart Mobile Studio 2.0 is available as a public beta at smartmobilestudio.com . Pascal for Everything I like to describe Smart Mobile Studio 2 as a development environment that allows you to run Pascal “everywhere”, meaning that you can run Pascal programs on phones (either from the browser, from the home screen or from PhoneGap/Cordova-packaged applications), on desktops ( in a browser), on servers (with the help of Node.js ) and even on microprocessors (with the Espruino initiative).
but a bunch of notes written by a proud programmer
Smart Mobile Studio 2 (SmartMS for short) supports six different project types. Canvas game project is a project type best used for writing games. It provides a game with a HTML5 canvas, a mechanism to call your application with a choosen framerate, and RTL support for CSS3 sprites and bitmapbased fonts. Console project creates an application which mimics a console-type program. This project type is suitable for testing ides, debugging portions of a larger framework and for unit testing. Espruino (Microcontroller) creates an application that will runonanEspruinofirmware( www.espruino.com ). Espruino is a JavaScript interpreter for microcontrollers which can run as a part of the microcontroller firmware. In short, this allows you to connect a development board to a Windows computer (either with a serial cable or with a Bluetooth module), write a Pascal program, press F9 and it will execute on the microprocessor. For more information about microcontroller programming with SmartMS, view the following article:
Figure 1: Overview of the IDE, in the next pages an overview - enlarged - with the new Delphi-like double-click http://smartmobilestudio.com/2013/11/17/ that opens the code in your Editor. Never thought of micro-controller-programming/. how important this is...
Figure 2: Create an new project
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
41
Smart Mobile Studio 2.0 (Continuation 1) Several Editions With version 2 we have decided to make Smart Mobile Studio more accessible for everyone and we have split it into multiple editions. On the entry level there is a Basic edition. It supports all
project types except the Visual components project and doesn't contain the visual designer (as it is only used in the visual project type). The price is merely $42, on par with the srcinal Turbo Pascal software. Figure 4: the Espruino board
Node.js project creates a server-side application designed
nodejs.org to a part of the Node.js platform (OS You canrun runassuch applications on Windows, X and ).Linux computers. The Visual components project creates a formand component-based application, just as you would do it in Delphi or Lazarus. A visual project can contain multiple forms and the user can navigate between them. Such projects can run in any modern browser. You can also use modal forms which do not use full display ( parts of old form are visible below).The WebWorker project (thread) is a special light-weight project type which creates a web worker, a thread-like browser entity that allows multithreaded computing in a browser.
The middle ground holds the Professional edition. It contains everything from the Basic edition plus the visual project type and the visual designer. The price is a bit higher, $149. If you want to work with the databases we are also offering the Enterprise edition which adds database connectors to the Professional level. You can easily connect to RemObjects services or to DataSnap servers and SmartMS will create an appropriate Pascal connector for you automatically. (To use with the DataSnap server you also have to have a Delphi/RAD Studio Enterprise installed on the same computer.) This edition costs $399.
Figure 8: Turning Figure 5: SmartMS-design
44
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Smart Mobile Studio 2.0 (Continuation 2) All editions come with a year worth of upgrades. You can, of course, use them without upgrading after the year has passed. We are also offering an Educational license which is a free license (functionally equivalent to Enterprise) for educational facilities (schools, universities, clubs …). New visual designer
In the SmartMS version 1, visual designer was a bit rough around the edges. It was functioning OK, but it was really not working as smooth as users wanted. This too has passed and with the version 2 users will be getting a new designer with better look and, besides all, better event support. Finally you'll be able to create events by doubleclicking on a button or an event in the Object Inspector . You will also notice that some components are better looking. The new designer allows us to display a better representation of components. This functionality is not yet finished and will be continually improved in 2.0 beta’s and in later (2.1) releases. User-components can be organized in packages and added via the new Packages menu. Smart comes with an example package containing the TeeChart graphing component. Compiler changes
Besides stability and code generation improvements, compiler now supports external variables and call variables. This simplifies interfacing with existing JavaScript libraries (such as used for Node.js integration). HTML and CSS templates are now scriptable and run through a DWScript preprocessor before they are used. CSS is compressed during the compilation so that minimum space is used. We have added new ways reference an {$R external resource. In addition to theto old command 'resource.js'} which copied the resource file into the deployment folder, the compiler now supports the {$R 'file:resource.js'} syntax. You can also specify an external file, which is not copied to the deployment folder, with a syntax {$R 'http://server/path/script.js'}. Additionally, you can specify resource MIME type: {$R 'text/javascript: http://server/path/script.js'}.
There is also a command-line compiler, smsc, which is freely redistributable and which you can deploy on your servers or bundle with your solutions. As the main IDE, command-line compiler runs only on Windows. When compiling inside the SmartMS IDE, you can use prebuild and post-build scripts to execute external actions (http://smartmobilestudio.com/2013/11/25/newbuild-system/ ). For improved debugging experience, the IDE supports source map debugging with Chrome. That allows you to debug your programs in Chrome while looking at the Pascal source, not at the JavaScript . (Sadly, this functionality is broken in the 2.0 beta. We'll fix it for the next beta.) IDE improvements
Besides the new visual designer and package manager, there's a bunch of smaller changes that make the IDE easier for use. For example, you can Ctrl+Click on a symbol to jump on its definition. You can move lines around with Alt+Shift+Up/Down keys. The Project-related state (open tabs, current line in the editor, bookmarks) is stored in a .dsk file and is restored when a project is opend. We have changed the project file format. The new project format is incompatible with Smart 1 and uses a new extension .sproj. Forms are stored in xml-based .sfm files and can be edited outside the SmartSM IDE. We have also changed the preferences file format which is now XML-based (preferences.xml). The snippets system was also redesigned, which now stores one snippet per folder and allows for a simple upgrade and backup. The Project management is now more flexible. You no longer have to choose the project path when creating a new project. In fact, you can create and compile a project without saving the source! ( Resulting HTML/CSS/JS program is still save in a TEMP folder but is deleted when SmartMS exits.) Externally modified files are now displayed in a manner similar to the excellent DDevExtensions Delphi extension (big thanks to Andreas Hausladen for showing us the way and allowing us to steal his idea). You can compare changes with in-editor state in external ( configurable) tool or in a built-in diff viewer.
Figure 6: Relod changed files
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
45
Smart Mobile Studio 2.0 (Continuation 3) Examples
I'd like to show you few examples of srcinal and compiled code so you can see how well the compiler works. For example, let's take a short method from the included Game of life demo. procedure TGameOfLifeEngine ( ,. : SetArea ) ; X Y Integer var Index : Integer ; begin FNumCellsX := X ; FNumCellsY := Y ; FArea . SetLength ( FNumCellsY )+; 2 // include two wrap-around lines fo r I n d e x : = 0 t o FArea . Length 1 do FArea[I n d e x] .SetLength (FNumCellsX )+; 2 // include two wrap-around columns end;
This would get compiled to a following JavaScript fragment: /// procedure TGameOfLifeEngine.SetArea(X: Integer; Y: Integer) /// [line: 91, column: 29, file: GameOfLifeEngine] ,SetArea :f u n c t i o n(Self ,X , $)5Y $ 5 { var Index$1 = 0; Self.FNumCellsX = X$5; Self.FNumCellsY = Y$5; $ArraySetLenC (Self . FArea , ( Self.FNumCellsY+2 ) , function return ( ){ [ ] }); v a r $temp27; fo (r I n, d=e x$ 1 = .0 $ temp27 . Self ; FArea length I n d e x$1< $temp27 ; I n d e x$1++) { $ArraySetLen ($DIdxR (Self .FArea ,Index$1 ," in TGameOfLifeEngine .SetArea [line : 99 , column: 17 , file : GameOfLifeEngine ]"), (Self.FNumCellsX+2 ),false );
Figure 7: the project options
As you can see, this is fairly long-worded JavaScript code
And for the end I'd like to show you how to write a Node.js
which does some run-time checking (as the Range checking compiler options was enabled). If we, however, uncheck the Range checking and enable Code packing and Code obfuscation options, we'll get something much more interesting, a very compact JavaScript code.
application (the code also comes from the Mileage clientserver demo).
,rl:f u n c t i o n(S , E1 yco , ) { v aIB r =0;S ny.E1 = ; S.kG=yco;$ArraySetLenC ( .S Vk , ( . S kG+2 ), function( ) {return[ ] } ) ;var $tR ; for(IB =0,$tR =S. Vk. length; IB<$tR ; IB ++) {$ArraySetLen(S. Vk [ IB ] , ( S.ny + )2, false )}}
//start http server http . createServer( request JServerRequest response ( procedure : ; : JServerResponse) begin if request. url. StartsWith('/Mileage/Read' ) then FetchData( response) else if equest. url. StartsWith('/Mileage/Save' ) then StoreData (request , response ) e l s e response . e n d('MileageServer v0.1' ) end) . listen (, 80 '' );
procedure TServer .Run; begin //load http module var http : = NodeJS . http . http ;
Next example (from the Mileage client-server demo) shows how to generate HTTP GET request and process the response. It also shows few features of the enhanced Pascal language used in SmartMS – lambda expressions (anonymous functions) and inline variable declaration. procedure TMainForm . InitializeForm; begin
.
Create : = var http . TW3HttpRequest ; http . OnDataReady := ( lambda ) Send er f o rv a r line i n Sender .ResponseText .( Split #13) d o b e gi n i f line = '' t h e n continue ; line var : = data . ( ) ;Split #9 var item := TListTemplate . [( lbData . ]Items ); lbData Add .item Date := []data ; 0 .item Distance := [ ]data ; 1 .item Volume := [ ]data ; 2 end; UpdateChart ; end; http Get 'http://localhost/Mileage/Read' ( end;
46
console_ .log ([ 'Server running at http://127.0.0.1:80/' end;
Credits At the end I'd like to give the credit where it's due. Smart Mobile Studio wouldn't be there without Lennart Aasenden, the srcinal author, Jørn Einar Angeltveit, our CEO, Eric Grange, author of the Pascal-to-JavaScript compiler, Christian Budde, who wrote lots of version 2 code including complete v2 designer, and André Mussche, who ) ; wrote RemObjects and DataSnap connectors.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
www.SmartMobileStudio.com
Interview with David I. As developers remember, Microsoft had releases that did not follow a calendar year. Sometimes there were several years between versions of Windows. Apple has followed a (mostly) annual cycle of releases for OS X and iOS. Google releases new Android platforms when they are ready. Microsoft has responded promising annual cycles and new releases of Windows SDKs within an annual cycle. We also look at the devices, Smartphones and Tablets for example, that are released with much more frequency. Add other devices and gadgets in the home automation, health/medical, wearable and other categories that are all programmable and have developer APIs and REST services and you can see that we need to continue to innovate to stay on top of all the platforms and devices that developers, their users and their businesses care about. The amount of R&D activity inside Embarcadero has ramped up enormously to meet the opportunities and deliver capabilities to our customers. We are spending much more than a lot of other developer tools companies and delivering more native optimized compiler, component and library functionality than ever before. For our customers, we offer many ways for them to keep up with us. Developers can choose what versions/releases they want to upgrade to. Developers can also sign up for Software Assurance, an annual program, so they can receive all of the technologies when they are available. Delphi and C++Builder developers have all the choices on
David seems to have a new job....
Editor: First to make it easy Could you explain the plans about updating buying etc of Delphi and how you want to proceed in the near future? David I. We are investing a lot of time and money to expand the reach of platforms we are supporting for Delphi and C++Builder. This is not just a Windows world anymore. Over the past few years, mobile and device platforms have expanded the types of applications developers need to build beyond just the desktop. At the same time the requirements for applications have grown to include the Internet, web services, REST services and cloud computing. All of these new platforms and architectures have allowed us to help expand the reach of developers. Along with these new innovations, we are delivering enhancements to our products, IDEs, components, libraries
how to proceed and to stay up to date on the latest platform, device and technology advances. Simply put, we are doing more for developers today than we have ever done in all the years since Turbo Pascal version 1.0. When we released our most recent XE5 release, we were also testing iOS7 and next generation Android. We released our iOS7 update in a very timely update soon after iOS7 appeared. We are committed to verifying that our products work with the next generation Windows, OS X, iOS and Android platforms. Where we need to we will also update our products to take advantage of new APIs, innovations and opportunities. Separate from platform support, our R&D teams are also innovating in IDEs, multi-device designers, tool chain enhancements, compiler technologies, components and in the runtime libraries. All of this is being done to help our existing customers
and tools.
move forward fast apps and also outon tomultiple new developers who want to build fast,reach deploy devices and Now we are supporting 4 major platforms - Windows, keep up with new innovations. Will the upgrade or new Delphi be 6 months from now on? If so there is the danger OS X, iOS and Android. Each of these platforms people will wait for quite some updates to engage again? (and the devices that support them) continues to move ahead We have our public roadmap that list some of the work we on their own pace of innovation and release schedules. are doing with general time frames - for example This makes it harder to synchronize with all of them on a C++Builder iOS and Android support this Winter. specific schedule. We must also work to support the has managed to create a Beta Version forparts lazarus. There are so many moving in the IDEs, compilers, release cycles of our R&D efforts and also the platforms FM,(for RTL andDelphi) device/platform support that itbe is notable easy to themselves. If you buy the full source code you will predict when we will release updates, new versions and It was easier in the past, when we were a Windows to install the firsteven working version. new products. only development product, to follow the cycles of Windows releases. We have allready tested it.
FastReport
48
COMPONENTS DEVELOPERS
4
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
For example, we had srcinally planned to have Delphi for iOS support in XE3, but we needed more time to complete the ARM compiler and mobile language enhancements, so these were released when ready in April of this year. Our first Android support was ready in the summer, so it fit our "traditional" fall release. Some developers will remember the early years of Turbo Pascal and Borland Pascal where we released versions sometimes in Springtime, sometimes when a new release of DOS or Windows appeared, and even Turbo Pascal for the Mac that was released separately on its own schedule. Some of our customers have waited for releases that had capabilities they are interested in or need. Other developers keep up with every release. Some developers are still using very old Windows only versions of Delphi (3, 5, 6, 7, 2007, etc). If Microsoft dropped 32-bit from Windows, I would expect that most Delphi developers would move forward to use the 64-bit Delphi compiler. We are very proud that our products are so good, that they can be used for years and support multiple versions of the platforms including Windows, OSX, iOS and Android versions. Since we don't time out the releases that developers purchase, it is their choice when to move forward. We continue to move forward and new and existing developers are coming with us. At the same time, we will continue to help developers understand how to use the current and latest capabilities for development. Our DocWiki online documentation system and our community/partner ecosystem still have all of the information going back multiple versions, so developers can get the information they need to be successful regardless of what version they are using. We will keep innovating; continue to support the latest platforms and development technologies across all of the platforms: Windows, OS X, iOS and Android. If someone is doing Windows only development on an older Windows version, they can keep using the latest versions products.
David I I see no dangers anywhere. We are providing capabilities for all developers whether they are mature, youngsters or newbies. We are the only native code, optimizing compiler, rapid application prototyping developer tools company on the planet that offers one code base, multi-device targeting that is on all of the major platforms Windows, OS X, iOS and Android.
We give you a wide range of RTL functions and components to improve your ability to build apps for the platforms and at the same time we allow you to go right down to the operating system and the hardware if you need to. We have been doing these things since the days of DOS (Turbo Vision) and Windows (Object Windows, VCL and now FM). We know how to abstract the OS and hardware without keeping developers out of those spaces. Everything, the APIs, devices, sensors, operating system and other hardware features are available for your programs. We have millions of seasoned professionals and new generations of young developers using our products around the world.. This year, South Africa has chosen Delphi as the standard programming language and product for their High Schools computer classes. This means that a new generation of South African students will learn object programming, component based development, event programming rapidprepared application prototyping in high school and willand be well for college and industry. Editor Will you go there? David Intersimone : I have been to South Africa one time before and am planning on going again sometime early next year. I look forward to my return visit to South Africa.
Thanks David!
We also provide customers, using our latest releases, with access to past releases of our products. For new developers and existing customers who want to move forward, we are here for them. And for those who eventually need something new - we are continuing to innovate in advance of their needs. We are doing more engineering, have invested more in new technologies and platforms, and acquired additional capabilities to advance and broaden the depth and breadth of our product offerings. Embarcadero is spending more and has more R&D centers around the world than ever in our history to meet the needs of new customers, existing customers and future developers. 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?
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
49
A simple superscript text editor By David Dirkse starter
expert
Delphi 7
This article describes a text editor suitable for formulas with exponents. The formula is displayed in a paintbox named formulabox. So, editing including cursor movement has to be programmed. The formula must be translated to "plain" text for further input to procedures for the painting of graphics or calculations. Also a procedure takes care of the translation from plain text to superscript text.
Figure 1: This is a picture of the project:
The top box is an editbox, holding plain text. The lower box is the paintbox with the superscript text. Bitbuttons super and plain take care of the translation and display of texts. While typing text, the first concern is the destination of the characters: edit- or paintbox. Therefore is var
plaintext : boolean ;
// true if plaintext editor
Edit- and paintbox are components of Form1. A form has a property keyPreview of type boolean. If true keyboard events and characters are , before being delivered to the active component on the form, presented in two procedures: (after procedure to ...end the code is supplied by myself) 1. ( :Sender ; procedure.TForm1 FormKeyDown var Key: Word ; Shift : TShiftState );
begin i f plaintext = false t he n begin i f (textlength ) = 0 ;t he n exit clearcursor ; ca se key o f VK_LEFT : VK_RIGHT : VK_UP : : VK_DOWN VK_BACK : VK_DELETE : VK_HOME : : VK_END
kbLeft ; kbRight ; kbUp ; kbDown ; kbBack ; kbDelete ; kbHome ; kbEnd ;
showformula; paintcursor;
end;//if end;
COMPONENTS DEVELOPERS
Neglecting this may result in unwanted changes of any active component on the form. VK stands for "virtual key" . VK_LEFT is the code generated when pressing the left arrow key. kbLeft, kbRight....are small procedures handling other codes. Showformula paints the formula in the paintbox.
2. procedure.TForm1 FormKeyPress ( Sender : ; TObject var Key: Char ); const fmask =[ . .' a ' , ' z '. .' A ' , ' Z ' . .' 0 ', ' 9,' ' +' ' - ' ,' * ' ,' / ', ' ^ ', ' = ', ' ; ', ' ', ' . ', ' , ', ' ( ' , ' )] '; v a r mask : se t o fchar ; begin mask := fmask ; i f plaintext t:h=e n mask+ [ mask ]; //add backspace character i f n o t( key ) i nmask t :h =e n key else i f plaintext = false t h e n begin addchar( key); key := #0 ; end; end;
#08 #0
In this case key is the ascii code of the typed character. If no further action may take place after this procedure then make key := #0 ; key is filtered to eliminate unused characters like #,$,%,&. Problem is, that an editbox uses the backspace (#08) ascii code for editing. In the case of plain text therefore, the backspace code has to be added to the filter. procedure addchar() inserts the character in the superscript tekst. With plain text only does it's workhas : the is automatically sendthe to filter the editbox which it'scharacter own editing procedures. Property activecontrol of a form indicates the active control (the component that has focus ). This control receives the events from mouse and keyboard. form1.activecontrol := nil takes care that no component is active. if assigned(activecontrol) shows if there is any control active. In this little project, plaintext = true when activecontrol = edit1 and plaintext = false when the forms' activecontrol is nil. Actions for setting and clearing plain text vs. superscript text editing are in
end;//case key := 0;
50
TObject
Key is a code (word) depending on the key that was pressed. This procedure is used for the cursor movements and also for backspace, delete, home and end. If no further action may take place, the procedure should supply the code key := 0;
4
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
A simple superscript text editor (Continuation 1) procedure setplaintext ; begin with form1 do activecontrol := edit1 ; clearcursor ; //verwijder cursor uit paintbox plaintext := true ; end;
procedure kbLeft; begin i f cursX > 1 t h e n begin
procedure setsupertext ; begin form1 . activecontrol : =; nil clearcursor ; //must be painted later plaintext := false ; end;
end; end;
To paint superscript text in the paintbox, in design time it's properties are set to
setmodeF sets variabele upmode true or false, which takes care of the vertical cursor position. In this case, the right character position (F = forward) is copied. procedure kbBack handles the backspace :
width height font font font
660 30 "courier new ", size 12
dec(cursX ); i-f cursX < b ias setmodeF;
(1 t h) e; ndec bias
If the cursor exits the box at the left side, the bias must be adjusted to keep the cursor inside.
style bold procedure kbBack; var i : byte ; which yields a fixed character width of 11 pixels. begin The formulabox may display 60 characters at the time. i f cursX > 1 t he n However, a formula may be much longer,(up to 250 begin characters) , in which case part of the formula is outside the f o r i := cursX- 1 t o textlength- 1 d o 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 ); i-f cursX < b ias (1 t h) e; ndec bias cursX has the index of the character where it is placed setmodeB; before in the text. Far left of the paintbox is position 1. 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 position which is remembered by var upmode : boolean = false ; For each character in de string, the position (up, down) must be recorded. The text has a special dataformat to facilitate this: co n s t charwidth = 1 1; maxchardisplay = 60 ; //characters displayed in paintbox //max length of text maxchar = 250; t y p e TFormText = r e c o r d ch : char ; //character code up : boolean ; //position end; v a r formtext : [ textlength :
Opposite, when translating plain text to superscript text, the text must be scanned for ^( to detect a low to hi transition. Then the number of parenthesis must be counted : +1 for ( and -1 for ). Occurrence of a ) together with a zero count detects a hi to low transition. A mousedown event in the paintbox calls Formulabox.mousedown(...) which delivers the (x,y) coordinates. These are used to calculate the cursorpositie in de superscript text: var hh : byte ; begin hh :=
bias + (( +x ) d charwidth ) + 4 iv if hh > textlength + 1 t he n hh := textlength + 1;
a r r a]y 1 .maxchar . ; oTFormText f =byte ; 0 //number of characters in text
Procedure showformula paints the complete line of
cursX := hh ;
text. For each character
if cursX <= textlength then upmode := formtext [ cursX]. up else upmode := false ;
procedure paintchar(n) is called to paint character n
of the text. Please look at the source code for details. The specific procedures for cursor control are small but tricky because accurate counting is needed. Below is procedure kbLeft to advance the cursor 1 place to the left.
; 1
Please refer to the source code for more details. This concludes the description of this small editor project.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
51
Interview with Gwan Tan - better office Gwan Tan is the owner of “better office”. We met a long time ago and had several times very interesting discussions – especially after meetings of first time Delphi presentations. Because we want to present very interesting people we meet and their companies using Delphi and Pascal we want to know their opinion about the market, developments and critical annotations.
So I did my final thesis with that faculty working on my own project for a Chinese consumer word processor although I speak no Chinese. Editor Was there any follow up for this Chinese consumer? Gwan 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. At the time I had no direct interest in doing any work in Gwan Germany. But as I got an invitation for a Christmas Party, I Electrical engineering is only taught at technical decided to go to Oldenburg ( city in the north of Germany near universities. At technical Universities you can achieve the engineers title Bremen). So the contact was continued on German soil. With these (Ir.) At general universities you can become a Master of first contacts I met other German developers (and Science, (M Sc) Docterandus in Dutch (Drs.) publishers) and I was invited to the first German Delphi But electrical engineering is mostly an applied science. Conference (EKON). The more theoretical study is physics. During the years I got more and more involved with I always liked the applied sciences more than theory. German developers and slowly got some clients there too. In the beginning I wanted to specialize in data As the German market was getting more and more communications. But during a training period in the USA interesting and there seemed to be good opportunities for I got to my hands on the first Apple Macintosh and found Dutch developers I sought a more structured approach to programming a much more interesting area, the German market leading to better office benelux. especially with user interfaces and databases. Back at university I continued in that area, also as at the time there was no department of Information Editor 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 In the past years our main projects are in Germany although we still have an old client with sites in the Netherlands, Ireland, France, Germany and Thailand. In Germany we specialize on ERP support programs. We have a production planning system running in cooperation with the clients SAP system. We also have a system running with another client that handles the sales departments, warehouse and distribution of the clients main German factory. Editor so you're mainly doing business with larger contractors? Is there any specializing like mobile or is that a new field? Gwan We work most of all directly for a client. As our programs are often connected with the backbone systems of the client we have to cooperate with their other contractors. In the future one can foresee much more use of tablets, handhelds and smartphones even within the factory. The information people need during a production is not confined to a working place and therefore to a single computer. The people need to have their info with them like they have their wireless company phones. Within a warehouse it a already more obvious and the sales departments especially those sales persons in the field need to be connected with the main company system. So we are looking the area of mobile computing and are therefore very interested in the (future) extensions of
Gwan What do you mean with combine with? We have done projects were we have Pascal clients with Java middleware etc. Is that what you want to know? Editor Yes. And the databases? Gwan As for databases we mostly use InterBase or Firebird if we have the freedom of choice. Often we have to connect and develop with MySQL, SQL Server or Oracle. Basically we know InterBase and Firebird best but we are extending our
knowledge and experience in MySQL and SQL Server as often clients expect us to work with these databases. Editor Do you have a project that you are especially proud of? Gwan Depends on what to be proud of. Technically I'm very proud of the first production planning system we developed for a customer in Mainz. On a more general level I'm very proud of 'having survived' so far in the ERP-project just across the border. That project was partly challenged for technical reasons but even more for organizational reasons. At the start the requirements seemed to be quite good defined but as the project went on it became clearer the requirements were not as complete as expected. So much more interaction with the users was necessary to find out what had to be developed.
Delphi andour Pascal in general. At present clients are not ready for mobile computing. They are in the field of IT mostly conservative, needing proven technology and do not want to be pioneers. So we have some time to develop applications that will get their interest growing.
In the meantime especially top management put a lot of pressure on getting the program(s) up and running. Especially Edwin van der Kraan, my main developer in this project, had a very hard time. But now after 2.5 years the system is up and running and stable. So I'm proud of the project and especially proud of Edwin.
Editor So you -up to now- are actually VCL users?
Editor What are fields you would especially point at for reasons of learning and avoiding eventual upcoming problems? How do you respond to pressure without getting squeezed?
Gwan Yes, in our German projects we are using VCL mostly. Especially Jeroen is investigating/playing with the newer extensions. Editor I got the impression that you are doing websites as well? What languages do you use for this? Intraweb maybe or php or... Gwan Websites are not our main area. It is mostly our German colleagues who work in that area. They use mostly “php” as far as I know. I have to admit that I'm not right person to ask about that area.
Gwan Pressure is at almost all projects present. But sometimes the pressure is very high. To keep the pressure bearable one has to keep the targets of the project well defined and plan sufficient moments where especially management can see the progress made and the direction of the project. Depending on the part of the project and the pressure of
management one has to plan those moments more often. But do not forget to explain to management that such moments cost time and therefore will slow down the project. These milestones should be useful for checking the progress and direction of the project.
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?
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
53
Interview met Gwan Tan - better office (Continuation 2) Gwan The main issue here is management. They are to be considered like normal users. When you design a screen flow you image to be that user. What do I want/need to see when I have to do something. Management is the same like users they (should) know what they need and like users they do not understand the technical issues behind the programs. Therefore one has to prepare the meetings with management not just by making nice looking planning sheets but also by showing them as much as possible what has been developed and want the ideas are for the next step(s).
Editor We organized that last time in Leiden - Netherlands and that was a great success - because of meeting each other. Some reasons: we had 14 speakers and all the speakers as well the developers were very "touchable". Attendees thought this was great - so we will do that over again at our next conferences. And now for something quite different: What is your opinion about FireMonkey? Do you ever use it in your projects?
Management is often very insecure about software projects. They need to have the feeling that they are still in control. A large part of my job is 'holding their hand' during the project. Giving them the confidence that the project will turn out to be a success.
future. have noIidea it willbut be as a success. in the Delphi IIDE and love ifPascal Pascal isI believe almost not used at schools and universities one has to attract the future developers and IT-managers. For that it has to be 'sexy' again. I will have a closer look at it as soon as I can find more time. I also hope to get more info from the upcoming conferences. Online fora are great but listening to the attendants of (Delphi-) conferences gives a better or at least other impression. If the impression is positive I will have a more detailed look in what way I can use FireMonkey, what kind of projects, what kind of applications and especially in what kind of situations. Clients might be asking about it in the future but I expect that in the beginning we will have to propose using it to potential clients. They often do not know it.
Editor Do you create a visual overview of what steps could be taken or what the flow will be? Do you use video for this or a conferencing room? Gwan Depending on the milestone and the audience I will create power point presentations but with a lot of switching to the prototype. Even management knows that power point presentations do not necessarily cover reality. I like to use a conference room.
Iand want to be able to scan the audience facial to expressions body language in general. Also infor contrast video conferencing I believe in the face-to-face communication. With video there is always a kind of wall in between, a distance. Still, sometimes a video conference is more practical. But I would only do that for smaller milestones. I still would want to have the main persons responsible for the project with me in the same room. Editor So this brings up another subject: nowadays Embarcadero is doing a lot of video conferencing. I must say it seemed interesting. But the details you just mentioned are not covered by that. I find these video examples etc. very tiring and at a distance. I think there should be more as we had in the past conferences you could personally attend to. Gwan I can see the organizational and financial reasons for video conferencing etc. but like you I like 'real' conferences much better. I still go to such events like the Delphi Tage in Leipzig. Saturday and the EKON in Cologne in November. I really miss the old BorCons. The chance to meet other developers from all over the world and discuss anything while having a beer in the bar or in the old days having a cigarette outside of the conference building. But I do not expect Embarcadero to organize anything like that again.
54
COMPONENTS DEVELOPERS
4
Gwan I believe FireMonkey might be the way to give Delphi a
Editor We have plans for creating a Pascal learning program, at least for the Netherlands. Gwan A Pascal learning program sounds great. The main issue will be how to get it to the attention of teachers and even better students? For those who are already interested in Pascal and want to learn it they will be very happy to find such a program as Pascal/Delphi-books are getting rare. But if you can get it to the schools and universities a major step in getting future Pascal developers is made. Editor What do you actually really miss in Delphi? What would you you like to be changed? Gwan I do not miss that much in Delphi. I'm still using Delphi XE for the current main project.
Missing functionality is mostly by extensions like GExperts and component packscovered like Developer Express. My main problem is the documentation of programs. Most developers do not like to document and they barely describe what is in each of the modules. I would like a way for developers to describe their reasoning for certain parts of the program. Next to the questions where did he do what. I often have the question WHY?
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Interview met Gwan Tan - better office (Continuation 3) Editor What's your special quality as company (in Delphi?) towards companies?
Gwan I have seen a demo of the Leap Motion in Leipzig at the Delphi Tage. I was quite impressed by its possibilities. It does need some work to get a decent version out but I can see some great opportunities with it. I will have to think some more about itt as I have to find possible uses for my customers. It might be interesting to develop some managerial/marketing applications with it for instance to manipulate the views of graphs etc.
Gwan As mentioned earlier we develop applications with databases, mostly Delphi and InterBase but sometimes with Visual Studio and other databases like MySQL, SQL Server and Firebird. Although we have developed a 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 information available to the corporation. I prefer not to give names of customers although with social housing corporations it would be possible. Ok, there are many corporations we worked for. Often it is easier to mention the cities they are at as that is mostly their working area. The main cities are: Maastricht, Nijmegen, Eindhoven, Nieuwegein, Almelo, Enschede, Ede, Dantumadeel, Woudenberg and Raamsdonks Veer. I probably have forgotten several corporations but this should give an idea
Gwan I find 3D printing very interesting. I even thought about getting me one just to try it. Except from finding the time to 'play' with it I did not have an immediate idea for something 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 searching for the best 3D-printer to do the job. I am certain that 3D-printing will become more and more important in the future. The price has gone down a lot and the capabilities of those 'cheap' printers are improving a lot. To be able to develop 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 I can understand Embarcadero needs to get a better cash flow in order to keep investments in development possible but I do not believe the market will accept updates to be released that often. Especially if they charge the normal cost. Developers (and their bosses) will not go for each update and will only update if one really needs to update. Also if updates are released that often most updates will be actually just patches. They should be free anyway and made available as soon as possible. So if Embarcadero is really going for a release schedule of once every 6 months and charges for each update their standard prices I expect part of the market to turn away from the product. How large that part will be is hard to predict but as the Delphi market is already small any part will hurt. Editor Did you ever consider the other Pascal? FPC- combined with Lazarus? Have you ever tried that?
Editor Sure! In this have started our firstthe articles about this. There isissue even we a possibility of creating so called 4th dimension: Interactive layer printing...Like printing a layer that is able to react towards light or temperature. One could even print layers that enable special things like creating an interactive object with printed chips on it... Gwan The future capabilities of 3D-printing are going to be enormous. Already some creative minds have found new ways to use 3D-printing. I wonder what will be done with it in the future. So your articles will give everyone the chance to become part of that future. I do think that that is one of the main reasons for magazines, conferences and fora etc. to be reading and discussing the future not just what can be done today but also what might be possible in 5 or 10 years....
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?
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
55
productivity software building blocks
Using GEO services in Delphi applications with TMS components Mapping
The fundament on which all geo services are built is the determination of a position on our planet. For this, in almost all cases, the system of longitude and latitude is used. The latitude is a value between -90 and +90 that defines the angle of the position between south-pole and north-pole. The longitude is a value between -180 and +180 that defines the position along the equator. These values are typically expressed in decimals or with degrees, minutes and seconds. For ease of calculation, most services use the decimal notation.
The 3 major services that provide digital maps are Google Maps, Microsoft Bing and Open Street Maps. Digital maps are provided via a HTTP service and can be displayed via a browser. These mapping services are targeted mainly at browser usage, so if we want to take advantage of these maps from a Delphi application, the main two challenges are calling a Javascript API from the Delphi application that is executed in the browser and handle Javascript events that are being triggered from the map and that ideally are exposed as class events to a Delphi application. For a Windows application, to call Javascript functions from a Delphi application for a map displayed in a browser, the HTMLWindow.execScript() function can be used. To handle Javascript events from the Windows TWebBrowser at Delphi application level, it is required to implement the IDocHostUIHandler interface. Fortunately, this is somewhat easier from an IntraWeb application as the map is displayed typically on the same page where the IntraWeb application page is rendered. For a FireMonkey mobile application, a technique similar to the Windows desktop application is necessary but in this case with the browser that runs on the mobile device. Fortunately, TMS software offers components for desktop, web and mobile application with the same interface, so this makes using the maps easy on any of these platforms.
If there is a classification to make for what geo services are being used, we'd divide these in following categories:
To get started with using Google maps, drop the TMS TWebGMaps component on the form and add the code:
Introduction In the past few years, a vast array of services related to a position on our planet earth became available. With a wide range of components, TMS software offers seamless access to these services from Delphi VCL desktop applications, IntraWeb web applications and FireMonkey mobile applications running on iOS or Android. As such, it's mostly your imagination that is the limitation of what you can do these days with geo services
in your applications. Basis of geo services
- Mapping: techniques for displaying & manipulating
:= ; webgmaps1.MapOptions .DefaultLatitude
maps and visualize information on maps - Geocoding / reverse geocoding: techniques for converting an address to a longitude and latitude and vice versa - Geolocation: techniques to obtain the longitude and latitude of a computing device - Geo POI services: services that provide information / data about points of interest at a specific position - Routes: calculate the routes between two or more positions
webgmaps1.MapOptions .DefaultLongitude :;= webgmaps1.Launch ;
In this article, we have examples for using each of these different services from Delphi applications.
51.2 4.37
This code snippet initializes the map for position 51.2 / 4.37 that is Antwerp, Belgium. Next we can add a marker at this location. Markers are exposed via a markers collection. By default a marker with a hint is added with this code: webgmaps1.Markers .Add (5 1 .2, 4 .3 7,'Antwerp' ) Next task is to draw a polyline on this map. To illustrate this, code will be added to draw a triangle between 3 cities: Antwerp, Gent, Brussels.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
57
productivity software building blocks
Using GEO services in Delphi applications with TMS components (Continuation 1) To draw a polygon, we basically create a path, i.e. a collection of coordinates between which the polygon is drawn. The path is of the type TPath and this collection contains TPathItem that holds the longitude and latitude of each point. When this path collection is created, it is added to the PolyLines collection that is available at TWebGMaps level. This results in code: var pt : TPath ; pti : TPathItem ;
begin : =pt . TPath (Create webgmaps1 ); pti : = . pt ;Add pti . Latitude := ; pti . Longitude := ;
51.2 4.37
pti : = . pt ;Add pti . Latitude := ; pti . Longitude := ;
51.05 3.7
pti : = . pt ;Add pti . Latitude := ; pti . Longitude := ;
50.85 4.35
pti : = . pt ;Add pti . Latitude := ; pti Longitude . := ;
51.2 4.37
webgmaps1 . Polylines . ( Add ,false , false false , ,
n,i lpt clred , , ,2 5 5 2true , ); 100
pt .Free ;
end;
58
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
productivity software building blocks
Using GEO services in Delphi applications with TMS components (Continuation 2) Geocoding / reverse geocoding
begin
// set the address Geocoding is the process of converting an WebGMapsGeocoding1 . Address := address to a longitude and latitude coordinate. '5617 Scotts Valley Dr #200, Scotts Valley,; CA' Reverse geocoding means obtaining an address // launch geocoding WebGMapsGeocoding1 . LaunchGeocoding ; starting from a longitude and latitude // pan map to location retrieved coordinate. This is typically performed by a WebGMaps1. MapPanTo WebGMapsGeocoding1 ( . ResultLatitude , company that has all the required mapping data WebGMapsGeocoding1ResultLongitude . ); to perform this function. Some of the services // add a marker that provide this are: Google Maps, Microsoft WebGMaps1 .Markers . Add (WebGMapsGeocoding1 ResultLatitude . , WebGMapsGeocoding1 . ResultLongitude ,'Embarcadero') ; Bing, OpenAddresses, Yahoo PlaceFinder and end ; several smaller services. When looking at the performance, reliability and quality of the T o have a streetview o n the location retrieved, following code can be used :
service, Google easily comes out as best.that Therefore, we created two components make using the Google geocoding and reverse geocoding service very easy. This is TWebGMapsGeocoding and TWebGMapsReverseGeocoding. To use these components is as simple as specifying the address and retrieving the result longitude and latitude and vice versa. To demonstrate geocoding, we'll perform a lookup of the geolocation of the Embarcadero office and have it displayed on a map and switch to streetview. With a TWebGMapsGeoCoding component and TWebGMaps component on the form, following code obtains the longitude & latitude of the Embarcadero office in Scotts Valley, adds a marker on the map and pans the map to it:
// set coordinates of location to see with stre et view WebGMaps1 .StreetViewOptions DefaultLatitude . := WebGMapsGeocoding1 ResultLatitude . ; WebGMaps1 . StreetViewOptions . DefaultLongitude := WebGMapsGeocoding1 ResultLongitude . // let the map s witch to streetview webgmaps1 .StreetViewOptions .Visible := ; true
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
59
productivity software building blocks
Using GEO services in Delphi applications with TMS components (Continuation 3)
Geolocation
Geolocation is the name used for all kinds of techniques that provide information about the location of a device. These days, most mobile devices have a GPS built-in and
• Wi-Fi service based: Similar as with cell phone based geolocation, Wi-Fi based geolocation could be an option when a device is connected via a Wi-Fi access point and
this can returnFrom the longitude andthe latitude of thecomponent device immediately. Delphi XE4, non-visual TLocationSensor is provided that allows you to get this information. Using TLocationSensor is easy. Set LocationSensor.Active = true and via the event OnLocationChanged, the position is returned. The accuracy of determining this location is around 10 metres typically. When no GPS is available, we must resort to different techniques. These techniques can be: • ISP IP address based: many ISPs have a database of what IP address range is being used in what area. Services exist that gather this information that can be used to retrieve location information based on an IP address. • Cell phone based: when a mobile device is connected to a cell phone access point, the position of the cell phone access point is known and thus also the area the signal of this cell phone access point covers. There are also services that collect this information and make it accessible. OpenCellID is an example. See: http://www.opencellid.org/cell/map
the location of Wi-Fi that access point information is known. on the An example ofthe a service collects position of Wi-Fi access points is http://www.skyhookwireless.com For mobile devices, typically a fallback mechanism is used. First, there is a check if a GPS exists. When not, it can try to see if it can find a position based on the IP address, the connected cell phone access point or Wi-Fi access point. This is the mechanism that is built-in these days in any HTML5 compliant browser. This allows web applications to determine where the device is located that connects to it. This is known in the HTML5 standard as HTML5 Geolocation API.
60
To make it easy for desktop applications to determine as good as possible the location of a machine, TMS software offers a component TAdvIPLocation that uses the FreeGEOIP service. To make it easy for IntraWeb web applications, we have a component TTIWIPhoneGeolocation that uses the HTML5 Geolocation API to determine the location.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
productivity software building blocks
Using GEO services in Delphi applications with TMS components (Continuation 4) Sample code:
i f AdvIPLocation1. GetIPLocation t h e n 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 location onon theaIP address of the machine andthe shows this based location map and adds the location city name, ZIP code and country in a memo.
perform query of(obtained the top 10with points of interests nearby computera location TAdvIPLocation) andthe fill the listbox with this info:
GEO POI services
( :Sender ); TObject procedure.TForm1 ListBox1Click var 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 ; begin things as railway stations, museums, restaurants, sports [ Items. listbox1 ] ItemIndex ; infrastructure etc... Typically, a service can provide a list of := id . listbox1 points of interest that matches a requested category and a :id = (copy , id(pos , '/' )+, id 1); 2 5 5 specific location. It can then offer information such as 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: AdvFourSquare1 .GetNearbyVenues ( location, i.e. longitude & latitude, specify category of orgla orglo , ,' ', 'id ', ); points of interest we're interested in and possibly also a radius. The service then returns a list of points of interests // Add the summary line to a listbox of which we can query a description, photo, etc.. f o r i0 :t=o advfoursquare1 . Venues . Count -1 To illustrate this, we'll use the component TAdvFourSquare, available in the TMS Cloud Pack. To start using this component, it is necessary to first obtain a (free) FourSquare application key and secret. You can register for this at https://developer.foursquare.com First we obtain the different categories and subcategories of points of interests that FourSquare has and fill a listbox with this:
do begin listbox2.Items .Add ( AdvFourSquare1 Venues . i [ Summary ].
);
end; end;
var
i,j : integer ; id: string;
begin AdvFourSquare1 . . := App Key
FourSquare_AppKey ;
AdvFourSquare1 . . := App Secret
FourSquare_AppSecret ;
AdvFourSquare1. GetCategories;
:= f o r i
0 t o advfoursquare1 . . Categories Count
1 do
begin listbox1.. Items Add (AdvFourSquare1 .Categories [i ].Summary ); fo=r: j 0 t o AdvFourSquare1 . Categories [ ]. i Sub Categories . Count
1 do
begin listbox1.. Items Add (AdvFourSquare1 .Categories [i ].SubCategories [j ].Summary +
'/'+AdvFourSquare1 .Categories [i ].SubCategories [j ].ID ); end; end; end;
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
61
productivity software building blocks
Using GEO services in Delphi applications with TMS components (Continuation 5) Other than the summary information, FourSquare makes a lot more information available. This includes the longitude & latitude of the point of interest, the address, phone number, website URL when available, opening hours when available etc... All this information is made easily accessible via the TFourSquareVenue class in the Venues collection.
Routes
A final important part in useful geo information based services we can consume from Delphi applications, is getting routing or directions information to travel from a location A to a location B. Again, the three major suppliers of these services are Google with the Google Directions API, Microsoft with Bing Routes API and the Openroute service (http://www.openrouteservice.org/). Such service typically works in following way: we make a request 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.
In this example, we use the TWebGMaps component as well as the TWebGMapsDirectionList. TWebGMapsDirectionList is a component especially designed to visualize HTML formatted directions information returned by Google. Getting directions is as simple as calling WebGMaps.GetDirections with start and end address. Here we obtain directions information from San Francisco center to the Embarcadero offices in Scotts Valley
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 );
// fill the list component WebGMapsDirectionList with route description webgmaps1. FillDirectionList( WebGMapsDirectionList1. Items) ; // render the route on the map webgmaps1. RenderDirections( from_address, to_address) ; end;
62
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
productivity software building blocks
Using GEO services in Delphi applications with TMS components (Continuation 6)
Many more options are available, such as specifying waypoints, route types, language, travel mode (walking/bike/car) etc... that can be explored with the TMS TWebGMaps component. Summary It is amazing what amount of rich (and in many cases free) geo information and geo services are available to us Delphi programmers these days.
With this information, we can add useful functionality to Delphi Windows based applications, IntraWeb web based applications and FireMonkey mobile applications for iOS and Android devices. At TMS software, we offer a wide range of components that allow you to consume these services right-away. This allows you to avoid studying the various APIs yourself and be productive immediately to integrate these in your applications. The several products covered in this article that make use of these services are: TMS WebGMaps: http://www.tmssoftware.com/site/webgmaps.asp TMS WebOSMaps:
About the author Bruno Fierens Studied civil electronic engineering at university of Ghent, Belgium (1987-1992) and started a career as R&D digital hardware engineer. Besides the fascination for electronics, Bruno Fierens set the first steps in programming with Turbo Pascal v3.0 and used all Borland Pascal & Delphi versions since that time. In 1996, he founded TMS software for the activity of application and component development with Delphi. TMS software became Borland Technology Partner in 1998, developed Delphi Informant award-winning grid & scheduling components and now has an international team of software developers working on a large portfolio of components. Bruno Fierens is from 2012 Embarcadero MVP and frequent speaker at Delphi conferences world-wide. He does and oversees VCL, IntraWeb, .NET and FireMonkey component development.
http://www.tmssoftware.com/site/webosmaps.asp
TMS Cloud Pack: http://www.tmssoftware.com/site/cloudpack.asp TMS Cloud Pack for FireMonkey:
http://www.tmssoftware.com/site/tmsfmxpack.asp TMS WebGMaps for FireMonkey: http://www.tmssoftware.com/ site/tmsfmxwebgmaps.asp TMS WebOSMaps for FireMonkey: http://www.tmssoftware.com/ site/tmsfmxwebosmaps.asp TMS IntraWeb WebGMaps: http://www.tmssoftware.com/site/webgmaps.asp
TMS IntraWeb WebOSMaps: http://www.tmssoftware.com/site/webosmaps.asp TMS IntraWeb iPhone controls pack: http://www.tmssoftware.com/ site/tmsiwiphone.asp
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
63
productivity software building blocks
Using GEO services in Delphi applications with TMS components (Continuation 5)
64
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Correcting a bad API design: By Alexander Alexeev missing user-arguments for callback functions This article is a supplement to the other article, "Designing an API: typical mistakes." on page 8 of this issue. In this article we will discuss how you can fix one of those mistakes listed in the previous article, namely: the lack of userarguments for the callback-functions. Introduction to the callback-functions
In computer programming, a callback is a piece of executable code that is passed as an argument to other code, which is expected to call back (execute) the argument
type PEnumArgs = ^ TEnumArgs ; TEnumArgs = record ClassName : String; Windows : TStrings ;
end; function FindWindowsOfClass ( : ; Wnd HWND lpData: LPARAM ): Bool ; stdcall; var Args : PEnumArgs ; WndClassName , WndText : String; begin lpData : =Args (Pointer) ;
at some convenient time. For example, if you want to set the timer by using the Windows API, you can call the SetTimer function, passing a pointer to the function to it, which will be the callbackfunction. The system will call your function whenever the timer fires:
SetLength ( WndClassName, Length( Args. ClassName+) ; 2 ( SetLength WndClassName , ( GetClassName , Wnd PChar(WndClassName ) , Length ( WndClassName )) );
i f WndClassName = Args .ClassName t h e n begin SetLength( WndText, GetWindowTextLength( Wnd) + ) 1; ( SetLength , WndText ( GetWindowText , Wnd PChar (WndText ) , Length ( WndText ) )); Args . Windows . Add ( Format ( '%8x:%s' [ , , Wnd WndText])); end;
procedure MyTimerHandler ( : Wnd; HWND : uMsg ; UINT idEvent : UINT_PTR ; dwTime : DWORD ); stdcall; begin
// Will be called after 100 ms. end;
( :Sender ); TObject procedure.TForm1 Button1Click begin SetTimer ( Handle ,@, 1 1 0 0 MyTimerHandler ); end;
Result := True ;
end; procedure.TForm1 Button1Click ( : Sender ) ; TObject var
Here's another example: if you want to find all the windows Args : TEnumArgs ; on your desktop, you can use the EnumWindows function: begin
// In the Edit, you can enter values ??such as: // 'TForm1', 'IME', 'MSTaskListWClass', 'Shell_TrayWnd', //'TTOTAL_CMD', 'Chrome_WidgetWin_1'
function MyEnumFunc ( : Wnd; HWND :lpData ):LPARAM Bool; stdcall; begin
// This is called once for each window in the system
Args.ClassName := Args . Windows := .
end;
( :Sender ); TObject procedure.TForm1 Button1Click begin EnumWindows (@ MyEnumFunc ,); 0 end;
Since the callback function normally performs the same task as the code that sets it, it turns out that the two pieces of code has to work with the same data. Consequently, the data from the code setting callback must be somehow passed to the callback function. For this purpose, the callback functions provide so-called user-argument: either a pointer or an integer (such as Native(U)Int, but not (U)Int), which are not used by the API and transparently passed to the callback-function. Or (in rare cases), it can be any value that uniquely identifies the function call.
.Edit1; Text Memo1 ; Lines
Memo1 . Lines. BeginUpdate;
try Memo1. Lines. Clear; EnumWindows @ ( FindWindowsOfClass , ( @ LPARAM )); finally Memo1. Lines. EndUpdate; end; end;
Args
Note: the equivalent of user-parameters are the Tag property and Data property, although their use is not always ideologically correct (correct: a derived child class).
For example, in SetTimer function has idEvent, and EnumWindows function has lpData. We can use these parameters to pass arbitrary data. Here, for example, how to find all the windows of a given class:
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
65
Correcting a bad API design (Continuation 1) Static callback-method I nstead of callback-function
Bad API design
As the modern applications are usually built as a set of classes - it would be nice to isolate the callback function: to make it not global, but a member of the class. This is easily done as follows, using a static class methods:
If not too experienced developer did the design of the API, he may not think about the need for a user-arguments in callback-functions (as well as many other things - see previous article). As a result, you may have the following code on hand:
type
type
TForm1 = class(TForm ) Button1 : TButton ; Memo1 : TMemo ; Edit1 : TEdit ;
TCallback = cdecl;
function ( FoundData : ) : TData ;
function RegisterCallback ( : Integer; cdecl;
Callback ):
BOOL
TCallback
// Adapter str ict teon cl ass pri funva cti InternalEnumWindowsCallback (Wnd : HWND ; lpData: LPARAM): Bool ; st dcal l; sta ti c;
// Hi-level interface
Using global variables
protected
fu n ct i o n EnumWindowsCallback ( c o n stAWnd : HWND ): Boolean ; virtual; function EnumWindows : Boolean ; end; // ...
class fun cti on TForm1.InternalEnumWindowsCallback (Wnd; : HWND lpData : LPA RAM ): Bool ; var
Form : TForm1 ;
Result := .
Of course, because the developer API - is not you, then you cannot change the prototype of the callback function. What to do? Since we cannot send data through a local option, the only option is a global parameter: var
function TForm1 .EnumWindows : Boolean ; begin =Result :. WinAPI . Windows( EnumWindows @InternalEnumWindowsCallback , LPARAM( Pointer( Self) ) ) ; end;
begin Form :=
As you can see, there are no user-parameter, and the only argument for callback-function represents the actual data for the function.
TForm1 lpData ( ); Form EnumWindowsCallback ( ); Wnd
end; function TForm1 . EnumWindowsCallback( HWND Boolean ; const:AWnd ): var WndClassName , WndText : String; begin // Your code - it can work with the members of the class. // For example: SetLength( WndClassName, Length( Edit1. Text)+ ) ;2 SetLength (WndClassName , GetClassName ( AWnd, PChar WndClassName ( ), Length( WndClassName) ) ) ;
GMemo : TStrings ;
function MyCallback ( FoundData : ) : TData ; BOOL cdecl; begin GMemo . Lines . Add ( DataToString ( FoundData )); end; function.TForm1 Button1Click ( : Sender ) ; TObject begin GMemo := Memo1 ; RegisterCallback ( MyCallback); end;
Of course, we can make callback-function to be a member of the class, as we did above (with help of “static” keyword); but it should be understood that in this case it will be all the same global function and variable, but a little disguised. Accordingly, we cannot access properties and methods of the class within callback. This solution may be considered as "satisfactory" because it works, but uses global variables - that is. Sometimes this may be acceptable, but often we need to call the callbackfunction multi-threaded (which, however, can be solved through threadvar) or even just a couple of different calls within a single thread. In this case, it seems that there is no reliable way to identify function's data?
i f WndClassName = Edit1 .Text t he n begin SetLength( WndText, GetWindowTextLength( AWnd) + )1; SetLength (WndText , GetWindowText ( AWnd, PChar ( WndText ) , Length ( WndText )) ); Memo1 . Lines . ( Add Format ( WndText]));
'%8x:%s' [ , , AWnd
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 guaranteed working version, "adding" user-parameter to the existing "bad" API. This is not an impossible task as it may seem, but the solution is not trivial. The essence of the idea is that the user-parameter can be replaced by the actual function callback that will be unique to each use. Thus, the call will not be identified by the parameter, but rather to which callback function is called.
First step: write the following dummy code: type TRealCallbackFunc = ( : FoundData ; function Data: Po inter ): BOOL ; cdecl;
( : FoundData ): function InternalCallback BOOL; cdecl; var
TData
TData
RealCallback : TRealCallbackFunc ;
begin RealCallback : = Pointer ( ;34 5 67 8 $ ) 12 Result : = RealCallback (
Statement of the problem
Of course, in a pre-compiled file there cannot be an arbitrary number of functions for arbitrary userarguments. That is why this solution requires generating code on the fly ( in run-time). Fortunately, it's not too difficult task because you can use the services of the Delphi compiler to generate the template. What's more - you may not even know assembler. But you need to have some idea of the memory architecture in Windows. So, suppose we have the following: type TData = Integer ; // just as an example, it could be
// anything: a pointer, record, etc. TCallback = fu n ct i o n(FoundData : TData ) : BOOL ; ;cd e cl TRegisterCallbackFunc = ( :functionCallback TCallback ): Integer ; cdecl ; TUnregisterCallbackFunc = procedure (Callback : TCallback ) ; cdecl ; var RegisterCallback : TRegisterCallbackFunc ; // imported from a DLL UnregisterCallback : TUnregisterCallbackFunc ; // imported from a DLL
This is our "bad" API. Since the API is usually located in a separate DLL, then I made an example of a functionvariable, rather than a normal function. So our challenge: to add support of the user-parameter to the API. Note: not always "bad" API is the result of developer mistake. Sometimes this kind of design is dictated by technical constraints. For example, the function SetWindowsHookEx can set hooks both locally (in the current thread) and globally ( for all processes in the system). You simply cannot have a user-parameter with the design: because the user-parameter during installation of the hook will not make sense at the time of the callback call - as the hook is called from another process. However, if you want to install a local trap in the same process, the user-argument would be quite useful. For example, if you want to keep track of window messages for some of your windows. In this case, the hook will be limited only to your main thread, but you may have many windows, so threadvar cannot help you.
FoundData , Pointer ( $ 8 7 65 4 32) 1);
end;
. Button1Click ( : Sender ); procedure TForm1 begin InternalCallback (0); end;
TObject
Here: InternalCallback - this is a callback-function, the prototype of which is fully consistent with API. TRealCallbackFunc – is a modified callback-function with desired prototype, which is different from the API only by the presence of an additional parameter: it is our user-parameter. Although the prototype of RealCallback can be arbitrary, but for ease of debugging, it is desirable that he would be as similar to InternalCallback as possible. However, you can also put a user-parameter to be the first parameter and change the call model from cdecl/stdcall to register - it will allow you to skip intermediate adapter in the future, if you ever want to make this callback-function a member of the class. In this case, the Self will be transferred in user-parameter - thus the signature of callback-function binary matches the signature of the conventional method of the class. We should just call RealCallback via fixed pointer, passing a fixed pointer as the user-parameter. The values of $12345678 and $87654321 are chosen for the simple reason that it will be easy to spot them in the native code. You can use any other "magic" values. Surely, in the case of 64-bit code you need to use 16 bytes magic values instead of 8 byte, for example: $1234567890ABCDEF and $FEDCBA0987654321. The RealCallback function is not called directly, but indirectly - through a RealCallback variable. I will explain below why it is done. So, add a direct call to InternalCallback in your code: procedure.TForm1 Button1Click ( :Sender ); TObject begin InternalCallback ); (0
// here: TData = Integer (in this example), // you can pass any value end;
Warning: do not run this code! This code will result in an exception being thrown (Access Violation), because it calls "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 (aka - the prologue), assigning the function to variable, the call, end (aka - the epilogue). Each line is labeled with comment (in bold font), which shows the location in the module and the code line itself. Lines below the comment is a machine (native) code that was compiled from that line. Do not worry, you absolutely do not need to understand what it says! All you need – is to determine the beginning and end of the function. It is easy to do: start of the function is at the very top and marked by a blue line. The end of the function is clearly in line Unit1.pas.43: end; - 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. Note: If you do know assembler, then you know that the
function ends in a line with a ret command ( return control), and the next line is just a garbage filler. Select the code with your mouse and copy it somewhere via the clipboard. Again, you do not need to run this code! If you do not know assembly language, then all you need to know: The first column is the address of the instruction. These addresses belong to your exe ( after all it is the exe code that we have called from Button1Click ) and we are not interested in these. The second column: hex-code of machine code. That is, this is the machine code in a pure form, which will be executed by the processor ( CPU) – and this is where we are concerned. The third column - is the assembly code corresponding to machine code. The great thing about this listing - we do not need the assembly code, we need only native code. Now I'll explain why... Now look: this function has only two values of the variables:
68
COMPONENTS DEVELOPERS
4
· ·
Address of the called function (RealCallback): $12345678 The va lue of u ser-parameter: $ 87654321
All other code is static and it does not depend on anything, i.e. it will be exactly the same in all cases: for any userarguments for any callback-functions. This means that if we want to generate the functions themselves, such as InternalCallback, we can just copy all the code and simply substitute two numbers: the address of the function and user-argument. Address of the function and user-parameter is easy to spot in machine code, since we used the magic value of $12345678 and $87654321. Note:
this is why I used the design with indirect function call instead of direct call: because in this design the call is defined as "call the function at that address." There clearly "this address" present - which that it can beiseasily changed. If the call was direct, themeans machine code were said to "call a function that lies in N bytes before this instruction". Altering such address would be much more difficult. That is all that relates to parsing the template. Please note that we did not use any special knowledge, apart from the ability to use a debugger and some common sense. Then proceed to the coding. First, you need to copy our template into a byte array for later use in the program code.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Correcting a bad API design (Continuation 4) To do this, I first wrote out the entire machine code (second column): 55 8BEC 83C4F8 C745F878563412 // 12345678 6821436587 // 87654321 8B4508 50 FF55F8 83C408 8945FC 8B45FC 59 59 5D C3 Remember, you do not need to write these codes manually - select text within CPU-debugger with the mouse and use Ctrl + C - this will copy the selected lines to the clipboard. Then, go to the code editor and paste the text from the clipboard, and then remove the four comment lines, as well as the first and last columns, leaving only native code. Note that x86 is little-endian architecture - which means that the numbers "are written in reverse." Of course, you do not have to change the byte order.
This weird code says that a CallbackTemplate pointer ( and any string - is a pointer, for a static array you'd have to take a pointer explicitly via @) should be interpreted not as a string, but as a function of the type TCallback. So this function, therefore, should be called. Here is a longer version of the same code: procedure TForm1 . Button1Click ( : Sender ); var
TObject
Template : AnsiString ; Ptr : Pointer ; CB: TCallback ;
begin Template := CallbackTemplate ; :=Ptr (Pointer Template ); := CB TCallback ( ); Ptr CB(0); end;
Set a breakpoint to the function call (in any form - long or short), run the program, click, stop up on the breakpoint. Do not press F7! As we have no source code for the function encoded in CallbackTemplate, the debugger will try to execute the whole function in a single pass - which will lead to Access Violation, as both of our pointer ( $12345678 and $87654321) point to trash. Instead, call the CPU debugger (Ctrl + Alt + C or View / Debug Windows / CPU View), and hit the F7 key few times - until you will be transferred to the familiar code:
After that, I removed the line breaks, combining all the machine code in a long stream of bytes: 558BEC83C4F8C745F87856341268214365878B450850FF55F8 005B5C70 55 push ebp 005B5C71 8BEC mov ebp, esp 83C4088945FC8B45FC59595DC3 005B5C73 83C4F8 add esp, - $ 08 005B5C76 C745F878563412 mov [ebp-$ 08], $ After that I put in the #$ every two characters and 12345678 designed this constant as a string: 005B5C7D push $ 6821436587 87654321 005B5C82 8B4508 mov eax, [ebp + $ 08] const 005B5C85 50 push eax CallbackTemplate : RawByteString = 005B5C86 FF55F8 call dword ptr [ebp-$ 08] ############# $ 5 5 $8 B $ E C $8 3 $ C 4 $F 8 $C 7 $4 5 $F 8 $7 8 $5 6 $3 4 005B5C89 83C408 add esp, $ 08 $1 2# $ 6 8# $2 1 + 005B5C8C 8945FC mov [ebp-$ 04], eax 005B5C8F 8B45FC mov eax, [ebp-$ 04] ############# $ 4 3 $6 5 $ 8 7 $8 B $ 4 5 $0 8 $ 5 0 $F F $5 5 $F 8 $8 3 $C 4 005B5C92 59 pop ecx $0 8# $ 8 9# $4 5 + 005B5C93 59 pop ecx ######## $F C $ 8 B $4 5 $ F C $5 9 $ 5 9 $5 D $C;3 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:
procedure TForm1 . Button1Click ( : Sender ); begin TCallback ( Pointer ( CallbackTemplate ))( );
end;
TObject
0
of course, the machine code is specific to the target CPU. Machine code for x86-32 will not run on x86-64 and ARM (and vice versa). That is why, if you are writing crossplatform 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:
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
69
Correcting a bad API design (Continuation 5)
and CallbackTemplate will be: const
CallbackTemplate : RawByteString =
{$IFDEF CPUX86} ################# $ 5 5 $8 B $E C $8 3 $C 4 $F 8 $C 7 $ 4 5 $F 8 $ 7 8 $5 6 $ 3 4 $1 2 $ 6 8 $2 1 $ 4 3 $+6 5 #$ 8 7# $8 B# $4 5# $0 8# $5 0# $F F# $5 5 # $ F# 8 $8# 3 $ C#4 $0#8 $ 8#9 $4#5 $ F#C $8 #B $# 4 5 $+F C #
$#5 9 $#5 9 $#5 D; $C 3
{$ENDIF} {$IFDEF CPUX64} ################# $ 5 5 $4 8 $ 8 3 $E C $3 0 $4 8 $8 B $E C $8 9 $ 4 D $4 0 $ 4 8 $B 8 $ E F $C D $ A B $+9 0 ################# $ 7 8 $5 6 $ 3 4 $1 2 $4 8 $8 9 $4 5 $2 0 $8 B $ 4 D $4 0 $ 4 8 $B A $ 2 1 $4 3 $ 6 5 $+8 7 #$ B A# $D C# $ F E# $0 0# $F F# $5 5# $2 0 # $8# 9 $4#5 $ 2#C $8#B $ 4#5 $2#C $ 4#8 $8 #D $# 6 5 $+3 0 #
$#5 D ;$C 3
{$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. Dynamic code generation
The next step - we need to create a real callback function (with real addresses) from the CallbackTemplate template. Actually, it is very simple - just replace the addresses in the template and the code is ready. There is only a small caveat: any executable code must be placed in a memory page that has the attribute of the EXECUTE in the x86 architecture. If we simply allocate memory (GetMem/AllocMem or just use a string array, or some other buffer), it will be the "data": it will have access to read, write, but not execute. Therefore, an attempt to call this code will result in Access Violation. Note: "read" and "execute" were equivalent in early generations of x86 processors. Therefore, although technically put equality between them was never correct, some lazy developers took advantage of this feature of early x86 implementations and pass control to the code in the data segment. Again: it was never correct, but it worked on older processors. Now, this code will crash. See also http://en.wikipedia.org/wiki/Data_Execution_Prevention. Let us remember how the memory manager works: it splits the memory page into blocks of memory that the program "allocates" from memory. This means that we cannot just take the memory via Delphi functions and change page attributes: this memory will be located in the same memory page along with some other data, and by changing access to the page we would change the access to some other data. For this reason, we need to allocate memory from the system directly, without intermediaries helpers. In summary, here is the appropriate code:
70
COMPONENTS DEVELOPERS
4
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Correcting a bad API design (Continuation 6) unit FixCallbacks; interface
// Function dynamically creates code from a template // The template should use values of // $12345678/$1234567890ABCDEF and $87654321/$FEDCBA0987654321 // as stubs for callback function and user argument respectively. Result of this function can be passed as callback into "bad" API fu n ct i o n AllocTemplate (
co n s :tATemplate
RawByteString ;
co,n sACallback t : AUserParam ):
Pointer ;
Pointer
Pointer ):
Pointer ;
// Function releases dynamic code allocated via AllocTemplate // You should pass result from AllocTemplate as ATemplate argument into DisposeTemplate procedure DisposeTemplate (
const : ATemplate );
Pointer
implementation uses Windows; {$IFDEF CPU386} {$DEFINE CPU32} {$ENDIF} {$IFDEF CPUX64} {$DEFINE CPU64} {$ENDIF} {$IFDEF CPUARM} {$DEFINE CPU32} {$ENDIF}
fu n ct i o n AllocTemplate ( co n stATemplate: RawByteString; procedure ( StrReplace var : ATemplate RawByteString ; var X : Integer ; begin f o r X : = 1 t o Length ( ATemplate ) SizeOf ( ASource ) i f PNativeUInt ( @ATemplate [X ] ) ^= ASource t he n begin PNativeUInt ( @ATemplate [X ] ) ^: = ADest ; Break ;
co n sACallback t , AUserParam : ,const ASource :
ADest) ; NativeUInt
do
end;end; var OrgPtr : Pointer ; OrgSize : Cardinal ;Ptr :PPointer begin :=Result ; nil ActualTemplate := ATemplate ; :=
OrgSize := ( Length ) ATemplate ; OrgPtr (VirtualAlloc , , n i l OrgSize i f n o t Assigned (OrgPtr ) t h e nExit ;
MEM_COMMIT
ActualTemplate ;
o ,rMEM_RESERVE
RawByteString :
Dummy ;
Cardinal :
PAGE_READWRITE );
try Ptr := OrgPtr ; StrReplace ( ActualTemplate,
{$IFDEFCPU32} $ 1 2 3 4 5 6 7{$ENDIF}{$IFDEF 8 CPU64}
$1 2 3 4 5 6 7 8 9 0 A B C D {$ENDIF} EF ,
NativeUInt () ) ;ACallback StrReplace ( ActualTemplate,
{$IFDEFCPU32} $ 8 7 6 5 4 3 2{$ENDIF}{$IFDEF CPU64} 1 NativeUInt ( ) )AUserParam ; ( Move Pointer ( ActualTemplate )^, ^ , i f n o t VirtualProtect ( , OrgPtr ,
Ptr (
{$ENDIF} $F E D C B A 0 9 8 7 6 5 4 3 21 ,
Length ActualTemplate ) );
PAGE_EXECUTE_READ ,@ ) ;Dummy GetCurrentProcess OrgPtr OrgSize i f n o t FlushInstructionCache ( , , ) ; OrgSize
Result := ; Ptr OrgPtr := ; nil finally i f Assigned(OrgPtr ) t h e n VirtualFree ( OrgPtr ,, 0 end; end;
procedure DisposeTemplate ( const : ATemplate ); begin i f ATemplate = n i l t h e nExit ; VirtualFree ( ATemplate ,,
t he nExit t h e nExit
MEM_RELEASE );
Pointer
0 )MEM_RELEASE ;
end; end.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
71
;
Correcting a bad API design (Continuation 7) This module offers two universal functions for dynamic code generation of callback-functions. The logic of the code should be clear from the preliminary description of the code idea. With these functions, we can alter the interface module for the "bad" API as follows: // ... interface // ... type =Integer ; // remains the same
TData
// New type – as TCallback replacement, with added lpUser TRealCallback = f u n c t i(o nFoundData: TData;
c o n slpUser t : Pointer ):
BOOL ;
// New register functions fu n ct i o n RegisterCallback ( :c o n stCallback ; TRealCallback o n st c ou t lpUser : Pointer ; Cookie :Pointer ):Integer procedure UnregisterCallback ( :constCookie ); Pointer
; cd e cl
;
implementation uses FixCallbacks ; // "magic" functions
// Old declarations are hidden in implementation type TCallback = f u n c t i o(nFoundData: TData ) : BOOL ; ;cd e cl TRegisterCallbackFunc Integer = ( f u n c t: i o nCallback ) : TCallback ; ; cdecl TUnregisterCallbackFunc = ( procedure : Callback ); TCallback ; cdecl var InternalRegisterCallback : TRegisterCallbackFunc ; InternalUnregisterCallback : TUnregisterCallbackFunc ;
:=
fu n ct i o n RegisterCallback ( :c o n stCallback ; TRealCallback o n st c ou t lpUser : Pointer ; Cookie :Pointer ):Integer var UniqueCallback : TCallback ; begin Cookie := AllocTemplate ( CallbackTemplate @, , Callback ); lpUser UniqueCallback TCallback Cookie := ( ); Result InternalRegisterCallback ( ) ; UniqueCallback end;
;
procedure UnregisterCallback ( :constCookie ); Pointer begin InternalUnregisterCallback TCallback Cookie ( )); ( DisposeTemplate( Cookie); end; // ...
And then we can write code like this: fu n ct i o n MyCallback ( FoundData : ; TData con : s tlpUser) : Pointer ; ;BOOL cd e cl var Self : TForm1 ; begin Self TForm1 lpUser = : ( ); Self Memo1. Lines. Add . ( IntToStr( FoundData) ) ; // <- NEW! Access to form's fields Result := True ;
end;
procedure.TForm1 Button1Click ( Sender : ) ; TObject begin // FCallbackHandle – is a private form's field RegisterCallback (MyCallback , Pointer ( Self ) , FCallbackHandle ); end; procedure TForm1 . Button2Click ( : Sender ); TObject begin UnregisterCallback ( FCallbackHandle); FCallbackHandle := nil ; end;
72
COMPONENTS DEVELOPERS
4
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Correcting a bad API design (Continuation 8) Voila! Da Magics!
Win64
A practical example Please note that the AllocTemplate and DisposeTemplate functions are universal and do not depend on your code. To illustrate the versatility of this - let's write code to monitor window messages using SetWindowsHookEx. We will use the WH_GETMESSAGE hooks. To do this, we need to create a template first. Write this code: type TGetMsgProc =
(function : Self ; Pointer : code WPARAM lParam LPARAM ;_ : ):
Integer ;_ wParam : LRESULT;
( : ;code function InternalGetMsgProc
Integer
_wParam : WPARAM ; _lParam : LPA RAM ): LRESULT ; stdcall; var GetMsgProc : TGetMsgProc ;
begin GetMsgProc := Pointer ({$IFDEF
CPUX86}$12345678{$ENDIF}{$IFDEF CPUX64}$1234567890ABCDEF {$ENDIF}); :=Result ( GetMsgProc ( Pointer {$IFDEF CPUX86}$87654321{$ENDIF}{$IFDEF CPUX64}$FEDCBA0987654321 {$ENDIF}), code, _wParam , _lParam ); end; ( :Sender ); TObject procedure.TForm1 Button1Click begin ( , , ) InternalGetMsgProc 0; 0 0 end;
We get such an assembly listing for this code: Win32
Unit1.pas.34: begin 00000000006990B0 55 push rbp 00000000006990B1 4883EC30 sub rsp,$30 00000000006990B5 488BEC mov rbp,rsp 00000000006990B8 894D40 mov [rbp+$40],ecx 00000000006990BB 48895548 mov [rbp+$48],rdx 00000000006990BF 4C894550 mov [rbp+$50],r8 Unit1.pas.35: GetMsgProc := Pointer({$IFDEF CPUX86}$12345678{$ENDIF}{$IFDEF CPUX64}$1234567890ABCDEF{$ENDIF}); 00000000006990C3 48B8EFCDAB9078563412 mov rax,$1234567890abcdef 00000000006990CD 48894520 mov [rbp+$20],rax Unit1.pas.36: Result := GetMsgProc(Pointer({$IFDEF CPUX86}$87654321{$ENDIF}{$IFDEF CPUX64}$FEDCBA0987654321{$ENDIF}), code, _wParam, _lParam); 00000000006990D1 48B92143658709BADCFE mov rcx,$fedcba0987654321 00000000006990DB 8B5540 mov edx,[rbp+$40] 00000000006990DE 4C8B4548 mov r8,[rbp+$48] 00000000006990E2 4C8B4D50 mov r9,[rbp+$50] 00000000006990E6 FF5520 call qword ptr [rbp+$20] 00000000006990E9 48894528 mov [rbp+$28],rax Unit1.pas.37: end; 00000000006990ED 488B4528 mov rax,[rbp+$28] 00000000006990F1 488D6530 rsp,[rbp+$30] 00000000006990F5 5D 00000000006990F6 C3
lea pop rbp ret
Cut the machine code and make it into a constant: Unit1.pas.34: begin const 005B5C64 55 push ebp 005B5C65 8BEC mov ebp,esp GetMsgProcTemplate : RawByteString = 005B5C67 83C4F8 add esp,-$08 {$IFDEF CPUX86} Unit1.pas.35: GetMsgProc := Pointer({$IFDEF CPUX86}$12345678{$ENDIF}{$IFDEF ############# $5 5 $8 B $E C $8 3 $ C 4 $F 8 $ C 7 $4 5 $ F 8 $7 8 $ 5 6 CPUX64}$1234567890ABCDEF{$ENDIF}); $1 2###### $ 8 B $4 5 $ 1 0 $5 0 $ 8 B $4 D+ 005B5C6A C745F878563412 mov [ebp-$08],$12345678 Unit1.pas.36: Result := ############# $0 C $8 B $5 5 $0 8 $B 8 $2 1 $ 4 3 $6 5 $ 8 7 $F F $ 5 5 GetMsgProc(Pointer({$IFDEF $8 9###### $ 4 5 $F C $ 8 B $4 5 $ F C $5 9+ CPUX86}$87654321{$ENDIF}{$IFDEF $5 9 $ 5 D $C 2 $ 0 C $0 ; 0 CPUX64}$FEDCBA0987654321{$ENDIF}), code, _wParam, ##### _lParam); {$ENDIF} 005B5C71 8B4510 mov eax,[ebp+$10] {$IFDEF CPUX64} 005B5C74 50 push eax 005B5C75 8B4D0C mov ecx,[ebp+$0c] ############# $5 5 $4 8 $8 3 $E C $3 0 $4 8 $ 8 B $E C $ 8 9 $4 D $ 4 0 005B5C78 8B5508 mov edx,[ebp+$08] $8 9###### $ 5 5 $4 8 $ 4 C $8 9 $ 4 5 $5 0+ 005B5C7B B821436587 mov eax,$87654321 005B5C80 FF55F8 call dword ptr [ebp############# $ 4 8 $B 8 $E F $C D $A B $9 0 $ 7 8 $5 6 $ 3 4 $1 2 $ 4 8 $08] $4 5###### $2 0 $4 8 $ B 9 $2 1 $ 4 3 $6 5+ 005B5C83 8945FC mov [ebp-$04],eax Unit1.pas.37: end; ############# $8 7 $ 0 9 $B A $D C $F E $8 B $5 5 $4 0 $ 4 C $8 B $ 4 5 005B5C86 8B45FC mov eax,[ebp-$04] 005B5C89 59 pop ecx $4 C###### $8 B $4 D $ 5 0 $F F $ 5 5 $2 0+ 005B5C8A 59 pop ecx 005B5C8B 5D pop ebp ############# $4 8 $ 8 9 $4 5 $2 8 $4 8 $8 B $4 5 $2 8 $ 4 8 $8 D $ 6 5 005B5C8C C20C00 ret $000c $5 D# $C 3;
$3 4
$F 8
$4 8
$8 9
$4 8
$3 0
{$ENDIF}
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
73
Correcting a bad API design (Continuation 9) Everything is ready to go, you can call the code now: type TForm1 = class(TForm ) Button1 : TButton ; Memo1: TMemo ; Edit1: TEdit ; ( : );Sender TObject procedure FormCreate procedure ( FormDestroy : ); Sender TObject
private FHook : Pointer ; FHookHandle : THandle ; function GetMsgProc ( : code ;_Integer: procedure InstallHook ;
wParam ;_
WPARAM :
lParam ):
LPARAM ;
LRESULT
procedure UninstallHook ; end; var Form1 : TForm1 ;
implementation uses FixCallbacks;
{$R *.dfm}
procedure.TForm1 FormCreate ( :Sender ); TObject begin InstallHook ;
end;
( :Sender ); TObject procedure.TForm1 FormDestroy begin UninstallHook ;
end; . GetMsgProc ( code : ; Integer _ : wParam _; function TForm1
WPARAM :
)lParam : ; LPARAM
LRESULT
// Determinates if this window message is designated to our form or any of its sub-windows fu n ct i o n FindParent (AWnd : HWND ; co n stATarget, AException: HWND) : Boolean; begin Result := False ; while AWnd <> 0 do begin i f AWnd = AException t h e n Break; i f AWnd = ATarget t h e n begin Result := True ; Break ; end; AWnd : = AWnd (GetParent ); end; end; var Msg : PMsg ; begin :Msg = (_ PMsg
lParam );
// Is this a message for our form or its components (excluding the Memo)? i f (code= HC_ACTION ) a n d FindParent ( . Msg ,hwnd Handle , . Memo1) Handle t he n begin // If yes – then log the window message Memo1. Lines Add Msg . Format ( ('Wnd: %d, Message: %d, wParam: %d, lParam: [ . , %d' .HWnd , Msg, Msg.wParam , Msg . lParam ]) ); end; Result := end;
74
CallNextHookEx ( FHookHandle , _,
COMPONENTS DEVELOPERS
4
code _,
wParam );
message
lParam
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Correcting a bad API design (Continuation 10) procedure TForm1.InstallHook ; const GetMsgProcTemplate : RawByteString =
{$IFDEF CPUX86} ################### $ 5 5 $ 8 B $ E C $ 8 3 $ C 4 $ F 8 $ C 7 $ 4 5 $ F 8 $ 7 8 $ 5 6 $ 3 4 $ 1 2 $ 8 B $ 4 5 $ 1 0 $ 5 0 $ 8 B +$ 4 D ################### $ 0 C $ 8 B $ 5 5 $ 0 8 $ B 8 $ 2 1 $ 4 3 $ 6 5 $ 8 7 $ F F $ 5 5 $ F 8 $ 8 9 $ 4 5 $ F C $ 8 B $ 4 5 $ F C +$ 5 9 ##### $ 5 9 $ 5 D $ C 2 $ 0 C $ 0; 0
{$ENDIF} {$IFDEF CPUX64} ################### $5 5 $48 $ 83 $EC $ 30 ################### $4 8 $B8 $E F $CD $ AB ################### $ 8 7 $ 0 9 $ B A $ DC $ F E ############## $4 8 $89 $4 5 $28 $4 8
$48 $90 $8B $8B
$ 8B $ 78 $ 55 $ 45
$E C $56 $40 $28
$ 89 $ 34 $ 4C $ 48
$4 D $1 2 $8 B $8D
$ 40 $ 48 $ 45 $ 65
$4 8 $8 9 $4 8 $3 0
$ 89 $ 45 $ 4C $ 5D
$ 5 5 $ 4 8 $ 4 C $ 8 9 $ 4 5 +$ 5 0 $ 2 0 $ 4 8 $ B 9 $ 2 1 $ 4 3 +$ 6 5 $ 8 B $ 4 D $ 5 0 $ F F $ 5 5 +$ 2 0 ; $C 3
{$ENDIF} type TGetMsgProc = fu n ct i o (n code : var
Integer ;_
wParam :
WPARAM ;_
lParam : )LPARAM :
LRESULT
; o f ob ject
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 i f FHookHandle <> 0 t he n begin UnhookWindowsHookEx( FHookHandle); FHookHandle := 0;
end; i f Assigned(FHook ) t h e n begin DisposeTemplate( FHook); FHook := nil;
end; end; end.
This example sets a hook on the window messages. Every message that targets our form or any component on it, excluding Memo, will be logged in the Memo. In this example, we pass the user-argument as the first parameter, and use the register calling convention instead of stdcall. And so we can use the form method as a callback. There is no need to introduce a static class method adapter. But be careful: the compiler cannot check you here. If you make a mistake in the method's signature, you crash your application. That's all I wanted to say today. IMPORTANT NOTE The approach described in this article is a hack. This means that it is a crutch, a workaround, a way to get the code to work somehow. It is no excuse for writing "bad" API. If you have control over the callback-function prototypes - change them! Introduce the support for user-arguments.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
About the Author: Alexander Alexeev (Александр Алексеев ) Alexander started working with Delphi while still at university. Following graduation as the a Master Mathematics, he took several jobs in Delphiof world. Today he works for the EurekaLab s.a.s. company, where he develops and supports EurekaLog for Delphi/C++ Builder. Alex lives in the Russian Federation and maintains a personal blog, where he writes about Delphi. Some of his articles are available in English at eurekalog.blogspot.com. His favourite topics are debugging-related. He is one of our best authors making you understand how Programming works...
COMPONENTS DEVELOPERS
4
75
maXbox PURE CODE FOR OBJECT SCRIPTING
Features • • • • • • • • • • • •
mobile programming, no installation, administration or configuration needed easy to deploy with text code or byte code scripting big library of 430 examples and exercices debug, recompile and decompile function worldwide 28 tutorials with maxbox starter series, arduino and Blix also starts from stick or share on linux systems softpedia, d3000 and heise award EKON best COM, serial, html, xml, http, rest and ftp functions (indy sockets) more than 15150 functions in V3.9.9 contains maXcom, maXbase, maXtex, maXnet and maXbook Units of JEDI,CLX,VCL,PCRE,SysTools,Indy,TurboPower in Delphi VirtualMachine Units Explorer, WebServer, Browser and CryptoBox on Board
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 pr og ram Motion_HTTPServer_Arduino41_RGB_LED;
We have to name the game, means the program's name is above. This example requires two instant objects (you remember, all objects are converted and precompiled) from the classes: TIdCustomHTTPServer and TComPort so the second one is to establish a connection with the COM Ports to Arduino ( see below). TComPort by Dejan Crnila (http://sourceforge.net/projects/comport/) are Delphi/C++ Builder serial communications components. It is generally easy to use for basic serial communications, alternative to the TurboPower ASYNCPro. It includes 5 components: •
maXbox or Hex in the Box maXbox is a free scripter tool with an inbuilt Delphi engine of Pascal Script in in one exe (including several DLLs and helper files)! It is designed for teaching, develop, test and analyzing apps and algorithms and runs under Win and Linux (CLX) to set Delphi in a box without installation or administration. The tool is based on an educational program with examples and exercises (from biorhythm, form builder to how encryption works). Units are precompiled and objects invokable! With a 26 part tutorial for coders or say it Pure Code for the Pascal Community (PC^2). Get the Code
So let's get a real example, the box has a lot of it. The tool is split up into the toolbar across the top, the editor or code part in the centre and the output window at the bottom. Change that in the menu /view at our own style. We will start with an extract of a http-server demo, just to show you several objects and a form in maXbox. In maXbox you will start the web server example as a script, so the web server IS the script that starts the Indy objects, configuration from ini-file and a browser too; on board is also an add-on or more: Options/Add_ons/Easy_Browser/. • Before this starter code will work you will need to download maXbox from a website. It can be downloaded from http://sourceforge.net/projects/maxbox (you'll find the download to maxbox3.zip on the top of the page). •
Oncethat the you download hasthe finished, unzip theas file, making sure preserve folder structure it is. If you double-click maxbox3.exe the box opens a default demo program. Test it with F9 / F2 or press Compile and you should hear a sound. So far so good now we'll open the examples: 305_webserver_arduino3ibz_rgb_led.txt
TComPort, TComDataPacket, TComComboBox, TComRadioGroup and TComLed.
With these tools you can build serial communication apps easier and faster than ever. First we start with the web server and second we explain the COM port. After creating the object in line weIP. use first methods configure our server calling Port125 and The object makestoa bind connection with the Active method by passing a web server configuration. :125 = HTTPServer TIdCustomHTTPServer . ( ); Create self So the object HTTPServer has some methods and properties like Active you can find in the TIdCustomHTTPServer.pas unit or IdHTTPServer library. A library is a collection of code or classes, which you can include in your program or in maXbox already done. By storing your commonly used code in a library, you can reuse more code. Let's get back to our HTTP Create in line 125. In line 131 and 132 you see a Port and IP address configuration of a const in line 13, instead of IP you can also set a host name as parameter. 1 26 1 27 1 28 129 130 1 31 1 32 133 134 135 1 36
w i t h HTTPServer d o b e g i n i f Active t he n Free ; i fn o t Active t he nb e g i n bindings. Clear; bindings. Add; // 8080 . bindings [ ] .items : = 0 Port ; APORT bindings .items [] 0 .IP : = IPADDR; // 192.168.1.53' Active:= true ; onCommandGet @:= HTTPServerGet ; PrintF('Listening HTTP on %s:%d.' ,[Bindings[0].IP , Bindings [ ]0. Port]); e n d;
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
Figure 1: Code design with Hex in the Box
Although you can find plenty to complain about in this code, it s really not that bad. It s compact and simple and easy to understand. However, within this code it is easy to see the power of scripting because it's agile and high available but you can't hide the functionality. If a maXbox script or app is programmed with the default host standard, it is always started relative to the path where the maXbox3.exe as the host itself is: playMP3(ExePath +'examples\maxbox.mp3' ); ’
’
This problem might not be identified in the testing process, since the average user installs to the default drive of the archive and directory and testing might not include the option of changing the installation directory. By the way you find all info concerning a script or app in menu /Program/Information/…
So for example you want to play a song or refer to other external resources in a script, your external file will be found relative to ExePath(): E:\Program Files\maxbox\maxbox3\ ‚examples\maxbox.mp3' ExePath is a useful function where you always get the path of maXbox. If someone tries to start (install) the script or the app to or from a different drive for space or organizational reasons, it may fail to (install) or to run the script after installation. (You don't have to install maXbox or a script anyway, just unzip or copy the file. ) A solution might be an absolute path: myMemo.lines .saveToFile ('D:\data\examples\mymemo ); _tester.txt'
78
COMPONENTS DEVELOPERS
4
Figure 2: menu /Program/Information/…
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
maXbox
Coding with maXbox (Continuation 2) Another solution to prevent hard coded literals is a constant or the call of a user dialog. An indirect reference, such as a variable inside the program called 'FileName', could be expanded by accessing a "select browse for file" dialog window, and the program code would not have to be changed whwn the file is moved.
We go on with the boot loader and his functionality. maXbox and the script loader system has default folders which organize files logically on the hard disk. You can also copy all 6 folders and root files to a folder on your USB-stick that stores the same content in your maXbox installation folder and it will start from the stick!
( : self ); TObject procedure GetMediaData 23.05.2013 begin 23.05.2013 if PromptForFileName( selectFile, 'Me dia files (*.mp 3)|*.mp3|*.mpg) |*.mp ,g' ,''09.05.2013 09.05.2012 'Select your mX3 media file Directory' , 12.05.2013 'D:\kleiner2005\download' , False) 17.03.2013 then be gin 29.03.2013
// Display this full file/path value However it is advisable for programmers and developers not to fix the installation path of a program or hard code some resources, since the default installation path is different in different natural languages, and different computers may be configured differently. It is a common assumption that all computers running Win have the primary hard disk labelled as drive C:\, but this is not the case. As you will see the configuration of maXbox is possible with a boot loader script and a simple ini-file too. Extensions are possible with the Open Tools API and
a small CLI (Command Line Interface). Tutorials (for downloading to the harddisk) You can read or download tutorials 1 till 26 from the program: ž /Help/Tutorials/: from the Internet:
http://sourceforge.net/apps/ mediawiki/maxbox/ maXbox Tutorial 00 Function-Coding (Blix the Programmer) Tutorial 01 Procedural-Coding Tutorial 02 OO-Programming Tutorial 03 Modular Coding Tutorial 04 UML Use Case Coding Tutorial 05 Internet Coding Tutorial 06 Network Coding Tutorial 07 Game Graphics Coding Tutorial 08 Operating System Coding Tutorial 09 Database Coding Tutorial 10 Statistic Coding Tutorial 11 Forms Coding Tutorial 12 SQL DB Coding Tutorial 13 Crypto Coding Tutorial 14 Parallel Coding Tutorial 15 Serial RS232 Coding Tutorial 16 Event Driven Coding Tutorial 17 Web Server Coding Tutorial 18 Arduino System Coding Tutorial 18_3 Arduino RGB LED Coding Tutorial 19 WinCOM /Arduino Coding Tutorial 20 Regular Expressions RegEx Tutorial 21 Android Coding (coming 2014) Tutorial 22 Services Coding Tutorial 23 Real Time Systems Tutorial 24 Clean Code Tutorial 25 maXbox Configuration Tutorial 26 Socket Programming with TCP Tutorial 27 XML & TreeView (coming 2014) Tutorial 28 Closures (coming 2014) Tutorial 29 UML Scripting (coming 2014) Tutorial 30 Web of Things (coming 2014)
11.12.2007 11.11.2012 09.04.2013 28.11.2010 27.10.2005 07.11.2010 07.02.2013 21.10.2011 02.01.2009 11.05.2013 11.05.2013 12.05.2013 03.12.2012 12.05.2013 12.05.2013 21.04.2012 14.11.2005 10.12.2012 12.05.2013 11.10.2010
docs examples exercices crypt source web 97'370 bds_delphi.dci 254'464 dmath.dll dbxint30.dll 580'096 5'426 firstdemo3.txt 3'866 firstdemo3.uc 103'424 income.dll 138 maildef.ini 10'544 maxbootscript_.txt 59'060 maxbox.mp3 71'807 maxbox.png 11'887'616 maxbox3.exe 5'133'220 maxbox3clx 994 maxboxdef.ini 12'503 maxboxerrorlog.txt 42'773 maxboxnews.htm 2'309'571 maxbox_functions_all.pdf 9'533 maxdefine.inc 383'488 midas.dll 17'202 pas_includebox.inc 36'854 readmefirst_maxbox3.txt 135'168 TIFFRead.dll
When you start the box a boot script is loaded. Within the boot script, you can perform common source control such asand filesynchronization check in, check out, andcurrent of course change tasks, IDE settings of your version. This is a script where you can put all the global settings and styles of the IDE of maXbox, for example: with maxForm1 do begin caption := caption +'Boot Loader Script
maxbootscript.txt' ; color := clteal ; IntfNavigator1Click( self); . tbtnCompile := caption ; 'Compile!' .tbtnUsecase := caption ; 'UML UC' maxform1 . ShellStyle1Click ( )self ; . memo2.font:size = ; 16 Info1Click (self );
end; Writeln ( '+B OOT S C R I P T + ' BOOTSCRIPT )
' lo ade d'
When you want to see a complete copy of that file, look at: 07.02.2013
10'544 maxbootscript_.txt
When you delete the underscore in the filename to maxbootscript.txt the system performs next time when you load maXbox and presents you with a different view. This boot script results in the picture below for example. The trick of renaming the file has a simple explanation. The inifile default to load the boot script is YES so it can be easier to rename the file instead of change the ini-file to set to YES, cause of missing permissions, testing or so: BOOTSCRIPT=Y 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.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
79
maXbox
Coding with maXbox (Continuation 3)
<>
<>
Load Boot Script
bds_delphi.dci (templates)
<>
Actor HEX in the BOX
Parse Ini File
maxbootscript.txt
maxboxdef.ini
PRE PROCESSING
Compile Script maxboxdef.ini include file
<>
Run App
Byte Code Figure 3: Configuration Step by Step
80
COMPONENTS DEVELOPERS
4
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Coding with maXbox (Continuation 4) In this section we deal with multiple instances of maXbox and its creation. You can create multiple instances of the same app to execute multitask code, just type . For example, you can launch a new instance within a script of the box in response to some user action, allowing each script to perform the expected response. ExecuteShell (ExePath +'maxb ox3.exe' , '"' +ExePath +' e xa m p l e s \ '+script + ' ") '; S_ShellExecute( ExePath+'maxbox3.exe' , ExePath +'examples\' +script ,secmdopen );
There's no good way to one application (maXbox) with multiple scripts in launch it. Maybe with OLE Automation in that sense you open office programs (word, excel) or other external shell objects. CreateOleObject creates a single uninitialized object of the class specified by the ClassName parameter. ClassName specifies the string representation of the Class ID (CLSID). CreateOleObject is used to create an object of a
specified type when the CLSID is known and when the object is on a local or in-proc server. Only the objects that are not part of an aggregate are created using CreateOleObject. • Try the example of OLE Objects 318_excel_export3.TXT and the tutorial 19.
An external script or a least a second one could also be a test case to compare the behaviour. Each test case and test project is reusable and rerun able, and can be automated through the use of shell scripts or console commands.
maXbox
• An advantage of using S_ShellExecute or ShellExecute3 is the no wait condition.
In this article we show 4 steps to build a configuration: 1. First building block is the use of a boot script maxbootscript.txt. 2. Second we jump to the template file bds_delphi.dci 3. Third the Ini-file maxboxdef.ini environment and code settings. 4. Forth we set an include file in our script, like pas_includebox.inc to call external functions of a unit, it's very powerful, but tough to built.
Ok we have finished the boot script what about a template file. The code template file bds_delphi.dci stands for code completion templates. In the Code Editor, type an object, class, structure or a pattern name followed by to display the object. But it's not a full code completion where you get after the dot (.) a list of types, properties, methods, and events, if you are using the Delphi or C# languages. It's more a template copy of your building blocks. In the menu /Debug/Code Completion List you get the defaults, here is an extract: [cases|
case statement | Borland .EditOptions .Pascal] case | of
: ; : ; end; [try | tr y excep|t Borland .EditOptions .Pascal] try | except end;
Figure 4:
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
81
Coding with maXbox (Continuation 5)
maXbox
Figure 5: mX bootscript GUI Loaded
I prefer\; myform which represents or copies a form builder in your editor. • Useless to say you can add your own templates to the file. Many of the Code Editor features are also available when editing HTML and CSS files. Code Completion (CTRL+J) and syntax highlighting are available for HTML, XML and CSS files. • Most of the Open Tools API declarations reside also in that file bds_delphi.dci.(at the bottom) Note: Call the methods with maxForm1., e.g. :maxForm1.ShellStyle1Click (self ); A standard approach to break a running loop in a script or configuration is the well known KeyPress or IsKeyPressed function you can use and check: procedure LoopTest; •
begin Randomize;
REPEAT Writeln(intToStr (Random (* 2 5 6 2 5 6))); UNTIL isKeyPressed; //on memo2 output i f isKeypressed t he n writeln (Key has been pressed!'); end;
82
COMPONENTS DEVELOPERS
4
As you know the memo2 is the output window as the shell, so the keypress is related to memo2; by the way memo1 is the editor itself! Another function KeyPressed(VK: Integer): Boolean; returns True, if key VK has been pressed. Let's jump to the Ini-file. Many applications use ini files to store configuration information. Using ini files has the advantage that they can be used in cross-platform applications and they are easy to read and edit. The ini file format is still popular; many configuration files (such as Desktop or Persistence settings file) are in this format. This format is especially useful in cross-platform applications, where you can't always count on a system Registry for storing configuration information. I never was a friend of the Registry so you can also start maXbox from a stick. In maXbox code, TIniFile is the game of advantage. When you instantiate the TIniFile or TMemIniFile object, you pass the name of the ini file as a parameter to the constructor. If the file does not exist, it is automatically created. •
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Coding with maXbox (Continuation 6)
maXbox
You are then free to read values using the various read methods, such as ReadString, ReadDate, ReadInteger, or ReadBool. This is how we can read the ini file of maXbox: maxboxdef.ini procedure getMaxBoxIniShort; begin with TIniFile . (+ Create ExePath 'maxboxdef.ini' ) do try except_conf := ReadString ( 'F or , m' 'E XC EPTI ON,LOG' ); '' := execute_conf ( ReadString , 'F or, m' ) 'E ; XE CU TE SH EL L' '' := boot_conf ( ReadString , , m');'B OOTSCR IP T' '' 'F or := ( , , 'W );eb ' 'I PPOR T' 0 ip_port ReadInteger finally writeln (' i n i fi l e sy s d a t a 1 : '+except_conf + ' : '+ execute_conf ); writeln ( ' i n i fi l e sy s d a t a+2 : ' boot_conf + + ' : 'intToStr ( ip_port )); Free;
end; end;
This process is handled directly, through an object so each time it changes timestamp of the file also and not on demand. In other words TIniFile works directly with the ini file on disk while • TMemIniFile buffers all changes in memory and does not write them to disk until you call the UpdateFile method. Alternatively, if you want to read an entire section of the ini file, you can use the ReadSection method. Similarly, you can write values using methods such as WriteBool, WriteInteger, WriteDate, or WriteString. 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. The Ini File
As you already know the object we now step through the meaning of the ini file. On subsequent execution of maXbox, the ini values are read in when the form is created and written back out in the OnClose and other “in between” events. • In maXbox you can also start with read only mode (Options/Save before Compile), so nothing will be write on the disk.
//*** Definitions for maXbox mX3 ***
[FORM] LAST_FILE=E:\maxbox\maxbox3\examples\140_drive_typedemo.txt //10 files FONTSIZE=14 EXTENSION=txt SCREENX=1386 SCREENY=1077 MEMHEIGHT=350 PRINTFONT=Courier New LINENUMBERS=Y EXCEPTIONLOG=Y EXECUTESHELL=Y BOOTSCRIPT=Y MACRO=Y NAVIGATOR=Y MEMORYREPORT=Y
//save log files – menu Debug/Show Last Exceptions //prevents execution of ExecuteShell()/ExecuteCommand() //enabling load a boot script //put macros in your source header file //set the nav listbox at the right side of editor //shows memory report on closing maXbox
[WEB] IPPORT=8080 //internal webserver – ../Options/Add Ons/WebServer2 IPHOST=192.168.1.53 ROOTCERT='filepathY' //for use of HTTPS and certificates… SCERT='filepathY' RSAKEY='filepathY' VERSIONCHECK=Y //checks over web the version
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
83
Coding with maXbox (Continuation 7)
maXbox
• Now let's take a look at the code of the memory report in the project file: Application .CreateForm (TMaxForm1 , MaxForm1 ); . i f maxform1 = STATMemoryReport true t h e nReportMemoryLeaksOnShutdown := ; true We name it, means the ini-file sets the STATMemoryReport true or false. • This example requires two objects from the classes: TMaxForm1 and TMemoryManager of mX4 so the second one is from the well known VCL Lib. This re includes a new memory manager that significantly improves start-up time, runtime speed, and hyper threading performance. If the ini-file doesn't exist, renamed or damaged, maXbox produces a new one with the default values. Test it by copy the maXbox3.exe in an empty directory; just says that a template file is missing but it starts and will run! If you only want to “install” a new maXbox with file or directory names, be sure the ini-file will not be overwritten by unpacking the zip (so let's make a copy before). Maybe you just know that by starting the maXbox it checks on the internet the last version if the ini-file allows this
VERSIONCHECK=Y.
Figure 6: Another ini file setting runs
84
COMPONENTS DEVELOPERS
4
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
maXbox
Coding with maXbox (Continuation 8) 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 no v alid file is fo und, the f ollowing message will be di splayed:
>>> Fault : Unable to find file '..\maxbox3\examples\305_eliza_engined.INC' used from 'E:\maxbox\maxbox3\maxbootscript_.txt'.
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 +' m a x b o x3 . e xe ' ,ExePath + ' e xa m p l e s\ ' +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.
The game of Life (see Examples)
Figure 7: Every teach and game programmer's portfolio should include two code standards, John Conway's Game of Life and Fractals.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
85
maXbox
Coding with maXbox (Continuation 9)
Figure 7: More of Script Configuration At pre last is to say you can use DLL's too. Selecting this type of application sets up your project as a DLL dependency, with the exported methods expected by the Library, e.g.: 43: procedure SetErrCode ( ErrCode : Integer ); external '[email protected]'; { Sets error code } function MyMessageBeep (para : integer): byte ; 1 0:
•
e x t e r n a l '[email protected] stdcall' ;
Test: How can we show the run dialog?
procedure TForm1_FormCreateShowRunDialog; var ShellApplication : Variant ; begin : = ( ShellApplication CreateOleObject);'Shell.Application' ShellApplication. FileRun;
end;
Check your system environment with GetEnvironmentString: SaveString (ExePath +'\Examples\envinfo.txt' , GetEnvironmentString ); ( OpenFile ExePath +'\Examples\envinfo.txt' );
86
COMPONENTS DEVELOPERS
4
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
maXbox
Coding with maXbox (Continuation 10)
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 * Date
: Demonstrates the functions of macros in header
: 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 : system demo for mX3, enhanced with macros, #locs
*********************************************
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 #, id ate , i host # , i path # , i ile # , if , i # head sign # teach # 10185: getUserNameWin 1 0 1 8 6: SearchAndCopy ( memo1 . lines, '#name' , , ); 1 1 1 0 1 8 7: SearchAndCopy ( memo1 . lines, '#date' , datetimetoStr ( )now , ); 1 1 1 0 1 8 8: SearchAndCopy ( memo1 . lines, '#host' , getComputernameWin , ); 1 1 1 0 1 8 9: SearchAndCopy ( memo1 . lines, '#path' , fpath , ); 1 1 ( memo1 . lines, '#file' , fname , ); 1 1 1 0 1 9 0: SearchAndCopy ( memo1 . lines , , ( ), ); 1 0 1 9 1: SearchAndCopy '#locs' intToStr getCodeEnd 11 1 0 1 9 2: SearchAndCopy ( memo1 . lines, '#perf' , perftime , ); 1 1 10193: SearchAndCopy ( memo1 . lines , , ' # he(a d 'Format ' % s: , % s: % s % s ' , getComputernameWin , datetimetoStr ( ),now Act_Filename ]), ); 1 0 1 9 4:[ getUserNameWin 11 (. , , ' # si g(n 'Format ' % s,: % s: % s ' 10195: SearchAndCopy memo1 lines [ getUserNameWin , getComputernameWin , (datetimetoStr )]), ); now 11 10196: SearchAndCopy ( . memo1, lines , (' # t e ch'Format ' p e r f : % s t hr,e a d s: % d % s % s ' [ , perftime numprocessthreads , ( getIPAddress getComputerNameWin ), ( )]) timetoStr , ); time
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
11
87
@max :149
maXbox
Coding with maXbox (Continuation 11)
<>
Actor HEX in the BOX
<>
<>
bds_delphi.dci (templates)
Load Boot Script Parse Ini File
Macro Macro
Macro Actor
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) ’#locs’, intToStr(getCodeEnd), 11)
maxboxdef.ini
’#perf’,perftime, internal ’#head’, Format(’%S: %S: func %S: 11) %S:)
Compile Script Some DLL or Lib
PRE PROCESSING include file
<>
Run App Byte Code
•
88
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.
COMPONENTS DEVELOPERS
4
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
maXbox
Coding with maXbox (Continuation 12) 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; //memo1.Gutter.ShowLineNumbers:= true;
//---> reflection to box! //---> 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: extractFileName( maxform1. appname if =) '370_synedit.txt' then begin Options+:[= eoShowSpecialChars ]; ActiveLineColor := clyellow ; maxform1 .tbtnUseCase caption; 'SynScriptUC' . := maxform1 . ShellStyle1Click( self) end else ActiveLineColor := clgreen ;
Be aware of the internal representation of SynEdit TSynMemo at maXbox editor is always memo1. and the console output as you know memo2., so don't name an object var memo1 otherwise your script will show or jump to unexpected content. More secure is the namespace with maxform1 maxform1 .memo1 .font . size := ; . font . size := ; 1 4 instead ofmemo1 14 •
with . CL AddClassN CL (FindClass . 'TFo ( rm ' 10230: ( ' m e m o 2,' ' TM e,m o ' iptrw ); , o' iptrw ); 10231:( ' me m o1,' 'TS yn M em
'T ),MaxF or m1 ' do ) begin
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 maxbox_functions_all. pdf 10197: //-------------------------------------------------------------------------10198: //**************mX4 Public Tools API ******************* 10199: //----------------------------------------------------------------
//-------------------------------------------------------------------------10702: 10703: 10704: 10705: 10706:
file : unit uPSI_fMain.pas; OTAP Open Tools API Catalog // Those functions concern the editor and pre-processor, all of the IDE Example: Call it with maxform1.Info1Click(self) Note: Call all Methods with maxForm1., e.g.:
maxForm1.ShellStyle1Click(self); You can also enhance the API with functions like the example above GetEnvironmentString: fu n c t i o n getEnvironmentString2: st r i n; g var list: TStringList ; i : Integer ; begin
:= list
TStringList . ; Create
try GetEnvironmentVars ( , ); list False . 0 t o list Count 1 do [ ]i+ #13#10 ; result := result + list finally list.Free ; end; end;
:= f o r i
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.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
89
Coding with maXbox (Continuation 13)
90
COMPONENTS DEVELOPERS
4
maXbox
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
maXbox
Coding with maXbox (Continuation 14) Feedback: [email protected] Links of maXbox and DelphiWebStart:
About the Author: Max Kleiner
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 security, navigation and simulators that need the power http://sourceforge.net/projects/maxbox 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.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
91
Ray Konopka Interview with the famous writer of Developing Custom Delphi Components
Editor:
When I first met Ray I was impressed because of his age. He seems so young, knowing that he is one of the very early adoptee’s of Delphi. Through the time that I spent with him I learned his secret: he is enormously precise, always on the job, concentrated and very disciplined. Given his enormous knowledge he surely is a man to learn from...
I would like to ask you about how you got started with Pascal? You were one of the very early adapters I suppose?
As you can imagine, most of my work there was in C. However, I continued to use Turbo Pascal at home and got into object-oriented programming with Turbo Pascal 5. For the next few years, I bounced around a few jobs trying to find a good fit for me. During this time, I also started reading a magazine called PC Techniques. I really liked this magazine because it covered a lot of topics and it had articles on Turbo Pascal, which were pretty hard to find. At this time, most my Pascal work was isolated to my own time and not involved with my job. I decided to write an article for the magazine and submitted it. My first published article was on creating a World Coordinate System in Turbo Pascal. That was in 1993...
Ray Konopka: My very first experience with Pascal was Berkley Software
Distribution Pascal atI llinois Benedictine College (now called Benedictine University) in 1986 in what I refer to as my first "real" programming course. I had a programming course in high school that used AppleSoft Basic on Apple IIe computers, but there was a real elegance to Pascal that I really liked. Soon after, I started working with Turbo Pascal 3. Editor:
How did you start, you met people from Borland by that time? Ray Konopka: Actually, meeting people from Borland came much later. During my time at University, I continued to use Turbo Pascal and really fell in love with the language and the product. I even bought a copy for myself so I could work with it at home. During my 3rd and 4th years at university I had the opportunity to work at Argonne National Laborator in the Physics department. I used Turbo Pascal to write software that
communicated with various pieces of scientific equipment to capture data for various experiments. It was during this time that I switched from TurboPascal 3 to TurboPascal 4. It was my first real world experience programming outside of the classroom.... I graduated from University in 1989, where I majored in Computer Science and Mathematics, with a minor in Physics. The following year I attended Northwestern University on a fellowship and 9 months later complete my Masters degree in Computer Science. I continued to use Turbo Pascal, and some other languages throughout this time. After graduating from Northwestern, I went to work for AT&T Bell Labs.
I submitted a couple more articles and then Jeff Duntemann, publisher of PC Techniques, contacted me to see if I was interested in writing a column for the magazine. I thought this was really cool, but not as cool as the name that Jeff came up with for the column. The column was called Blazing Pascal. How cool is that! It was through the magazine and column that I first got involved in what was to become Delphi. I was very fortunate to work with very early versions of the product. I was even able to write articles about Delphi in 1994, about 6 months before Delphi even shipped. My first column that covered Delphi was published in September 1994. Shortly after that, Borland setup a Delphi 95 preview day at their Scotts Valley campus. I was not going to miss that... It was at that preview event that I first met many of the people behind Delphi. It was a great experience for me, especially when I had a chance to meet Anders Hejlsberg. Imagine my surprise when after I introduced myself to Anders, he responds, "Oh, yes, I know. I've been reading your articles." It was an experience I'll never forget.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
93
Interview with Ray Konopka (continuation 1) Editor:
Jeff Duntermaan was also an author for Delphi books? Ray Konopka: Yes, he was. In fact, he wrote the Object-Oriented Programming Guide that shipped with Turbo Pascal. I had talked with and emailed Jeff for quite a while before we had a chance to meet. We finally met at the Borland Conference in San Diego in 1995. Editor:
You also wrote a very famous book. Could you tell something about that? Will you try to do so again? I sometimes regert that it isn’t available anymore... Ray Konopka: Very early on in my experience with Delphi, I saw the potential that the VCL provided. It was Jeff that suggested I write a book about Delphi, even before Delphi shipped, and I said we should do it on how to build custom components. It was also at this time, that I decided to start working for myself as a Delphi consultant. I started Raize Software Solutions ( now called Raize Software) on February 7, 1995, a week before Delphi 1 officially launched. I consulted during the day and worked on the book at nights. One of my primary goals for the book was to come up with real-world practical examples. (I really do not like contrived examples with nondescript names like Foo and Bar. )... The complete title of the book was called Developing Custom Delphi Components. Editor:
I have the first and the second edition...
I've been working with it since its inclusion into Delphi, and it is very flexible and has tremendous potential. But it is also very different from the VCL. When FMX was first released for desktop development, it was very hard to justify using it unless you really needed the enhanced display functionality it offered, or if you really needed to go to Mac OSX. Editor:
So whenn do you start writing articles about this? There is a tremendous need for this... Ray Konopka:
I know, I know I've been presenting sessions on FM for quite some time, so some material is out there, but those are not the same as an article. One thing that I've been working on is a redesign of the Raize Software website. My plan is to have a formal blog and other resources where I can provide some of this information to developers. I believe this approach will be easier for me to manage given my time constraints and also provided developers with additional resources for Delphi development and also provide developers... Editor:
Maybe you could help explaining what firemomkey really is and what it means. Ray Konopka: Actually, that is something that I focus on in the first part of my Creating Custom FM Controls sessions. A developer has to have an understanding of what the framework provides and how it works before one can even think of extending it.
Ray Konopka: FireMonkey is very interesting. Excellent. The first edition Editor: I've been working with it since its inclusion was published in 1995 and Maybe even explain what a into Delphi, and it is very flexible and has the second edition titled framework itself means. tremendous potential. A lot of people have vague Developing Custom Delphi 3 But it is also very different from the VCL. ideas about that... Components came out in 1997. You can still find a Ray Konopka: copy or two on eBay every once and a while. It is also I understand. Although I do like to write things that are possible to get a PDF Edition of the second book. What is more applied rather than simply provide a high level really cool is that all of the material covered in the book explanation. still applies to developing Delphi components today. But, I do understand your point. Certainly there are more capabilities and features in the VCL and FMX, but the principles covered in the book still apply. Of course, this begs the question of writing another Editor:
book.
I am always trying to attract new people towards Pascal. Do you have any ideas about that?
Editor:
Ray Konopka: The biggest hurdle is the incorrect perception that Pascal is an old language. Unfortunately, that is a hard hurdle to get over because even if you look at 3 of the most popular implementations of Pascal: Delphi, Lazarus, FreePascal, only one is easily connected to Pascal. So someone may have heard about Delphi, but may not know its relationship to Pascal. However, to make real in-roads, developers have to be able to see that using a particular tool will allow them to become successful.
Maybe you could do something about Firemonkey? Ray Konopka: The biggest factor in writing another book is time. Back when I wrote the first two books, Raize Software was just starting out, we didn't even have a product out when the first book was written. Now, things are just so much busier. What I have tried to do in place of writing a new book is to cover the new topics in presentations and articles. FireMonkey is very interesting.
94
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Interview with Ray Konopka (continuation 2) Ray Konopka: What I mean by this is that the days of using a single language are long gone. Developers will select the tool that allows them to deliver a particular solution. Just look at how popular Objective C has become. It's an odd little language, and I much prefer Delphi and Pascal, but if you wanted to target iPhones and iPads, you needed to learn Objective C. Now, that Delphi XE4 and XE5 are out with support for mobile development, that may change. If developers see value in a tool, they will learn the language. Editor:
We need - easy to do exaples. I think espacially for younger people. Ray Konopka: Certainly, but again, they can't be contrived. On the other hand, you cannot make them too complicated. This is the problem that Apple has with their documentation. There are lots of examples, but they are overly complex and it is very difficult to extract the one piece of information you need. The other problem you have, and this applies to the FM Framework, is that the examples need to change as the framework changes. Think about the Generics presentation that I presented at Be-Delphi this year. I could have come up with contrived names like TTest or TFoo but that gives no context for the attendee. On the other hand, I did not want to create an overly
complex example that overloads the attendee with nonrelevant information. Finding that balance is key to a good presentation or article.And no more DB Grids on the side of a 3D rotating cube. Editor:
Editor:
That is a real problem. I would love to hear an answer from our readers. I was thinking of starters and doing the VCL. Because we are starting this teaching project for schools: PEP (Pascal Ecducation Program).
The problem with FM is, that its still evolving... So you opt for the VCL? Ray Konopka: If you already have the VCL ones, done, then yes, I would go with that and get some feedback. Editor:
Problem is there are about 550 to be Back to youre explainations: you haddone... the chance to do buisnes with the Walt Disney Company. How did that start? And what is your job there? Advisor or Developer or Creator? Ray Konopka: I started consulting with Walt Disney Parks and Resorts back in 2002. Initially, I was brought in for Delphi training, but shortly after that, I became involved in the development of the FASTPASS system, which is used in all of the Disney Parks world-wide. My work on FASTPASS led to other development projects, such as the “Free On Your Birthday” promotion that ran at Disneyland and Walt Disney World in 2009, and the “Give a Day, Get a Disney Day” promotion that ran in 2010. The success of these projects led to even more work, but as a consultant there were restrictions on just how much I could take on. So, in October of 2011, I switched from consultant to part-time cast member, which is what employees of The Walt Disney Company are called. And yes, I even have a Disneyland name badge. To be precise I am a Sr Application Developer and I continue to work on the architecture and development of the FASTPASS system as well as several other projects, including many new mobile projects that are used by Operations Cast Members in the parks.
I loved your presentation, because of its simpliticity that you showd. I told you about our Help Component project. We will very soon ship the first free examples - so people can become known to it - so they can try out and give their opinion. I was asked to do this for Firemonky at first. We actually already started on the VCL. What would you suggest to start with?
About your component book: you think its still a good book for starters in the field? I was wondering if the book might still be worth to be reprinted?
Ray Konopka: I suppose that would depend on what type of apps the people testing it out are building. The VCL is certainly a safe bet as there is still a lot of
Ray Konopka: The first few chapters in the book are certainly useful for any Delphi developer, but the book as a whole does focus on a specific kind of development, namely reusable component
Editor:
development being done with the VCL. But if your target audience is doing more mobile development, then FM might make sense.
building. As a result, it’s a book that is great to have in your toolbox, but not necessarily the best choice for a developer just trying to learn Delphi. Marco Cantu’s written several editions of his Mastering Delphi book that would be better Editor: suited for that. However, one of the points that I make early We already started with the first 50 components. in the book is that there is a lot of value in gaining a better understanding of how Delphi and the VCL is organized and Ray Konopka: You also need to consider which version of FM you want to architected. So while a developer may not have a specific need to develop their own custom component, support. Are you going to suppor them all? understanding what all is involved will make the reader a Or, just the most recent? more effective Delphi developer. That will dictate which version of Delphi is required,
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
95
Interview with Ray Konopka (continuation 3) Editor:
How did your own company start? Raize Components came about as a direct result from
feedback I received from the first book. Shortly after it was published, I started to receive quite a few emails from developers asking if it was okay for them to use the components that I presented in the books in their own applications. You will recall that I specifically wanted to create real-world practical examples in the book. From the responses I got, I met my goal. With this initial demand in place, I used the book's component exapmles as a foundation, extended those
Free Tools http://www.raize.com/DevTools/FreeTools.asp BDS/Delphi Icons Raize BDS Icons is an icon library that contains a set of icons for the various file types used by the BDS and earlier versions of Delphi. The icons in this new library are more consistent in their design and show a direct association with the BDS/Delphi. IDE Palette Menu Are you tired of scrolling through the pages of the
components to provide even more functionality and added Borland Developer Studio, Delphi, or C++Builder 24 new components it. This became Raize Components for component palettes looking for the right page? If so, then download and try out the Raize Palette Delphi version 1.0. Menu expert, which when installed adds a new The product has grown a lot since that initial release. menu to the selected IDE that provides instant There are now more than 125 controls in the access to any page on the component palette. library and it is loaded with features and capabilities. CodeSite also has its roots in some material that was Raize Font presented in the book, and was an internal tool for quite a The Raize Font is a clean, crisp, fixed-pitched sans while before it became a product. CodeSite was developed serif screen font that is much easier to read than to help debug Raize Components. the fixed pitched fonts that come with Windows. Back in the early days of Delphi, custom controls were Ideally suited for programming, scripting, html added to the IDE by rebuilding the component librarywriting, etc., the Raize Font can be used in any IDE packages were not introduced until Delphi 3. or text editor. As a result, if your custom control had an issue or crash, it would take all of Delphi down and so it was critical to have a system in place to log information about how a custom control is working (especially at design-time). CodeSite is available in two editions: CodeSite Express This is how CodeSite started. and CodeSite Studio. The Express edition includes core But it wasn't until I showed CodeSite to Mark Miller logging functionality but does not include the full range of
then of Eagle Software, now with Developer Express and he (used to to track down issues he was having with)his CodeRush product that it became clear that CCodeSite clearly had value as a product of its own. CodeSite too has evolved since its srcinal version. It has grown from just a debugging tool to a full featured application logging system. And starting with RAD Studio XE, CodeSite Express has been included "in the box". CodeSite Express is a fully functional version of CodeSite that provides basic logging functionality, while the CodeSite Studio edition provides more powerful features and services. A list of the differences between the two editions can be found the colum alongside and http://www.raize.com/DevTools/CodeSite/ Editions.asp
functionality included in CodeSite Studio. • CodeSite Studio includes these additional features and capabilities: • Using TraceMethod to record both an EnterMethod and ExitMethod message with a single statement • Recording time durations using a built-in highprecision timer • Remote Destinations (i.e. transporting CodeSite messages to a remote machine) • Have logging classes directly connect to a remote CodeSite Dispatcher • Special event in logging classes to hook into logging process (VCL: OnSendMsg; .NET: Sending) • Sending Color, Point, Size, Rectangle structures • Sending Bitmaps, Icons, Images, Screen Shots • Sending Collections • Sending Controls, Parents, and Window Handles (WinForms) Editor • Sending Custom Data and the ICodeSiteCustomData Yes I know this is really a great tool. interface I know developers that would want re-event it if they didnt • Sending Text Files, Files, and Streams have this tool. • SendIf methods Thank you so much Ray for your explanations and help • Sending System Info, Memory Status, and Stack Trace • Sending Xml Data and Xml Files With respect to Raize Software, there is a lot of • ExitMethodCollapse method information on our website http://www.raize.com. • Event Log Methods: LogError, LogEvent, LogWarning As noted earlier, I started the company • Writing values to the CodeSite Scratch Pad back in 1995 with the specific intent of providing • .NET Configuration File Support tools and services to the Delphi community. • CodeSite Express is currently included in Embarcadero's RAD Studio XE, XE2, XE3, and XE4.
You can find a nice overview of each of the developer tools that we offer on the following page: http://www.raize.com/DevTools/Products.asp
96
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Attention to detail is our passion
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 for us because we needed it for our Leap Motion Development. It was so much interesting that we decided to publish the basic Delphi code and handling... This article describes a Delphi project for bitmap rotation. It does it in three different qualities: Simple, Good and Excellent Result for the Bitmap.
Rotation takes place between a source and a destination bitmap. In coarse mode, the source bitmap is scanned pixel by pixel and projected on the destination bitmap. Therefore, not every pixel of the destination bitmap may be covered. In medium mode, the pixels of the destination bitmap are scanned and their value is loaded from the source bitmap. This insures that all pixels of the destination bitmap are covered. 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 - rotation_unit : procedures for bitmap rotation - clock_unit : time measurement procedures
Parts may cover different pixels in the source map, the proportions are summed for the final color of the destination pixel.
Exerciser
Programming
The form has buttons for loading and saving bitmaps. Also 3 modes of rotation are selectable
The programmer has to create both the source and the destination bitmap. Before a rotation may take place, the rotation_unit has to be informed about the names of the bitmaps. This is done by a call to
- coarse: fast but less accurate - 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. Do not forget to call the setmaps procedure after loading an image from a file into the source map. This insures the proper 32 bit format and dimensions of the destination map. Rotation theory (medium mode) Picture below shows the coordinate system relative to the bitmaps:
Figure 1 / 2: Little info
98
Figure 3: Rotation theorie
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.
Figure 4: Basic position
The destination map always is a square. Also, width and height are odd. The maps are divided into 4 quadrants 1 : right bottom 2 : left bottom 3 : left top 4 : right top Pixel scanning of the destination map takes place horizontally from the center outward. For quadrants 1 and 4, a row is scanned left to right starting at a certain y position. For quadrants 2 and 3, a row is scanned right to left starting at a y position. So, for a certain rotated position (x,y) in the coordinate system, the coordinates of the srcinal position have to be calculated. Then these positions are used to calculate the corresponding pixels in the source and destination maps. Let's observe a rotation of 30 degrees (clockwise) using the above bitmaps
Figure 6: Rotation calculation
in the above figure, (x',y') is the corresponding source map position which provides the color for (x,y) on the destination map. Rotation of the horizontal vector results in OD. Rotation of the vertical vector results in OC. OD is the addition of (horizontal) vector OB and (vertical) vector BD. OC is the addition of (horizontal) vector OA and (vertical) vector AC. Addition of the horizontal vectors make x', addition of the vertical vectors make y'. In the source code: (x,y) are the coordinates on the destination map xtx = OB xty = BD ytx = OA yty = AC xty means : x to y, the contribution of the rotated x vector to the final y vector. Now, for quadrant 1 : tx = xtx + ytx ty = - xty + yty
Figure 5: Rotation of 30 degrees
For the other quadrants, the signs of xtx, ytx, xty, yty may change.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
99
Programming Bitmap Rotation (Continuation 2) Addressing the pixels in the bitmaps
Of course, Delphi property pixels[x,y] may be used to change pixelvalues. However, this way is incredibly slow. Also, the scanline[y] property, which returns the memory pointer of the first pixel in row y, is very slow. Better is to calculate the pointer to a certain pixel. Because the bitmaps are in 32 bit format we first define type PDW = ^dword; PDW is a pointer to a 32 bit unsigned variable. Address calculations are done with variables in dword ( = cardinal) format. First, the pointer to pixel [0,0] must be obtained. For this, scanline[0] is used by the setmaps procedure. scanline[1] returns the pointer to pixel [0,1]. scanline[1] - scanline[0] gives the pointer difference between two rows. (note: row 1 pointer is smaller then the row 0 pointer). In the rotation procedure
Also an adjustment must be made to position the center of a bitmap over (0,0), the coordinate system srcin. scx is the center x pixel of the source map scy is the center y pixel of the source map dcxy is the x and y center of the destination map Now, pixel (x,y) of the source map is addressed by trunctx := trunc(tx); //tx is floating point format truncty := trunc(ty); //... ttx := scx + trunctx; //add center tty := scy + truncty; PS := PSBM - tty*Slinestep + (ttx shl 2); //pointer to source pixel pix := PDW(PS)^; pix receives the color value of the source map pixel. For quadrant 1, the pointer to the destination map is Ybase1 := PDBM - (dcxy + y)*Dlinestep; PD := Ybase1 + ((dcxy+x) shl 2); PDW(PD)^ := pix; 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
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
101
Introduction to MVVM and the Caliburn Micro for Delphi framework By Jeroen Pluimers starter
expert
Delph XE5
Introduction to MVVM The Model View ViewModel (MVVM) is an architectural pattern used in software engineering that srcinated from Microsoft as a specialization of the Presentation Model design pattern introduced by Martin Fowler MVVM in Delphi using the „Caliburn Micro for Delphi framework“.
A bit of background on Model, View and View Model MVVM is one of the ways to help split parts of your application into three layers. Doing so makes it easier to "organize the mess", allows you to unit test not only the 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 with MVVM from scratch. You can apply the ideas from MVVM into existing applications as well and evolve them 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. My encounter with MVVM was in the .NET and later in the Delphi world with a self written framework similar to what Malcolm Grooves presented at CodeRage 7. Back then I wasn't too impressed with what I achieved. Even though I was used to naming conventions, most of the things were still manual labour. Too much tedious manual labour. Later I bumped into Caliburn for .NET , but at that 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
It all comes down to Separation of Concerns: Cutting business logic away from your UI Swapping your UI (VCL, FireMonkey, Mobile, Web, …) Making it easier to test user interaction without a View layer By pushing tests from a View to automated tests, you move them from the time consuming and labour intensive acceptance test or end-to-end test phases into the automated unit test phase. This way it becomes much easier and cheaper to test the user interaction logic from the View Model. (The image was made thanks to Jonas Bandi ) S S E N I S U B
G N I C A F
Y G O L O N H C E T
G N I C A F
TESTS
UNIT TESTS
INTEGRATION TESTS
END TO END TESTS
GRANUARITY / SCOPE
Binding the 3rd object
The question is: how do you bind View, Model and the 3rd object? That highly depends on what kind (or even flavour) of 3rd object architecture you use: MVC, MVP, MVVM, et cetera. A few of them are here: MODEL
VIEW
PRESENTER
happy that it was being revamped into something very much alike Caliburn Micro,which was the successor of Caliburn. Pieces of the puzzle started falling into place when I recognized how easily you could integrate it with existing applications. Before explaining more about MVVM, let’s take a step back and look at the 3rd object.
1
The 3rd object
2
(3rd object images thanks to Paul Stovell's 3rd object article) All too often, applications - Delphi or otherwise are "structured" like these:
ACCEPTANCE
MODEL
VIEW
VIEW MODEL
MODEL
VIEW
VIEW = MODEL
or, if you are lucky, like:
VIEW MODEL
3
MODEL
VIEW
Adding a 3rd object makes sense to make things less monolithic and more maintainable:
MODEL
VIEW
VIEW MODEL
4
MODEL
VIEW
MODEL
VIEW
THIRD OBJECT
5 102
COMPONENTS DEVELOPERS
4
VIEW MODEL
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, and the View Model to the Model. Updates come from the Model to the View Model an d from the View Model to the View. There is no binding between the View and Model at all. This setup allows you to change any of the 3 layers and be very flexible. The traditional 3rd layer in Delphi is to separate user interface into forms, business logic into data modules and your data into some form of storage. This has worked for 15 years, but still needs a lot of plumbing and it is hard to change the UI as the binding is usually tied to the VCL platform.
with these class and sequence diagrams:
MVVM: the View Model
When starting with MVVM, the term View Model wasn't making much sense to me at first. I was missing the steering layer from the controller or presenter in MVC and MVP. And I'm not the only one, as you can see from this MVVM Wikipedia excerpt: The term "View model" is a major cause of confusion in understanding the pattern when compared to the more widely implemented MVC or MVP patterns. The role of the controller or presenter of the other patterns has been substituted with the framework binder (e.g., XAML) and view model as mediator and/or converter of the model to the binder.
In MVVM the steering role is taken over by the framework, and the View Model focuses a lot more on (testable!) logic than the controller in MVC or the presenter in MVP. MVVM favours these concepts: • Decoupling • Composition over inheritance • Test driven development • Patterns, conventions, ...
MVVM srcins: XP, Agile and the Presentation Model
The srcins from MVVM go far back to the Agile software mantra that nowadays everybody advocates but actually evolved in 2001 with the Agile Manifesto that was based on the eXtreme Programming (aka XP) from the mid 90s of last century.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
103
Steps in MindScape AppView Step - By - Step (Introduction 2) Back in 2004, Martin Fowler published his famous Presentation Model article presenting a simple UI: This is very much like the MVVM, with the exception that in MVVM the framework will take care of all the binding. (Martin Fowler kindly gave permission to use his pictures and the below book cover and portrait). Refactoring
Back in 1999 - in the middle of the XP era Martin Fowlerwrote a great book titled Refactoring: Improving the Design of Existing Code . It influenced the way we write software until today even in Delphi. It is so current that in 2012 it appeared in a special Kindle edition . The book has a catalog of simple recipes that describe how to improve certain bad coding patterns into better code. Patterns like Extract Method or Encapsulate Field are essential to how we develop code and give us a set of conventions on how to do just that. MVVM is all about patterns and conventions, so let’s look at another important book, but now at the start of the Agile era.
Why this MVVM article? MVVM combines refactoring, patterns, conventions and much more into a way of developing software that is maintanable and testable in a way that makes the best use of modern language features without the need of the developer to master each and all of those features in great detail. There are two reasons I started advocating using MVVM with Delphi. The primary one is that there are exciting times ahead for Delphi developers. With added functionality like generics, attributes and a truckload of RTTI, new (and hopefully old!) platforms, lots of new possibilities - including MVVM - are already there, or on the horizon. Another reason is that these open source projects can use your help. Simple help like just using it and giving feedback can tremendously move them forward. So here is my favourite shortlist of projects you could contribute to:
• Spring4D • DSharp (including the Caliburn feature branch on which this article is based) • DUnit • DUnitX and DelphiMocks • DelphiSpec • The Delphi JEDI projects (including JCL and JVCL) • GExperts 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 srcin. 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 Delph i is in alpha stage. It is hosted at the DSharp repository from Stefan Glienke .
Patterns
In 2004, the - instantly famous - Gang of Four(abbreviated to GoF) published the book Design Patterns: Elements of Reusable Object-Oriented Software. The book is another catalog, this time on patterns, with a common vocabulary about recipes for developing new code. The Gang of Four consists of Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides. Martin Fowler on the GoF book in relation to the 3rd object: „In my view the Gang of Four is the best book ever written on object-oriented design - possibly of any style of design“. This book has been enormously influential. The 3rd object is “just” a (relatively) new way of using patterns.
104
COMPONENTS DEVELOPERS
4
Stefan has done most of the core DSharp work and a lot of Spring4D work ( both DSharp and Caliburn depend on Spring4D). Most of the Caliburn specific work has been done by Marko Vončina. Internally, all these frameworks heavily depend on these Delphi features: interfaces - attributes - generics - RTTI They expose many classes and interfaces (attributes are based on classes). Applications based on Caliburn will use interfaces and attributes a lot. Make sure you read the Coding in Delphi book by Nick Hodges if you feel unfamiliar with them. Actually: just read the book anyway. I learned a lot while reading it!
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Steps in MindScape AppView Step - By - Step (Continuation 1) The goal is to write an application using MVVM that:
is based on patterns and conventions by using RTTI - is driven by the View Model n ( ot the View) - has the user interaction logic in the Model View - can have the View Model - be unit tested in an early stage - has the data storage in a Model - can have the Model - be unit tested in an early stage as well runs in Delphi XE or higher builds warning and hint free. During the steps you will see some failures that will be fixed in subsequent steps.That is deliberate:it is nice having working demos but in practice you will bump into things, so it is good to see the things you might bump into upfront and how easy it is to solve them. The projects are based on the Mindscape HQ MVVM demos that use the Caliburn Microfor .NET framework. Though Delphi is different than .NET, the ideas in the framework are kept the same as much as possible. The demos will teach you where things differ most. So the Delphi application will eventually look similar to this Caliburn Micro for .NET based WPF application: it can add, double and increment, all from a View Model automagically updating the View. In fact the Delphi application will also add a Model that is persistent in an INI file!
VCL project Name the form AppView, the form unit AppViewForm and clean up the uses list, private and public sections:
unit AppViewForm; interface uses Forms; type TAppView = class(TForm ) end; var AppView: TAppView;
implementation
{$R *.dfm} end.
Name the application MindScape_AppViewVCL: program MindScape_AppViewVCL_Step00; uses Forms, AppViewForm in 'AppViewForm.pas'{AppView};
{$R *.res} begin ReportMemoryLeaksOnShutdown := True; Application. Initialize(); Application .MainFormOnTaskbar :=; True Application .CreateForm (TAppView , AppView ); Application. Run();
end. DUnit testing project
This is based on the standard DUnit project template with a twist: it reports memory leaks, and a bit more cleaned up code when switching between console and GUI applications. program MindScape_AppViewTests_Step00;
{$IFDEF CONSOLE_TESTRUNNER} {$APPTYPE CONSOLE} {$ENDIF} uses Forms , TestFramework , GUITestRunner , TextTestRunner;
{$R *.res} begin
A common thing is that Caliburn Micro for Delphi depends heavily on RTTI (Run Time Type Information) to make the conventions and patterns work for you. In the steps, I will put () parenthesis to parameterless methods to set them apart from properties or fields. Most of the times that is optional, but at times it is required, and for me it makes it easier to see what the code is aimed at: method calls often have side effects, but most of the time accessing fields and properties should not. Some of the steps might sound overly detailed, and the total might scare you at first. The scaring is not on purpose, but the fine grained steps are: for me it is important to show you what it is involved, and what you might bump into. In practice these steps will take only a short amount of time. For the project names, you can optionally include a _Step## prefix where ## has the step number. I've done that in the examples below as that makes it easier for you to find back the individual steps in the DSharp code repository. Lets get on with the steps... Step 00: an empty VCL and DUnit application
Create a new project group that contains an empty VCL project with one empty form, and a DUnit test project that has no tests yet.
ReportMemoryLeaksOnShutdown := True ; i f IsConsole t he n with TextTestRunner. RunRegisteredTests do Free()
else begin Application. Initialize(); GUITestRunner. RunRegisteredTests();
end; end. Step 01: adding the interfaces and View Model
Add two units to the projects: AppInterfaceswith the interface definitions and AppViewModel that contains the View Model. Later we will will add another unit with a Model as well. After that make the modifications to the units and project file as shown in these sections. The AppInterfaces unit For now, there will be one interface in it that defines the Model View. Note that you need a GUID for this interface, as that will enable the compiler to generate RTTI (RunTimeTypeInformation)
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
105
Steps in MindScape AppView Step - By - Step (Continuation 2) unit AppInterfaces; interface type IAppViewModel = interface ['{38C5B8ED-8269-463D-847D-09F6A7B99584}' ] end; implementation end.
The AppViewModel unit The AppViewModel unit contains the View Model for our application. It will drive the View. For now the View Model will be empty: it's just the scaffolding for the rest of the steps. Since the TAppViewModel depends on the IAppViewModel interface, the AppViewModel unit uses the AppInterfaces unit. TAppViewModel descends from TScreen which is a base class that can be bound to a container that can be visible (usually a TForm or TFrame). The final step is in the initialization section of the unit: it refers to the ClassName of the TAppViewModel. This effectively instructs the compiler and linker to include the class and the RTTI in the executable. This allows Caliburn to use the RTTI and bind the View Model and View together. unit AppViewModel; interface uses AppInterfaces, DSharp. PresentationModel;
type TAppViewModel = (
class,TScreen
IAppViewModel )
end; implementation initialization
TAppViewModel. ClassName;
end.
No modifications for the form.
The really cool thing is that the form does not require any modification. It does not need to implement the IAppViewModel interface (that is done by the TAppViewModel class). The Caliburn framework will take care of binding the View Model and View for you. The project file also needs a few modifications: Insert these at the top of the uses list: {$IFDEF CodeSite} DSharp.Logging .CodeSite , {$ENDIF CodeSite} DSharp.PresentationModel .VCLApplication ,
The first lines will initialize the logging. The last line will use the TAppViewModel to start the application with. Later we will switch to the IAppViewModel for that. This is the main program you are after: program MindScape_AppViewVCL_Step01; uses {$IFDEF CodeSite} DSharp. Logging. CodeSite, {$ENDIF CodeSite} DSharp. PresentationModel. VCLApplication, Forms, AppInterfaces in 'AppInterfaces.pas' , AppViewForm in 'AppViewForm.pas'{AppView}, AppViewModel in 'AppViewModel.pas' ; {$R *.res} begin ReportMemoryLeaksOnShutdown := True ; Application. Initialize( ) ; Application .MainFormOnTaskbar True : =; {$IFDEF DEBUG} Application. WithDebugLogger( ) ; {$ENDIF DEBUG} {$IFDEF CodeSite} Application. WithLogger< TCodeSiteLog> ; {$ENDIF CodeSite} Application. Start< TAppViewModel> ( ) ; end.
Add unit AppViewModelTestCase to the test project Like the View and View Model, the initial TAppViewModelTestCase is also empty. The next steps will add tests each time functionality is added to the View Model. For the test case to compile, also add the AppViewModel unit to the DUnit test project. unit AppViewModelTestCase; interface uses TestFramework, AppInterfaces, AppViewModel; type TAppViewModelTestCase = class(TTestCase ) strict privat e FAppViewModel: IAppViewModel ; strict protected property AppViewModel: IAppViewModel read FAppViewModel; public procedure SetUp ; override ; procedure TearDown; override ; end;
This enables the CodeSite logging tool that is included with most recent Delphi versions. DSharp has support of other logging destinations like SmartInspect, Console, or OutputDebugString as well.
implementation procedure TAppViewModelTestCase. SetUp; begin FAppViewModel := TAppViewModel . () Create ; end;
It will also extend the TApplication using class helpers so the Caliburn framework can initialize the repository of View Models and Views, and then start the application. Finally replace
TAppViewModelTestCase. TearDown; procedure begin FAppViewModel := nil ; end; initialization RegisterTest( TAppViewModelTestCase. Suite) ; end.
Application .CreateForm (TAppView , AppView ); Application. Run( ) ;
with {$IFDEF DEBUG} Application. WithDebugLogger( ) ; {$ENDIF DEBUG} {$IFDEF CodeSite} Application. WithLogger< TCodeSiteLog> ; {$ENDIF CodeSite} Application. Start< TAppViewModel> ( ) ;
106
COMPONENTS DEVELOPERS
4
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Steps in MindScape AppView Step - By - Step (Continuation 3) Step 2: making Caliburn recognize your form as a valid View
It almost is, and indeed it is the first step. And you get another error that too is part of the learning experience: Exception EResolveException
No component was registered for the service type: IAppViewModel
When you try running the unit test from Step 01, it works. But the VCL app shows a tiny form with caption ChildForm and content View not found for TAppViewModel. So lets start to explain why you get the View not found for TAppViewModel error in the first place. Caliburn needs to be able to find which View belongs to a View Model. In our case, it needs to find a View for the TAppViewModel. We know it is TAppView ( it is one of the Caliburn conventions), but Caliburn doesn't know about TAppView as it needs the RTTI for it. So, like we did with TAppViewModel, we need to make sure that the RTTI for TAppView is registered as well. So we need to do the TAppView.ClassName trick. But that is not all: we also need to make sure that Caliburn can add some extra functionality. For that, add the unit DSharp.Bindings.VCLControls to the uses list. It contains (among a lot of other things) a new TForm implementation that descends from Forms.TForm (this is called an interceptor class, something presented for instance by Delphi.about.com in 2009). The TForm interceptor adds extra behaviour to Forms.TForm like notifications. In fact DSharp.Bindings.VCLControls contains interceptor classes for these Delphi units: ComCtrls CommCtrl Controls ExtCtrls Forms Grids StdCtrls
So the AppViewForm unit now becomes this: unit AppViewForm; interface uses DSharp.Bindings .VCLControls ;
type TAppView = class(TForm )
end; implementation
(Sometimes you will get a series of other exceptions, we will investigate that to see where it needs fixing). The cause of this exception is that Caliburn tried to find the class implementing IAppViewModel but couldn't get to TAppViewModel. RTTI is not the only part in the foundation of Caliburn: there is also a composition framework inside DSharp that - not surprisingly - is mimicked after a .NET framework as well. You need to decorate the IAppViewModel with the InheritedExport or InheritedExportAttribute attribute which works virtually identical to the InheritedExportAttribute in .NET. When booting your application, Caliburn composes a graph of dependent objects (Models, View Models, Views) that build your application. The InheritedExport attribute is in the DSharp.PresentationModel unit so that's why your IAppViewModel declaration should look like this: unit AppInterfaces; interface uses DSharp.PresentationModel ;
type
[InheritedExport] IAppViewModel = interface ['{38C5B8ED-8269-463D-847D-09F6A7B99584}' ] end; implementation end.
If you now run the application, it looks nice again
{$R *.dfm} initialization TAppView.ClassName ;
end.
Step 3: drive the application using the View Model Interface
The app now is already View Model driven by the TAppViewModel class. Wouldn't it be cool if it were driven by the IAppViewModel interface? You'd think it is as simple as modifying the project and replace Application.Start(); with Application.Start();
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
107
Steps in MindScape AppView Step - By - Step (Continuation 4) Wow, an empty form in just 3 steps! The cool things: • it is View Model driven (the next steps will show more of that ) • Caliburn automagically did the binding - hence the Caption is now TAppViewModel, not AppView • no global variables, Application.FormCreate and stuff like that any more: everything is dynamic So lets move on! Step 4: have the View Model drive the View's Caption
Add an Increments... caption Lets have the View Model steer a better Caption on the View, like Increments...:
unit AppViewModel; interface uses AppInterfaces, DSharp .PresentationModel ; type TAppViewModel = ( class,TScreen IAppViewModel ) public Create co n s t r u ct o r(); o;v e r r i d e end; implementation constructorTAppViewModel ( ).; Create begin inherited Create (); DisplayName := IAppViewModel_DisplayName ; end; initialization TAppViewModel. ClassName; end.
Caliburn will automatically call this constructor when composing the object graph. Try it and enjoy how easy this step was, and how little design-time effort it took. test for the Increments... caption Add a published method Test_DisplayName to the test case: published procedure Test_DisplayName(); then add the DSharp.PresentationModel unit to the DUnit test project and implementation uses clause: uses DSharp. PresentationModel;
and implement the Test_DisplayName method.
It's a simple two-step process add a resourcestring to the AppInterfaces unit:
end;
resourcestring
; IAppViewModel_DisplayName = 'Increments...'
then add a public constructor to the TAppViewModel like this:
108
procedure AppViewModelTestCase. Test_DisplayName( ) ; var LHaveDisplayName : IHaveDisplayName ; begin LHaveDisplayName := AppViewModel as IHaveDisplayName; CheckEquals( IAppViewModel_DisplayName, LHaveDisplayName. DisplayName);
COMPONENTS DEVELOPERS
4
The IHaveDisplayName interface is one of the many interfaces in Caliburn. This one exposes the DisplayName property. TScreen implements that interface so it indicates it supports having and supporting the DisplayName property. Now run and see if the first unit test on the View Model succeeds. Do you get the same result as left?
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Steps in MindScape AppView Step - By - Step (Continuation 5) Step 05: adding a Count property to the View Model and control to the View
Step 06: fixing the binding.
The previous step told about the importance of the interceptor classes in the DSharp.Bindings.VCLControls unit. And that is exactly the reason why at run-time you got Count into the caption of the TEdit: it wasn't bound to the integer value of Count in the View Model, as the interceptor classes could not do their work. The reason is that Delphi does not see them as they are obscured by the units that expose the actual control.
One of the Caliburn conventions is that if you have a control in the View with the same name as a property in the View Model, then they will automatically be bound together. So lets start with adding a TEdit control called Count on the View, and notice that Delphi automatically extends the uses list for you: uses
The lesson is easy: always make sure that units like
DSharp. Bindings. VCLControls, Classes, Controls , StdCtrls;
DSharp.Bindings.VCLControls that have interceptor classes are always the last in the uses list.
type TAppView = class(TForm ) Count : TEdit ;
So the solution is very simple, modify the uses list from uses DSharp.Bindings .VCLControls , Classes , Controls , StdCtrls ;
end;
Now add the Count property to the View Model: TAppViewModel = (
class,TScreen IAppViewModel ) strict priva te FCount : Integer ; strict protected f u n c t i o n GetCount (): Integer ; v i r t u a l; procedure SetCount ( const : Value ) ; Integer virtual; public c o n s t r u ct o r(); Create o;v e r r i d e property Count : Integer readGetCount write SetCount; end;
into uses Classes , Controls , StdCtrls , DSharp. Bindings. VCLControls;
Now run and enjoy the results of this step that was very easy to perform, but had a high impact.
and have it backed by get and set methods function TAppViewModel . ( )GetCount : ; begin Result := FCount ; end; procedure TAppViewModel . (SetCount:
Integer
constValue Integer);
begin i f Count <> Value t h e n begin FCount := Value ;
Step 07: add buttons to increment or decrement the count
NotifyOfPropertyChange('Count');
end; end
The DSharp.Bindings.VCLControls have class interceptors adding notification to most controls that ship with Delphi, the View Model must also notifications. The above SetCount implementation shows this for properties; we will see later that this also can hold for methods Now let’s run the application and see if design-time gets bound on run-time:
Here you will see that the Caliburn convention of naming controls not only holds for properties in the View Model, but also for methods. Lets start with the View Model: add two public methods here named DecrementCount and IncrementCount procedure TAppViewModel. DecrementCount; begin Count := Count- ;1 end; procedure TAppViewModel. IncrementCount; begin Count := end;
Count+
;1
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.
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
109
Steps in MindScape AppView Step - By - Step (Continuation 5) Now run and see how the design-time gets translated:
Update the SetCount method so that Caliburn gets a notification that CanDecrementCount and CanIncrementCount change when Count changes: procedure TAppViewModel . (SetCount: constValue Integer); begin i f Count <> Value t h e n begin FCount := Value ; NotifyOfPropertyChange('Count'); NotifyOfPropertyChange('CanDecrementCount' ); NotifyOfPropertyChange('CanIncrementCount' ); end; end;
Caliburn binds public methods and properties in the View Model to controls with the same name inThe last step is very important: if you forget it the buttons the View. Methods are bound to the action of a will not get disabled when the value of Count control. Properties are bound to the content of gets at the edge of the allowed range. the control.
This was almost too easy!. Let’s add some logic to limit the values to which Count can be incremented or decremented in the next step.
Without it, you can get the run-time behaviour on the left, but you want the run-time behaviour on the right:
Step 8: limiting the range of Count between -10 and +10 Modifying the View Model and View The easiest way of limiting the range is by using constants, so add these to the interfaceof the AppInterfaces unit: const MinimumCount = -10 ; MaximumCount = +10 ;
Nowa ddt hese public properties to theV iewM odel: property CanDecrementCount : Boolean read GetCanDecrementCount; property CanIncrementCount : Boolean read GetCanIncrementCount;
and have them backed by get methods: GetCanDecrementCount function TAppViewModel . ( ): Boolean; begin Result Count MinimumCount := > ; end; GetCanIncrementCount function TAppViewModel . ( ): Boolean; begin Result Count MaximumCount := < ; end
Adding unit tests
I like having unit tests around the boundary cases, and to have a certain symmetry. So these are the published test methods added: procedure Test_DecrementCount_MaximumCount(); procedure Test_DecrementCount_MaximumCount_Minus1(); procedure Test_DecrementCount_MaximumCount_Plus1(); procedure Test_DecrementCount_MinimumCount(); procedure Test_DecrementCount_MinimumCount_Minus1(); procedure Test_DecrementCount_MinimumCount_Plus1(); procedure Test_DisplayName(); procedure Test_IncrementCount_MaximumCount(); procedure Test_IncrementCount_MaximumCount_Minus1(); procedure Test_IncrementCount_MaximumCount_Plus1(); procedure Test_IncrementCount_MinimumCount(); procedure Test_IncrementCount_MinimumCount_Minus1(); procedure Test_IncrementCount_MinimumCount_Plus1();
with implementations like these:
It is always a good idea to make the View Model robust so that it can withstand unwanted calls. So update the DecrementCount and IncrementCount so they throw an exception when they cannot perform their
procedure TAppViewModelTestCase.
respective action:
end;
procedure TAppViewModel. DecrementCount; begin i f n o t CanDecrementCount t he n raise EInvalidOperation. Create( 'not CanDecrementCount' ); Count Count- ;1 := end; procedure TAppViewModel. IncrementCount; begin i f n o t CanIncrementCount t he n raise EInvalidOperation. Create( 'not CanIncrementCount' ); Count := Count+ ;1 end;
110
COMPONENTS DEVELOPERS
4
Test_DecrementCount_MaximumCount();
begin AppViewModel.Count: = MaximumCount ; AppViewModel. DecrementCount( ) ;
procedure TAppViewModelTestCase. Test_DecrementCount_MaximumCount_Minus1();
begin AppViewModel.Count: = MaximumCount - ;1 AppViewModel. DecrementCount( ) ;
end; procedure TAppViewModelTestCase. Test_DecrementCount_MaximumCount_Plus1();
begin AppViewModel.Count: = MaximumCount + ;1 AppViewModel. DecrementCount( ) ;
end;
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
Steps in MindScape AppView Step - By - Step (Continuation 6) Since the unit tests use the IAppViewModel interface to access the View Model, we need to modify it to expose the Count, GetCount, SetCount, DecrementCount and IncrementCount members: type
[InheritedExport] IAppViewModel = interface ['{38C5B8ED-8269-463D-847D-09F6A7B99584}' ] procedure DecrementCount(); (): ;Integer function GetCount procedure IncrementCount(); procedure SetCount ( const : Value); Integer property Count : Integer readGetCount 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();
but these should have been caught when assigning the Count property: procedure Test_DecrementCount_MinimumCount_Minus1(); procedure Test_IncrementCount_MaximumCount_Plus1();
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
111
Steps in MindScape AppView Step - By - Step (Continuation 7) So the unit tests reveal that the View Model isn't protecting Count from being assigned out of range values. Lets fix that first, then take another look at the unit tests. Since Caliburn is part of DSharp which depends on Spring4D which has a Guard.CheckRange method, fixing Count is as easy as including this line at the start of the SetCount method: Guard .CheckRange ((Value >=
and (Value <= MaximumCount ),
MinimumCount )
'Value' );
BTW: Don't forget to add the unit Spring to the
implementation uses list of the AppViewModel unit. Now setting Count to an out-of-range value causes an EArgumentOutOfRangeException to be raised. That brings us to the unit tests: we need certain tests to expect certain kinds of exceptions. DUnit can do just that using the little known ExpectedException property, which means that some of the tests need to be modified. First add the SysUtils and Classes units to the implementation uses list of the AppViewModelTestCase unit. Add the line ExpectedException := EArgumentOutOfRangeException;
at the start of these methods:
About the Author Jeroen Pluimers
Makes things work. Specialist in .NET, Win32, x64, SQL, Visual Studio and Delphi. Knows how to strike a balance between old and brand new technology to make things work. DOS, mobile, big systems, you name it. Married to a cancer survivor. As curator responsible for his brother who has an IQ < 50. 30+ year member of world class marching band Adest Musica. Trained and performed the 2013 half marathon in New York. Twitter: jpluimers Blog: wiert.me LinkedIn: jwpluimers Continuation:
Future steps for the example will be in the next Blaise issue. They will cover binding TAction with TActionManager, using the Caliburn logging, adding unit tests for the new actions, adding increment-byvalue with tests, adding a Model to the View Model, and creating a FireMonkey UI with TActionList next to the VCL UI.
procedure Test_DecrementCount_MaximumCount_Plus1(); procedure Test_DecrementCount_MinimumCount_Minus1(); procedure Test_IncrementCount_MaximumCount_Plus1 (); procedure Test_IncrementCount_MinimumCount_Minus1();
Then add the line ExpectedException := EInvalidOperation ;
at the start of these methods: procedure Test_DecrementCount_MinimumCount(); procedure Test_IncrementCount_MaximumCount();
Now the unit tests run fine!
112
COMPONENTS DEVELOPERS
4
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
kbmFMX for XE5 By Fikret Hasovic starter
expert
Delphi XE5
Performance on mobile platforms was the initial reason for creation of the kbmFMX controls, which are currently only readonly. kbmFMX components are part of kbmMemTable v. 7.40 Standard beta installation. They include data aware grid, memo, image component. kbmFMX is designed for XE5 only, but supports all targets, including mobile platforms. Great news is that kbmMW supports mobile platforms now!
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
I'll create Tabbed with Navigation Application:
After clicking OK button, Delphi XE5 will ask for a directory where your application source code should be generated:
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
113
COMPONENTS kbmFMX for XE5 (Continuation 1)
DEVELOPERS
4
After selecting the folder, the Delphi designer will open, and here is the main window with the mobile application unit created:
Notice here that the default layout is for Google Nexus 4 phone. Since I have aSamsung Galaxy Tab 2 7“, I will change layout to Google Nexus 7, using device ComboBox:
So, the new form in design mode will look like this:
114
COMPONENTS DEVELOPERS
4
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS kbmFMX for XE5 (Continuation 2)
DEVELOPERS
4
So, here you notice that Delphi has created a sceleton application with gesture support, and with tabbed interface, located at the bottom of the form, as Android standard:
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 i f Key = vkHardwareBack t he n begin i f (TabControl1 .ActiveTab = ) TabItem1 ( a n.d= TabControl2 ) ActiveTab TabItem6 begin ChangeTabAction2 .Tab : = TabItem5 ; ChangeTabAction2 . ( ExecuteTarget ); Self ChangeTabAction2 . := Tab ; TabItem6 Key := 0 ; end; end; end; procedure TTabbedwithNavigationForm . TabControl1Gesture ( :; Sender c o n:st EventInfo TGestureEventInfo ; : v a rHandled ); Boolean begin {$IFDEF ANDROID} c a s e EventInfo. GestureID o f
then
TObject
sgiLeft begin :
i f TabControl1 .ActiveTab <> TabControl1 .ActiveTab := Handled := True ; end;
TabControl1 .[ Tabs TabControl1 . TabCount - ] 1 TabControl1 .[ Tabs TabControl1 . TabIndex + ]; 1
then
sgiRight:
begin i f TabControl1 .ActiveTab< > TabControl1 .ActiveTab := Handled := True ; end; end; {$ENDIF} end;
TabControl1 . Tabs [ ] 0 then TabControl1 .[ Tabs TabControl1 . TabIndex - ];
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
1
COMPONENTS DEVELOPERS
4
115
COMPONENTS kbmFMX for XE5 (Continuation 3)
DEVELOPERS
4
I want this app to be able to use camera and/or pick a picture from the gallery, so I'll create an ActionList for Media actions:
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
COMPONENTS kbmFMX for XE5 (Continuation 4)
DEVELOPERS
4
On 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:
Here you can specify the Database, but you should specify it dinamically because Android requires that each sqlite database is used together with the application which must reside in /data/data/application_namespace/files/, so we do something like following, using OnSetupDBConnection Event of kbmMWSQLiteConnectionPool: procedure TTabbedwithNavigationForm. kbmMWSQLiteConnectionPool1SetupDBConnection ( : Connection TkbmMWSQLiteConnection ; : Database ); Pointer begin
{$IF DEFINED(ANDROID)} kbmMWSQLiteConnectionPool1 .\ Database := .
TPath ( Combine . TPath GetDocumentsPath ,
'myimages.sqlite' );
{$ENDIF} end;
In this instance, the Database path on the device will be /data/data/com.fikrethasovic.AndroidFMXApp/ files/myimages.sqlite If your device is rooted, you can phisically access it, but as default it is hidden from user, unless you root our device, which will void your warranty.
you need to add System.iOUtils to the uses list. OK, back to kbmMW, you create standard QueryService, and use SQLite specific components: Beside other components, added are two buttons, which are connected to the alMedia ActionList, which is responsible for the camera and gallery manipulation.
Notice, to use the Tpath class,
But, wefrom needthe to use the Image captured), by Camera picked Gallery ( or CameraRoll so the we will use or OnDidFinishTaking Event of standard media actions: procedure TTabbedwithNavigationForm. TakePhotoFromLibraryAct ion1DidFinishTaking( Image: TBitmap );
begin i f qClientSide . <> State
dsEdit t he n qClientSide .Insert ; qClientSide FieldByName . 'Image' ( as) TBlobField ).Assign (Image );
( end;
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
117
COMPONENTS kbmFMX for XE5 (Continuation 5)
DEVELOPERS
4
You can use the same code for both Media actions here. Here you notice kbmFMXDBGrid, kbmFMXDBImage and kbmFMXDBMemo components which are connected to qClientSide kbmMWQuery responsible for fetching data from SQLite database. I have removed some tabs I don't need, and left two only, so my interface looks like this:
Before we try to run .it,RegisterService don't forget to add (following code to OnCreate method of ) main kbmMWServer1 TkbmMWInventoryService , false ; form: 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: procedure TTabbedwithNavigationForm . qClientSideAfterScroll ( : ); DataSet begin Memo1 DataSet(FieldByName . : = Text . ) . 'Comment' ; AsString end; procedure TTabbedwithNavigationForm . qClientSideBeforePost (: ); DataSet begin qClientSide . FieldByName ( 'Comment' ). : AsString = . ; Memo1 Text end;
TDataSet
TDataSet
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:
Now, to use SQLite db in your app, you add kbmMWSQLiteConnectionPool component:
118
COMPONENTS DEVELOPERS
4
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS kbmFMX for XE5 (Continuation 6)
Delphi will add the launcher related files, but you need to 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:
DEVELOPERS
4
Here is one more:
Since I have a few android devices here, I have tested it on different android versions, and it works on 4.0.3, 4.2.2, 4.3.1 and 4.4.2, stock LG and custom ROM's ( SlimBean, SlimKat). Please note that you can just recompile (you might need to change a couple of lines of code ) this application for iPhone, but since I don't own that kind of device, I haven't tested it. It is a very exciting time for us folks! I have added several images to database which is distributed Use full the kbmMW and kbmMemTable potential together with kbmFMX components to create stunning together with application, so this is the first screen, and android business applications. following is when you change tab to single photo:
Nr 5 / 2013 BLAISE PASCAL MAGAZINE
COMPONENTS DEVELOPERS
4
119
kbmMW v4.40 available! kbmMemTable v. 7.40 available! Supportsfor Delphi/C++Builder/RAD Studiofor 2009 toXE5 (32bit, 64bit and OSX where applicable). kbmMW XE5 includes full support Android and IOS (client and server).
kbmMW is the premier high performance, high functionality multi tier product for serious system development. -
Native high performance 100% developer defined application server with support for
loadbalancing and failover -
Native high performance JSON and XML (DOM and SAX)for easy integration with
external systems -
Native support for RTTIassisted object marshalling to and from XML/JSON High speed, unified database access
(35+ supported database APIs) with connection pooling, metadata and data caching on all tiers -
Supports Delphi/C++Builder/RAD Studio 2009 to XE5 (32bit, 64bit and OSX where applicable). kbmMW for XE5 includes full support for Android and IOS (client and server). kbmMemTable is the fastest and most feature rich in memory table for Embarcadero products. -
Easily supports large datasets Easy data streaming support Optional to use native SQL engine Supports nested transactions and undo Native and fast build in aggregation/grouping features Advanced indexing features for extreme performance
Multi head access to the application server,
Warning!
via AJAX, native binary, Publish/Subscribe, SOAP, XML, RTMP from web browsers, embedded devices, linked application servers, PCs, mobile devices, Java systems and many more clients
kbmMemTable and kbmMW are highly addictive! Once used, and you are hooked for life!
COMPONENTS DEVELOPERS
4
ESB, SOA,MoM, EAI TOOLS FOR INTELLIGENT SOLUTIONS. kbmMW IS THE PREMIERE N-TIER PRODUCT FOR DELPHI / C++BUILDER BDS DEVELOPMENT FRAMEWORK FOR WIN 32 / 64, .NET AND LINUX WITH CLIENTS RESIDING ON WIN32 / 64, .NET, LINUX, UNIX MAINFRAMES, MINIS, EMBEDDED DEVICES, SMART PHONES AND TABLETS.