************************************************************************************ The Best of delphi3000.com from Max Kleiner ************************************************************************************ (03/29/2008) OpenGL Intro (02/22/2008) OpenSSL with Delphi (07/20/2007) Messages between a ClientEXE and a FormDLL (08/17/2006) How to use the command line compiler (03/17/2008) Switch between one or many instances (10/30/2007) Multilanguage Easy Translator (12/29/2002) Build a Table Tree from Records to Objects (11/18/2002) Building a Terminal Server Client-Application (11/16/2001) The 5 Relationships between Classes (11/06/2003) Some Relations between CLX and Qt (10/22/2001) Five of the best tools for Delphi (10/22/2001) How do you add Interfaces to a List ? (10/01/2002) How to implement an Array Property ? (09/28/2002) News and Highlights from EKON 6/8 in Frankfurt (09/22/2002) Safety Design with a Static Instance (09/11/2001) Exception or Event Logger (09/06/2004) What's a buffer overflow and how to avoid it in Delphi? (09/06/2001) Calling a C++ DLL which exports a class? (08/22/2003) When use Interfaces, when use Inheritance ? (08/19/2002) How do we store Graphics/Shapes like an Object ? (08/14/2001) Building a business object (07/31/2001) Copy a FileStream from Server to Client (07/13/2001) Managing a lot of Forms (07/12/2004) What's the ECO framework? (07/05/2002) Understanding VisualCLX (06/26/2003) Direct File Access with a StringGrid (06/14/2002) Create a DBExpress-Connection at Runtime (06/13/2004) Handle lots of classes at runtime (06/11/2002) When do we use Application (Owner), Self or NIL ? (06/08/2002) An Iterative ASCII-Export (06/07/2001) Callback function with a DLL (06/01/2001) Running Application already exists (06/01/2001) Validate an object (05/14/2002) Easy Parsing an XML File (05/14/2001) Params in a Web-Query at run time (05/12/2002) How do you subclassing a versatile TList ? (05/10/2001) What's DelphiScript ? (05/08/2001) DLL+ (04/29/2004) BLOBs in InterBase (04/09/2002) Web Service Workshop with Remote Data Storing (04/03/2002) Building a Fractal Generator (03/29/2002) How do we implement the Choice Pattern (03/17/2003) An Application Loader with a TCPServer (02/21/2002) Why use Assembler ? (02/08/2004) How to get data from InterBase via WebServices (01/28/2002) Why use Packages ? (01/03/2004) An easier alternative for a TIniFile object (10/31/2004) Optimize your Code (06/05/2005) Universal embedding of files in Delphi units (05/09/2005) Build one Resfile from files located in a Directory (03/28/2005) Hotspot function with CLX (05/09/2005) How to realize a ChangeFinder (11/27/2005) DelphiWebStart in v1.5 (02/23/2006) How to build Testcases with DUnit (06/11/2006) DLL Cross Independencies (06/29/2006) How to use a hash function? ____ ___ _ ____ _ _ _ | _ \ | _| | | | _ \ | | | | | | | | . | | |_ | | | |_| | | |_| | | | | | | | | _| | | | __/ | _ | | | | |_. | | |_ | |__ | | | | | | | | |____/ |___| |____| |_| |_| |_| |_| 2008: Max Kleiner ******************************************************************************** OpenGL Intro OpenGL with Delphi Primer ******************************************************************************** Reference: http://www.opengl.org/code/ Component Download: http://www.softwareschule.ch/download/opengldelphidemo.zip OpenGL is low level but the fascination is much higher. I will give you a short introduction of this astonishing world. Answer: OpenGL with Delphi #1 ---------------------------------- OpenGL is a low-level graphics library specification originally developed by Silicon Graphics and by low-level we mean near system-level. The system's particular implementation of this specification, often called OpenGL driver, allows you to use a set of geometric primitives (points, lines, polygons, images, and so on) to describe a scene we wish to paint (rendering). The interface consists of about 150 distinct commands with a lot of parameters so you must build up your desired model from a small set of geometric primitives. There are many components or units for Delphi or Lazarus which encapsulates all the complexity that can result in using OpenGL. In other words, all the hassle of dealing with pixel format, matrixmode, viewports, the GL rendering context, etc. is hidden by many free components on the market. The only thing that needs to be done is to handle a few events: OnGLInit(), OnGLPaint() and the classical OnResize(): procedure TGLform.OpenGLControl1Resize(Sender: TObject); begin if (AreaInitialized) and OpenGLControl1.MakeCurrent then glViewport (0, 0, OpenGLControl1.Width, OpenGLControl1.Height); end; Of course this is the simplest one. On top of that, many components are missing two facilities. The first one is to print out the rendered scene, while the second should save it into a bitmap. Visualizing a scene of moderate complexity takes mere milliseconds, which means the library has sufficient performance to support you in the creation of animations, simulations and virtual worlds. The OpenGL driver is generally provided as a library in binary format. It can be linked to your program dynamically. On the Win platform, it will be a DLL (check for opengl32.dll or / and GLU32.dll). const opengl32 = 'OpenGL32.dll'; glu32 = 'GLU32.dll'; initialization Set8087CW($133F); try LoadOpenGL('opengl32.dll'); except end; finalization FreeOpenGL; end. from the GL unit we get: function wglGetProcAddress(ProcName: PChar): Pointer; stdcall; external opengl32; function wglCopyContext(p1: HGLRC; p2: HGLRC; p3: Cardinal): BOOL; stdcall; external opengl32; function wglCreateContext(DC: HDC): HGLRC; stdcall; external opengl32; ........................ Since Delphi is able to use almost any DLL, it is as simple as with any other language to program OpenGL 3D graphics, animations or games. Obviously, by the matrix nature of OpenGL, a good understanding of matrix manipulation and uses of matrices in 3D graphics will help. Also a really solid grasp of algebra, trigonometry, and geometry will certainly be a plus. The more you know, the easier you can produce algorithms to reach your goals. You may be able to solve many problems without much knowledge in these areas, but you will have to work harder (think softer) to get there. This article will help you to get first steps with OpenGL techniques in Delphi. There are two kinds of data that enter OpenGL processing: pixel data and geometry data. These two kinds of data go through two different pipelines like a big state machine until they merge near the end to produce the final step of pixels in the frame buffer. SwapBuffers(Info^.DC); You put it into various states (or modes) that then remain in effect until you change them. The bitplanes are themselves organized into a framebuffer, which holds all the information that the graphics display needs to control the color and intensity of all the pixels on the screen. By the way doubleBuffering means one picture is displayed while the other is being drawn at the background then swapped! Let's illustrate a typical use of OpenGL. First we need a device context (you remember those old days) of the window: Info^.DC:= GetDC(Result); We set a pixel format: Info^.PixelFormat:=ChoosePixelFormat(Info^.DC,@pfd); Then we pass it to a render context: Info^.WGLContext:= wglCreateContext(Info^.DC); TWGLControlInfo = record Window: HWND; DC: HDC; PixelFormat: GLUInt; WGLContext: HGLRC; end; The following describes the major graphics operations which OpenGL performs to render an image on the screen: - Construct shapes from geometric primitives (OpenGL considers points, lines, polygons, images, and bitmaps to be primitives) - Arrange the objects (or models) in three-dimensional space. - Calculate the color of all the objects which must explicitly assigned by the application, determined from specified lighting conditions and textures. - Convert the math. description of objects and their associated color information to pixels on the screen (called rasterization). In addition, after the scene is rasterized but before it's drawn on the screen, you can perform some operations on the pixel data if you want. procedure TExampleForm.IdleFunc(Sender: TObject; var Done: Boolean); begin OpenGLControl1Paint(Self); Done:= false; end; Here's how we draw a white rectangle on a black background: InitializeAWindowPlease(device context and so on); glClearColor (0.0, 0.0, 0.0, 0.0); glClear (GL_COLOR_BUFFER_BIT); glColor3f (1.0, 1.0, 1.0); glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0); glBegin(GL_POLYGON); glVertex3f (0.25, 0.25, 0.0); glVertex3f (0.75, 0.25, 0.0); glVertex3f (0.75, 0.75, 0.0); glVertex3f (0.25, 0.75, 0.0); glEnd(); glFlush(); UpdateTheWindowAndCheckForEvents(timer or onidle()); Some notes about the timing and events. You can choose between onIdle() or a Timer. You can specify a function that's to be executed if no other events are pending or add the time from a timer it takes for your system to clear the screen and to draw a typical frame. Sources: ************************************ http://www.opengl.org/code/ http://opengl.org/resources/faq/technical/ http://www.delphigl.com/ (also german) http://www.delphi3d.net http://wiki.delphigl.com/index.php/Tutorial http://www.glprogramming.com/red/chapter01.html Glut is the OpenGL Utility Toolkit, a window system independent toolkit for writing OpenGL programs. GLUT makes it considerably easier to learn about and explore OpenGL programming. and provides a portable API so you can write a single OpenGL program that works on across all machines or platforms. GLUT provides many of the modeling features, such as quadric surfaces and NURBS curves (Non-uniform rational B-splines) and surfaces. Dot, short for "Delphi OpenGL Toolkit", is a utility library designed to speed up OpenGL development. The Dot can help you manage rendering contexts for your application windows and for offscreen rendering buffers. It also contains a large library of vector and matrix math functions, an assortment of image and 3D model loaders. ******************************************************************************** OpenSSL with Delphi Build your own end to end secure channel ******************************************************************************** Reference: http://sourceforge.net/projects/delphiwebstart Using https, secure socket server or an end to end cipher channel is possible with 2 libraries from OpenSSL and 3 components from Indy. Answer: The OpenSSL Project is a collaborative effort to develop a robust, commercial-grade, full-featured, and Open Source toolkit implementing the Secure Sockets Layer (SSL v2/v3) and Transport Layer Security (TLS v1) protocols as well as a full-strength general purpose cryptography library. The project is managed by a worldwide community of volunteers that use the Internet to communicate, plan, and develop the OpenSSL toolkit and its related documentation. Delphi also supports the toolkit since the integratin of Indy Sockets. Indy is developed by a large team of dedicated and active people and there are absolutely no costs to use Indy, or deploy an application that uses Indy. While Indy is open source, Indy also has commercial support options. Atozed Software offers both commercial support, as well as consulting on Indy. Every layer of Indy is pluggable, including RFC replies, encryption, authentication, encodings, and more. Delphi can use OpenSSL library invoking DLL. Indy supports Win and Linux as well the proof you can find in IdSSLOpenSSLHeaders.pas: const {$IFDEF LINUX} SSL_Indy_DLL_name = 'libindy_ssl.so'; {Do not localize...} SSL_DLL_name = 'libssl.so'; SSLCLIB_DLL_name = 'libcrypto.so'; {$ELSE} SSL_DLL_name = 'ssleay32.dll'; SSLCLIB_DLL_name = 'libeay32.dll'; {$ENDIF} Latest compile of the OpenSSL libraries with the Indy modifications built into you can find by intelicom.si. These files have been built from the OpenSSL version 0.9.6m source code from http://www.openssl.org Since intelicom published their recommendation regarding how to compile OpenSSL with the Indy modification, a couple of functions have been added to the OpenSSL exports, requiring the Indy functions to be renumbered. Version 0.9.6g compiled by Intelicom for Indy project seem to work correctly; Finally, you can retrieve the libeay32.dll and ssleay32.dll files from the zip folder and place them in your application’s folder or system32 folder. So how can you start with an OpenSSL Project; I'll show you two possibilities one of them in the Demos\Indy\HTTPServer\ as a straight https example. Second is a whole project from DWS (DelphiWebStart) with a manual and the lib's of OpenSSL included, just download the source from: http://sourceforge.net/projects/delphiwebstart/ and open the projectgroup in d7, so it works with Indy 9.00.18 Both projects are routable, means if your router is set up correctly, SSL has all the routing necessary to achieve ip-forwarding, one way NAT or hoping over the net. Furthermore you have to set the certificate path in the ip_a.ini file: [CERT] ROOTCERT=cert\CA_indy_cert.pem SCERT=cert\client_indy_cacert.pem RSAKEY=cert\client_indy.pem OpenSSL includes a command line utility that can be used to perform a variety of cryptographic functions like generating your machine certificate in [CERT]. First you need a RootCA (selfsigned) // we genrate the private key of the CA: 1. openssl genrsa -des3 -out CA_pvk.pem 1024 // we sign the private to make a certificate of CA 2. openssl -new -x509 -days 365 -key CA_pvk.pem -out CA_crt.pem //CA_indy_cert // we need the host private key 3. openssl genrsa -des3 -out host_pvk.pem 1024 //client_indy.pem // we sign the host private from the CA (machine certificate) 4. openssl req -new key host_pvk.pem -out host_csr.pem 5. openssl ca -out host_crt.pem - in host_csr.pem -cert CA_crt.pem -keyfile CA_pvk.pem //client_indy_cacert.pem in this way we get [CERT] ROOTCERT=cert\CA_crt.pem SCERT=cert\host_crt.pem RSAKEY=cert\host_pvk.pem With the same openssl.exe shell you can verify your certificate chain, mean you will prove the signature of the certificate: But and there is a great caveat you must produce a hash from a ROOT_CA first: - openssl x509 -in CA_crt.pem -hash output 68e57cac then you copy the hash value in a new file name with 0 extension: copy CA_crt.pem 68e57cac.0 Now you're ready to verify: >openssl verify -verbose -CAfile CA_crt.pem -CApath ./ host_crt.pem then you should get host_crt.pem: OK Working with Indy is straight. Indy implements more protocols than any other library and such mechanism like the Interceptor or the IOHandler are easy to use from an existing project, e.g. to enhance it with SSL: IdTCPServer1.IOHandler:= IdServerIOHandlerSSL1; But be carfully, Indy has no standard method to prove the certificate chain, you must do it with the event handler OnVerfyPeer() on your side! The change from indy 9 to 10 is obvious; the difference is that the connections's Intercept property has been moved into the IOHandler in indy 10, so you can call the IOHandler property before calling Connect() also assign the handler at design-time. Do note that the IOHandler has to be assigned before calling Connect() if you want to log the commands that Connect() sends internally: IdTCPServer1.IOHandler.Intercept:= idLogEvent; Its interface follows the blocking model. There are no event based state machines to manage. Everything happens in a sequence, just like accessing a file: IdTCPServer1.Bindings.DefaultPort:= SSL_PORT; IdServerIOHandlerSSL1.OnStatusInfo:= dwsInfoCallback; Hope you'll callBack ;) Last Version is 1.9. Major changes between DWS 1.8 and DWS 1.9: o Now OpenSSL 0.9.8g support ******************************************************************************** Messages between a ClientEXE and a FormDLL ******************************************************************************** This overview describes messages send back from a DLL to a client and explains how to receive and use them in your application. Answer: The code snippet provides a set of mechanisms for facilitating communications between a client and a form DLL. It's possible to check for example in a multiple choice test the answers from a form DLL with the help of win messages. Let's start with the client (EXE), which declares functions of DLL: procedure WMNotifyMsg(var Msg: TMessage); Message wm_User+2001; function createDialog(Parent: THandle): DlgHandle; FAR; EXTERNAL 'mcdialog.dll'; index 1; procedure setQuestion(Handle: DlgHandle; quest, answ1, answ2, answ3: PChar); FAR; EXTERNAL 'mcdialog.dll'; index 2; the index number increases speed slightly, cause the function that is to be dynamically linked doesn't need to be searched by name. So next we have to create the form (questDialog) from the DLL: type dlgHandle = Pointer; private questDialog : dlgHandle; //property Handle: HWnd read GetHandle; from Controls procedure TForm1.FormCreate(Sender: TObject); begin questDialog:= createDialog(handle); S_Stop:= false; timer1.Enabled:= true; timesec:= 1; newQuestion; end; With the handle passed to the DLL we provide a kind of callback. When the client establishes a link by using the createDialog(handle), with the handle property of itself in the Controls structure, the client has requested the DLL send the answer item back each time the item's value in the dllform changes. The questDailog is then a pointer to the dllform. questDialog:= createDialog(handle); In such cases, the DLL sends the new value of the data item in the specified format by PostMessage(ParentHandle, WMNotifyMsg, answer, 0) and sends the client back a WMNotifyMsg message, as shown in the next example: from the DLL --------------------------------------------------- We use the same msg identifier like the client: const WMNotifyMsg = wm_User+2001; In the dll we create the form and save the client handle (parentHandle) which is the handle to the the window that received WM_NOTIFY. function createDialog(Parent: THandle): TfrmQuest; export; begin result:= TfrmQuest.Create(Application); result.ParentHandle:= Parent; result.Show; end; Now we're able to proceed a simple message to the client: procedure Tfrmquest.BitBtnIgnoreClick(Sender: TObject); begin //50 is coded as ignore an answer from DLLForm to EXE notify(50); end; The notify procedure in the DLL is the name of the remote procedure which sends messages to the client: procedure Tfrmquest.Notify(answer: WORD); begin if ParentHandle <> 0 then PostMessage(ParentHandle, WMNotifyMsg, answer, 0); end; The WM_NOTIFY message informs the parent window of a control that an event has occurred in the control or that the control requires some kind of information. procedure Tfrmquest.btnnextClick(Sender: TObject); begin Notify(ansGroup.ItemIndex+1); end; At least the client proceeds with the calculation of answers: procedure TForm1.WMNotifyMsg(var Msg: TMessage); begin if not S_Stop then begin; Inc(qnumber); if CORRECT_[questNr] = Msg.WParam then Inc(correct) else begin if Msg.WParam = 50 then inc(ignored) else if msg.WParam <> 0 then Inc(wrong); end; calcAnswers; end; end; clientEXE formDLL I I I------createDialog(handle)---------------> I parenthandle I I I <------questDialog--------------------- I I I---show--I I I<--------I I------newQuestion------------------------> I I ---notify-I I I<--------I I <------postMessage(parentHandle)---- I I <------WMNotifyMessage(Msg)-------------- I Tip: When you want to open n-Forms in a form-dll with ShowModal() but you and others don't want to see each form in the task-manager, you can switch it off with: procedure CreateParams(var params: TCreateParams); override; begin inherited createParams(Params); params.ExStyle:= WS_EX_PALETTEWINDOW; end; ******************************************************************************** How to use the command line compiler dcc32.exe or running like a steam machine ******************************************************************************** The great advantage is to have a unified configuration file of the project configuration settings when you work in a team or on different machines. So you are independent of installed packages, options or components in the IDE. The magic of the command-line compiler. Answer: The great advantage is to have a unified configuration file of the project configuration settings when you work in a team. So you are independent of installed packages, options or components in the IDE. More you are in a certain context independent of the machine and each developer runs the same configuration file over the INTRANET. So we never again have to listen on sentences like this: On my machine the app is just fine ;) Further small non visual changes are possible in a simple editor and just start the clc. The command-line compiler (clc) lets you invoke all functions of the IDE from a command line shell. Running the clc from the command line prompt using the syntax: dcc32 [options] filename [options] for example you work with a project named mcclient.dpr you type in the shell or start the equivalent batch: D:\Programs\Borland\Delphi7\Bin\dcc32 -B D:\FRANKT~1\DELPHMAX\LOADER\HEXER3\mcclient.dpr Then you get something like this: -------------------------------------------------------------------- Borland Delphi Version 15.0 Copyright (c) 1983,2002 Borland Software Corporation 14 lines, 1.67 seconds, 337236 bytes code, 7661 bytes data. -------------------------------------------------------------------- where options are zero or more parameters that provide information to the compiler and filename is the name of the source file to compile. Option -B means to build all units. Note: The command-line compiler options are not case-sensitive. To display a help screen of command-line options and syntax you just type dcc32. If the source text contained in filename is a program, the compiler creates an executable file named filename.exe. If filename contains a library, the compiler creates a filename.dll or if filename contains a package, the compiler creates filename.bpl or with a unit, the compiler creates filename.dcu. You can specify a number of options for the clc. An option consists of a slash (/) or hyphen (-) immediately followed by an option letter. In some cases, the option letter is followed by additional information, such as a number, a symbol, or a directory name. So the magic is found in a configuration file named DCC32.cfg or in our case mcclient.cfg which is build by Delphi. The compiler searches for a dcc32.cfg in the compiler executable directory, then for dcc32.cfg in the current directory, and then finally for projectname.cfg in the project directory. In our case we deal with two configuration files: - DCC32.cfg for Delphi specific or general options and libraries - mcclient.cfg for Project specific options and libs You can set up a list of options in a configuration file, which will then be used in addition to the options entered on the command line. Each line in configuration file corresponds to an extra command-line argument inserted before the actual command-line arguments. Thus, by creating a configuration file, you can change the default setting of any command-line option. So you have to know that the clc lets you enter the same command-line option several times, ignoring all but the last occurrence. This way, even though you've changed some settings with a configuration file, you can still override them on the command line. When dcc32 starts, it looks for DCC32.CFG in the current directory. If the file isn't found there, dcc32 looks in the directory where DCC32.EXE resides. Here's an example DCC32.CFG file, defining some default directories for include, object, and unit files, and changing the default states of the $O and $R compiler directives: -ID:\DELPHI\INC;D:\DELPHI\SRC -OD:\DELPHI\ASM -UD:\DELPHI\UNITS -$R+ -$O- -aWinTypes=Windows;WinProcs=Windows;DbiProcs=BDE;DbiTypes=BDE;DbiErrs=BDE -u"D:\Programme\Borland\Delphi7\lib";"D:\Programme\Borland\Delphi7\lib\Obj" You can also type: make -f -B Hexer2projectgroup.bpg to compile all the targets in the project group! Many version control systems are also indepedent of the IDE and you can control them in a make file. At last an example of our project configuration file with some explanations: mcclient.cfg ----------------------------------------------------------- -$A8 -$B- -$C+ -$D+ -$E- -$F- -$G+ -$H+ -$I+ -$J- -$K- -$L+ -$M- -$N+ -$O+ -$P+ -$Q- -$R- -$S- -$T- -$U- -$V+ -$W- -$X+ -$YD -$Z1 -GP -cg -H+ -W+ -M -$M16384,1048576 -K$00400000 -LU"W:\franktech\Delphmax\Loader\hexer3\rpackages\" //The -U option lets you specify additional directories in which to search for run time packages. -U"W:\franktech\Delphmax\Loader\hexer3\units\" //The -U option lets you specify additional directories in which to search for units. -LE"W:\franktech\Delphmax\Loader\hexer3\bpl" //This option tells the clc where to put the file it creates when it compiles a package. -LN"W:\franktech\Delphmax\Loader\hexer3\bpl" //This option tellsl the clc where to put the .dcp (package symbol info) file it creates. -E"D:\franktech\Delphmax\Loader\hexer3\executables" //This option lets you tell the clc where to put the executable file it creates. -w -UNSAFE_TYPE -w -UNSAFE_CODE -w -UNSAFE_CAST //Disabled Output warning messages one last compile on max.kleiner.com/download/pascalspeed.exe you can find a classic clc assembler app (10kb) which draws a fire on your screen ;) this is my one cent contribution to the 50th article. ******************************************************************************** Switch between one or many instances ******************************************************************************** Question: Under Win16, you could check the value of hPrevInst at startup. Under Win32, on hPrevInst you can't rely upon; we can create a uniquely named mutex. But if you want to control instances of forms (not the app) at runtime or change the behaviour at starttime with the commandline, here's a straight solution: The default check of the function is to allow one instance. But you can control the start of many instances at the commandline prompt with: :> yourApplication.exe /allowinstances The function itself: If the window is running and you don't want multiple instance at the command line then no further creation of another instance is possible; the function letAllowInstances() returns false else true. uses HWND.windows, FindCmdLineSwitch.SysUtils function letAllowInstances(const mainForm, cmdLine: PAnsiChar): boolean; var keyWindow: HWND; begin result:= false; keyWindow:= 0; keyWindow:= FindWindow(mainForm, NIL); //check if delphi is at design time and creation is allowed if FindWindow('TAppBuilder',NIL) <> 0 then keywindow:= 0; if (keyWindow <> 0) and not FindCmdLineSwitch(cmdLine, true) then //then set existing instance ShowWindow(keyWindow, SW_SHOWNORMAL) else result:= true; end; and the integration call of the func() into the project-file: begin if letAllowInstances('TKeyHandlingDialogForm', 'allowinstances') then begin Application.Initialize; Application.CreateForm(TKeyHandlingDialogForm, KeyHandlingDialogForm); Application.CreateForm(TPasswordInputDialog, PasswordInputDialog); Application.CreateForm(TGeneratingKeyDialog, GeneratingKeyDialog); Application.Run; end; end. FindWindow will return a valid handle if it finds the window class in the current active session (desktop). While using a mutex will prevent the user to start an application allready running in another session. Link to mutex solution: http://www.delphi3000.com/articles/article_870.asp?SK=mutex ******************************************************************************** Multilanguage Easy Translator ****************************************** Uploader: Max Kleiner Company: kleiner kommunikation Reference: http://www.softwareschule.ch/download/pascal_multilanguage.pdf Component Download: http://www.softwareschule.ch/download/delphi_multilang_demo.zip Question/Problem/Abstract: For many projects you need to localize your application. An easy solution for all delphi platforms will be given no license or special tools needed. Just a small component and one straight resource file. I know there's a lot of solutions to localize, but this one... Let's start with the advantages: 1. Separation of strings from the form at design time 2. All languages linked in one executable at runtime (if necessary) 3. Lesser overhead for forms and easy to understand cause resources 4. Language change at runtime possible 5. No licence, component or additional tool needed 6. Runs on all Delphi versions, Lazarus and Kylix! 7. Whole translator engine in one unit (component) 8. Fast and easy to deploy 9. Easy change management of the Stringtable (just edit and compile) 10. Multiple use of one string to many controls possible 11. Extensible to any string or control 12. Event OnLanguagChanged() implemented 13. External Resource DLL at runtime 14. Switch between static linking or dynamic loading 15. Demo available for all delphi platforms (win32, CLX, dotnet, freepascal): for Delphi, dotnet & Kylix http://www.softwareschule.ch/download/delphi_multilang_demo.zip for freepascal/Lazarus http://www.softwareschule.ch/download/laz_multilang.zip The most obvious task in localizing an application is translating the strings that appear in the user interface from a form, a component or a database. To create an application that can be translated without altering code everywhere, the strings in the user interface should be isolated into a single module or file. In our case we put all the strings in one resource file called *.rc This resource file will be compiled with D:\Programme\Borland\Delphi7\Bin\brc32.exe -r filename.RC or straight forward in delphi project main form with the following compiler directive {$R 'filenameSTR.res' 'filenameSTR.RC'} so you don't need a resource batch or the resource compiler! (just in case of problems you get more error logs) At start- or at runtime we call objMultilang:= TMultiLangSC.Create(self); objMultilang.LanguageOffset:= 1000; //objMultilang.LanguageOffset:= objMultilang.currentLanguage; and all the well prepared strings (caption, hint, lines ...) will be translated. Note: strings from *.dfm are no longer visible, cause they now come from the linked resource file! Note: if no registry or ini will be defined you can set and call the language straight forward: case langRGroup1.itemindex of 0: objMultilang.LanguageOffset:= 0; 1: objMultilang.LanguageOffset:= 1000; 2: objMultilang.LanguageOffset:= 2000; 3: objMultilang.LanguageOffset:= 3000; 4: objMultilang.LanguageOffset:= 4000; end; Each language can have 999 strings {'D': Result:=0; 'E': Result:=1000; 'F': Result:=2000; 'I': Result:=3000; 'S': result:=4000;} Now a simple example of a resource file *.rc: (you can add this file in your delphi project) STRINGTABLE { 3, "Arbeiten im Team" 1003, "work in team" 2003, "travailler en groupe" 3003, "lavorare nel gruppo" 4003, "trabajo en equipo" } In this case we have to assign the value 3. In practice each language has its own section of STRINGTABLE. STRINGTABLE { 1, "Für die Installation brauchen Sie Admininstratoren-Rechte." 2, "Setup kann nicht gestartet werden!" 3, "&Schliessen" } /**************************************************************************** ** English *****************************************************************************/ STRINGTABLE { 1001, "You require administrator rights for the installation." 1002, "Impossible to start setup!" 1003, "&Close" } /**************************************************************************** ** French *****************************************************************************/ STRINGTABLE { 2001, "Pour l'installation, vous avez besoin des droits d'administrateur." 2002, "Impossible de lancer le programme d'installation !" 2003, "&Fermer" } /**************************************************************************** ** Italian *****************************************************************************/ STRINGTABLE { 3001, "Per l'installazione sono necessari diritti d'amministratore." 3002, "L'installazione non può essere avviata!" 3003, "&Chiudere" } /**************************************************************************** ** Spain *****************************************************************************/ STRINGTABLE { 4001, "Necesita derechos de administrador para la instalación." 4002, "No se puede iniciar la instalación" 4003, "&Cerrar" } Preparation of the form and the resource file: The magic behind is the tag property of a control. It stores an integer value as part of a component and has no predefined meaning. The Tag property is provided for the convenience of developers so in our case to define a relationship to the resource file! Changing the language of captions of controls on a form to a particular language means the tag property of the controls have to be set to values corresponding with the according resource strings. Leaving a Tag value 0 means that the caption of the according control isn't changed. Note that languages are distinguished by an offset of a multiple of 1000. For instance german is 0, English has an offset of 1000, French one of 2000 and so on. Important: each tag has a number between 0..999 : object bbtClose: TBitBtn, tag = 3 The component will add then the offset depending the current language! Currently the component (class TMultilangSC of the component MultilangTranslator) supports controls defined in : procedure ChangeComponent(theComponent: TComponent; const theLanguageOffset : integer); This concept can be easily extended to other controls not yet listed in the procedure ChangeComponent(). How it works: ------------------------------------------------------------------- Delphi automatically creates a .dfm (.xfm in CLX applications) file that contains the resources for your menus, dialogs, and bitmaps (Streaming and filing of resources are inherited from TPersistent). After a component reads all its property values from its stored description, it calls a virtual method named Loaded, which performs any required initializations. The call to Loaded occurs before the form and its controls are shown, so you don't need to worry about initialization causing flicker on the screen. The sequence is the following: function TMultiLangSC.currentLanguage: integer; property LanguageOffset: integer read fLanguage write SetLanguage; SetLanguage(const Value: integer); procedure TMultilangSC.ChangeLanguage(const languageOffset: integer); ChangeComponent(GetTopComponent,languageOffset); if Assigned(fOnLanguageChanged) then fOnLanguageChanged(Self); Here's an extract of the important method ChangeComponent: begin if theComponent.ComponentCount>0 then begin for x:= 0 to theComponent.ComponentCount-1 do ChangeComponent(theComponent.Components[x],theLanguageOffset); end; if theComponent.tag <> 0 then begin if (theComponent is TForm) then (theComponent as TForm).Caption:= GetResourceString(theComponent.tag) else if (theComponent is TLabel) then (theComponent as TLabel).Caption:= GetResourceString(theComponent.tag) else if (theComponent is TImage) then (theComponent as TImage).Hint:= GetResourceString(theComponent.tag) ..... To initialize a component after it loads its property values, override the Loaded method. This is done when you use the translator component from the component palette. In addition to these obvious user interface elements, you will need to isolate any strings, such as error messages or string literals, that you present to the user. String resources are not included in the form file. You can isolate them by declaring constants for them using the resourcestring keyword. In our case we put them also in the resource file! Therefore its possible to use the function GetResourceString at any time to load MultilangTranslator strings from the language resource file. This is especially necessary if the Translator should be used independently from a TForm or a visual component. This is how we call the API function: showmessage(objMultilang.GetResourceString(21)); or in a memo component without tags: memInfo.lines.Add(objMultilang.GetResourceString(11)); memInfo.lines.Add(''); memInfo.lines.Add(objMultilang.GetResourceString(12)); Isolating resources simplifies the translation process. The next level of resource separation is the creation of a resource DLL. A resource DLL contains all the resources and only the resources for a program. Resource DLLs allow you to create a program that supports many translations simply by swapping the resource DLL. Note: The Translation Manager in Delphi provides a mechanism for viewing and editing translated resources. To open the Translation Manager from within the IDE, choose View|Translation Manager. Before you can use the Translation Manager in the IDE, you must add languages to your project using the Resource DLL wizard. You will get n directories and n files with a big overhead and complicated rules! ------------------------------------------------------------- Update 1: If we want to change the resfile at runtime (means no compilation) we have to build a resfile DLL in a new project: library reslang; uses SysUtils; {$R 'filename.res'} begin end. Second we have to load the DLL and adjust the HInstance call in our component (function getResourceString()), here's the proof of concept (so we can get all resources from a runtime dll!): procedure TForm1.FormCreate(Sender: TObject); const badDLLload = 1; var h: tHandle; pP: array[0..255] of char; begin h:= loadLibrary('reslang.dll'); if h <= badDLLload then showmessage('no dll load') else begin if loadString(h, 5, pP, sizeof(pP)) > 0 then showmessage((pP)); //... end; end; Update 2, Version 1.4: A port for CLX has to be done, small changes with a sound interface: function TMultilangSC.GetResourceString(Ident: Integer): string; var StrData: TStrData; begin StrData.Ident:= Ident + LanguageOffset; StrData.Str:= ''; EnumResourceModules(EnumStringModules, @StrData); Result:= StrData.Str; end; ---------------------------------------------------------- There's an introduction show on http://max.kleiner.com/download/multilang_intro.pdf (630kb) or a technical report on german at: http://www.softwareschule.ch/download/pascal_multilanguage.pdf (124kb) Update always on: http://www.softwareschule.ch/download/laz_multilang.zip ******************************************************************************** Params in a Web-Query at run time ******************************************************************************** SQL-Query (web-request) complete in code section Search Keys: delphi delphi3000 article SQL Params Webqueries Times Scored: Company: kleiner kommunikation Reference: http://max.kleiner.com/uml_buch.htm Component Download: http://max.kleiner.com/download/webqueries.zip Question/Problem/Abstract: Property editors are fine, but code is more readable and maintainable if we put the data out from TQuery's ressource direct in the implementation section. So the serialisation is important. Answer: 1. It's important to assign the query first (SQL:= aQuery) and second assign the Params including definition of dataType: procedure TModWebBroker.execWebQuerySQL; var aQuery: TStringList; begin try aQuery:= TStringList.Create; aQuery.clear; Build_kontoSQLString(aQuery); with qryWebCust do begin SQL.Clear; SQL:= aQuery; params[0].name:='clientname'; params[0].dataType:=ftstring; OPEN; end; //with finally aQuery.Free; end; //try end; 2. We build the query in a own procedure, so it's open for change (programming for change ;)): procedure TModWebBroker.Build_kontoSQLString(var aQuery: TStringList); begin with aQuery do begin Add('SELECT cust_no, customer, descript, amount'); Add('FROM account, customer, acctype'); Add('WHERE account.cust_no = customer.cust_no'); Add('AND account.acc_type = acctype.acc_id'); Add('AND UPPER(customer) LIKE UPPER(:clientname)||'#39'%'#39); Add('ORDER BY amount'); end; end; 3. We get the parameter from a browser through a request-object in a ISAPI-DLL: (The example "webqueries" is available on the net,now) procedure TModWebBroker.kontoStand1Action(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var paraField: string; begin with qrywebAccount do begin paraField:=(Request.QueryFields.Values['clientname']); if Pos (uppercase(paraField), uppercase(Content)) > 1 then Response.Content:= Content else Response.Content:= Format('Client"%s" not found!' + '', [Request.QueryFields.Values['clientname']]); end; //qrywebAccount end; Running Application already exists ******************************************************************************** find out if an application is already running Search Keys: delphi delphi3000 article application once solo exists singleton oneinstance Times Scored: Question/Problem/Abstract: how do you prevent an application from starting more than one time ? Answer: There is already stuff about that topic (ID=1081, ID=278), but this solution keeps it simple and efficient enough... We set a mutex in the project-file, so the next time the same application is starting, the mutex provides an error-flag: program Broker; uses Forms, windows, dialogs, bankData in 'bankData.pas' {DataModule1: TDataModule}, bankLogic in 'bankLogic.pas', bankControll in 'bankControll.pas', bankView in 'bankView.pas' {frmBank}, bankView2 in 'bankView2.pas' {frmDataView2}, trans in 'trans.pas' {frmTrans}, bankexport in 'bankexport.pas', zinsView in 'zinsView.pas' {frmIncome}; {$R *.RES} var hMutex: THandle; begin hMutex:= createMutex(nil, true, 'broker'); if getLastError = ERROR_ALREADY_EXISTS then begin showmessage(' already running'); HALT; end; Application.Initialize; Application.Title := 'BROKER'; Application.CreateForm(TfrmBank, frmBank); Application.CreateForm(TDataModule1, DataModule1); Application.CreateForm(TfrmTrans, frmTrans); Application.Run; if hMutex <> 0 then closeHandle(hMutex); end. Validate an object ******************************************************************************** Search Keys: delphi delphi3000 article design-critics object-validating conventions Times Scored: Question/Problem/Abstract: How do you check if an object fits your condition and that of the compiler ? Answer: From time to time you want to be sure, if an object fits your conditions like naming conventions, robustness or you want the compiler find no further errors. The function checkOBJ determines whether a passed object, with a boolean returned from the function, represents an error condition. function TTrans.checkOBJ(aObject: TObject): boolean; var str: string; i: integer; begin result:= false; if aObject= NIL then exit; try str:=ansiUppercase(aObject.classname); if str= '' then exit; for i:= 1 to length(str) do if not (str[i]in['0'..'9','A'..'Z','_']) then exit; aObject.classType; if aObject.InstanceSize < 1 then exit; aObject.ClassnameIs('TObject'); result:=aObject.ClassNameIs(aObject.Classname); except exit; end; end; You can call it then with an assert, so you get during the development a bit of quality assurance ;). accObj:= TAccount.createAccount(FCustNo, std_account); assert(aTrans.checkOBJ(accObj),'bad condition with OBJ'); //trans Use Assert as a debugging check to test that conditions assumed to be true are never violated. Assert provides an opportunity to intercept an unexpected condition and halt a program rather than allow execution to continue under unanticipated conditions. Callback function with a DLL ******************************************************************************** learn more about function types Search Keys: delphi delphi3000 article borland vcl code-snippet function-type callback controller Times Question/Problem/Abstract: how to make a DLL like a controller and how to write a callback-function with a DLL Answer: Callback function with a DLL ----------------------------- First a brief definition: A callback function is a function which you write, but is called by some other program or module, such as windows or DLL's. For example a DLL (like a watchdog) controls many clients, so when a certain event occurs from the DLL that you called once, the callback function in the client is called (being passed any parameters or signals you need) and when the DLL-callback has completed, control is passed back to the controller-DLL or the client. By the way, there is almost no possibilitie to make it more OO-like with a class, cause a callback is always an address of a standard procedure or function. So the reason for this is that windows does not pass back any reference to SELF (means the instance of the class), which is used by classes when deciding which method from the instance to work with. Let's get back to the framework and create a callback function, you must first: 1. declare a function type 2. the function itself 3. define the DLL reference 4. then implement the function in the client 5. and call the DLL: Callback example in client unit ----------------------------------------------- interface... 1. TCallBackFunction = function(sig: integer):boolean; 2. function callME(sig: integer):boolean; implement... 3. procedure TestCallBack(myCBFunction: TCallBackFunction); register; external('watchcom.dll'); 4. function callMe(sig: integer): boolean; begin {whatever you need to do, case of...} showmessage('I was called with'+ inttostr(sig)); end; 5. procedure TForm1.Button1Click(sender: TObject); begin testCallBack(callMe); //subscribe function in DLL end; Callback in the DLL ----------------------------------------------- In the DLL you would also declare a function type and a procedure (or function) itself, so use it like this: type TCallBackFunction = function(sig: integer):boolean; procedure TestCallBack(clientFunc: TCallBackFunction); var sigAlive: boolean; begin {timer stuff... set the signal...} if(clientFunc(55)) then sigalive := true; end; exports TestCallBack; ************* Simple Sequence Diagram************** Client DLL ¦ TestCallBack(clientFunc) ¦ ¦---------------------------------------------->¦ ¦ clientFunc.callMe(sig) ¦ ¦<----------------------------------------------¦ ¦ ¦ ¦ true (or something to return) ¦ ¦---------------------------------------------->¦ ¦ ¦ max kleiner callback (See also ID582) Managing a lot of Forms ******************************************************************************** Form Management with an Array and Class References Search Keys: delphi delphi3000 article class-reference form-management forms Times Scored: Question/Problem/Abstract: How do you controll hundreds of forms by number in a main unit ? Answer: Suppose you have a call from a dear developer that had about 100 forms that he have to create and run. So you tell him, create and run it by number in, here's a solution to this problem... The following part is to be placed on the main form's unit, maybe a frmController unit and the purpose is to have a couple of arrays that are referenced by number and then accessible to manipulate: const maxForms = 100; var frmController: TForm1 frmArray: array[1..maxForms] of TForm; frmRefArray: array[1..maxForms] of TFormClass; implementation uses Unit7; // and all of the units procedure TForm1.btnfrmController(sender : TObject); begin ... // iterating or indexing as you like frmArray[7]:= frmRefArray[7].create(self); frmArray[7].showModal; // whatever you need frmArray[7].free; end; The next step is, each form must register itself in the array of the controller or main form unit. This can be done at load time or runtime, let's get straight to the implementation part: unit Unit7; implementation uses frmControllerU; procedure TForm7.FormCreate(sender: TObject); frmArray[7]:= self; end; initialization frmRefArray[7]:= TForm7 //hard coded The last part means that you must tell the array which class has to be associated with which array element. So you get by ObjectPascal the classReference, another way that this can be done is by using an extra unit where all of the form's information is centralized and easier to maintain, but the idea remains the same. ps: If the form is already instantiated, then you find it through the TScreen object (don't scream use TScreen ;): for i:= 0 to screen.FormCount - 1 do if screen.forms[i].className = 'TForm7' then ... DLL+ ******************************************************************************** Returning Classes from a DLL Search Keys: delphi delphi3000 article DLL COM_light Pattern Framework OO-DLL Interface Times Scored: Company: kleiner kommunikation Reference: http://max.kleiner.com/uml_buch.htm Component Download: http://max.kleiner.com/download/dllplus.zip Question/Problem/Abstract: Export an object-reference from a DLL is one approach to get real OO-access to a DLL. The DLL must create and return the object, so the client gets the methods without encapsulating. Let's see how this framework, called DLL+, works: Answer: There is also an example uploaded with UML-Diagrams (*.tif) from ModelMaker. We always work with MM since we had to redesign a big project. First, we have to built an abstract class in a separate unit (see below the same with a real interface). You might consider this unit like an interface: unit income1; interface type IIncome = class public function GetIncome(const aNetto: Currency): Currency; virtual; abstract; procedure SetRate(const aPercent, aYear: integer); virtual; abstract; function queryDLLInterface(var queryList: TStringList): TStringList; virtual; abstract; end; Second we built the DLL in a new unit (*.dpr) with the corresponding class, which has to implement the methods from the interface: type TIncomeReal = class(IIncome) private FRate: Real; public constructor Create; function GetIncome(const aNetto: Currency): Currency; override; procedure SetRate(const aPercent, aYear: integer); override; ... 3. And now comes the export, cause objects in DLL+ are created by calling a global Constructor function, you can see that returning objects from the function that creates them is acceptable by the client: {-------------------------------------------------------------} function CreateIncome: TIncomeReal; stdcall; begin result:= TIncomeReal.Create; end; exports CreateIncome resident; begin {fake} end. {-------------------------------------------------------------} 4. At last we take a look at the client. In managing objects the client consider who owns the object and is responsible for freeing it up: Uses income1; private IncomeRef: IIncome; //member ... function CreateIncome:IIncome; stdcall; external('income.dll'); ... procedure TfrmIncome.FormCreate(Sender: TObject); begin IncomeRef:=createIncome; end; 5. So the access is easy, stable and improves maintanance of the DLL. It should be mandatory to implement a function called queryDLLInterface, in order to reproduce the interface and all the parameters in case of lost (see example). procedure TfrmIncome.BitBtnOKClick(Sender: TObject); begin incomeRef.SetRate(strToInt(edtZins.text), strToInt(edtJahre.text)); cIncome:= incomeRef.GetIncome(StrToFloat(edtBetrag.Text)); edtBetrag.text:= Format('%m',[cIncome]); end; See also: http://www.delphi3000.com/articles/article_1416.asp or the book: "UML mit Delphi" (in german) Calling an Interface ------------------------------------------------------------------- 1. Now the client calls an InterfaceReference: private incomeIntRef: IIncomeInt; procedure TfrmIncome.BitBtnOKClick(Sender: TObject); begin incomeIntRef:=createIncome; try with incomeIntRef do begin if QueryInterface(IIncomeInt, incomeIntRef) = S_OK then begin SetRate(strToInt(edtZins.text), strToInt(edtJahre.text)); cIncome:=strTofloat(edtBetrag.text); 2. The Unit Income1 is enlarged with the interface: IIncomeInt = interface (IUnknown) ['{DBB42A04-E60F-41EC-870A-314D68B6913C}'] function GetIncome(const aNetto: Currency): Currency; stdcall; function GetRate: Real; function queryDLLInterface(var queryList: TStringList): TStringList; stdcall; procedure SetRate(const aPercent, aYear: integer); stdcall; property Rate: Real read GetRate; end; 3. The DLL exports now a real Interface-Pointer: TIncomeRealIntf = class (TInterfacedObject, IIncomeInt) private FRate: Real; function Power(X: Real; Y: Integer): Real; protected function GetRate: Real; public constructor Create; destructor destroy; override; function GetIncome(const aNetto: Currency): Currency; stdcall; function queryDLLInterface(var queryList: TStringList): TStringList; stdcall; procedure SetRate(const aPercent, aYear: integer); stdcall; property Rate: Real read GetRate; end; function CreateIncome: IIncomeInt; stdcall; begin result:= TIncomeRealIntf.Create; end; 4. When n-classes implements one interface push a parameter to the export routine in the DLL: function CreateIncome(intfID: byte): IIncomeInt; stdcall; begin case intfID of 1: result:= TIncomeRealIntf.Create; 2: result:= TIncomeRealSuper.Create; 3: result:= TIncomeRealSuper2.Create; end; end; exports CreateIncome; client-calling looks like this: incomeIntRef:=createIncome(3); What's DelphiScript ? ******************************************************************************** Short Overview to PascalScript and similiar interpreters Search Keys: delphi delphi3000 article borland vcl code-snippet scripting scriptrecorder delphiscript Company: kleiner kommunikation Reference: www.remobjects.com, www.dream-com.com Question/Problem/Abstract: There are tools which support DelphiScript or PascalScript. Or other tools are interpreter which implement sort of pascal-scripting. What's the aim of these tools and where you can find it. Answer: Maybe you have noticed, that I put the article in the Object Pascal category but I could also put it in the OLE category. So in a short way, DelphiScript is an OLE scripting language, like VBScript or JScript. Depending on the history, the language root is Standard Pascal and not ObjectPascal. Extensions ---------- There are extensions which supports OLE objects and the OLE variant type. It then deals with all Standard Pascal types as OLEVariant, or it does support them, e.g. not supported are pointers (of course), files, set operators (replaced with procedures), records (but records are replaced by untyped arrays). Therfore you can declare the type of a variable or parameter, it is allowed, but has no effect. Another extension supports exception handling the same way as OPascal does. Bref, all variables are of the OleVariant type. Comparison ----------------- Delphi Script and VBScript are case insensitive while JavaScript is case sensitive. Delphi Script is strongly typed while VBScript and JavaScript is loosely typed. This means variable declaration is mandatory in Delphi Script, but variable declaration is not needed in VBScript and JavaScript. Delphi Script uses square brackets to access data property and round brackets for function calls. VBScripts and JavaScript do not make this distinction. For example, the following statements are equivalent: (Delphi Script) sum := Data1.Value[0] + tq_min(Data2.Value[0],0); (VBScript) sum = Data1.Value(0) + tq_min(Data2.Value(0),0) (JavaScript) sum = Data1.value(0) + tq_min(Data2.Value(0),0); A Tool Example -------------- The easiest way to begin scripting is to use a recorder. Later you can edit the script and so on. For example DelphiScript support is built into AQTest: www.automatedqa.com It's a highly recommended tool that comes out of the box with 19 profilers. The two that we are interested in most, is "listing unused units", and "identifying who calls what method" are included. (The method call profiler display its info through a diagram. fantastic cool.;) So let's get back to DelphiScript(DS) or PascalScript (PS). DS or PS code can be read by Delphi. DS supports almost all Standard Pascal operators and routines, minus those that deal with type conversion or I/O, type conversion are always implicit. Interesting is, there are units. But as DS is meant for scripting and selfstanding routines, units are simply groupings of scripts in one file. That means, no unit structure (interface, implementation etc.) is possible. 1. Concrete Implementation Dream Scripter ------------------------------------------ Dream Scripter supports Delphi Script language - the subset of Object Pascal. Before execution the script is compiled to native processor code and that's why Delphi Script is much faster than other scripting languages. Another cool feature - you don't need to have any scripting engine or extra DLLs on the user computer to use Delphi Script. Dream Scripter is written entirely with Delphi. You don't need any extra DLLs or OCXs. Source is compatible and tested with Delphi 3, Delphi 4, Delphi 5, C++ Builder 3, C++ Builder 4, and C++ Builder 5 We are very happy to announce the release of the first version of our very popular Dream Scripter for Delphi 6 (CLX) and Kylix too. The most exciting feature of Dream Scripter: you can use any object, function, or variable you have in your Delphi source within your scripts. Thanks to the import program that comes with Dream Scripter. It analyzes your sources and generates import files. Just add the resulting units to your project uses section and you can use everything that was declared in the source units. We provide imported files for all VCL units so you can use VCL in your scripts right away. www.dream-com.com 2. Concrete Implementation PascalScript ------------------------------------------ We are happy to announce that Carlo Kok (formerly Innerfuse PascalScript) has joined RemObjects Software, and that as of today PascalScript has been merged and improved into the product line of RemObjects Software, Inc. PascalScript (PS) has a bright future, e.g: With the release of V0.845 of X-Force, which will be soon available, there will be extensive changes in the script language X-Script. The special for X-Force developed script language will be replaced by the extensive and flexible Pascal Script by Carlo Kok (www.remobjects.com). With only one code line you can import an excisting Delphi function in PS and could be called from there. PS supports the direct interaction with Delphi objects and Delphi records so that the expansion will be easier in the future. PS is a free scripting engine that allows you to use most of the ObjectPascal language within your Delphi projects at runtime. Written completely in Delphi, it's composed of a set of units that can be compiled into your executable, eliminating the need to distribute any external files. The script engine never calls Application.ProcessMessages by itself, so your application may hang, while the script is running. To use the component version of PS, you must first place it on your form or data module, set or assign the script property, call the compile method and call the execute method. A scripting engine allows an end user to customize an application to his or her needs without having to recompile it. In addition, you can update your applications by just sending a new script file. http://www.remobjects.com Short example of how to use it: Maybe you want to set a script which you can change at runtime like this: program ScriptTest; // first interpreter of runmax var i,a: byte; begin for i:= 1 to 10 do begin a:= random(150); writeln(inttostr(a)); end end. Next you have to register functions like random or writeln in your exe: function myrandom(const a: byte): byte; begin result:= random(a); end; procedure MyWriteln(const s: string); begin Form1.Memo2.Lines.Add(s); end; procedure TForm1.PSScriptCompile(Sender: TPSScript); begin Sender.AddFunction(@MyWriteln, 'procedure Writeln(s: string);'); Sender.AddFunction(@myrandom, 'function random(const a: byte): byte'); Sender.AddRegisteredVariable('Application', 'TApplication'); Sender.AddRegisteredVariable('Self', 'TForm'); Sender.AddRegisteredVariable('Memo1', 'TMemo'); Sender.AddRegisteredVariable('Memo2', 'TMemo'); end; The rest to compile and execute in a demo called /Samples/TestApp is available on http://www.remobjects.com/articles/ 3. Conrete Implementation PasScript ----------------------------------------- PasScript is an interpreter of a vast subset of the OP (ObjectPascal) language which supports all OP data types except interfaces. This subset was extended by the Poly data type that allows you to operate with dynamic data structures (lists, trees, and more) without using pointers and apply Pascal language in the Artificial Intelligence data domain with the same success. PasScript supports more wide subset of the OP language. You can use such concepts as units, default parameters, overloaded routines, open arrays, records, sets, pointers, classes, objects, class references, events, exceptions, and more in a script. PasScript syntax is 100% compatible with OP. All calling conventions register, pascal, stdcall, cdecl, safecall are supported Script-defined handlers for Windows messages Script-defined callback functions. For example, you can define WindowProc function directly in a script More flexible importing Delphi classes. You can use instances of any Delphi class in a script create new instances of a Delphi class in a script and destroy them create new PasScript class in a script which inherits a Delphi class. More wide possibilities regarding the event handlers. You can create script-defined event handler for Delphi defined event and vice versa. You can pause, resume and terminate scripts and PasScript allows you to control an OLE Automation server. The TPasScript component allows you to embed the interpreter into your Delphi, Kylix or C++ Builder application, so you can extend and customize the application without having to recompile it. http://users.ints.net/virtlabor/passcript/ 4. Conrete Implementation DelphiScript in ASP.NET -------------------------------------------------- One of the latest invention is to use OP as a script language for ASP.NET: Recognizing Delphi as a script language works like this: The first step in getting support for ASP.NET is making sure it recognizes Delphi as a scripting language, and knows how to invoke the Delphi for .NET compiler for the various ASP file types. ASP.NET will look for a web.config file in the root of whatever virtual directories you set up for IIS. Here are the contents of this file for using Delphi as a scripting language with ASP.NET. The first step in getting support for ASP.NET is making sure it recognizes Delphi as a scripting language, and knows how to invoke the Delphi for .NET compiler for the various ASP file types. ASP.NET will look for a web.config file in the root of whatever virtual directories you set up for IIS. Here are the contents of this file for using Delphi as a scripting language with ASP.NET. configuration> system.web> compilation debug="true"> assemblies> add assembly="DelphiProvider" /> assemblies> compilers> compiler language="Delphi" extension=".pas" type="Borland.Delphi.DelphiCodeProvider,DelphiProvider" /> compilers> compilation> system.web> configuration> more on that on: http://bdn.borland.com/article/0,1410,28974,00.html Copy a FileStream from Server to Client ******************************************************************************** How do you copy Filestreams (e.g. an EXE) with Buffers Search Keys: delphi delphi3000 article Stream Buffer Source-Destination Filecopy Times Scored: Company: kleiner kommunikation Reference: http://max.kleiner.com Component Download: http://max.kleiner.com/download/processloader1.pas Question/Problem/Abstract: It could be that you want more control over a Filestream-Copyprocess, e.g. you want to update from time to time a progressbar or to check blocksize in a LAN or WAN. With the Buffer-Solution you get more flexibility and scalability during a Streamprocess. Answer: Suppose you declare a Source- and Destname, with read and writebuffer you can copy source to destination in a manageable way, then you create the destination-filename on the client: // sSourceFileName:= ReadString(sStartFile); from Registry // sDestFileName:= sDestPath + ExtractFileName(sSourceFileName); const cDataSize = 2048; var Reg: TRegistry; //Source is stored in the Reg fsSource, fsDestination : TFileStream; sExecCall : string; pBuffer: Pointer; iSize, i: integer; The mainroutine opens two FileStream-objects and copys with read- and writebuffer a predefined size (2048) from the source to the destination. In the meantime a progressbar is updated until fileSize. try fsSource:= TFileStream.Create (sSourceFileName, fmOpenRead); fsDestination:= TFileStream.Create(sDestFileName, fmCreate); iSize:= fsSource.Size; //normal way with CopyFrom! //fsDestination.CopyFrom (fsSource, fsSource.Size); ProgressBar1.Max:= fsSource.Size; GetMem(pBuffer, cDataSize); i:= 1; while i * cDataSize <= iSize do begin fsSource.ReadBuffer (pBuffer^, cDataSize); fsDestination.WriteBuffer (pBuffer^, cDataSize); ProgressBar1.Position:= i * cDataSize; inc(i); end; fsSource.ReadBuffer (pBuffer^, iSize- (i-1)* cDataSize); fsDestination.WriteBuffer (pBuffer^, iSize- (i-1)* cDataSize); finally fsSource.Free; fsDestination.Free; end; The unit is available to download an needs further actions: 1. two name-entries in the registry in one key // HKEY_USERS \.DEFAULT\Software\Prosystem\DynaLoader name value myExe: 'path to exe' destinationPath: 'path to client' 2. create the file "regpath.txt" with the registryPath, for example: // rem HKEY_USERS RegKey on server, 3.8.01 \.DEFAULT\Software\Prosystem\DynaLoader 3. start the loader from clients (a link-file) with two command-line options: PROLOADER.EXE servername myExe In our implementation it's only the link (of the loader) and the file on the client, the loader, the application and registry are on the server. Update 07.04.02 copy a MemoryStream in a StringStream ------------------------------------------------------- procedure TMainFrm.TemplateBtnClick(Sender: TObject); var M: TMemoryStream; S: TStringStream; begin M := TMemoryStream.Create; S := TStringStream.Create(''); try M.LoadFromFile(ExtractFilePath(Application.ExeName)+ '/Test1.txt'); M.Seek(0,0); // TStream.CopyFrom() method uses WriteBuffer() and ReadBuffer() template methods S.CopyFrom(M, M.Size); S.Seek(0,0); with Memo.Lines do begin Clear; Add(S.ReadString(S.Size)); end; finally M.Free; S.Free; end; end; http://max.kleiner.com/download/processloader1.pas Building a business object ******************************************************************************** What are business objects and how to build it ? Search Keys: delphi delphi3000 article business-objects rules business-class business-logic Times Scored: Company: kleiner kommunikation Reference: http://max.kleiner.com/uml_buch.htm Component Download: http://max.kleiner.com/download/businessobj.zip Question/Problem/Abstract: Each business object should encapsulate queries, rules or calculations autonom in the class and independent from GUI or data-access. Parameters came from the GUI and with Delphi's TQuery or TDataModule you can change the data-access, as long you handle with SQL. Answer: How do you build a business object? ----------------------------------- What's a business object ? To provide business services, a business object works in collaboration with data storage objects and interface objects. A business object transforms data into information with queries or calculations. The data may be acquired from data services or file services. Business objects are sometimes referred to as conceptual objects, because they provide services which meet business requirements, regardless of technology. The idea of this article is in fact dedicated to the development of such a business object, following example with InterBase. In a data module’s unit file, you can write methods, including event handlers for the components in the module, as well as global routines that encapsulate business rules. For example, you might write a procedure to perform fee calculation in a bank you could call such a procedure from an event handler for a component in the module or from any form that uses the module. In a simple business object (without fields in the class), yo do have at least 4 tasks to fulfill: 1. The Business-Class inherits from a Data-Provider 2. The query is part of the class 3. A calculation or buiness-rule has to be done 4. The object is independent from the GUI, the GUI calls the object 1. We build the class, Superclass can be TQuery or TDataModule type TBusinessObj = class(TQuery) private function open_QueryFee(qryID: integer):boolean; function calcFee(fee: double):double; public procedure changeFee(amount: double); procedure changeLimit(amount: double); end; 2. We define a parametrised query function TBusinessObj.open_QueryFee(qryID: integer):boolean; begin result:= false; try SQL.Clear; SQL.add('SELECT * from ACCOUNT'); SQL.add('WHERE acc_no =:pClient_acc'); params[0].name:='pClient_acc'; params[0].dataType:=ftInteger; params[0].AsInteger:= qryID; Open; result:= true; finally //garbage or something end; end; 3. Before we save the amount straight, we calculate something procedure TBusinessObj.changeFee(amount: double); begin edit; FieldByName('FEE').asFloat:= calcFee(amount); post end; // like a business rule function TBusinessObj.calcFee(fee: double): double; begin result:= (2 * fee) / BANK_FACTOR //just a calc end; 4. Let's go to the client, which has a business object as his member: private ... tblAccount: TBusinessObj; end; procedure TForm1.btnFeeClick(Sender: TObject); begin with tblAccount do begin if open_QueryFee(strToInt(edtAccount.text)) then changeFee(strToFloat(edtFee.text)); end; end; As long as we don't have fields in the business object in order to map a relational database in a OO-manner, we can simple typecast the instance, without a constructor. procedure TForm1.FormCreate(Sender: TObject); begin tblAccount:= TBusinessObj(Query1); //typecast end; Outlook: --------------------------------------------------------- When time comes, you want to map the attributs from a database to the fields of the corresponding class. That means, all SQL-statements are encapsulated, so you got a real OO-access, which I will describe in a second part. Let's see an example of a highlight in object database programming, which is similar by Boldsoft with BOLD or gs-soft with MetaBASE: Person:=TPerson.Create; oAddressList:=TAddressList.Create; try oPerson.Person_ID:=30; oAddressList:=TAddressList.DBReadAllRelatedToObject(oPerson); for i:=0 to oAddressList.Count-1 do S_MBox(oPerson.LastName+' '+oAddressList.Adresses[i].Town); finally oPerson.Free; oAddressList.Free; end; Calling a C++ DLL which exports a class? ******************************************************************************** Search Keys: delphi delphi3000 article DLL DLL+ C++ Convention Memory-Management Times Scored: Company: kleiner kommunikation Reference: http://www.delphi3000.com/articles/article_2243.asp Component Download: http://max.kleiner.com/download/cpluscall.zip Question/Problem/Abstract: As I stated in an earlier article, it's possible to get an object-reference out from a DLL. This technique is known under the name DLL+. But how about the DLL is written in c++? Answer: First of all, you have to translate the header-file (should be delivered with the DLL), which is like an interface-section in ObjectPascal. Headers in c usually contain all sorts of definitions which are relevant outside the module. In our c++ example it looks like: /*FILE: income.h */ class CIncome { public: virtual double __stdcall GetIncome( double aNetto ) = 0 ; virtual void __stdcall SetRate( int aPercent, int aYear ) = 0 ; virtual void __stdcall FreeObject() = 0 ; } ; Then you translate it to an Abstract Class in a unit of her own: //FILE: income.pas interface type IIncome = class public function GetIncome(const aNetto: double): double; virtual; stdcall; abstract; procedure SetRate(const aPercent:Integer; aYear:integer); virtual; stdcall; abstract; procedure FreeObject; virtual; stdcall; abstract; end; In the c++ dll, there is a procedure FreeObject this is necessary because of differences in memory management between C++ and ObjectPascal: void __stdcall FreeObject() { delete this ; } When you call the DLL written in C or C++, you have to use the stdcall or cdecl convention. Otherwise, you will end up in violation troubles and from time to time the application may crash. By the way the DLL, you are calling, should be on the search path;). So these conventions pass parameters from right to left. With this convention, the caller (that's Delphi)has to remove the parameters from the stack when the call returns. At least the DLL-call is simple: incomeRef: IIncome; //member of the reference function CreateIncome: IIncome; stdcall; external('income_c.dll'); procedure TfrmIncome.FormCreate(Sender: TObject); begin incomeRef:=createIncome; end; procedure TfrmIncome.btncplusClick(Sender: TObject); var cIncome: Double; begin // this is the c++ dll+ call ;) incomeRef.SetRate(strToInt(edtZins.text), strToInt(edtJahre.text)); cIncome:= incomeRef.GetIncome(StrToFloat(edtBetrag.Text)); edtBetrag.text:= Format('%f',[cIncome]); end; Delphi uses a mixed memory model, but it is very close to the "C" large model. The defaults are: - Methods are far - Procedures in an interface section are far - Procedures only used in an implementation section are near - Heap data and all pointers in general (including class instances) are far - Global variables are near (DS based) - Procedure parameters and local variables are near (SS based) - Procedures declared FAR or EXPORT are far - Virtual memory tables are far for the new class model and near for the old Exception or Event Logger ******************************************************************************** How to keep track of runtime exceptions or component events? Search Keys: delphi delphi3000 article Exceptions Logger Error-report Quality Assert events Times Scored: Question/Problem/Abstract: Each project has logical errors or by runtime. It would be fine to write these exceptions down to a file in order to find out, what's happened weeks ago ;) or to track component events Answer: Exceptions are mistakes and errors due to some run-time problem. This is obviously a wishy-washy definition, but generally run-time problems would be things like running out of memory whilst adding a data object or an index out of bounds. In our team we wrote a procedure (months ago), which intercepts those nasty things like exceptions by assigning a new event-handler in the main-unit: {$IFDEF DEBUG} Application.OnException:= AppOnException; {$ENDIF} If DEBUG is not set the code runs at full speed, but I advise to set the handler all the time, cause then you can analyse each applications exception file. Normal testing should identify programming mistakes, whereas the other type of error are exceptions to the norm. The event-handler goes like this: procedure TCWForm.AppOnException(sender: TObject; E: Exception); var Addr: string[9]; FErrorLog: System.Text; FileNamePath: string; begin //writes errorlog.txt file FileNamePath:= extractFilePath(application.exeName) + 'errorlog.txt'; AssignFile(FErrorLog, FileNamePath); try System.Append(FErrorlog); except on EInOutError do Rewrite(FErrorLog); end; Addr:= IntToHex(Seg(ErrorAddr),4) + ';' +IntToHex(Ofs(ErrorAddr),4); Writeln(ErrorLog, format('%s[%s]%s%s',[DateTimeToStr(Now), getNetUserName, E.Message, Addr])); System.Close(FErrorLog);} MessageDlg('CW5' + E.Message +'. occured at: '+Addr,mtError,[mbOK],0); end; To avoid scope conflicts, Assign File replaces the Assign procedure that was available in previous versions of Delphi. The Addr also depends on the OS. Note, that you want still see exceptions on the screen, you get it with the last MessageDlg in the AppOnException-routine. Then you get an output in a well shaped manner: *************************ERRORLOG************************************ 26.09.99 12:09:16 [MAX] List index out of bounds 52FF;1226 26.09.99 13:05:28 [MAX] Database BezSpr not found 5F6F;1226 26.09.99 13:21:37 [THOMAS] List index out of bounds 69DF;1226 26.09.99 13:43:35 [MAX] GP fault in module CW5.EXE at 0002:3588 2A9F;1226 30.09.99 14:32:23 [SIMON] Cannot perform this operation on a closed dataset 320F;1254 30.09.99 14:35:36 [MAX] Record locked by another user. Table:GBK.DB Maybe, the function getNetUserName has to be changed, it depends on the operating-system or the database you deal with: function getNetUserName: string; var szVar: array[0..32] of char; begin DBIGetNetUserName(szVar); result:= StrPas(@szVar) ; end; Instead of Addr:= by an Exception, use in a 32-bit environment: mem: TMemoryStatus; mem.dwLength:=sizeOf(TMemoryStatus); GlobalMemoryStatus(mem); edit3.text:=intToStr((mem.dwAvailPageFile) div 1024) ; edit4.text:=intToStr((mem.dwAvailPhys) div 1024); Component Event Logger (Update 1.3.02) ------------------------------------------------------ On the other side you want to know component events to track down user or system behavior. This is also usefull to show the events on runtime in a listbox or to store it in a file. First your declare a procedure in your class: events: TListBox; procedure LogEvent(const EventStr: string; Component: TComponent = nil); Second you define the procedure with a listbox as events: procedure TransAct.LogEvent(const EventStr: string; Component: TComponent = nil); var ItemCount: Integer; begin if (csDestroying in ComponentState) or not Events.Visible then Exit; if (Component <> nil) and (Component.Name <> '') then Events.Items.Add(Format('%s(%s)', [EventStr, Component.Name])) else Events.Items.Add(EventStr); ItemCount := Events.Items.Count; Events.ItemIndex := ItemCount - 1; if ItemCount > (Events.ClientHeight div Events.ItemHeight) then Events.TopIndex := ItemCount - 1; //tracing end; Third you call the procedure LogEvent in your code as you want, e.g.: LogEvent('OnDataChange', Sender as TComponent); LogEvent('BeforeOpen', DataSet); LogEvent('AfterClose', DataSet); procedure TransAct.DataSetBeforeClose(DataSet: TDataSet); begin LogEvent('BeforeClose'); end; procedure TransAct.DataSetError(DataSet: TDataSet; E: EDatabaseError; var Action: TDataAction); begin LogEvent('OnDelete/OnEdit/OnPost Errors', DataSet); end; procedure TransAct.Disconnect(Connection: TADOConnection; var EventStatus: TEventStatus); begin LogEvent('Disconnect', Connection); end; Update 06.04.02 with automatic file writing each with a time-stamp ------------------------------------------------------------------- function TScanner.SaveLogData(const UserData: WideString; const CheckSum : DWORD) : Boolean; var SL : TStringList; FileName : String; begin SL := TStringList.Create; FileName := 'D:\Scanner\LogData\'+FormatDateTime('yyyymmdd-hhnnsszzz',Now)+'.txt'; SL.Text := UserData; SL.SaveToFile(FileName); SL.Free; Result := True; end; Why use Packages ? ******************************************************************************** A checklist for your runtime packages Search Keys: delphi delphi3000 article borland vcl code-snippet packages modular module contains requires Question/Problem/Abstract: Packages offer a structural library approach to developing applications and offers a precondition for package diagrams. In Delphi/Kylix a Package is a combination, collection of units, which have dependencies. Answer: The goal is to provide some notions about runtime packages and why use packages at all in a "designed" project? Runtime packages, which are optional in a project, offer several advantages over a conventional programming. By compiling reused and modular code into a runtime library, we can share it among Delphi/Kylix applications. Package diagrams are used to describe parts of a system at a high level, such as 'report generator' or 'single sign on ckecker'. These will have internal structure, and many tools will allow you to jump from the package to a detailed view of its structure. Remember: Packages could be nested. Two types can be made by Delphi/Kylix: - design time packages used to install components in the IDE and to create special property editors, experts etc. for custom components this type is used only by IDE and is never distributed with your app - run time packages provide functionality when we run an application they operate much like DLL's Rule: Design time packages need run time packages to work but not the other way round. In this article we deal with run timers. First Decision -------------------- Packages allow also a faster compilation cause only code concerning the "small" application is compiled with each build. In comparison with a DLL, packages provide an easier approach with forms or method calls cause packages are owner of the application so they interact better with other classes. On the other side a DLL is more "other language friendly" and less IDE dependent. Advantages of Packages: - packages are specific to Delphi - packages are UML conform - faster compilation, less parsing - packages save memory for many applications - testing becomes more plannable (DUnit) - better installation, setup's and deployment possible - scalable in product, e.g. a light- or professional version The first decision we should make is: Do we put components in a package or do we modularize a whole application with a lot of forms, data-modules, multi-language or ressources? That means dependencies between classes should be stronger (inner coupling) than dependencies between packages. Imagine a scheduling system for public transports with an optional messaging system or an optional reporting system. Some installation need only the schedSystem and others will install the ME and REP modul too. Build the Package ----------------------- Ok., let's start with a simple application with two forms (form1 and form2), form2 should become a package, means we modularize the app within a reusable module. 1. Remove Form2 from the auto-created form list 2. The Event-Handler goes like this: with TForm2.Create(application) do begin try showModal; finally free; end; 3. Add Unit2 to Unit1 uses (implementation part) 4. Now we create a package with Form2... 5. View/Project Manager then right click on Project Group and select " Add New Project..." till the Package Editor arrives 6. We select Package from the New list 7. We select the "Contains" item and add Unit2.pas 8. It's time now to save and compile our package 9. Since our package is run-time only clicking on the Install buttton will not work The package is complete and we call it picinPac so the file generated is picinPac.bpl in the project order. Hint: If we rename the extension from bpl to dll and take a look with quickview, you can see the package like a DLL! We also define in the package editor/options the package as "Runtime only" so the property editors are wiped out (we don't need them at runtime). Second part is now to compile the app with our package: 1. We select Project Options/ Packages tab 2. Activate the checkbox "Build with runtime packages" 3. Add in the edit box below our package like vcl50;picinPac 4. Packages listed in the edit box are automatically linked to your app's 5. Recompile it After we compiled it, the app is about 17k but we need on a second machine the package and vcl50.bpl too, as we can see in the package editor "Requires". (But with multiple packages we need another check). The whole check for deployment is found within the compile-info from the Delphi Menu "Information and finally Package used". The mechanic get's Delphi by statically linking our package at compile time. Note: Like a DLL, also a package is able to load dynamically with LoadPackage() and UnloadPackage(). But either you choose static or dynamic, any changes in Form2 needs a rebuild within the package-editor (compile) after then Form1 is up to date. I can't say how many times we forget that. To prevent this add the package to the Project Manager Group so we can switch between compilations. Versioning Problem ------------------------ When we update a DLL (change function's implementation), we simply compile it, export some new routines and ship the new version. All the applications using this DLL will still work (unless, of course, you've removed existing exported routines). On the other hand, when updating a package, you cannot ship a new version of your package without also updating the executable. This is why we cannot use a unit compiled in Delphi 4 in a Delphi 5 project unless we have the unit's source; the compiler checks version information of DCU's and decides whether an unit has to be recompiled so any package that you provide for your application must be compiled using the same Delphi version used to compile the application. Note: You cannot provide a package written in Delphi 6 to be used by an application written in Delphi 5. Note: although the app is linked to run-time packages, the program's units must list all the units they require in the uses clause. Compiling with run time packages only tells the app where to find it's component code. Last check is the project-source *.bpg, sort of a editable make script, that shows our packages in a "well documented" way. Create project groups to handle related projects at once. In our example, you can create a project group that contains multiple executable or binary files such as two .BPL and an .EXE: #------------------------------------------------------------------------------ VERSION = BWS.01 #------------------------------------------------------------------------------ !ifndef ROOT ROOT = $(MAKEDIR)\.. !endif #------------------------------------------------------------------------------ MAKE = $(ROOT)\bin\make.exe -$(MAKEFLAGS) -f$** DCC = $(ROOT)\bin\dcc32.exe $** BRCC = $(ROOT)\bin\brcc32.exe $** #------------------------------------------------------------------------------ PROJECTS = p_lcd.exe adopac.bpl picinPac.bpl #------------------------------------------------------------------------------ default: $(PROJECTS) #------------------------------------------------------------------------------ p_lcd.exe: ..\p_lcd.dpr $(DCC) adopac.bpl: adopac.dpk $(DCC) picinPac.bpl: picinPac.dpk $(DCC) How do we implement the Choice Pattern ******************************************************************************** Working with Interfaces Search Keys: delphi delphi3000 article borland vcl code-snippet Choice-Pattern Design-Patterns Interfaces Question/Problem/Abstract: The Choice Design Pattern is new and relies on interfaces and runtime associations. No aggregation or composition between classes are needed. Answer: The Choice Pattern needs one interface, n-classes which supports the interface and a worker-class to provide the choice of an algorithm at runtime. The Choice Pattern is like the Strategy Pattern, but smaller and more runtime in his behaviour. With interfaces we don't have to concern about memory management. Interface references are managed through reference-counting, which depends on the _AddRef and _Release methods inherited from IUnknown. When an object is referenced only through interfaces, there is no need to destroy it manually; the object is automatically destroyed when the last reference to it goes out of scope. The following restrictions apply. - The member List can include only methods and properties. - Fields are not allowed in interfaces. - Interfaces have no constructors or destructors. They cannot be instantiated, except through classes that implement their methods. Methods cannot be declared as virtual, dynamic, abstract, or override. Since interfaces do not implement their own methods, these bindings have no meaning. So let's practice the Choice Pattern in 5 steps: 1. We need an Interface in order to be type-compatible. IChoicePattern = Interface procedure doPatternSearch; end; 2. We declare 2 or n classes. A class from an Interface can support/implement multiple interfaces. TInterfacedObject implements the methods of IUnknown, so TInterfacedObject automatically handles reference counting and memory management of interfaced objects. One of the concepts behind the design of interfaces is ensuring the lifetime management of the objects that implement them. The AddRef and Release methods of IUnknown provide a way of implementing this functionality. TCheckFormatA = class(TInterfacedObject, IChoicePattern) public procedure doPatternSearch; end; TCheckFormatB = class(TInterfacedObject, IChoicePattern) public procedure doPatternSearch; end; 3. We need a worker-class which calls the runtime Interface-methods: TDirWorker = class procedure callCheck(myInst: IChoicePattern); end; 4. Now we implement the Interface Classes and the Worker Class too: procedure TCheckFormatA.doPatternSearch; var ldbPath: string; begin if FileExists(extractFileDir(application.exeName)+'\'+ DBNAME) then ldbPath:=extractFileDir(application.exeName)+'\'+DBNAME; messageDlg('formatASearch doing', mtInformation,[mbok],0); end; procedure TCheckFormatB.doPatternSearch; var ldbPath: string; begin if OpenDialog1.Execute then ldbPath:=openDialog1.FileName; messageDlg('formatBSearch doing', mtInformation,[mbok],0); end; procedure TDirWorker.callCheck(myInst: IChoicePattern); begin myInst.doPatternSearch; messageDlg('do_some_Work', mtInformation,[mbok],0); end; Here we can see, no myInst.Free is needed. Each object from TCheckFormatA or TCheckFormatB is automatically destroyed. Interfaces track the lifetime of an object by incrementing the reference count on the object when an interface reference is passed, and will destroy the object when that reference count is zero. 5. At least the client calls the Choice Pattern and every object is local at runtime: procedure TMainFrm.Button1Click(Sender: TObject); begin with(TDirWorker.Create) do begin callCheck(TCheckFormatA.create); callCheck(TCheckFormatB.create); Free; end; end; Have fun and choice with OP (Delphi, Kylix and FreePascal) Building a Fractal Generator ******************************************************************************** A Fractal Library for Science, Chaos and Financials Search Keys: delphi delphi3000 article borland vcl code-snippet Chaos Fractal Canvas Science Mandelbrot Times Company: kleiner kommunikation Reference: max.kleiner.com Component Download: http://max.kleiner.com/download/chaoslib.pas Question/Problem/Abstract: How do you build those bright, weird, beautiful shapes called fractals they're everywhere in science, art and forecasting with graphic-routines for Delphi and Kylix ? Answer: Fractals are geometric figures, just like rectangles, circles and squares, but fractals have special properties that those figures do not have. There's lots of information on the Web about fractals, but most of it is either just pretty pictures or very high-level mathematics. So this article shows the important routine to draw the famous mandelbrot on a canvas. Benoit Mandelbrot was largely responsible for the present interest in fractal geometry. He showed how fractals can occur in many different places in both mathematics and elsewhere in nature. Much research in mathematics is currently being done all over the world. Although we need to study and learn more before we can understand most modern mathematics, there's a lot about fractals that we can understand. Before we take a look at the mandelbrot-code a note about the unit: A lot of programms does exists. I would only give you a glance at some code-snippets to motivate you, building your own generator with your own prameters in it. The OP-oriented library is free for download and shows some topics in Chaos like - Logistic Map - Henon - Lorenz Attractor - Bifurcation - Mandelbrot So here's the mandelbrot(not a real universum picture just plain code ;) procedure TModelMandelbrot.process(X, Y, au,bu: double; X2, Y2: integer); var c1, c2, z1, z2, tmp: double; i, j, count: integer; begin c2:= bu; for i:= 10 to X2 do begin c1:= au; for j:= 0 to Y2 do begin z1:= 0; z2:= 0; count:= 0; {count is deep of iteration of the mandelbrot set if |z| >=2 then z is not a member of a mandelset} while (((z1*z1 + z2*z2 <4) AND (count <= 90))) do begin tmp:=z1; z1:= z1*z1 - z2*z2 + c1; z2:= 2*tmp*z2+c2; inc(count); end; //the color-palette depends on TColor(n*count mod t) cFrm.Canvas.pen.Color:= (16*count mod 255); cFrm.Canvas.DrawPoint(j,i); c1:=c1 + X; end; c2:= c2 + Y; end; end; The different colors depends on the count which tells us the set of mandelbrot, those are different sets in and out so are different colors. You call the unit simply by: with TChaosBase(TModelMandelbrot.create) do begin setup(frmChaos); //aForm has to be set Free; end; All models inherit from a baseclass: TChaosBase = class private cFrm: TForm; public scaleX1: double; scaleX2: double; scaleY1: double; scaleY2: double; procedure setup(vform: TForm); virtual; abstract; procedure scaleResults(const X, Y: double; var intX, intY: integer; width, height: integer); end; Thus, fractals graphically portray the notion of "worlds within worlds" which has obsessed Western culture from its tenth-century beginnings. I hope you enjoy the magic world of fractals and maybe you earn some money on the stock-exchanges too, cause they belong to the same chaos-theorie... Zooming isn't as simple as it seems, cause zooming of fractals is dependent on iterations not on graphic scales like form.widht/range or so on. the steps are as follow: 1. enlarge the picture with an increase of iterations cFrm:= vForm; X1:=20; X2:=trunc(cFrm.ClientWidth * zoomfact); ..... 2. compute the enlargment for i:= 10 to X2 do begin c1:= au; for j:= 0 to Y2 do begin ....... 3. copy the section as you wish either by mouse or by position cFrm.Canvas.CopyRect(cfrm.Canvas.ClipRect, cFrm.Canvas, SourceRect); the point is the enlargment, cause it takes time to deepen the fractal and my solution isn't efficient enough, cause it draws the fractal and then grabs the clipping area, better is to define a structure or collection, fill the data in an two dimensional array and then set the area to draw. AppRect:= Rect(cfrm.Left, cfrm.Top, cfrm.Left+ cfrm.Width, cfrm.Top+ cfrm.Height); GetCursorPos(Point); //Check If The Mouse Pointer Is Outside Of The Mainform. //If not PtInRect(AppRect,Point) Then pointTL.X:= trunc((cfrm.Width - basew + 066) * zoomfact); pointTL.Y:= trunc((cfrm.height - baseh + 140) * zoomfact); pointBR.X:= trunc((cfrm.width + 266) * zoomfact); pointBR.Y:= trunc((cfrm.height + 233) * zoomfact); SourceRect:= Rect(pointTL.x, pointTL.Y, pointBR.X, pointBR.Y); cFrm.Canvas.CopyRect(cfrm.Canvas.ClipRect, cFrm.Canvas, SourceRect); //InflateRect(sourceRect,Round(cfrm.Width/40),Round(cfrm.Height/40)); Five of the best tools for Delphi ******************************************************************************** Why you need more than an just the IDE Search Keys: delphi delphi3000 article borland vcl code-snippet Tools Productivity Quality Delphi-Tools Question/Problem/Abstract: From time to time (if we have time) I have been asked, which are the best tools beside delphi, for example to design a OO-database or to make calls between instances or units visible. Answer: On the other side, it's difficult to find tools we don't know they exists. Anyway, I want to start with that article to make a very short introduction to keep you informed that such best tools with delphi/kylix exist and what they solve. But before we start, here?s the disclaimer. Each tool is a complex and fully-featured product (not a component), and a review such as this can only scratch the surface, and try to give you a motivation of what the following tools are and what they can do. By the way, Delphi is not a language it's the product so ObjectPascal IS the language and is getting more and more engineers around the world with the products Delphi, Kylix and Virtual Pascal! ;) 1. Bold for Delphi The product Bold for Delphi (Enterprise R3.0-D5, release 3.0.1.9), provides a model-driven software development environment for Delphi developers. It means that developers can design their applications using UML (the class-diagram), and Bold will help to build a system right from the model. Bold is designed to implement 'business object logic?. The point is that many business applications are basically the same: GUI front end, logic-based middle layer, and a database at the back end to make things memorizable. So a lot amount of duplication of design, and probably duplication of code as well. It would be very useful if there was some sort of framework to help you implement multi-tier business solutions. Bold is that framework and their product recently gained Software Development?s Productivity Award, and it is being used in the Swedish (it's a Swedish Product) and French parliaments, US government departments, also the Swiss tax authorities among many others. http://www.boldsoft.com 2. ModelMaker ModelMaker 6.1 represents a new way to develop classes and component packages for Delphi (1-6). ModelMaker is a two-way class tree oriented productivity, refactoring and UML-style CASE tool specifically designed for generating native ObjectPascal code (in fact it was made using Delphi and ModelMaker). Delphi's ObjectPascal language is fully supported by ModelMaker. From the start ModelMaker was designed to be a smart and highly productive tool. It has been used to create classes for both real-time / technical and database type applications. ModelMaker has full reverse engineering capabilities. It supports drawing a set of UML diagrams and from that perspective it looks much like a traditional CASE tool. The key to ModelMaker's magic, speed and power however is the active modeling engine which stores and maintains all relationships between classes and their members. A unique feature, currently not found in any development environment for Delphi, is the support for design patterns. A number of patterns are mplemented as 'ready to use' active agents. It's the 100% UML-Tool which works with so tightless with Delphi. http://www.delphicase.com 3. AQTest AQtest, the complete application testing system from AutomatedQA, means automated support for functional, unit, and regression testing in one easy to use and totally flexible package. AQtest brings testing out of the Dark Ages (that's what they say;). AQTest is the only true test automation and management tool for Delphi applications (That's true). It is written in Delphi and boasts several industry firsts, especially a uniquely flexible, user-oriented approach. With AQtest, you test your own way, with scripts or Delphi code written your own way, and test reports organized to your own needs. AQtest gives external test scripts access to all onscreen VCL elements, with their properties and even their methods. Optionally, it gives access to all published elements of the Delphi application under test, and optionally again it gives scripts practically the same access to application internals as the Delphi IDE has. Start using AQtest, and you'll be able to make every test run into a contribution to development. AQtest can keep a project on schedule and on spec, the team confident and the work 100% productive. For example DelphiScript support is built into AQTest. It's a highly recommended tool that comes out of the box with 19 profilers (We use it). The two that we are interested in most, is "listing unused units", and "identifying who calls what method" are included. (The method call profiler display its info through a diagram. so a fantastic cool tool.;) http://www.automatedqa.com 4. MetaBASE MetaBASE makes it possible for the Delphi development platform to access the relational data model in a 100% object-oriented manner. MetaBASE supports the program development process on all common SQL servers, such as Oracle, Sybase, Watcom and InterBase. The metalayer enables an easy port of applications between different 'flavors' of RDBMS. The client developer always has full details of the underlying data structure. High speed application development is ensured, even when the data model reaches an acute level of complexity. MetaBASE provides Visual Components (VCLs) that are data model aware. The data model itself is stored as an object stream and can be accessed throughout the application's development and runtime phase. MetaBASE allows the programmer to ?Drag and Editor Drop" entities, relations, attributes and their extensions as indices into the Delphi environment. This is accomplished with almost zero manual code writing. Application development has reached a new dimension and the programmer can concentrate on the 'real' programming task. http://www.gssoft.ch 5. RemObjects PascalScript PascalScript is a free scripting engine that allows you to use most of the ObjectPascal language within your Delphi projects at runtime. Written completely in Delphi, it's composed of a set of units that can be compiled into your executable, eliminating the need to distribute any external files. We are happy to announce that Carlo Kok (formerly Innerfuse Pascal Script) has joined RemObjects Software, and that as of today PascalScript has been merged and improved into the product line of RemObjects Software, Inc. PascalScript (PS) has a bright future so an ex. of real life: With the release of V0.845 of X-Force, which will be soon available, there will be extensive changes in the script language X-Script. The special for X-Force developed script language will be replaced by the extensive and flexible PS by Carlo Kok (www.remobjects.com). With only one code line you can import an excisting Delphi function in PS and could be called from there. PS supports the direct interaction with Delphi objects and records so that the expansion will be easier in the future. The script engine never calls Application.ProcessMessages by itself, so your application may hang, while the script is running. To use the component version of PascalScript, you must first place it on your form or data module, set or assign the script property, call the compile method and call the execute method. A scripting engine allows an end user to customize an application to his or her needs without having to recompile it. In addition, you can update your applications by just sending a new script file. The TPSScript component allows you to embed the interpreter into your Delphi, Kylix or C++ Builder application, so you can extend and customize the application without having to recompile it. The script engine never calls Application.ProcessMessages by itself, so your application may hang, while the script is running. To use the component version of PascalScript, you must first place it on your form or data module, set or assign the script property, call the compile method and call the execute method. http://www.remobjects.com You can enlarge the functionality with the RemObjects SDK: The RemObjects SDK 3.0 is the award winning remoting framework for Delphi, Kylix and .NET. It allows you to remotely access objects residing on a server from clients inside a LAN or across the Internet. It supports object pooling, asynchronous invocation, compression, encryption, and a variety of protocols such as TCP/IP, HTTP, UDP, POP3/SMTP, NamedPipes, etc. It includes the RemObjects Service Builder and allows you to expose your services as SOAP web-services. 6. FreeVCS Although many developers think of a version control system (VCS) as an expensive, complex, hard to use tool for a big development shared with many users (they are right for some systems) it may also be a very useful and handy help for the stand alone development of a small application or a component. Have you ever modified a module from a working application to make the thing's going better or faster and after a while nothing will work as you expected (or will not work at all)? Have you ever had 10 directories with different source version and no idea which one holds the change you are searching for? Have you ever started a faulty application from the IDE without backing up your source files? Have you ever lost one of your source files? When you use FreeVCS, your files and projects are stored compressed in a local or remote database (the version archive). FreeVCS uses an open source application server, scaleable from Two-Tier DBISAM to Three-Tier Oracle, Interbase 5/6, MSSQL or Informix for this. http://www.freevcs.de/ Borland anounces to ship ModelMaker with Delphi 7 Studio Enterprise Use ModelMaker visual modeling based on UML technology to develop your applications efficiently, and add interactive content to Web sites by visually building dynamic server-side HTML Web applications with AToZed Software IntraWeb. DataSnap technology (formerly MIDAS) now delivers royalty-free scalable middleware drivers for creating multi-tier enterprise-class database solutions. And with the included Borland Kylix 3 for Delphi environment, take your Windows® applications cross-platform to Linux® for increased ROI. How do you add Interfaces to a List ? ******************************************************************************** Search Keys: delphi delphi3000 article Interface QueryInterface TInterfaceList Times Scored: Question/Problem/Abstract: It's more efficient to control Interfaces in a List and ask with QueryInterface() which objects support an Interface Answer: First we need some Interfaces (the same goes also in Kylix, pure Interfaces are independent from COM, it's a feature of ObjectPascal): type IKiss = interface (IUnknown) ['{19A231B1-269F-45A2-85F1-6D8A629CC53F}'] procedure kiss; stdcall; end; ISpeak = interface (IUnknown) ['{B7F6F015-88A6-47AC-9176-87B6E313962D}'] procedure sayHello; stdcall; end; Second the interfaces must be implemented: TDog = class (TInterfacedObject, ISpeak) public procedure sayHello; stdcall; end; TFrench = class (TInterfacedObject, ISpeak, IKiss) public procedure kiss; stdcall; procedure sayHello; stdcall; end; TEnglish = class (TInterfacedObject, ISpeak) public procedure sayHello; stdcall; end; e.g. the dog with procedure TDog.sayHello; begin showmessage('dog is barking wauwau'); end; Now we add the instances of the interface in the list, using the defined type TInterfaceList so we are able to ask with QueryInterface if an object supports an Interface, in our example if a dog as an object can kiss or just sayhello: procedure TForm1.btnCollectClick(Sender: TObject); var collection: TInterfaceList; i: Integer; aObjspeak: ISpeak; aObjKiss: IKiss; begin collection:= TinterfaceList.create; try with collection do begin add(TEnglish.create); add(TFrench.create); add(TDog.create) ; end; for i:= 0 to collection.count - 1 do begin aObjSpeak:= collection[i] as ISpeak; //TFrench, TEnglish, TDog if aObjSpeak <> NIL then aObjSpeak.sayHello; collection[i].queryInterface(IKiss, aObjKiss); //only TFrench if aObjKiss <> NIL then aObjKiss.kiss; end; finally collection.free; end; end; The 5 Relationships between Classes ******************************************************************************** Find the Notation in a UML Class-Diagram with ObjectPascal Search Keys: delphi delphi3000 article Classes Class-Diagram UML Relationships Times Scored: Company: kleiner kommunikation Reference: http://max.kleiner.com Component Download: http://max.kleiner.com/download/businessobj.zip Question/Problem/Abstract: How do we find related classes with the right UML-Notation, means which relationship belongs to which code ? Answer: Despite the fact that several advanced languages have come out of the OO Revolution, such as Java, C++, OP a lot of people are still designing their code with minimal design in mind. UML was formed in attempt to unify the best (or most popular in this case) modelling methods in Object-Oriented Analysis and Design. Let's focus on the Class Diagram and learn the 5 Relationships with OP (ObjectPascal). So good up design will actually shorten the development cycle, give you an idea of the resources you need, and how to end the project. The 5 Relationships are: - Inheritance (1) - Association (2) - Aggregation (3) - Composition (4) - Dependency (5) - Realisation (6) new UML 1.4 The Class Diagram is the static architectural representation of your software and capable with a CASE-Tool to generate Code. It allows you to see the overall object structure of the system. Let's start with the Inheritance (Generalization). All Relations are represented by Fig.1, (download cd_busobj.tif) but also by Code: 1) Inheritance is represented by a triangle and TBusinessObj is a subclass of TDataModule1, inheriting all of the members (Attributes and Operations) of the superclass. TBusinessObj = class (TDataModule1) private function calcSalary(salary: double): Double; procedure changeGrade(amount: integer); public constructor Create(aOwner :TComponent); override; destructor destroy; override; procedure changeSalary(amount: double); function getFullName: string; function getOldSalary: Double; function open_QueryAll: Boolean; function open_QuerySalary(qryID: integer): Boolean; end; 2) Association is represented by a line, means a relationship at runtime. In Fig.1 seen by from TDataToXML. Association is not a tight coupling between objects, you call it and free it at runtime with local instances: procedure TForm1.btnToXMLClick(Sender: TObject); begin with TDataToXML.create do begin try dataSetToXML(datEmployee.query1, 'salaryXport.xml'); finally free; end end; 3) Aggregation is a whole-part relationship. A TDataModule1 has Queries from TQuery, so the white diamond is positioned near the container to represent the Queries are the parts of the DataModule. It means also a relationship at designtime, Query1 is a steady member of TDataModule1: TDataModule1 = class (TDataModule) Database1: TDatabase; DataSource1: TDataSource; Query1: TQuery; public procedure loadTree(myTree: TTreeView; fromFile: string); procedure storeTree(myTree: TTreeView; toFile: string); end; 4) Composition is a stronger form of Aggregation. Composition is represented by a black diamond. For example in the VCL you can often find constructs like this: memo1.lines.add, so memo1 is TMemo and lines is TStrings. Means in our example if a class TForm1 has an instance and needs another instance too, there we have a composition: procedure TForm1.fillEmployees; begin with datEmployee.dataSource1 do begin while not dataSet.EOF do begin cmxEmployee.items.add(intToStr(dataSet.fieldValues['EMP_NO'])); dataSet.next; end; end; end; 5) Dependency is a dotted arrow and not shown in our diagram. It is used to show that one UML Element depends upon another. Dependencies can be used to describe the relationship not only between classes, also packages, or components. In a Class Diagram you find it for ex. that one class depends on a type of another class and the class is part of a Library, like the VCL. In our case TDataModule1 depends upon TTreeView (TreeView uses ComCtrls). But it's TForm1 which really depends on TTreeView, cause the instance TTreeView1 is a member of the Form: TForm1 = class (TForm) TreeView1: TTreeView; procedure TDataModule1.storeTree(myTree: TTreeView; toFile: string); begin with TFileStream.create(toFile, fmcreate) do begin try writeComponent(myTree); finally free; end; end end; 6) Interface support is like inheritance but there is a strict interface-specification and a class which supports the interface, marked in UML like a lollipop in the diagram or a dotted arrow from implement to interface: IIncomeInt = interface (IUnknown) ['{DBB42A04-E60F-41EC-870A-314D68B6913C}'] function GetIncome(const aNetto: Currency): Currency; stdcall; function GetRate: Real; stdcall; ..... TIncomeRealSuper = class (TInterfacedObject, IIncomeInt) private FRate: Real; function Power(X: Real; Y: Integer): Real; protected function GetRate: Real; public constructor Create; Interfaces works the same way in CLX or Kylix, as long as you don't use IDispatch from COM! So you don't need a MS-specific library to use interfaces. There is much more details that can be described with the Class Diagram, but with a Tool ,e.g. ModelMaker or my book "UML mit Delphi, 2000", you'll get into touch ;) Update 26.4.03: New Book "Patterns konkret" or patterns in practice will be published at 3.Q. 2003 and shows a continuous use of patterns in model-driven projects. download businessobj.zip (240k) and open cd_busobj.tif Why use Assembler ? ******************************************************************************** High Speed with OP (ObjectPascal) Search Keys: delphi delphi3000 article borland vcl code-snippet assembler register speed hardcore Times Company: kleiner kommunikation Reference: delphi3000.com/articles/article_2245.asp Question/Problem/Abstract: What's behind Assemblercode and a general understanding of what is meant by terms such as instantiation, null pointer and register memory allocation. Answer: We have always found OP (ObjectPascal) to produce fast and more efficient code, add this to the RAD Environment of Delphi and Kylix, so therefore the need to use assembler becomes a question mark. The article is somewhat an extract of the excellent paper "Learning Assembler with Delphi" from Ian Hodger at: http://www.delphi3000.com/articles/article_2245.asp It was the wish of my students to shorten it and say a few words about debugging. At the end of the article, you'll find an impressive assembler example in a DOS-Shell, it's shows a graphic-fire on the screen. In all of our work with OP, we faced just five situations where we have felt one should consider the use of low level system code: 1. Stepping and processing large quantities of data. Of course I exclude from this any situation where a data query language with a query optimizer is employed, but not always in a automation- or micro-controller environment. 2. For scientific reason to provide high speed simulations or just for education with Compiler-Assembler-Linker-Loader steps. I call that CALL 3. For controller programming or to develop or test peripherals like COM-devices, e.g. on how to detect free COM ports (as far as Windows knows..) and call some functions Windows doesn't allow. I.e, does not detect a COM port if a mouse is attached. Uses a DPMI call with assembler 4. High speed display routines; here we want quick and easy routines that comes with OP, not the strange C++ headers, external function libraries or confused hardware demands of DirectX 5. Strong and fast encryption algorithm like ciphers, hashes or checksums, so the core encoding and decoding routines are written in highly optimized assembler code. To say that writing machine code is cosy would be an understatement, and as for debugging an assembler language is just an easy way of remembering what machine code operations are available. The job of converting to machine code is done by an assembler, so Borland's Turbo Assembler is built into Delphi. Let's practice a little "Bit": If we look at adding an integer 15 to the register eax, the appropriate assembler instruction is add eax,15 //a := a + 15 Almost the same, to subtract the value of ebx from eax sub eax,ebx //a := a - b To save a value for a happy day, we can move it to another register mov eax,ecx //a := c or even better, save the value to a memory address mov [1733],eax //store value of eax at address 1733 and of course to retrieve it with mov eax,[1733] This means also the the largest number we can store in a register, e.g. eax is 2 to the power 32 minus 1, or exactly 4294967295. Bear in mind the size of the values you are moving about; the mov [1733],eax instruction affects not only memory address 1733, but 1734,1735 and 1736 as well, because as you will recall eax is 32 bits long, or rather 4 bytes, therefore memory is always addressed in bytes! Next step is the example: step:= step + 1; we would get something like this: mov eax, step add eax, 1 mov step, eax cause at least one parameter of almost each instruction must be a register. Now wer'e ready for Our first snippet of assembler, but let aside the simple nature of this example. Consider the following lines of OP code: function BigSum(A, B: integer): integer; begin result := A+B; end; OP provides the asm .. end block as a method of introducing just plain assembler to our code of life. So we could rewrite the function BigSum function BigSum(A, B: integer): integer; begin asm mov eax,A add eax,B mov result,eax end; end; This works fine, but there is a point to consider. There is no speed gain and we've lost the readability of our code. But the fact is, all our sophisticated class design is "broken" bi an assembler. You can also write complete procedures and functions using inline assembler code, without including a begin...end statement, e.g: function LongMul(X, Y: Integer): Longint; asm mov eax, X imul Y end; The compiler performs several optimizations on these routines so no code is generated to copy value parameters into local variables. This affects all string-type value parameters and other value parameters whose size isn’t 1, 2, or 4 bytes. Within the routine, such parameters must be treated as if they were var parameters. If we are going to produce really useful code, at some point we shall need to implement more deeper routines E.G. we have to display the output of some function dependant upon two variables. You might imagine this as a three-dimensional map, where the coordinates [X,Y] correspond to a height H. When we plot the point [X,Y] on the screen we need to give the imagination of depth. This can be achieved by using colours of differing intensity, blue below sea level and green above. What is needed is a function that will convert a given height into the appropriate depth of color for a given sea level. Debugging code ----------------------------------- To finish this article, let's say few words about debugging in order. It is very easy to set up watches, program break's, and traverse OP programs a line at a time. The same is true, even when using assembler. All we need to do is add the four 32bit general registers eax, ebx, ecx and edx to one's watch list, and see the effect of each line of assembler. In a DOS-Shell you can also type "debug" and then u for unassemble. The built-in assembler allows you to write Intel assembler code within OP programs. It implements a large subset of the syntax supported by Turbo Assembler and Microsoft’s Macro Assembler, including all 8086/8087 and 80386/80387 opcodes and all but a few of Turbo Assembler’s expression operators. Assembler functions return their results as follows. - Ordinal values are returned in AL (8-bit values) - AX (16-bit values), or EAX (32-bit values). - Real values are returned in ST(0) on the coprocessor’s register stack. - Pointers, including long strings, are returned in EAX. - Short strings and all the variants are returned in the temp. location pointed to by @result. So I hope you feel a little "Bit" the speed of Delphi and thanks Ian for his fundamentals. Fire example -------------------------------- PROGRAM firefast; {$G+} USES CRT, DOS; const intensit=200; var buf: array[0..102,0..159] of integer; i: word; delta: integer; pal: array[0..255,1..3] of byte; PROCEDURE setpalette; var i: integer; (* 0..65'535 -> in 2 Bytes abgespeichert *) begin for i:= 0 to 63 do begin pal[i,1]:=i; pal[i,2]:=0; pal[i,3]:=0; pal[i+64,1]:=63; pal[i+64,2]:=i; pal[i+64,3]:=0; pal[i+128,1]:=63; pal[i+128,2]:=63; pal[i+128,3]:=3; pal[i+192,1]:=63; pal[i+192,2]:=63; pal[i+192,3]:=63; end; asm; mov ax,seg pal; mov es,ax mov dx,offset pal; mov ax,$1012 mov bx,$0000; mov cx,$00FF; int $10; end; end; PROCEDURE interpolation; assembler; asm; mov cx,16159; mov di,offset buf; add di,320 @L1: mov ax,ds: [di-2]; add ax,ds: [di] add ax,ds: [di+2]; add ax,ds: [di+320] SHR ax,2 jz @L2; sub ax,1 @L2: mov word ptr ds: [di-320], ax add di,2; loop @L1; end; PROCEDURE purgebuf; assembler; asm; mov si,offset buf mov ax,$A000 mov es,ax; mov di,0; mov dx,100 @L3: mov bx,2 @L2: mov cx,160 @L1: mov al,[si] mov ah,al; mov es:[di],ax add di,2 add si,2;dec cx jnz @L1 sub si,320;dec bx jnz @L2 add si,320;dec dx jnz @L3; end; BEGIN randomize; inline($B8/$13/$00/ $CD/$10); setpalette; fillchar(buf,sizeof(buf),0); repeat interpolation; for i:=0 to 159 do begin if random < 0.4 then delta:=random(2)*intensit; buf[101,i]:=delta; buf[102,i]:=delta; end; purgebuf; until keypressed; textmode(CO80); end. Web Service Workshop with Remote Data Storing ******************************************************************************** The Borland VCLScanner explained almost Step by Step Search Keys: delphi delphi3000 article borland vcl code-snippet web-services remote URL SOAP XML Times Component Download: http://max.kleiner.com/download/webservices.zip Question/Problem/Abstract: How do you transfer scanned client-data with a web service from a client to a database server or a file automatically and store it? Answer: Weeks ago I got a source code example called VCLScanner from Borland that scans your harddisk and sends a report to a server like a Remote Procedure Call over the web. My students were fascinated and put me in charge to write an article about the main topics about Web Services and how the source works. VCL Class Scanner will generate a class usage report based on Delphi and C++Builder applications located on your system and send the report to a http location. Now I'm going to explain the build an made a few modifications, so you can download and compile the source with Delphi6 and test the Web Service on your local host and your personal web server previously installed. What's a Web Service? ---------------------------------------------------------------------------- Delphi’s support for Web Services is designed to work using SOAP (Simple Object Access Protocol). SOAP is a standard lightweight protocol for exchanging information in a decentralized, distributed environment. It uses XML to encode remote procedure calls and typically uses HTTP as a communications protocol. ObjectPascal’s SOAP-based technology is available on Windows and will later be implemented on Linux, so that it can form the basis of cross-platform distributed applications. There is no special client runtime software to install, as you must have when distributing applications using CORBA. Because this technology is based on HTTP messages, it has the advantage that it is widely available on a variety of machines. Build the Server ---------------------------------------------------------------------------- Open please the group-file ProjectWServices.bpg. Let's have a look first in the server code so you can see the Web Service main methods in the interface IVCLScanner: type IVCLScanner = interface(IInvokable) ['{8FFBAA56-B4C2-4A32-924D-B3D3DE2C4EFF}'] function PostData(const UserData : WideString; const CheckSum: DWORD) : Boolean; stdcall; procedure PostUser(const Email, FirstName, LastName: WideString); stdcall; end; We can see (so I hope) the Web Service is able to PostData in a file or to PostUser in a database. Before a Web Service application can use this invokable interface, it must be registered with the invocation registry. On the server, the invocation registry entry allows the invoker component (THTTPSOAPPascalInvoker) to identify an implementation class to use for executing interface calls. All this goes with the Web Service Wizard so define the interfaces that make up your Web Service is easy (at least). Note: It is a good idea to create your interface definitions in their own units, separate from the unit that contains the implementation classes. In this way, the unit that defines the interfaces can be included in both the server and client applications. Now we made our own entries to store data in a file. The only thing you must do is changing the path of the FileName: function TVCLScanner.PostData(const UserData: WideString; const CheckSum: DWORD) : Boolean; var SL : TStringList; FileName : String; begin SL := TStringList.Create; FileName := 'D:\Franktech\Webservices\'+FormatDateTime('yyyymmdd- hhnnsszzz',Now)+'.txt'; SL.Text := UserData; SL.SaveToFile(FileName); SL.Free; Result := True; end; The same goes for the database configuration with dbExpress, please have a look at the help about configuring TSQLConnection and compare with the source code. After configuration only one line left customising depending on your InterBase file location and the server name, in my example the same like the client machine (milo2): Params.Add('Database=milo2:D:\franktech\webservices\umlbank.gdb'); Compile and copy the Web Service to the Web Server ----------------------------------------------------------------------------- That's all for the server so we can compile it. Now comes the more interesting part. You have to copy the VCLScannerServer.exe in your web server's scripts directory, like a CGI-script or an NSAPI-DLL. Then you made a double click on the EXE and it will generate a file which goes like this: ------------------------------------------- VCLSCANNERSERVER_WSDLADMIN.INI [IWSDLPublish] IWSDLPublishPort=http:///soap/IWSDLPublish [IVCLScanner] IVCLScannerPort=http:///soap/IVCLScanner ------------------------------------------- This comes from the method (from path info soap*) WSDLHTMLPublish1.DispatchRequest(Sender, Request, Response); and we need this data to customize the client. The whole WSDL publisher publishes a WSDL document that describes your interfaces and how to call them. It enables clients that are not written using Delphi to call on your Web Service application. In our case (Delphi to Delphi on a local machine) we only need to fill the URL info path in our client main.pas. Build the Client ---------------------------------------------------------------------------- Note: On client apps, an invocation registry entry allows components to look up information that identifies the invokable interface and supplies information on how to call it. Next, we provide the THTTPRio object with the information it needs to identify the server interface and locate the server. All you need to do is supply the URL where you install the Web Service app. However, you may want to make your Web Service available to a wider range of clients. E.G., you may have clients that are not written in Delphi. If you are deploying several versions of your server app, you may not want to use a single hard-coded URL for the server, but rather let the client look up the server location dynamically. For these cases, you may want to publish a WSDL document that describes the types and interfaces in your Web Service, with information on how to call them. But in our case we only change the component THTTPRIO the URL property: http://milo2/scripts/VCLScannerServer.exe/soap/IVCLScanner //(milo2 or localhost is the web server) As I said, if the server is written in Delphi, the identification of the interface on the server is handled automatically, based on the URI that is generated for it when the interface is registered. Note: The path portion of this URL should match the path of the dispatcher component in the server’s Web Module, that's why the information of the generated file is so important. For testing and time saving you can change line 310 FindFiles(dlbDirectory.Directory,Aborted); with a hard-coded file to scan like: FindFiles('D:\franktech\entwickl\comdll_intf',aborted); The client calls the web service trough an interface object: try WS := HTTPRIO1 as IVCLScanner; WS.PostData(reFinalResults.Text,CRC); Now if all goes well it writes some files on your disk with scanned data in it. When you have trouble with database configuration or running out of time (lost in space) deactivate line 1063: WS := HTTPRIO1 as IVCLScanner; WS.PostUser(leEmail.Text,leFirstName.Text,leLastName.Text); Conclusion ----------------------------------------------------------------------------- To be happy and give some contributions try also the original Borland URL: WSDLLocation = 'http://ww6.borland.com/webservices/VCLScanner/ VCLScannerServer.exe/wsdl/IVCLScanner' Hope you'll learn from the example, for further actions here a step be step to build a Web Service Server: 1 Define and implement classes that implement the invokable interfaces you defined. 2 If your application raises an exception when attempting to execute a SOAP request, the exception will be automatically encoded in a SOAP fault packet, which is returned. 3 Choose File|New|Other, and on the WebServices page, double-click the Soap Server application icon. Choose the type of Web server application you want to have implement your Web Service. 4 The wizard generates a new Web Service application that includes three components: An invoker component (THTTPSOAPPascalInvoker). The invoker converts between SOAP messages and the methods of any interfaces you registered. A dispatcher component (THTTPSoapDispatcher). The dispatcher automatically responds to incoming SOAP messages and forwards them to the invoker. A WSDL publisher (TWSDLHTMLPublisher). The WSDL publisher publishes a WSDL document that describes your interfaces and how to call them. 5 Choose Project|Add To Project, and add the units you created to your Web server application. Note: All hints about publishing or registering is get done at runtime, so no registry or helper-file is needed but you can use the admin-file to change at runtime ports or adresses (VCLSCANNERSERVER_WSDLADMIN.INI) Calling the Service from .NET goes like this: private void Page_Load(object sender, System.EventArgs e) { IVCLScanner.IVCLScannerservice objIVCLScanner = new IVCLScanner.IVCLScannerservice(); bool booScanner = objIVCLScanner.PostData("blabla",413049395); lblScanner.Text = "" + booScanner; } We're learning from day to day (more at night ;)) so wisdom is where knowledge ends. How do you subclassing a versatile TList ? ******************************************************************************** The Hidden Path of the Worker in OP: The TList Search Keys: delphi delphi3000 article borland vcl code-snippet TList Subclassing Dynamic-Array Pointer Times Question/Problem/Abstract: You can use a TList almost for everything, so an own class leads to better design and maintainability therefore the article shows how and why. Answer: A certain view is that the TList class in Object Pascal (OP) is not a class from which we can descend, so the choice lies between subclassing (inheritance) or delegation (means create a separate class which holds the TList instance). But you can combine the two OO-technologies, especially you have multiple objects to store: 1. Subclass the TList that exposes only function equivalents of TList 2. Create a separate class that uses a TList instance Some Advantages and Tricks of TList: **************************************************************** TList, which stores an dynamic array of pointers, is often used to maintain lists of objects or records. TList introduces properties and methods to - Add or delete the objects in the list. - Rearrange the objects in the list. - Locate and access objects in the list. - Sort the objects in the list. The Items of a TList are numbered from 0 to Count-1, that means zero based. Above D5 and Kylix, Borland changed the operation of TList with the introduction of a new descendant called TObjectList. They changed only the mechanism of freeing objects in a TList. If the OwnsObjects property of a TObjectList is set to True (the default), TObjectList controls the memory of its objects (by a new virtual method Notify), freeing an object when its index is reassigned or or when the TObjectList instance is itself destroyed, but the more items in the TList, the longer it takes. The worse is that a TList gets slower, so write always like in the following example your own Free-method (as it was with pre-Delphi 5 TList)! var Childs: TSubTList; For i:= 0 to Childs.count - 1 do BusinessClass(Childs[i]).Free; Childs.Free BusinessClass(Childs[i]).Free calls every object on the list and frees the memory of every object or record that we add on the list. Then Child.Free calls Destroy and then it calls Clear of TList but Clear only empties the Items array and set the Count to 0. Clear frees the memory used to store the Items array and sets the Capacity to 0. Be care about Delete, Delete does not free any memory associated with the item. Gain speed with TList **************************************************************** The TList Sort mechanism is implemented with a quicksort algorithm, means we're fast enough, but how about the access? The normal way of accessing an object or item in a TList is the Items property in a default manner like theList[i]. The performance problem is the reading or writing, cause the compiler in OP inserts code to call getter or setter-methods, like theList.get[i] which checks the index between 0 and Count -1. If we want gain speed and get rid of the getter/setter we can call direct a variable of type PPointerList (named List), but no validation takes place. Childs.List^[i]; You then takes responsability of making sure reading or writing can't be beyond the ends of an array of the TList. Example **************************************************************** The subclassing is like a wrapper class with simle one-line calls to the corresponding methods of the inherited TList without typecasts. The example shows how to add a record but with an object you have to change only the type and instead of Dispose use Free. The Method Add always inserts the Item pointer at the end of the Items array, even if the Items array contains nil pointers: var Childs: TSubTList; //or TBrokerList Childs.Add(BusinessClass.create(self)); Not all of the entries in the Items array need to contain references to objects. Some of the entries may be NIL pointers. To remove the NIL pointers and reduce the size of the Items array to the number of objects, call the Pack method. type TBrokerRec = record intVal: integer; strVal: string; ptrStr: pChar; end; PBrok = ^TBrokerRec; TBrokerList = class (TList) protected procedure freeElement(elem: PBrok); function GetItems(Index: Integer): PBrok; procedure SetItems(Index: Integer; item: PBrok); public destructor destroy; override; function Add(Item: PBrok): Integer; procedure Delete(index: integer); function First: PBrok; function indexOf(item: PBrok): Integer; procedure Insert(index: integer; item: PBrok); function Last: PBrok; procedure pClear; function Remove(item: PBrok): Integer; property Items[Index: Integer]: PBrok read GetItems write SetItems; end; ********************* TBrokerList *************************************** destructor TBrokerList.destroy; begin clear; inherited Destroy; end; function TBrokerList.Add(Item: PBrok): Integer; begin result:= inherited Add(Item); end; procedure TBrokerList.Delete(index: integer); begin freeElement(items[index]); inherited delete(index); end; function TBrokerList.First: PBrok; begin result:= inherited First; end; procedure TBrokerList.freeElement(elem: PBrok); begin if elem <> NIL then dispose(elem); end; function TBrokerList.indexOf(item: PBrok): Integer; begin result:= inherited indexOf(item); end; procedure TBrokerList.Insert(index: integer; item: PBrok); begin inherited insert(index,item); end; function TBrokerList.Last: PBrok; begin result:= inherited Last; end; procedure TBrokerList.pClear; //instead of Free from outer class var x: Integer; begin for x:= 0 to count-1 do freeElement(items[x]); inherited clear; end; function TBrokerList.Remove(item: PBrok): Integer; begin result:= indexOf(item); if Result <> -1 then delete(result); end; function TBrokerList.GetItems(Index: Integer): PBrok; begin result:= inherited get(index); end; procedure TBrokerList.SetItems(Index: Integer; item: PBrok); begin inherited put(index, item); end; Easy Parsing an XML File ******************************************************************************** How do you get the elements from an XML file ? Search Keys: delphi delphi3000 article borland vcl code-snippet XML Parser DOM Times Scored: Component Download: http://max.kleiner.com/myconfig.xml Question/Problem/Abstract: In modern times, a configuration file has to be an XML standard so you want to parse that file to get the elements from corresponding nodes. Answer: First you have to import the Type library. This will create a wrapper class for that component and all you have to do is to name it in uses in your unit. I used msxml.dll(Version 2.0) to install the XML parsing components in the IDE through the Import Type Library option. See for more details: Importing XML DOM Parser in Delphi /ID 2021 Second we produce a simple XML file like a configuration file: (Name the file myconfig.xml) (Strange things happen (cause the xml interpreter in d3k-editor) with a well-formed file after submit the article, so I had to cancel first tags between databases and databases ) please download the file: http://max.kleiner.com/myconfig.xml ****************************************************************************** ?xml version="1.0"?> {databases database db="InterBase" connection name>TrueSoft path>superPath user>MaxMin /connection> /database> database db="myMax"> connection> name>maXml server>kylix02 user>carpeDiem /connection> /database> /databases>} ***************************************************************************** Third we build the procedure that parses 3 elements and assign it to strings: myname, mypath, myuser: string[255]; The accID and idNr are just to make the procedure more flexible for permissions and node navigation. The whole procedure goes like this: 1. Find the file myconfig.xml 2. Create the interface pointer xmlDoc 3. Load the XML file 4. Check the error handling with xmlDoc.parseError 5. Select the node 6. Check if it has childs 7. Get the nodelist and assign the elements to strings ***************************************************************************** procedure TForm1.parseXML; var SRec1: TSearchRec; accID: boolean; idNr: byte; myXMLpath: string; xmlDoc: IXMLDomDocument2; xmlnode: IXMLDomNode; xmlnodelist: IXMLDomNodeList; myname, mypath, myuser: string[255]; begin accID:= true; idNr:= 1; myXMLpath:= extractFilePath(Application.ExeName); if FindFirst (myXMLpath+'myconfig.xml',faAnyFile,SRec1)=0 then begin xmlDoc:= CoDomDocument30.Create; xmlDoc.validateOnParse := True; xmlDoc.async := False; xmlDoc.load(myXMLpath+'myconfig.xml'); if xmlDoc.parseError.errorCode = 0 then begin if accID then begin xmlDoc.setProperty('SelectionLanguage','XPath'); xmlnode := xmlDoc.selectSingleNode('//databases/database [@db='+''''+'InterBase'+''''+ ']'); if xmlnode.hasChildNodes then begin if (idNR <= xmlnode.childNodes.length) and (idNR > 0) then begin xmlnodelist := xmlnode.childNodes.item[(idNR-1)].selectNodes ('name'); if xmlnodelist.length <> 0 then myname:=xmlnode.childNodes.item[(idNR-1)].selectNodes('name').item [0].text; xmlnodelist := xmlnode.childNodes.item[(idNR-1)].selectNodes ('path'); if xmlnodelist.length <> 0 then mypath:=xmlnode.childNodes.item[(idNR-1)].selectNodes('path').item [0].text; xmlnodelist := xmlnode.childNodes.item[(idNR-1)].selectNodes ('user'); if xmlnodelist.length <> 0 then myuser:=xmlnode.childNodes.item[(idNR-1)].selectNodes('user').item [0].text; end; end else showmessage('no XML Childs'); end else showmessage('no permission'); end else begin showmessage('load XML error'); xmlDoc := NIL; FindClose(SRec1) end; end else showmessage('no XML file'); end; Carpe Diem... Understanding VisualCLX ******************************************************************************** What are hook-objects in a Qt application? Search Keys: delphi delphi3000 article borland vcl code-snippet CLX slots Qt Kylix library Times Scored: Company: kleiner kommunikation Reference: Code Central #16795 Question/Problem/Abstract: Beginning with Kylix needs some understanding between signals and slots, the way Linux/Qt deals with events and the Qt-library Answer: VisualCLX is the part of CLX that represents the Visual Components that would noramlly reside in the TWinControl hierarchy in the VCL. VisualCLX framework is a set of classes that represent Visual Controls but have to work (if possible) on both MS Windows and X in Linux. The controls represented by the VisualCLX components are implemented by a C++ class library called Qt and widgets, from the Norwegian development company called Trolltech. Qt is also available on Windows. - The VCL TWinControl class is called TWidgetControl Qt is a C++ class library, cause of differences in C++ and OP (ObjectPascal)details, an OP program cannot directly manipulate Qt widgets. Instead, VisualCLX makes use of an additional library, called the Qt interface library (written in C++ as libqtintf.so) which exports all the Qt functionality in a manner that is accessible to OP code. The import unit for this interface library is called Qt.pas but this means that rather than being declared as classes, the Qt widget methods are all imported as flat methods or strictly speaking functions. We define a flat method as a method of a class that is declared as a standalone subroutine or function. However, since at the C++ side they are indeed classes, almost every flat method takes one extra parameter, which is the reference to the Qt widget. You think this might slow applications but most of the time you won't measure any difference in run-time behaviour. So what's the difference in a architectural manner: In a known OP application, you call methods via object references, e.g.: myButton.setBounds(15, 15, 65, 35); Turning the method into a flat method, the object reference is passed as the first parameter so the method code knows which instance it should be invoking. Here is a "it goes almost like this" example flat method, which is equivalent to the method just used: QButton_SetBounds(myButton, 15, 15, 65, 35); or in a Kylix Qt-manipulation: uses Qt, QTypes; var Btn: QButtonH; Btn := QButton_create(Handle, PChar('Btn')); QButton_setGeometry(Btn, 15, 15, 65, 35); Of course you would normally have no need to write code like this as a QButton does it all for you, but it serves as a simplified example of how CLX components do their thing by using the CLXDisplayAPI. What's the CLXDisplayAPI ------------------------- The CLXDisplay API is the official name for the Qt.pas unit that ships with Kylix and also with Delphi6 or later. It acts as an import unit for the Qt widget library used by VisualCLX. So things in life are a bit more complicated than this. Qt is a C++ class library, and OP cannot direct manipulate C++ classes. Because of this, Borland wrote an additional library to lay between a CLX application and the Qt library. This extra library is called libqtintf.so (the Qt interface library), and Qt.pas is actually the import unit for this interface library. TWidgetControl--->Qt.pas->libintf.so--->Qt_Widget_Classes Understanding Signal/Slot mechanism ------------------------------------ A hook object is a simple C++ object that exists in the Qt interface library as we said as an intermediary. So you want to customise the reaction of a widget like in windows with eventhandlers, signal/slot play the role: - A signal (event) from a widget - A slot (event handler) responds to a signal So we learnt, it's not possible to have the slot written directly in OP, means the Qt interface library defines a hook class for each widget class. The hook class implements a simple slot for each available widget signal, whose sole job is to call some code in our Kylix application. More on signal / slot and the Way Kylix does: --------------------------------------------- So it seems that messages (callback functions) are not the CLX way of doing things, it means not that CLX provides no support of messages, but it's not the Kylix way of doing things so. We suggest, e.g. mouse movements, that you let CLX respond to the mouse and simply override the methods that CLX uses for those events. Creating a component and need to catch the mouse messages, you can use the following method: procedure MouseMove(shift: TShiftState; X, Y: integer); override; The way we have to think is that in Qt, developers do not respond directly to messages. Instead they work with a signal / slot mechanism and the connect function like QObject::connect(timer, SIGNAL(timeout)), SLOT(timerSlot())); timer -> start(1000); or another example to get acustomed to: QObject::connect(myslider, SIGNAL(sliderMoved(in )), mylcdNumber, SLOT(display(in ))); There is nothing special about the sliderMoved and display methods. Just ordinary C++ methods that are marked as signals and slots, just as some Kylix methods are marked as being virtual. QObject is the base class in Qt, just as TObject is the base class in OP (ObjectPascal). QObject has a class or static method named connect. An OP class method is the same thing as a C++ or Java static method. In particular, you can call a class or static method without first creating an instance of the object to which it belongs. And where's the event-loop in Kylix? Here is the event loop that lies at the center of CLX applications: procedure TApplication.HandleMessage; Hooks again and Overview --------------------------- Fact: So you learned that Qt uses a signal and slot mechanism, and CLX uses an event mechanism. It's not so important how the two are connected, it might be valuable some time later, but here is an overview: Qt has a signal and slot mechanism. CLX has an event mechanism. To translate Qt signals and slots into CLX events, the Kylix team created a mechanism known as hooks. Each CLX object type has a hook object. This hook object converts the signals and slot events associated with a particular object into CLX events. It then sends these events to the appropriate CLX control. In particular, there is a CLX method of TWidgetControl named EventFilter that receives the majority of these events. You can find more on this topic on the Kylix2 CompanionTool CD: sams_publishing/kdgch07.pdf chapter 7 CLX architecture & Visual Development or Code Central Entry ID #16795 ---------------------------------------------------------------- Here an impressive extract: If you feel the urge to go beyond the usual CLX API, then here is one of the methods that you want to override: function TWidgetControl.EventFilter(Sender: QObjectH; Event: QEventH): Boolean; This one is the big Kahuna. EventFilter gets most of the events that Qt and the OS throws at it. Just opening up QControls and looking at the 500+ lines that form the implementation of this method is enough to send any sane programmer running for the safety of the standard CLX APIs. However, some people like to live on the edge. They claim that the air is thinner but cleaner out there. function TWidgetControl.MainEventFilter(Sender: QObjectH; Event: QEventH): Boolean; cdecl; var Form: TCustmForm; begin try if csDesigning in ComponentState then begin Form := GetParentForm(Self); if (Form <> nil) and (Form.DesignerHook <> nil) and Form.DesignerHook.IsDesignEvent(Self, Sender, Event) then begin Result := True; Exit; end; end; Result := EventFilter(Sender, Event); except Application.HandleException(Self); Result := False; end; end; Safety Design with a Static Instance ******************************************************************************** How to build a real Singleton ? Search Keys: delphi delphi3000 article borland vcl code-snippet singleton class-reference pattern Times Question/Problem/Abstract: The Singelton Pattern is widely used, on the other side OP lacks of statics, means one instance for all classes. No problem with the following design which acts like a time-server. Answer: Sometimes operations are performed on a class itself, rather than on instances of a class (that is, objects). This happens, for example, when you call a constructor method using a class reference. TTimeKeeper = class; TTimeKeeperClass = class of TTimeKeeper; You can always refer to a specific class using its name, but at times it is necessary to declare variables or parameters that take classes as values, and in these situations you need class-reference types. In our case we need a class-method and a global function too to get the one and only instance: class function Instance: TTimeKeeper; function TimeKeeper: TTimeKeeper; //global function When this function is called, a safety instance is returned: function TimeKeeper: TTimeKeeper; begin Result := TTimeKeeper.Instance; end; A class method is a method (other than a constructor) that operates on classes instead of objects. The definition of a class method must begin with the reserved word class. A class method can be called through a class reference or an object reference. So the client calls the class method first: procedure TMainDlg.NewBtnClick(Sender: TObject); var myTimer: TTimeKeeper; begin myTimer:=TimeKeeper; StatusBar.Panels[0].Text:=timeToStr(myTimer.now); end; And the class method returns the protected and local instance: class function TTimeKeeper.Instance: TTimeKeeper; // Single Instance function - create when first needed begin Assert(Assigned(TimeKeeperClass)); if not Assigned(TimeKeeperInstance) then TimeKeeperInstance := TimeKeeperClass.SingletonCreate; Result := TimeKeeperInstance; end; ****************************************************************************************** unit SafetyTimeKeeper; interface uses SysUtils; type ESingleton = class(Exception); TInvalidateDestroy = class(TObject) protected class procedure SingletonError; public destructor Destroy; override; end; TTimeKeeper = class; TTimeKeeperClass = class of TTimeKeeper; TTimeKeeper = class(TInvalidateDestroy) private class procedure Shutdown; function GetTime: TDateTime; function GetDate: TDateTime; function GetNow: TDateTime; protected // Allow descendents to set a new class for the instance: class procedure SetTimeKeeperClass(aTimeKeeperClass: TTimeKeeperClass); // Actual constructor and destructor that will be used: constructor SingletonCreate; virtual; destructor SingletonDestroy; virtual; public // Not for use - for obstruction only: class procedure Create; class procedure Free(Dummy: integer); {$IFNDEF VER120} {$WARNINGS OFF} {$ENDIF} // This generates warning in D3. D4 has reintroduce keyword to solve this class procedure Destroy(Dummy: integer); {$IFDEF VER120} reintroduce; {$ENDIF} // Simple interface: class function Instance: TTimeKeeper; property Time: TDateTime read GetTime; property Date: TDateTime read GetDate; property Now: TDateTime read GetNow; end; {$IFNDEF VER120} {$WARNINGS ON} {$ENDIF} function TimeKeeper: TTimeKeeper; implementation class procedure TInvalidateDestroy.SingletonError; // Raise an exception in case of illegal use begin raise ESingleton.CreateFmt('Illegal use of %s singleton instance!', [ClassName]); end; destructor TInvalidateDestroy.Destroy; // Protected against use of default destructor begin SingletonError; end; { TTimeKeeper } var TimeKeeperInstance: TTimeKeeper = nil; TimeKeeperClass: TTimeKeeperClass = TTimeKeeper; class procedure TTimeKeeper.SetTimeKeeperClass(aTimeKeeperClass: TTimeKeeperClass); // Allow change of instance class begin Assert(Assigned(aTimeKeeperClass)); if Assigned(TimeKeeperInstance) then SingletonError; TimeKeeperClass := aTimeKeeperClass; end; class function TTimeKeeper.Instance: TTimeKeeper; // Single Instance function - create when first needed begin Assert(Assigned(TimeKeeperClass)); if not Assigned(TimeKeeperInstance) then TimeKeeperInstance := TimeKeeperClass.SingletonCreate; Result := TimeKeeperInstance; end; class procedure TTimeKeeper.Shutdown; // Time to close down the show begin if Assigned(TimeKeeperInstance) then begin TimeKeeperInstance.SingletonDestroy; TimeKeeperInstance := nil; end; end; constructor TTimeKeeper.SingletonCreate; // Protected constructor begin inherited Create; end; destructor TTimeKeeper.SingletonDestroy; // Protected destructor begin // We cannot call inherited Destroy; here! // It would raise an ESingleton exception end; // Protected against use of default constructor class procedure TTimeKeeper.Create; begin SingletonError; end; // Protected against use of Free class procedure TTimeKeeper.Free(Dummy: integer); begin SingletonError; end; class procedure TTimeKeeper.Destroy(Dummy: integer); begin SingletonError; end; // Property access methods function TTimeKeeper.GetDate: TDateTime; begin Result := SysUtils.Date; end; function TTimeKeeper.GetNow: TDateTime; begin Result := SysUtils.Now; end; function TTimeKeeper.GetTime: TDateTime; begin Result := SysUtils.Time; end; // Simplified functional interface function TimeKeeper: TTimeKeeper; begin Result := TTimeKeeper.Instance; end; initialization finalization // Destroy when application closes TTimeKeeper.Shutdown; end. News and Highlights from EKON 6 in Frankfurt ******************************************************************************** Prepared for .NET or Native ? Search Keys: delphi delphi3000 article borland vcl code-snippet Native .NET Managed Language Times Scored: Company: kleiner kommunikation Reference: http://community.borland.com/article/0,1410,28972,00.html Question/Problem/Abstract: How Borland and Delphi will be the freedom of choice and reaching new highs? Answer: I know this isn't the right place for general information but it's the right time to inform shortly the community ;) This document is intended to support some of the new concepts under research for the Delphi Language based on my visit as a speaker at the EKON 6. Most of you asked themselves the question if Delphi will survive in front of .NET. Absolutely, cause the Delphi language will cover or fit almost all our needs. So why this? - the new Delphi for .NET compiler is in the beta pipeline and will be shipped with D8(none of us wants MS Studio) - IBM signs a contract with Borland to provide Delphi with DB2 support - Native Compiler in Delphi 7 provides with CLX real cross-platform applications for Windows and Linux - Kylix Wins Best of Show at LinuxWorld 2002 ... - Using Delphi as a script language for ASP.NET is now possible Borland named the language ObjectPascal now Delphi language (I prefered OP) and will be the heart of 3 product-lines: - Delphi (Native Compiler) - Delphi for .NET - Kylix (for Linux) Also the recently published German IT-Magazine "Objekt Spektrum" Nr.5 mentions a great Delphi Project concerning E-Government in his editorial! Beside the fact that ObjectPascal (OP) belongs to the three top OO-language (OP, C++, Java), every attempt has been made by Borland and JEDI with providing SOAP and WebServices Support to keep Delphi running. Compiler support for .NET has also provided some exciting opportunities for enhancement to the Delphi language. In order to fully embrace the CLR and make Delphi a first class citizen in the new world of the so called managed code, some language features must be deprecated, and others are the subject of ongoing research. To prepare your code for a migration to .NET keep the following in mind: ------------------------------------------------------------------------------ Unsafe types arent't allowed like --------------------------------- PChar, PWideChar, and PAnsiChar Untyped pointers Untyped var and out parameters File of Real48 Variant records (records containing overlapping fields) The old Object Type from Borland Pascal 7 Unsafe code is also a red flag in the "well behaved" code environment --------------------------------------------------------------------- Absolute variables to override the MEM Addr(), Ptr(), Hi(), Lo(), Swap() standard procedures BlockRead(), and BlockWrite() Fail() routines GetMem(), FreeMem(), ReallocMem() inline assembler the @ operator ------------------------------------------------------------------------------ It's obvious that RealTime applications for fast engines like scanner, drivers, controller or parser dont't fit with .NET cause no pointers or assembler op-code is allowed anymore. So there is still the Native Compiler available but how long will it last, as long as we pay M$ to keep it open ;( Hopefully, we like what we can do otherwise nobody forces us to migrate to XP or next generation operating systems. Thats the freedom of choice by Borland, also called the switzerland of software. We do hate a new "softwar" like in the 80's between MAC and PC so the bridge between .NET and J2EE will be definitely WebServices with SOAP. Let's go back to Facts: Starting with Delphi 7, the compiler includes three new warnings that can help you locate code that is non-portable (i.e. it uses a deprecated language feature or type), or unsafe in the .NET Framework. In the .NET Framework, "unsafe" simply means that the code can't be verified by the static analysis performed by the CLR when the code is loaded. The compiler can warn about the usage of unsafe types, unsafe code, and unsafe casts. More of the new compiler you find in a document that is intended to introduce some of the new features and concepts under research for the upcoming Delphi for .NET compiler: http://community.borland.com/article/0,1410,28972,00.html Update one year later: Octane (Delphi for .NET) will be shipped at the beginning of 2004 and a Release of Delphi 7 will also be shipped so we have three choices: - Octane - Delphi 7 Release - Kylix 3 ****************************************************************************** So enough impressions, a last word to keep the good mood on: 1 The Bitles YELLOW SUBROUTINE 2 John Tra Volt EVERY NIGHT JAVA 3 Elvis Presley IN THE GOTO 4 Talking Heads STOP SENDING SOAP 5 Scrolling Stones GIMME PASSWORD 6 VAX Pistols GOD SAVE THE PIN 7 Think Floyd DARK SIDE OF THE CPU 8 Simon & Forunkel BIT OVER TROUBLED DATA 9 Tina Turner NETWORK CITY LIMIT 10 Low Read WALK ON THE FILE SIDE How to implement an Array Property ? ******************************************************************************** Working with an Interface and Array Properties too Search Keys: delphi delphi3000 article borland vcl code-snippet array-property Times Scored: Uploader: Max Kleiner Company: kleiner kommunikation Reference: http://www.delphi3000.com/articles/article_990.asp Question/Problem/Abstract: In an interface we can't use fields so when you declare a class that implements one or more interfaces, you must provide an implementation of all the methods declared in the interface and the fields too. Therefore Array Properties are welcome. Answer: Properties come in two mode behaviour, scalar and array. An array property can't be published in component design, but they have many other uses to struct your class. One of the best features is the array index can be ANY type, and multidimensional arrays are possible, too. For array-type properties, we must use getter and setter, means read and write methods; no possibilitie to map an array-type property directly to an array-type field. Thats real design. First we need a class or interface: IChaosBase = Interface(IUnknown) ['{C6661345-26D1-D611-9FAD-C52B9EAAF7C0}'] function getScales(index: integer): Tdouble; stdcall; procedure setScales(index: integer; scale: Tdouble); stdcall; property scales[index: integer]: TDouble read getScales write setScales; end; The aim is to store those 4 fields with the property scales[]: scaleX1: double; scaleX2: double; scaleY1: double; scaleY2: double; Second we need the implementing class. The simplest way to implement the _AddRef, _Release, and QueryInterface methods is to inherit them from TInterfacedObject, thats the meaning of TInterfacedObject: type TDouble = double; TDoubleArray = array[1..4] of TDouble; TChaosBase = class(TInterfacedObject, IChaosBase) protected myscales: TDoubleArray; function getScales(index: integer): TDouble; stdcall; procedure setScales(index: integer; scale: TDouble); stdcall; property scales[index: integer]: TDouble read getScales write setScales; end; Now comes the setter and getter, especially the setter setScalses() needs a second parameter to define the type (you remember the array index can be ANY type) in our case a simple double. Also the datastructure can be choosen (list, map, collection), in our case the structure is a simple array. function TChaosBase.getScales(index: integer): Tdouble; begin result:=myscales[index]; end; procedure TChaosBase.setScales(index: integer; scale: Tdouble); begin myScales[index]:=scale; end; At least the write-access goes like this from any class or method: scales[1]:=0.89; scales[2]:=1.23; scales[3]:=0.23; scales[4]:=1.34; or the read-access is a simple call to the propertie: scaledX:= (X-scales[1])/(scales[2]-scales[1]); scaledY:= (Y-scales[4])/(scales[3]-scales[4]); Default access: There can be only one default array property for each class, means instead of myChaosBase.scales[i] we can use myChaosBase[i] with the directive default: property scales[index: integer]: TDouble read getScales write setScales; default; An Iterative ASCII-Export ******************************************************************************** Exports database records to a delimited file Search Keys: delphi delphi3000 article borland vcl code-snippet ASCII Export Times Scored: Question/Problem/Abstract: All in one procedure, Delimiter- and SaveFile Dialog, no parameters cause of speed Answer: The procedure exports records from a Table to a specified ASCIIFile text file. Fields are separated by provided Delimiter Dialog character. All the forms are created by dynamic and the owner is the application. This means that when the application is destroyed, all the components are also destroyed. procedure ExportToASCII_Iterative; var i: Integer; dlg: TSaveDialog; ASCIIFileName: String[150]; ASCIIFile: TextFile; Delimiter: String[20]; Res: Boolean; begin Application.CreateForm(TDelimitFrm, DelimitFrm); with grunddatFrmModule.tblGrund do begin // the table to be exported! DelimitFrm:=TDelimitFrm.create(Application); DelimitFrm.ShowModal; if (DelimitFrm.OKBtn.ModalResult = idOK) then Delimiter := DelimitFrm.Select.Text; if Delimiter = '^M^J' then Delimiter := ^M^J; if Active then if (FieldCount > 0) and (RecordCount > 0) then begin dlg := TSaveDialog.Create(Owner); dlg.Filter := 'ASCII-Dateien (*.asc)|*.asc'; dlg.Options := Dlg.Options+[ofPathMustExist, ofOverwritePrompt, ofHideReadOnly]; dlg.Title := 'Data to ASCII export'; try Res := dlg.Execute; if Res then ASCIIFileName := Dlg.FileName; finally dlg.Free; end; if Res then begin AssignFile(ASCIIFile, ASCIIFileName); Rewrite(ASCIIFile); First; begin for I := 0 to FieldCount-1 do begin Write(ASCIIFile, Fields[I].FieldName); if I <> FieldCount-1 then Write(ASCIIFile, Delimiter); end; Write(ASCIIFile, Delimiter); while not EOF do begin for I := 0 to FieldCount-1 do begin Write(ASCIIFile, Fields[I].Text); if I <> FieldCount-1 then Write(ASCIIFile, Delimiter); end; Next; if not EOF then Write(ASCIIFile, Delimiter); end; CloseFile(ASCIIFile); if IOResult <> 0 then MessageDlg('Fault to ASCII-Write', mtError, [mbOK], 0); end; {field count} end; {Res check} end else {FieldCount else} MessageDlg('No Data to be exported',mtInformation, [mbOK], 0) else {Active else} MessageDlg('Table has to be open, mtError, [mbOK], 0); end; end; (* ================================================================= *) (* Ende von ASCIIEXP *) Building a Terminal Server Client-Application ******************************************************************************** Working with MSTerminal Services Advanced Client ActiveX Search Keys: delphi delphi3000 article borland vcl code-snippet Terminal-Server Architecture Company: kleiner kommunikation Reference: http://www.microsoft.com/windows2000/downloads/recommended/TSAC/tsmsi.asp? Question/Problem/Abstract: Even if a full Terminal Server Client is not installed on a user's computer or you want Server Apps embedded in Delphi applications, the ActiveX control (MSTSCAX) can be a great opportunity, let's see how it works. Answer: Since August 02 MS provides a new way to connect to a Terminal Server called the MSTerminal Services Advanced Client (TSAC). The TSAC is a Win32-based ActiveX control (COM object) that can be used to run Terminal Services sessions within EXE's that interact with applications running on a terminal server. The TSAC is an interim release of Terminal Services components and features. The purpose is to extend the functionality of Terminal Services, client side, so that system administrators, Web page designers, and Web administrators can implement Terminal Services client sessions in Web pages or Delphi applications without requiring the user to download or install the full Terminal Services client program. These innovations greatly extend the usefulness of Terminal Services for remote administration of servers or server applications. So how it works in Delphi? --------------------------- When you install the ActiveX Client Control, a minimal set of sample Web pages is also installed on your Web server, but now we're interested only in a Delphi-application, which shows in a "bitmaplike" the Servers desktop or in our example an application that runs on a server! Developers can use the TSAC to develop client-side applications that interact with applications running on a terminal server. MSTSCAX provides full control of Terminal Services user session. The Client Control is an ActiveX control that provides virtually the same functionality as the full Terminal Services Client, but it is designed to deliver this functionality in a simple EXE or over the Web. When embedded in Delphi applications, the ActiveX control can host a client session with a Terminal server, even if the full Terminal Server Client is not installed on a user's computer. Note: In order to run the following application on another computer like NT or W98 you must copy "mstscax.dll" on the target and register it with regsvr32. The Import of the Control -------------------------- Further tests and coding with the control are made by Dmitry Arefiev and myself. First of all we have to import the activeX control mstscax in Delphi 1. Download the TSMSISETUP.EXE (435kb) from http://www.microsoft.com/windows2000/downloads/recommended/TSAC/tsmsi.asp? 2. run TSMSISETUP.EXE and it will install MSTSCAX into specified directory. 3. run REGSVR32.EXE \MSTSCAX.DLL and it will register the control on your machine, then run Delphi and import the control into Delphi (via ActiveX) and create a Unit. 4. Put the imported control from the palette to a form. MSTSCAX is windowed control, which has 2 user session painting modes: Window mode. The window client region represents area, in which Terminal Server session will be painted. Full Screen mode. Here will be created special window, which will cover all desktop area. And user session will be painted in this window, and not in MSTSCAX window client region. The creating of the Client ------------------------------- Now we create a Delphi project and first have a look at four interesting properties: 1. Server: type a Terminal server name or TCP\IP address or select a server from the list of Available servers. Note: If you previously disconnected from a Terminal server without ending the session, the Terminal Services Client reconnects to that session (if the connection is configured for the reconnection of disconnected sessions). 2. Domain: Type your user name, password, and domain (if required), so it's the domain which user name belongs. procedure TfrmMain.btnConnectClick(Sender: TObject); var pathToRun: string[255]; begin pathToRun:='C:\WINNT\system32\clock.exe'; with TfrmTerminal.Create(Application) do begin MsTscAx1.Server := edtServer.Text; MsTscAx1.UserName := edtUserName.Text; MsTscAx1.Domain := edtDomain.Text; MsTscAx1.SecuredSettings.StartProgram := pathToRun; .... 3. BitmapPeristence: if we want to cache bitmaps set it to 1 (bitmaps stored on your local hard drive) 4. Compress: if we want to compress data set it to 1 procedure TfrmTerminal.FormCreate(Sender: TObject); begin MsTscAx1.AdvancedSettings.BitmapPeristence := 1; MsTscAx1.AdvancedSettings.Compress := 1; end; After all with the connect method we open the terminal emulation session: MsTscAx1.Connect; and with the disconnect method we close the connection-channel but session is not closed on the server. Therefore we must close the app on server by first closing the client application: procedure TfrmTerminal.FormClose(Sender: TObject; var Action: TCloseAction); begin if not frmMain.btnConnect.enabled then begin messageDlg('close server app', mtInformation, [mbOK], 0); Action := caNone; end; end; Then we receive onDisconnected so we can close the form: procedure TfrmTerminal.MsTscAx1Disconnected(Sender: TObject; DisconnectReason: Integer); begin Close; end; Benefits: Full application is on the server. Then MSTSCAX may be used to setup and run user session on client computer. You can also split application into parts. Some parts will be located on server and some on client computer. The MSTSCAX will be used to integrate parts into single application. One last compile: the propertie BitmapPeristence, I mean it should written Persistence but a look at the wrapper shows BitmapPeristence ;): // *********************************************************************// // DispIntf: IMsTscAdvancedSettingsDisp // Flags: (4416) Dual OleAutomation Dispatchable // GUID: {809945CC-4B3B-4A92-A6B0-DBF9B5F2EF2D} // *********************************************************************// IMsTscAdvancedSettingsDisp = dispinterface ['{809945CC-4B3B-4A92-A6B0-DBF9B5F2EF2D}'] property Compress: Integer dispid 121; property BitmapPeristence: Integer dispid 122; property allowBackgroundInput: Integer dispid 161; property KeyBoardLayoutStr: WideString writeonly dispid 162; property PluginDlls: WideString writeonly dispid 170; property IconFile: WideString writeonly dispid 171; property IconIndex: Integer writeonly dispid 172; property ContainerHandledFullScreen: Integer dispid 173; property DisableRdpdr: Integer dispid 174; end; When do we use Application (Owner), Self or NIL ? ******************************************************************************** Passing the right Owner to a component-constructor Search Keys: delphi delphi3000 article borland vcl code-snippet NIL Self owner aowner Times Scored: Question/Problem/Abstract: Which owner of a component passed to the constructor is suitable when dealing with run-time dialogs or forms Answer: The owner of a component is determined by the parameter passed to the constructor when the component in the VCL or CLX is created. But there are three or four possibilities passing one: Application, Self, NIL or a Owner by Yourself 1. Passing Application or owner --------------------------------------------------------------------------- You can set the Owner to application, means when manually creating a form like in our example to assure when the application closes the form is destroyed cleanly. When you pass Owner to a constructor you want to stay with the component as long as the application stays, for ex.: dlg:= TmySaveDialogFrm.Create(owner); This means that when the application is destroyed, all the components are also destroyed. This means also, passing Owner or application is the same! dlg:= TmySaveDialogFrm.Create(application); You can check that in the debugging mode with "pointer(application)" e.g.: $C11A3C as the reference is the same like "pointer(owner)" 11. Automatic passing -------------------------- For components created in the form designer by design-time, the form is automatically assigned as the owner to the component. By default, a form owns all components that are on it. In turn, the form is owned by the application. Thus when the application shuts down and its memory is freed, the memory for all forms (and all their owned components) is also freed. Hint: Owner is a property of TComponent so you don't have to declare it when passing to the Constructor: property Owner: TComponent; constructor TComponent.Create(AOwner: TComponent); begin FComponentStyle := [csInheritable]; if AOwner <> nil then AOwner.InsertComponent(Self); end; 2. Passing Self -------------------------------------------------------------------------- Self is useful for a variety of reasons. Within the implementation of a method, the identifier Self references the object in which the method is called, so the owner is the object. procedure TfrmIncome.Button3Click(Sender: TObject); var dlg: TSaveDialog; XMLFileName: String[150]; begin Dlg := TSaveDialog.Create(self); This means that when the form TfrmIncome is destroyed, the component TSaveDialog on the form TfrmIncomeis also destroyed. When dynamically creating a component as part of a form, the owner has to be the form itself, so you refer to the form via the Self pointer. 3. Passing NIL -------------------------------------------------------------------------- There is one common special case where you can set the owner to NIL. Imagine a component is created and destroyed within a single procedure, in UML called an association between objects, like a loose coupling. Then you prefer to create the dialog on the fly and no owner is needed; this means that the component has no owner and will never be destroyed by e.g. a Delphi Form . IT's your responsability to free it in a try/finally block after the create method. Passing NIL to create is an optimisation cause no owner has to be added by the compiler to the list of components it owns! dlg:= TSaveDialog.Create(NIL); dlg.Filter := 'ASCII-Dateien (*.asc)|*.asc'; try Res := Dlg.Execute; if Res then ASCIIFileName := dlg.FileName; finally dlg.Free; end; 4. Passing by Yourself -------------------------------------------------------------------------- In object terms spoken every control is a component. When dynamically creating one or more components as part of a dynamic form, the owner has to be the form itself, so you refer to the form via the self pointer and a pointer by yourself (frmMon). eg: constructor TTransMonitor.create(aOwner: TComponent); begin application.onMessage:= showTransMessage; frmMon:= TForm.create(self); memMon:= TMemo.create(frmMon); memMon.parent:=frmMon; memMon.setbounds(10,10,250,150); frmMon.caption:=frmTrans.strLit[14]; frmMon.show; end; Create a DBExpress-Connection at Runtime ******************************************************************************** Search Keys: delphi delphi3000 article borland vcl code-snippet dbExpress runtime connection Times Scored: Question/Problem/Abstract: If you have a Webservice or a nonvisual component, you can't put a TSQLConnection on a form so you have to call the connection at runtime Answer: The normal way for Delphi and Kylix is just to check dbExpress, put a TSQLConnection on a form then double-click the TSQLConnection to display the Connection Editor and set parameter values (database path, connection name etc.) to indicate the settings. But in our example, all goes by runtime (path and login) with dbExpress we don't need an alias or the BDE either. procedure TVCLScanner.PostUser(const Email, FirstName, LastName: WideString); var Connection : TSQLConnection; DataSet : TSQLDataSet; begin Connection:= TSQLConnection.Create(nil); with Connection do begin ConnectionName:= 'VCLScanner'; DriverName:= 'INTERBASE'; LibraryName:= 'dbexpint.dll'; VendorLib:= 'GDS32.DLL'; GetDriverFunc:= 'getSQLDriverINTERBASE'; Params.Add('User_Name=SYSDBA'); Params.Add('Password=masterkey'); Params.Add('Database=milo2:D:\frank\webservices\umlbank.gdb'); LoginPrompt:= False; Open; end; DataSet:= TSQLDataSet.Create(nil); with DataSet do begin SQLConnection:= Connection; CommandText:= Format('INSERT INTO kings VALUES("%s","%s","%s")', [Email,FirstN,LastN]); try ExecSQL; except end; end; Connection.Close; DataSet.Free; Connection.Free; end; Sending commands to the server ================================= Another possibilities is to send commands like CreateTable to the Server. For TSQLConnection, Execute takes three parameters: a string that specifies a single SQL statement that you want to execute, a TParams object that supplies any parameter values for that statement, and a pointer that can receive a TCustomSQLDataSet that is created to return records. Note: Execute can only execute one SQL statement at a time. It is not possible to execute multiple SQL statements with a single call to Execute, as you can with SQL scripting utilities. To execute more than one statement, call Execute repeatedly. It is relatively easy to execute a statement that does not include any parameters. For example, the following code in our example executes a CREATE TABLE statement (DataDefinitionLanguage) without any parameters on a TSQLConnection component: procedure createUserTable; var Connection: TSQLConnection; SQLstmt: String; begin Connection := TSQLConnection.Create(nil); with Connection do begin ConnectionName := 'VCLScanner'; DriverName := 'INTERBASE'; LibraryName := 'dbexpint.dll'; VendorLib := 'GDS32.DLL'; GetDriverFunc := 'getSQLDriverINTERBASE'; Params.Add('User_Name=SYSDBA'); Params.Add('Password=masterkey'); with TWebModule1.create(NIL) do begin getFile_DataBasePath; Params.Add(dbPath); free; end; LoginPrompt := False; Connected := True; SQLstmt := 'CREATE TABLE NewMaxCusts ' + '( ' + ' CustNo INTEGER NOT NULL, ' + ' Company CHAR(40), ' + ' State CHAR(2), ' + ' PRIMARY KEY (CustNo) ' + ')'; try Execute(SQLstmt, NIL, NIL); except raise end; Close; Free; end; //end Connection end; Build a Table Tree from Records to Objects ******************************************************************************** How to show a database self join in a TreeView Search Keys: delphi delphi3000 article borland vcl code-snippet recursion composite-pattern self-join Question/Problem/Abstract: In a relational database like the well known IBLocal you find a table DEPARTMENT which has a self join on her own, means there is a tree inside with a Parent/Child structure. Answer: In a relational database like the well known IBLocal you find a table DEPARTMENT which has a self join on her own, means there is a tree inside with a Parent/Child structure: DEPT_NO DEPARTMENT HEAD_DEPT 100 Sales and Marketing 000 120 European Headquarters 100 121 Field Office Swiss 120 and so on... This article is aimed mainly at people interested in trees and recurcions who need to find their way around self referencing system and get going with a object-visualisation in a tree. Furthermore it's an example for the Composite Design Pattern. First we need a startprocedure mainly to call the query, build the tree with records in objects and show the tree at last: procedure TForm1.btnTreeSelfClick(Sender: TObject); var dimlist: TStringList; begin dimlist:= TStringlist.create; datdepartment:= TBusinessObj.Create(NIL); try if datDepartment.open_recursiveQuery then with TDims.create(NIL, DimList) do begin FillTree(treeview1, NIL); Free; end; treeview1.FullExpand; btnLTree.visible:=true; finally; dimlist.Free; datDepartment.Free; end; end; The records in the query must follow one condition [child# > parent#], means a child like "FieldOffice Swiss" has a child# 121 so the parent it belongs is 120. So we call the query and open the dataset: function TBusinessObj.open_recursiveQuery: boolean; begin result:= false; with qryselfTree do begin try SQL.Clear; SQL.add('SELECT dept_no, department, location, head_dept' + ' FROM department ORDER BY dept_no'); open; result:= true; except on EDataBaseError do showmessage('data not found'); end; end; end; Next we implement a class which holds at runtime the whole tree table in objects. Every object is a stored record from the table DEPARTMENT with the attributes you want to publish or manipulate at runtime. Grace the members FParent, FChild the whole table is chained in objects with a top level object, in our case the CORPORATE HEADQUARTERS. This top level object doesn't have a parent, the parent is NIL. TDims = class(TObject) private FstrDimArt: string ; FstrDimArtBez: String ; FParent: TDims ; FChilds: TList ; public Constructor create(Sender: TDims; myRegister: TStringList); Destructor Destroy; override; Procedure FillTree(aOl: TTreeview; xnode: TTreenode); Function IsDimartInChilds (DimArt: string): Boolean; property DimArtBez: string Read FstrDimArtBez; property DimArt: string Read FstrDimArt; property treechilds: TList read FChilds; end ; Now comes the real power part, a recursive constructor which collects all records to build the tree in memory. When a parent like "Sales and Marketing" finds some childs like "European Headquarters" it creates new objects in a recursion and adds the object to the list: FChilds.Add(TDims.create(self, myRegister)); Recursions aren't dark chapter by opening in Delphi the debug windows "Call Stack and Local Variables" you'll learn a lot. When a function name appears anywhere else in a statement block, the compiler interprets it as a recursive call to the function itself. The constructor has been used to recursively include another objects. But in every tree an object without childs terminates without having cycles in them. The last level of a tree is almost the deepness of recursions. By the way do you know the explanation of a recursion in a "well behaved" dictionary: Recursion: See under Recursion ;) Let jokes aside, here it is: A programming technique in which a subroutine calls itself. Use care to ensure that a recursion eventually exits. Otherwise, an infinite recursion will cause a stack fault. constructor TDims.create(Sender: TDims; myRegister: TStringList); var bmAkt: TBookmark; Begin inherited Create; with datDepartment.qrySelfTree do begin FstrDimArt:= fieldByName('DEPT_NO').AsString; FstrDimArtBez:= fieldbyName('DEPARTMENT').AsString; myRegister.AddObject(Format('%10s',[FstrDimArt]), self); FChilds:= TList.Create; FParent:= Sender; bmAkt:= GetBookmark; if Locate('DEPT_NO', FstrDimArt,[]) then while Not (EOF) Do Begin if (fieldByName('HEAD_DEPT').Asstring = FstrDimArt) then FChilds.Add(TDims.create(self, myRegister)); Next; end; GotoBookmark (bmAkt); FreeBookmark (bmAkt); end; end; Destructor TDims.Destroy; var i: integer; Begin if FChilds <> NIL Then For i := 0 to FChilds.Count -1 do TDims(FChilds[i]).Free; FChilds.Free; Inherited Destroy; end; Now comes the last part, the most efficient way to represent the tabel tree in a view. TTreeView represents a window that displays a hierarchical list of items, such as the headings in a document, the entries in an index, or the files and directories on a disk. Use TTreeView to add an expanding and contracting outline to a form. Each node in a tree view control consists of a label and a number of optional bitmapped images. Each node can have a list of subnodes associated with it. By clicking on a node, the user can expand or collapse the associated list of subnodes. At run-time nodes can be added and inserted by using the TTreeNodes methods AddChildFirst, AddChild, AddChildObjectFirst, AddChildObject, AddFirst, Add, AddObjectFirst, AddObject and Insert. We only need AddChild: Procedure TDims.FillTree(aOl: TTreeview; xnode: TTreenode); var i: integer ; dbcontent: string[255]; begin dbcontent:= dimart +' '+ dimartbez; xnode:= aOl.items.addchild(xnode, dbcontent); for i:= 0 to treechilds.Count -1 do TDims(treechilds.items[i]).FillTree(aOl, xnode); end; I wish more X-mas trees like treeviews ;) How do we store Graphics/Shapes like an Object ? ******************************************************************************** Storing and loading components to and from streams Search Keys: delphi delphi3000 article borland vcl code-snippet streams persistent shapes writer graphics Component Download: http://max.kleiner.com/download/usecase.zip Question/Problem/Abstract: Designing a diagram editor or a graphic-tool raises the problem of storing all the painted shapes in a file without get lost in too much overhead. Answer: Suppose you have to to design a diagram-editor with bitmaps, charts, arrows and a lot of shapes inside, you'll get the problem how to store (and hopefully load) all these objects. Happy you find the proposition on delphi3000.com, all objects descend from a baseclass (TmCustomShape) and you call the class method from a SaveDialog1 like this: procedure TMainDlg.SaveBtnClick(Sender: TObject); begin if SaveDialog1.Execute then begin TmCustomShape.SaveToFile(SaveDialog1.FileName,ScrollBox1); end; end; Class Method A class method is a method (other than a normal constructor) that operates on classes instead of objects. The definition of a class method must begin with the reserved word class. If the method is called in the class TMainDlg in our case, then Self is of the type class of TMainDlg. Thus you cannot use Self to access fields, properties, and normal (object)methods, but you can use it to call constructors and other class methods. Our graphic base class TmCustomShape (the descendant of TGraphicControl) with other class methods goes like this: TmCustomShape = class (TGraphicControl) private protected ..... public constructor Create(AOwner : TComponent); override; destructor Destroy; override; procedure AlignCaption(Alignment : TAlignment); class procedure DeleteAllShapes(ParentControl : TWinControl); class procedure DeleteSelectedShapes(ParentControl : TWinControl); class procedure LoadFromFile(const FileName: string;ParentControl: TWinControl); class procedure SaveToFile(const FileName : string;ParentControl: TWinControl); procedure SetBounds(ALeft,ATop,AWidth,AHeight : Integer); override; class procedure UnselectAllShapes(ParentControl : TWinControl); property Selected: Boolean read FSelected write SetSelected; published property Caption: TmTextShape read FCaption write SetCaption; property OnClick; property OnDblClick; end; Inherit from TWinControl As we can see, we paint all graphic objects on a ScrollBox1 which has the type of TWinControl. So every object has the parent of ScrollBox1. To maintain a consistent appearance across your diagram editor, you can make any control look like its container—called its parent—by setting the parent properties to ScrollBox1. And TWinControl is the base class for all windowed controls, including many of the items that you will use in the user interface of an application. The following SaveToFile method profits from a Stream Class. Streams are just ways of reading and writing data. Streams provide a common interface for reading and writing to different media such as memory, strings, sockets, and blob streams. In the following we use TFileStream to access the information in disk files. 1. Create a TFileStream instance with passing FileName 2. Create a writer object, it allocates memory for a filer object, and associates it with the stream passed in the Stream parameter, with a buffer of size BufSize in our case 1024. 3. Write all the components, in our case from ScrollBox1 in a stream A Streaming System WriteComponent is used internally in the Delphi component streaming system, but can also be called directly when writing components to memory streams or database blobs. WriteComponent constructs a writer object and calls its WriteRootComponent method to write the component specified by Instance, and its owned objects, to the stream. TWriter handles the mechanics of writing the data associated with a component to a stream. It is the writer object, rather than the stream, that is responsible for handling the complexities of streaming components. class procedure TmCustomShape.SaveToFile(const FileName: string;ParentControl: TWinControl); var FS: TFileStream; Writer: TWriter; RealName: string; begin FS:= TFileStream.Create(Filename,fmCreate or fmShareDenyWrite); Writer := TWriter.Create(FS,1024); try Writer.Root := ParentControl.Owner; RealName := ParentControl.Name; ParentControl.Name := ''; Writer.WriteComponent(ParentControl); ParentControl.Name := RealName; finally Writer.Free; FS.Free; end; end; ComponentCount & ControlCount Once the streaming process is underway, programs do not need to directly manipulate writer objects. The interaction between the writer, component, and stream objects happens automatically in methods of these objects that make calls to each other! We also check the RealName to prevent from duplicates so we loop through all the components on the form to ensure that this name is not already in use: for i := 0 to Owner.ComponentCount - 1 do begin if Owner.Components[i].Name = TempName then begin AlreadyUsed := True; Break; end; end; The question arise how do we store all the shapes during runtime, is it an internal structure like TList or TConnection or a great bitmap ? Not at all, we simple profit from ObjectPascal in the way using the ParentControl list from TWinControl and add or delete shapes dynamically at runtime (KISS, keep it smart and simple;)): while i < ParentControl.ControlCount do begin if ParentControl.Controls[i] is TmCustomShape then begin ParentControl.Controls[i].Free; end else begin Inc(i); end; end; Painting At last the creation of a graphic object must be an descendant of TmCustomShape with overriding the paint methode and the parent is ScrollBox1, so we can save it with WriteComponent in a StreamFile: with TMoveableChart.Create(Self) do begin Caption:= TmTextShape.Create(Self); Caption.Text:= 'Chart Generator'; Caption.OnDblClick := CaptionDblClick; ShapeType:= stEllipse; Top := Y; Left := X; OnClick := ShapeClick; Parent := ScrollBox1; AlignCaption(taCenter); end; So I hope to bring a glance at this framework, please send me an email to get the full source code for free or see you at EKON06 in Frankfurt. An Application Loader with a TCPServer ******************************************************************************** How to build DelphiWebStart (DWS) ? Search Keys: delphi delphi3000 article borland vcl code-snippet indy dws tcp loader thread deploy sourceforge Component Download: http://max.kleiner.com/download/dws.zip Question/Problem/Abstract: Loading Delphi apps without a browser and on Win as Linux as well needs a decision once. With a loader on the client side, no further installation is in charge. Answer: 28.10.03 Project DWS is now under Sourceforge: http://sourceforge.net/projects/delphiwebstart *********************************************** We had the requirement starting different Delphi apps from a linux or windows server, wherever you are. We call it Delphi Web Start (DWS). The dws-client gets a list and after clicking on it, the app is loading from server to client with just a stream. First we had to choose between a ftp and a tcp solution. The advantage of tcp is the freedom to define a separate port, which was "services, port 9010 - DelphiWebStart". You will need indy. Because it is simple to use and very fast. The tcp-server comes from indy which has one great advantage: CommandHandlers is a collection of text commands that will be processed by the server. This property greatly simplify the process of building servers based on text protocols. First we start with DWS_Server, so we define two command handlers: CTR_LIST = 'return_list'; CTR_FILE = 'return_file'; By starting the tcp-server it returns with the first command handler "CTR_LIST" a list of the apps: procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread); ... // comes with writeline from client if sRequest = CTR_LIST then begin for idx:= 0 to meData.Lines.Count - 1 do athread.Connection.WriteLn(ExtractFileName(meData.Lines[idx])); aThread.Connection.WriteLn('::END::'); aThread.Connection.Disconnect; One word concerning the thread: In the internal architecture there are 2 threads categories. First is a listener thread that "listen" and waits for a connection. So we don't have to worry about threads, the built in thread will be served by indy though parameter: IdTCPServer1Execute(AThread: TIdPeerThread) When our dws-client is connected, this thread transfer all the communication operations to another thread. This technique is very efficient because your client application will be able to connect any time, even if there are many different connections to the server. The second command "CTR_FILE" transfers the app to the client: if Pos(CTR_FILE, sRequest) > 0 then begin iPos := Pos(CTR_FILE, sRequest); FileName := GetFullPath(FileName); if FileExists(FileName) then begin lbStatus.Items.Insert(0, Format('%-20s %s', [DateTimeToStr(now), 'Transfer starts ...'])); FileStream := TFileStream.Create(FileName, fmOpenRead + fmShareDenyNone); aThread.Connection.OpenWriteBuffer; aThread.Connection.WriteStream(FileStream); aThread.Connection.CloseWriteBuffer; FreeAndNil(FileStream); aThread.Connection.Disconnect; Now let's have a look at the client side. The client connects to the server, using the connect method of TIdTcpClient. In this moment, the client sends any command to the server, in our case (you remember DelphiWebStart) he gets the list of available apps: with IdTCPClient1 do begin if Connected then DisConnect; showStatus; Host:= edHost.Text; Port:= StrToInt(edPort.Text); Connect; WriteLn(CTR_LIST); After clicking on his choice, the app will be served: with IdTCPClient1 do begin ExtractFileName(lbres.Items[lbres.ItemIndex])])); WriteLn(CTR_FILE + lbres.Items[lbres.ItemIndex]); FileName:= ExpandFileName(edPath.Text + '/' + ExtractFileName(lbres.Items[lbres.ItemIndex])); ... FileStream := TFileStream.Create(FileName, fmCreate); while connected do begin ReadStream(FileStream, -1, true); .... execv(pchar(filename),NIL); Better with a compiler directive to load delivered files: {$IFDEF LINUX} execv(pchar(filename),NIL); //libc.system(pchar(filename)); {$ENDIF} {$IFDEF MSWINDOWS} // shellapi.WinExec('c:\testcua.bat', SW_SHOW); with lbstatus.items do begin case shellapi.shellExecute(0,'open', pchar(filename), '',NIL, SW_SHOWNORMAL) of 0: insert(0, 'out of memory or resources'); ERROR_BAD_FORMAT: insert(0, 'file is invalid in image'); ERROR_FILE_NOT_FOUND: insert(0,'file was not found'); ERROR_PATH_NOT_FOUND: insert(0,'path was not found'); end; Insert(0, Format('%-20s %s', [DateTimeToStr(now), filename + ' Loaded...'])); end {$ENDIF} The datastructure is a direct file access. In this case, rather than populating a stand-alone memory structure, the data is written to the StringGrid (which is serving both as a memory structure for holding the data and as a visual control for navigating and editing the data). type TAppData = record Name: string[50]; Size: longint; Release: string[30]; descript: string[80]; end; TBuildAppGrid = class (TObject) private aGrid: TStringGrid; app: TAppData; f: file of TAppData; FaDatfile: ShortString; Fmodified: Boolean; protected function GetaDatfile: ShortString; procedure SetaDatfile(const Value: ShortString); public constructor initGrid(vGrid: TStringGrid; vFile: shortString); procedure fillGrid; procedure storeGrid; property aDatfile: ShortString read GetaDatfile write SetaDatfile; property modified: Boolean read Fmodified write Fmodified; end; One note about execution on linux with libc-commands; there will be better solutions (execute and wait and so on) and we still work on it, so I'm curious about comments on "Delphi Web Start" therfore my aim is to publish improvments in a basic framework on sourceforge.net depends on your feedback ;) Many thanks to Dr. Karlheinz Mörth with a first glance. Test your server with the telnet program. After connecting with host, type "return_list" and you'll see a first result. I know that we haven't implement an error handling procedure, but for our scope this example is almost sufficient. The DWS-source holds version 0.9. When use Interfaces, when use Inheritance ? ******************************************************************************** Search Keys: delphi delphi3000 article borland vcl code-snippet Interface Inheritance Class Times Scored: Question/Problem/Abstract: There are two possibilities to define a (same) class hierarchy: - with interfaces - with inheritance Which one suits your needs Answer: You can fulfill the same operations with interfaces or inheritance as the following shows: IShape = interface procedure paint; end; TSquare = class(TInterfacedObject, IShape) procedure paint; end; TCircle = class(TInterfacedObject, IShape) procedure paint; end; TShape = class procedure paint; virtual; abstract; //procedure makeShape(afigure: TShape); end; TSquare2 = class(TShape) procedure paint; override; end; TCircle2 = class(TShape) procedure paint; override; end; Interfaces are useful when a set of operations, such as rendering or streaming, are used in a broad range of objects. They can reuse code and apply methods to a variety of different applications. Almost the same could have been accomplished by having TSquare2 and TCircle2 descend from TShape which implemented the virtual method Paint. So whats the difference from a point of design? With inheritance you can implement a base behaviour in the base-class like makeShape(), interfaces are pure abstract and dont't allow a real method. You have garbage collection with interfaces and you can handle objects without having to require the object to descend from a particular base class. Even if two classes did not share a commen ancestor, they are assignment compatible with a variabel of IShape: procedure TfrmGen.Button2Click(Sender: TObject); var painter: IShape; painter2: TShape; begin // interface painter:= TSquare.create; painter.paint; painter:= TCircle.create; painter.paint; // inheritance virtual painter2:= TSquare2.create; //painter2.paint; painter2.makeShape(painter2); painter2:= TCircle2.create; painter2.makeShape(painter2); // virtual alternative with painter2.Create do begin makeShape(TSquare2.create); makeShape(TCircle2.create); end; end; A well designed inheritance can be more stable and maintainable in comparison to much runtime objects that implement the same interface. So inheritance has more advantage in a well established design-time hierarchy, interfaces are best in run-time between components. For example we improve a method in a base class, inheritance makes it possible. On the other hand interfaces are more flexible to replace or delegate objects at runtime. For this it's a must do generate a GUID (Globally Unique Identifier) in square brackets. GUID's arent strictly necessary, but if you want to switch between interfaces you'll need them to make QueryInterface work! Now let's compare the advantages between the two Inheritance Interface - big hierarchy - delegation - base behavior - more implemantations of one interface - libraries - run time packages - real time freeing - garbage collection (reference counter) - subclassing - run time flexibility - design time properties - design by contract - fields - properties The most part is the realisation that you cannot mix object references and interface references. Interfaces are reference counted and objects not, so mixing the two approaches gets an access violation. See you at EKON7 in Frankfurt or in my new book "Patterns konkret" ;) // example implementation { TShape } procedure TShape.makeShape(afigure: TShape); begin if afigure <> NIL then afigure.paint; end; { TSquare2 } procedure TSquare2.paint; begin frmgen.memo1.lines.add('square virtual painted'); end; { TCircle2 } procedure TCircle2.paint; begin frmgen.memo1.lines.add('circle virtual painted'); end; end. Direct File Access with a StringGrid ******************************************************************************** Search Keys: delphi delphi3000 article borland vcl code-snippet PAC Architecture StringGrid Fileaccess Question/Problem/Abstract: From time to time we need to populate a stringgrid from a file and save to a file in a well defined structure like a record. Here's a way to handle this with a contol class. Answer: Direct file access refers to that reading physical files from a local file system, and managing the data stored in them. These files can be simple of binary or ASCII filetyp, in our case the container is a record. type TAppData = record Name: string[50]; Size: string[20]; Release: string[30]; descript: string[80]; end; You can always use a StringGrid to display a Query Result, but if you don´t need the overhead of a data aware component or you don't want a database in an embedded system for ex., direct file access is the choice. But as a developer you are responsible for every aspect of the data access. In short, before any data access can occur, data must be read from one or more files and stored in memory. Memory structures are - Streams, - StringLists - file of record - arrays of records, and so on Next we need a class, which shows sort of the principle of the PAC Architecture (Presentation, Abstract, Control) in One. Every agent is responsible for a specific aspect of the application's functionality and consists of three elements: presentation (stringgrid), abstraction (record), and control (load from file of record to a stringgrid). TBuildAppGrid = class (TObject) private aGrid: TStringGrid; app: TAppData; f: file of TAppData; FaDatfile: ShortString; protected function GetaDatfile: ShortString; procedure SetaDatfile(const Value: ShortString); public constructor initGrid(vGrid: TStringGrid; vFile: shortString); procedure fillGrid; procedure storeGrid; property aDatfile: ShortString read GetaDatfile write SetaDatfile; end; Next, if a data file already exists, it is opened and the previously stored data is read from it. In our case the data is written to the stringgrid (which is serving both as a memory structure for holding the data and as a visual control for navigating and editing the data). We need the constructor to pass the grid and the filename, so the class TBuildAppGrid is independent from a form namespace, no uses like frmUnit is needed, just uses QGrids as a dependency. { ******************************** TBuildAppGrid ********************************* } constructor TBuildAppGrid.initGrid(vGrid: TStringGrid; vFile: shortString); begin aGrid:= vGrid; aDatfile:= vFile; with aGrid do begin ScrollBars:= ssAutoVertical; FixedRows := 1; FixedCols:= 0; ColCount:= 4; RowCount:= 10; end; end; procedure TBuildAppGrid.fillGrid; var crow: Integer; begin crow := 1; with aGrid do begin Cells[0,0]:= 'Application Name'; ColWidths[0]:= 120; Cells[1,0]:= 'App Size'; ColWidths[1]:= 60; Cells[2,0]:= 'Release Date'; ColWidths[2]:= 90; Cells[3,0]:= 'Description'; ColWidths[3]:= 140; if aDatFile <> '' then begin AssignFile(f,aDatFile); Reset(f); try while not Eof(F) do begin Read (F, app); Cells[0,crow]:= app.Name; Cells[1,crow]:= app.size; Cells[2,crow]:= app.Release; Cells[3,crow]:= app.descript; Inc(cRow); RowCount:= crow; end; finally CloseFile(f); end; end;// if FileExists... end; //with end; function TBuildAppGrid.GetaDatfile: ShortString; begin if FileExists(FaDatFile) then result:= FaDatFile else result:= ''; end; This CLX example of direct access presented so far assumes that only a single user can access the files. If two or more users (or applications) can be permitted to access the data simultaneously, your code must also be build to resolve competition for records. Next, we have to store the data after editing: implementation uses sysutils, QDialogs, QControls, QStdCtrls; procedure TBuildAppGrid.SetaDatfile(const Value: ShortString); begin if FaDatfile <> Value then begin FaDatfile := Value; end; end; procedure TBuildAppGrid.storeGrid; var crow: Integer; begin //if FModified then if MessageDlg('Save Changes in ' + aDatFile, mtConfirmation,mbOkCancel,0) = mrOK then begin AssignFile(f, aDatfile); Rewrite(f); try for crow:= 1 to Pred(aGrid.RowCount) do begin app.Name:= aGrid.Cells[0, crow]; app.size:= aGrid.Cells[1, crow]; app.Release:= aGrid.Cells[2, crow]; app.descript:= aGrid.Cells[3, crow]; Write (f, app); end; finally CloseFile(f); end; end; //if MessageDlg... end; end. At least the client calls the class like this: procedure TForm1.FormCreate(Sender: TObject); begin myDatFile:= 'binaries3.txt'; myGridC:= TBuildAppGrid.initGrid(strGrd, myDatFile); myGridC.fillGrid; end; procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin myGridC.storeGrid; myGridC.Free; end; Some Relations between CLX and Qt ******************************************************************************** Crossplatforming Search Keys: delphi delphi3000 article borland vcl code-snippet CLX Qt FreeCLX Crossplatform Times Scored: Company: kleiner kommunikation Reference: http://sourceforge.net/projects/freeclx/ Component Download: http://max.kleiner.com/download/clxtetris.tar Question/Problem/Abstract: How about licensing, libraries, constructors or event-handling in CLX with Qt? How to built Tetris on Linux for example? Answer: The Relationship between CLX and Qt As you may know 3 porting techniques exists: - Platform specific port: targets an OS and underlying APIs (Win32) - Crossplatform port: targets a crossplaform API (like CLX) - Emulation: leaves the code alone and port parts of the API it uses Kylix uses CLX in place of the VCL for Crossplatform. CLX provides access to Qt widgets (from window+gadget) in the Qt shared libraries. This Qt-library is available on Linux and Win as well! Functions are then wrapped in a shared object in Linux and DLL in Win. In most cases, we don't need to change occurences of TWinControl to TWidgetControl. The following type declaration appear in the QControls.pas to simplify sharing of source code: TWinControl = TWidgetControl; Because CLX is a wrapper around Qt, and most of the controls on the Kylix Component palette are simply Pascal wrappers around Qt classes, so most of the units starts with a Q in their names: unit tetris1; interface uses SysUtils, Classes, QGraphics, QDialogs, QComCtrls, QTypes, QExtCtrls, QButtons, QControls, QForms, QStdCtrls; Crossplatform ------------------------------- Kylix with CLX runs only on Linux, and Delphi 6/7 supports also CLX on Windows. However, Qt alone has a wider range of deployment e.g.: AIX 4.1, FreeBSD 2.1, HP-UX 10.20, Solaris 2.5.1, Tru64 (Digital UNIX) 4.0, Windows NT and so on. Constructors ------------------------------- When you explicitly create a CLX object in code, by using the Create(aHandle) instead of Create() method of the object, you are passing the instance of an existing Qt widget to the CLX so this CLX object does not own the Qt widget that is passed to it. Licensing ------------------------------- There is a free distribution of Qt that you can use for open-source projects on Linux. Various licensing agreements that involve payments are put into effect if you decide not to go to the opensource route. That means, you can distribute Kylix apps in any manner you like. However, if you include even one direct call to Qt in your Kylix app, then the free Borland license for distributing your apps is no more valid. FreeCLX ------------------------------- At the time of this writing, the open-source version of Kylix is available since the summer of 2001. Updates to CLX appear on the following site. For instance, you can get the latest version of libqtintf.so.2.2.4.1 from the FreeCLX site.You can access FreeCLX at http://sourceforge.net/projects/freeclx/ or freeclx.sourceforge.net with a redirect to Borland. FreeCLX is the Open Source project for Borland's CLX Component Library for GNU/Linux. The BaseCLX, VisualCLX and DataCLX packages have been dual-licensed to allow for open source collaboration. They are licensed under both the Borland proprietary No-Nonsense License and the GNU General Public License(GPL). Events ------------------------------- There is a complex interrelationship between CLX and Qt when it comes to capturing events. In Qt, programmers traditionally do not respond directly to messages. Instead, they work with a signal and slot mechanism. If you are creating a component and want to catch mouse move messages, you should use the following method: procedure MouseMove(Shift: TShiftState; X, Y: Integer); override; This method overrides the CLX MouseMove method of the TControl object. The MouseMove method is called whenever the user moves the mouse over a component. You ll get this event on right clicks and middle clicks as well as left clicks or keyDown like the example, so your first task is to check which Button or Key is generating the event. procedure TTetro1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if ReentKeys then Exit else ReentKeys := True; if not FigureActive then begin ReentKeys := False; Exit; end; case Key of Key_Up: RotateFigure; Key_Down, Key_Space: begin //VK_SPACE in Win repeat ClearFigureIntoGlass; Inc(FigureY); until not PutFigureIntoGlass(mdDown); Inc(Score,5); end; Key_Left: if FigureX>0 then begin ClearFigureIntoGlass; Dec(FigureX); PutFigureIntoGlass(mdLeft); end; Key_Right: if FigureX+FigureXSize ClearFigureIntoGlass; Inc(FigureX); PutFigureIntoGlass(mdRight); end; end; ReentKeys := False; end; For the standard Latin-1 characters (ie, #32 to #255), the Key code is the Ord() of the character. Setting the Key code to 0 suppresses further processing. If you do this in an OnKeyDown handler, for example, you ll still get an OnKeyUp event when the key is released, but you will not get an OnKeyPress. Any Delphi code that uses Windows VK_ key names will have to be rewritten to use Qt s Key_ names. It should be sure to note that the CLXs Brush.Bitmap is considerably more useful than the VCL s: Window s brushes only use the top-left 8x8 pixels of their bitmap, while Qt s brushes use the whole bitmap. for I := 1 to MaxFigureSize-2 do for J := 1 to MaxFigureSize do begin X1 := NextLeftOfs+(J-1)*NextBarWidth; X2 := X1+NextBarWidth; Y1 := NextTopOfs+(I-1)*NextBarHeight; Y2 := Y1+NextBarHeight; if CurSheet[I,J]>0 then begin NewRect := Rect(X1+1,Y1+1,X2-1,Y2-1); Canvas.Brush.Color := NextColor; Canvas.FillRect(NewRect); Canvas.Pen.Color := clGray; Canvas.MoveTo(X1,Y1); Canvas.LineTo(X1,Y2-1); Canvas.LineTo(X2-1,Y2-1); Canvas.Pen.Color := clWhite; Canvas.LineTo(X2-1,Y1); Canvas.LineTo(X1,Y1); end else begin Canvas.Brush.Color := clSilver; NewRect := Rect(X1,Y1,X2,Y2); Canvas.FillRect(NewRect); end; end; Move(GlassWorkSheet,OldGlassWorkSheet,SizeOf(OldGlassWorkSheet)); You can get the whole tetris-project under: http://max.kleiner.com/download/clxtetris.zip Migration ------------------------------- Migration from VCL to CLX can be easy even if you have graphic-routines like in a game. But CLX/Qt don't prevent you from differentiate between Linux and Win. Let's have a last look at the differences: CLX uses SysUtils, Classes, QGraphics, QDialogs, QComCtrls, QTypes, QExtCtrls, QButtons, QControls, QForms, QStdCtrls; VCL uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, Buttons, ComCtrls; CLX: uses tetris2, Qt, Types; VCL: uses Tetris2; CLX only: tetro1.BorderStyle:= fbsDialog; CLX on Linux: case Key of Key_Up: RotateFigure; Key_Down, Key_Space: begin //VK_SPACE in win CLX on Win: case Key of VK_UP: RotateFigure; VK_DOWN, VK_SPACE: begin So here we do have also a difference in between CLX, cause there is a dependency on the operation system in the constant table from CLib, the workaraound is easy: {$IFDEF LINUX} Key_Up: RotateFigure; ... {$ENDIF} {$IFDEF MSWINDOWS} VK_UP: RotateFigure; ... At the end some steps migrating existing Delphi/VCL apps to CLX - Transfer or copy files to Linux - Fix "{$R *.dfm}" references to xfm (keep separate) - Rename *.dfm to *.xfm (names are CASE sensitiv!) - Resolve different units, e.g. Forms.pas to QForms.pas - Open project in Kylix and res file will be recreated if not copied - Try to build project to get new dcu's - Resolve any errors, diff. units or types or win32 API calls It's an open secret, the future for CLX at the moment is unsecure. Maybe it's a process. On the server-side Linux is definitly strong. On the client, it still may take some time. But why wait for the Desktop? Delphi/Kylix is nice for server development, too. Missing apps are still the end-user problem on Linux and that's precisely what Kylix is all about solving. But we developers seem to wait for the market, and the market waits for the developers;). An easier alternative for a TIniFile object ******************************************************************************** Search Keys: delphi delphi3000 article borland vcl code-snippet ini stringlist configuration tstringlist Question/Problem/Abstract: There is an alternative for the usage of the TInifile object. Any TStringList has a LoadFrom- and SaveToFile method and using the values property, we can extract and change item values from them. Answer: INI was the old and "new" way to store settings (outside the registry), cause not everything is worth XML parsing or the complicated registry. Delphi provides us with a TIniFile object, but for simple cases (read from CD-ROM, Games, Demos, platform independence) we can use a INI-like file with the help of a TStringList. It's a simple way to read csv-files and other files into your program or system. Developping for a crossplatform, TStringList do show the same behaviour in CLX, TIniFile differs in the way from inheritance: Win: TCustomIniFile <--- TIniFile Linux: TMemIniFile <--- TIniFile So on Linux the whole ini-file is persistent in a RAM. To place information in a global location, you could store the configuration file with file permissions and access rights in the root directory. However in Kylix, you need to use TMemIniFile instead of TRegIniFile. Let's go to a solution with a StringList. First you define the configuration file as you wish: DATABASENAME=milo2:D:\franktech\Delphmax\interbase\stormax.gdb USER_NAME=SYSDBA PASSWORD=masterkey PATTERNSIZE=39 OPTIONS=10110011 Second you read the file, store it to a StringList and you get access to each line with the value property: patLst:= TStringlist.create; try fN:= filepath+ 'stormax.dat'; if fileexists(fN) then begin patLst.LoadFromFile(fN); ibase.DatabaseName:= patLst.Values['DATABASENAME']; ibase.Params.Values['USER_NAME']:= patLst.values['USER_NAME']; ibase.Params.Values['PASSWORD']:= patLst.values['PASSWORD']; patternsk:= patLst.values['PATTERNSIZE']; end else begin patLst.values['DATABASENAME']:= 'LOCALHOST:C:\example.gdb'; patLst.values['USER_NAME']:= 'SYSDBA'; patLst.values['PASSWORD']:= 'masterkey'; patLst.values['PATTERNSIZE']:= '39'; patLst.SaveToFile(fN); end; ibase.open; ..... The Name index is case-insensitive. The Name that identifies the string is to the left of the equal sign (=), and the current Value of the Name identifier is on the right side. There should be no spaces present before or after the equal sign. If the list does not contain any strings of the proper Name=Value form, or if none of those strings matches the Name index, Values returns an empty string. Caveat: the values property does not support sections, this may cause problems with duplicate item names but we can say: as smart as possible, all the best in 2004. How to get data from InterBase via WebServices ******************************************************************************** Using complex types in invokable (SOAP) interfaces Search Keys: delphi delphi3000 article borland vcl code-snippet soap webservices webservice tremote dbexpress Company: kleiner kommunikation Reference: www.borland.com Question/Problem/Abstract: This DBServer with a Server and Client part demonstrates how to use WebServices to create your own Database Server to retrieve or update Data and Types. Answer: First we have to define a project (CGI or shared object) with an output to the apache directory in my example: output directory: /usr/local/httpd/cgi-bin/ The goal is to start a client which gets InterBase data over the web through SOAP, CLX and dbExpress: http://localhost:80/cgi-bin/soapdbserver.cgi/soap The 2 example are more or less available under /opt/kylix2/demos/webservices/dbsamples/server or with delphi6 in demos/webservices/dbserver The server directory project, soapdbserversample.dpr, should be built first, then the client directory project. If you can run the DbxExplorer example in demos/db, and explore the IBConnection files, you are configured to access InterBase data. Tip: Just for testing (either in a production or testing environment),you can define and compile a CGI program and later on uncomment the // in the following project to get an real Apache DSO module: //library soapdbserversample; program soapdbserver; {$APPTYPE CONSOLE} uses WebBroker, CGIApp, //HTTPD, //ApacheApp, SOAPDbSampleUnit in 'SOAPDbSampleUnit.pas' {WebModule1: TWebModule}; {$E cgi} //exports //apache_module name 'soapdbserversample'; begin //ContentType := 'soapdbserversample-handler'; //ModuleName := 'soapdbserversample_module'; application.Initialize; application.CreateForm(TWebModule1, WebModule1); application.Run; end. The SoapDBServerUnit will do the following jobs and steps: 1. Defines IWebServicesDataSet interface derived from IInvokable. It has two methods, RetrieveDataSet and UpdateDataset. The Interface is registered in the initialization section (RegisterInterface). 2. Defines and Implements TWebServicesDataSet, which is derived from TInvokableClass and IWebServicesDataSet. Is registered in the initialization section (RegisterInvokableClass). 3. Defines and implements a number of Classes (TIndexDesc, TColDesc, TFieldValue and TSoapDataPacket) which are derived from TRemotable. These objects are registered in the initialization section (RegisterXSClass). 4. Defines arrays of the objects in 3 and registers these arrays in the initialization section (RegisterXSInfo). 5. Define and implements a set of functions and procedures to convert TSoapDataPacket to DataSet and vice versa. 1. From Face to Face with an Interface ----------------------------------------------------------- The invocation registry (InvRegistry) knows how to create instances of TInvokableClass and (because it has a virtual constructor) its descendants. This allows the registry to supply an invoker in a Web Service application with an instance of the invokable class that can handle an incoming request. initialization InvRegistry.RegisterInterface(TypeInfo(IWebServicesDataSet), 'urn:SOAPDBServerUnit-IWebServicesDataSet',''); IWebServicesDataSet = interface(IInvokable) ['{38ABE9A2-A6A8-D511-87C6-00C04FA06B45}'] procedure RetrieveDataSet(const SQL: String; var DataSet: TSoapDataPacket; var UpdateInfo: TUpdateInfo); stdcall; function UpdateDataSet(const UpdatePacket: TSoapDataPacket; var UpdateInfo: TUpdateInfo; out UpdateErrors: TDBErrorArray):Integer; stdcall; end; 2. Implement a bit ----------------------------------------------------------- In the implementation section of the server-unit that declares this class, we fill in the RetrieveDataSet and UpdateDataSet methods later. TWebServicesDataSet = class(TInvokableClass, IWebServicesDataSet) public procedure RetrieveDataSet(SQL: string; var DataSet: TSoapDataPacket; var UpdateInfo: TUpdateInfo); stdcall; function UpdateDataSet(UpdatePacket: TSoapDataPacket; var UpdateInfo: TUpdateInfo; var UpdateErrors: TDBErrorArray): Integer; stdcall; end; 3. Now let's have a look at the corresponding classes ----------------------------------------------------------- like TSoapDataPacket = class(TRemotable), which is the important one and descends from TRemotable. TRemotable is the base class for classes that can be passed as parameters or return values in a Web Service application. Tip: Both the client app and server app must register a TRemotable descendant with the remotable class registry before they can use it. The caller of an invokable interface (client) is responsible for creating any TRemotable instances that it passes as input parameters, and for freeing any TRemotable instances it creates or that are returned as output parameters or method results. Use TRemotable as a base class when defining a class to represent a complex data type on an invokable interface. For example, in the case where you would ordinarily pass a record as a parameter, you would instead define a TRemotable descendant where every member of the record is a published property on your new class. In the initialization section of the unit that defines the TRemotable descendant, you must register this class with the remotable type registry and don't forget adding the InvokeRegistry unit to the uses clause. This unit declares two global functions, RemClassRegistry and RemTypeRegistry, which return the remotable type registry. Unit Structure and Registration -------------------------------- It is an idea to implement and register TRemotable descendants in a separate unit from the rest of your server application, including from the units that declare and register invokable interfaces. In this way, you can use the unit that defines your type in both the client and server, and you can use the type for more than one interface. For example, the following line comes from the IWebServiceUnit4. It registers TSoapDataPacket, a TRemotable descendant that represents DataPackets values: Unit IWebServiceUnit4; implementation initialization RemClassRegistry.RegisterXSClass(TSoapDataPacket,'urn:ServerDBObjects','TSoapDataPacket',''); ...... The first parameter is the name of the TRemotable descendant. The second is a uniform resource identifier (URI) that uniquely identifies the namespace of the new class. If you supply an empty string, the registry can generate a URI for you. TRemotable is compiled with runtime type information (RTTI) and has a virtual constructor that the remotable class registry recognizes and uses to supply class instances. 4. Define the arrays of objects ----------------------------------------------------------- We have to decompose a complex structrue in its elements so the class TSoapDataPacket has a member of TRowArray which is an array of TSoapRow with the Fields inside: TRowArray = array of TSoapRow; TSoapRow = class(TRemotable) private FRowID: Integer; FFieldValueArray: TFieldValueArray; FUpdateType: TUpdateType; published property RowID: Integer read FRowID write FRowID; property FieldValueArray: TFieldValueArray read FFieldValueArray write FFieldValueArray; property UpdateType: TUpdateType read FUpdateType write FUpdateType; end; As you see, we must map these complex types to a class like TSoapRow that includes runtime type information (RTTI, in published), which the invoker can use to convert between data in the SOAP stream and type values. (It can also handle dynamic arrays, as long as they are registered with the remotable type registry). If you are using dynamic arrays, enum types, or booleans for parameters, you don't need to create a remotable class to represent them, but you do have to register them with the remotable type registry. Thus, for example, if your interface uses the type mentioned before such as: type TRowArray = array of TSoapRow; then you must add the following registration to the initialization section of the unit where you declare this dynamic array: RemClassRegistry.RegisterXSInfo(TypeInfo(TRowArray),'urn:ServerDBObjects','TRowArray',''); RemClassRegistry.RegisterXSInfo(TypeInfo(TUpdateType),'urn:ServerDBObjects','TUpdateType',''); RemClassRegistry.RegisterXSInfo(TypeInfo(TColDescArray),'urn:ServerDBObjects','TColDescArray',''); Now here are the two important classes at last: TSoapDataPacket = class(TRemotable) private FColDescArray: TColDescArray; FIndexDescArray: TIndexDescArray; FRowArray: TRowArray; FTableName: string; public function UpdateRow(Row: TSoapRow; var UpdateInfo: TUpdateInfo; var UpdateErrors: TDBErrorArray): Integer; virtual; published property ColDescArray: TColDescArray read FColDescArray write FColDescArray; property IndexDescArray: TIndexDescArray read FIndexDescArray write FIndexDescArray; property RowArray: TRowArray read FRowArray write FRowArray; property TableName: string read FTableName write FTableName; end; TPooledData = class private FInUse: Boolean; FLock: TRTLCriticalSection; FIndex: Integer; FThreadID: Cardinal; procedure ServerSetup; public SQLConnection: TSQLConnection; SQLDataSet: TSQLDataSet; procedure Lock; procedure UnLock; constructor Create(AIndex: Integer); destructor Destroy; override; end; 5. Implement the client/server methods ----------------------------------------------------------- Next I will show from a client view how the methods are implemented and in which sequence they are called. The client creates a TLinkedRIO object, and uses it (cast as an IWebServicesDataSet) to call RetrieveDataSet and get the SoapDataPacket. procedure TClientForm.FormCreate(Sender: TObject); var SURL: string; begin SURL:= InputBox('Webservices', 'Input URL:', SURL); HTTPRIO1.URL:= SURL; WebServicesDS:= HTTPRio1 As IWebServicesDataSet; FUpdateInfo:= TUpdateInfo.Create; FUpdateInfo.UseIndexMetadata := True; SetLength(FUpdateErrors, 0); end; When a Button or event is hit, it calls the IWebServicesDataSet.RetrieveDataSet by calling the interface method: event() ---> if Assigned(SoapDataPacket) then ClearPacket(SoapDataPacket); WebServicesDS.RetrieveDataSet(Edit1.Text, SoapDataPacket, FUpdateInfo); The client uses utility functions to convert SoapDataPacket (passed as parameter) to DataSet Data and sets this to a navigationale ClientDataSet. DataSet:= TSoapDataPacket.Create; DataSetFromRowArray(ClientDataSet1, SoapDataPacket.RowArray); Soap Server Storm -------------------------------- On the server side the important method goes like this: procedure TWebServicesDataSet.RetrieveDataSet(SQL: string; var DataSet: TSoapDataPacket; var UpdateInfo: TUpdateInfo); stdcall; var AData: TPooledData; begin try AData:= PooledData.GetAvailableConnection; try DataSet:= NIL; try AData.SqlDataSet.CommandText:= SQL; AData.SqlDataSet.Open; DataSet:= TSoapDataPacket.Create; DataSet.TableName:= GetTableNameFromSQL(SQL); The question arises, how the server know which database connection is valid. Before we pass a SQL statement, the method getAvailableConnection calls for each connection ServerSetup so the magic of this method is responsible for the configuration of dbExpress: function TGetPooledData.GetAvailableConnection: TPooledData; ...... if Result = NIL then begin SetLength(FPooledData, Length(FPooledData) + 1); FPooledData[Length(FPooledData)-1]:= TPooledData.Create(Length(FPooledData) -1); FPooledData[Length(FPooledData)-1].ServerSetup; ...... procedure TPooledData.ServerSetup; begin SQLConnection:= TSQLConnection.Create(Nil); with SQLConnection do begin DriverName:= 'INTERBASE'; VendorLib:= 'libgds.so'; GetDriverFunc:= 'getSQLDriverINTERBASE'; LibraryName:= 'libsqlib.so.1.0'; ConnectionName:= 'IB_WebBank'; LoadParamsFromIniFile('/usr/local/httpd/cgi-bin/dbxconnections'); LoginPrompt:= False; end; SQLDataSet:= TSQLDataSet.Create(Nil); TSQLDataSet(SQLDataSet).SQLConnection:= SQLConnection; end; One powerful feature of WebBroker, WebSnap or a WebService with dbExpres in CLX is that they offer several different target server types (CGI, DSO, NSAPI etc.) or databases (InterBase, DB2, Oracle etc.). Also Kylix allows you to easily convert from one target type to another. See you in the book "Patterns konkret" ;) What's the ECO framework? ******************************************************************************** Search Keys: delphi delphi3000 article borland vcl code-snippet ECO Model UML Bold MDA Persistence Objectrelational Times Scored: Question/Problem/Abstract: ECO and Bold share the same idea to provide executable UML, in bref what lies behind ECO for Delphi.NET ? Answer: ECO is a cutting edge technology from Borland ;) The Version Delphi 8 for .NET contains support for ECO - the Enterprise Core Objects. ECO is only available in the architect edition or the personal /trial edition. Maybe you know Bold. Borland made in 2003 the aquisition of boldsoft sweden and the bold-team too, so the essence and different layers of Bold you may also find in ECO. But Bold is dependent on special BoldControls where ECO is independent. Why: ECO needs to have the model compiled so the GUI designer can display the model information. The runtime system of ECO relies on the interrogation of the classes using .Net Reflection. For Reflection to work you must compile the application! Bold for Delphi 6 or 7 and ECO for Delphi 8 applies Object Oriented flexibility and design to a persistence layer of the application making design, maintainability and reuse easier. It is worth spending time on this upfront because this is a huge part of the productivity enhancement you will experience when using Bold /ECO for Delphi architecture. UML is critical for the use of ECO. The Delphi product line includes its own UML modeling tool, either ModelMaker or Together. Learning UML is more or less easy and applying the full range of UML modeling techniques (especially class- and sequence diagram) is a rewarding process. How it works? ------------------------------------------------ ECO forces developers to work model centric (MDA) where most of the nowadays object relational mapping frameworks lean against data centric development. The question is if it is at its best by first designing your data model in conjunction with your use case scenario’s and then map against classes or first design the class model and the map against a persistent data model or an XML file set. ECO works the second way. Nary a company whishes the "rip-and-replace" mantra because it is costly, instead opting to rework, rebuild code and build it out to scale for necessary IT projects. ECO like Bold is a persistence engine layer with code generator using a model-driven architecture in class models and provides a run-time OCL (Object Constraint Language) evaluator/query language and a complete run-time UML meta-model from the help of the ECO Space. What's the ECO Space? ------------------------------------------------ Fromm the Borland Delphi help file: "An ECO Space is a container of objects. At runtime, the ECO Space contains actual instances of the classes in your model. It is helpful to think of the ECO Space as an actual instance of a model, much like an object is an instance of a class. The objects contained in the ECO Space retain the domain properties (attributes and operations) and relationships defined in the model. As a container of objects, an ECO Space is both a cache, and a transactional context. Within the ECO Space, another component called a persistence mapper is used as the conduit between the persistence layer (either an RDBMS or XML file), and the instances of the classes defined in the model. When you configure an ECO Space using the IDE, you will select those UML packages in your model that you wish to persist in the ECO Space." A caveat: In reality this transformation results in an implementation that loses or can lose the intent of the original class model and may even drop off important business rules if careful documentation processes aren’t followed, cause the direction is only forward. Indeed even if these rules are documented it is reliant on them being understood and implemented by the application programmer. As with all documentation it must also be manually maintained during the life cycle of the database. What's OCL? ------------------------------------------------ OCL is a formal language for adding information to UML models. That information can be divided broadly into two categories: - constraints and queries. The query side of OCL is often a surprise to developers learning OCL. In fact OCL can express any query that can be expressed in standard SQL. Given that flexibility, OCL can provide a variety of additions to UML class diagrams and other diagrams in a model. OCL can be used to describe valid values for properties, pre and post conditions for operations, calculated property values and object queries and the important thing is, OCL doesn't changes the data. Maybe you've heard the Design by Contract principle which also OCL and his constraints or condition stands for. OCL like Pascal is a strongly typed language. ECO in Delphi and Eclipse Modeling Framework (EMF, Java) mappings are provided as examples of mapping OCL to a purpose-built model implementation frameworks. Starting with ECO ------------------------------------------------- 1. There is a great starter from Bob, how we can use the Architect edition Delphi 8 for .NET, and specifically the functionality found in ECO to design an Object Model (EcoSpace) which is made persistent inside a database, such as SQL Server or InterBase in this case at http://www.ebob42.com/sdc/2004/ECO.htm 2. Delphi.NET provides in Demos/ECO two examples to built: Before running an example, take a look at the UML class diagram by selecting View | Model View in the IDE. Then expand the CoreClasses node, representing a UML Package, to expose the included classes and associations (if any). Double-click the diagram node (also named CoreClasses) within the package, to open the diagram designer. Both examples uses an XML file for persistence. http://www.borland.com/delphi_net/architect/eco/tutorial/ Those are the steps: 1. Start Delphi 2005, and select File | New - Other which will show the Delphi 2005 Object Repository. 2. Define the model. For this, we need to click on the Model View tab of the Project Manager. We open the treeview with the model nodes, and select the CoreClasses node. Expand this node, and select the CoreClasses leaf node inside. This is the node that we can use to draw the UML diagram (classes, packages) for our ECO model. 3. Build the form with expression handles. This is the ReferenceHandle between the Form and the EcoSpace (and all objects inside it) – the connection between the delphi.net form and the ECO objects and lists. 4. Set the persistence mapper component. A handle can be used to evaluate an OCL expression, returning all instances of an ECO class which came from a XML file for example: Self.PersistenceMapperELocoXml1.FileName:= '\locomotivestore2.xml'; 5. Store the data just smart and easy: procedure TWinForm.btnstore_Click(sender: System.Object; e: System.EventArgs); begin ecoSpace.UpdateDatabase end; Abstract: ------------------------------------------------- ECO is a purpose-built model implementation framework which is based on 3 different toolsets: - UML (use case, class- sequence diagram etc.) - OCL to set the business rules and "protecting" your code - Persistence Mapping (object relational) to bridge the gap between classes and relational databases Short view of ECO generated Code CoreClassesUnit.pas This file contains an empty "UML package". This is where the classes for our ECO application will be created by default. It is possible to create multiple packages. ------------------------------------------------- unit CoreClassesUnit; interface uses System.ComponentModel, System.Collections, Borland.Eco.Services, Borland.Eco.ObjectRepresentation, Borland.Eco.ObjectImplementation, Borland.Eco.UmlRt, Borland.Eco.UmlCodeAttributes; type Person = class; Building = class; ResidentialBuilding = class; IBuildingList = interface; [UmlElement('Package')] [UmlMetaAttribute('ownedElement', TypeOf(Person))] [UmlMetaAttribute('ownedElement', TypeOf(Building))] [UmlMetaAttribute('ownedElement', TypeOf(ResidentialBuilding))] CoreClasses = class public type [UmlElement('Association')] Ownership = class end; type [UmlElement('Association')] Residents = class end; end; -------------------------------------------------- procedure TBldOwnEcoSpace.InitializeComponent; begin Self.PersistenceMapperXml := Borland.Eco.Persistence.PersistenceMapperXml.Create; // // PersistenceMapperXml // Self.PersistenceMapperXml.CacheData := False; Self.PersistenceMapperXml.FileName := 'EcoBuilding.Data'; // // TBldOwnEcoSpace // Self.PersistenceMapper := Self.PersistenceMapperXml; end; ...see you at EKON8 in Frankfurt ;) What's a buffer overflow and how to avoid it in Delphi? ******************************************************************************** Is Delphi really safer and what's the danger? Search Keys: delphi delphi3000 article borland vcl code-snippet "buffer overflow" security getmem stack hacker "delphi string" Times Scored: Question/Problem/Abstract: Buffer overflow problems always have been associated with security wholes. Lots of security problems have occurred due to buffer overflow. Answer: This article tries to explain what a buffer overflow is and what countermeasures (or counterattacks;) can be taken to avoid it. A buffer is a contiguous allocated block of memory, such as an array or a pointer in Pascal. In C and C++, there are no automatic bounds checking on the buffer (responsability by the programmer), which means a hacker can spoil the buffer. for example: int main () { int buffer[5]; buffer[7] = 10; } In Delphi I mean as far as Delphi uses Pascal style strings, programs are much safer than those made in C/C++. Strong types with internal checks of operations and ranges by the compiler at design-time can prevent an overflow. Delphi can use Pascal strings as well as generic windows strings (PChar). When interfacing with Win API there is no other option except using Pchar. This mean that potentially Delphi is a bit safer, because the potential problems can be isolated - more experienced developers are allowed to work with PChar, while less experienced allowed to work only with encapsulated functionality using Pascal String. Internal represantation of a Delphi String: ------------------------I-------------I---------------I--------------I reference counter 32bit length(32bit) payload(nbyte) unused space ------------------------I-------------I---------------I--------------I I--->string variable The Delphi compiler hiddens the fact that the string variable is a heap pointer to the above structure but setting the memory in advance is advisable: path: string; setLength(path, 1025); setLength(path, getSystemDirectory(pChar(path),length(path)-1)); Is it really safer? Ok. we can say it's safer against character-array buffer overflows or Delphi isn't used enough to make attacking it interesting. But most attacks succeded on the STACK but not on a HEAP structure, cause overriding a heap is more difficult . This does not imply that the language is overall safer - there could be very well significant damage elsewhere, and char-based overflows are only one attack method. For example you declare a PChar buffer, you have the possibility to check with GetMem() the capacitiy otherwise you get an EOutOfMemory-exception: Buffer: PChar; //not a buffer before you use getmem Size:= FileSize(F); GetMem(Buffer, Size); //allocates n-Bytes on the heap BlockRead(F, Buffer^, Size); What's the danger? ------------------------------------------------------------ Most of "unsecure" programs are valid, and "almost" every compiler can compile it without any errors. However, the attacked program attempts to write beyond the allocated buffer memory, which might result in unexpected behavior. Because malicious code (assembly instructions to spawn a root shell) is an input argument to the program, it resides in the stack and not in the code segment. Therefore, the simplest solution is to invalidate the stack to execute any instructions. Execution is done by overwriting a function's return address (in the stack), an intelligent hacker might want to spawn a shell (with root permissions) by jumping the execution path to such code. A hacker or attacker places the code they are trying to execute in the buffer's overflowing area. With the manipulated return address it points back to the buffer and executes the intended code. That's it. Avoid it ------------------------------------------------------------ So Delphi is mostly secure against buffer overflows (Imho), cause of compiler and conceptual basics. Hence, the best way to deal with buffer overflow problems is to not allow them to occur in the first place. Developers should be educated about how to minimize the use of these vulnerable functions. It is advisable to make a buffer too large in the first place or implement a circular buffer (that you may receive data quicker than you process it, resulting in a buffer overflow): var Source, Dest: PChar; CopyLen: Integer; begin Source:= aSource; Dest:= @FData[FBufferEnd]; if BufferWriteSize < Count then raise EFIFOStream.Create('Buffer over-run.'); Or you check with GetMem or a simple count-logic possible overflows: GetMem(Buffer, BufferSize); Ptr:= Buffer; Count:= 0; for I:= 0 to ListBox.Items.Count - 1 do begin Line:= ListBox.Items.strings[I]; // Check buffer overflow Count:= Count + Length(Line) + 3; if Count = BufferSize then Break; //the function aborts immediately. Or use try finally/raise to check buffer with a DefSize and free it: begin if FBufFixed then BSize := FBufSize else BSize:= DefSize; GetMem(Buffer, BSize); try BufEnd:= Buffer; Count:= Stream.Read(Buffer[0], BSize); BufEnd:= BufEnd + Count; if Count < BSize then BufEnd[0]:= #0 else begin raise EStreamError.Create(LoadStr(SLineTooLong)); end; SetText(Buffer); finally FreeMem(Buffer, BSize); end; end; Apart from education or experience, modern compiler like Delphi change the way a program is compiled, allowing bounds checking to go into compiled code automatically, without changing the source code. These compilers generate the code with built-in safeguards that try to prevent the use of illegal addresses. Any code that tries to access an illegal address is not allowed to execute. Or a tool does it by protecting the return address on the stack from being altered. It places a canary word next to the return address whenever a function is called. If the canary word has been altered when the function returns, then some attacks or attempt has been made on the overflow buffers. Furthermore, it may affect the application's performance to a great extent. In some case, executable size and execution time may increase a certain way. That's the prize for more code security. Max Kleiner BLOBs in InterBase ******************************************************************************** How do we design and store BLOBs Search Keys: delphi delphi3000 article borland vcl code-snippet blob interbase memory Times Scored: Question/Problem/Abstract: Befor e we can manipulate BLOBs in a database some design should be made before Answer: You can use TDBMemo or TDBImage controls to display large text fields or text data contained in BLOB fields. So a TBlobField is the direct ancestor of TMemoField and TGraphicField in a dataset that holds a reference to a BLOB. But how do we use a stream returned by a dataset's CreateBlobStream method to read or write the data managed by a BLOB field: The term in // is an alternative to save a BLOB with a dataset. First an example from a table ibTablemax in a DataModule called cachedata: procedure TCacheDemoForm.Button1Click(Sender: TObject); var blobdataset: TIBClientDataset; var blob: TStream; fs: TFileStream; begin //blobdataset:= cachedata.ibclientds1 as TIBClientDataSet; //SetControlStates(true); //cachedata.IBClientDS1.FileName:= ''; //cachedata.ibdatabase1.sqldialect:= 2; //cachedata.ibclientds1.close; //cachedata.IBClientDS1.CommandText:= 'select * from MBLOB where ID = 5'; //cachedata.ibclientds1.Open; //blobdataset:= cachedata.IBclientds1; //cachedata.letDocuSaveasBlop(blobdataset); //try it with tibtable cachedata.IBDatabase1.SQLDialect:= 1; with cachedata.IBtablemax do begin active:= true; if Locate('ID','5',[loCaseInsensitive]) then begin Edit; //IBtablemaxBLOBNAME: TBlobField; blob:= createBlobStream(cachedata.ibtablemax. FieldByName('Blobname'),bmwrite); try blob.Seek(0, soFromBeginning); fs:= TFileStream.create('c:\milo_test\nmlogo.bmp', fmopenRead or fmsharedenyWrite); try blob.copyFrom(fs, fs.size); Post; finally fs.free; end finally blob.free; cachedata.IBDatabase1.SQLDialect:= 2; end; end; // if end; Here the example with a dataset for more flexibility: procedure TCacheData.letDocuSaveasBlop(Vdataset: TIBClientDataSet); var blob: TStream; fs: TFileStream; begin vDataset.Edit; blob:= vDataSet.createBlobStream(vDataset. FieldByName('Blobname'),bmwrite); try blob.Seek(0, soFromBeginning); fs:= TFileStream.create('c:\milo_test\grid_del5.doc', fmopenRead or fmsharedenyWrite); try blob.copyFrom(fs, fs.size); vDataset.Post; finally fs.free; end finally blob.free; end; end; The design in InterBase DDL SQL goes like this: /*OPEN DATABASE "MILO:D:/Program Files/Borland/InterBase/seekmachine/suchdb8.gdb" USER "SYSDBA" PASSWORD "masterkey" */ SET AUTODDL ON; /* Domain definitions */ CREATE DOMAIN "DOM_DESCRIPTION" AS VARCHAR(1024); CREATE DOMAIN "DOM_ID" AS INTEGER NOT NULL; /* Table: MBLOB, Owner: SYSDBA */ CREATE TABLE "MBLOB" ( "ID" "DOM_ID", "BLOBNAME" BLOB SUB_TYPE 0 SEGMENT SIZE 80, "DESCRIPTION" "DOM_DESCRIPTION", CONSTRAINT "PK_MBLOB" PRIMARY KEY ("ID") ); CREATE GENERATOR BlobID_GEN; /*COMMIT WORK;*/ If you want to test the database by saving a BLOB null-record without Delphi, just use an INSERT from an SQL Monitor: SET AUTODDL ON; SET TERM ^ ; INSERT INTO MBLOB VALUES (GEN_ID(blobid_GEN, 1), null, 'shows next example new'); /*COMMIT WORK;*/ Last there it is possible to loads BLOB data from a stream into a field of a ClientDataSet: procedure LoadFromStream(stream: TStream); var ms: TMemoryStream; begin if not (cDataSet1.state in [dsInsert, dsEdit]) then cDataSet.insert; ms:= TMemoryStream.create(); try image1.picture.Bitmap.SaveToStream(ms); cDataSet.LoadFromStream(ms); finally ms.free; end; cDataSet.post; end; But the stream parameter is typically not a BLOB stream like the dataset's CreateBlobStream() which provide a completely separate mechanism for streaming data into a BLOB field. Handle lots of classes at runtime ******************************************************************************** How to implement a RegisterClass function Search Keys: delphi delphi3000 article borland vcl code-snippet registerclass classreference classfactory Question/Problem/Abstract: Classes which can't support TPersistent or TPersistentClass had to implement another technique to manage the creation of obejcts at runtime Answer: If a class inherits from TPersistent we can use RegisterClass in several units so we can use the function GetClass to convert a class name to a class instance: TFinanceClass = Class of TFinance; var refClass: TFinanceClass; refClass:= TFinanceClass(getClass(values['RegClass'])); dynObj:= refClass.Create; dynObj.getCharge(9.2); RegisterClass(TFinance); If you can't inherit from TPersistent we can use a "Name=Value" pair of objects in a stringlist and loaded when a module is created or by user events. So you can determine which object should be created. function initObjinList; var //refClass: TFinanceClass; dynObj: TFinance; myClsname: string[240]; begin with TStringList.Create do begin clear; values['RegClass']:= 'TRegularCharge2'; values['PrefClass']:= 'TPreferredCharge2'; values['TrialClass']:= 'TTrialCharge2'; myClsname:= 'TrialClass'; if indexofname(myClsname) > -1 then begin {refClass:= TFinanceClass(getClass(values['RegClass'])); dynObj:= refClass.Create; dynObj.getCharge(9.2); } case IndexOfname(myClsname) of 0: dynObj:= TRegularCharge2.create; 1: dynObj:= TPreferredCharge2.create; 2: dynObj:= TTrialCharge2.create; end; dynObj.getcharge(9.1); dynObj.Free; result:= true; end; Free; end; end; The value is the class type and the name simplifies the maintainability of the classes. syntax: mystringlist.values[name]:= value //write myname:= mystringlist.values[name] //read I made only one function to show the simplicity but registering classes in a StringList and choosen from a case of structure should be separated in differnt functions. All the classes inherit from TFinance and in comparison to RegisterClass we don't use a ClassReference. Descendants override the public or protected getCharge() methods to perform their actions. TFinance = class public function getCharge(const Balance: double): double; virtual; abstract; end; TRegularCharge2 = class(TFinance) public function getCharge(const Balance: double): double; override; end; TPreferredCharge2 = class(TFinance) public function getCharge(const Balance: double): double; override; end; TTrialCharge2 = class(TFinance) public function getCharge(const Balance: double): double; override; end; 10 tips concerning speed and space ******************************************************************************** Search Keys: delphi delphi3000 article borland vcl code-snippet optimization guideline analyze Times Scored: Company: kleiner kommunikation Reference: http://www.peganza.com Component Download: http://www.softwareschule.ch/download/pascal_analyzer.pdf Question/Problem/Abstract: A short guideline to check your knowledge in the heat of the night fight with your code Answer: Some tips and hints to optimize your code. Either code can be improved /extended or code can be reduced. Removing superfluous directives or data will make the code smaller and easier to follow. 1. one statement 2. with speed 3. assert call 4. class reference 5. try finally 6. naming convention 7. $IF-directive 8. var parameters 9. threadvar 10. uses list ------------------------------------- 1. An easy one states that only one statement should exists for every source line, like next begin .. I := 5; CallProc(I); //bad //better begin .. I:= 5; CallProc(I); .. end; 2. Use of the With can increase considerably the speed in your programs since the sentences are executed quickly, enclosed between the begin and the end of the With; the compiler optimizes the chunk of code. But there can be a trap like an ambiguous references in with -blocks reports locations where a reference to a record field or class member exists in a with -statement, and where this identifier could be mixed up with another identifier in the same scope. var title: string; type TMyRec = record title: string; end; var Rec: TMyRec; begin .. with Rec do begin .. title := ´Hello'; end; end; 3. Use assert calls as much, but don't forget the $C - setting is active or not. This means that identifiers used in the Assert procedure call, will be registered in your tests, but when compiled by Delphi, this code line will be (should) stripped out if $C- is defined. procedure MyProc(P: pointer); begin Assert(P <> nil); 4. Use a Class Reference to define a actual class is determined at runtime. type TMyClassRef = class of TMyClass; TMyClass = class .. procedure AirMax; virtual; end; TMyDerivedClass = class(TMyClass) .. procedure AirMax; override; end; .. procedure Proc(c: TmyClassRef); begin c:= TMyClassRef.Create; end; 5. Find locations in your code with possible memory leaks, cause you forget try finally: procedure ListMaxBox; var sL: TStringList; begin sL:= TStringList.Create; .. sL.Free; // missing try-finally block! end; 6. Use a naming convention like TBitBtn;bitn TButton;btn TCheckBox;chk TComboBox;cbo TRadioButton;rb TRadioGroup;rg ......... Ordinary types start with "T" Exception types start with "E" Pointer types tart with "P" Interface types start with "I" Class fields exposed by properties (read/write) start with "F" Method pointers start with "On/Before/After" 7. In Delphi 6, the new $IF-directive was introduced. The $IF-directive is followed by an expression, that evaluates to TRUE or FALSE. Use it to differentiate your code An unnecessary action concerning strings is next, since long strings are automatically initialized as empty strings upon creation. procedure MyMaxBoxProc; var S: string; begin S:= ''; // !! unnecessary 8. Avoid var parameters that are used, but never set never set in the subprogram they belong to. Although this is not an error, it may be an indication that something is wrong with your code. Otherwise, you may omit the var keyword, or change it to a const parameter. Declare with the const directive, resulting in better performance since the compiler can assume that the parameter will not be changed. procedure MyHexMaxProc(var i : integer); //const begin .. if i = 5 then // !! I is not set begin .. end; end; by the way: use not more than 3 parameters in a method, so the compiler can build it with fastcall directives, means registers are used instead of stack! 9. Beware of threadsafe. Reference -counted variables (such as long strings, dynamic arrays, or interfaces) are not thread-safe and should not be declared with threadvar: threadvar S: string; P: pointer; X: IMyInterface; W: TProcedure; 10. Optimize uses list, we know the linker is a smart one, but nevertheless init and finals are executed. Removing unused uses references has multiple benefits: - cleaner code to maintain, no need to bother about code that's not used - code from initialization and finalization sections in unused units is not linked in and run, reducing the size of the EXE - compilation runs smoother and quicker When you drop a VCL component on a Delphi form the Delphi IDE automatically adds the unit or units required by the component to the interface section uses statement. This is done to ensure that the form file (DFM/XFM) can locate the code needed to stream the form and components. Even if you later remove the component, the units are not deleted from the uses statement. ---------------------------- Many of those hints can be detected by a tool, but don't get me wrong, it's not a marketing meaning of myself. I'm talking about Pascal Analyzer and it's great. Pascal Analyzer, or PAL for short, is a utility program that analyzes, documents, debugs, and helps you optimize your source code. Fore more is a testreport (in german) available on http://www.softwareschule.ch/download/pascal_analyzer.pdf May the source be with you. What's OCL? ******************************************************************************** OCL is a formal language for adding information to UML models. That information can be divided broadly into two categories: - constraints and queries. The query side of OCL is often a surprise to developers learning OCL. In fact OCL can express any query that can be expressed in standard SQL. Given that flexibility, OCL can provide a variety of additions to UML class diagrams and other diagrams in a model. OCL can be used to describe valid values for properties, pre and post conditions for operations, calculated property values and object queries and the important thing is, OCL doesn't changes the data. Maybe you've heard the Design by Contract principle which also OCL and his constraints or condition stands for. OCL like Pascal is a strongly typed language. ECO in Delphi and Eclipse Modeling Framework (EMF, Java) mappings are provided as examples of mapping OCL to a purpose-built model implementation frameworks. Universal embedding of files in Delphi units ********************************************************************************************* Component Download: http://www.softwareschule.ch/download/hexer2.zip Question/Problem/Abstract: This article attempts to explain how to include files inside a Delphi unit / application as different kinds of binaries and how to manage them without the resource technology. Answer: The app is called the Hexer works on VCL and CLX. //Update 1, 2005-05-29: Migration to Linux in one pas file //Update 2, 2005-06-05: Call the bytearray from a dll (export) You can put your files into your delphi project at design time and then just compile it to get all into one or more units. First you have to convert the file in a delphi unit by the Hexer. Then you call it from your own app which embedds the unit. You can even store waves or pictures and play it without the need to create a file, play it from memory. if not PlaySound(@UtopiaWinstarten_wav, 0, SND_MEMORY + SND_SYNC) It is possible to embed any kind of file (also executables) in an executable using the Hexer. First example is a picture: procedure TForm1.btnPicloadClick(Sender: TObject); var mybitmap: TBitmap; bitStream: TStream; begin //mybitmap.LoadFromFile('dws_logo.bmp'); for the mortals mybitmap:= TBitmap.Create; bitStream:= TMemoryStream.Create; try bitStream.Writebuffer(dws_logo_bmp, sizeof(dws_logo_bmp)); bitStream.Position:= 0; mybitmap.LoadFromStream(bitStream); if assigned(mybitmap) then begin image1.Picture.assign(mybitmap); image1.update; end; finally bitStream.Free; mybitmap.Free; end; end; Second example is an executable: procedure TForm1.btnApploadClick(Sender: TObject); var mybitmap: TBitmap; bitStream: TMemoryStream; begin mybitmap:= TBitmap.Create; bitStream:= TMemoryStream.Create; try bitStream.Writebuffer(viergewinnt_exe, sizeof(viergewinnt_exe)); bitStream.Position:= 0; bitstream.LoadFromStream(bitstream); bitstream.SaveToFile('vierg.exe'); WinExec(pchar('vierg.exe'){ + ' ' + ParamStr},SW_NORMAL); finally bitStream.Free; mybitmap.Free; end; end; -------------------------------------------------------------------- How does the Hexer works? MainForm2.TForm1.btnConvertClick (97i) MainForm2.TForm1.ConvertToUnit (103i) MainForm2.WriteString (108i) MainForm2.EmbedFile (117i) MainForm2.WriteString (108i) We open with createFile() the unit for write down the file in the unit. The CreateFile function creates or opens objects and returns a handle that can be used to access the object. f_hndoutput:= CreateFile(PChar(ChangeFileExt(edtUnitToCreate.Text, '.pas')), GENERIC_WRITE, 0, NIL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); Then we call EmbedFile which opens the file to be write down in the unit. The WriteFile function writes data to a file and is designed for both synchronous and asynchronous operation. The function starts writing data to the file at the position indicated by the file pointer. BOOL WriteFile( HANDLE hFile, // handle to file to write to LPCVOID lpBuffer, // pointer to data to write to file DWORD nNumberOfBytesToWrite, // number of bytes to write LPDWORD lpNumberOfBytesWritten, // pointer to bytes written LPOVERLAPPED lpOverlapped ); After the write operation has been completed, the file pointer is adjusted by the number of bytes actually written. f_hndopen:= CreateFile(PChar(filename), GENERIC_WRITE + GENERIC_READ, 0, NIL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); Each time the file pointer is adjusted we write a block of abuf: array[0..19] of byte; Result:= ReadFile(f_hndopen, abuf, sizeof(abuf), BytesRead, NIL); if bytesRead > 0 then begin At least our generated unit looks like Unit dws_logo2; interface const dws_logo_bmp: array[0..21717] of byte = ( $42,$4D,$D6,$54,$00,$00,$00,$00,$00,$00,$76,$00,$00,$00,$28,$00,$00,$00,$F0,$00, $00,$00,$B4,$00,$00,$00,$01,$00,$04,$00,$00,$00,$00,$00,$60,$54,$00,$00,$C4,$0E, $00,$00,$C4,$0E,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, .............. ----------------------------------------------------------------- The app der Hexer unit MainForm2; {Analyzed by: PAL - Pascal Analyzer version 2.1.9.1 Parse speed: 186 lines in 0.11 seconds (1691 lines/sec). Main file: D:\FRANKTECH\DELPHMAX\HEXER\MAINFORM2.PAS Compiler: Delphi 7.1, ModelMaker 6.2} interface uses Windows, Classes, Controls, Forms, Dialogs, ExtCtrls, StdCtrls; type TForm1 = class(TForm) btnConvert: TButton; lbFilesToConvert: TListBox; btnAddFiles: TButton; btnDeleteFiles: TButton; edtUnitToCreate: TEdit; btnBrowse: TButton; Bevel1: TBevel; lblfiles: TLabel; lblunit: TLabel; OpenDialog: TOpenDialog; SaveDialog: TSaveDialog; btnExit: TButton; procedure FormCreate(Sender: TObject); procedure btnAddFilesClick(Sender: TObject); procedure btnBrowseClick(Sender: TObject); procedure lbFilesToConvertClick(Sender: TObject); procedure btnDeleteFilesClick(Sender: TObject); procedure btnExitClick(Sender: TObject); procedure btnConvertClick(Sender: TObject); private { Private declarations } procedure CheckButtonStatus; procedure ConvertToUnit; public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} uses sysutils; const CRLF = #13#10; CRLF2 = #13#10#13#10; STR3L = ' '; procedure TForm1.CheckButtonStatus; begin btnConvert.Enabled:= (edtUnitToCreate.Text <> '') and (lbFilesToConvert.Items.Count > 0); btnDeleteFiles.Enabled:= lbFilesToConvert.SelCount > 0; end; procedure TForm1.FormCreate(Sender: TObject); begin CheckButtonStatus; end; procedure TForm1.btnAddFilesClick(Sender: TObject); var x : integer; begin if OpenDialog.Execute then if OpenDialog.Files.Count > 0 then for x:= 0 to OpenDialog.Files.Count-1 do if not lbFilesToConvert.Items.IndexOf(OpenDialog.Files[x]) > -1 then lbFilesToConvert.Items.Add(OpenDialog.Files[x]); CheckButtonStatus; end; procedure TForm1.btnBrowseClick(Sender: TObject); begin if SaveDialog.Execute then edtUnitToCreate.Text:= SaveDialog.FileName; CheckButtonStatus; end; procedure TForm1.lbFilesToConvertClick(Sender: TObject); begin CheckButtonStatus; end; procedure TForm1.btnDeleteFilesClick(Sender: TObject); begin lbFilesToConvert.DeleteSelected; CheckButtonStatus; end; procedure TForm1.btnExitClick(Sender: TObject); begin Close; end; procedure TForm1.btnConvertClick(Sender: TObject); begin //procedure could be moved to a logic unit ConvertToUnit; end; procedure TForm1.ConvertToUnit; var f_hndoutput: THandle; cntr: integ er; f_error: boolean; function WriteString(const theString: string) : boolean; var bytesToWrite, bytesWritten: cardinal; begin bytesToWrite:= Length(theString); WriteFile(f_hndoutput, theString[1], bytesToWrite, bytesWritten, NIL); Result:= bytesToWrite = bytesWritten; end; function EmbedFile(const filename: string): boolean; var f_hndopen: THandle; abuf: array[0..19] of byte; bytesRead, x, fSizeHigh, fSizeLow: cardinal; fSize: int64; fExtension: string; begin f_hndopen:= CreateFile(PChar(filename), GENERIC_WRITE + GENERIC_READ,0, NIL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); Result:= f_hndopen <> INVALID_HANDLE_VALUE; if Result then begin fSizeLow:= GetFileSize(f_hndopen, @fSizeHigh); Result:= (fSizeLow <> $ffffffff) or (GetLastError = NO_ERROR); if Result then begin fSize:= int64(fSizeHigh) * $100000000 + fSizeLow; fExtension:= ExtractFileExt(filename); if Length(fExtension) > 0 then Delete(fExtension,1,1); Result:= WriteString(STR3L + ExtractFileName(ChangeFileExt(filename,'_'+fExtension)) + ': array[0..' + IntToStr(fSize-1) + '] of byte = (' + CRLF); if Result then begin repeat //bytesToRead:= sizeof(buffer); Result:= ReadFile(f_hndopen, abuf, sizeof(abuf), bytesRead, NIL); if bytesRead > 0 then begin Result:= Result and WriteString(STR3L); for x:= 0 to bytesRead-1 do begin if x > 0 then Result:= Result and WriteString(','); Result:= Result and WriteString('$'+IntToHex(abuf[x], 2)); end; fSize:= fSize - bytesRead; if fSize > 0 then Result:= Result and WriteString(',' + CRLF); end; until (bytesRead = 0) or not Result; Result:= Result and WriteString(');' + CRLF2); end; end; CloseHandle(f_hndopen); end; //result end; begin f_hndoutput:= CreateFile(PChar(ChangeFileExt(edtUnitToCreate.Text, '.pas')), GENERIC_WRITE, 0, NIL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); f_error:= false; if f_hndoutput <> INVALID_HANDLE_VALUE then begin Screen.Cursor:= crHourGlass; if WriteString('Unit ' + ExtractFileName(ChangeFileExt(edtUnitToCreate.Text, '')) + ';' + CRLF) then begin if WriteString(CRLF + 'interface' + CRLF2) then if WriteString('const ' + CRLF) then begin for cntr:= 0 to lbFilesToConvert.Items.Count-1 do if EmbedFile(lbFilesToConvert.Items[cntr]) then if WriteString(CRLF + 'implementation' + CRLF2) then f_error:= not WriteString('end.'); end; end; Screen.Cursor:= crDefault; CloseHandle(f_hndoutput); end; if f_error then ShowMessage('Some Errors occured'); end; end. ---------------------------------------------------------------------------- Conclusion: You do have 3 possibilites to embed files: 1. article 2606 or 4217 shows the way with resources / resource workshop 2. article 2321 is based on TComponent and TStream with write and read 3. this article is more generic the advantage is transparency at design time and protection at runtime of the embedded files, you can even use binaries which you control with an inline assembler format. The whole project Hexer and loader/player is downloadable. --------------------------------------------------------------------- Update 1 Some points to CLX: Despite the low level stuff of the winapi no great obstacles were found during the migration. QForms, QDialogs, QStdCtrls, QControls, QExtCtrls, Classes; a few functions had to be reselected, for ex: FileWrite writes Count bytes to the file given by Handle from the buffer specified by Buffer. Handle is a file handle returned by the FileOpen or FileCreate method. or another approach to define the filesize has to be considered: {If the file is declared as a file of byte, then the record size defaults to one byte, and FileSize returns the number of bytes in the file.} Update2 Exporting a great const from a DLL First you declare the type of the const and second you define a wrap function around the type: library hexerdll; type Tviergewinnt_exe = array[0..88598] of byte; const viergewinnt_exe: TViergewinnt_exe = ( $4D,$5A,$00,$01,$01,$00,$00,$00,$08,$00,$10,$00,$FF,$FF,$08,$00,$00,$01,$00,$00, ............. function getArrayofByte: Tviergewinnt_exe; begin result:= viergewinnt_exe; end; exports getArrayofByte; The call of the client has the following structure: the important thing is to declare a local var of the const in our case dllexe: Tviergewinnt_exe; unit playmain; // examples to call the embedded file in the unit or the dll ........ type Tviergewinnt_exe = array[0..88598] of byte; function getArrayofByte: Tviergewinnt_exe; external 'hexerdll'; procedure TForm1.btnApploadClick(Sender: TObject); var bitStream: TMemoryStream; dllexe: Tviergewinnt_exe; begin bitStream:= TMemoryStream.Create; //getArrayofByte dllexe:= getArrayofByte; try // import DLL const bitStream.Writebuffer(dllexe, sizeof(dllexe)); bitStream.Position:= 0; bitstream.LoadFromStream(bitstream); bitstream.SaveToFile('viergewinnt.exe'); ........ Build one Resfile from files located in a Directory ********************************************************************************* Component Download: http://www.softwareschule.ch/download/memory_source.zip Question/Problem/Abstract: you may have a picture or sound-collection with n-files (bmp, wav etc.) you want to pack in one resourcefile without manually add them in the resource-workshop. Answer: { We want to get an output like this in a *.res format: BMP1 BITMAP "shiftshaft.bmp" BMP2 BITMAP "max2uml.bmp" BMP3 BITMAP "maxbox3.bmp" ..... 1. We put all the files in a directory 2. We start the scriptResourceFile() procedure gets all the files like *.bmp or *.wav in a *.rc format 3. Activate the resource-compiler to produce the res-file 4. Bind the res-file in an exe or a dll } procedure TStatForm.scriptresourceFile2(restype: string); var f: textfile; ResFile: ShortString; resstr: string; s: array[0..2048] of Char; i, filecount: Byte; myResList: TStringList; begin myresList:= TStringList.Create; filecount:= getfilelist(myResList); if filecount > totalPictures then filecount:= totalPictures; for i:= 0 to filecount - 1 do begin resstr:= Format('%s%d %s %s%s%s', ['bmp', i, restype, '"', myReslist.Strings[i], '"']); StrCat(s, PChar(resstr)); StrCat(s, #13#10); end; ResFile:= 'membmp.rc'; AssignFile(f, ResFile); Rewrite(f); Write(f, s); closefile(f); myResList.Free; compileResfile(ResFile); end; procedure TStatForm.btnGenClick(Sender: TObject); begin scriptResourceFile2('Bitmap'); end; function TStatForm.getFileList(aList: TStringList): Integer; var DOSerr: Integer; fsrch: TsearchRec; begin Result:= 0; doserr:= FindFirst('*.bmp', faAnyFile, fsrch); if (DOSerr = 0) then begin while (DOSerr = 0) do begin aList.Add(fsrch.Name); if (fsrch.attr and faDirectory) = 0 then Inc(Result); DOSerr:= findnext(fsrch); end; findClose(fsrch); end; end; procedure TStatForm.compileResfile(vfile: string); var i, iCE: Integer; begin {$IFDEF MSWINDOWS} iCE:= shellapi.shellExecute(0, nil, PChar('BRCC32.exe'), PChar(vfile), nil, 0); i:= 0; repeat Inc(i); sleep(600); Application.ProcessMessages; until i >= 10; if iCE <= 32 then ShowMessage('compError Nr. ' + IntToStr(iCE)); {$ENDIF} end; 4. Then you link the file in an exe {$IFDEF MEMSOUND} {$R memsound.RES} {$ELSE} {$R mempics.RES} {$ENDIF MEMSOUND} A memory game project with this range of technic is available (108kb): http://www.softwareschule.ch/download/memory_source.zip there is also a report how to build a memory game with resources, but sorry only in german: http://www.softwareschule.ch/download/ressourcen_total.pdf Hotspot function with CLX **************************************************************************************3 creating a generic hotspot component Question/Problem/Abstract: How can we build a transparent control, like a hotspot? It is transparent, has its own hint property and you can even click on it. Answer: To do what you want, just create a descendant of TCustomControl and publish a few protected properties and events. Especially the masked property is CLX specific. To create a transparent control, all you need to do is descend from TCustomControl, publish the Masked property (or hard-code it to True at runtime), and override the Paint() virtual method. Paint() allows you to essentially create a design time frame representing the area you want transparent with an overlayed bitmap or image. Set an image and select a hotspot to trigger another event.. Example: Display a train layout and when you select a part display information about that train. Or you'll have to find some easter eggs in a bunny bitmap ;) You can call the component at run- or at designtime: procedure TThreadSortForm.initHotspot; var myhotspot3: THotspot; begin myhotspot3:= THotspot.Create(owner); with myhotspot3 do begin parent:= self; left:= 46; top:=288; width:= 50; height:= 50; hint:=('this is building STARTRAIN'); masked:= true; OnMouseEnter:= Hotspot1MouseEnter; end; end; The method changeCursor in the event OnMouseEnter() changes the cursor after the constructor, cause the masked property would hide the new cursor at mouse move time! So FCursor is for further use. unit hotspot; // march of 2005 CLX version // cause of masked property no special cursor is available // works best with onmouseEnter ;) interface uses QControls, Classes, QGraphics, SysUtils, QStdCtrls, QTypes, QForms; type THotspot = class(TCustomControl) private FFirstPaint: Boolean; FCursor: TCursor; protected procedure Paint; override; published constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure changeCursor(howlong_msec: integer); property OnClick; property OnMouseDown; property OnMouseUp; property OnMouseMove; property OnMouseEnter; property OnMouseLeave; property Masked; end; procedure Register; implementation procedure THotspot.Paint; begin inherited Paint; if (csDesigning in ComponentState) then if FFirstPaint then begin FFirstPaint:= False; Width:= Width + 1; masked:= false; end else //just the formframe of design time with Canvas do begin Pen.Style:= psDot; Pen.Width:= 1; Pen.Color:= clBlack; Pen.Mode:= pmNotXor; Brush.Style:= bsClear; //brush.Style:= bsSolid; Rectangle(0,0,Width,Height); Rectangle(1,1,Width-1,Height-1); end; end; //old style in windows {procedure THotspot.CreateParams(var Params: TCreateParams); begin inherited CreateParams(Params); Params.ExStyle := Params.ExStyle or ws_ex_Transparent; end;} constructor THotspot.Create(AOwner: TComponent); begin inherited Create(AOwner); Width:= 100; Height:= 100; FFirstPaint:= True; Cursor:= crHandPoint; FCursor:= crDefault; //transparentmode //controlstyle:= controlstyle -[csOpaque]; showhint:= true; masked:= true; end; procedure THotspot.changeCursor(howlong_msec: integer); var save_cursor: TCursor; begin save_cursor:= screen.Cursor; try screen.Cursor:= crHandPoint; //howlong in milliseconds sleep(howlong_msec); finally screen.Cursor:= save_cursor; end; end; destructor THotspot.Destroy; begin inherited Destroy; end; procedure Register; begin RegisterComponents('PascalScript', [THotspot]); end; end. //STARTRAIN // start train // start rain // star train How to realize a ChangeFinder ********************************************************************************************************* last Update: 05/09/2005 Scored: 4 Visits: 713 Uploader: Max Kleiner Company: kleiner kommunikation Reference: N/A Question/Problem/Abstract: Where or when a modification has changed an existing file or a new one, the ChangeFinder can show the files in a listview by searching the now system date on your drives. The changed files are shown with attributes, size and path names rather than numbers making it easier to see what's going on on your storage system or hard drive. Answer: This should make it easier to determine which files had changed and diminish the chance that a change is causing an undiscovered impact (spyware, caching, integrity etc.). First we need a class: type TChangeFinder = class private nYear, nMonth, nDay: word; dflistView: TListView; protected procedure ShowFiles (Showpath: string; sr: TSearchRec); public constructor prepList_and_Date(alistView: TListView); procedure SearchDirectories(path: string; const fname: string); end; The call from the client needs an instance of a 3 column TListView and the choosen drive letters: procedure TmainForm1.cfinderClick(Sender: TObject); //tshChangefind: TTabSheet, pageindex=5, event= onShow var drive: string[20]; mycf: TChangeFinder; begin screen.cursor:=crHourglass; drive:= dcbHD.Drive; drive:= drive + ':'; mycf:= TChangeFinder.prepList_and_Date(mainForm1.view); mycf.SearchDirectories(drive + '\','*.*'); mycf.Free; screen.cursor:=crDefault; end; The form independent unit itself scans for all files with the system date of today or now so you get a list in a listview for a filechange detection system for Web Servers, protocol machines, loggers, databases or something else useful ;). Implementation of the unit changefinder: ------------------------------------------------------------ constructor TChangeFinder.prepList_and_Date(alistView: TListView); begin inherited Create; dflistView:= alistView; decodedate(date, nYear, nMonth, nDay); with alistView do begin columns[0].Caption:= 'Filename'; columns[1].Caption:= 'Size'; columns[2].Caption:= 'Attr'; end; with alistView.Items do begin BeginUpdate; Clear; EndUpdate; end; end; procedure TChangeFinder.ShowFiles(Showpath: string; sr: TSearchRec); var arcdisp: string[9]; dateRec: TDateTime; lenStr, fname, fext: string; itNewItem: TListItem; AYear, AMonth, ADay: Word; begin if sr.Attr IN [$8..$F, $28..$2F] then begin if Pos('.', sr.Name) > 0 then Delete(sr.Name, Pos('.', sr.Name), 1); end; if (Pos('.', sr.Name) > 0) AND (Length(sr.Name) > 0) then begin fname:= Copy(sr.Name, 1, Pos('.', sr.Name) - 1); fext:= sr.Name; Delete(fext, 1, Pos('.', fext)); end else begin fname:= sr.Name; fext:= ' '; end; arcdisp:= ' '; {$WARN SYMBOL_PLATFORM OFF} if sr.Attr AND faArchive = faArchive then arcdisp[1] := 'A'; if sr.Attr AND faReadOnly = faReadOnly then arcdisp[2] := 'R'; if sr.Attr AND faHidden = faHidden then arcdisp[3] := 'H'; if sr.Attr AND faSysFile = faSysFile then arcdisp[4] := 'S'; {$WARN SYMBOL_PLATFORM ON} //attr. 8..15, 40..47 if NOT (sr.Attr IN [$8..$F, $28..$2F]) then Str(sr.Size, lenStr); if NOT (sr.Attr IN [$8..$F, $28..$2F]) then begin //check the system now date! dateRec:= FileDatetoDateTime(sr.Time); DecodeDate(dateRec, AYear, AMonth, ADay); if (ADay = nDay) AND (AYear = nYear) AND (AMonth = nMonth) then begin if Showpath[Length(Showpath)] = '\' then Delete(Showpath, Length(Showpath), 1); itNewItem:= dflistView.Items.insert(0); itNewItem.Caption:= Showpath + '\' + fname+ '.' + fext; itNewItem.SubItems.Add(lenStr); itNewItem.SubItems.Add(arcdisp); end; end; end; procedure TChangeFinder.SearchDirectories(path: string; const fname: string); var SRecord : TSearchRec; locShowpath: string; begin (* search of files: *) if Length(path) > 0 then if path[Length(path)] <> '\' then path := path + '\'; SRecord.Name := ''; if FindFirst(path + fname, faAnyfile MOD faDirectory, SRecord) = 0 then begin //Showpath:= ' . '; if SRecord.Name <> '' then begin if Length(path) > 3 then locShowpath:= Copy(path, 1, Length(path) - 1) else locShowpath:= path; end; try repeat //attr 0..14, 32..46 if SRecord.Attr IN [$0..$E, $20..$2E] then ShowFiles(locShowpath, SRecord); until FindNext(SRecord) <> 0; finally FindClose(SRecord); end; end; //search of dir. including hidden dirs! if FindFirst(path + '*.*', faDirectory or faHidden, SRecord) = 0 then begin try repeat if (SRecord.Attr AND faDirectory <> 0) AND (SRecord.Name[1] <> '.') then //recursion to get subdirectories SearchDirectories(path + SRecord.Name, fname); until FindNext(SRecord) <> 0; finally FindClose(SRecord); end; end; end; end. DelphiWebStart in v1.5 *************************************************************************************** CLX app with a TCP socket stream Product: Delphi 6.x (or higher) Category: NetCLX Skill Level: 11/27/2005 Company: kleiner kommunikation Reference: http://www.softwareschule.ch/download/dws_system_manual.pdf Question/Problem/Abstract: Loading different apps or several files without a browser over a TCP/IP net and on Win as Linux as well needs a decision once. With a loader on the client side, no further installation is in charge, we call that DelphiWebStart (DWS). Answer: In short the DWS-client gets a list and after clicking on it, the app or the file is loading from a socket server over the net to client with just a stream. Indy Sockets on CLX DWS is building on the Relationship between Indy Sockets, CLX and the Qt library on Delphi 7. The new free version 1.5 is available on http://sourceforge.net/projects/delphiwebstart *********************************************** - new package with executables (win32) and qtint70.dll - recompile without change finder also on CLX and Linux - source improvments - uml diagrams, tooltip descriptions - load&store definition files - change finder and copy checking - better client disconnecting, monitor as shortmessages You can find also a 9 page system manual on http://www.softwareschule.ch/download/dws_system_manual.pdf ---------------------------------------------------------------- Tip: To compile CLX (without xfm forms) apps in Delphi 2005 you have to fulfill the following steps: First you have to set the paths in options/searchpath cause of "cannot resolve unit name QStdCtrls...." C:\Program Files\Borland\Delphi7\Source\Clx; C:\Program Files\Borland\Delphi7\Source\Rtl; C:\Program Files\Borland\Delphi7\Source\Rtl\Common C:\Program Files\Borland\Delphi7\Source\Indy If you see [Fatal Error] Qt.pas(5499): F2051 Unit SysUtils was compiled with a different version of Windows.Succeeded there are more to add to the search path C:\Program Files\Borland\Delphi7\Source\Rtl\Sys; Last you can copy the six *.res files local like QButtons.res etc. On win each CLX app needs the qt dll, this is from the source: const {$IFDEF MSWINDOWS} QtShareName = 'qtintf70.dll'; QtLibName = 'qtintf70.dll'; {$ENDIF} {$IFDEF LINUX} QtShareName = ''; QtLibName = ''; {$ENDIF} How to build Testcases with DUnit ************************************************************************************************ Testing and Refactoring last Update: 02/23/2006 Company: kleiner kommunikation Reference: http://dunit.sourceforge.net/ Question/Problem/Abstract: Finding meaningfull testcases isn't that easy. As we grow with experiences I will show few testcases to inspire and deliver some ideas. Answer: The idea of DUnit or another xUnit is that, as we develop or change code, we develop appropriate verification tests at the same time, rather than postponing them to a later test phase. By keeping the tests up-to-date and re-applying them at regular intervals, it becomes easier to produce reliable code, and to be confident that alterations (above all refactorings) do not break existing code. Applications should become self-testing. But finding meaningfull testcases isn't that easy. As we grow with experiences I will show few testcases to inspire and deliver some ideas. Note that DUnit builds a separate instance of the class for each method that it finds, so test methods cannot share instance data. We dig into 4 methods: 1. Test Roboter for Forms 2. Test an Exception (like testing a disaster recovery) 3. Test the Fileformat to prevent Viruses, Hoaks and so on 4. Test a Reference Value It's important to be a little familiar with DUnit. First we build the testclass which will test our units: (Testing procedures should be published so RTTI can find their method address with the MethodAddress method.) Add an instance variable for every fixture (i.e. starting situation like myForm, FFull...) you wish to use. type TTestCaseMethods = class(TTestCase) private fFull: TList; myForm: TForm1; incomeref: CIncome; protected procedure SetUp; override; procedure TearDown; override; published procedure testFormRobot; procedure testExceptionIndexTooHigh; procedure testFileFormat; procedure testReferenceValue; private function WaveFileCheck(flname: string): boolean; end; As we can see the 4 testmethods in the publish section, we should keep 3 things in mind: - Test methods are parameter-less procedures. - Test methods are declared published. - Test methods generally map methods from an application But it's possible to set private methods which can be used independent of an application, like the function WaveFileCheck! We often need to do some common preparation before running a group of tests, and some freeing afterwards. For example, when testing a form or a file, we might want to create an instance of that form, run some checks on it, and finally free it. If we have a lot of tests to make, we'll end up with repetitive code in each test method. DUnit provides support for these situations through the virtual methods SetUp and TearDown, which are called before and after each test method is executed. In Xtreme testing jargon, a prerequisite state like the one provided by these two methods is known as a fixture. Our fixture for the 4 cases are: //------------------------------------------------------------------ procedure TTestCaseMethods.SetUp; begin myform:= TForm1.Create(NIL); fFull:= TList.Create; fFull.Add(TObject.Create); fFull.Add(TObject.Create); end; //------------------------------------------------------------------ procedure TTestCaseMethods.TearDown; var i: Integer; begin for i:= 0 to fFull.Count - 1 do TObject(fFull.Items[i]).Free; fFull.Free; myform.Release; end; //------------------------------------------------------------------ Now the 4 testmethods: 1. The following calls check() to see if everything went OK. It's up to you to enlarge the check with more calls or methods like a test roboter does. procedure TTestCaseMethods.testFormRobot; begin myForm.lblserial.Caption:= 'another text test'; myform.ShowModal; check(myForm is TForm, 'this is not the right type'); end; 2. The following example extends exception handling to do a couple of tests which can prove exception handling works. This case shows that it is possible to test the exceptions of the test subject: procedure TTestCaseMethods.testExceptionIndexTooHigh; begin try fFull.Items[2]; //Check(false, 'should have been an EListError.'); except on E: Exception do Check(not(E is EListError)); end; end; 3. Next a test to prove if it's or not the right format like a scanner does: resourcestring rSrc_RIFF = 'RIFF'; rSrc_WAVE = 'WAVE'; procedure TTestCaseMethods.testFileFormat; begin check(waveFileCheck('maxmor2.wav'),'this is not a wave'); end; function TTestCaseMethods.WaveFileCheck(flname: string): boolean; var f: file; BuffHeader: array[1..12] of char; begin AssignFile(f, flname); FileMode:= 0; Reset(f,1); try BlockRead(f, BuffHeader, 12); result:=(Copy(BuffHeader, 1, 4)=rSrc_RIFF) and (Copy(BuffHeader, 9, 4)=rSrc_WAVE); finally CloseFile(f); end; end; 4. At last a reference test in this case a function which returns a value, so we can refactor as much we want as long the reference value is still the same. This test checks with checkEquals() if the rate of an income results the right value 1040. // When checkEquals fails, it will end up in the TestResult as a failure (not reference value, expected: <1040> but was: <1042>). procedure TTestCaseMethods.testReferenceValue; begin incomeRef:= createIncome2; incomeref.SetRate(4,1); checkEquals(1040, incomeRef.GetIncome(1000), 'no reference value'); incomeRef.FreeObject; end; Let's have a look at the main program which since Delphi 9 and 10 (Delphi 2006) will be build with the help of an integrated expert: program ListTest; uses TestFramework, KylixGUITestRunner, GUITestRunner, TListTestCase in 'TListTestCase.pas'; {$R *.res} begin {$IFDEF LINUX} KylixGUITestRunner.runRegisteredTests; {$ELSE} GUITestRunner.runRegisteredTests; {$ENDIF} end. have Fun and Run, greetings from maxland ;) DLL Cross Independencies ********************************************************************************** How to build a DLL for others Company: kleiner kommunikation Reference: http://www.softwareschule.ch/download/dlldesign_ekon9.pdf Question/Problem/Abstract: Build a DLL more "other language friendly" and less IDE or platform dependent. Answer: First we have to decide between a DLL or a package: When we update a DLL (change function's implementation), we simply compile it, export some new routines and ship the new version. All the applications using this DLL will still work (unless, of course, you've removed existing exported routines). On the other hand, when updating a package, you cannot ship a new version of your package without also updating the executable. This is why we cannot use a unit compiled in D4 in a D5 project unless we have the unit's source; the compiler checks version information of DCU's and decides whether an unit has to be recompiled so any package that you provide for your application must be compiled using the same Delphi version used to compile the application. Note: You cannot provide a package written in D6 to be used by an application written in D5. The advantage of a DLL is: For most applications, packages provide greater flexibility and are easier to create than DLLs. However, there are several situations where DLLs would be better suited to your projects than packages: - Your code module will be called from non-Delphi applications. - You are extending the functionality of a Web server. - You are creating a code module to be used by 3.party developers. - Your project is an OLE container. DLL independencies what’s all about? A Exception Handling B Include Files C Call Convention D Data alignment and Types E Name Mangling ------------------ A: For exception handling to work across multiple binary modules, all binary modules need to use the same copy of the shared exception handling code. To accomplish this, the following must be done: 1) All binary modules which use the Delphi RTL (CLX) must be built with the same version of the RTL runtime package. 2) All binary modules must dynamically link to the exception handling shared object libborunwind.so. This is accomplished by enabling the "Use dynamic RTL/STL" linker option. If this is not done, exceptions raised in one module may cause unintended side effects in other modules. ------------------ B: Include File, The $I parameter directive instructs the compiler to include the named file in the compilation. In effect, the file is inserted in the compiled text right after the {$I filename} directive. The default extension for filename is .pas. If filename does not specify a directory path, then, in addition to searching for the file in the same directory as the current module, unit recompile if file newer. To specify a filename that includes a space, surround the file name with single quotation marks: {$I 'My file'}. ------------------ C: Call Convention, When you call the DLL written in C or C++, you have to use the stdcall or cdecl convention. Otherwise, you will end up in violation troubles and from time to time the application may crash. But for the rest of the world you can use pascal! By the way the DLL, you are calling, should be on the search path;). So these conventions (stdcall, cdecl) pass parameters from right to left. With cdecl, the caller (that's Delphi) has to remove the parameters from the stack when call returns, others clean-up by the routine. Tip in c++: DLL_IMPORT_EXPORT means after all stdcall. ------------------- D: Data Types and Alignment, To understand which operations can be performed on which expressions, we need to distinguish several kinds of compatibility among types and values. These include: - Type identity - Type compatibility - Assignment compatibility The common type system defines how types are declared, used, and managed in the runtime, and is also an important part of the runtime's support for cross-language integration. Records or pointer to records should have the right alignment: Structure members are stored sequentially in the order in which they are declared: the first member has the lowest memory address and the last member the highest. Applications should generally align structure members at addresses that are "natural" for the data type and the processor involved. For example, a 4-byte data member should have an address that is a multiple of four. #pragma pack Using packed in Delphi slows data access and, in the case of a character array, affects type compatibility ! In status {$A1} or {$A-} fields don’t get an alignment. All records and structures of classes will be packed. type TTeststruct = record iVar: Integer; { 4 Byte } dVar: double; { 8 Byte } bVar: boolean; { 1 Byte } sVar: Array[1..50] of char; { n * 1 Byte } end; To avoid BORLNDMM.DLL in linker pass strings as PChar or ShortString: function SelectDir(Dest: PChar):Boolean; if dirSelect.ShowModal = idok then begin StrPCopy(Dest, dirSelect.DirectoryListBox1.Directory); caller: Var pathback: array[0..MAX_PATH] of Char; SelectDir(pathback) Deliver types others can understand: Don’t use unknown objects or structs like string or stringlist between different compilers or class libraries in a DLL: Offsets and alignments are different! Ex.: C++ TStringList Delphi (mylist.add(‘hi c++dll’) C++ TStringList It won’t work to fill the stringlist from c++ DLL TStringList *MyStringList = new TStringList; ------------------------ E: Prevent Name Mangling, You can work with an index instead of a name in a .DEF file and export section (depends on your signature) C++: LIBRARY mxlump_dll EXPORTS FunctionName1 @1 FunctionName1 @2 ProcedureName1 @3 Solution: Set an alias in the Delphi external declaration: function CreateIncome2: CIncome; stdcall; external 'income.dll' name '_CreateIncome'; You can work with an block to prevent all functions from decorating: macro NoMangle means ‘extern "C“’ extern "C" { __declspec(dllexport) CIncome *CreateIncome(); void __EXPORT_TYPE SayHello2(); } function CreateIncome2: CIncome; stdcall; external 'income.dll' name '_CreateIncome'; You can work with an NoMangle decorator macro to prevent the decoration!: C: NoMangle long DLL_IMPORT_EXPORT csp2GetDeviceId(char szDeviceId[8], long nMaxLength); Call it from Pascal: function csp2GetDeviceId(szDeviceId: PChar; nMaxLength: Longint): Longint; stdcall; external 'csp2.dll' name 'csp2GetDeviceId'; var myBuffer: array [0..7] of Char; begin csp2GetDeviceId(@myBuffer[0], SizeOf(myBuffer)); How to use a hash function? ******************************************************************************************* Company: kleiner kommunikation Reference: http://sourceforge.net/projects/tplockbox/ Question/Problem/Abstract: A typical hash function at work and the aim of such functions will be shown Answer: So first whats the use of a hash function: A hash function takes a long string (or message or digest) of any length as input and produces a fixed length string or integer as output, sometimes termed a message digest or a digital fingerprint. {************************************************************} {* input hash sum *} {* a maxfox... -->hash function() --> DCFS456CA63223AD1256 *} {* 2354AEAAD435D12AEDF9 *} {************************************************************} So whats the connection between digital signatures and hash functions? For both security and performance reasons, most digital signature algorithms specify that only the digest of the message be "signed", not the entire message. Hash functions can also be used in the generation of pseudorandom bits. All major digital signature signing techniques (including DSA and RSA) involve first hashing the data then signing the hash. Raw message data is not signed because of both performance and security reasons. There is a Public License from TurboPower called LockBox. The components are LockBox from Turbopower - available at http://sourceforge.net/projects/tplockbox/ LockBox is a cross-platform toolkit for data encryption. It contains routines & components for use with Borland Delphi, C++Builder, & Kylix. It provides support for Blowfish, RSA, MD5, SHA-1, DES, triple- DES, Rijndael, & digital signing of messages. The interface of LBCIPHER.PAS v2.07 expects 3 parameters: procedure HashSHA1( var Digest: TSHA1Digest; const Buf; BufSize: Longint ); var Context: TSHA1Context; begin InitSHA1( Context ); UpdateSHA1( Context, Buf, BufSize ); FinalizeSHA1( Context, Digest ); end; you can choose between two hash functions: type TMD5Digest = array [0..15] of Byte; {128 bits - MD5} TSHA1Digest = array [0..19] of Byte; {160 bits - SHA-1} SHA-1 is considered to be the successor to MD5, an earlier, widely-used hash function. The SHA algorithms were designed by the National Security Agency (NSA) and published as a US government standard. The following procedure shows a call to the LockBox library: --------------------------------------------------- //fKey : array of byte; procedure TRijndaelCipher.KeyByPassword(const thePassword: string); var sha1: TSHA1Digest; pSha1: ^TSHA1Digest; cntr: integer; begin if Length(thePassword) > 0 then begin HashSHA1(sha1,thePassword[1],Length(thePassword)); pSha1:= @fKey[0]; for cntr:= 1 to Length(fKey) div sizeof(TSHA1Digest) do begin pSha1^:= sha1; Inc(pSha1); end; Move(sha1, pSha1^,Length(fKey) mod sizeof(TSHA1Digest)); end else raise TRijndaelCipherException.CreateWithErrorCode('No valid password.',cRCECInvalidPassword); end; so the call of the hash is simple in HashSHA1(sha1,thePassword[1],Length(thePassword)); On .net1.1 and VCL.net we can use a straight forward solution like this: -------------------------------------------------- uses system.Security.Cryptography, system.Text; procedure TForm1.button1Click(sender: TObject); var arrStr: array of Byte; oHash: SHA1CryptoServiceProvider; oStream: TMemoryStream; begin oHash:= SHA1CryptoServiceProvider.create; oStream:= TMemoryStream.create; arrStr:= BytesOf('maxfox in a box'); oStream.write(arrStr, lenght(arrStr)); showMessage(oHash.computeHash(oStream)); end; another use of a hash is to find data just by the name (string), so we don't need an index or a tree. Immagine each number (longint) defines a place so we just need the name to find his place again (name - value rule), here's an example of such a function: function MakeHash(const s: string): Longint; {small hash maker} var i: Integer; begin Result:= 0; for i:= 1 to Length(s) do Result:= ((Result shl 7) or (Result shr 25)) + Ord(s[i]); end; From http://en.wikipedia.org/wiki/Cryptographic_hash_functions: A typical use of a cryptographic hash would be as follows: Alice poses to Bob a tough math problem and claims she has solved it. Bob would like to try it himself, but would yet like to be sure that Alice is not bluffing. Therefore, Alice writes down her solution, appends a random nonce, computes its hash and tells Bob the hash value (whilst keeping the solution secret). This way, when Bob comes up with the solution himself a few days later, Alice can verify his solution but still be able to prove that she had the solution earlier. Hope Dan Brown will understand this ;) from www.delphi3000.com all articles by Max Kleiner max@kleiner.com http://max.kleiner.com