[Back to TUTOR SWAG index] [Back to Main SWAG index] [Original]

                            Turbo Pascal for DOS Tutorial
                                 by Glenn Grotzinger
                           Part 20: Miscallaneous Topics
                         copyright (c) by Glenn Grotzinger

    Hello.  Here is a solution for the programming problem that was given in
    part 19 with a little background explanation.

    PART19A.PAS
    -----------
    This was the program written to generate the binary data file for use with
    the actual word checker.  A text file was written to facilitate reading in
    the data, one word per line, to ease implementation.

    program part19a;

      var
        infile: text;
        outfile: file;
        datastring: string[14];
      begin
        assign(infile, 'SOMEWRDS.TXT');
        assign(outfile, 'SOMEWRDS.DAT');
        reset(infile);
        rewrite(outfile, 1);

        readln(infile, datastring);
        while not eof(infile) do
          begin
            blockwrite(outfile, datastring, sizeof(datastring));
            writeln(datastring);
            readln(infile, datastring);
          end;
        blockwrite(outfile, datastring, sizeof(datastring));
        writeln(datastring);

        close(infile);
        close(outfile);
      end.

    PART19B.PAS
    -----------
    This is the actual word checker.  It is made so the words typed into the
    keyboard do not have to be case sensitive with reference to the datafile.
    All prevalent errors have error-checking code.

    program part19b;

      type
        strtype = string[14];
        nodeptr = ^node;
        node = record
          str: strtype;
          left, right: nodeptr;
        end;

      var
        tree: nodeptr;
        datastring: strtype;
        infile: file;

      procedure opendatafile(var infile: file; afile: string);
        begin
          assign(infile, afile);
          {$I-}reset(infile, 1);{$I+}
          if IOResult <> 0 then
            begin
              writeln('Terminating.  ', afile, ' does not exist!');
              halt(1);
            end;
        end;

      function upstr(instr: strtype):strtype;
        var
          i: byte;
          tempstr: strtype;
        begin
          tempstr := '';
          for i := 1 to length(instr) do
            tempstr := tempstr + upcase(instr[i]);
          upstr := tempstr;
        end;

      procedure memoryerror;
        begin
          writeln('Out of memory.');
          halt(1);
        end;

      procedure deletetree(var tree: nodeptr);
        begin
          if tree <> nil then
            begin
              deletetree(tree^.right);
              deletetree(tree^.left);
              dispose(tree);
            end;
        end;

      procedure inserttree(datastring: strtype; var tree: nodeptr);
        begin
          if tree = nil then
            begin
              if memavail - sizeof(tree) > 0 then
                new(tree)
              else
                memoryerror;
              tree^.str := datastring;
              tree^.left := nil;
              tree^.right := nil;
            end
          else
            begin
              if upstr(datastring) > tree^.str then
                inserttree(upstr(datastring), tree^.right);
              if upstr(datastring) < tree^.str then
                inserttree(upstr(datastring), tree^.left);
            end;
        end;

      procedure buildsearch(var infile: file; var tree: nodeptr);
        var
          datastring: strtype;
        begin
          blockread(infile, datastring, sizeof(datastring));
          while not eof(infile) do
            begin
              inserttree(datastring, tree);
              blockread(infile, datastring, sizeof(datastring));
            end;
          inserttree(datastring, tree);
          close(infile);
        end;

      procedure searchtree(datastring: strtype; tree: nodeptr);
        begin
          if tree = nil then
            writeln(datastring, ' was not found in the data file.')
          else
            begin
              if datastring = tree^.str then
                writeln(datastring, ' was found in the data file.')
              else
                begin
                  if datastring > tree^.str then
                    searchtree(datastring, tree^.right);
                  if datastring < tree^.str then
                    searchtree(datastring, tree^.left);
                end;
            end;
        end;

      procedure promptwrite;
        begin
          writeln('Type QUIT to terminate.');
          writeln('Type a word, and we''ll see if it''s in the database.');
          writeln;
        end;

      begin
        writeln('Database checker.');
        writeln;
        opendatafile(infile, 'SOMEWRDS.DAT');
        buildsearch(infile, tree);
        promptwrite;
        readln(datastring);
        while (upstr(datastring) <> 'QUIT') do
          begin
            searchtree(upstr(datastring), tree);
            promptwrite;
            readln(datastring);
          end;
        deletetree(tree);
      end.
    The written explanation of the delete code that was written in the example,
    to completely remove the BST from memory...

    We know that in a pointer-linked structure if we remove a node that has
    links assigned to it, that we will cause a heap leak and render the rest
    of the data structure unremovable from memory.  In a BST, that case becomes
    when both right and left branches of the node are nil.  We have to use
    recursion, evidently, since the problem of deleting the entire binary tree
    comes down to many smaller problems of deleting nodes with both sides
    assigned to nil, basically.  So basically, if we observe those lines of
    code, recursion occurs until both the left and right sides of nodes become
    nil, and then a dispose occurs.  In working back up in returning from the
    procedure calls, the tree is essentially disposed of from the bottom up to
    the root node, in reverse from the way the insert code illustration created
    it.

    Contents for Part 20
    ====================
    Since this part contains many random varied topics, a table of contents is
    in order....

            A) Linking OBJ code into Pascal programs.
            B) Including ASM or inline statements in Pascal programs.
            C) Hooking an interrupt in pascal programs.
            D) Calling an interrupt procedure.
            E) Conditional Compiler Compilation.

    Linking OBJ code into Pascal programs
    =====================================
    This is a short program, which describes the exact process, USING A C
    program's function (the topic of using C OBJ's came up).  If you do
    this with C OBJ, the code must not make any calls to any C libraries,
    or make any calls to a DLL.

    A description of what's going on...

    TEST1.H

    int far pascal add2numbers(int num1, int num2)

      {
        return (num1 + num2);
      }

    This is a C header file with a small defined function in it.  You MUST
    define it using either void far pascal for a procedure or <datatype> far
    pascal for a function.  I describe a function in test1.h.  For those who
    wonder, a header file in C functions much like a unit does in Pascal.

    For an OBJ you intend to use, each and every function must be defined in
    the resultant pascal program.  (I know it is not liked, and you SHOULD
    NEVER normally post attachments in c.l.p.b.) With differences
    between so many different C compilers and syntaxes that exist in the world,
    I will post the OBJ file I got from the compiler I use, for purposes of
    enabling others to test this.  I recommend you to cut and paste it before
    you uu or xx encode it if you need to obtain it.

    begin 644 test1.obj
    M@`D`!U1%4U0Q+D..B!\````;5$,X-B!";W)L86YD(%1U<F)O($,K*R`S+C`P
    MD8@/``#I=G%"(`=415-4,2Y#3H@&``#E`0``C(@5``#F!&YU;3($"@8`!&YU
    M;3$$"@@`2(@&``#E`08`AH@@``#F!F%N<W=E<@0"_O\$;G5M,00*"``$;G5M
    M,@0*!@"DB`4``.<:`'*(!0``YQH`<H@*``#N`0``!@`4`&6(`P``Z8R(!0``
    MZ@$+?98H```%7U1%6%0$0T]$105?1$%4001$051!!%]"4U,#0E-3!D1'4D]5
    M4-&8!P`H&@`"`P$9F`<`2```!`4!#Y@'`$@```8'`0N:!@`(_P+_`U60$@``
    M`0M!1$0R3E5-0D524P```#N("P``XQ@````C!`4`1H@%``#A&!ABH!X``0``
    M58OL@^P"BT8(`T8&B4;^BT;^ZP"+Y5W*!`"ZB!```.@`!U1%4U0Q+D-V<4(@
    <3I03```!`@````8`!@`'``\`"``4`!B*`@``=```
    `
    end

    TEST2C.PAS

    {$F+}
    program test2c;

      {$L TEST1.OBJ}
      function add2numbers(num1, num2: integer): integer;external;

      begin
        writeln(add2numbers(2, 3));
      end.

    This is the pascal code using the OBJ.  Basically, the $F+ compiler
    directive is required.  Then observe the usage of the $L compiler
    directive.  The $L specifies the OBJ file name to link in.
    Also note the use of the external; statement at the end of the function
    declaration.  That specifies that the function is defined externally,
    and not in any Pascal unit or defined in the program itself.  The
    function or procedure declaration listed in the pascal program must
    match EXACTLY with the one defined in the OBJ file.  It does in this
    case.

    Including ASM or inline statements in Pascal
    ============================================
    Pascal is a compiler, which translates the data into ASM, and then into
    machine language.  Naturally, if it takes the data to ASM, it can use
    straight assembler code as as well.  Take a look at the short procedure
    mockup below.

    procedure dothis; assembler;
      asm
       { assembler code goes in here }
      end;

    asm is a keyword that may be used anywhere in code, which
    tells the compiler to start looking for assembler code instead of pascal code.
    The assembler keyword is one that can optionally be added to make the
    compiler look for an asm keyword instead of the begin at a beginning of a
    procedure or function.

    An inline statement works much like a function call.  It makes it so
    that machine language can be used in pascal code.  For example (the hex
    codes are garbage I'm making up so don't use this in a real program),
    an inline statement can look like this...

    inline($80/$23/$14/$36);

    The codes can go on infinitely, and are separated by /'s...

    References
    ==========
    There is so much specific information out there that is required to know
    about computers that references are almost always needed.  Here are a few
    of my recommendations.

       Ralf Brown's Interrupt List (INTERXXY.ZIP  XX being a version #, y
           being a through e. -- this is a big set of text files!)
           This is a listing of all known interrupt functions of a PC.

       The PC Game Programmer's Encyclopedia (PCGPE.ZIP)  This is a
           collection of text files...some hold tutorials about how to
           program in assembler and VGA, a lot of them hold specific
           information about how to program peripherals of your system.
           Another big download

       The SourceWare Archival Group collection (SWAG).  Very much known,
           this is a collection of pascal code which has been donated by
           programmers from all over the world that reference how to do
           various topics.  I recommend you use this code only as a reference
           for instruction and do not copy the code.  This is almost a 7.5MB
           download.

    Hooking an Interrupt
    ====================
    An interrupt is a signal made from the computer to the CPU for many
    functions that occur in the computer.  There are many different types
    and numbers of interrupts.  There are two basic types, though, hardware
    and software interrupts.

    A interrupt is put in simpler terms is an "attention order" from some
    component of the system.  In most common systems, there are 255 of these
    available, both software and hardware.

    Below is an example where we hook an interrupt.  Hooking an interrupt
    involves the replacement and augmentation of the basic system interrupt
    process with something else, or nothing at all (if we want to temporarily
    disable a piece of hardware for a security program, for example).

    What this program does is hook the interrupt that handles the internal
    PC timer.  That function is augmented by a count that is added by the
    procedure "count" listed below.  As a background, for the PC timer,
    approximately 18.2 timer ticks occur every second, or 0.054945 seconds
    per tick.  Ultimately, you can see by running this program, that all it
    does is wait for you to press a key.

    program writehello; uses dos;

      var
        intsave: procedure;
        counter: integer;

     {$F+}
     procedure count; interrupt;
       begin
         inc(counter);
         inline($9C); { prods an interrupt }
         intsave;
       end;
     {$F-}

     begin
       writeln('Press a key.');
       getintvec($1C, @intsave); { get current int. procedure. }
       setintvec($1C, @count);   { replace it with our procedure. }
       readln;
       setintvec($1C, @intsave); { save the regular int. procedure back }
       writeln(counter, ' timer ticks have passed.');
     end.

    We will now describe what is different here....

    getintvec() is a TP DOS unit function which pulls the procedure currently
      defined on that interrupt.
    setintvec() is a TP DOS unit function that resets the function defined by
      the interrupt.
    The interrupt keyword must be placed on the procedure definition, and the
      procedure definition must be defined as far, like you see.

    A word of note for a procedure defined as an interrupt...the original
    interrupt procedure must be called within that interrupt procedure.  The
    inline($9C) seems to also be a required element.

    NOTE: Hooking an interrupt is essentially a dangerous proposition, to a
    degree.  Be sure that you are careful and learn what happens on each
    interrupt and know what the interrupt does before you try to hook it.
    Doing this actually CHANGES the hardware behavior of your system.
    If attempting to hook an interrupt causes the system to behave adversely,
    STOP the program right there (reboot the system even), and do not attempt
    the code again until you are aware of what exactly happened and what is the
    right thing to do to accomplish what you want.  For example, I once tried
    hooking a replacement procedure on the interrupt for the hard disk, and I
    had garbled output from the directory commands until I rebooted....

    Basically, BE VERY CAREFUL IN HOOKING INTERRUPTS.

    Calling Software Interrupt Procedures
    =====================================
    The system (hardware and operating system) has varied software interrupts
    which may be called to perform certain functions in the system.  This is
    how most of the file access works in the other functions of the DOS unit,
    such as assign, reset, rewrite, and close; for example.

    The record registers or tregisters are defined in the DOS unit or WinDOS
    unit respectively. It looks like:

    type
      registers = record
        case integer of
          0: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Word);
          1: (AL, AH, BL, BH, CL, CH, DL, DH: byte);
        end;

    This is considered a variant record, which may be used to discriminate
    the latter part of the field if data dictates it.  If you are familiar
    with the assembler language, you should recognize the fields from the
    record.

    Here is a sample program, which holds an example of a software interrupt
    call.  In this case, the program is calling function 9 (?) of interrupt
    $21, which involves writing a string out via regular BIOS calls (this is
    what TP does when it writes out a string).  Note the conversion of the
    pascal string to a blocked array string.  A string must end with a "$"
    when using this function.

    program writetest; uses dos;

      type
        strtype = array[0..255] of char;

      procedure writestring(astring: string);
        var
          tstr: strtype;
          i: byte;
          regs: registers;
        begin
          i := 1;
          repeat
            tstr[i] := astring[i];
            inc(i);
          until i = length(astring);
          astring[i+1] := '$';

          Regs.Ah := 9;
          Regs.Ds := Seg(astring);
          Regs.Dx := Ofs(astring);
          intr($21, regs);  {call interrupt $21 using regs register set }
        end;

      begin
        writestring('Hello all!');
      end.

    Amazingly, this program is 304 bytes smaller as an EXE than the
    equivalent program  listed below written using exclusively the TP write
    procedure...remember, the most efficient code is USUALLY the code with
    the shortest # of lines, but not ALWAYS.

    program writetest1;
      begin
        write('Hello all!');
      end.

    This program also introduces a few new function calls.  Intr() is a
    Pascal function in the DOS unit which executes a function call.  It is
    called like the example program.

    Memory addresses, as you may have already seen from following pointers
    using the debugger, are set up in a 20-bit segment/offset manner (20-bit
    memory = 1024KB addressable -- they did this for the original 8088's...).
    Seg() shows the memory segment of any variable you place in there.
    Ofs() shows the memory offset of any variable you place in there.
    Addr() shows the complete memory address of any variable you place in
    there.

    Consequently, we may also use the function MsDos() in this case to replace
    the Intr() call above.  MsDos() is an Intr() call to interrupt $21 (the
    DOS operating system control interrupt).

    Conditional Compiler Compilation
    ================================
    Let us first define a few more compiler directives for your collection of
    knowledge....defaults marked with ~

                    + state                      - state
                    --------                    ----------
    $N               FPU on                     ~FPU off
    $G               286 code gen. on           ~286 code gen. off
    $E              ~FPU emulation on            FPU emulation off

    These are compiler directives often used conditionally.  Conditional
    compilation is essentially another language, independent of the actual
    language code.  Identifiers may not be used from code in compiler
    directives and vice versa.  Let's look at a few of the pertinent
    constants that Pascal defines in relation to this....

    MSDOS => boolean: indicates that the operating system is MS-DOS or PC-DOS
    CPU87 => boolean: true if there is a math coprocessor (FPU) present.

    The only construct you will typically see is an if then or if then else.

    The compiler directives I listed above are most commonly used with this
    type of situation.  There are additional floating point data types available
    called comp, single, double, and extended; which are usable via the FPU
    generally.  The definitions of these variables may be defined using the
    conditional compiler directives....

    The conditional compiler directives we need to know are:

    {$IFDEF <name>}                                If item defined
    {$IFNDEF <name> }                              if item not defined
    {$ELSE}                                        ELSE directive
    {$IFOPT <compiler directive>}                  If compiler opt..
    {$ENDIF}                                       END IF STATEMENT
    {$DEFINE <name>}                               define a statement
    {$UNDEF <name>}                                undefine a statement.

    Functionally, these work and compile the source code if certain things are
    true.  For example, {$IFOPT N+} <some code> {$ENDIF} in some code will make
    it so the code represented by <some code> will only be used if N+ is defined.

    Here is a short example program using conditional compiler directives....

    program condcomptest;

      begin
        {$IFDEF CPU87}
          writeln('There is a math coprocessor in the system.');
        {$ELSE}
          writeln('There is not a math coprocessor in the system.');
        {$ENDIF}
      end.
    You will notice that only ONE writeln statement will execute itself.  The
    dependence will be whether a FPU exists in your system.

    Practice Programming Problem #20
    ================================
    Write a program which will count the number of keystrokes a user presses
    in a period of 15 seconds.  Use proper programming interest in this
    program, and be sure to be friendly to the user with this program.

    Background information.
       Keyboard interrupt is $09.  Interrupt occurs 2 times for every
       key pressed.

    Notes: Unfortunately, in all attempts I've made, to make it TERMINATE
    exactly on 15 seconds, it would cause a nasty problem.  The program will
    have to read in at least one key after termination.  Do not worry about
    this.

    Next Time
    =========
    We use the BGI.  Write comments to ggrotz@2sprint.net.

[Back to TUTOR SWAG index] [Back to Main SWAG index] [Original]