Encapsulation

When you reach this point, with a well-behaved, segmented MS-DOS application in hand, the worst of a port to OS/2 is behind you. You are now ready to prepare your program for true conversion to protected-mode operation by encapsulating, in individual subroutines, every part of the program that is specific to the host operating system. The objective here is to localize the program's "knowledge" of the environment into small procedures that can be subsequently modified without affecting the remainder of the program.

As an example of encapsulation, consider a typical call by an MS-DOS application to write a string to the standard output device (Figure 16-1). In order to facilitate conversion to OS/2, you would replace every instance of such a write to a file or device with a call to a small subroutine that "hides" the mechanics of the actual operating-system function call, as illustrated in Figure 16-2.

Another candidate for encapsulation, which does not necessarily involve an operating-system function call, is the application's code to gain access to command-line parameters, environment-block variables, and the name of the file it was loaded from. Under MS-DOS, this information is divided between the program segment prefix (PSP) and the environment block, as we saw in Chapters 3 and 12; under OS/2, there is no such thing as a PSP, and the program filename and command-line information are appended to the environment block.

stdin equ 0 ; standard input handle

stdout equ 1 ; standard output handle

stderr equ 2 ; standard error handle

msg db 'This is a sample message'

msg_len equ $-msg

.

.

.

mov dx,seg msg ; DS:DX = message address

mov ds,dx

mov dx,offset DGROUP:msg

mov cx,msg_len ; CX = message length

mov bx,stdout ; BX = handle

mov ah,40h ; AH = function 40h write

int 21h ; transfer to MS-DOS

jc error ; jump if error

cmp ax,msg_len ; all characters written?

jne diskfull ; no, device is full

.

.

.

Figure 16-1. Typical in-line code for an MS-DOS function call. This particular sequence writes a string to the standard output device. Since the standard output might be redirected to a file without the program's knowledge, it must also check that all of the requested characters were actually written; if the returned length is less than the requested length, this usually indicates that the standard output has been redirected to a disk file and that the disk is full.

stdin equ 0 ; standard input handle

stdout equ 1 ; standard output handle

stderr equ 2 ; standard error handle

msg db 'This is a sample message'

msg_len equ $-msg

.

.

.

mov dx,seg msg ; DS:DX = message address

mov ds,dx

mov dx,offset DGROUP:msg

mov cx,msg_len ; CX = message length

mov bx,stdout ; BX = handle

call write ; perform the write

jc error ; jump if error

cmp ax,msg_len ; all characters written?

jne diskfull ; no, device is full

.

.

.

write proc near ; write to file or device

; Call with:

; BX = handle

; CX = length of data

; DS:DX = address of data

; returns:

; if successful, carry clear

; and AX = bytes written

; if error, carry set

; and AX = error code

mov ah,40h ; function 40h = write

int 21h ; transfer to MS-DOS

ret ; return status in CY and AX

write endp

.

.

.

Figure 16-2. Code from Figure 16-1 after "encapsulation." The portion of the code that is operating-system dependent has been isolated inside a subroutine that is called from other points within the application.

When you have completed the encapsulation of system services and access to the PSP and environment, subject your program once more to thorough testing under MS-DOS. This is your last chance, while you are still working in a familiar milieu and have access to your favorite debugging tools, to detect any subtle errors you may have introduced during the three conversion steps discussed thus far.