Thursday, November 29, 2007

Trouble-shooting

Well, as you've seen, there are many factors that come into play porting from Turbo C to gcc/TurboC. Some of these are describe above, but there are others that haven't even been hinted at. For the sake of completeness, here's a somewhat-complete list of the difficulties I've personally encountered:

Summary or illumination of trouble-shooting items covered earlier

  • Whenever possible, run the ported program under xterm, with the command-line switches "+sb -tn linux".
  • Functions needed by Turbo C may not have been provided in the TurboC library function list. Solution: Write them and send them to me.
  • Integer datatypes may not have been converted properly. Solution: Handle as described earlier.
  • Some functions, such as strupr, strlwr, and fcloseall are not handled by any of the Turbo C header files. Solution: #include .
  • The TurboC library cannot physically resize the console unless the ported program is being run under xterm. Solution: Manually resize the console, rewrite the code to adjust automatically to size changes, or send me a fix.
  • The ported program does not release control of the console on exit. Solution: Add a call to textmode(EXITMODE) before exiting the program.
  • Functions don't behave as expected. Quite a few functions are common to Turbo C and to gcc, but act somewhat differently. An example is printf. In gcc this supports the carriage-return character ('\r'), but in true Turbo C does not. I've chosen not to correct this. Other functions, like getch , ungetch, and mkdir, are different enough that I've had to correct for their differences. Typically this is done by means of macros, so that these functions have their gcc interpretations above the point in the source file where their associated header files are included, and have their Turbo C interpretations below that point. Solution: Learn to recognize it, I guess. The gcc functions are always available, even after macro assignment, by means of a new name, such as getchNcurses.
  • Not all special-function keys on the keyboard are supported. Solution: Rewrite the code to eliminate the keys that aren't supported, or send me a fix .
  • Not all graphics characters are supported in console i/o mode (cprintf/putch/etc.), and none of them are supported in regular text mode (printf/putchar /etc.). Solution: Rewrite the code to eliminate those characters, or send me a fix.
  • char is signed but is supposed to be unsigned (or vice versa). Solution: Both Turbo C and gcc have options/switches for setting whether characters are signed or unsigned by default. Make sure you set the switch in gcc the same way as the option in Turbo C.
  • Main function is of the wrong type. The gcc compiler expects main to be of type int, whereas in Turbo C these are often (always?) of type void. Solution: Change it.
  • You may experience some compiler errors if integer datatype conversion is turned on but a compiler optimization level higher than -O0 is used when compiling your code. Solution: reduce the optimization level to -O0 and report the problem. This applies only to compiling your own code, and not to building the TurboC library.
  • Allocating the image buffers for the getimage function: The proper sequence of operations, as defined by Borland's docs, is this:
    • // Good code, approved by Borland and by the TurboC library.
      void MyFunction (void)
      {
      void *bitmap;
      bitmap = farmalloc (imagesize (left, top, right, bottom));
      getimage (left, top, right, bottom, bitmap);
      ... do stuff, such as call the putimage function ...
      farfree (bitmap);
      return;
      }
    If instead your program does stuff like the following, it will probably work, but you'll have a memory leak in which the memory allocated for the image buffers won't be fully deallocated:
      // Bad code, by any standards.
      void MyFunction (void)
      {
      char bitmap[4 + X_SIZE * Y_SIZE * COLOR_DEPTH];
      getimage (left, top, left + X_SIZE - 1, top + Y_SIZE - 1, bitmap);
      ... do stuff, such as call the putimage function ...
      return;
      }
    The problem is that getimage has to transparently allocate memory of which you're not aware -- specifically, X-window system Pixmap structures. In the olden days, programmers (such as myself) were tempted to write this kind of bad code because they believed that they understood the format of the graphical data in the image buffers -- even though Borland did not specify or even hint at its nature. This assumption is now revealed as being false.
  • Manipulation of the image buffers used by the getimage and putimage functions: Borland does not specify the format of the data in the image-buffer structures used by the getimage and putimage functions. (See the item above.) All that is specified is that this:
    • struct {
      unsigned short width;
      unsigned short height;
      ... some undocumented stuff representing graphical data ...
      };
    For practical reasons, it is likely that some (perhaps many) programmers -- such as myself -- hacked the format of the graphical-data area, and chose to manipulate the area directly. Programs based on this principle will not work, because the format of this area -- at least in the X-window based implementation of graphics.h functions -- is not the same in the TurboC library as it was in Borland's Turbo C. (Note: it is possible to rewrite the code for getimage and putimage so that the old Borland formats are preserved. The formats were dependent on the graphics controller and the graphics mode. Anyone who cares to hack all of these and to rewrite the code accordingly is very welcome to do so. Please send me the fixes. As of Borland C++ v5.0, the graphics.h file listed 43 graphics controllers and 29 graphics modes by name. The comments in the next trouble-shooting item below may be of assistance to you, if you try to undertake this task.) However, it is possible to manipulate the graphical data in the TurboC library image buffers, because I will tell you the format. You need to use X-window drawing functions such as XDrawLine, as applied to X-windowPixmap structures. The format of the image buffer, for the X-window implementation of the TurboC library, is as follows:
      struct TcImageBuffer
      {
      gushort Width;
      gushort Height;
      Pixmap Handle;
      };
  • Saving getimage image buffers to disk, or loading putimage image buffers from disk: If you read the discussion above, it will be obvious to you that this is next-to-useless, since the image buffers themselves no longer contain graphical data. What needs to be save or loaded is the Pixmap structures themselves, whereas the image buffers contain only the handles of the Pixmap structures. I believe that it is possible to get around this problem by rewriting the getimage /putimage functions in terms of the X-window functions XGetImage and XPutImage rather than Pixmap structures. If you want to do this and send me the patches, tell me about it.
Trouble-shooting items not covered earlier
  • Various standard C library "functions" may actually be implemented as macros on some target platforms, and as true functions on others. A common example is strcpy. When such macros contain casts (such as "(unsigned int)") that are questionable as described above under handling of integer datatypes, code containing them will fail to compile. It's difficult for me to catch all of these, since they may be perfectly fine on my test machines. For clues as to how to work around such problems, look at the implementation of strcpy in TurboC.h. Sometimes, changing the compiler optimization level to "-O0" fixes the problem. Or, just don't use the automatic datatype conversion.
  • Header files are messed up. The TurboC header files all include TurboC.h, which by default redefines short, int, unsigned , and long. Solution: Any standard system headers, must be included before any TurboC header, or else the reassignments of the integer datatypes will destroy them. Actually, TurboC.h itself includes a variety of standard system headers -- like stdlib.h, stdio.h, string.h, ctype.h, and so on -- and this problem won't arise for those headers. But your typical *nix system has a cadjillion header files, and TurboC.h can't include them all.
  • Filenames don't work in fopen. MS-DOS expected filenames like C:\MYDIR\MY.FIL, whereas *nix expects names like /MYDIR/MY.FIL. Also, *nix is case-sensitive, whereas MS-DOS is not. So if you have any hardcoded filenames that violate these rules, you'll have to fix them. I suppose I could have fixed fopen to get around some of these problems, but I have not done so.
  • scanf/gets/getchar doesn't work. In Turbo C, to a certain extent you can get away with mixing and matching getchar & getch or cgets & gets. This doesn't work with the TurboC library because once the conio text console is active you can't use scanf/gets/getchar until closing the conio console. Solution: Rewrite the code to eliminate gets/getchar in favor of cgets/getch (or vice versa). But note this: In my experience, a large motivator for using conio was often simply that Turbo C's print/putsgcc 'sprintf/puts functions do correctly support '\r', so you might not even need conio. functions did not correctly support '\r'.
  • The compiler gives weird errors for printf/scanf/(etc.) commands. The gcc compiler is intelligent enough to find many discrepancies between format strings and value lists in printf statements. For example, it can recognize that this is an error:
    • long n; // a long
      printf ("%d\n", n); // an int
    But it's not smart enough to necessarily recognize that the following isn't an error:
      uint32_t n; // a long
      printf ("%ld\n", n); // a long
    That's okay, though, because gcc produces only warning message rather than fatal errors for these problems. Besides, you'll want to check all of your i/o statements carefully anyway, since stuff like the following is correct in Turbo C but not in gcc/TurboC:
      int n; // 16-bits
      printf ("%d\n", n); // 16-bit in Turbo C but 32-bit in gcc.
  • printf/scanf/(etc.) don't work, but there's no compiler error. See the item above.
  • File i/o is messed up. File i/o is not messed up, but since different datatypes are supported in Turbo C than in gcc/TurboC, the data formats you're reading/writing may be different. This can happen in numerous ways:
    • Integer datatypes are the wrong size.
    • Integer datatypes are the wrong endian type.
    • printf/scanf/(etc.) may use the wrong format strings (see above).
    • Floating-point formats differ. Sorry, but I've no clue about this.
    • Structures are packed differently. For example, a structure like this in Turbo C
      • struct MyStruct {
        char c;
        short s;
        int i;
        long n;
        };
      might have to be rewritten as follows in gcc/TurboC if you expect to use it for direct i/o to a file:
        struct MyStruct {
        int8_t c __attribute__ ((packed));
        int16_t s __attribute__ ((packed));
        int16_t i __attribute__ ((packed));
        int32_t n __attribute__ ((packed));
        };
      I can't guarantee that this always works, but I've ported a reasonably substantial text-indexing system from MS-DOS (16-bit, little-endian) to LinuxPPC (32-bit, big-endian) that made heavy use of directly inputting/outputting structures to files, and the file formats were preserved. That seems to me to be a good sign.
  • Interrupt service routines. Sorry, I don't even want to think about this.
  • Inline assembler code. It can be done, but I don't know how, and the syntax will be different. I personally don't care about this problem, because I'm interested only in completely portable code. Sorry!
  • Making DOS or BIOS system calls. Obviously, you can't.
  • Turbo C pragmas. These will get warning messages from the compiler, but will be otherwise ignored. But they're confusing, so unless you expect to go back to MS-DOS someday, I'd suggest removing them manually. :-)

Examples

Hello, world! Consider the following Turbo C program:
#include
#include

void
main (void)
{
time_t t, newt;
int i = 0, x, y;
time (&t);
clrscr ();
cprintf ("Hit any key to continue: ");
x = wherex ();
y = wherey ();
// Wait for a keypress, flashing the message
// "I'm waiting!" on and off once per second.
// After any key is hit, display "Hello, world!"
while (!kbhit ())
{
time (&newt);
if (t != newt)
{
t = newt;
i++;
if (0 == (i & 1))
cprintf ("I\'m waiting!");
clreol ();
gotoxy (x, y);
}
}
getch ();
cprintf ("\r\nHello, world!\r\n");
}

This program almost compiles with GNU gcc (and the TurboC library), and almost works as-is, except for a couple of points:
  • You will want to add the following statement at the very end of the program:
    • textmode (EXITMODE);
  • The "Hello, world!" message won't actually be seen, because the you have to end with will instantly restore whatever was on the screen prior to running the program. So, you might want to add another getch() textmode(EXITMODE) printing the "Hello, world!" message.
  • GNU gcc expects main to be of type int rather than void, and of course you can't actually use int because of the automatic conversion of integer datatypes. So use gint instead.
Having done these things, you'll find that you can compile the program and run it without any errors:
gcc -o Hello Hello.c -lTurboC -lncurses
xterm +sb -e Hello
Compiling without warnings is actually a little unusual, because there are quite a few things that can cause non-fatal warning messages. See the trouble-shooting section for more detail.

Endian conversions

Since all Turbo C functions have been run only on Intel 'x86 processors, it is not uncommon in Turbo C code to find that the programmer has simply assumed that the CPU is little-endian -- i.e., that the LSB of multi-byte integers precedes the MSB. In porting such code to GNU gcc, one has to recognize that it may not necessarily be run on a little-endian processor. For example, I do a lot of work on an iMac, which is based on a big-endian PowerPC processor. This is not important in most cases, but does affect various operations such as the matching of bytes within union objects and, especially, reading/writing files.

To make it easier to deal with this problem, I've provided a number of functions not originally present in Turbo C that can be used to convert endian types where required. Unfortunately, it's still up to you to figure out that there's a problem and to add these function calls to the program.

There are two separate groups of functions, those which convert the endian type of an integer value already stored in memory, and those which convert data on-the-fly whilst reading/writing a file. All of the functions require the TurboC.h header file.

Function prototype Description
void FixLittle16 (uint16_t *); Performs an in-place conversion of a 16-bit integer value stored in memory from little-endian format to the natural CPU endian format, or vice-versa . Using this function twice in succession gets back whatever you started with. There's no harm using this function if the CPU happens to be little-endian, since the value is passed through unchanged in that case.
void FixLittle32 (uint32_t *); Same as FixLittle16, but 32-bit instead.
void FixBig16 (uint16_t *); Same as FixLittle16, but big-endian instead.
void FixBig32 (uint32_t *); Same as FixLittle16, but 32-bit big-endian instead.
int ReadLittle16 (FILE *fp, uint16_t *Value); Reads a 16-bit little-endian integer from a file, and delivers it in the natural endian-format of the CPU. Returns 0 on success, non-zero on failure.
int ReadBig16 (FILE *fp, uint16_t *Value); Same as ReadLittle16, but big-endian instead.
int ReadLittle32 (FILE *fp, uint32_t *Value); Same as ReadLittle16, but 32-bit instead.
int ReadBig32 (FILE *fp, uint32_t *Value); Same as ReadLittle16, but 32-bit big-endian instead.
int WriteLittle16 (FILE *fp, uint16_t Value); Takes a 16-bit integer value in the natural endian format of the CPU, and writes it to a file as a 16-bit little-endian integer. Returns 0 on success and non-zero on failure.
int WriteBig16 (FILE *fp, uint16_t Value); Same as WriteLittle16, but big-endian instead.
int WriteLittle32 (FILE *fp, uint32_t Value); Same as WriteLittle16, but 32-bit instead.
int WriteBig32 (FILE *fp, uint32_t Value); Same as WriteLittle16, but 32-bit big-endian instead.

Alternative graphics-mode functions

If the graphics-mode discrepancies described above are too awful for you to live with, it is possible in theory to disable the TurboC-provided graphics functions and instead to use alternate functions. (I've not actually tried any of this myself, so if you make it work I'd appreciate it if you could send me detailed instructions.)

For example, there is a 2D graphics library called the GRX library ( by Csaba Biegl, Michael Goffioul, and Hartmut Schirmer). The GRX website does not advertise (or even mention) Turbo C compatibility. However, it contains quite a few functions of the same name (and, apparently, the same functionality) as Turbo C library functions. To find out more, download the library using the link above. After unpacking the tarball, you'll find the Borland replacement functions in the src/bgi directory, and information about them in the doc/readme.bgi file.

Disabling the graphics functions present in the TurboC library involves these steps:

  1. Edit the TurboC library's Makefile, remove the compiler switch "-DWITH_X", and rebuild the TurboC library.
  2. Eliminate the TurboC version of graphics.h from the system includes (probably /usr/include).
  3. Install GRX.
Realize that I have not really evaluated the GRX library, and so do not understand its ins and outs. For example, I don't know how many BGI functions it implements. I believe that the main advantage of GRX relative to TurboC is that in a system without X it can directly used the graphics controller. I believe that the main disadvantage of GRX relative to TurboC is that it is not integrated with the other non-graphics Turbo C functions like getch. Since the TurboC library's version of getch (for example) depends on the TurboC library's implementation of graphics functionality, it will not work properly with GRX.

Graphics mode

Note: The material in this section and the next describes both features that exist and features that are planned for the future. Consult the change log for more info about the support provided in specific code versions.

In true Turbo C, graphics-mode screens (as opposed to text-mode screens) are handled by something called the Borland Graphics Interface, or BGI for short. The BGI is an API containing 80+ functions. Note that the graphics functions provided with the TurboC library are somewhat less portable than the remainder of the library, in that the X-window system is required. If you need graphics functions that can operate without X, I'd suggest reading the next section.

Some of the more important points of departure from true Turbo C are these:

  • External files. True Turbo C relies on the existence of certain plug-in files, specifically the BGI "drivers" (such as EGAVGA.BGI, HERC.BGI, etc.) and the fonts (such as TRIP.CHR, SANS.CHR, etc.). The TurboC library has no such dependence, though it may eventually be possible to load new fonts at runtime. Functions related to manipulation of these files are provided, so that you don't have to take the effort of writing them out of your code, but often they are empty stubs.
  • Missing functions. Implementation of the floodfill function is not presently planned.
  • Screen hiding. Since true Turbo C ran under MS-DOS, a non-windowed non-graphical environment, the way it had to operate was to take over the entire display screen, and to place the hardware graphics controller itself either in "text mode" or in "graphics mode". In other words, if the graphics controller was in text mode then only textual functions (like those in stdio.h or conio.h) could be used, whereas in graphics mode only graphical functions (graphic.h) could be used. Material written to the screen in text mode disappeared when graphics mode was started, and vice-versa. With the TurboC library, because a windowed environment is used, both text-mode windows and graphics-mode windows can appear simultaneously. In other words, when graphics mode is started, a new graphics-mode window is created, but no effort is made to hide the old text-mode window.
  • Palettes. Since true Turbo C dealt with actual modes of the hardware graphics controller, palettes were built into the graphics controller itself, and changes to palettes (for example, reassignment of a palette color, like the background color) were instantly reflected on-screen. With the TurboC library, changes to the paletted (and specifically, to the background color) are reflected only in future screen-writes, and don't affect anything already written to the screen.
  • Text. The bitmapped font is supported. For various reasons, sadly -- having spent a considerable amount of work on it -- I find that it's not convenient to support Borland's "stroked font" (CHR) format. (These reasons include the following: Borland's license does not allow using the Borland-supplied fonts with the TurboC library; nobody has made any free CHR fonts available on the Internet; and, while there are TrueType-to-CHR programs available, they cost more than I'm willing to pay -- i.e., more than zero.) Therefore, the functionality otherwise normally provided by the stroked fonts will eventually be supplied indirectly by means of *nix system fonts. Currently, this has not yet been implemented. Or, you may draw text directly using XlibTcUnicodeMappingsXlib. functions, as described a few lines below. You may also find the array useful when using
  • The getimage and putimage functions . This is described in detail above. Briefly, these functions are used to fetch a rectangular area of the display into an image buffer, or to write the image back to the screen. The formats of the image buffers were undocumented by Borland, but actually corresponded to the memory maps used by the specific graphics controllers and graphics modes. These formats are not duplicated by the TurboC library.
  • Flexibility. You don't need to confine yourself to the BGI functions. You can also use X-window library functions. For example, *nix system fonts -- which BGI obviously doesn't support -- can be used in a TurboC-ported program via Xlib functions like XLoadFont , XDrawString, etc. All you have to know -- other than basic X operations -- is that the TurboC library exposes the following X objects for your use:
    • Display *TcDisplay; // The X-window "display".
      Window TcWindow; // The X-window "window".
      gint TcScreen; // The X-window "screen".
      GC TcGc; // The X-window "graphics context".
    It's necessary to modify the graphics context (TcGc) for any serious drawing. If you don't restore it to its original settings, the TurboC library will become seriously confused. Therefore, it's best to copy followed by the graphics context (with X-window function XCreateGCXCopyGC), and work only with your copy of the graphics context rather than with the original. Also, to avoid threading conflicts, always wrap your X operations as follows:
      XLockDisplay (TcDisplay);
      // ... your X code goes here ...
      // For Example, draw a text string at x=10, y=20:
      XDrawString (TcDisplay, TcScreen, TcGc,
      10, 20, "I am text", 9);
      XSync (TcDisplay);
      XUnlockDisplay (TcDisplay);

Graphical display characters in text mode

When using functions such as printf, putchar, etc., no attempt is made to translate displayed characters so that they appear identical to those on an IBM PC. In other words, you're just using the native *nix functions, and you get whatever you get. Typically, you should expect only the characters in the numerical range 0x20-0x7E to display accurately. However, when using the various conio.h functions, such as cprintf , putch, etc., we attempt to match the output characters as closely as possible to those in true Turbo C on an IBM PC.

When I say that we attempt to map the characters as closely as possible, I mean that given a character with a certain numerical code, we attempt to display a character that looks as much as possible like the IBM PC character with that same numerical code. On the other hand, we have no way to translate string characters appearing in your source code. What do I mean by this? Well, consider the character 'é'. This happens to correspond to character 0x82 in the IBM PC character set. Suppose you have source code like this:

putch ('é'); /* bad! */
putch (0x82); /* good! */

What makes the first of these two lines "bad" is that the numerical code which the compiler creates for 'é' is based on the character sets and languages installed on the computer doing the compiling, and is very likely not 0x82. So the first form is almost guaranteed not to work properly. Even so, calling the second form "good" is stretching a point, because (as it happens), the TurboC library isn't presently able to display the character 'é' anyway. True Turbo C is able to display any of the characters in the IBM PC character set -- i.e. the standard printable 7-bit ASCII characters, plus additional characters in the numerical ranges 0-31 and 128-255. However, the TurboC library does not fully support this capability in text mode, because the ncurses library does not. A subset of the IBM PC character set is supported, including linedraw characters. (However, all linedraw characters representing double-lines or combinations of double/single lines are represented simply in terms of single lines.)

Screenshots of displayable Characters
True Turbo C display characters -- click to enlarge.
True Turbo C
TurboC library display characters -- click to enlarge.
TurboC library

(Click here to see the program that generated this output.) As you may notice, non-supported characters are rendered as dots. Actually, this can be changed within the ported program to any other character as follows:

/*
Add this code prior to the FIRST use of any conio function.
The default character cannot be changed after initialization.
The values used are any integer character code recognized by
ncurses (NOT IBM PC character codes).
*/
extern gint DefaultChar;
DefaultChar = ' '; /* or whatever */
If this still isn't good enough for you, you can individually control the mapping of every character. For example:
/*
Add this code AFTER the first use of textmode.
It will be overridden if placed BEFORE textmode.
*/
extern gint TranslatedChar[256];
TranslatedChar[0x9d] = 'Y'; /* support YEN as 'Y'. */

Text Screen resizing

In true Turbo C, the resolution of the text console is completely under the control of the programmer. The textmode function sets the resolution, in terms of the number of text rows and columns, and sets the graphics modes appropriately. If a Turbo C program is run under Microsoft Windows, the same situation applies, in essence, because even though Windows renders the text console by means of graphical operations, the number of text rows and columns is fixed, and cannot be changed by the user.

The situation is different in ncurses -- at least, if using the X-Window system -- because the size of the text console is entirely under the control of the user. The user can resize the text console at will, and the program has little effective control over the resizing.

This presents a difficulty, because Turbo C programs have naturally been written under the suppositions that the text console size has been set by the program itself, and that the text console is not going to be unexpectedly resized during program execution. Yet, these suppositions are both rather dodgy with code that has been ported using the TurboC library.

There are several possible approaches to the problem:

  1. For TurboC versions 04/18/02 and later, the screen size is now correct if the ported program is run under xterm. TurboC physically resizes the xterm console correctly upon starting the program. While it cannot prevent the user from manually resizing the window later, it will correct any manual resizing by automatically restoring the window to its correct size. (Sadly, it is possible for a persistent user to corrupt the window by trying hard enough to resize it, but the behavior is usually pretty acceptable.) Or,
  2. If for some reason it's impractical to use xterm, or if the window manager being used resists TurboC's attempts to resize xterm , then you may need to use some method outside of TurboC (such as instructions to the user) to prevent resizing of the window; or
  3. Set the BypassResizeXterm variable to 1, and recode those portions of your program that depend on knowledge of the screen size (such as those using gotoxy and windowgotoxy(1,25) , but use gotoxy(1,LINES) instead. functions) so that they are more flexible. For example, don't use things like
In implementing approach #3, several tools are at your disposal. Firstly, the variables LINES and COLS are available for your use. These variables always contain the number of text rows and text columns, respectively, in the text console. Secondly, the TurboC library can detect (in versions 20020331 and later) if the user has resized the console. When it detects that such resizing has occurred, it calls a function called ConioResizeCallback. You can optionally provide this function yourself (though the TurboC library provides a default version of the function which does nothing), and can use this function in a variety of ways to take care of the screen resizing. Perhaps the most straightforward way to do so is to simply allow ConioResizeCallback to completely redraw the screen.

In the latter case, by the way, if the user manually resizes the text console, then your current window (as determined by the windowor textmode function) will automatically be reduced in size (if necessary) to fit the new console size. However, it will not be correspondingly increased in size of the user later enlarges the text console. To override this behavior, you'll need to provide an alternative to the ResizeTurboC function. You'll need to examine the source code for any further explanation.