Every open handle has a file pointer: the offset where the next INT 21h AH=3Fh reads or INT 21h AH=40h writes. Both move it forward. INT 21h AH=42h sets it directly. The offset is a signed 32-bit number split across two registers, CX the high word and DX the low word, and it counts from the origin chosen in AL.
| AL | New position | Typical use |
|---|---|---|
| 0 | the offset from the start | go to record n: offset = n × record size |
| 1 | the current position + the offset | skip forward or back; offset 0 asks where you are |
| 2 | the end of the file + the offset | offset 0: append there, and DX:AX = the file size |
The result in DX:AX is always the new position counted from the start of the file. Nothing is read or written and the disk is not touched: the call only changes a number in DOS's table of open files.
MOV AH, 3Ch ; DOS: create TEST.TXTXOR CX, CX ; CX = 0: normal attributesMOV DX, OFFSET FNAMEINT 21hCALL CHECK ; stop if CF = 1MOV BX, AX ; BX = the handleMOV AH, 40h ; write ten digitsMOV CX, 10MOV DX, OFFSET DIGITSINT 21hCALL CHECKMOV AX, 4202h ; DOS: seek, AL = 2: from the endXOR CX, CX ; CX:DX = 0XOR DX, DXINT 21hCALL CHECKMOV SI, AX ; DX:AX = the size (DX = 0 here)MOV DX, OFFSET M1MOV AH, 09h ; DOS: print the string at DS:DXINT 21hMOV AX, SICALL PDECMOV DX, OFFSET CRLFMOV AH, 09h ; DOS: print the string at DS:DXINT 21hMOV AX, 4200h ; AL = 0: from the start, CX:DX = 3XOR CX, CXMOV DX, 3INT 21hCALL CHECKMOV DX, OFFSET M2MOV AH, 09h ; DOS: print the string at DS:DXINT 21hCALL READ2 ; read 2 bytes and print themMOV AX, 4201h ; AL = 1: from here, offset 0XOR CX, CXXOR DX, DXINT 21hCALL CHECKMOV SI, AX ; where the pointer is nowMOV DX, OFFSET M3MOV AH, 09h ; DOS: print the string at DS:DXINT 21hMOV AX, SICALL PDECMOV DX, OFFSET CRLFMOV AH, 09h ; DOS: print the string at DS:DXINT 21hMOV AX, 4202h ; AL = 2, CX:DX = -2MOV CX, 0FFFFhMOV DX, 0FFFEhINT 21hCALL CHECKMOV DX, OFFSET M4MOV AH, 09h ; DOS: print the string at DS:DXINT 21hCALL READ2MOV AH, 3Eh ; close the handleINT 21hMOV AH, 41h ; delete TEST.TXTMOV DX, OFFSET FNAMEINT 21hFNAME DB "TEST.TXT", 0DIGITS DB "0123456789"M1 DB "size: $"M2 DB "at 3: $"M3 DB "now at: $"M4 DB "end - 2: $"READ2 PROC NEAR ; read 2 bytes from BX, print them and CR LFMOV AH, 3FhMOV CX, 2MOV DX, OFFSET BUFINT 21hCALL CHECKMOV CX, AX ; DX still = OFFSET BUFCALL OUT1MOV DX, OFFSET CRLFMOV AH, 09hINT 21hRETREAD2 ENDPCRLF DB 13, 10, "$"BUF DB 64 DUP (?)CHECK PROC NEAR ; CF = 1: print the error code in AL and exitJNC CHECK1PUSH AXMOV DX, OFFSET FAILMSGMOV AH, 09hINT 21hPOP AXCALL PHEXBMOV AX, 4C01h ; exit with code 1INT 21hCHECK1:RETCHECK ENDPFAILMSG DB "failed: error $"PHEXB PROC NEAR ; print AL as two hex digitsPUSH CXPUSH DXMOV DH, AL ; keep the byteMOV CL, 4SHR AL, CL ; the high digit firstMOV CX, 2PHEXB1:AND AL, 0FhADD AL, "0"CMP AL, "9"JBE PHEXB2ADD AL, 7 ; 10..15 -> "A".."F"PHEXB2:MOV DL, ALMOV AH, 02h ; DOS: print DLINT 21hMOV AL, DH ; then the low digitLOOP PHEXB1POP DXPOP CXRETPHEXB ENDPPDEC PROC NEAR ; print AX in decimalPUSH BXPUSH CXPUSH DXMOV BX, 10XOR CX, CX ; CX = digits pushedPDEC1:XOR DX, DXDIV BX ; AX = AX / 10, DX = the last digitPUSH DXINC CXOR AX, AXJNZ PDEC1PDEC2:POP DX ; the digits come back first to lastADD DL, "0"MOV AH, 02hINT 21hLOOP PDEC2POP DXPOP CXPOP BXRETPDEC ENDPOUT1 PROC NEAR ; write CX bytes at DS:DX to handle 1PUSH BXMOV BX, 1 ; standard outputMOV AH, 40hINT 21hPOP BXRETOUT1 ENDP
A negative offset is the 32-bit two's complement spread over CX:DX: -2 is CX = FFFFh, DX = FFFEh. For a backward move by a 16-bit amount, put its negation in DX and FFFFh in CX.
Pitfalls
The size comes back in DX:AX, and a file over 64 KB has DX ≠ 0: do not keep only AX. Asking for the size moves the pointer to the end, so seek back (AL = 0, CX = DX = 0) before reading the file. A position past the end is allowed: a read there returns 0 bytes and a write there extends the file.
A position before the start is accepted by MS-DOS without complaint and leaves the trouble to the next read or write. DOSBox-X, on a host folder, moves the pointer to the end of the file instead. Do not rely on either. On a device the call succeeds and changes nothing: devices have no position.