Delphi in Perspective - Felix John COLIBRI. Delphi's 25 the Anniversary |
- abstract : Delphi 25 th anniversary rememberings: the first encounter, the context, evolution of the framework, the future
- key words : Object Oriented Programming, Visual Basic, Delphi, Unicode,
FireMonkey, Toghether, C++, Python , Soap, Rest Web Services
- plan :
1 - The first Delphi Demonstration In 1995, I was quietly continuing my usual Pascal life when Eric WYSS from Borland invited me to the presentation of a new product at the Borland Paris
office. I met Zack URLOCKER for the first time and he presented me Delphi. I could not understand what he was showing me. the Form, the Tool Palette, the Object Inspector, the OnClick event. How could have been so unprepared ?
2 - Here comes Delphi ! 2.1 - Object Oriented Programming It all started with the Object Oriented Programming revolution. The BYTE
magazine had published in August 1981 a special edition about Smalltalk and object programming. Adele GOLDBERG.
But since we did not have this construct in the Apple ][ UCSD Pascal, this was a pure abstract concept I did not fully understand the implications at the time. However the writing was on the wall.
Then, in January 1984, came the MacIntosh with Object Oriented Programming and the event programming paradigm. I purchased a Mac and even a Lisa, which was required to program the Mac. I also started to read the 3 "Inside MacIntosh"
Addison Wesley books about the MacIntosh programming. But I did not finish the whole thing. MacIntosh was not for me: mainly a user audience, but no programming books, magazines, examples. Wonderfull tools for publishing
(PageMaker was the buzz of the day) but nobody to buy books, attend training sessions or buy inventory or order processing software, which was my primary customer base. So I sold the Mac and the Lisa after 6 months, and still believe
it was the right decision. Nevertheless, the object concept was still very much discussed in our Pascal User Group. To finally understand the concept, I wrote and published in our
Pascalissime magazine a Pascal pre-processor enabling the writing of object oriented code. Basically the pre-processor was building a VMT (Virtual Method Table: an Array of procedure pointers), thereby allowing inheritance and
polymorphism. At that time the syntax was still unclear: should the method be implemented in the same bloc as the definition (as in C++ or Object Pascal) or separately in the Interface part, as was finally decided by Borland.
In 1989 Turbo Pascal 5.5 came out. 5.5 tells it all: so urgent as to require a new edition before the launch of "Turbo 6". I still remember David Intersimmone as he presented this version in Paris. It is also the first time Borland
included in the documentation a tutorial to explain the object concept. I was told that even Philip KAHN presented this at conferences. He presented hierarchies showing pictures of plants and animals, fish and mammals, horses,
cats and dogs. He then was frustrated that after the conference people did not talk about encapsulation or inheritance but only remembered that he had a dog !
2.2 - Turbo Vision
Turbo Pascal 6 came in 1991. The existence of objects allowed Borland to rolled out Turbo Vision: a DOS framework with windows, menus, "controls", all within an object layer. I personnally never really programmed in Turbo Vision. I could
code whatever I needed using my own DOS menus, formulars, with input areas using some home cooked libraries. And was somehow too lazy to learn this new framework. In addition the syntax was also a little bit "academic". To present 3 radiobuttons:
you had to write
R.Assign( , , , ); B := New
(PRadioButtons, Init(R,
NewSltem('-S-olid' ,
NewSltem('-R-unny' ,
NewSltem('-M-elted' ,
Nil ))))); Insert(B |
Even Lisp had come up with some shortcut notation avoiding the 5 closing parentheses ! However there is little doubt that the writing of this library helped the
Borland team gain much experience in writing such big object oriented framework.
2.3 - Windows 3.1 A couple of years later, in 1992, Windows 3.1 came out. As ususal, Borland
quickly offered a versions of Turbo Pascal enabling Window Programming. I immediately purchased the PETZOLD book, read the full book and then went thru all the examples and translated them in Pascal.
Using Windows basic APIs, to create a window, you had to - first register a new class for each window kind:
(* -- définit une nouvelle classe si c'est le premier appel *)
If g_instance_precedente = 0
Then enregistre_classe(cs_vRedraw Or cs_hRedraw,
@f_traite_message,
k_pas_octets_classe, k_pas_octets_fenetre,
g_instance,
LoadIcon(k_icone_en_stock, idi_Application),
LoadCursor(k_curseur_en_stock, idc_Arrow),
GetStockObject(White_Brush),
k_pas_ressource_menu, k_nom_classe); | - the procedure being:
Procedure enregistre_classe(p_style_classe: Word; p_pt_fonction_messages: TFarProc;
p_octets_classe, p_octets_fenetre: Integer; p_poignee_instance: tHandle;
p_poignee_icone: hIcon; p_poignee_curseur: hCursor; p_poignee_brosse: hBrush;
p_nom_ressource_menu: pChar; p_nom_classe: pChar);
Var l_classe: tWndClass; Begin
With l_classe Do Begin
(* -- style: quand doit redessiner etc. *)
style:= p_style_classe;
(* -- la procédure de traitement des messages *)
lpfnWndProc:= p_pt_fonction_messages;
(* -- place supplémentaire dans la classe ou la fenêtre *)
cbClsExtra:= p_octets_classe; cbWndExtra:= p_octets_fenetre;
(* -- poignée de l'instance qui a enregistre la classe, pour pouvoir détruire *)
hInstance:= p_poignee_instance;
(* -- poignées de l'icône, du curseur et du fond *)
hIcon:= p_poignee_icone; hCursor:= p_poignee_curseur; hBrBackGround:= p_poignee_brosse;
lpszMenuName:= p_nom_ressource_menu;
lpszClassName:= p_nom_classe;
End; (* with l_classe *)
If Not RegisterClass(l_classe)
Then Halt(255); End; (* enregistre_classe *) |
- then you had to create a window (meaning the main window, a Label, a Button etc), with 14 parameters. Yes, 14:
Var l_poignee_fenetre: HWnd; l_message: TMsg;
l_poignee_fenetre := CreateWindow(
(* -- le nom identifiant la classe *) k_nom_classe,
(* -- titre affiché *) k_titre_fenetre,
(* -- le style *)
ws_Overlapped Or ws_Caption Or ws_SysMenu Or ws_ThickFrame
Or ws_MinimizeBox Or ws_MaximizeBox,
(* -- la fenêtre par défaut *)
cw_UseDefault, cw_UseDefault, cw_UseDefault, cw_UseDefault,
(* -- la poignée de la mère *) k_sans_parent,
(* -- poignée du menu *) k_poignee_menu,
(* -- le numéro de cette instance *) g_instance,
(* -- parametre de création *) k_pas_parametre_de_creation); |
- and, to react to the events, you had, for each window, to create a special WinProc procedure with the code for each of the events you wanted to handle:
Function f_traite_message(p_poignee_fenetre: HWnd;
p_code_message, p_w_param: Word; p_l_param: Longint): Longint; Export;
Var l_appeler_defaut: boolean;
l_resultat: longint;
Const ls_largeur_caractere: Word= 0;
ls_hauteur_caractere: Word= 0;
Procedure traite_wm_create(p_pt_create_structure: pCreateStruct);
Var l_poignee_contexte_ecran: hDC;
l_metrique: tTextMetric; Begin
l_poignee_contexte_ecran:= GetDC(p_poignee_fenetre);
SelectObject(l_poignee_contexte_ecran, GetStockObject(System_Fixed_Font));
GetTextMetrics(l_poignee_contexte_ecran, l_metrique);
With l_metrique Do Begin
ls_largeur_caractere:= tmAveCharWidth;
ls_hauteur_caractere:= tmHeight;
End; (* with l_metrique *)
ReleaseDC(p_poignee_fenetre, l_poignee_contexte_ecran);
End; (* traite_wm_create *)
Procedure traite_wm_l_button_down(p_bouton_ou_touche: Word;
p_x, p_y: integer);
(* -- p_bouton_ou_touche (p_w_param): indique les touches / boutons enfoncés *)
Var l_poignee_contexte_ecran: hDC;
l_ligne: Word;
Procedure affiche(p_rouge, p_vert, p_bleu, p_rouge_fond, p_vert_fond, p_bleu_fond: Byte;
p_texte: String); Begin
SetTextColor(l_poignee_contexte_ecran, RGB(p_rouge, p_vert, p_bleu));
SetBkColor(l_poignee_contexte_ecran, RGB(p_rouge_fond, p_vert_fond, p_bleu_fond));
affiche_chaine(l_poignee_contexte_ecran, 0, l_ligne, p_texte, 20);
SetTextColor(l_poignee_contexte_ecran, RGB(p_rouge_fond, p_vert_fond, p_bleu_fond));
SetBkColor(l_poignee_contexte_ecran, RGB(p_rouge, p_vert, p_bleu));
affiche_entier(l_poignee_contexte_ecran, 21* ls_largeur_caractere, l_ligne, p_rouge, 5);
affiche_entier(l_poignee_contexte_ecran, 26* ls_largeur_caractere, l_ligne, p_vert, 5);
affiche_entier(l_poignee_contexte_ecran, 31* ls_largeur_caractere, l_ligne, p_bleu, 5);
inc(l_ligne, ls_hauteur_caractere);
End; (* affiche *)
Begin (* traite_wm_l_button_down *)
l_poignee_contexte_ecran:= GetDC(p_poignee_fenetre);
SelectObject(l_poignee_contexte_ecran, GetStockObject(System_Fixed_Font));
l_ligne:= 0;
affiche(0, 0, 0, 255, 255, 255, 'noir / blanc');
affiche(255, 0, 0, 0, 255, 0, 'rouge / vert');
affiche(0, 0, 255, 0, 255, 255, 'bleu / bleu-vert');
affiche(255, 0, 255, 255, 255, 0 ,'violet / jaune');
ReleaseDC(p_poignee_fenetre, l_poignee_contexte_ecran);
End; (* traite_wm_l_button_down *)
Procedure traite_wm_destroy; Begin
(* -- demande à Windows d'envoyer wm_Quit à notre instance *)
PostQuitMessage(0); End; (* traite_wmDestroy *)
Begin (* f_traite_message *)
(* -- par défaut, si traite un message, retourne 0 à Windows *)
l_resultat:= 0;
(* -- par défaut, si traite un message, il ne faut pas appeler DefWindowProc *)
l_appeler_defaut:= false;
Case p_code_message Of
wm_Create : traite_wm_create(Pointer(p_l_param));
wm_lButtonDown: traite_wm_l_button_down(p_w_param,
t_long_integer(p_l_param).lo, t_long_integer(p_l_param).hi);
wm_Destroy: traite_wm_destroy;
Else (* -- pour toutes les commandes non traitées dans le case: *)
l_appeler_defaut:= true;
End; (* case p_message *)
(* -- traite ici les messages non traités dans le CASE *)
If l_appeler_defaut
Then f_traite_message:= DefWindowProc(p_poignee_fenetre, p_code_message,
p_w_param, p_l_param)
Else f_traite_message:= l_resultat;
End; (* f_traite_message *) | - now you could see the window:
(* -- affiche la fenêtre en effaçant la zone client *)
ShowWindow(l_poignee_fenetre, g_allure_initiale);
(* -- envoie wm_Paint pour redessiner la zone client *) UpdateWindow(l_poignee_fenetre); |
- and you had to write your own event polling loop:
(* -- la boucle de recuperation et de ventilation des messages *)
While GetMessage(l_message, k_toutes_fenetres, k_message_min, k_message_max) Do
(* -- appelle la procédure de traitement *) DispatchMessage(l_message); |
Just imagine a window with 10 or 20 labels, edits, buttons and you get an idea of the magnitude of the task.
From a teaching point of view, I was quite a happy man though: this thing was
so new and convoluted that my Pascal Windows Api programming book was selling reasonably well, and my training were a success. I had up to 20 persons in our little training room.
2.4 - Visual Basic
At the same time, there were rumors of a Visual Basic thingy. Any feller could have a window with a couple of controls and some code in 15 minutes sharp. With Pascal, to explain the window creation and the WinProc to my students took me
about 3 hours. Just to display Hello ! Obviously the Visual Basic folks had come up with a revolutionary concept. We assumed Borland would sometime follow up.
2.5 - Delphi And the answer was Delphi !
Just after the first presentation by Zak, I ran to the nearest store and purchased a Visual Basic edition to understand the concept. Unsurprisingly I did not like the untyped language, but could grasp what the Form - Palette -
Object Inspector were all about. Shorlty after that Eric WYSS sent me the beta version discs (I thing there were 10 or 15 of those 3 1/2" floppies) to install the product. There was no
documentation, so I decompiled the Help, printed the whole thing and was jumping up and down in our training room to reorder the pages and assemble some kind of manual.
3 - Delphi in retrospect
The concept was clearly the Visual Basic one. In addition, Delphi added - a compiled mode (VB was interpreted)
- the possibility to write components in the language (VB required the use of C or C++)
- the full source code of the VCL to start the component movement rolling
The first beta was called VBK : Visual Basic Killer. It did not kill Visual Basic, but I could not care less: we had a wonderfull product.
Behind the scenes, some more subtle concepts - all data was dynamic. This is a must for any windowing system. Lets take a simple menu: how many columns: it depends. How many lines in each column:
impossible to tell. So dynamic data is necessary. In Pascal parlance, this means pointers. However pointers were taught in the last chapter of all books, being advertised as difficult to program. Since Borland wanted to
capture the Visual Basic market, pushing pointers at the forefront seemed too frightening. I believe that pointers, explained correctly, are quite easy to use. Additionnally, VB programmers could have managed the shock.
Anyway, Borland decided to hide the pointers, and presented the "reference model": Button1 is the pointer, Button1.Left is the pointed Left field
(instead of Button1^.Left) And all data was encapsulated in objects. This certainly added some derived contortions, like using Assign to test if an object is not Nil, but this is a small price to pay in exchange of a
single "all objects are pointers" model.
- Borland did a superb job at hiding the complexity. The first thing the programmer sees ("File | New | Vcl Form") is a tForm:
Unit Unit1; Interface
Uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs; Type
TForm1 =
Class(TForm)
End; // tForm1
Var Form1: TForm1; Implementation
{$R *.dfm} End. | Right from the start you have
- Units which are already a kind of abstract type with the Interface / Implementation separation
- libraries (Uses)
- objects (Class),
- inheritance (tForm1= Class(tForm) )
and yet I never encountered any difficulties to present this as the first contact with Delphi . - if you follow the Window organization, or the Turbo Vision framework, each
control should be a separate Class. So a single button should be a descendent of the basic ancestor tButton
Unit Unit2; Interface
Uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms, Dialogs; Type
TButton1= Class(tButton)
Click; Override;
End; // TButton1 TForm2 =
Class(TForm)
Button1: TButton1;
End; // TForm2
Var Form2: TForm2; Implementation
{$R *.dfm} Procedure TButton1.Click;
Begin End; // Click
End. | In fact, the fields of Button1 are static (usually not changed during the
execution), and the only differences between tButton and tButton1 are the events. Hence the "event delegation": tButton 1 delegates the OnClick event to the tForm1, and the Sender parameter enables to test who
triggered the event, should you decide to share the event: Unit Unit1;
Interface
Uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs; Type
TForm1 =
Class(TForm)
Button2: TButton;
Procedure Button1Click(Sender: TObject);
End; // tForm1
Var Form1: TForm1; Implementation
{$R *.dfm}
Procedure TForm1.Button1Click(Sender: TObject);
Begin End; // Button1Click
End. | Just imagine our forms today with 50 or 100 controls: the "traditional" solutions would overpopulate your Unit with 50 or 100 Classes with just
one or two events (versus a single tForm Class with 50 OnClick events). Anders HELJSBERG brought this concept with him when he left Borland to join Microsoft an create C#. To the disappointment of the Java community where
each new button should be a descendent of the bacic tButton Also take the time to compare the current Delphi solution to print Hello ! to the Windows API solution presented before !
- on the marketing front, the main decision was to include database access.
When Philip KAHN launched Turbo Pascal, the price was $ 49.95. This was more than double the JRT Pascal (19.95 ), but which was compiling on disc and
way too buggy. UCSD was over $ 1.000. However at $ 49.95 apiece, increasing the company turnover was not that easy. Adding add-ons, like a small worksheet, games, numeric libraries or a Logo compiler, each below $ 100,
did not help much. At the same time Mich KAPOR was selling $ 800 Lotus packages. Visicalc is the software which made the Apple ][ a success. All those Californian execs which had to wait monthes for the central data processing
department to add this other column or that additional field in their monthly reporting system. With Visicalc and some typing they could get whatever data massaging they needed within a couple of hours.
PowerBuilder was a very popular Windows product at the time: "non programmer" could build a small business application (invoicing, inventory management, accounting etc) Therefore integrating database access in the product really was a fabulous
decision. Borland had already purchased Paradox, acquired the dBase product (which was struggling because they could not build an SQL engine) and integrated Interbase. The gathering of those became the BDE with access to all popular
databases thanks to ODBC. We could still write Text Editors (like the one I am using to write books, .PDF letters, or the articles of this web site), assemblers and interpreters
of all kinds, scientific software, drilling machine controllers etc. But it is on the business side that the money is: budgeting, payrolls, factory scheduling, etc. The bank robber Willie Sutton was may times arrested, and someone asked him
one day: "why do you continue to attack banks over and over again ?" and he answered "but that's where the mouney is !"
4 - The Delphi evolution
We hardly started to get familiar with Delphi, and already Borland had Delphi 2 in the pipeline. In fact the 32 bit version was developed at the same time as Delphi 1. Windows 95 allowed 32 bit programming, and we could get rid of the
Windows over DOS solution. Delphi 3 to 7 continued to follow the market evolution, mainly with the introduction of Sockets, Internet, SOAP Web Services and XML. Some people still
believe that Delphi 7 was an improvement over its predecessors and is better then its successors ! Around 2005, Delphi tried to follow the C# revolution. Delphi 8 allowed some
compilation, Delphi 2006 and following added C#, Vcl Net, Asp Net. But with the coming years those were gradually dropped from the product. Then in 2009, came the Unicode breaking change. This was a mixed blessing.
People had to migrate their application. We wrote a couple of papers about Unicode and the migration process. And we still have an active market with the migration business. But on the same time it forced many companies which were
quite happy with Delphi 7 to upgrade their tool. One must understand that Borland, or CodeGear, Embarcadero, Idera for that matter basically must sell products each and every year. So the Unicode change somehow forced this
purchase. The new trend to subscription systems will hopefully alleviate the problem. Then came the phone revolution. I certainly did not anticipate this one. In retrospect, it is fascinating to see how they managed the whole process :
- for Delphi 2011, nothing much new. Some external tools were added (Beyond Compare, Aqtime, CodeSite etc). I guess those were thrown in to keep the community buying the new version, as the phone framework was not yet fully complete
- then in 2012, FireMonkey with iOs and iPhone
- after that gradually Android
Other features were added, some of which I am not to familiar with (the Cloud, BAAS, IOT etc)
However the addition of the Interface concept and the inclusion of unit tests remain two key points of the last 10 years. There are two other points I consider important:
- UML and Object Oriented analysis and conception : this does not depend on the Delphi framework, but is an evolution compared to the first step "do it all in the main form" trials we all used in the beginning. Together is a
reasonable UML tool, has a very nice Design Pattern generation feature and was helpful when the ECO (Enterprise Core Object) object persistence framework was included in Delphi. But I am not sure UML is still used by many Delphi developers
- REST allows web services, but the main point is microservices, which is a step beyond Service Oriented Programming. Micro services can be implemented using REST, and many Delphi frameworks or simply Indy (or any other HTTP library) can be used.
4.1 - Some Mishaps Of course, along the road, there were some mishaps - the Kylix Linux version
- the gradual dropping of the .Net features
- the BlackFish in memory database
- then ECO framework (the Borland adaptation of the BOLD object persistence library)
5 - The Delphi Future Since 1990, people keep asking me what the future of Borland / Delphi will be.
I have no idea. As Niels BOHR is supposed to have said "prediction is difficult, especially the future" If Delphi were to disappear have we, or have I other alternatives ?
5.1 - C++
Some still advertise C++ as THE solid professional language. The same propaganda we had in the early Pascal days. Well just a short story. I had to build a Delphi interface to the
MORPHO (now IDEMIA) framework, which offers fingerprint capturing devices. The whole system offers C++ DLLs containing C++ Classes. So I used the very nice articles by the late and very much regretted Rudy VELTHUIS explaining how
to access C++ classes from Delphi, either flattening the Classes or building a COM wrapper. I first used Borland C++. Encountering mistakes, I assume the DLL was not compiled with the Borland C++. I tried to translate the .LIBs
(obj format differences) or adding .DEFs (taking care of name mangling). Just imagine: the business of a C++ compiler is to mangle names. Then you have to unmangle thoser if you use another compiler (or perhaps remangle them
differently before dismangling them). At the end I had to accept that interfacing C++ objects in a DLL built with another C++ compier from Borland C++ builder is next to impossible (at least for me). I finally downloaded the
free Visual Studio, and it took me a full week to get this thing working. Incompatibility between compilers, gadzillion of files, all kind of macros to avoid double compilation, add prefixes, qualifiers and what not. Boy am I happy
to now manage this thing in Delphi !
5.2 - PHP, perhaps ? My book printer hired a student to build his on-line catalog. When this student started to slowly stop answering his update requests, I tried to help him. This
would a good opportunity to look at PHP. Well, it took me over an hour to find out how to write "next week". In Delphi : my_date_time +7 This taught me that Delphi proficiency is not only concepts (OOP, REST etc),
syntax and grammar, but also the memory of a huge run time library. Using another system will require some effort to learn the way we can accomplish some mundane tasks as "in a week"
5.3 - Or Python, maybe ?
Python is another possibility. Many advertise Python as a quick prototyping system. In 10 lines of code you can try new concepts, like Deep Learning. I tried. You can find loads of examples of how to recognize images or forecast
the stock market. GitHub is full of them. Now to make those work, you often have to add huge libraries to your basic Python system. Fair enough. At this stage, Python is just a thin scripting layer calling many scientific libraries
like TensorFlow. But installing some additional library can ruin your current Python installation. So they invented Docker: you save Python and the library for project 1, then save Python and some other library for project 2 and so on.
This makes me think of the people who invented the Lint or the Debuggers to be able to run some C programs: the compiler does not check anything because, being a C system programmer, you surely will not do any mistakes. But if things
go wrong, just use Lint or the debugger to find out (what a simple Pascal compiler would have detected in the first place). The Compiler won't let you shoot yourself in the foot !
However, Deep learning still looks like an immense opportunity. There already are some Delphi projects allowing to create CNNs (Convolutional Neural Networks) LSTMs (Long Term Short Term Memory), and some attempts to interface with TensorFlow.
But would it not be wonderful if Delphi integrated such functionalities ? Sure this is not in the current "general framework" positioning of the product. But this is a gigantic market, and it is the future. I have a dream ...
5.4 - Conclusion 40 years in the business allow me to draw some conclusions: - Pascal and Delphi are the right tools for me. Meandering in untyped languages with all kind of behind the scenes second guessing and inferencing
leave me profoundly unhappy.
- Delphi is the most productive tool I know, covering the full spectrum from assembler to design patterns
- from a commercial point of view, selling a Delphi development might still be
an uphill battle. But if you are convinced of the quality of the tool, it is easy to explain the benefit of using Delphi to your customer
- hiring Delphi developper, or even convincing Java developers to work on
Delphi project still remains difficult today. The free community edition, or even the "Best Delphi App" competitions certainly are steps in the right direction. Writing books, publishing a magazine, and now writing articles is
my small contribution to help bring more people aboard
6 - Some Links Here are some references and links
And on our side
- Pascalissime our French Pascal / Delphi magazine
- Pascal and Delphi
Books : some of the books I wrote
- Unicode Migration : explaining Unicode and the Delphi 2009 Unicode migration steps
- Rest Web Services OAuth2 Tutorial : Delphi DropBox Rest Service Client using the OAuth2
protocol. Implemented with the tRestClient Delphi components or Indy tIdHttp component. Get the DropBox token, list the files, download and upload files
- Delphi Mobile Point of Sale Software : Android tablet or smartphone point of sale application
using a WIFI connection to a DataSnap REST Server connected with FireDac to a FireBird database
7 - The author Felix John COLIBRI works at the Pascal
Institute. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly
active in the area of custom software development (new projects, maintenance, audits, BDE migration, Delphi
Xe_n migrations, refactoring), Delphi Consulting and Delph
training. His web site features tutorials, technical papers about programming with full downloadable source code, and the description and calendar of forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP / UML, Design Patterns, Unit Testing training sessions. |