Tuesday, April 24, 2012

Displaying special characters the hard way


Back in 1991 a programming language called Clipper (version Summer'87) was a popular choice for making simple business applications for IBM PC computers. Clipper was a specialised language designed to easily create business forms of 25 rows by 80 columns of characters. It was also very simple to read and write data using dBase IV format dbf files.

It had one tiny problem back then - it did not handle special Polish characters: ĄĆĘŁŃÓŚŹŻ, which at that time were encoded typically in the Mazovia code page.

Luckily, Clipper was extensible through the assembly language, so somebody wrote this little 8086 library to write and read characters using processor interrupts. I don't know who wrote it, because there was no author in the file, and every Clipper programmer had the source, so I assume it was, and is, in the public domain. The version presented here has been slightly updated by me, mainly by adding comments.

Please note: this program is very fragile and dangerous: inputs are not validated, which makes buffer overrun a possibility.

Clipper commands:
SCREENSTR read characters from SCREEN to Clipper STRing.
STRSCREEN write characters from Clipper STRing to SCREEN.

Assembly functions:
__RETNI, __PARNI, _RETC, __PARC allow passing of data to/from Clipper.
__PARNI, for example, allows passing of a Clipper numeric value to the assembly language, see Nantucket's Guide to Clipper here.
__PARC places the address of a Clipper character string in DX:AX.

16 bit registers like DX can can be accessed as DH and DL - higher and lower byte. Read the guide to 8086 registers, by Thomas Scarff, and the guide to Input and Output (I/O) in 8086 by Joe Carthy for more information.

And here is the assembly code:


PUBLIC   SCREENSTR
PUBLIC   STRSCREEN

EXTRN    __RETNI:FAR
EXTRN    __PARNI:FAR
EXTRN    __RETC :FAR
EXTRN    __PARC :FAR

DGROUP   GROUP   DATASG

DATASG   SEGMENT 'DATA'
; string to return to Clipper
         RETSTR DW 80 DUP (0)  
DATASG   ENDS

_PROG    SEGMENT 'CODE'
; cs -> code, ds -> data
         ASSUME cs:_PROG,ds:DGROUP 

SCREENSTR PROC   FAR


; save registers
          push bp         
          mov bp,sp
          push ds
          push es
          push si
          push di


; get param. #1: row into dh
          mov ax,1        
          push ax
          call __PARNI
          add sp,2
          mov dh, al
          push dx


; get param. #2: col into dl
          mov ax,2        
          push ax
          call __PARNI
          add sp,2
          pop dx
          mov dl, al      


; get video page in bh
          mov ah,0fh
          int 10h         


; set cursor
          mov ah,02h
          int 10h         


; get param. #3: count into cl
          mov ax,3        
          push ax
          call __PARNI
          add sp,2
          mov cl, al
          mov ch, 0        


; di is an index for RETSTR
          mov di, 0        
          push di


; start loop1
; bh: video page, read char
loop1:    mov ah,08h        
          int 10h           


; write to RETSTR
          pop di
          mov RETSTR[di],ax 


inc. offset  
          inc di             
          inc di
          push di


; inc column
          inc dl           


; set cursor
          mov ah,02h       
          int 10h         
; loop: dec cx 
          loop  loop1      


; restore registers
          pop di           
          pop di
          pop si
          pop es
          pop ds
          pop bp


; return string
          mov dx, seg RETSTR       
          mov ax, offset RETSTR
          push dx
          push ax
          call __RETC
          add sp,4

          ret

SCREENSTR ENDP

STRSCREEN PROC   FAR

          push bp
          mov bp,sp
          push ds
          push es
          push si
          push di


; get param. #2: row
          mov ax,2         
          push ax
          call __PARNI
          add sp,2
          mov dh, al
          push dx


; get param. #3: col
          mov ax,3         
          push ax
          call __PARNI
          add sp,2
          pop dx
          mov dl, al
          push dx


; get param. #4: count
          mov ax,4         
          push ax
          call __PARNI
          add sp,2
          mov ch,0
          mov cl, al
          push cx


; get param. #1: string
          mov ax,1         
          push ax
          call __PARC  
          add sp,2
          mov es,dx
          mov bp,ax


; get video page (in bh)         
          mov ah,0Fh
          int 10h          
          mov bl,0


; write to screen
          mov ah,13h
          mov al,2
          pop cx
          pop dx
          int 10h          

          pop di
          pop si
          pop es
          pop ds
          pop bp

          ret

STRSCREEN ENDP

_prog    ENDS
         END



No comments:

Post a Comment