HOME - - - - - Delphi Tutorials TOC - - - - - - Other material for programmers
Delicious  Bookmark this on Delicious    Recommend to StumbleUpon

Delphi tutorial: Using units for shared subroutines

This has good information, and a search button at the bottom of the page

Please don't dismiss it because it isn't full of graphics, scripts, cookies, etc!

Click here if you want to know more about the source and format of these pages. It may be easier to read this if you re-size the window, so that it does not use the full width of your screen.

You may download the sourcecode for the application developed in this tutorial. The zip file also includes a compiled copy of the application.

This tutorial is intended to help you use units well. They are a great aid to productivity and to making software bug-unfriendly.

What is done by the application which is used to illustrate using units, and how it does it are not the point of this tutorial... in fact what the application does may have little point in any context! But if you are interested, or feel you need to know more than is presented here, those issues were covered in another tutorial I did. That other tutorial, if I do say so myself, covers some important ground fairly well. If you go to that tutorial now, a new browser window will open, and you won't lose your place in this tutorial.

At the heart of this application are three functions. You have to call the first one before you can call either of the others, and there are checks in the software to let you know you've made a mistake if that happens. It is easiest just to put the first function in the main form's OnCreate handler.

The first function returns a byte (zero if no problems occurred during the execution of the function, a different number if a problem did occur.)

The other two functions have a string passed to them during the call, and return a string.

The reason for this tutorial is to show you how such functions can be placed in a separate unit, isolated from the main source code of the application.

There are several reasons you might want to do this. Suppose you wrote many programs for small retailers who needed to charge sales tax or VAT. You would probably have an oft called function, say rCalcTax(rSale:real):real; that would tell you the tax on a sale of whatever amount is passed to the function in rSale. If you weren't able to take that in easily, you might want to visit my tutorial on subroutine calling. Again, clicking on that link will open a new browser window.) -->

If that was your need, it would pay you to master external units, because you could write the code for the tax calculation once, and then very easily incorporate it in every program that needed it. If you discovered some flaw in what you'd done, you'd fix the unit, and then merely recompile the different programs which used it.

Another reason for mastering units is that you can sell them! Without selling the sourcecode. Other programmers can buy the use of your "CalcTax" procedure, without having access to the part of it that says "Tax calculated by MyWare.Com's CalcTax unit"... and thus they can't take it out! (They only need the .dcu file to be able to compile programs using your subroutines.)

Units may not be a Big Deal to intermediate level Delphi programmers... I'm happy for you if you think you know all you need! But this tutorial covers something you may not have mastered. Consider again the CalcTax unit. Wouldn't it be nice if you could somehow "set" the rate of tax to be charged, rather than having to pass it to the function each time it was called? Of course, that would introduce a requirement that the "set rate" function be called before the CalcTax function was called. No problem! As we shall see.

Remember: I make no claims for the worth of the application this tutorial revolves around. Just watch how things connected with units are accomplished.

Remember: You can visit my other tutorial if you feel you need to know more about the application using the functions we're going to put into a unit. (A new browser window will open, and you won't lose your place in this tutorial.)


====
(*Why this application was written. How it works.

Ever been asked to type something like "ABC01ol" into a computer,
to gain access to something? Is that "ABC zero one oh ell"? Or
some other mix of ohs and zeros, ones and ells?

This application will take a string of any printable characters,
and even some characters that aren't "printable" (e.g. a space, or a TAB)
and convert them into a second string... one that does not contain
any troublemakers! You can quite easily modify the code to re-define
"troublemaker".

The resulting string will sometimes be longer than the original... but often it
will not be much longer.

The "code" is not very complex. Don't use it to hide text. But you can use
it to make something that has already been scrambled by another application
harder to mistype.

The application includes a de-coding routine, so that you can get back the
original string, should you need it, say within the application the string
has been typed into.

The method is an extended version of the following. To follow the example,
remember that the ascii for "0" (zero), in hex, is 30, and the ascii for
"o" (lower case oh), in hex, is  6F, and the ascii for "+", in hex is 2B.

Something you don't have to understand, but may help some readers:
We're simple going to use an escape character. (Don't worry if
that doesn't mean anything to you.)

The string ABCo0567+XYZ, when run through my system, becomes...

ABC+gw+da567+2bXYZ

... if the encoding string (more on this in a moment) is...

These letters:           abcdefghjkmnprtw  :-- note that not every letter has been used
    are used to stand...
for these hex digits:    0123456789abcdef

There's only one little bit of "cleverness" in this program.

The UNclever bit is as follows: To make the output, just transcribe the input,
one character at a time. If you get to one of the "unacceptable" characters,
replace it with a plus sign, and two characters which stand for the replaced
character's ascii code in hex.

The clever bit is to remember that the plus sign itself must be shown the way
the unacceptable characters are shown. (Otherwise you don't know if a plus sign
means "a plus sign", or means "make a character out of the next two characters,
using the ascii table."

Encoding string:

This is just 16 characters which are going to stand for the sixteen hex digits,
0-9, a-f. They can be any characters you like... as long as none of the
unacceptable characters (like 0 and O), and the escape character ("+", as this
has been presented) are included in the set.

*)
====

This is not an easy, or a short, tutorial, but it was written in May 07, when I'd had lots of experience of writing these things. It covers some very useful tricks that you may well benefit from having in your repertoire.

In this tutorial, we're going to go farther with material developed in an easier tutorial. In that tutorial, three functions were developed. They were all connected with making it easy for humans to enter passwords, by excluding ambiguous characters in the passwords. Suppose I send you an email saying your password is "he11o". Is that h-e-one-one-oh? h-e-ell-eel-zero?

The first function sets up some constants the other functions will need, and checks to see that the values in constants are suitable.

The second function turns a password with ambiguous characters into one without any. (It does make the password longer). If the application developed in qCD83-tut is run on he11o (h-e-one-one-oh), the application returns he+db+db+gx.

The third function reverses the process, it would turn he+db+db+gx into he11o.

The code for those functions could be copied and pasted into any subsequent project wanting them functions, but putting them in their own unit, and making use of the unit has numerous advantages, and this tutorial is going to tell you how. As I said earlier: If it moves too fast, consider working through the gentler tutorial introducing the functions first.


Start by writing a small application to exercise the features of the unit we are going to write. For now, we are inserting dummy substitute function in the body of the exercising application.

Stack vertically...

Leave their names as they are set by the system.

Make the text of Label1 say "That can be replaced with..."

Name the form DD69f1

Save everything in a folder called anything you like... mine's "DD69"

Save the code for the application as DD69u1.pas, and the project as DD69.dpr. Just to get things rolling, make the Button1Click handler be....

label2.caption:=edit1.text;

(If you've typed everything correctly, when you run the program, and click the button, whatever is in the edit box is copied to the second label.)

q2edit...Next, make it a little closer to our destination by making it....

add...

function sNoBadChars(sSource:string):string;

... to the source code just after the....

public
    { Public declarations }
... near its top.... and put.... function TDD69f1.sNoBadChars(sSource:string):string; begin result:='dummy from sNoBadChars'; end;

...into the sourcecode just before the final "end."

Make Button1's Click handler be....

label2.caption:=sNoBadChars(edit1.text);

... even though for the moment, the function isn't doing anything with the string it is passed.

Run the program again; get rid of any typos.

Add a third button to the form, in the upper right. It will be a "click this to close the application" button, but don't change any of it's properties yet.

Replace ALL of the code you have so far with....

unit DD69u1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TDD69f1 = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    Label1: TLabel;
    Label2: TLabel;
    Button2: TButton;
    Label3: TLabel;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    cEsc,sHexSubs,sNotAllowed:string;
    function sNoBadChars(sSource:string):string;
    function sBackToAmbig(sSource:string):string;
    function bSetAndCheckAreNoBadConstantsOkay(cEscT,
         sHexSubsT,sNotAllowedT:string):byte;
  end;

var
  DD69f1: TDD69f1;

implementation

{$R *.DFM}

procedure TDD69f1.Button3Click(Sender: TObject);
begin
close;
end;

procedure TDD69f1.Button1Click(Sender: TObject);
begin
label2.caption:=sNoBadChars(edit1.text);
end;

procedure TDD69f1.Button2Click(Sender: TObject);
begin
label3.caption:=sBackToAmbig(label2.caption);
end;

procedure TDD69f1.FormCreate(Sender: TObject);
var bTmp:byte;
begin
bTmp:=bSetAndCheckAreNoBadConstantsOkay('a','b','c');
if bTmp<>0 then showmessage('There''s a problem with the values used in '+
   'bSetAndCheckAreNoBadConstantsOkay');
end;

function TDD69f1.sNoBadChars(sSource:string):string;
begin
result:='dummy from sNoBadChars';
end;

function TDD69f1.sBackToAmbig(sSource:string):string;
begin
result:='dummy back to';
end;

function TDD69f1.bSetAndCheckAreNoBadConstantsOkay(cEscT,
              sHexSubsT,sNotAllowedT:string):byte;
begin
result:=0;
end;

end.

... and study it a bit, after running it to be sure all is well. If it isn't, you should probably just restart from scratch.... (sorry!)

The new code has some new global variables, and it has a new function, bSetAndCheckAreNoBadConstantsOkay.

Before we do very much with what we have, we're going to move "the guts" of several things into a separate unit. We're also going to create some variables within the unit, so pay close attention!

First, create the unit: File | New | Unit.

Immediately after you've done that, do File | SaveAs, and save the file as CD84sau.pas (The "sau" is for "stand alone unit")

To DD69u1.pas's "Uses" clause add ", CD84sau".

Just to make a gentle start, make the sourcecode for CD84sau be....

unit CD84sau;

interface

function sHi:string;

implementation

function sHi:string;
begin
result:='Hello, world, from CD84sau';
end;

end.

... add...

showmessage(sHi);

... to DD69.pas's Button1Click handler.

Run the program. Be sure that when you click on button 1, you get the message from CD84sau.


This might be a good time to review what CD83 did.

CD84sau is going to be almost the same code... but there's going to be a bit of "cleverness" in respect of what were constants in CD83. This is necessary so that the same unit can be useful in many programs, and flexible. Unfortunately, it is going to introduce some complexity. It also is going to introduce something tiresome, which I'll get to in a moment.

CD84sau will have three major functions....

Before sNoBadChars or sBackToAmbig can be called, the one with the big name will have to be called. This is because variables used in the other two will be initialized during the call of bSet. This isn't a very clever way to operate.... but is the best I could come up with. As humans are frail, I'm going to include something in the programming that will provide a warning if someone accidentally tries to call sNoBadChars or sBackToAmbig before calling the other function.

It is going to work like this.

By adding an additional section to CD84sau, one that is not always present, we can arrange for some code to be executed right at the beginning of the application's execution. We also have to add the variables involved. The necessary changes make the current whole of CD84sau....

unit CD84sau;

interface

var cEscU,sHexSubsU,sNotAllowedU:string;

function sHi:string;
function bSetAndCheckAreNoBadConstantsOkay(cEscT,
         sHexSubsT,sNotAllowedT:string):byte;

implementation

function sHi:string;
begin
result:='Hello, world, from CD84sau';
end;

function bSetAndCheckAreNoBadConstantsOkay(cEscT,
         sHexSubsT,sNotAllowedT:string):byte;
begin
result:=0;
end;


initialization
cEscU:='xxSetupNotCalledYetxx';// rogue value.


end.

You must also take...

function bSetAndCheckAreNoBadConstantsOkay(cEscT,
sHexSubsT,sNotAllowedT:string):byte;

...OUT of the interface part of the DD69u1.pas unit, and you need take the whole function out of the implementation section.

At this point, the application should "run" again, even though it won't yet do much.


Look again at DD69's sourcecode. There's already a FormCreate handler, and it is already doing a little useful work.

Replace the 'a','b' and 'c' with....

'+','abcdefghjkmnprtx','oO01l5Ss'

If you study CD83, you will find these values are the escape code, the hex signifiers, and the unacceptable characters. In CD83, these were held in constants, but for CD84sau, these strings will be held in variables. The contents of the variables won't change during the program's execution, so they behave rather like constants, but as different programs will use CD84sau, it will be nice to have a way to use different values for different needs.

In the CD84sau unit, replace the code for bSetAnd... with...

function bSetAndCheckAreNoBadConstantsOkay(cEscT,
         sHexSubsT,sNotAllowedT:string):byte;
begin
cEscU:=cEscT;
sHexSubsU:=sHexSubsT;
sNotAllowedU:=sNotAllowedT;
result:=0;
end;

Check that it the program still "runs", and after fixing any typos, add the following just before the "end" in what we just added.

if pos(cEscU,sNotAllowedU)<>0 then result:=1;

That will implement one of the checks that we had back in CD83. The others will need to be added in due course, but we'll leave that detail for now.

Run the program again... and still it looks like nothing is happening.

But! In the FormCreate part of DD69, change the "+" in the call of bSetAndCheck... to "S", which is one of the not allowed characters. Now when you try to run the program, you get a sensible message. (It would be against all of the logic of CD83 and the CD84sau alternative if the escape character was one of the characters CD83 and CD84sau are expunging from the password.) The error message would only arise if the programmer had accidentally chosen bad values to send to bSetAndCheck. Try taking the "S" out of the third parameter. You will see that the "problem" has gone away. (And when you've seen that, put the "S" back.. it is too easy to confuse with a "5", and make the first parameter a "+" again.)


Next we will continue the process of moving the subroutines of CD84sau from the calling program (DD69) into CD84sau's unit. For both sNoBadChars and sBackToAmbig, move the forward declarations and the code from DD69u1.pas to CD84sau.pas. The code goes before the word "initialization. Be sure to remove the "TDD69f1." that was needed when the functions were part of DD69u1.pas.

This would also be a good time to take out function sHi, and the call of it in DD69.Button1Click

The whole of CD84sau.pas will now be....

unit CD84sau;

interface

var cEscU,sHexSubsU,sNotAllowedU:string;

function bSetAndCheckAreNoBadConstantsOkay(cEscT,
         sHexSubsT,sNotAllowedT:string):byte;
function sNoBadChars(sSource:string):string;
function sBackToAmbig(sSource:string):string;

implementation

function bSetAndCheckAreNoBadConstantsOkay(cEscT,
         sHexSubsT,sNotAllowedT:string):byte;
begin
cEscU:=cEscT;
sHexSubsU:=sHexSubsT;
sNotAllowedU:=sNotAllowedT;
result:=0;
if pos(cEscU,sNotAllowedU)<>0 then result:=1;
//other checks to be added later. See CD83.
end;

function sNoBadChars(sSource:string):string;
begin
result:='dummy from sNoBadChars';
end;

function sBackToAmbig(sSource:string):string;
begin
result:='dummy back to';
end;

initialization
cEscU:='xxSetupNotCalledYetxx';// rogue value.


end.

=== We've now broken the back of the hard stuff. The program runs. When you click on Button1, created and provided for in DD69, it makes use of a function in CD84sau. Likewise Button2.

As a guard against a forgetful programmer failing to include a call of bSetAndCheckAreNoBadConstantsOkay in the FormCreate procedure of the program using CD84sau, add, in CD84sau, to sNoBadChars and sBackToAmbig the following line:

if cEscU='xxSetupNotCalledYetxx' then
     showmessage('Error: sSetAndCheck... should have been called before this was.');

You'll also need to add....

uses dialogs;

... just after the....

interface

...line to use "showmessages" within the unit.

Test that it works as it should by remming out everything currently in DD69u1's FormCreate, and trying the program. The error warning won't arise until you click one of the buttons, of course. (You knew that, didn't you?)


Now we "only" have to fill in the code in CD84sau under sNoBadChars and sBackToAmbig. You can either do that yourself, working from CD83, or you can download the machine readable copy of the finished software. Do look through it carefully and be sure you understand all the parts of it, though?


Concluding remarks...

Well! there you have it. In what follows, I speak a lot about "units". Of course, every Delphi application has at least one unit. In what follows, when I say "unit", I'm talking about a sub-unit, running independently within the wider scope of some application.

In the tutorial, the functions....

... are neatly parceled in a little unit so that you don't need to look into their internals again. (But you can, if you wish to!) Any time you want to use them, you just add the unit's name to the "uses" clause of whatever application is your latest creation, and the functions are available to it. (You have to put the unit's .dcu file someplace where the compiler can see it when you are building your application's sourcecode. I tend simply to put it in the same folder as the application's .pas and .dfm files.)

You've seen the "initialization" word in action, working as sort of an "OnCreate" handler for units. You've seen how to ensure that one unit-based subroutine can be made to insist that another pre-requisite subroutine is always called before itself.

You've seen a bit about working with unit-resident variables and their contents.

It was a long tutorial. I hope you feel it was worth the trouble you went to? I promise you that writing it took more work than reading it did!


            powered by FreeFind
  Site search Web search
Site Map    What's New    Search This search merely looks for the words you enter. It won't answer "Where can I download InpOut32?"

Click here if you're feeling kind! (Promotes my site via "Top100Borland")


Looking for email, domain registration, or web site hosting? If you visit 1&1's site from here, it helps me. They host my website, and I wouldn't put this link up for them if I wasn't happy with their service. They offer things for the beginner and the corporation.

Ad from page's editor: Yes.. I do enjoy compiling these things for you. I hope they are helpful. However... this doesn't pay my bills!!! Sheepdog Software (tm) is supposed to help do that, so if you found this stuff useful, (and you run a Windows or MS-DOS PC) please visit my freeware and shareware page, download something, and circulate it for me? Links on your page to this page would also be appreciated!
Click here to visit editor's freeware, shareware page.

Link to Tutorials main page
How to email or write this page's editor, Tom Boyd


Valid HTML 4.01 Transitional Page tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org


If this page causes a script to run, why? Because of things like Google panels, and the code for the search button. Why do I mention scripts? Be sure you know all you need to about spyware.

....... P a g e . . . E n d s .....