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/dil/index.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.softpile.com/Development/Libraries/Review_24756_index.html
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
eds2008 @ 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-2006 & 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.
________________________________________________________________________
Support us! Vote for the Pascal Newsletter in The Programming Top 100!
http://top100borland.com/in.php?who=20
________________________________________________________________________
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 <eds2008 @ 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://tech.groups.yahoo.com/group/components/
Subscription:
http://tech.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://tech.groups.yahoo.com/group/software-developers/
Subscription:
http://tech.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://articles.techrepublic.com.com/5100-10878_11-1051673.html
* 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.com/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.programmingpages.com/?r=latiumsoftwarecomenpascal
http://top100borland.com/in.php?who=20
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 <eds2008 @ 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/en/file.php?id=p40
________________________________________________________________________
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? eds2008 @ latiumsoftware.com
________________________________________________________________________
Latium Software http://www.latiumsoftware.com/en/index.php
Copyright (c) 2002 by Ernesto De Spirito. All rights reserved.
________________________________________________________________________
|