Chapter 12 -- The Rest of DOS





Introduction

Hi! Nice to meet you again! This time I'd like to explain about the remaining commands I haven't explained to you in the first lesson. It is because the commands are closely related to low-level things so that I think it is better to put it here instead. Of course you need to keep the uses dos clause intact to follow this chapter.

What to Learn

We will learn about setting DOS' flags, i.e. verify flag and break flag. Then we learn about dealing with environment settings such as PATH and TEMP. After that, we'd learn how to 'spawn' child programs. Also, we'd like to learn how to hook interrupt vectors. I tell that part just the basics only. The only part we do not learn this time is making terminate and stay residents programs. I think that needs special chapter to discuss. May be I'll add this in the third lesson.

Setting DOS flag

Setting DOS flags, verify and flag is a very easy task. Borland Pascal provides commands to deal with it. The first is verify flag. If we want to know the verify flag, we input getverify procedure. That's pretty strange... It's suppose to be a function right? The only parameter to pass is a boolean variable that later holds the value of the verify flag. If it holds true value, then the all file writing operations are verified, otherwise are not.

Setting verify flag is, yeah, you've guessed it, done using setverify. It is done in quite the same way as getverify. You'd only need to pass one boolean parameter holding the new value.

The break value is that the value that DOS check every time when doing its operations whether or not user has pressed <control+break> key. If that value is off, then DOS only check the <control+break> key in console (screen), printer, and communication operations only. If the value is on, then DOS check the key in every operations.

Getting the break value is done in getcbreak procedure. (It's strange too). The only parameter it takes is a boolean variable. Setting break value is done through setcbreak. Pass one boolean value again.

Dealing with Environment Strings

What is environment strings? It is showed whenever you type set then press <Enter>. Voila! All environment strings are shown. There are many environment variables inside, such as PATH, TEMP, PROMPT, COMSPEC, and many others. In case you are interested in checking the strings inside your program, you need to know this very well.

There are several commands that allow you to check the environment settings. The first command is envcount. It is a function with no parameters. It returns a value of how many environment variables are currently "in action". The second command is envstr. It is a function with one integer parameter as environment index. It returns the environment string at specified index. The last one, and the most commonly used is getenv. It returns the environment strings, but it takes a string as the parameter. So, if you'd like to know the contents of PATH, you'd like to write:

s:=getenv('PATH');

Easy, right? Of course s is a string variable.

Spawning Child Programs

Sometimes we'd like to execute another executable files under our program. It means that we want to spawn child programs. Yes! Pascal provide just all the tool we need. Whenever we want to spawn a child programs, we just need to invoke exec procedure. It takes two parameters, both are strings. The first string holds the path of the will-be-spawned program. The second is the name of the program. The name of the program here doesn't need to be an EXE or COM or BAT in extension. Any file extension would do fine as long as the file is truly executable.

Just before and right after calling exec, Borland Pascal requires us to call swapvectors procedure. It is all because we need to create a clean room to live to the child programs.

After exitting the child program, you must check whether the execution done properly or not. This is done through checking DosError variable. Just like dealing with files. However the only probable values are 2, 3, 5, and 8. Which are respectively file not found, path not found, access denied, and not enough memory.

You can check the exit value of that program. Remember that exit value is usually put as the parameter of halt procedure? Yeah, you can invoke just halt without any parameters, but it means that you give no clue to the calling program. So, it is better for us to always give the exit value.

OK, back to the topic. Checking the exit value is done through dosexitcode function. It takes no parameter and returns a byte that contains the child's exit code.

However, we need to be cautious to memory settings. You need to give the child program a room to breathe. Pascal's default memory setting is always eating up the entire memory for our program. So, we need to tell the compiler about the memory settings. This is done through $M settings. Remember that? OK! The first thing is that you need to adjust stacksize value to a reasonable value, usually 4096, 8192, or may be 16384. Leave heapmin value 0. Now, you may not specify a big number for heapmax. Otherwise, the compiler will eat up that amount of memory to our program. Then, the reasonable amount is between 0 to 65536. If the child program is small, then you could specify a bigger number. Just try adjusting the settings until the child program can run.

This is an excerpt how to invoke programs:


{$M 8192, 0, 0}
uses dos;
var
  s : string;
begin
  write('Type a filename to execute : '); readln(s);
  swapvectors;
  exec('.',s);
  swapvectors;
  if doserror<>0 then
  begin
    writeln('Error encountered:');
    case doserror of
      2 : writeln('File not found');
      3 : writeln('Path not found');
      5 : writeln('Access denied');
      8 : writeln('Not enough memory');
      else writeln('Unknown error');
    end;
    halt(1);
  end;
  writeln('Child program is successfully executed');
  writeln('The exit code is ',dosexitcode);
end.


Setting Up Interrupt Routine

What is an interrupt routine? That is similar to a procedure that service a specific interrupt. Usually the interrupt routine is called interrupt service routine (ISR). Is it different? In bare low-level world, such as assembly, it is very different. However, Borland Pascal make things easier. We just make a normal procedure. But we may not take anything as parameters. Then, we add interrupt word behind the semicolon of procedure declaration. Yes, you expert programmers, you can take registers as the parameters. But, I'll explain that usage later. You need to enable far call in interrupt routine by specifying $F+. You also need to turn off the stack checking feature by setting $S-.

You should know which interrupt you want to serve. Usually you build an ISR to handle hardware or software interrupts. You're rarely need to handle CPU-generated interrupts. That thing has been handled by operating system, so take it easy. In servicing hardware routine, you must check the hardware and do proper settings. Then, you should notify the interrupt controller chip that after you are done servicing hardware interrupt. Things are different when you design ISR to handle software interrupts. All things needed in hardware interrupt is no longer applied here.

Interrupt service address table is set up in the first kilobyte in the conventional memory. The table consists of 256 entries of pointers. The pointers are coupled in segment:offset pairs. That table refers to the interrupt 0 to 255 respectively. So, whenever you need to set up your routine, you need to modify that table inside your program.

Pascal provides a mean to set up interrupt routine. This is done through setintvec and getintvec. These instructions are designed to fiddle with such table so that we don't need to modify the table directly. The main thing is that you must restore the original table upon exiting. So, you need to reserve the old value first before modifying so that you can restore that before the exit.

Getting the table entries is done through getintvec. It is a procedure that takes 2 parameters. The first is the interrupt number you want to hook, then the second is a variable to hold the pointer value of the entry. Setintvec is similar to getintvec. It also takes two parameters. The first is the interrupt number. The second is the address of your ISR procedure.

The example below is used to trap the Print Screen key, in interrupt 5h. It is a software interrupt, so we don't need to fiddle with hardwares.


uses crt,dos;

{$F+,S-}
procedure ourISR; interrupt;
begin
  writeln('Hi !');
end;
{$F-,S+}

var
  oldaddr : pointer;
begin
  clrscr;
  getintvec($5,oldaddr);
  setintvec($5,@ourISR);
  writeln('ISR is set up!');
  readkey;
  setintvec($5,oldaddr);
  writeln('Restoring hooked interrupt');
end.

Run that and the program would wait for a key. Press Print Screen button and you will see the message 'Hi!'. Easy right?

OK, I think I'd like to end the discussion about setting up interrupts. That was just the basics. You still need to learn about setting up the hardware interrupts (IRQ) and reentrancy thingy in order to make a good interrupt routine. However, the servicing ISR is best done in assembly... Oh yes, you can modify the registers in your ISR but it is strongly prohibited. Why? It would cause a severe damage to the running program. The running program must be ready to accept such change, but not every program is that tough. That's why.

Notes

That's all folks! Low level programming is not so hard, isn't it? Shall we go to the next lesson?


Where to go?

Back to main page
Back to Pascal Tutorial Lesson 2 contents
Quiz? No quiz. I think you are pretty mature in programming! :-)
To Chapter 13 about memory accesses.
My page of programming link
Contact me here


By: Roby Joehanes, © 1997,2000