Document :: Making Use of Threads
Author :: Jon Jenkinson
From “The Bits...” the c++ Builder Information & Tutorial Site
http://www.cbuilder.dthomas.co.uk
Contact :
Main Site : forgot@mcmail.com
Newspages : bitsnews@mcmail.com
Comments : comments@mcmail.com
Editors:
Components : Alan D. Mills, millsad@tgis.co.uk
Tutorials : Kris Erickson, kson@istar.ca
Others bits: Jon Jenkinson, jon.jenkinson@mcmail.com
Legal Stuff
This document is Copyright © Jon Jenkinson, December 1997
The PDF distribution release of this document is Copyright ©"The Bits...”, December 1997
You Can,
Redistribute this file FREE OF CHARGE providing you do not charge recipients anything
for the
process of redistribution, or for the document itself.
You Can,
Use this document for educational purposes.
You Must Gain Permission, *Initial contact forgot@mcmail.com*
If you choose to redistribute this document in any way, (book/CD/Magazine/Web Site
etc.). (note, this is to avoid any possible conflict with commercial work the author may
be undertaking, though it is unlikely permission will be refused).
You Must Gain Permission, *Initial contact forgot@mcmail.com*
If you wish to use all or part of this document for commercial training purposes.
You Cannot,
Under any circumstances alter this document in any way without the express permission
of the author or “The Bits...”.
You Cannot,
Receive **any** monies for this document without the prior consent of the author.
You Cannot,
Take any of the ideas presented here and produce a product for distribution without the
express consent of the author.
In plain words, if you wish to do anything other than read and print this document for you own
individual use, let us know so we can track what’s going on:)
No liability is accepted by the author(s) for anything which may occur whilst following
this tutorial
This project has been built and tested under Win95, (4.00.950a not OEMR2), and using
CBuilder Standard, (both patches). It has not been tested under later releases of Win95,
NT nor different versions of CBuilder, and whilst no side effects should occur, follow
this at your own risk.
Making Use Of Threads
This document has come about after a little “quick” project I wrote to do a simple but time
consuming job for myself. Here we will write a, (very simple), duplicate file finder, which will
be multi-threaded to enable more than one check to be made at one time. Once you’ve gone
through this there will be loads of enhancements you can think of, but I stopped when it did what
it needed to do.
The major thing we’ll look at here is making an advanced use of threads to carry out all the
actual work. The reason for doing this is that the process is a long one. We’re doing a full file
comparison, not just a check of the filename/date/size, which takes time. (In fact, I don’t even
bother checking the file sizes!). Before reading this, check out the document on Threads
available from the site. That document gives you an overview of what a thread is, and how it
works, but this one will show you how to make a thread do some real work to make your
applications more effective.
Colour Coding:
Information displayed in this colour is information provided by my copy of BCB.
Information displayed in this colour is information you need to type.
Information displayed in this colour is information which is of general interest.
********************************************************************************
Whilst not required, I use TSlidePanel available from the sites component sections to
create splitter windows for the display within this project. If you have your own
component or method for doing this use that, or, you don’t even have to split the
windows at all.
********************************************************************************
Underlying Principals
As mentioned above, I’m expecting you know what a thread is, and similarly how to use
CBuilder. Once you’ve read through this, you’re unlikely to learn much more than the Threads
tutorial above, but you will see how to use threads in a real application, and some of the things
that entails. Also to track the threads we’ll be using a TList object, and that wasn’t as easy as I
first thought :)
How to Make a threaded Duplicate file finder.
Before diving in, a bit of product design. The problem this little application solves is as follows.
Having four hard drives, a Zip drive, and innumerable floppies, it becomes very easy to have
duplicate files littered all over the place. This isn’t normally too much of a problem, they have
the same name, size, etc., but then you get a fool like me who has a penchant for renaming files
when backing them up to other local drives. Similarly, when searching out components or
information from the web, I download lots of files at once, frequently all on the same subject.
Web site curators are another lot who love to rename files to fit their own structure, for instance
this document may end up on your own drives as it’s original “thdupli.pdf” as well as having been
renamed by another curator to “athreads.pdf”.
In all the above cases, you don’t wish to manually search through each file checking for
duplicates, you just wish to remove those duplicates. That is what this program does. It ignores
file names and sizes, (except for one particular case), and does an exact match on each file
presented it. Then a list is produced of those files which it believes are identical, (though this
could be easily produced as a list for deletion, or simply deleted anyway.
Now the special thing here is the file matching, which isn’t hard, just takes a great deal of time.
The commercial software tend to lend themselves to matching filenames/sizes/dates etc., but
from my own experience frequently produce erroneous results, (especially when checking down
lists of readme files:). So this is a simple tool to check the files exactly. (*Note, the source code
for this application is being put into a Global Project, so if you see an enhancement, follow that
project to create the greatest file cleaner of all time*).
Step One : Create The Application Windows.
My layout led me to needing two application windows, the first for choosing the files and
directories to search, and the second for displaying the results produced.
First, the application window, (note, uses TSlidePanel, use your own method if you prefer).
(a) Create an new application with File|New, and set the forms caption = Dupli
(b) drop a TPanel on the form, Panel1, set properties, Align = alTop, Alignment = taLeftJustify,
both bevels to bvNone, and Caption = Dupli.
(c) drop another TPanel on the form, Panel2, set properties, Align = alRight, Alignment =
taCenter, both bevels to bvNone
(d) another TPanel, Align = alClient, BevelOuter = bvLowered
(e) drop a TSlidePanel on the form, Align = alLeft, BevelOuter = bvLowered, HandlePosition =
hpRight, SlideType = stLine
(f) drop another TSlidePanel on the first one, Align = alBottom, BevelOuter = bvRaised,
HandlePosition = hpTop, SlideType = stLine
(g) Place a TPanel, (Panel4), to the right of the first SlidePanel, Align = alClient, BevelOuter =
bvNone
(h) and another TSlidePanel onto Panel4, Align=alBottom, BevelOuter = bvRaised,
HandlePosition = hpTop, SlideType = stLine.
Okay, just to check, you have a panel at the top, one to the right, a pair of slidepanels on the
left, and one on the right. (Check the screenshot at the end of the next step).
Step Two : Adding the Bits.
Now we’ll add the components which will make up the rest of our main window, in effect two
drive explorers and some buttons. (Again check the screen shot below:)
(a) Onto Panel1, drop the following, two TDriveComboBox’s, Named, DC1 & DC2, two TEdit’s
named fil1 & fil2.
(b) Place a TDirectoryListBox on the SlidePanel on the left, named DL1, Align = alClient
(c) Place another TDirectoryListBox onto Panel4 on the right, named DL2, Align = alClient
(d) Place a TFileListBox on the second SlidePanel on the left, named fl1, Align = alClient
(e) Place a TFileListBox on the final SlidePanel, named fl2, Align = alClient, Mask = *.*
(f) Link the boxes together, DC1:DirList = DL1, DC2:DirList = DL2, DL1:FileList = fl1,
DL2:FileList = fl2
(g) Place three TBitBtn’s on the panel right hand panel, named GoBtn, StopBtn, ClearBtn. For
the GoBtn, set (in this order), Kind = bkAll, ModalResult = mrNone, Caption = &Go. For the
StopBtn, Kind = bkNo, ModalResult = mrNone, Caption = &Stop. For the ClearBtn, Kind =
bkCancel, ModalResult = mrNone, Caption = &Clear. (This basically gives us some quick
glyphs on our buttons:).
Your application window should look something like this,
The layout doesn’t matter too much, this one simply suits my purposes.
Step Three : Adding the other modules.
Before saving our project, lets add our other units.
(a) Create a new form, File|New Form
(b) Create a new Thread, File|New, choose Thread Object from the list, and give it the class
name JSearch.
Now lets save our project with File|Save Project As, choose your location, save unit1 & unit2,
but name the threads file as JSearch.
Step Four : Form2.
Here we’ll fill out form2 with our output display, and also enable the user to save the output.
(a) Drop a TPanel on Form2, Align = alBottom
(b) Drop another TPanel on the form, Align = alClient
(c) Place a TSaveDialog on the form, (it doesn’t matter where), Name = SD.
(d) Place a TRichEdit on each panel, Align = alClient, WordWrap = false, ScrollBars = ssBoth
for each of them, name the one at the bottom of the form r2, and the one at the top of the
form r1.
(e) Place a TMainMenu onto the form, (again it doesn’t matter where), Name MainMenu1.
(f) Double Click the TMainMenu on your form, and create the following Menu display,
(g) Name Duplicate List, SaveList
(h) Name Debug Window, SaveDebug
(note, I’ve pinched this window from part of the larger project I’m working on, so ignore the
grayed about)
(i) Double click the Duplicate List and add the following into it’s OnClick handler,
//---------------------------------------------------------------------------
void __fastcall TForm2::SaveListClick(TObject *Sender)
{
if(SD->Execute())
{
r1->Lines->SaveToFile(SD->FileName);
}
}
//---------------------------------------------------------------------------
(j) Double click the Debug Window, and add the following into it’s OnClick handler
//---------------------------------------------------------------------------
void __fastcall TForm2::SaveDebugClick(TObject *Sender)
{
if(SD->Execute())
{
r2->Lines->SaveToFile(SD->FileName);
}
}
//---------------------------------------------------------------------------
You’ve now finished with Form2:)
Step Five : The Buttons.
Honest, we get to the interesting stuff in a moment, but for now lets make our buttons do
something. Well actually, here we do something interesting. We’re going to use a TList class to
track our threads. This is needed because we’re going to enable the user to fire threads off
whenever they want, and we’re not going to limit in any way the number of searches they can
start. In practice, most (old:P120, 32meg) PCs like mine will start becoming unresponsive once
we’ve got 8 or 9 threads going, but I’ve no idea what will happen on the newer and faster
machines.
So what is a TList. It’s a container for pointers, simple:). To enable the TList we need to create
an instance of one, so into Unit1.h, place the following two lines in red, (the Counter is
something we’ll come to later.
class TForm1 : public TForm
{
__published:
// IDE-managed Components
//<snipped for simplicity sake:)>
private:
// User declarations
TList *ThreadList;
public:
// User declarations
__fastcall TForm1(TComponent* Owner);
int Counter;
};
//---------------------------------------------------------------------------
The first thing to notice is that we’ve placed our TList within the private section of our class’
declaration. We don’t want to be able to access this List from outside our class, so it is good
practice to keep something so important private.
And it is important. This list is going to track all our threads, and whilst we’ll allow our thread to
terminate itself, and we’ll allow our application to terminate, (which consequently terminates all
threads), we also wish to be able to terminate all the threads ourselves, and thus we need an
accurate record of which threads are running.
What our list will do is provide us with control over our threads. When we launch a thread, we
will record a pointer to that thread within our list, and whilst we don’t care if the thread finishes
itself, or the user terminates, we will come to a point, (our stop button), when the threads need
to be terminated. We pack our list, (remove all null pointers), and then delete(terminate) our
threads.
(And here is a question for the Geni out there. The logic I’ve used for this application whilst
perfect, is flawed. It is possible that when we’re scrolling through our list terminating our
threads, that a thread will finish itself, and thus present us with a null pointer. Whilst highly
unlikely, it can happen, though it hasn’t happened to me yet ! Solutions ?:)
(a) Okay, first of all let’s allocate our list. In Form1’s OnCreate handler, the following.
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
//Create the TList to hold pointers to the threads;
ThreadList = new TList;
Counter = 0;
Panel2->Caption = Counter;
}
//---------------------------------------------------------------------------
(b) In the OnClose handler,
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
delete ThreadList;
}
//---------------------------------------------------------------------------
What we’ve done here is allocate and de-allocate our list. The beauty of TList is that you don’t
have to worry about pointer sizes or anything else too complicated, just let it manage itself.
(If
you’re interested, the help describes TList badly, how it auto allocates memory in blocks etc.,
but for our purposes, and most that I can imagine, you can let TList take care of such things
itself)
.
The other two lines in our OnCreate function are simply to set up and display our counter.
(c) The Clear Button, will clear our output windows, and our counter, so the following code in
ClearBut’s on click handler
//---------------------------------------------------------------------------
void __fastcall TForm1::ClearBtnClick(TObject *Sender)
{
Form2->r1->Lines->Clear();
Form2->r2->Lines->Clear();
Counter = 0;
Panel2->Caption = Counter;
}
//---------------------------------------------------------------------------
Note here that hitting clear will not stop any running threads. Depending on how our use wishes
to use our application, they can go through the richedit’s and cut paste, delete and so on. They
may choose to delete (clear), whilst the thread is running, which is what we allow them to do.
(If you wish to stop the threads with a hit of the clear button, put a call into the stop button click
handler, i.e., StopBtnClick(Sender);)
(d) The Go button. This button will create a thread, and our list will record this thread. There
are two ways of starting threads, and both can be enabled here. Put the following code into
the Go Buttons OnClick handler.
//---------------------------------------------------------------------------
void __fastcall TForm1::GoBtnClick(TObject *Sender)
{
//fire off the threads to do the searching
//JSearch *NewThread;
fl1->ItemIndex = 0;
fl2->ItemIndex = 0;
Form2->Show();
//add our thread pointer to our list
ThreadList->Add(new JSearch(false));
//the above fires it off and let's it run:)
//do the following if choose to start suspended
//ThreadList->Add(new JSearch(true));
//NewThread = reinterpret_cast<JSearch *> (ThreadList->Items[ThreadList->Count-1]);
//NewThread->Resume();
}
//---------------------------------------------------------------------------
Okay, there’s a lot there:). I’ve chosen to fire my thread directly, (that is not to create it in a
suspended manner). When we look at our thread later, you’ll see that everything we need to
start it is put in place by the thread, so we shouldn’t need to pause to allow something else to
catch up. It is however considered good practice to create a thread in suspended mode and then
only allow it to run when everything is in place, which is why I’ve presented both methods here.
However, as neither we, nor the application has anything else to do between our threads
creation and our threads run, the lines above which are commented out are redundant, so I’ve
not bothered with them.
When we enter this function, we make the output form visible, (it doesn’t matter if it’s already
on show),
and then launch our thread, and add it to our list. The line, ThreadList->Add(new
JSearch(false)); does this in one go. If you’ve read the Threads tutorial, you’ll know that to
launch a thread, you declare a pointer of the correct type, and allocate the pointer with a call to
the thread’s constructor. In effect, the above line could have been typed in the following
manner,
JSearch *MyPointer;
MyPointer = new JSearch(false);
ThreadList->Add(<JSearch *> MyPointer);
which is actually a lot more complicated. Remember that a TList keeps track of pointers, so it
expects a pointer when you do an add. Thus, the single line ThreadList->Add(new
JSearch(false)); does the following,
ThreadList->Add(........) creates our pointer, and the ...new JSearch(false)... returns a pointer to
our thread, and allocates all required for our threads constructor.
A small thing to note here. If our thread fails to create for any reason, the pointer it returns is
NULL, which we store regardless. This is not a problem for us, for when we reference the list
we remove NULL pointers first, but it’s something to be aware of.
(For information. If we had chosen the create suspended mode for our thread, it becomes a
little more complicated. Create a pointer of the type we want, JSearch *NewThread; We add
our pointer to the list in the same manner as above, with the call to, ThreadList->Add(new
JSearch(true)); which creates the pointer, stores it in our list, allocates and creates our thread,
but suspends it. We then need to resume our thread, which requires us to access the pointer.
This is done with the following line, NewThread = reinterpret_cast<JSearch *> (ThreadList-
>Items[ThreadList->Count-1]); which brings our pointer back from the list, and casts it back to
the type we expect. Note the use of the TList’s Count function, we’ve just added a thread, so
the count goes up, but it’s a 0 based list, so we need count-1. Finally we resume our thread.
NewThread->Resume();
Now then, taking the argument of the single line allocation, the above four lines and expanding
them, would become the following four lines
JSearch *NewThread;
NewThread = new JSearch(true);
ThreadList->Add(<JSearch *> NewThread);
NewThread->Resume();
which is probably easier. The method you choose is really dependant on whether you need to do
anything with your thread before you let it start, and is best left to the individual cases. I’ve
included all the options so at least you know how it can be done, though in our case as we don’t
need to do anything, we can do it all in one line:)
Okay, now this will actually create and run a thread which doesn’t do anything, (we will put the
thread code in place soon:). The next thing we need to look at is the forced termination of our
threads, the StopBtn.
(e) Place the following code in the stop buttons OnClick handler.
//---------------------------------------------------------------------------
void __fastcall TForm1::StopBtnClick(TObject *Sender)
{
int x;
JSearch *TempPtr;
//Terminate the thread(s)
ThreadList->Pack();
//remove empty pointers
if(ThreadList->Count != 0)
{
for(x = 0; x < ThreadList->Count; x++)
{
TempPtr = reinterpret_cast<JSearch *>(ThreadList->Items[x]);
TempPtr->Terminate();
}
TempPtr->Terminate();
}
}
//---------------------------------------------------------------------------
(for those who are questioning the for loop above, I know there is a flaw in the above for loop,
but I’m damned if I can mend it, however the above works flawlessly:)
Here we run through our list and terminate the threads that are running. The first thing we do is
call TList’s Pack function, which as mentioned earlier removes all NULL pointers from the list.
This means that if a thread has finished running since the go button was hit, it’s space in our list
is removed by this call. We then check to see if there are actually any threads left running by
checking the count.
(A couple of things to note here. Our TList has two things of interest. It has a capacity, and a
count. The capacity is best left to the TList to handle, but in effect, we could have a list
capacity of 8 here, and a count of 0. However, before the pack, we could have a capacity of 8,
with a single pointer to a thread at position 8, that is seven null pointers before the pointer we’re
interested in. What pack actually does is remove all the null pointers and “compress” the list so
that 0 to count produces valid pointers of the type we want. Pack also reallocates the list
capacity, but we really don’t need to know that, we’re only interested in the “real” pointers that
are there.
The second thing to note is that I’ve made no attempt of checking the pointer type returned. In
our use of TList, we’re assuming that all the items stored in the list are going to be of type
JSearch, (and it should only contain such pointers as that’s all we ever send it). However, a
TList can store pointers of any type, in any order. You could for instance have a TList which
contained the following pointers,
Items[0] = int *
Items[1] = char *
Items[2] = JSearch *
Items[3] = Null
Items[4] = JSearch *
Now our call to pack will remove our NULL, but what to do if you have mixed pointers. Well,
that’s your problem, because it is my opinion that you’re asking for trouble if you do such a
thing. In such a case simply maintain lists for each pointer type, as if you were maintaining a
keyed database table. That is when you remove an item from one list, you remove it from
another list.
Finally, as mentioned earlier, there is no check here on what to do if a thread terminates whilst
we’re in this for loop. It is possible, though I haven’t managed it yet. My gut reaction is that
prior to our thread terminating, we call a function which searches our list for it’s own pointer and
removes it, but that seems to me like taking a chainsaw to snap a match, and also brings in a
problem of what to do if you remove the pointer from the list, again during the loop. To me, this
would bring in the possibility of trying to terminate a thread which is in the process of trying to
terminate itself by removing the pointer you’re using. Hmmm....note that this is highly unlikely,
but be aware of the possible problem.)
(f) And before we write our thread code, the following two handlers for our display.
//---------------------------------------------------------------------------
void __fastcall TForm1::fil1Exit(TObject *Sender)
{
fl1->Mask = fil1->Text;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::fil2Exit(TObject *Sender)
{
fl2->Mask = fil2->Text;
}
//---------------------------------------------------------------------------
These basically update the filelists with the chosen mask(s) supplied by the user when they exit
the edit boxes.
(Note on masks, “*.jpg”, is valid, as is “*.jpg;*.gif”, so they can specify multiple
wildcards:)
Step Six : The Search Thread.
So far we haven’t run the application, and the reason is simple, it wouldn’t do anything if we did.
The Search Thread, JSearch, exists, but currently does absolutely nothing, other than start
and stop almost instantly. Now we’ll put some meat into our thread.
One of the many problems in comparing files is that it is in effect a single process. You compare
file 1 with file 2 and file.... until you’ve compared them all. In a single thread application it can
be difficult to maintain control of your application, you effectively need to wait for the search to
finish, or check a flag which may, or may not be accessible. The second major difficulty is that
you can only run one search at a time. Within a thread, this is all much easier. With a little
foresight you can use the same thread to do all the searches you need, and you can control the
threads from your main application thread. In other words, no matter what happens in your
thread, your application is always accessible.
(During the development of this thread I frequently made logic errors, some of which brought
down Windows :(, however, much to my surprise, my main application thread always continued
to work completely, and I could fire up new threads to cause another GPF even though I had
outstanding violations in some current threads:)
So to work with this thread we need to think about what it is we’re trying to do. We wish to take a
list of files, and compare each file with another list of files. Now we have our filelists within our
current main application window, and we could use these, but this would create problems,
especially if the user changes the filelist whilst our thread is running, suddenly we’re not
comparing the list we started to compare. So we need to make our thread create and maintain
all the information it requires to do the search it’s being asked to do itself.
The information we need for a search is quite simple, the list of files to compare against another
list of files, two lists. Now on our main display we have these two lists, fl1 and fl2, so when our
user hits the Go button we need to copy these into our thread. As a filelist maintains its lists of
files a TStringList, we’ll make life easy and maintain two TStringLists of our own.
Before we dive in and create these as items in the thread’s Execute() function, we need to give
a little thought in the way threads in CBuilder work. Whenever you’re doing something which
involves talking to another thread within your application you must synchronise the calls, to
ensure that nothing else is trying to do the same. In CBuilder threads this is done for you
automatically by using the Synchronize function, and to this end we need to ensure that any
variable set by referencing any other thread, (our main app window for instance), needs to
available to all parts of our thread class.
(a) Edit JSearch.h and put in the following declarations, (in Red).
//---------------------------------------------------------------------------
#ifndef JsearchH
#define JsearchH
//---------------------------------------------------------------------------
#include <vcl\Classes.hpp>
//---------------------------------------------------------------------------
class JSearch : public TThread
{
private:
String CheckMask;
String AginMask;
String CheckDirec;
String AginDirec;
TStringList *Files;
TStringList *AgainstList;
String Name1;
String Name2;
void __fastcall SetFile();
void __fastcall SetAgin();
void __fastcall UpdateDisplay();
void __fastcall Error();
void __fastcall UpdateCounter();
void __fastcall UpdateLog();
protected:
void __fastcall Execute();
public:
__fastcall JSearch(bool CreateSuspended);
};
//---------------------------------------------------------------------------
#endif
Now that probably strikes you as a lot of declarations:). Let me explain. From the top,
CheckMask and AginMask are to help us speed the search, CheckDirec and AginDirec are
holders for the two directories being used, Name1 and Name2 will be the actual filenames for
each check, and the two important ones, Files and AgainstList, are our TStringLists which will
hold the lists of files to check.
The functions are all declared as private, though they could be in the public section. The reason
for this is that within a thread any function which talks to other threads, (anything within your
application), should be done within a call to the Synchronize() function. As such, any such calls
are always going to be private to your class, as your class is the only thing that should call them.
(b) Setting our variables and lists. In the Execute() function, put the following
//---------------------------------------------------------------------------
void __fastcall JSearch::Execute()
{
//---- Place thread code here ----
int Loop1, Loop2;
FILE *one, *two;
bool NotTheSame;
AgainstList = new TStringList;
Files = new TStringList;
do
{
Synchronize(SetFile);
Synchronize(SetAgin);
}while(!Terminated);
if (one)
{
fclose(one);
}
if (two)
{
fclose(two);
}
FreeOnTerminate = true;
Terminate();
}
//---------------------------------------------------------------------------
Simple eh ? The actual declarations here, Loop1, Loop2, one, two and NotTheSame we’ll use
later when we put in our actual search algorithm. For now, we allocate memory for our
TStringLists, and enter a do/while loop, which effectively stops our thread from terminating
unless we let it.
Before going further, note the following. At the end of the Execute function are four lines. The
first two ifs check to see if the file pointers actually point to something, and if they do, close the
files they point to. Next comes FreeOnTerminate, which we set to true. This means that any
memory allocated by the thread object is freed, and the thread object itself is freed when it
terminates, if we didn’t do this, we would have to free resources ourselves in the main
application. This is important because of the way we close threads ourselves. If we left it till our
application closed, we could end up using a lot of unrequired resource.
Next in our list are the synchronised function calls, SetFile and SetAgin. Lets add these to our
JSearch.cpp file before we explain their simplicity, (yes you have to type it all:)
//---------------------------------------------------------------------------
void __fastcall JSearch::SetFile()
{
CheckMask = Form1->fil1->Text;
Files->Text = Form1->fl1->Items->Text;
CheckDirec = ExtractFilePath(Form1->fl1->FileName);
}
//---------------------------------------------------------------------------
void __fastcall JSearch::SetAgin()
{
AginMask = Form1->fil2->Text;
AgainstList->Text = Form1->fl2->Items->Text;
AginDirec = ExtractFilePath(Form1->fl2->FileName);
}
//---------------------------------------------------------------------------
What we have here are two functions which set appropriate variables within our thread. The first
sets our first file list, directory and mask, the second our second list, directory and mask. They
could be combined into one function, but I chose to separate them.
(c) Our loops:)
Our thread will work through two loops whilst conducting the comparisons. It will in effect scroll
through list one, and for each item in list one, compare it to each item of list two. Enter the
following code into our Execute() function, (trust me, this function is going to be large:)
//---------------------------------------------------------------------------
void __fastcall JSearch::Execute()
{
//---- Place thread code here ----
int Loop1, Loop2; //, Loop2Start;
FILE *one, *two;
bool NotTheSame;
AgainstList = new TStringList;
Files = new TStringList;
do
{
Synchronize(SetFile);
Synchronize(SetAgin);
Loop1=0;
while((Loop1 <= Files->Count-1) && (!Terminated))
{
Name1 = CheckDirec + Files->Strings[Loop1];
if((CheckDirec == AginDirec) && (CheckMask == AginMask)
{
Loop2 = Loop1;
}
else
{
Loop2 = 0;
}
while(((Loop2 != AgainstList->Count) && (!Terminated)))
{
Name2 = AginDirec + AgainstList->Strings[Loop2];
Loop2++;
}//end of loop2
Loop1++;
}//end of loop1
//finished so may as well terminate
Terminate();
}while(!Terminated);
if(one)
{
fclose(one);
}
if(two)
{
fclose(two);
}
FreeOnTerminate = true;
Terminate();
}
//---------------------------------------------------------------------------
The loops are very important to us as they control the flow of our comparisons. I’ve used while
loops to enable us to check more than one thing at a time, and the only thing to note is that the
counts are -1 each time. If you remember, a loop laid out in this fashion will always run once,
so in our case, on entry, if the number of items in our list is 0, we check Loop1 (0) against
Files->Count(0) -1, i.e. less than Loop1 so it doesn’t enter the loop. If our count is 1, Loop1(0)
Files->Count(1)-1, i.e. Loop1 is equal to our count so it runs, dropping when Loop1 = 1 at the
end. The next thing that must be true for our while loop to run is the !Terminated statement. If
you’ve read the threads tutorial, you’ll know that Terminated is a boolean value which all threads
have. In our case, if Terminated is true, it means that our user or application has requested
that the thread be terminated, so we terminate, (albeit at the end of the loop).
We’ll use terminated throughout this function to see if we should be terminating the thread, and
please note this usage, it is possible for your thread to be “locked” into doing something, and
whilst killing the application kills the thread, a little thought on your part means that your thread
should terminate when told !
So to recap,
Loop1=0;
while((Loop1 <= Files->Count-1) && (!Terminated))
{
The next section set’s up the filename Name1, by adding the item at Loop1 to our directory. We
then do something to speed up the whole process, by saying that if both the search Directory
and Mask are the same as the against Directory and Mask, we don’t need to check every file
twice ! This is done by setting the start of Loop2 = to the start of Loop1, otherwise, we set
Loop2 to 0.
(If this logic isn’t obvious to you, think in these terms. A directory of two files, file1, file2,
compared against itself. First run through, Loop1 = 0, file1, compared to Loop2 = 0, file1, (we
remove this later:). Next run, Loop1=0, file1, compared to Loop2 =1, file2. Next, Loop1=1,
file2 compared to Loop2=0, file1, but we’ve just done that ! So we don’t bother to do it again).
Name1 = CheckDirec + Files->Strings[Loop1];
if((CheckDirec == AginDirec) && (CheckMask == AginMask))
{
Loop2 = Loop1;
}
else
{
Loop2 = 0;
}
Now we enter our second while loop, in the same manner as the first. Again the important note
here is that we check Terminated, to free our thread as quickly as we can, we get the second
filename, increment our loops, and then break our overall do/while loop, with the call to
Terminate(). At this point notice something, we control our thread. We control what is being
done when we want it to be done, and the only other things which can access our thread are
Windows and our parent application.
while(((Loop2 != AgainstList->Count) && (!Terminated)))
{
Name2 = AginDirec + AgainstList->Strings[Loop2];
Loop2++;
}//end of loop2
Loop1++;
}//end of loop1
//finished so may as well terminate
Terminate();
Note this, our application is a thread as far as Windows is concerned, and our thread is a thread
of our application. When Windows terminates, it asks all it’s threads to terminate, and when it
asks our application to terminate all it’s threads before terminating itself. Ensure you understand
this. When Windows is given an Exception from our thread, it ignores it, the fault lies with
something it doesn’t wish to know about, it is only concerned with “it’s own” threads, our
application. As I mentioned earlier whilst developing this thread, I kept throwing exceptions all
over the place. These were handed to my application, which said Oh, I’ll throw a message box
up, (all CBuilder programs handle these by default), but the application doesn’t terminate itself
until the user accepts the messagebox, thus, bring the main app window to the front and fire
another thread, get another exception and so on. I stopped at ten exceptions, but it does show
the control flow between your thread, application, (CBuilder if you’re running from within the
IDE) and Windows. GPF’s however are another matter:)
(d) Our Search “Engine”
Now we’ve finished with Loop1, we just need to put the working bit inside Loop2. Enjoy yourself
and type the following complete into between the two lines shown in the Execute() function.
{
Name2 = AginDirec + AgainstList->Strings[Loop2];
if((one = fopen(Name1.c_str(), "rb")) == NULL)
{//error, should terminate
Synchronize(Error);
Terminate();
}
if((two = fopen(Name2.c_str(), "rb")) == NULL)
{//error, should terminate
Synchronize(Error);
Terminate();
}
NotTheSame = false;
if(!Terminated)
{
Synchronize(UpdateCounter);
Synchronize(UpdateLog);
while(!feof(one) && (!Terminated))
{
//do comparisons
if(Name1 == Name2)
{
NotTheSame = true; //don't bother
}
if (fgetc(one) != fgetc(two))
{//flag not the same
NotTheSame = true;
}
if(NotTheSame || feof(two) )
{//fseek to endof one
lseek(fileno(one), 0L, SEEK_END);
}
}
if(!NotTheSame)
{
if(!Terminated)
{
Synchronize(UpdateDisplay);
}
}
fclose(one);
fclose(two);
}
Loop2++;
}//end of loop2
Okay, there’s a lot there. The first few lines deal with opening the files we wish to compare.
They’re both wrapped in an if statement, in other words if they return a null pointer, we call the
function Error, which simply places a message in our debug window, and then puts in a call to
the threads Terminated flag with the Terminate() function. We then set the NotTheSame flag to
false for later.
{
Name2 = AginDirec + AgainstList->Strings[Loop2];
if((one = fopen(Name1.c_str(), "rb")) == NULL)
{//error, should terminate
Synchronize(Error);
Terminate();
}
if((two = fopen(Name2.c_str(), "rb")) == NULL)
{//error, should terminate
Synchronize(Error);
Terminate();
}
NotTheSame = false;
The next line uses the Terminated flag to see if we bother entering the next stage. If you study
the process of this loop throughout, you’ll notice that if Terminated is true at any point, the
program control will fall out of all loops, and that we check Terminated as frequently as makes
sense, and certainly before entering any time consuming processes, (or even during them.
if(!Terminated)
{
Synchronize(UpdateCounter);
Update the counter function
Synchronize(UpdateLog);
Update the Debug window
Next we start to compare the files. We do this by checking two flags, Terminated again, and
the end of file1. We then check to see if the file names are identical, (right down to the path),
and fall out of our loop fairly quickly if this is the case. This should really be done before entering
the loop, but for simplicity and clarity in the use of the Terminated flag, I’ve placed it here so as
not to introduce another flag. We then read a character from file one and compare it to a
character from file two. If they’re not the same we flag it as true, and then end the sequence if
the files are either the same file, or contain a character which is not the same, or when we
reach the end of file2. The statement
if(NotTheSame || feof(two) )
checks this, and then sets the
file pointer for file one to the end of fileone, so that the while loop ends, (remember, at the
bottom of the while loop Terminated is also checked).
while(!feof(one) && (!Terminated))
{//do comparisons
if(Name1 == Name2)
{//don't bother
NotTheSame = true;
}
if(fgetc(one) != fgetc(two))
{//flag not the same
NotTheSame = true;
}
if(NotTheSame || feof(two) )
{//fseek to endof one
lseek(fileno(one), 0L, SEEK_END);
}
}
Okay, the last part is quite straightforward. If the above while has finished, we check the status
of NotTheSame, and only if it’s true do we Update our display. Again, we put a final check on
Terminated here before calling the Update function. This is because the process of calling the
UpdateDisplay, which updates the top RichEdit on Form2, could be delayed by many things,
other threads, or even our user saving the contents of the RichEdit, so we only want to update it
if the thread should still be running. Close the two files and increment Loop2 or drop from the
loop.
if(!NotTheSame)
{
if(!Terminated)
{
Synchronize(UpdateDisplay);
}
}
fclose(one);
fclose(two);
}
Loop2++;
}//end of loop2
There is a lot there, so check through it and make sure you understand what’s happening, and
more importantly why. Track the Terminated flag and see how our thread can terminate at
frequent intervals. This is the reason that While loops are used, it’s harder to break for loops.
(e) Our final Bit,
Here are the remaining functions, all concerned with updating the output. Again you have to
type all the code in full:(
//---------------------------------------------------------------------------
void __fastcall JSearch::UpdateDisplay()
{
//for now lets update the richedit with our duplicate files
Form2->r1->Lines->Add(Name1 + " is identical to " + Name2);
Form2->Update();
}
Straightforward update of the display, adding a line to the top RichEdit of Form2.
//---------------------------------------------------------------------------
void __fastcall JSearch::Error()
{//display on error
Form2->r1->Lines->Add("File Open Error");
}
//---------------------------------------------------------------------------
This gives us a message that an error occurred, and as the only place we called it from is the
fileopening parts, that is the most likely cause.
//---------------------------------------------------------------------------
void __fastcall JSearch::UpdateCounter()
{
//update the counter for number of files.
Form1->Counter++;
Form1->Panel2->Caption = Form1->Counter;
Form1->Update();
}
The above function increments the counter, for all threads. This means that if we’ve more than
one thread running the counter can race up:)
//---------------------------------------------------------------------------
void __fastcall JSearch::UpdateLog()
{
Form2->r2->Lines->Insert(0, "Comparing " + Name1 + " to " + Name2);
}
//---------------------------------------------------------------------------
And finally, this message continually updates the log window by telling us which files are being
operated on.
Compile and run the application, and just hit Go with the defaults. You should find every file in
the directory of your project being checked, and some things should be identical, (*.~cp *.cpp
for instance.). Play with it as you wish:)
No liability is accepted by the author(s) for anything which may occur whilst following
this tutorial