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 bitAttribute
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.

prints two lines: "handle 5" and "after the second create: 0 bytes"
MOV AH, 3Ch ; DOS: create TEST.TXT
XOR CX, CX ; CX = 0: normal attributes
MOV DX, OFFSET FNAME
INT 21h
CALL CHECK ; stop if CF = 1
MOV BX, AX ; BX = the handle
MOV DX, OFFSET HMSG
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
MOV AX, BX
CALL PDEC ; print the handle number
MOV DX, OFFSET CRLF
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
MOV AH, 40h ; write 5 bytes into the file
MOV CX, 5
MOV DX, OFFSET HELLO
INT 21h
CALL CHECK
MOV AH, 3Eh ; close it
INT 21h
MOV AH, 3Ch ; create it again: cut to 0 bytes
XOR CX, CX
MOV DX, OFFSET FNAME
INT 21h
CALL CHECK
MOV BX, AX
MOV AH, 3Fh ; try to read the 5 bytes back
MOV CX, 5
MOV DX, OFFSET BUF
INT 21h
CALL CHECK
MOV SI, AX ; SI = bytes read
MOV AH, 3Eh ; close the handle
INT 21h
MOV AH, 41h ; delete TEST.TXT
MOV DX, OFFSET FNAME
INT 21h
MOV DX, OFFSET LMSG
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
MOV AX, SI
CALL PDEC
MOV DX, OFFSET BMSG
MOV AH, 09h ; DOS: print the string at DS:DX
INT 21h
FNAME DB "TEST.TXT", 0
HMSG 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 exit
JNC CHECK1
PUSH AX
MOV DX, OFFSET FAILMSG
MOV AH, 09h
INT 21h
POP AX
CALL PHEXB
MOV AX, 4C01h ; exit with code 1
INT 21h
CHECK1:
RET
CHECK ENDP
FAILMSG DB "failed: error $"
PHEXB PROC NEAR ; print AL as two hex digits
PUSH CX
PUSH DX
MOV DH, AL ; keep the byte
MOV CL, 4
SHR AL, CL ; the high digit first
MOV CX, 2
PHEXB1:
AND AL, 0Fh
ADD AL, "0"
CMP AL, "9"
JBE PHEXB2
ADD AL, 7 ; 10..15 -> "A".."F"
PHEXB2:
MOV DL, AL
MOV AH, 02h ; DOS: print DL
INT 21h
MOV AL, DH ; then the low digit
LOOP PHEXB1
POP DX
POP CX
RET
PHEXB ENDP
PDEC PROC NEAR ; print AX in decimal
PUSH BX
PUSH CX
PUSH DX
MOV BX, 10
XOR CX, CX ; CX = digits pushed
PDEC1:
XOR DX, DX
DIV BX ; AX = AX / 10, DX = the last digit
PUSH DX
INC CX
OR AX, AX
JNZ PDEC1
PDEC2:
POP DX ; the digits come back first to last
ADD DL, "0"
MOV AH, 02h
INT 21h
LOOP PDEC2
POP DX
POP CX
POP BX
RET
PDEC 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

AXMeaning
03hpath not found: a directory in the name does not exist
04htoo 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
05haccess 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.