Visual Basic Online
ADVANCED TOPICS
by Joe Oliphant (joe-o@vbonline.com)
November, 1995

[MJA Accounting]

DOS interrupts.

Every once in a while, you need to do something that seems simple but there doesn’t seem to be any way to do it. You search the help files and flip through the manuals but nothing’s there. It seems no one ever tried to do it before. One such problem is trying to change the modification date on a file. Seems simple doesn’t it? Just try to find something that mentions it. Well, there is a way to do it, but you have to go way back to the days before Windows and use something called a DOS Interrupt. Without going into a lot of detail, an interrupt is a way of telling the CPU that you want to do something. The CPU then accesses whatever program is associated with the particular interrupt and runs it.

Interrupts are divided into seven categories:

  1. microprocessor
  2. hardware
  3. software
  4. DOS
  5. BASIC
  6. address
  7. general use
Interrupts can be used to get equipment lists and memory information, to control the video and BIOS, and to perform DOS functions and many other things.
This article deals with DOS interrupts.
You QuickBasic people out there might remember the CALL INTERRUPT and CALL INTERUPTX functions that performed DOS Interrupts. C has similar functions called int86 and int86x. To use an interrupt, you need to place information in what are called registers. The original 8088 was designed with fourteen registers each of which are 16 bits long. There are four scratch-pad registers which temporarily hold intermediate results and operands of arithmetic and logical operations, four segment registers which hold the starting addresses of memory segments, five pointer and index registers which are used to find data in memory, and one flag register used to give status information and control operations. The four scratch-pad registers are called AX (accumulator), BX (base), CX (count), and DX (data). The four segment registers are called CS (code segment), DS (data segment), SS (stack segment), and ES (extra segment). The offset registers are called IP (instruction pointer), SP (stack pointer), BP (base pointer), SI (source index), and DI (destination index). The flag register is called cflag.
This all may seem confusing and looks like it might be a real hassle to use, but it really isn’t. All you need to do to place data or read data from the registers is to use structures that are already set up in C. The DOS.H file contains the structures for the registers as follows:

struct WORDREGS {

Each register is 16 bits long so an unsigned integer can be used to hold the information you want to place or read from the registers. The WORDREGS structure contains seven integers for holding the ax, bx, cx, dx, si,di, and cflag registers. The SREGS structure contains four integers to contain the information for the segment registers. int86x does not use the remaining registers. In some cases, you might want to use only half of a register. This is what the BYTEREGS structure is for. It contains eight unsigned chars (two for each of the ax, bx, cx, and dx registers) which hold the high and low words of each register.

Unions
C has something called unions that VB doesn’t have. Unions allow you to use one variable with two or more different structures that point to the same data. The following statement creates a new structure REGS containing both the WORDREGS and BYTEREGS structures:

Suppose you have a structure with a single integer and you want to be able to read either the high or low word of that integer. First you create a structure containing the integer: You then create a structure containing two bytes:
Then a new structure containing both the structures:
You can now create one variable that can access either of the two structures and use them: bothtemp.i refers to the INTSTRUCT structure and bothtemp.b refers to the BYTETEMP structure. In the above case, l will be 255 and h will be 1.

Using int86x
int86x takes four arguments and has the following syntax:

int int86x(int, union REGS *, union REGS *, struct SREGS *);

The first argument is the interrupt to call which is always 33 (hex 21) for DOS functions. The second argument is the initial register settings, the third is the variable to hold the results of the call, and the fourth is the variable for the segment registers.
The function returns the same value contained in the ax register after the call. To use int86x, you create variables to hold the initial and resulting values of the registers as follows:

union REGS inregs, outregs;

Then a variable to hold the segment values:

struct SREGS segregs;

You’re then ready to start using int86x. The ChgTimeStamp function shows how to use int86x to set the time/date on a given file name. This function takes a file name, time, and date passed from VB.

int FAR PASCAL ChgTimeStamp(LPSTR fname, long ntime, long ndate) {

The VB declaration to call ChgTimeStamp follows:

Declare Function ChgTimeStamp% Lib "mydll.dll" (ByVal infile$, ByVal ntime&, Û
ByVal ndate&)

The file name variable is obvious but you probably wonder what form the ntime& and ndate& variables take. The time is stored in a two-byte word and is based on the following formula:

Time = Hour ´ 2048 + Minutes ´ 32 + Seconds ¸ 2

A file created at 17:30:10 would be stored as the value 35781 (17 ´ 2048 + 30 ´ 32 + 10 ¸ 2 = 35781). So, if you want to change the file time to 11:30:12, you would set ntime to 23494 (11 ´ 2048 + 30 ´ 32 + 12 ¸ 2 = 23494). The time is stored in two-second intervals so you can’t set the time to an odd value like 11:30:13. Since VB doesn’t have unsigned integers, you need to use a long integer to store the time and date values.
The date is also stored in a two-byte word and is based on the following formula:

Date = (Year - 1980) ´ 512 + Month ´ 32 + Day

The DOS world starts January 1, 1980 and ends December 31, 2099. The date November 1, 1995 would be stored as 8033 ((1995 - 1980) ´ 512 + 11 ´ 32 + 1 = 8033). To change the date to February 2, 1985, you would set ndate to 2626 ((1985 - 1980) ´ 512 + 2 ´ 32 + 2 = 2626). The process to actually change the date requires that you open the file to get a file handle, then change the date and finally close it. To open the file takes DOS Function 61 (hex 3D). This function requires a variable to hold the file handle:

    int ofile;                           /* variable to hold file handle              */

and that you set the dx and ds registers to point to the file name string.  Fortunately, there are two C macros to do this for you.  FP_OFF and FP_SEG take a far pointer (fname) and converts it to its offset and segment components as follows:

    inregs.x.dx = FP_OFF(fname);  
segregs.ds = FP_SEG(fname);
You can set the access mode but since we aren’t using the file, the access mode can be set to zero: segregs.es = 0; /* Access mode doesn't matter */
ah is used to hold the DOS function we want to call and the ax register holds the result of the call. The following statements open the file: inregs.h.ah = 0x3D; /* Open the file */
ofile = int86x( 0x21, &inregs, &outregs, &segregs);
if(outregs.x.cflag)
return(outregs.x.ax); /* return error code if file won't open */
The ax register is also used to return an error code so you need to check cflag to see if an error occurred. If cflag is non-zero then the possible error values in ax are 2 (file not found), 4 (file not found), 5 (access denied), or 12 (invalid access code). If no error occurred, we can then change the date using DOS function 87 (hex 57) as follows: inregs.h.ah = 0x57; /* Change the date */ inregs.h.al = 1; /* 0 = Get time, 1 = Set Time */ inregs.x.bx = ofile; /* File to open */ inregs.x.cx = (unsigned int)ntime; inregs.x.dx = (unsigned int)ndate; int86x( 0x21, &inregs, &outregs, &segregs); if(outregs.x.cflag) result = outregs.x.ax; /* return error code if date can't be changed */ al is used to either get or set the time, bx holds the file handle and cx and dx hold the time and date (they need to be converted to an unsigned integer). The possible error codes are 1 (invalid function¾caused if you set al to something other than 0 or 1) or 6 (invalid handle). Finally, all we need to do is close the file using DOS function 62 (hex 3E). The date/time change will not take affect unless the file is successfully closed. inregs.h.ah = 0x3E; /* Close the file */ int86x( 0x21, &inregs, &outregs, &segregs); if(outregs.x.cflag) return(outregs.x.ax); /* return error code if file can't be closed */ else return(result); /* return error or zero */
The only possible error code is 6 (invalid handle). It’s not often that you need to use a DOS function call, but it’s nice to know how to do it if you need to. The VB Project CHNGDT.MAK shows how to use the C function ChgTimeStamp.

Click here to go back to the November '95 Article Index