Delphi Newsletter #1
INDEX
1. A FEW WORDS FROM THE EDITOR
2. THREADS
- Introduction
- TThread class
- TThread example
3. EXECUTION OF AN EXTERNAL APPLICATION
4. WHAT'S NEXT?
________________________________________________________________________
1. A FEW WORDS FROM THE EDITOR
I'm pleased to announce that last week we had a great success when we
started our Kylix Newsletter, and now we hope the Delphi Newsletter will
follow the same path.
This newsletter is a nonofficial publication and is intended for
intermediate Delphi programmers. We assume that the reader is already
somewhat familiar with the VCL, the Object Pascal language and the
Delphi IDE, so we can go directly to those tasks a programmer often
needs to perform and for which the information contained in the
documentation that accompanies Delphi is scarce (or even nonexistent!).
This first edition was scheduled to appear by mid June, but since it
generated so much expectation, we decided to make an effort and to
accede to the demands of our subscribers and release it much earlier
than planned. We hope you enjoy it.
This newsletter is copyrighted, but it is very important for us to keep
it growing in audience and contents to keep it going, so please feel
free to forward it to friends, acquaintances and colleagues that you
know might be interested in this publication as long as you send it in
full and without modifications.
In this issue we will show you how to write a multithreaded application.
This article responds to a quite common programming task, but we are
aware that it is too technical, and so we tried to write it as
explicative as possible, hoping beginner programmers can at least
understand the basics of multi-threading. If you have problems, doubts
or questions about the example presented here, please email us. We
will be happy to help you all we can.
To compensate, we will also address in this issue a less difficult
subject, as it is the execution of other applications from ours.
We are very happy with this first issue and we anxiously expect all your
feedback. We would like to know your comments and opinions about this
newsletter so we can improve it. We would also like to know what your
programming needs are so we can see if we can cover them in the
following issues. Please stay in touch!
Regards,
Ernesto De Spirito
eds2008 @ latiumsoftware.com
________________________________________________________________________
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-6 and C++ Builder 3-5. http://www.jfactivesoft.com/
________________________________________________________________________
2. THREADS
Introduction
============
In Windows 32-bit, you can have many programs running at the same time.
For example, you can print a document and simultaneously check your hard
disk for viruses with an antivirus program and meanwhile, to kill time,
you can play solitaire, check your mail, or perform any other task.
However, since the microprocessor (CPU) can perform only one operation
at a time, how is it possible to run several programs at the same time?
Well, first, when a program is loaded into memory to be run, it is
called a "process", and second, if your computer has only one CPU, then
only one process can actually be running at a given time.
How is it then that it looks like many processes are running
simultaneously? This is the right question. It "looks like" many
processes are running at the same time thanks to Windows multi-tasking
capabilities.
How does this multitasking work? Well, let's suppose we have five
processes (A, B, C, D and E). The OS (Operating System) gives a very
short amount of time to one process (A for instance) and when that time
is over, the process is temporarily stopped and the OS gives some amount
of time to another process (like B, for instance), and so on with the
rest of the processes (C, D and E in our example), until it is time to
give time to the first process again (A in our example) again, and the
whole cycle gets repeated again and again. So, in the end, processes
run a little while, stop to allow other processes to run, then run
another while, stop, and so on until they terminate, giving the
sensation of continuos non-stop runs, in almost the same way a movie is
no more than just a series of still frames that change so fast that we
perceive the illusion of continuos movement. In this way, it looks like
many processes are running at the same time.
Now, Windows multitasking capabilities are not limited to processes, but
within one process it is also possible to have parts of it running "at
the same time", i.e. to have multiple execution threads. Normally we use
only one thread (the main VCL thread) but we can create and run other
threads within our application. In this case your application is said to
be multi-threaded.
When would I need to create other threads? When you want your
application to do more than one thing at the same time, of course, being
the typical case when you need to perform a time-consuming task, like
searching for a file in your hard disk for example, and yet you want
your application to still be able to respond to events so the user can
stop the task before it completes and/or do other things with your
application.
TThread class
=============
The TThread class (declared in the Classes unit) encapsulates the
Windows API calls needed to create and execute different threads. This
class has an abstract method (named Execute), so it is an abstract
class, and therefore you cannot instanciate it (i.e. create objects of
this class). What you have to do is create a descendant of TThread (for
example TThread1) and override the Execute method. This Execute method
is the one that will run in a different thread.
To actually execute the thread, first you must declare an object of this
class (for example Thread1) and then construct it:
Thread1 := TThread1.Create(False);
The parameter for the constructor is the Suspended property. If it is
False, Execute is called right after the object is created. This call
will be done in asynchronous mode, that is, the Execute method will run
in a new execution thread and the main VCL thread will also continue
executing, meaning the application will not stop waiting until Execute
finishes, but will continue executing "at the same time" as Execute.
Normally a thread is created suspended (passing True to the constructor
call), so first you can adjust other properties like Priority and
FreeOnTerminate and then set Suspended to False to start executing the
thread (the Execute method will be called asynchronously).
Threads with higher priority get more CPU time than the others, and
although that can improve the performance of the thread, it might slow
down other threads in your application, so you should be careful when
establishing the value of the Priority property.
The FreeOnTerminate property determines whether the thread object is
destroyed upon completion of the Execute procedure (freeing as from that
task), and if so, the Destroy method will be called (and we can use for
example to notify the main thread that this thread has finished).
To abort the execution of the thread, you must set the Terminated
property to True or call the Terminate method. This does not terminate
the thread immediately. Supposedly your Execute method should constantly
check the value of the Terminated property to see if has been requested
to finish, and if so, clean up and exit the procedure.
TThread example
===============
In this example, we are going to use a thread as a property of a form.
The form will have a start button (to start the thread), a cancel button
(to abort the execution of the thread) and a progress bar to display the
progress of the execution of the thread, that will consist solely of a
for loop.
To try this example first create a Form and add to it a progress bar and
two buttons. In the Object Inspector, set the following properties of
these objects:
ProgressBar1
Max := 100000
Step := 1
Smooth := True
Button1
Caption := 'Start'
Button2
Cancel := True
Caption := 'Stop'
Enabled := False
The stop button is disabled because the thread is not running when the
form is created. When we make it run (in the Click event of Button1) we
will enable this button (so the user can stop the thread) and disable
the start button (because the thread will already be running). When the
thread finishes executing, we should re-enable the start button and
disable the stop button, but how do we know when the thread finishes?
One way to know is to set FreeOnTerminate to True before executing the
thread, so when it finishes executing, the thread object will be
automatically freed and in its Destroy method we can send a message to
the thread's owner form and capture it in the form.
Now, let's get to the code. Before the type declarations we will declare
a constant to identify the message that will be posted from the thread
to the form:
const
WM_ThreadDoneMsg = WM_User + 8;
WM_User is a predefined constant indicating the minimum value for user-
defined messages. Message values below WM_User are reserved for Windows.
Eight (8) is an arbitrary value.
Inside the type declarations, before the declaration of the form, we
will declare the thread, as follows:
TThread1 = class(TThread)
private
OwnerHandle: HWND; // Window handle of the owner form
ProgressBar1: TProgressBar; // Reference to the progress bar
procedure UpdateProgressBar; // Update ProgressBar (synchronized)
protected
procedure Execute; override; // Threaded code
published
constructor Create(Owner: TForm; ProgressBar: TProgressBar);
destructor Destroy; override;
end;
This deserves some explanations:
1) We need the window handle of the thread's owner form to be able to
post the WM_ThreadDoneMsg message to that form and that's why we declare
the OwnerHandle property (of type HWND: a window handle) and we override
the Destroy method (because we need to post that message before actually
destroying the object).
2) Since we will access the progress bar of the owner form from within
the thread, we use a reference to that component. Another possibility
would have been directly to have a reference to the form of type TForm1
(which would have forced was to declare the class as a forward) or as
type TForm (which would have forced us to typecast the form as TForm1
where we wanted to access the progress bar), so we considered the
solution we adopted to be the simplest.
3) The UpdateProgressBar method will be called from the Execute method
to update the progress bar (it will only step it). Why not stepping the
progress bar directly from within the Execute method? To avoid multi-
threading conflicts (two threads accessing the same memory/resources
could potentially introduce bugs in your application). This
UpdateProgressBar method will be called using the Synchronize method of
TThread, so it executes in the main VCL thread, thus not conflicting
with it.
4) We override the Execute method. A must, if we want to instantiate the
TThread1 class.
5) We declare our own constructor, with a reference to the thread's
owner form and the progress bar as parameters.
Now that we are done declaring the TThread1 class, in the private
section of TForm1 let's add the following declarations:
private
Thread1: TThread1;
procedure Thread1Done(var AMessage: TMessage);
message WM_ThreadDoneMsg;
Here, Thread1 is an object (the thread) of the TThread1 class and
Thread1Done is a kind of event that will be executed when the form
receives the WM_ThreadDoneMsg message. Message methods always receive a
reference to a TMessage object as a parameter. In this example we will
not use it since the simple reception of the message is all that matters
to us.
In the Object Inspector let's generate the OnClick events for the two
buttons and the form's OnClose event, and use the following code:
procedure TForm1.Button1Click(Sender: TObject);
begin
Button1.Enabled := False;
Button2.Enabled := True;
// Create and start Thread1
Thread1 := TThread1.Create(Self, ProgressBar1);
// The execution of the application continues here and "at the
// same time" the Execute method of Thread1 is also running.
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Thread1.Terminate; // Terminate the thread
// The execution of the application continues here since
// Terminate doesn't wait for Execute to finish.
end;
procedure TForm1.FormClose(Sender: TObject;
var Action: TCloseAction);
begin
if Button2.Enabled then begin // If thread is running
Thread1.Terminate; // Terminate
Thread1.WaitFor; // Wait for it to finish
end;//if
Action := caFree; // Free form after closing it
end;
Now add the following code and we are done.
procedure TForm1.Thread1Done(var AMessage: TMessage);
// Captures message sent by the thread signaling it has
// finished executing
begin
Button1.Enabled := True;
Button2.Enabled := False;
end;
constructor TThread1.Create(Owner: TForm;
ProgressBar: TProgressBar);
begin
inherited Create(True); // Create thread suspended
OwnerHandle := Owner.Handle; // Window handle of owner form
ProgressBar1 := ProgressBar; // Reference to the progress bar
ProgressBar1.Position := 0; // Initialize the progress bar
Priority := tpNormal; // Set Priority Level
FreeOnTerminate := True; // Thread will free itself
Suspended := False; // Start the thread
// ==> Execute gets called
// The execution of the application continues here and "at the
// same time" the Execute method of Thread1 is also running.
end;
destructor TThread1.Destroy;
// Called when Execute finished because FreeOnTerminate is True
begin
// Posts a message to the form telling the thread is finished.
PostMessage(OwnerHandle, WM_ThreadDoneMsg, Self.ThreadID, 0);
inherited Destroy; // Calls Destroy method of the ancestor
end;
procedure TThread1.Execute; // Main execution of the thread
var i: integer;
begin
for i := 1 to 100000 do begin
Synchronize(UpdateProgressBar); // Updates the progress bar
// in the main VCL thread
if Terminated then break; // Exits the for loop if
// thread is aborted
end;//for
end;
procedure TThread1.UpdateProgressBar;
// This procedure is called synchronized from the Execute method
// so it runs in the main VCL thread, avoiding potential multi-
// threading conflicts.
begin
ProgressBar1.StepIt; // Advances the progress bar
end;
________________________________________________________________________
3. EXECUTION OF AN EXTERNAL APPLICATION
Many times we need to execute another program from ours. To do this, we
can use the ShellExecute function declared in the ShellAPI unit. The
syntax is:
ShellExecute(Handle, Operation, FileName, Parameters,
Folder, ShowCmd)
Handle (HWND) is the window handle of the parent window, for instance
the window handle of the main form of our application.
Operation (PChar) is a pointer to a null terminated string that contains
the name of the operation to perform, which can be "open", "print" or
"explore" (explore folder). This parameter can be Nil and in this case
"open" will be assumed.
FileName (PChar) is a pointer to a null terminated string that contains
the path and the name of the application to execute, the document to
open or print with its associated application, or the folder to open or
explore.
Parameters (PChar) is a pointer to a null terminated string that
contains the parameters that will be passed to application specified in
FileName. If FileName doesn't indicate an executable file but a
document, then Parameters should be Nil.
Folder (PChar) is a pointer to a null terminated string that contains
the path that will be taken as the application's folder by default. This
parameter can be Nil.
ShowCmd (Integer) indicates the way the application specified in
FileName will be shown. There are various possible values:
SW_HIDE SW_SHOWMAXIMIZED
SW_MAXIMIZE SW_SHOWMINIMIZED
SW_MINIMIZE SW_SHOWMINNOACTIVE
SW_RESTORE SW_SHOWNA
SW_SHOW SW_SHOWNOACTIVATE
SW_SHOWDEFAULT SW_SHOWNORMAL
The documentation states that if FileName refers to a document, ShowCmd
should be 0, but if you try other values you will see they work.
Return value:
If ShellExecute succeeds, it returns a value of type HINST (a LongWord)
with the instance handle of the application that was run, or the handle
of a dynamic data exchange (DDE) server application. If it fails, it
returns an error code from 0 to 32.
Now let's see some examples:
if ShellExecute(Form1.Handle, nil, 'c:\windows\general.txt', nil, nil,
0) <= 32 then
Application.MessageBox('Couldn''t execute the application',
'Error', MB_ICONEXCLAMATION);
ShellExecute(Form1.Handle, nil, 'c:\windows\notepad.exe',
'c:\windows\general.txt', nil, SW_SHOWMAXIMIZE);
ShellExecute(Form1.Handle, 'open', 'c:\windows\notepad.exe',
'general.txt', 'c:\windows', SW_SHOWNORMAL);
ShellExecute(Form1.Handle, nil, PChar(fname + '.txt'), nil, nil,
SW_MAXIMIZE);
ShellExecute(Form1.Handle, nil, 'c:\windows\notepad.exe', nil, nil,
SW_SHOWNORMAL);
Note:
The applications called by ShellExecute will run asynchronously, that
is, the execution of our application continues without waiting the end
of the application called by ShellExecute. If we want an application to
execute synchronously, we shouldn't use ShellExecute, but a code like
the following:
procedure TForm1.Button1Click(Sender: TObject);
var
proc_info: TProcessInformation;
startinfo: TStartupInfo;
ExitCode: longword;
begin
// Initialize the structures
FillChar(proc_info, sizeof(TProcessInformation), 0);
FillChar(startinfo, sizeof(TStartupInfo), 0);
startinfo.cb := sizeof(TStartupInfo);
// Attempts to create the process
if CreateProcess('c:\windows\notepad.exe', nil, nil, nil,
false, NORMAL_PRIORITY_CLASS, nil, nil, startinfo,
proc_info) <> False then begin
// The process has been successfully created
// No let's wait till it ends...
WaitForSingleObject(proc_info.hProcess, INFINITE);
// The process has finished. Now we should close it.
GetExitCodeProcess(proc_info.hProcess, ExitCode); // Optional
CloseHandle(proc_info.hThread);
CloseHandle(proc_info.hProcess);
Application.MessageBox(
PChar(Format('Notepad finished! (Exit code=%d)', [ExitCode])),
'Info', MB_ICONINFORMATION);
end else begin
// Failure creating the process
Application.MessageBox('Couldn''t execute the application',
'Error', MB_ICONEXCLAMATION);
end;//if
end;
To open a file with the application, use a code like the following:
if CreateProcess(nil,
'"c:\windows\notepad.exe" "c:\windows\general.txt"', ...
The double quotes are only needed when the path name of the application
or the document contain spaces.
The CreateProcess function only works with executable files, i.e. it
doesn't open documents with the associated application. We will see the
solution to this problem in the next issue.
________________________________________________________________________
4. WHAT'S NEXT?
In the next issue we will try to answer all the questions of our readers
regarding the subjects addressed in this issue, and we will try to do
something more useful with our Execute method, like searching for a
keyword in a set of files, and we will also see the use of the Windows
Registry to open a document with its associated application and wait
till it ends.
See you then!
________________________________________________________________________
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.
________________________________________________________________________
If you haven't received the archive with the source code for this issue,
you can get it from http://www.latiumsoftware.com/en/file.php?id=d01
________________________________________________________________________
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.
________________________________________________________________________
Main page: http://www.latiumsoftware.com/en/pascal/delphi-newsletter.php
Group home page: http://groups.yahoo.com/group/pascal-newsletter/
Subscribe/join: pascal-newsletter-subscribe@yahoogroups.com
Unsubscribe/leave: pascal-newsletter-unsubscribe@yahoogroups.com
Problems with your subscription? eds2008 @ latiumsoftware.com
________________________________________________________________________
Latium Software http://www.latiumsoftware.com/en/index.php
Copyright (c) 2000 by Ernesto De Spirito. All rights reserved.
________________________________________________________________________
|