Delphi Newsletter #1 (now Pascal Newsletter)
The full source code examples of this issue are available for download.
![]() |
![]() |
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 eds2004 @ 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.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. ________________________________________________________________________ If you haven't received the archive with the source code for this issue, you can get it from http://www.latiumsoftware.com/download/delphi-1.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. ________________________________________________________________________ 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? eds2004 @ latiumsoftware.com ________________________________________________________________________ Latium Software http://www.latiumsoftware.com/en/index.php Copyright (c) 2000 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!






