>Newsgroups: comp.lang.perl >From: jgd@cix.compulink.co.uk ("John Dallman") >Subject: Re: Perl for DOS needs int86x() (was Serial I/O & port setup :DOS: >Message-ID: >Organization: Compulink Information eXchange >References: >Date: Mon, 19 Sep 1994 19:37:06 GMT >X-News-Software: Ameol from CIX >Lines: 156 OK, I give up - here it is. Warning: lots of DOS stuff in here; slight revisions for clarity over the version I mailed to darrylo@sr.hp.com (Darryl Okahata) The MS-DOS/BIOS programming interface is complex and woolly. All of it is addressed via 8086 interrupts, with at least some parameters passed in registers - but after that it gets complicated. A primary requirement for a workable scheme is that it should not require the Perl implementation to know all about all the MS-DOS calls - in any case, addressing extensions to DOS is one major requirement for this job. Rather than add keywords to Perl, the best idea seems to be to make use of syscall(), since that is implementation-defined in any case, The manpage has: > syscall(LIST) > > syscall LIST > Calls the system call specified as the first element > of the list, passing the remaining elements as arguments > to the system call. If unimplemented, produces a fatal > error. > > The arguments are interpreted as follows: if a given > argument is numeric, the argument is passed as an int. > If not, the pointer to the string value is passed. > You are responsible to make sure a string is pre-extended > long enough to receive any result that might be written > into a string. If your integer arguments are not > literals and have never been interpreted in a numeric > context, you may need to add 0 to them to force > them to look like numbers. Clearly Perl's syscall() handler can deal with the string and numeric parameter stuff; we need to design a C syscall() for protected mode that can handle all of MS-DOS. We start by looking at the C function int86x(), which takes an interrupt number and a struct holding register values and does the interrupt. If let off from real (or VM86) mode, this will do for a lot of things, but not for any of the calls that take pointers to parameter blocks in registers. So, we need a way of passing such blocks down into the low 1Mb. I think it goes like this: syscall( SCALAR, # Interrupt number SCALAR, # Class - see below SCALAR, # Registers - see below LIST # Depend on Class ) ; This version of syscall returns success or failure values thus: If the carry flag was set on return (indicates an error on most calls), return the undefined value. Otherwise, return the value of AX, returning zero as "0 but true". On an error, $! is set. The interrupt number is just that. The registers scalar holds a packed set of values to be loaded into registers, thus: pack( "SSSSSSSSS", $AX, $BX, $CD, $DX, $SI, $DI, $BP, $DS, $ES) ; This block is copied into low memory and loaded into the registers before the real-mode interrupt is let off. When it returns, the new register values are packed back into the low-memory block and then copied back into the scalar's string buffer in high memory. The class value is used to control the interpretation of the parameter LIST. It is the bit-wise OR of the following values. Each set bit in class eats one value from the LIST. 1: A filehandle is used: the corresponding element of the LIST is a Perl filehandle, which is reduced to an MS-DOS filehandle and placed in BX before the interrupt is executed, overwriting the value passed in the registers scalar. One of the few standardised parts of the MS-DOS API is that file handles are always passed in BX; the only exceptions are the dup() calls, which Perl supplies an interface for anyway. Warning: mixing handle i/o calls and Perl i/o on the same file will produce an unholy mess. This facility is intended for doing IOCTL calls on filehandles, not for moving data. 2: The call uses a buffer whose address is passed in DS:DX. The contents of the corresponding SCALAR are copied into a low-memory buffer whose address is placed in DS:DX (overwriting the value from the registers scalar) before the interrupt is executed. This low-memory buffer can be a maximum of 512 bytes; no normal call uses more. Users should be warned that trying to use will probably crash the system. Once the interrupt returns, the contents of the buffer that DS:DX *now* points to are copied back into the SCALAR. The user is responsible for making sure that the SCALAR's string value is big enough to hold any returned data block: its size should be noted by the syscall() handler and used to control the amount of data copied back from low memory. 4: As above, but for a DS:BX buffer. This is used by some INT21 calls. (INT25 and INT26 also use it, but leave an extra copy of the CPU flags on the stack. A really helpful syscall() implementation would remmember to pop the extra copy of the flags off the stack before trying to return, thus avoiding a crash. However, Perl programs probably don't have any good reason for doing raw disk sector I/O and INT25 and INT26 have been obsoleted by others that don't share this problem.) MS-DOS Perl syscall() support should allow for 2 buffers to be used in the same call; don't make any effort to preserve the contents of the low-memory buffers between syscall() invocations. 8: As above, for DS:SI. 16: As above, for ES:BX 32: As above, for ES:DI 64: As above, for ES:BP (used by BIOS) This pointer meachanism does not support *all* DOS calls, but the ones excluded are highly obscure. They include the DOSSHELL task swapper, which uses DX:DC and BX:CX pointers, plus INT2F/AE0x and INT2F/B804 which use pairs of DS-based pointers. Fictional example: SmashScreen is called via INT23h. with AH=14h and a subfunction code in AL [0=enquire screen existence, 1=enquire if screen is already smashed, 2=select colour/mono screen for other operations, 3=reserved, 4=Smash screen, 5=repair screen]. DS:DX points to a parameter block giving the pattern to smash the glass with, ES:DI points to an undocumented block used for energy-saving controls and BX holds the handle of an already open file to sweep the bits of glass into. # Include the support values for SYSCALL. These define $AX, # $BX, etc as zero. require "syscall.ph" ; # Set up register values for a screen-smash open( TRASHFILE, ">\temp\splinters") || die "Couldn't open file: $!\n" ; $SmashAPI = hex( "23") ; $AX = hex("1404") ; $regs = pack( "SSSSSSSSS", $AX, $BX, $CD, $DX, $SI, $DI, $BP, $DS, $ES) ; $smashpattern = "+++++++++" ; $energy_save = "Snooze" ; $class = 1 + # 1 => Filehandle used 2 + # 2 => DS:DX buffer used 32 ; # 32 => ES:DI buffer used $success = syscall( $SmashAPI, $class, $regs, TRASHFILE, $smashpattern, $energy_save) ; Syscall has to interpret $class so that it knows how to deal with the parameters, and how to pass them back. Clearly, it will be very easy to crash the system by making use of this interface, but it is also possible to manage all of the assorted DOS enquiry/toggle functions (disk verify, raw/cooked terminal I/O, etc) quite easily. John Dallman, jgd@cix.compulink.co.uk - still using MS-DOS Perl and enjoying it.