Pascal Newsletter #40
The full source code examples of this issue are available for download.
![]() |
![]() |
Pascal Newsletter #40 - 27-OCT-2002 Contents 1. A few words from the editor 2. Windows Hooks (or "How do all those spy programs work?") 3. Base64 (MIME) Encode and Decode 4. Example of a Windows Service, with a thread 5. Capture Output of a Console Application 6. Inline Assembler in Delphi (IV) - Records 7. Forums / mailing lists 8. Delphi on the Net - Components, Libraries and Utilities · Freeware - Articles, Tips and Tricks - Tutorials - Other links ________________________________________________________________________ 1. A few words from the editor I intended to have this issue published some time ago as promised, but in very particular circumstances a new and unidentified virus got into my PC and managed to overwrite the first cylinder of my hard disk (the master boot record, and the boot sector and part of the FAT of the first partition), so go figure... (I'm still installing all the Delphi components I used to have). I apologize for the delay. I'd like to thank the authors who contributed articles for this issue, and I'm glad to award them the following prizes: * Florin Sabau ("Windows Hooks") · llPDFLib v1.1 - by llionsoft, Shareware ($70, $280 with source) llPDFLib is pure Object Pascal library for creating PDF documents. Does not use any DLL and external third-party software to generate PDF files. Library consists of TPDFDocument component with properties and methods like Delphi's TPrinter but designed to generate a PDF file. http://www.llion.net/ * Jochen Fromm ("Capture Output of a Console Application") · Greatis Form Designer v3.4 - by Greatis Software, Shareware ($49.95) It's a runtime form designer that allows you to move and resize any control on your form. You don't need to prepare your form to use Form Designer. Just drop TFormDesigner component onto any form, set Active property to True and enjoy! For Delphi 4-7 and BCB 3-6. http://www.greatis.com/formdes.htm * Kim Sandell ("Example of a Windows Service, with a thread") · Developer Information Library (DIL) CD - by UK Borland User Group Over 17,000 Tips, Tricks, FAQs and Technical Articles · Patches and Updates for Borland Tools · Over 4000 Components & Tools · Over 4000 Bitmaps ready to use with another 20000 zipped · Over 350 ready to use JavaScripts · Complete Set of Linux How-tos · and much much more... http://www.richplum.co.uk/html/dil.asp For the next issue, we have available the following prize for one of our contributors: * TSDBGridFooter v2.0 by Jovan Sedlan, Shareware ($74.50) This component is a powerful tool that provides automatic calculations for your DBGrid and displays that information in a customizable footer under the grid. It is designed to work with TSDBGrid (also included) although you can use it with any TCustomDBGrid descendant. http://www.sedlan.com/dbgrid_footer.php Changing subjects, the first update pack for Delphi 7 has been released: * Delphi 7 Update Pack 1 http://community.borland.com/article/0,1410,29209,00.html I hope you enjoy this issue. Regards, Ernesto De Spirito eds2004 @ latiumsoftware.com __________________ Collaborated in this issue: Dave Murray and Charl Linssen ________________________________________________________________________ JfControls Library. Multi-language. Multi-appearance. Skins. Privileges. More than 40 integrated and customizable components. Impressive GUI. Centralized resources administration. Multiple programming problems solved. For Delphi 3-7 and C++ Builder 3-6. http://www.jfactivesoft.com/ ________________________________________________________________________ 2. Windows Hooks (or "How do all those spy programs work?") By Florin Sabau <aaa111@go.ro> Have you ever wondered how all those spy programs get everything that is typed on the computer they are installed on? Well, I don't know exactly how THEY do it :), but I'm going to show you a possible approach using Windows Hooks. This article will show you how you can create a program that "listens" for a certain combination of keystrokes and when it's activated it does something meaningful (opening the CDROM tray). Technically a hook is just another subroutine ("hook procedure") that "gets in the way" of the normal message-handling mechanism of Windows. The hook procedure can be installed in the system so that it gets certain messages from Windows BEFORE they are dispatched to their designated window procedure. Windows contains many different types of hooks; each type provides access to a different aspect of the Windows message-handling mechanism. Here are some of them (actually the constants that identify them taken from windows.pas) with a small description: WH_KEYBOARD: Installs a hook procedure that monitors keystroke messages. We'll use this one in our program. WH_MOUSE: Installs a hook procedure that monitors mouse messages. WH_CBT: Installs a hook procedure that receives notifications useful to a computer-based training (CBT) application. WH_JOURNALRECORD: Installs a hook procedure that records input messages posted to the system message queue. This hook is useful for recording macros. WH_JOURNALPLAYBACK: Installs a hook procedure that posts messages previously recorded by a WH_JOURNALRECORD hook procedure. Because more than one program might install a hook in the system at the same time, Windows maintains internally a "hook chain", which is just a list of pointers to the hook procedures the programs have installed. When a message occurs in the system, Windows first passes it to every procedure in the hook chain, one after the other. Then if the message was not "blocked" by any of the hook procedures, Windows dispatches it to the designated window procedure. One more thing before we get to the next section: hooks can be classified in some other way. There are system wide (global) hooks that receive messages for all the threads in the system, and thread specific (local) hooks, that receive messages only designated to one individual thread. Because a global hook procedure can be called in the context of any application (to capture messages from all applications), it must be located in a DLL (Dynamic Link Library). This restriction doesn't apply to thread specific hooks, so the hook procedure can be located in any part of the application that owns the thread to be hooked. In this article we deal with global hooks only. Installing a WH_KEYBOARD hook procedure into the hook chain ----------------------------------------------------------- The hooks API contains 3 very important functions: SetWindowsHookEx (which installs a hook procedure), UnhookWindowsHookEx (which uninstalls the hook procedure) & CallNextHookEx (which calls the next hook procedure in the hook chain). The parameters that these functions take (signature) are shown below (from windows.pas): function SetWindowsHookEx(idHook: Integer; lpfn: TFNHookProc; hmod: HINST; dwThreadId: DWORD): HHOOK; stdcall; "idHook": type of hook to be installed (i.e. WH_KEYBOARD); "lpfn": pointer to the hook procedure the messages should be posted to; "hmod": handle of the DLL that installs the hook, usually hInstance (for global hooks) or 0 for local hooks; "HINST": identifier for the thread that the hook will be associated with. If 0 the hook procedure is associated with all the threads. Returns a value used to identify the hook. function UnhookWindowsHookEx(hhk: HHOOK): BOOL; stdcall; "hhk": identifier of the hook to be uninstalled. Returns True if successful, False if failed. function CallNextHookEx(hhk: HHOOK; nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; "hhk": identifier of the current hook; "nCode","wParam","lParam": parameters that should be send to the next hook procedure in the hook chain. Returns the value returned by the next hook procedure in the chain. We'll see later in the example what this means. The hook procedure ------------------ The hook procedure for the keyboard hook has a standard format and in order for the hook to work the programmer must supply this exact format: function HookProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; "nCode": = HC_ACTION - the wParam and lParam parameters contain information about a keystroke message = HC_NOREMOVE - the wParam and lParam parameters contain information about a keystroke message, and the keystroke message has not been removed from the message queue (an application called the PeekMessage function, specifying the PM_NOREMOVE flag). "wParam": specifies the virtual key code of the key that generated the message (i.e. VK_F9 for the F9 function key) "lParam": specifies additional information (like repeat count, scan code ...); not used in our program; see Win32SDK for details; HookProc should return a nonzero value to prevent Windows from passing the message to the rest of the hook chain or to the target window procedure or 0 to let Windows pass the message to the target window procedure. Example ------- This example creates a global hook on the keyboard and when a certain combination of keystrokes occur it does something meaningful (see below): WinKey + F9: Shows the main form if it's hidden; WinKey + F10: Ejects the CDROM tray; WinKey + F12: Shutdowns application. The communication between the DLL that implements the hook and the application is done with the API function SendMessage, which sends a HOOK_MSG (defined in constants.inc) to the main application, with the command (SHOW, EJECT, QUIT) in wParam (see below). Because we don't want to be nagged by the form being shown all the time, on clicking the minimize button it hides itself (even from the Tasks List), but can be later shown by the combination Winkey+F9. For the complete source code see the attached file. I only show here the most important parts of the application: 1. HookDll.dpr function KeyboardProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; var Handled: Boolean; KeyState: TKeyboardState; Han: HWND; function WinKeyPressed: boolean; begin Result := (KeyState[VK_LWIN] and $80 <> 0) or (KeyState[VK_RWIN] and $80 <> 0); end; begin Handled := False; Result := 1; if nCode = HC_ACTION then begin GetKeyboardState(KeyState); Han:=FindWindow('TForm1',APP_CAPTION); if (IsWindow(Han)) and (KeyState[wParam] and $80 <> 0) and WinKeyPressed then begin Handled := True; case wParam of VK_F9: SendMessage(Han,HOOK_MSG, APP_SHOW, 0); VK_F10: SendMessage(Han,HOOK_MSG, EJECT_CDROM, 0); VK_F12: SendMessage(Han,HOOK_MSG, APP_QUIT, 0); else Handled := False; end; end; end; if not Handled then Result := CallNextHookEx(hhk, nCode, wParam, lParam); end; On entering the hook procedure we test for the HC_ACTION flag in nCode (so we know we have a keystroke), then we save in KeyState the state (if it's pressed or depressed, toggled for CapsLock, etc.) of all the virtual keys by using the GetKeyboardState. We also find the window handle of the main application to which we send commands (APP_SHOW, EJECTCDROM, APP_QUIT). If we find it (IsWindow(han)) and we have a PRESSED key message (KeyState[wParam] and $80<>0) and the Winkey is also pressed, then we send a command to the main form according to which key is pressed. If we can't handle the keyboard message (Handled=False) then we pass it to the next hook in chain. 2. Hooks.Dpr & Unit1.pas The most important function here is HOOK_MSG_PROC which receives the commands sent from the DLL: type TForm1=class(TForm) ... procedure HOOK_MSG_PROC(var Msg: TMessage); message HOOK_MSG; ... end; ... procedure TForm1.HOOK_MSG_PROC(var Msg: TMessage); begin case Msg.WParam of APP_SHOW: begin Application.ShowMainForm := True; Visible:=True; end; EJECT_CDROM: mciSendString('set CDAudio door open',nil,0,0); APP_QUIT: Close; end; end; The "Hide on minimize" feature is accomplished by subclassing the WM_SYSCOMMAND message which is sent by Windows when a system command (like close, minimize, maximize) occurs. Then we hide the form if we have a SC_MINIMIZE command: procedure TForm1.OnMinimize(var Msg: TMessage); begin if Msg.WParam = SC_MINIMIZE then begin if not IsHookInstalled then begin ShowMessage('Install the hook first or you''ll'#13#10 + 'not be able to access the program'); Exit; end; Application.ShowMainForm := False; Visible := False; end else Inherited; end; To hide the program from the tasks list we use: function RegisterServiceProcess(dwProcessID, dwType: integer): integer; stdcall; external 'KERNEL32.DLL'; NOTE: The above hiding technique works only on Windows 9x. Of course this is only a very simple application of hooks, but the possibilities are numerous. A small spy program, perhaps! :) Anyway, if you have any questions about this article feel free to send me a note to <aaa111@go.ro>. __________________ NOTE: Full source code is attached. ________________________________________________________________________ When was the last time you voted for the Pascal Newsletter? Please support this initiative voting for us in The Programming Top 100! http://www.sandbrooksoftware.com/cgi-bin/TopSite2/rankem.cgi?id=latium ________________________________________________________________________ 3. Base64 (MIME) Encode and Decode By Daniel Wischnewski delphi3000@wischnewski.tv I have written the following unit to replace the INDY TIdEncoderMIME and TIdDecoderMIME components. This "Codec" is used in email software, primarily. First reason was the lack of speed of these components. Second reason was that they are components and therefore require the VCL - a heavy extra load on otherwise nonVCL systems. Both routines are written in Assembler and overloaded by different versions allowing for easy access. I am sure some of your are able to increase the speed even some more. Please let me know. Thanks. Update May 2003: I have introduced a compiler switch to switch between a fast decoding mode and a more secure decoding mode. It is up to you, which you like to use. I recomend using the more secure mode, especially if the user is required to work with the encoded data. __________________ NOTE: Full source code is attached. ________________________________________________________________________ AnyShape Transpack v2.0 - by MindBlast Software (DELPHI + KYLIX) Tired of boring, rectangular windows? AnyShape Transpack is a cross- platform component that eases the creation of transparent, weirdly shaped windows with WYSIWYG editing, design-time preview, automatic dragging, REAL stay-on-top forms, and the ability to combine regions. http://www.mindblastsoftware.com/?page=transpack&ref=PascalNL ________________________________________________________________________ 4. Example of a Windows Service, with a thread By Kim Sandell - kim.sandell@nsftele.com www.nsftele.com Delphi 5 and 6 have a template project for services, but it is incomplete. This example builds on that template and completes the service. It also shows how to start a thread that beeps every two seconds. You can use this as a base when developing servers as services. The example is meant for Delphi 5-7 and works on Windows NT/2000/XP. The service startup option is set to MANUAL. If you want to make a service that starts automatically with Windows then you need to change this. BE CAREFUL! If your application hangs when running as a service THERE IS NO WAY to terminate it. __________________ NOTE: Full source code is attached. __________________ Author's Profile Summary: Name : Kim Sandell Born : 1973 Helsinki, Finland Skills : VB, Delphi (1-6), C/C++, SQL, Interbase, IP Networks. Special areas: System Service, Protocols, Security (VPN), Authentication, Servers (Deamons) + More Company: NSF Telecom Ab Title: CTO WWW : http://www.nsftele.com EMail: kim.sandell@nsftele.com ________________________________________________________________________ 5. Capture Output of a Console Application By Jochen Fromm <Jochen.Fromm@manserv.de> How do you start a DOS or Console Application and capture the output while it is running? For example, how do you capture the output of the FileCompare (FC) Command? There are already two articles about this problem, http://www.delphi3000.com/articles/article_2112.asp http://www.delphi3000.com/articles/article_2298.asp but they get the output when the process is finished. There are also two Microsoft Articles about Redirection of DOS Applications : * Microsoft Knowledge Base Article - Q190351 HOWTO: Spawn Console Processes with Redirected Standard Handles http://support.microsoft.com/default.aspx?scid=kb;en-us;Q190351 * Microsoft Knowledge Base Article - Q150956 INFO: Redirection Issues on Windows 95 MS-DOS Applications http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q150956 The basic idea is to start the console application with CreateProcess and redirect the input and output with pipes, enabling the calling process to access the output of the console application. It is important to capture the output while the process is still running. If the output-pipe is blocked by an overflow, new information can not be written from the console app to the output-pipe, and the program stops. In this case we have a classic deadlock situation: the user (parent process) is waiting for the child-process to finish, and the child-process is waiting for the user to clear the buffer. __________________ NOTE: Source code and demo application is attached. ________________________________________________________________________ 6. Inline Assembler in Delphi (IV) - Records By Ernesto De Spirito <eds2004 @ latiumsoftware.com> Passing records as parameters ============================= Like static arrays, records are internally passed as pointers to the data, independently of whether the parameter is passed by value or by reference (either as "var" or as "const"). Given the following declarations... type TRecord = record Id: integer; Name: string; end; var a, b: TRecord; procedure InitRecord(var r: TRecord; Id: integer; const Name: string); begin r.Id := Id; r.Name := Name; end; ...a call to the procedure InitRecord in assembler would be like this: // In Object Pascal: // InitRecord(a, n, s); // In Inline Assembler: asm lea eax, a // EAX := @a; // 1st parameter in EAX mov edx, n // EDX := n; // 2nd parameter in EDX mov ecx, s // ECX := s; // 3rd parameter in ECX call InitRecord // InitRecord; end; Accessing the fields of a record ================================ Record fields are located at a certain offset from the address of the record (the address of the first field). In the example, assuming we have the address of a record of type TRecord in the EAX register, the field Id is located at [EAX+0] (or simply [EAX]), and the field Name is located at [EAX+4], but normally we don't write code using hardwired numbers. Instead, to produce self-explanatory and maintainable code we have five alternative ways: mov edx, [eax + TRecord.Name] mov edx, (TRecord PTR [eax]).Name mov edx, (TRecord [eax]).Name mov edx, TRecord[eax].Name mov edx, [eax].TRecord.Name The five previous sentences would assemble as: mov edx, [eax + 4] Instead of a register (like EAX), the syntaxes also apply to local variable names. You can infer from the first syntax that in inline assembler the expression RecordType.Field is evaluated at compile time as a constant representing the offset at which the Field is located in the RecordType. For example, the following sentence is valid: mov ecx, TRecord.Name // mov ecx, 4 Returning to the topic, the procedure InitRecord (introduced above) can be implemented in assembler like this: procedure InitRecord(var r: TRecord; Id: integer; const Name: string); asm // EAX = @r; EDX = Id; ECX = @Name[1] mov (TRecord PTR [eax]).Id, edx // EAX^.Id := EDX; // Id // _LStrAsg(@EAX^.Name, @Name) --> EAX^.Name := Name lea eax, (TRecord PTR [eax]).Name // EAX := @(EAX^.Name); mov edx, ecx // EDX := @Name[1]; call System.@LStrAsg // _LStrAsg(EAX, EDX) end; Upon entry to the procedure, we have EAX pointing to the record (first parameter), EDX containing the Id (second parameter), and ECX pointing to the data of the Name string (third parameter). Assigning an integer is quite simple, but assigning a string is a little bit more complicated: If the destination string is not the empty string then begin Decrement the reference count of the destination string; If the reference count of the destination string reaches zero then Release the destination string; end; If the source string is not the empty string then Increment the reference count of the source string; Assign source to destination; The _LStrAsg procedure (in the System unit) implements this logic for us. The procedure receives two parameters: the first (in EAX) is the destination string passed by reference, and the second (in EDX) is the source string passed by value (what is actually passed is the pointer, since strings are pointers to the actual characters). Therefore, in our case, EAX should be the address of the string variable that will be assigned (i.e. EAX should contain the address of r.Name), while EDX should be the value to be assigned: EAX --> r.Name --> r.Name[1] ==> EAX = @r.Name EDX --> Name[1] ==> EDX = @Name[1] Ref.: "-->" means "points to" (or "contains the address of") So, we set EAX and EDX and then we call _LStrAsg: lea eax, (TRecord PTR [eax]).Name // EAX := @(EAX^.Name); mov edx, ecx // EDX := @Name[1]; call System.@LStrAsg // _LStrAsg(EAX, EDX) Low level functions to work with records ======================================== Like with static arrays, if the record is passed by value, it is responsibility of the called function to preserve the record. When a function needs to change the values of one or more fields of a record passed by value, normally it creates a local copy and works on the copy. The compiler creates a copy for us in the "begin" of Pascal functions, but in full assembler functions we have to do it by ourselves. One way of doing this is like it was shown in part III with static arrays. Here is another way: procedure OperateOnRecordPassedByValue(r: TRecord); var _r: TRecord; asm // Copy the elements of "r" (parameter) in "_r" (local copy) // Move(r, _r, sizeof(TRecord)); lea edx, _r // EDX := @_r; mov ecx, type TRecord // ECX := sizeof(TRecord); call Move // Move(EAX^, EDX^, ECX); lea eax, _r // EAX := @_r; mov edx, TRecord_TypeInfo // EDX := TRecord_TypeInfo; call System.@AddRefRecord // System._AddRefRecord(EAX,EDX); lea eax, _r // EAX := @_r; // optional // Here goes the rest of the function. We'll work on the // record "_r" (the local copy), now pointed by EAX. end; This time we called the Move procedure instead of copying the data with REP MOVSB right there. This way we write less code. IMPORTANT: Copying the memory values only works with records that don't contain fields of reference-counted types such as strings, dynamic arrays, or variants of type string or dynamic array. If we have one or more string fields, or fields of any other reference- counted type, after copying the memory values we have to increment their respective reference counts. The procedure _AddRefRecord (in the System unit) does that. It takes two parameters: a pointer to the record (in EAX) and a pointer to the type information data for the record generated by the compiler (in EDX). The type information for a record is basically a data structure which contains the positions and types of the reference-counted fields of the record. The procedures to work with records declared in the System unit (_InitializeRecord, _AddRefRecord, _CopyRecord, and _FinalizeRecord) require a pointer to the type information data as their last parameter. But, where is that data? Well, unfortunately there is not a symbol to access its location directly. We have to get its address by a call to the TypeInfo function, but this is not a function we can call from assembler code because it's not a true function, but a built-in function that the compiler resolves at compile-time. One possible workaround it to initialize a global variable calling the TypeInfo function from our Pascal code: var TRecord_TypeInfo: pointer; : initialization TRecord_TypeInfo := TypeInfo(TRecord); And then we can use it like this: procedure OperateOnRecordPassedByValue(r: TRecord); var _r: TRecord; asm // Copy the elements of "r" (parameter) in "_r" (local copy) // Move(_r, r, sizeof(TRecord)); lea edx, _r // EDX := @_r; mov ecx, TYPE TRecord // ECX := sizeof(TRecord); call Move // Move(EAX^, EDX^, ECX); // System._AddRefRecord(@_r, TypeInfo(TRecord)); lea eax, _r // EAX := @_r; mov edx, TRecord_TypeInfo // EDX := TypeInfo(TRecord); call System.@AddRefRecord // System._AddRefRecord(EAX, EDX); lea eax, _r // EAX := @_r; // optional // Here goes the rest of the function. We'll work on the // record "_r" (the local copy), now pointed by EAX. // We have to finalize the local copy before returning // System._FinalizeRecord(@_r, TypeInfo(TRecord)); lea eax, _r // EAX := @_r; mov edx, TRecord_TypeInfo // EDX := TypeInfo(TRecord); call System.@FinalizeRecord // System._FinalizeRecord(EAX, EDX); end; Notice that before the function returns we have to make a call to _FinalizeRecord to destroy the local record (for instance, this will decrement the reference count of strings pointed by string fields). Calling Move and then _AddRefRecord is a valid way to copy records if and only if the destination record hasn't been initialized (after calling _AddRefRecord, the record is initialized). If the destination record is already initialized, then we have to call _CopyRecord instead. For example: procedure proc(const r: TRecord); var _r: TRecord; begin // _r := r; asm mov edx, eax // EDX := @r; lea eax, _r // EAX := @_r; mov ecx, TRecord_TypeInfo // ECX := TypeInfo(TRecord); call System.@CopyRecord // System._CopyRecord(EAX, EDX, ECX); end; end; Note that since this is a normal Pascal function (not a full assembler function), the compiler automatically generates code to initialize and finalize the local record variable (in the "begin" and "end" of the procedure respectively). The combination Move plus _AddRefRecord is identical in effect to _InitializeRecord plus _CopyRecord: procedure OperateOnRecordPassedByValue(r: TRecord); var _r: TRecord; asm // Copy the elements of "r" (parameter) in "_r" (local copy) // System._InitializeRecord(@_r, TypeInfo(TRecord)); push eax // Push(EAX); // @r lea eax, _r // EAX := @_r; mov edx, TRecord_TypeInfo // EDX := TypeInfo(TRecord); call System.@InitializeRecord // System._InitializeRecord(EAX, EDX); // _r := r; lea eax, _r // EAX := @_r; pop edx // EDX := Pop(); // @r mov ecx, TRecord_TypeInfo // EDX := TypeInfo(TRecord); call System.@CopyRecord // System._CopyRecord(EAX, EDX, ECX); lea eax, _r // EAX := @_r; // optional // Here goes the rest of the function. We'll work on the // record "_r" (the local copy), now pointed by EAX. // We have to finalize the local copy before returning // System._FinalizeRecord(@_r, TypeInfo(TRecord)); lea eax, _r // EAX := @_r; mov edx, TRecord_TypeInfo // EDX := TypeInfo(TRecord); call System.@FinalizeRecord // System._FinalizeRecord(EAX, EDX); end; Like _AddRefRecord, the procedure _InitializeRecord is only meant to be used with uninitialized records. Returning record values ======================= Returning record values is exactly the same as returning static array values. Functions returning records receive an additional last parameter which is the pointer to the memory location where they should place their return value, i.e., the value of the last parameter is @Result. The memory for the result record should be allocated, initialized and freed by the caller (it is not responsibility of the called function). For example, let's consider the following function: function MakeRecord(Id: integer; const Name: string): TRecord; begin Result.Id := Id; Result.Name := Name; end; The function is declared to receive two parameters and to return a record, but internally is like a procedure that gets three parameters: 1) EAX = the Id for the new record 2) EDX = the Name for the new record 3) ECX = the address of the Result record (@Result) The function can be rewritten in assembler as follows: function MakeRecord(Id: integer; const Name: string): TRecord; asm // EAX = Id; EDX = @Name[1]; ECX = @Result mov (TRecord PTR [ecx]).Id, eax // ECX^.Id := EAX; // Id // (@Result)^.Id := EAX; // Result.Id := EAX; // Result.Name := Name; // System.@LStrAsg(@(Result.Name), @Name[1]) // System.@LStrAsg(@(ECX^.Name), @Name[1]) lea eax, (TRecord PTR [ecx]).Name // EAX := @(ECX^.Name); call System.@LStrAsg // _LStrAsg(EAX, EDX) end; NOTE: We don't assign the value of EDX before calling _LStrAsg because EDX already contains the desired value (passed as parameter). Calling functions that return records ===================================== Consider the following code: a := MakeRecord(n, s); One would be tempted to think that the compiler translates it to: asm mov eax, n mov edx, s lea ecx, a // ECX := @a; // @Result call MakeRecord end; But things don't happen that way, at least not in Delphi 5. The compiler allocates and initializes a local variable to hold the result, and then copies the result record to the destination record. Not only we have an inefficiency for performing a copy that would be unneeded if we used a code like the above, but -as we have seen above- the copy itself is not as innocent as a call to the Move procedure (_CopyRecord checks the type information data at runtime to locate the fields that require special treatment). Of course, the invisible local variable is first initialized and eventually gets finalized. This way of doing things is terribly inefficient. If you need speed, call record-returning functions using assembler as I showed above, passing directly the address of the variable that will hold the result as the last parameter (@Result). Well, this is it for now. In the next part we'll see some basics of working with objects. __________________ NOTE: Full source code is attached. ________________________________________________________________________ 7. Forums / mailing lists To join any of our forums, the best way is to subscribe from the web, since that way you'll be able to access the features available at the web site (like changing your subscription options, viewing the past messages, accessing the files section, etc.). A Yahoo! ID is required for that, and you can get yours free by registering as a Yahoo! user, but if you don't want to register or if you don't have full Internet access, you can also subscribe by email (you'll only have email access). * Delphi: If you know a lot about Delphi but you are still far from being a guru this forum is for you. This is the only forum for intermediate-level Delphi programmers on the Web (Delphi experts are also welcome :-) http://groups.yahoo.com/group/delphi-en/ Subscription: http://groups.yahoo.com/group/delphi-en/join delphi-en-subscribe@yahoogroups.com * Kylix: Kylix programming. http://groups.yahoo.com/group/KylixGroup/ Subscription: http://groups.yahoo.com/group/KylixGroup/join KylixGroup-subscribe@yahoogroups.com * Components: This is a forum for searching/recommending software components (VCL and CLX components, ActiveX objects, DLL libraries, shared objects, etc.), as well as utilities, tutorials, information, etc. http://groups.yahoo.com/group/components/ Subscription: http://groups.yahoo.com/group/components/join components-subscribe@yahoogroups.com * Software Developers: This is a forum for discussions about software development and to share experience in the work, professional or commercial environments. It is not a programming forum, matters treated here are supposed to be more general or language independent. http://groups.yahoo.com/group/software-developers/ Subscription: http://groups.yahoo.com/group/software-developers/join software-developers-subscribe@yahoogroups.com ________________________________________________________________________ 8. Delphi on the Net By Dave Murray <irongut @ vodafone.net> Components, Libraries and Utilities =================================== Freeware -------- * Copernic Agent 6.0 - Freeware (adware, but not spyware) *NEW* It's the successor of Copernic 2001 5.0, the top metasearch software for the Internet ("Top Ten Internet Tools" by USA Today, 5-star rating from ZDNet, and "Best For 2001 - Top Five Software" by PC Magazine). Are you tired of wasting your time with meaningless results from search engines? Try searching with Copernic. It will surprise you! http://www.copernic.com/desktop/products/agent/index.html * MathX 2D and 3D graphics of one- and two-variables functions using OpenGL. Full source code is included. http://www.opensource.as.ro/public/MathX.zip Articles, tips and tricks ========================= * Migrating BDE Applications to dbExpress - by Bill Todd This white paper discusses the migration of BDE-based applications to dbExpress. http://community.borland.com/article/0,1410,29106,00.html * Cross-platform Development with Borland RAD Tools A white paper on native cross-platform development with Borland tools. http://community.borland.com/article/0,1410,29139,00.html * Report to the Delphi Community on Project JEDI - by Alan C. Moore Alan C. Moore, the Project JEDI director, provides an update on the many projects under development with the Project JEDI team. http://community.borland.com/article/0,1410,29157,00.html * Borland Kylix 3 versus Linux GCC Development - by William Roetzheim http://community.borland.com/article/0,1410,29171,00.html * Using the .NET Preview compiler in the Delphi 7 IDE - by John Kaster An Open Tool for using the preview compiler with the Delphi 7 IDE is available for download. http://community.borland.com/article/0,1410,29159,00.html * Top Windows API Books for Delphi Developers - by Zarko Gajic Recommended selection of top books on programming Delphi with Windows API, with links to book reviews. Books cover all of the most common Windows API functions, and each function has the syntax and an example of its use in Delphi Pascal. http://delphi.about.com/library/toppicks/aatpdelphiapibook.htm * How to validate an IBAN? http://www.swissdelphicenter.ch/en/showcode.php?id=1470 * How to get the installed keyboard layouts? http://www.swissdelphicenter.ch/en/showcode.php?id=1471 * How to get/set the caret blink time? http://www.swissdelphicenter.ch/en/showcode.php?id=1472 * How to encrypt/decrypt files with Windows NTFS functions? http://www.swissdelphicenter.ch/en/showcode.php?id=1473 * How to get mouse wheel line count? http://www.swissdelphicenter.ch/en/showcode.php?id=1474 * How to obtain all characters in Delphi 4+ IDE - by m3Rlin www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=179 * How to obtain object property list - by m3Rlin www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=180 * How to refresh Windows desktop - by m3Rlin www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=181 * How to draw rotated text - by m3Rlin www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=182 * How to swap two numbers without a third variable - by m3Rlin www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=183 * How to turn numlock on by code - by m3Rlin www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=184 * Access a MySQL database from Delphi Standard or Personal - TheSaviour How to access a MySQL database from Delphi Standard or Personal using the TMySQL component. www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=185 * How to sort a TList - by m3Rlin www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=186 * How to set the width of a TComboBox - by m3Rlin www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=187 * How to access a remote registry - by m3Rlin www.delphifaq.net/modules.php?op=modload&name=FAQ&op=view&id=188 * How to change a property of all components at one time - by M. Suesens How to change a special property of all components that own this property at one time. http://www.delphi3000.com/articles/article_3365.asp * CPU ID unit - by Mironescu Tony http://www.delphi3000.com/articles/article_3367.asp * How to implement a Sequential List in a database - by Adesh Jain http://www.delphi3000.com/articles/article_3372.asp * Export ALL tables from MS jet to CSV via ADO - by John Pears http://www.delphi3000.com/articles/article_3373.asp * High speed parser V1.5 - by Yuriy Pisarev Component is intended for some mathematics and logical calculations. Works at high speed - about 10000000 operations per second for simple mathematics formulas and a little more for logical formulas. http://www.delphi3000.com/articles/article_3374.asp * Safety Design with a Static Instance - by Max Kleiner How to build a real Singleton? http://www.delphi3000.com/articles/article_3376.asp * Interbase Backup on the Fly in a thread - by Kim Sandell Make interbase database backups on the fly, in a background thread. http://www.delphi3000.com/articles/article_3378.asp * Example of a Windows Service, with a thread - by Kim Sandell Delphi 5&6 has a template project for services, but it is incomplete. This example builds on that template and completes the service. It also shows how to start a thread that beeps every 2 seconds. You can use this as a base when developing servers as services. http://www.delphi3000.com/articles/article_3379.asp * Interbase Sweep on the Fly in a thread - by Kim Sandell In the Interbase Admin components there is a IBValidationService but is hard to use as it is. Sweeping is just one of the functions of the validation service. This component makes doing sweeps of databases alot easier, and also works in a thread. http://www.delphi3000.com/articles/article_3380.asp * How to write a TCP Redirector - by Kim Sandell Many people ask how to write servers in Indy, this primer goes through how a TCP server is created, and how to redirect all traffic to another remote server. This is the same as port-mapping in firewalls. http://www.delphi3000.com/articles/article_3381.asp * ENTER instead of TAB key - by Léo Souza A simple alternative to allow form navigation with the ENTER key. http://www.delphi3000.com/articles/article_3382.asp * How to create a animated (rotating) hourglass - Henk Schreij http://www.delphi3000.com/articles/article_3383.asp * How to implement an Array Property - Max Kleiner 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. http://www.delphi3000.com/articles/article_3387.asp * Change the color of a Listview or TreeView - by LExter LExter http://www.delphi3000.com/articles/article_3388.asp * SQL without bureaucracy - by Josir Gomes How to quickly run SQL without dropping components on your form. http://www.delphi3000.com/articles/article_3389.asp * Keyboard Hook - by William Egge Creating a keyboard hook in your application. http://www.delphi3000.com/articles/article_3390.asp * Converting a SID to a string - by Bryan Ashby Convert a Security Identifier (SID) into a human-readable string. http://www.delphi3000.com/articles/article_3391.asp * Changing Creation and Last accessed date/time for files - David Bolton http://www.delphi3000.com/articles/article_3392.asp * How to check if an OLE object is installed - by Mike Shkolnik http://www.delphi3000.com/articles/article_3394.asp * How to read contact list from MS Outlook - by Mike Shkolnik http://www.delphi3000.com/articles/article_3395.asp * Fast data transfer to MS Excel - by Mike Shkolnik http://www.delphi3000.com/articles/article_3396.asp * Multi Lingual BoolToStr() and SexToStr() - by Mike Heydon http://www.delphi3000.com/articles/article_3398.asp Tutorials ========= * Navigating and Editing a ClientDataSet - by Cary Jensen You navigate and edit a ClientDataSet in a manner similar to how you navigate and edit almost another other dataset. This article provides an introductory look at basic ClientDataSet navigation and editing. http://community.borland.com/article/0,1410,29122,00.html * Sophisticated Delphi Pascal techniques - by Zarko Gajic A Beginner’s Guide to Delphi Programming: Chapter 7. Time to extend your Delphi Pascal knowledge to the max. Here are some intermediate Delphi problems and articles for everyday development tasks. http://delphi.about.com/library/weekly/aa091702a.htm * Designing an XML Grammar with DTDs - by Philip Page XML is a great medium for data transfer and definition, but it must be consistent to make it consumable. Learn more about creating a DTD to determine consistent XML. http://builder.com.com/article.jhtml?id=u00320021004ppg01.htm * SQL Basics: Creating and Altering Tables - by Shelley Doll Learn the basic DDL commands to add, alter, and remove tables and databases with this article from the Builder.com SQL basics series. http://builder.com.com/article.jhtml?id=u00320020902dol01.htm * SQL Basics: Number Data Types - by Shelley Doll Failing to understand number data types poses a DBA's greatest risk of compromised data. The SQL92 standard dictates how database manufacturers define number behaviors, such as length and truncation. http://builder.com.com/article.jhtml?id=u00320020924dol01.htm * SQL Basics: String Data Types - by Shelley Doll Data type implementations vary from database to database but a working knowledge of the SQL specification will always give you a good idea of what's going on. This article breaks down the basic rules of deploying string data types. http://builder.com.com/article.jhtml?id=u00320020918dol01.htm * A Guide to Automatic Garbage Collection Systems - by Paul Tyma Java and .NET feature automatic garbage collection, which allows you to worry about programming instead of system cleanup. Learn more about the approaches often used to add this feature to applications. http://builder.com.com/article.jhtml?id=u00320020930gcn01.htm Other Links =========== * Delphi 7 Update 1 - by Anders Ohlsson This update for Delphi 7 contains an updated MSSQL driver that fixes the problem with empty user names and passwords. It also fixes an issue with extra NULL characters being added to VARCHAR columns. The update also contains several documentation updates. http://community.borland.com/article/0,1410,29209,00.html * Delphi 7/.NET User Group Tour 2002, US / Canada - by David Intersimone Borland is taking Delphi 7 and the Delphi Preview for Microsoft .Net on the road in the US and Canada. http://community.borland.com/article/0,1410,29089,00.html * The Delphi Bug List http://buglist.jrsoftware.org/ * Pascal and its Successors - by Niklaus Wirth Pascal was designed in 1969 in the spirit of Algol 60 with a concisely defined syntax representing the paradigm of structured programming. With the advent of the micro computer it became widely known and was adopted in many schools and universities. In 1979 it was followed by Modula-2 which catered for the needs of modular programming in teams. In an effort to reduce language complexity and to accommodate object- oriented programming, Oberon was designed in 1988. This article presents some aspects of the evolution of this family of languages. http://www.swissdelphicenter.ch/en/niklauswirth.php * Pascal IRC channel There is a very nice IRC channel about Pascal on DalNet (http://www.dal.net). The channel is just about Pascal (not Delphi). You can find it on irc.dal.net at #turbopascal. The channel also has a nice page with lots of sample codes. You can find their homepage at http://www.pastcow.org - As you can imagine, a channel for Delphi users can also be found there: #delphi. * BorCon Europe London UK, October 28-29 2002 http://www.borconeurope2002.com/ * BorCon Japan Tokyo Japan, November 19-20 2002 http://www.borland.co.jp/ * BorCon France Paris France, November 21-22 2002 http://info.borland.fr/conference/2002/ ________________________________________________________________________ YOU CAN HELP US We need your help to keep this newsletter going and growing. You can help by referring the newsletter to your colleagues: http://www.latiumsoftware.com/en/pascal/delphi-newsletter.php Or you can help by voting for us in some or all of these rankings to give more visibility to our web site and thus increase the number of subscriptions to this newsletter: http://www.sandbrooksoftware.com/cgi-bin/TopSite2/rankem.cgi?id=latium http://news.optimax.com/delphi/links/links.exe/click?id=70C517ECAE6E http://www.programmingpages.com/?r=latiumsoftwarecomenpascal http://www.top219.org/cgi-bin/vote.cgi?delphi&83 http://top100borland.com/in.php?who=20 http://top200.jazarsoft.com/delphi/rank.php3?id=latium http://213.65.224.200/cgi-bin/toplist.cgi/hits?Id=80 It's just a few seconds for you that REALLY mean a lot to us. Don't forget we also need articles for this newsletter and there is a prize for one of the authors in each issue. All articles will be considered but we are particularly interested in articles about Kylix because there is so little available online to help Kylix developers. Send articles to <eds2004 @ latiumsoftware.com>. We are also looking for shareware authors who would like to offer their components or applications as prizes for articles in the newsletter. In return you will be promoted in this newsletter and the Latium Software web site. For more information contact Dave <irongut @ vodafone.net>. ________________________________________________________________________ If you haven't received the full source code examples for this issue, you can get them from http://www.latiumsoftware.com/download/p0040.zip ________________________________________________________________________ This newsletter is provided "AS IS" without warranty of any kind. Its use implies the acceptance of our licensing terms and disclaimer of warranty you can read at http://www.latiumsoftware.com/en/legal.php where you will also find a note about legal trademarks. Articles are copyright of their respective authors and they are reproduced here with their permission. You can redistribute this newsletter as long as you do it in full (including copyright notices), without changes, and gratis. ________________________________________________________________________ Group home page: http://groups.yahoo.com/group/pascal-newsletter/ Subscribe/join: pascal-newsletter-subscribe@yahoogroups.com Unsubscribe/leave: pascal-newsletter-unsubscribe@yahoogroups.com Report spam/abuse: abuse@yahoogroups.com Problems with your subscription? eds2004 @ latiumsoftware.com ________________________________________________________________________ Latium Software http://www.latiumsoftware.com/en/index.php Copyright (c) 2002 by Ernesto De Spirito. All rights reserved. ________________________________________________________________________ |
The full source code examples of this issue are available for download.
![]() |
Errors? Omissions? Comments? Please contact us!






