It's a crazy business, these magazines. The very first computer magazine I bought was a Dr. Dobb's Journal, back in my COSMAC days of late '76. Still, it's got to be one of the very few serious mags I haven't yet written for. So let me drive a stake into that 12-year record by introducing myself as your new "Structured Programming" columnist.
Not a few of you will recognize me as that grinning gremlin from the editorial page of Turbo Technix, Borland's incandescent programmers' magazine that vividly demonstrated what happens when the tail of a circular queue of dollars catches up with and seriously overtakes the head. After a heartbreak of that magnitude, I had thought about laying aside the magic keyboard forever. However, it was either write or sell shower curtains to discount stores in the Missouri/Iowa/Minnesota territory --and if you're reading this, well, my decision should be obvious.
I believe that in starting a project like this it's only fair to be up front about my biases. With all due respect to the Dr. Dobb's majority, I don't think all that much of C. Therefore, a significant part of the mission of this column will be to demonstrate that there's nothing you can do in C that you can't do in Modula-2 or Pascal, and with less need for Band-Aids and Mad-Dog Colombian Buzz-Beans. (Tell you about those some day ...)
I strongly favor object-oriented programming, and while OOP is wasted on mongrels like C++, it's true that there are few OOP options for DOS outside the C sphere. This is why I'll be talking about Smalltalk and Actor from time to time. Whether or not there will ever be a NeXT machine for the south side of $10,000, OOP is hot. Read up on it. Try it if you get the chance. (Digitalk's $99 Smalltalk/V is an excellent learning vehicle.)
Finally, I've been a Borland employee in the past and still have ties to the company, so I will politely decline any invitations to review Pascal or Modula-2 compilers here or elsewhere. While I will be mentioning programming tools of various sorts as I go, the emphasis will be on awareness rather than evaluation. No matter what they tell you, a lot of powerful stuff still comes out of garages, and I'll be pointing out the best of it as I find it.
I recognize that Pascal and Modula aren't the only non-C languages kicking around. Ada? I don't know. If I see a good one that doesn't cost six percent of the gross national product, I'll try it out and let you know. Cobol or Fortran? No chance. And while I don't promise to talk about Basic I don't promise not to, either.
Your input is solicited. What languages would you like me to discuss? (Is anybody really using Smalltalk on the PC?) What do you want to know how to do? Do you care about OS/2? Windows? TSRs? High level? (How to Design a Coherent Program?) Low level? (How to Load an EGA Font?) Assembler interface? Send me a laundry list. Also, tips are welcome; I only have two eyes and so many hours in the day. Send me your opinions and your best mini hacks. What's hot? What's not? Be my eyes and ears. Bearer of the best tip of each month gets my secret recipe for Structured Chicken Soup plus a little bag of Mad-Dog Colombian Buzz-Beans.
The problem with keeping video on the back plane (as the PC does and the Mac does not) is that the Vendor Gorilla (guess who) keeps changing boards on us. There are now two different text video devices for the PC (monochrome and color) and four different graphics devices (CGA, EGA, MCGA, and VGA), not counting the unlamented PCjr. To avoid programming to the least common denominator (and there isn't any truly common denominator), you have to be able to figure out which board is on the bus before you go out and try to do anything adventurous like set a mode or, lord knows, turn the cursor off.
It's easier than it sounds. You don't have to do anything as underhanded as examining bits in undocumented I/O locations. It's all a question of working with video-specific BIOS calls.
BIOS support for video exists in three layers, corresponding to the three generations of IBM video boards. The first generation consists of the original color graphics adapter and monochrome display adapter. Think of this as the innermost video BIOS layer, the one in planar ROM. (There is no ROM on those two first generation video boards.)
The introduction of the EGA carried with it ROM BIOS enhancements on the video board. These enhancements were attached to the main planar BIOS through a feature called ROM scan. During power-on self-test (POST), the PC's BIOS scans memory looking for ROM BIOS extensions identified by a special signature. In essence, the INT 10H (video) support on the mother-board was replaced by a software interrupt service routine on the EGA board, simply by repointing the INT 10H vector in the vector jump table to the routine in the EGA board's ROM.
VGA and MCGA BIOS support was provided in the same way (on-board BIOS connected to the planar BIOS through ROM scan), but it is a superset of the EGA's. Wrapped around EGA, BIOS support is a third layer of additional BIOS video services that don't exist on any board prior to the VGA and MCGA.
The algorithm for detecting adapters comes down to this: First, identify the generation of video boards by looking for each layer. In other words, check for VGA/MCGA-specific features. If you can't find that outermost layer, you know you don't have a VGA or MCGA. Next, look for the second layer, EGA support. If you can't find that, you know you don't have an EGA. If there's no EGA, you've defaulted to the first generation video boards.
Once you have the generation, you can use BIOS services to distinguish between a VGA and an MCGA or between a CGA and an MDA. Because the EGA is the only board in its generation, there's nothing further to be done once you've identified the generation as the EGA's.
There is one additional complication. Beyond the first generation of boards, all IBM-style video boards may be connected to either a color or monochrome monitor. The type of monitor connected to the board affects which modes may legally be set, and drastically changes the mechanics of working with certain modes. (And a minor complication to this complication ... the creaking, ancient composite video monitors may be either color or monochrome but are considered color monitors and may be used only with the CGA.)
The differences in semantics between an EGA/VGA/MCGA connected to a color monitor and an EGA/VGA/MCGA connected to a monochrome monitor are so severe that for coding purposes I recommend thinking of the two situations as two separate types of display boards. I consider this supportable since monitors are changed rarely and (because it's madness to consider swapping boards with the power on) never during the course of a DOS session.
One way to represent the different types of display devices is through an enumerated type:
Note that the EGA, VGA, and MCGA each have two different representations, depending on which monitor is connected to the installed board. The None constant exists to capture that plaguey hundredth chance that the host machine is running its console through a serial link with no video board of any kind on the backplane.
A video board identifier function should return a value of type AdapterType. I've implemented that function as QueryAdapterType. The Turbo Pascal 5.0 version is given in Example 1, while the TopSpeed Modula-2 version is given in Example 2.
Identifying the presence of a PS/2-generation video BIOS hinges on a service IBM first incorporated into the VGA BIOS: Service 1AH, Identify Adapter. Service 1AH is quite courteous in that it always copies register AH into AL.IBM has always used the convention that if you attempt to call a video BIOS service that does not exist, all registers are left unchanged. Because video services are selected by placing their number in register AH before calling INT 10H, if you call service 1AH and find 1AH in register AL, service 1AH (and hence a PS/2 VGA or MCGA BIOS) exists. A good safety measure is to zero register AL before calling service 1AH so that there is no chance that a garbage bit pattern of 1AH will not be passed to INT10H and returned unchanged in AL.
Now, if service 1AH exists at all, it does all the rest of the work. It returns a code in BL that specifies which adapter is on the bus. A simple CASE statement translates the code into a value of type AdapterType and we're done.
If service 1AH is not found, there's no VGA or MCGA on the bus, and we have to go looking for the EGA layer. The plan is similar: The EGA added a video BIOS service called alternate function, 12H. Service 12H does not promise to copy AH into AL, but (as with all video BIOS calls) if service 12H does not exist, no registers will be changed. Service 12H contains several subfunctions, one of which is return EGA information, subfunction 10H. Subfunction numbers are selected by placing them in BL. If video service 12H exists, it will place EGA information in the various registers. If service 12H does not exist, all registers will be returned unchanged.
By a (highly) fortunate coincidence, the subfunction number 10H is not valid EGA information. Therefore, if after a call to service 12H with subfunction number 10H in BL we still see 10H in BL, then service 12H does not exist, and the device is one of the first-generation CGA or MDA boards.
If BL changes, there's an EGA on the bus, and we can use some of that returned EGA information to determine whether the attached monitor is color or monochrome. BH contains the goods: If BH is found to be 0, a color monitor is attached; otherwise a monochrome monitor is there, and QueryAdapterType can return the appropriate value of EGAColor or EGAMono.
This sounds more complicated than it is. The code itself is quite straightforward; only the reasons for the BIOS's behavior are complex.
If after searching for the outer two layers we turn up nothing, then by default the attached board is either the CGA or MDA. (Or, as mentioned before, no adapter is installed at all.) The way to tell the difference is to call BIOS interrupt 11H, Equipment Determination. A word value encoded with a summary of installed equipment will be returned in register AX.Bits 4 and 5 specify the attached video adapter. Shifting the adapter bits 4 bits to the right translates them into a simple numeric code that may be translated to an AdapterType value with a CASE statement.
That's about all there is to detecting standard IBM display adapters. In most cases it devolves to asking the machine what its got. Now, I can never quite rid myself of the fear that the Anthropomorphic Fallacy (that is, this habit of "asking the machine" as though the machine were the little guy behind the counter at the liquor store) will throw me a curve. It's worth wondering how the machine knows what it has --and how likely the machine is to be right.
In the case of the EGA, VGA, and MCGA the machine knows because during POST, ROM scan attached the video board's BIOS chip to the planar BIOS. Because what you are doing when you are calling INT 10H is calling a routine in a ROM chip on the video board, you would like to assume that the board knows what it is.
If only IBM made video boards, that would be a good bet. However, I've seen enough totally blatant BIOS bugs in some of the scruffier Yuk Foo EGA clones to downgrade that certainty to about 99 percent. Not enough to worry about, but keep it in the corner of your mind if a client ever reports truly bizarre behavior on the part of your software.
For the CGA and MDA, what the BIOS does is simply report the state of the display adapter DIP switches (PC and XT) or CMOS RAM bits (AT). Since human beings set these switches or bits, you'd think the chances for error here are greater. Not so --ironically, if a machine tells you through Query-AdapterType that it contains a CGA or MDA, you can believe it, because POST double checks the switches and bits. The MDA and CGA are fairly simple creatures whose differences far outweigh their similarities, and are easy to tell apart by examining refresh buffers and I/O locations.
There is still the possibility that a weirdly designed CGA/MDA combination board might fool POST into making the wrong determination, but I've used a few such boards and POST has pegged them correctly every time.
Keep QueryAdapterType handy. Several routines and utilities in upcoming columns will require it.
I guess I was marked forever when I decided to wirewrap my first computer (a COSMAC Elf, lordy) out of loose parts. Those people who view with terror the idea of hooking loose wires onto serial or parallel ports remind me of James Thurber's granny, who worried endlessly about electricity leaking out of empty light bulb sockets and slithering around the house, crackling at her.
Then again, if you decided you wanted your machine to control the motion of a telescope, what would you do? Regardless of your answer, before doing anything, lay hands on Bruce Eckel's new book Computer Interfacing With Pascal and C. With wit and some superb technical figures, Bruce captures the essence of making electrons out of bits and vice versa. Among the described devices are temperature sensors, stepper motor drivers, TRIAC switches, digital/analog converters (DACs), op-amps, optoisolators, and numerous others. Most of the I/O is through standard PC serial and parallel ports. Bruce does, however, relate how to create your own PC bus prototyping board.
Bruce assumes that you all know how to program, so the emphasis is on practical interface electronics. The driver code is in Turbo C and Turbo Pascal and is disarmingly simple. Most of the interface devices can be cobbled together out of Radio Shack parts without a heavy investment in money or skill. Having done some of this stuff myself, I can tell you that it's easier than it may seem. With Bruce's book in front of you, it might even be fun. Highly recommended.
It's Halloween, and I just handed our last Reese's Peanut Butter Cup to a six-foot tall cardboard milkshake with a cowlick. Nothing much more will get done tonight, I suspect. We'll pick up the subject of PC display adapters next time. For now, it's time to turn out the lights and go engage in some devil-try of my own. Perhaps I'll be truly wicked and scrawl GOTO 100 on my neighbor's garage door ... naw, too nasty.
Anybody know where there's a good outhouse?
Computer Interfacing With Pascal and C Bruce Eckel eisys 1009 N. 36th Str. Seattle, WA 98103 Book $29.95 (postpaid) Listing disk $19.95
Copyright © 1989, Dr. Dobb's JournalLooking For Mr. Goodboard
TYPE AdapterType = (None,MDA,CGA,EGAMono,EGAColor, VGAMono, VGAColor,MCGAMono,MCGAColor);
Example 1: QUERYDSP.SRC
FUNCTION QueryAdapterType : AdapterType;
VAR
Regs : Registers;
Code : Byte;
BEGIN
Regs.AH := $1A; { Attempt to call VGA Identify Adapter Function }
Regs.AL := $00; { Must clear AL to 0 ... }
Intr ($10, Regs);
IF Regs.AL = $1A THEN { ...so that if $1A comes back in AL... }
BEGIN { ...we know a PS/2 video BIOS is out there. }
CASE Regs.BL OF { Code comes back in BL }
$00 : QueryAdapterType := None;
$01 : QueryAdapterType := MDA;
$02 : QueryAdapterType := CGA;
$04 : QueryAdapterType := EGAColor;
$05 : QueryAdapterType := EGAMono;
$07 : QueryAdapterType := VGAMono;
$08 : QueryAdapterType := VGAColor;
$0A, $0C : QueryAdapterType := MCGAColor;
$0B : QueryAdapterType := MCGAMono;
ELSE QueryAdapterType := CGA
END { CASE }
END
ELSE
{ If it's not PS/2 we have to check for the presence of an EGA BIOS: }
BEGIN
Regs.AH := $12; { Select Alternate Function service }
Regs.BX := $10; { BL=$10 means return EGA information }
Intr ($10, Regs); { Call BIOS VIDEO }
IF Regs.BX <> $10 THEN { BX unchanged means EGA is NOT there...}
BEGIN
Regs.AH := $12; { Once we know Alt Function exists...}
Regs.BL := $10; { ...we call it again to see if it's...}
Intr($10,Regs); { ...EGA color or EGA monochrome.}
IF (Regs.BH = 0) THEN QueryAdpterType := EGAColor
ELSE QueryAdapterType := EGAMono
END
ELSE { Now we know we have an CGA or MDA; let's see which: }
BEGIN
Intr ($11, Regs): {Equipment determination service }
Code := (Regs.AL AND $30) SHR 4;
CASE Code of
1 : QueryAdapterType := CGA;
2 : QueryAdapterType := CGA;
3 : QueryAdapterType := MDA
ELSE QueryAdapterType := None
END { Case }
END
END;
END;Example 2 QUERYDSP.JPI
PROCEDURE QueryAdapterType() : AdapterType;
VAR
Regs : Registers;
Code : SHORTCARD;
BEGIN
Regs.AH : = 1AH; (* Attempt to call VGA Identify Adapter Function *)
Regs.AL : = 0; (* Must clear AL to 0 ... *)
Intr (Regs,10H);
IF Regs.AL = 1AH THEN (* ...so that if $1A comes back in AL... *)
(* ...we know a PS/2 video BIOS is out there. *)
CASE Regs.BL OF (* Code comes back in BL *)
0 : RETURN None |
1 : RETURN MDA; |
2 : RETURN CGA; |
4 : RETURN EGAColor; |
5 : RETURN EGAMono; |
7 : RETURN VGAMono; |
8 : RETURN VGAColor; |
0AN, 0CH : RETURN MCGAColor; |
0BH : RETURN MCGAMono; |
ELSE RETURN CGA
END (* CASE *)
ELSE
(* If it's not PS/2 we have to check for the presence of an EGA BIOS: *)
Regs.AH : = 12H; (* Select Alternate Function service *)
Regs.BX : = 10H; (* BL=$10 means return EGA information *)
Intr (Regs, 10H); (* Call BIOS VIDEO *)
IF Regs.BX <> 10H THEN (* BX unchanged means EGA is NOT there...*)
Regs.AH : = 12H; (* Once we know Alt Function exists... *)
Regs.BL : = 10H; (* ...we call it again to see if it's... *)
Intr (Regs, 10H); (* ...EGA color or EGA monochrome. *)
IF (Regs.BH = 0) THEN RETURN EGAColor
ELSE RETURN EGAMono
END
ELSE (* Now we know we have an CGA or MDA; let's see which: *)
Intr(Regs,11H); (* Equipment determination service *)
Code : = SHORTCARD (BITSET(Regs.AL) * BITSET(4..5)) >> 4;
CASE Code OF
1 : RETURN CGA |
2 : RETURN CGA |
3 : RETURN MDA
ELSE RETURN None
END (* Case *)
END
END
END QueryAdapterTYPE;The Machine Told Me; Now who Told The Machine?
Dare to Hack the Hardware
BOO!
Products Mentioned