home *** CD-ROM | disk | FTP | other *** search
- Article text, originally published as "Demagnetizing Static
- Variables," Programmer's Journal 7.1, 1989, pp 42-52.
-
- -----
- Copyright (c) 1989 by Tom Swan. All rights reserved. Limited
- permission granted to use programming in compiled form only.
- -----
-
- Author's note: This information and the accompanying programs are
- compatible with Turbo Pascal 5.5. I wrote the article before this
- version was available, so any references to page numbers in the
- Borland references may be incorrect.
-
-
-
-
- Demagnetizing Static Variables
-
- by Tom Swan
-
-
-
- "Modifying typed constants (alias static variables) stored in
- Turbo Pascal 4.0 and 5.0 compiled EXE code files is hairy
- business--but it can be done if you're careful."
-
-
- Static variables, or typed constants as Turbo Pascal calls them,
- stick to compiled code like magnets to iron. Declared as
- constants but compiled as variables, typed constants are stored
- on disk inside a program's EXE code file. Because typed
- constants have predefined values, they eliminate wasteful
- initializations of global variables at run time. Despite this
- advantage, however, after compiling, there's no easy way to
- change a program's typed constants--for example, to let people
- make keyboard assignments, select window colors, and modify other
- default settings.
- A telephone call from a reader prompted me to think of
- several possible solutions to this problem. I knew I'd have to
- locate the typed constants embedded in the compiled code file and
- modify only the necessary bytes, storing the result back on disk
- with all other bytes intact. Mucking around with EXE disk files
- gives me the heebie jeebies, so the code would have to be
- reasonably immune to changes in future compiler versions. The
- program should run fast, be easy to modify, work with both small
- and large applications, wash the floor, dust the shelves, and
- take out the garbage.
- The following describes the results of my experiments,
- largely successful although I'm still doing the floor, shelves,
- and garbage by hand. After a brief refresher course on typed
- constants as stored on disk and in memory, I'll explain how to
- use the accompanying library unit to write custom installation
- programs. Also included are demonstration programs that you can
- use as starting places for your own projects.
-
-
- A Needle in a Byte Stack
-
- Finding typed constants in memory and on disk isn't nearly as
- difficult as finding a needle in you know what. Let's start with
- values in memory, as these are the easiest to locate. The first
- typed constant in memory is always stored at offset zero in the
- data segment. (There is only one data segment in a Turbo Pascal
- program.) Suppose you have these declarations:
-
- const
- aword : word = ( 1024 );
- achar : char = ( 'X' );
- abyte : byte = ( 15 );
-
- The typed constant aword is stored at DSeg:0000, occupying
- two bytes. After this comes achar at DSeg:0002. Next, abyte
- follows at DSeg:0003. DSeg is a predeclared constant equal to
- the value of register DS. Rather than calculate these offsets
- manually, use the Ofs function to find the address of any typed
- constant. For example, this displays the offset addresses of the
- three typed constants in memory:
-
- writeln(
- ' aword=', ofs(aword),
- ' achar=', ofs(achar),
- ' abyte=', ofs(abyte) );
-
- In Turbo Pascal 4.0, typed constant values follow each other
- with no wasted bytes between. As a result, lone byte values can
- lead to extra CPU machine cycles by forcing words to begin at odd
- addresses. If you insert a fourth typed constant word between
- achar and abyte, the new value resides at DSeg:0003, making the
- CPU work harder to access the value. To fix this problem, Turbo
- Pascal 5.0 provides a new switch {$A+} to align typed constants
- and other variables on even addresses, letting the CPU access 16-
- bit quantities as quickly as possible. (Word alignment offers no
- advantage on PCs with 8088 processors.) The default is {$A-} for
- no alignment.
-
-
- We Are Not Alone
-
- Your typed constants are not alone in memory. The System unit,
- containing Turbo Pascal's runtime library and linked into every
- compiled program, declares its own set of typed constants. (The
- 5.0 Reference Guide lists these on page 125.) In memory, items
- in the data segment follow this order:
-
- 1. Program typed constants
- 2. Unit typed constants
- 3. System typed constants
- 4. Global variables
-
- The main program's typed constants are always first,
- beginning at offset zero. Next come any typed constants declared
- in units that the program lists in a USES declaration. Last come
- the System unit's typed constants, followed by the program's
- global variables. Together, these four items make up the
- program's data segment, which can be as large as 64K. Items 1-3
- are stored in the compiled EXE code file. Memory for global
- variables (item 4) is allocated at run time--global variables are
- never stored in the disk code file.
-
-
- Smart Linker; Dumb Programmer
-
- Turbo Pascal's smart and crafty linker, which attempts to
- optimize compiled code, requires you to refer to at least one
- typed constant declared in your program and in all units. If you
- don't, the linker realizes you aren't using the typed constants
- and does not reserve space for them in the code! While writing
- codeless test programs to determine where typed constants are
- stored, it took me longer than I care to reveal to realize the
- linker was "helping" me by throwing my constants away. The
- following short program demonstrates how Turbo's smart aleck
- linker can get you into trouble as it did me:
-
- program test;
- const
- atc:char=('a');
- begin
- writeln( atc );
- writeln(
- char( ptr(dseg,0 )^) )
- end.
-
- The program declares one typed constant, atc. Two writeln
- statements display atc's value, the letter 'a'. The first
- writeln refers to atc by name. The second refers to atc the hard
- way, dereferencing a pointer to DSeg:0000 and recasting as type
- Char, admittedly the long way around a simple job. Take out the
- first writeln statement and guess what happens. The second
- writeln no longer displays 'a'! Turbo Pascal throws out atc
- because it doesn't realize that the ptr expression refers to the
- typed constant.
- Obviously, you won't usually refer to typed constants this
- way, but you might write a program that expects another module to
- reference a typed-constant array or large record at DSeg:0000.
- Don't do this. Turbo Pascal does not save typed constants unless
- you refer by name to at least one in each module. The same rule
- goes for typed constants in units. Either the unit code or
- another module must refer to at least one typed constant by name
- or the linker throws these babies out with the bathwater.
-
-
- Sniffing for Typed Constants
-
- Turbo Pascal stores typed constants as the last bytes in a disk
- code file. At run time, the bytes are copied to the start of the
- program's data segment in memory. On disk and in memory, the
- bytes are paragraph aligned--that is, beginning at an offset in
- the code file and at an address in memory evenly divisible by 16.
- The problem of writing an installation program to modify
- default values in compiled disk code files requires finding the
- offset to the first byte of the first typed constant. Listing 1
- (WINDGLOB.PAS) makes this easy by declaring a marker CBase
- assigned the string value '@CBASE@'. The program's default
- settings follow CBase--in this sample, the typed constants
- WBForeColor to WTitle. Last, a dummy typed constant EBase marks
- the end of the typed constants area. Ebase's data type and value
- are unimportant, but CBase must be a string.
- Because the application and installation program refer to
- the same values, it's best to declare all typed constants in a
- separate unit similar to WINDGLOB.PAS. In this example, the unit
- has no code, although it certainly could.
- The next step is to write the main application, WINDTEST.PAS
- in Listing 2. This program lists WINDGLOB in a USES declaration,
- importing the typed constants from Listing 1. Run WINDTEST to
- display a simple window with scrolling random characters. Press
- any key to end the program.
- The last job is to write the installation program, which
- modifies the typed constant default values in the compiled
- WINDTEST.EXE program. Listing 3 (WINDINST.PAS) displays a menu
- to let you change window colors and type a new window title
- string. The next section describes how to compile and run the
- programs.
-
-
- Compiling The Test Programs
-
- First compile Listing 4, TCUNIT.PAS, creating TCUNIT.TPU. (With
- the integrated Turbo Pascal compiler, be sure the Compile menu's
- Destination option is set to "Disk.") TCUNIT has procedures to
- help you write your own installation programs and contains a
- function to locate and modify typed constant values in compiled
- EXE files. I'll explain how to use the unit in a moment.
- After compiling TCUNIT, compile WINDGLOB.PAS to
- WINDGLOB.TPU. Then, compile WINDTEST.PAS and run the test
- application. Finally, compile WINDINST.PAS and run. Change any
- of the values listed in the menu. In this no-frills version, you
- have to specify colors by number even though WINDGLOB.PAS
- declares the default colors by name, using identifiers declared
- in the standard Crt unit. Press Q to quit and modify
- WINDTEST.EXE. Press X to quit and not save changes. Run
- WINDTEST again to see the effect of your new values.
-
-
- How TCUnit Works
-
- Two procedures in TCUNIT, GetWord and GetStr, prompt for word and
- string values. WINDINST calls these procedures to let you enter
- new values for various typed constant values. You'll probably
- want to add your own procedures to prompt for values of other
- types: such as GetInteger, GetChar, GetReal, and so forth. Use
- the two procedures listed here as guides.
- Function ChangesSaved in TCUNIT does all the work of opening
- the compiled EXE code file, locating the typed constants, and
- writing the modified values back to disk. See the end of
- WINDINST for an example call to this function, which requires a
- file name, the CBase marker string, and the offset addresses of
- the CBase and EBase typed constants. ChangesSaved performs
- several operations:
-
- * First the EXE file is opened. Parameter fileName must be the
- name of a compiled EXE file with CBase and EBase typed constants
- around the values to be modified, as shown in WINDGLOB.PAS.
-
- * Next, function FoundCBase returns true if the CBase marker
- string is found in the EXE code file. If so, FoundCBase returns
- the byte offset to this string. If not, FoundCBase returns
- false, causing an error message to be displayed.
-
- * If CBase is found, procedure SaveData copies the in-memory
- typed constants to the compiled EXE code file, overwriting only
- the bytes from CBase to the byte just before EBase. No other
- bytes are changed in the disk code file.
-
- For all this to work correctly, you must be sure to use the
- identical typed constants in both the main (WINDTEST) and
- installation (WINDINST) programs. The safest bet is to declare
- all typed constants in one separate unit (WINDGLOB) and then use
- the unit in both programs.
-
-
- Writing Your Own Installers
-
- Even without fully understanding every detail about how TCUnit
- operates, it's easy to write your own installation programs.
- Just follow these steps:
-
- 1. You don't have to modify TCUnit, although you might want to
- add procedures to prompt for specific data types as mentioned
- earlier.
-
- 2. Store all typed constants in a separate unit and compile.
- Start with CBase and end with EBase as in WINDGLOB.PAS. The
- number, type, names, and values of typed constants in between
- CBase and EBase are completely up to you.
-
- 3. Write your application, using the unit of typed constants from
- step 2. Compile to disk.
-
- 4. Write your installation program, using WINDINST.PAS as a
- guide. Add TCUNIT and your typed constants unit to the program's
- USES statement. Call ChangesSaved to write modified values to
- your program's code file.
-
- By the way, you can also use WINDINST to modify its own code
- file, WINDINST.EXE. Doing this displays the modified settings
- the next time you run the installation program. Also, the CBase
- marker string (see WINDGLOB.PAS) can be anything you like--it
- doesn't have to be '@CBASE@' as it is here. The string must be
- unique, occurring nowhere else in the code file. You might
- change CBase to your name or copyright. Then, if someone tries
- to remove your notice from the code file, the installation
- program will no longer work. Sneaky, no?
-
-
- Improving the Programs
-
- When you run these tests, you'll notice that WINDINST pauses for
- several seconds while hunting for CBase in the compiled code.
- There's a simple way to reduce this time. First, remove the (*
- and *) comment brackets from the two writeln statements inside
- TCUNIT's FoundCBase function. Recompile WINDINST and press Q.
- Jot down the reported offset. When I did this, I got 4768,
- although your number is likely to be different. The value is
- decimal, not hex.
- Edit TCUNIT.PAS again and replace the comment brackets in
- FoundCBase. Then change the double IF statement in ChangesSaved
- near the end of the program to read:
-
- IF err=0 THEN
- SaveData(f,4768,...)
-
- In other words, replace variable offset with the value you
- noted earlier and remove the call to FoundCBase. Now when you
- compile and run WINDINST, the disk update takes place almost
- instantaneously as TCUNIT no longer has to hunt for the offset to
- CBase.
- Perform this step only after compiling your main program for
- the last time. (Is there ever a last time?) Any changes to your
- typed constants or to the program require you to recalculate the
- offset in the code file. Using the wrong offset is almost sure
- to lead to a crash.
- Although these ideas and test programs are experimental, I'm
- planning to use them as the basis for an installation program in
- my Turbo Pascal database system as well as in other programs. In
- the past, I've found many uses for typed constants, but I'm
- planning to work them in more frequently, especially now that I
- have a way to demagnetize their previously all too static values.
-
-
- Listing 1: WINDGLOB.PAS
- Listing 2: WINDTEST.PAS
- Listing 3: WINDINST.PAS
- Listing 4: TCUNIT.PAS
-
- ###
-
-
- About the author...
-
- Tom Swan writes the monthly PC World columns Star-Dot-Star and
- Developer's Toolbox. He is a contributing editor to PC World and
- Programmer's Journal, and is the author of Mastering Turbo Assembler
- (1989) and Mastering Turbo Pascal 5.5 (Sep 1989) published by Howard
- W. Sams, Indianapolis. You can contact the author at:
-
- Swan Software
- P. O. Box 206
- Lititz, PA 17543
- (717) 627-1911
-
- Compuserve ID: 73627,3241
- MCI Mail handle: TSWAN
-
-
-