The WATCOM C/C++ Game Programmer's FAQ || || || |||||||| |||| |||| || || cccc / || || |||| || || || || || |||||||| cc / || || || || || || || || || |||| || cccc/ || || || || || || || || || || || / || || || || || || || || || || || || / || |||| || |||||||| || || || || || || || / cccc + + |||||||| || || || || || || || || || || cc +++ +++ || || || || || |||| |||| || || || cccc + + Game Programmer's *********************** ******* ************** *********************** ********* ******* ******* ******** *********** ******* ******* ******** ************* ******* ******* ***************** ******* ******* ******* ******* ***************** ******* ******* ******* ******* ******** ******* ******* ******* ******* ******** ********************* ******* ************ ******** *********************** ******************* ******** ******* ******* ********************* ******* Created October 23, 1994 by Lee A. Lorenz Version 0.7 ============================================================================== HISTORY: Version Date Description ---------+----------+-------------------------------------------------------- 0.5 | 10/23/94 | Initial version, skimpy, yes, but at least here it is! | | Not much, just to get it started. I will add to it, but | | others could add to it as well... ---------+----------+-------------------------------------------------------- 0.6 | 10/24/94 | A little more added. ---------+----------+-------------------------------------------------------- 0.7 | 10/29/94 | Table of Contents added (uses Line#, though). ---------+----------+-------------------------------------------------------- ============================================================================== Table Of Contents Line Numbers Data sizes in protected mode.....................................110 MK_FP doesn't work the same......................................128 Hooking Interrupts...............................................151 Finding WATCOM files (FTP sites, BBS)............................197 Libraries for WATCOM C/C++32.....................................218 Problems linking non-WATCOM object files.........................249 Using Inline assembly............................................285 Books for WATCOM C/C++32.........................................319 DOS extenders for WATCOM C/C++32.................................325 Differences in using assembly....................................345 Using DPMI.......................................................359 Accessing VESA BIOS functions....................................367 Trying to recompile other source code............................390 ============================================================================== Well, since the "Betrayal" at Borland, and WATCOM's strategic pricing of WATCOM C/C++ version 10.0, many users unfamiliar with protected mode programming have been posting alot of questions. The purpose of this FAQ is to clear up many of those questions. For now, I will concentrate on the specifics of programming WATCOM C/C++ (9.5 or 10.0) in protected (or 32-bit) mode, using the DOS/4GW Dos Extender from Rational Systems. To begin this FAQ, I'd like to outline my own reasons for switching to WATCOM's C/C++ compiler. I've been a longtime Borland C/C++ user, in fact, I was a registered user from version 1.0 on up to 3.1. About two years ago, the need for a good 32-bit compiler began surfacing with my growing requirements for memory and the complications of segmented programming became apparent. In November of 1993, Borland released version 4.0- but while it was advertised as a "32-bit" compiler, this was only true of Windows programming. Other disturbing things happened- Borland announced that they would not support TurboVision, they withdrew the DOS IDE, they seperated TASM, to make it an additional purchase. Still, I was reassured by Borland (via Paul Gross himself on CompuServe) that Borland might still upgrade the package to include better DOS tools (including 32-bit development). Until they did that, the appearance was that Borland was saying that DOS development was DEAD AND BURIED. I waited... and waited... until I saw an ad in Dr.Dobb's Journal for the PowerPak in June of 1994. Great... but when I called to upgrade my Borland C/C++ to 4.0 and order the PowerPak (now $199+$99 for powerpak), the operators didn't even know about it! Even worse was after a few days, they told me it *MIGHT* ship in July, but they weren't sure! That was when I saw WATCOM's ad, advertising the special $199 price. I called The Programmer's Shop and ordered it. They told me 10.0 wasn't shipping yet, HOWEVER, they would send me version 9.5. Cool. Better still, while the $199 price did not include printed manuals (CD-ROM docs), the 9.5 sent to me *DID* have all of the manuals. Well, now I'm a convert... it doesn't hurt that one of the hottest PC games ever, DOOM, was written using WATCOM C/C++ and the DOS/4GW extender, as well as many other leading games (X-COM, Indy Car Racing). My next task was to port many of my libraries over to WATCOM C, luckily, I had dabbled with the DJGPP release of GNU C/C++, a freeware 32-bit C compiler, so I had a bit of code already converted. ------------------------------------------------------------------------------- Now for the questions: ------------------------------------------------------------------------------- Q. My structures appear to be all messed up. Loading from files directly into the structures gets "shifted" and the wrong values show up. What's happening? A. Well, if you have declared "int" in your structures, bear in mind that the size of the int has CHANGED. You need to change them to "short". TYPE Size in 16-bit compiler Size in 32-bit compiler char 8 bits (1 byte) 8 bits (1 byte) int 16 bits (2 bytes) 32 bits (4 bytes) short 16 bits (2 bytes) 16 bits (2 bytes) long 32 bits (4 bytes) 32 bits (4 bytes) Thus, use "short" and "long" where the size of the data is specifically required. ------------------------------------------------------------------------------- Q. How come this doesn't work anymore for accessing video memory: unsigned char far *ScrPtr; ScrPtr=MK_FP(0xa000,0); A. Well, that's because memory is no longer segmented. In 32-bit mode, we use "selectors" wich refer to chunks of memory defined in GDTs (Global Descriptor Tables). In DOS/4GW, the "default" selector maps the first megabyte of memory (your traditional DOS memory) to the first linear megabyte of addresses. In short, the screen memory for graphics is located at 0x000a0000 through 0x000affff. So, our program needs the following: unsigned char *ScrPtr; /* No "far" required here */ ScrPtr=(unsigned char *)0x0a0000; The reason the call to MK_FP() is allowed, is that there will be cases where you need "far" pointers, as in the case of DPMI calls (see below). In this case, a selector is a 16-bit value and the offset if a 32-bit value. ------------------------------------------------------------------------------- Q. I don't understand "bimodal" interrupts, how am I going to hook into INT9 to capture keys with my own keyhandler? A. The interrupt mechanism for protected mode is quite complicated. When an interrupt is called, it may call one of TWO possible routines, either a "Real-mode" routine or a "Protected-mode" routine, depending on which state the CPU was in. To deal with this, DOS/4GW has a feature known as "auto-passup/auto-passdown". Simply put, if the interrupt function is defined for Real-mode, and not in Protected-mode, and the interrupt is called in protected mode, DOS/4GW will pass it "down" to the Real-mode function. Now the kicker... if you define a Protected-mode function for that interrupt, DOS/4GW will pass all interrupts occuring in Real-mode *UP* to Protected-mode! There is a price to pay for this feature, speed. There is a little bit of overhead involved. In many cases, this is not a problem, so attempt this method first before trying a bimodal interrupt. Here is a shell of implementing an IRQ handler: void (__interrupt __far *OldISR)(); void interrupt ISR_0( void ) { /* Your code here... */ } void main(void) { : : OldISR=_dos_getvect(TheVectorHere); _dos_setvect(TheVectorHere,ISR_0); : : _dos_setvect(TheVectorHere,OldISR); } That's all there is to it! Remember all of the normal things involved with hooking into IRQs. Also, as the old vector (protected mode) redirects the IRQ to Real mode, it is possible to chain back through using it. The "auto-passup" range are INTs 08H to 02EH, excluding 021H (DOS) ------------------------------------------------------------------------------- Q. Where can I find Watcom-specific files? A. There is a small site run by Ivan Pulleyn at netcom.com: ftp.netcom.com /pub/hypereal/watcom You will find patches and other Watcom-related files here. WATCOM has several ways to get information, files and patches: BBS: (519)884-2103. Internet FTP: ftp.watcom.on.ca E-Mail: tech@watcom.on.ca Compu$erve: GO WATCOM Less specifically, but equally useful ftp sites: x2ftp.oulu.fi /pub/msdos ftp.eng.ufl.edu /demos ------------------------------------------------------------------------------- Q. What kind of libraries are available? A. Well, quite a few, with more coming every day. Commercial: MetaWindow/386 - A graphics/windowing library ShareWare: (two net sites to try: x2ftp.oulu.fi and ftp.eng.ufl.edu) WGT 5.0 - A nice gaming graphics library, includes VESA SVGA functions, joystick and keyboard handling, sprites and more. Currently in Beta release. TinyPlay 2.11 - A decent 8-track MOD player/sound effects package. Source is included. ll_comm - "Lord Logic's" Communication code. Good start for writing some basic serial port code. ll_kbd - "Lord Logic's" Keyboard handler. text_eng - Code to warp an image to any 4-sided polygon. I have successfully converted 99% of GifLib 1.2. Perhaps I'll get around to posting it sometime. ------------------------------------------------------------------------------- Q. I've got library "X" in .LIB form. How come the linker keeps giving me this "_funcname not found" when "funcname" is in the header files? A. WATCOM uses its own naming conventions for functions, which FOLLOWS the function name with an underscore instead of PRECEDING the function name. The fix for this is to use #pragma aux cdecl (funcname); for each external function. Note that if the .LIB calls other library functions (i.e., printf, abs, anything like that) the linker cannot resolve these names. (Thanks to Joe Ottinger for this one). -------------------------- WATCOM gives you pretty fine control over the linkage convention. For example, you can do #pragma aux foo "_*" /* common style */ #pragma aux foo "*_" /* WATCOM style */ #pragma aux foo "*" /* common asm or HP style */ or even #pragma aux foo "FOO" /* linked as FOO */ I take advantage of this control to do profiling (through __PRO and __EPI) and occasionally to access directly WATCOM's internal functions for development and testing. (Thanks to Charles Fu) -------------------------- As a side note: A. Using a pre-compiled 16-bit library with a 32-bit compiler can be a very risky thing. Some code might work fine (small model?), but the general rule is that it must be recompiled. Also, some parameters won't match correctly (short->int->long problem). With straight C, this is pretty easy to convert, but inline assembly and .ASM files require greater conversion. (See below) ------------------------------------------------------------------------------- Q. How do I use inline assembly? Why doesn't my old Borland C/C++ program compile correctly with asm { }? A. Inline assembly is quite different in WATCOM C/C++ than in other compilers. The "asm {}" construct is in the ANSI standard, but may (according to the standard) simply treat it as comment, which WATCOM does. The mechanism for inline assembly in WCC++ is using the #pragma aux. The advantage is that registers are defined for input and output, and the compiler will optimize the code generated (something Borland and MSC don't do). Here is an example: unsigned short int10(short,short,short,short); #pragma aux int10 = \ "int 10H" \ parm [ax] [bx] [cx] [dx] \ value[ax]; OR: unsigned char GetRow(void); #pragma aux GetRow = \ "mov ax,0300H" \ "mov bh,0" \ "int 10H" \ modify [ax bx cx dx] \ value [dh]; Now, you need only use it like a function within your program! ------------------------------------------------------------------------------- Q. What books are available specifically for WATCOM C/C++? A. None that I know of right now.... anybody care to write one? ------------------------------------------------------------------------------- Q. What other extenders are available? How good are they? A. WATCOM C/C++ 10.0 (and prior versions) comes with DOS/4GW, a DOS extender from Rational Systems. It is a "freebie" - that is - it does not cost any extra, and may be distributed with your programs royalty-free. It does have a 32 megabyte limit on memory size, but works with DPMI, VCPI, EMS, XMS, and raw. There is also support for virtual memory (using disk for memory). It cannot be used to create TSRs. Rational also has a professional version, which accesses 4 gigabytes of memory, ability to generate TSR applications, better support of virtual memory and more. (about US$250) Phar-Lap is the extender of choice for Autodesk and others. Beyond that, I have almost no info. FlashTek is another one I have heard about. ------------------------------------------------------------------------------- Q. What considerations do I have using assembly? A. Well, you have more robust versions of the registers to use in protected mode. Back in real-mode, a programmer had to use size prefixes to use the full 32-bit registers of the 386 CPU. In protected-mode, they will *REDUCE* the size of the operands! Thus, all the 66H and 67H references (the opcodes of the size modifiers) will need to be stomped out. Now, you have "eax", "ebx", "ecx", "edx", "esi", "edi" and so forth. Only the "segment" registers remain 16-bit, but on that note, you won't have to use them anyway, as they become selectors, and you will see ALL OF YOUR MEMORY in the default selectors. ------------------------------------------------------------------------------- Q. How do I use DPMI? A. First, dig out the WATCOM C/C++ user's guide and read the "INTERRUPT 31H DPMI FUNCTIONS" chapter. Next, be prepared to wrap the DPMI functions for easier use. ------------------------------------------------------------------------------- Q. How do I access the VESA BIOS functions? A. First, start off by getting the DPMI stuff down pat. Now, allocate a "buffer" (using DPMI function 0x0100) to store data you wish to pass back and forth, and use DPMI function 0x0300 to call the real-mode int 10H. This function will convert any pointers passed to a 32-bit address: void *Dos2Protected(unsigned long dosaddr) { unsigned long a,b; a=dosaddr>>16; b=dosaddr&0x0ffff; a<<=4; return((void *)(a+b)); } Also, you can't use the WinFuncPtr functions, as they are real-mode routines. ------------------------------------------------------------------------------- Q. I bought "Tricks of the Game Programming Gurus" and can't get the code to compile at all. A. Well, that, like ACK, YAKICONS, XLIB, and others, involves a major overhaul to compile, mostly because of the abundance of 16-bit assembly. Strange thing is, much of the assembly could be coded in pure "C" without affecting the performance. (this is of course my own opinion). ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- More Questions: Some I will answer in the next edition, but I want to get this posted right now... Q. I *REALLY* need to use a Bimodal interrupt, how do I do it? Q. How do I assemble my .ASM files? What switches using WASM/TASM/MASM? I'm still calling for contributions, criticisms, and corrections. I don't really want to maintain this (I don't have the time), so when it reaches version 1.0, I will probably cut back on supporting this. Lee llorenz@delphi.com Check out my invisible .SIG: ----------------------------------------------------------------------------- -----------------------------------------------------------------------------