DOS keeps one current drive, and every file or directory name without a drive letter refers to it. INT 21h AH=0Eh changes it, which is what the shell does when you type Z: at the prompt. The drive in DL counts from 0: A = 0, B = 1, C = 2, the same numbering INT 21h AH=19h returns. The services that ask about a drive, such as INT 21h AH=36h and INT 21h AH=47h, count from 1 instead and use 0 for the current drive.

There is no error return. Given a drive that does not exist, DOS leaves the current drive as it was and returns AL as usual. To know whether the switch worked, ask with 19h afterwards:

MOV AH, 0Eh ; try drive D:
MOV DL, 3
INT 21h
MOV AH, 19h ; where are we now?
INT 21h
CMP AL, 3
JNE NODRIVE ; still on the old drive: there is no D:

AL comes back with the number of drive letters, not the number of drives. MS-DOS returns the LASTDRIVE setting of CONFIG.SYS (5, A to E, unless set), or the number of block devices if that is larger. DOSBox-X returns one past the highest mounted letter, and since its own drive Z: is always there, the answer is 26.

prints: C: -> Z: -> C:, drive letters: 26 (when the program starts on C:)
MOV AH, 19h ; DOS: the current drive in AL, 0 = A
INT 21h
MOV OLD1, AL ; to come back to
CALL PDRV
MOV AH, 0Eh ; DOS: select drive DL
MOV DL, 25 ; 25 = Z:, DOSBox-X's own drive
INT 21h
MOV COUNT1, AL ; AL = the number of drive letters
MOV DX, OFFSET ARROW1
MOV AH, 09h
INT 21h
MOV AH, 19h ; did it work?
INT 21h
CALL PDRV
MOV AH, 0Eh ; back to the old drive
MOV DL, OLD1
INT 21h
MOV DX, OFFSET ARROW1
MOV AH, 09h
INT 21h
MOV AH, 19h
INT 21h
CALL PDRV
MOV DX, OFFSET COUNTM1
MOV AH, 09h
INT 21h
MOV AL, COUNT1
MOV AH, 0
CALL PDEC
MOV DX, OFFSET CRLF
MOV AH, 09h
INT 21h
OLD1 DB 0
COUNT1 DB 0
ARROW1 DB " -> $"
COUNTM1 DB ", drive letters: $"
CRLF DB 13, 10, "$"
PDRV PROC NEAR ; print drive AL (0 = A) as "C:"
ADD AL, "A"
MOV DL, AL
MOV AH, 02h
INT 21h
MOV DL, ":"
MOV AH, 02h
INT 21h
RET
PDRV ENDP
PDEC PROC NEAR ; print AX in decimal (changes AX BX CX DX)
MOV BX, 10
XOR CX, CX ; digits on the stack
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
RET
PDEC ENDP

Every drive has its own current directory

DOS remembers a current directory for each drive. Switching to Z: and back to C: lands in the same C: directory as before, and INT 21h AH=3Bh with a path such as D:\GAMES changes the directory of D: without making D: current. The current drive is not part of the program either: a program that switches drives and exits leaves the user on the new drive, so put the old one back first, as the example does.

Floppies

On a PC with one floppy drive, A: and B: are the same physical drive. Selecting B: works, and the first access to it makes DOS ask the user to insert the diskette for drive B:, and to swap again when A: is used. A program that walks through the drives to find disks should use INT 21h AH=36h or an IOCTL call to check each one, not just select it.