INT 21h AH=3Ch makes a new file of length 0 and opens it. If a file of that name already exists, it is cut to length 0 instead: whatever it held is gone, and DOS does not ask. Either way you get a handle in AX, open for both reading and writing, with the file pointer at offset 0. Everything else goes through that handle: INT 21h AH=40h writes, INT 21h AH=3Fh reads, INT 21h AH=3Eh closes.
The name is an ASCIIZ string: the characters, then a zero byte, not the "$" that INT 21h AH=09h wants. It may start with a drive and a path (C:\WORK\OUT.DAT); without them the file goes into the current directory of the current drive. DOS names are 8.3 and DOS upper-cases them, so test.txt and TEST.TXT are the same file.
| CX bit | Attribute |
|---|---|
| 0 (01h) | read-only: the directory entry is marked, but the handle you get back can still write |
| 1 (02h) | hidden: DIR does not list the file |
| 2 (04h) | system: hidden, and meant for DOS's own files |
| 5 (20h) | archive: DOS sets it on a new file anyway |
CX = 0 is the usual choice. Bit 3 (08h) makes a volume label instead of a file, an old trick that some disk tools use; directories are not made here but with INT 21h AH=39h.
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 DX, OFFSET HMSGMOV AH, 09h ; DOS: print the string at DS:DXINT 21hMOV AX, BXCALL PDEC ; print the handle numberMOV DX, OFFSET CRLFMOV AH, 09h ; DOS: print the string at DS:DXINT 21hMOV AH, 40h ; write 5 bytes into the fileMOV CX, 5MOV DX, OFFSET HELLOINT 21hCALL CHECKMOV AH, 3Eh ; close itINT 21hMOV AH, 3Ch ; create it again: cut to 0 bytesXOR CX, CXMOV DX, OFFSET FNAMEINT 21hCALL CHECKMOV BX, AXMOV AH, 3Fh ; try to read the 5 bytes backMOV CX, 5MOV DX, OFFSET BUFINT 21hCALL CHECKMOV SI, AX ; SI = bytes readMOV AH, 3Eh ; close the handleINT 21hMOV AH, 41h ; delete TEST.TXTMOV DX, OFFSET FNAMEINT 21hMOV DX, OFFSET LMSGMOV AH, 09h ; DOS: print the string at DS:DXINT 21hMOV AX, SICALL PDECMOV DX, OFFSET BMSGMOV AH, 09h ; DOS: print the string at DS:DXINT 21hFNAME DB "TEST.TXT", 0HMSG DB "handle $"HELLO DB "Hello"LMSG DB "after the second create: $"BMSG DB " bytes", 13, 10, "$"CRLF 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 ENDP
A new handle is the lowest number not in use. Handles 0 to 4 are already open when a program starts (standard input, output and error, AUX and PRN), so the first file normally gets 5.
Errors
| AX | Meaning |
|---|---|
| 03h | path not found: a directory in the name does not exist |
| 04h | too many open files: the program's 20 handles are all in use, or the system's table of open files (FILES= in CONFIG.SYS) is full |
| 05h | access denied: the file exists and is read-only, the name is a directory, or the directory has no room for another entry |
Pitfalls
Because 3Ch truncates without a word, a program that means "create, but never over an existing file" should use INT 21h AH=5Bh. If the name is a device (CON, NUL, PRN, even with an extension, NUL.TXT), 3Ch opens the device and creates nothing. Close the handle when done: until then the directory entry may still say length 0.
In DOSBox-X, on a drive mounted from a host folder (the workbench's C:), the file is an ordinary host file. On a Linux or macOS host the attributes in CX are not applied when the file is created; set them afterwards with INT 21h AH=43h.