5.24 UNITs and the EXTERNAL Directive


5.24 UNITs and the EXTERNAL Directive

Technically, the #include directive provides you with all the facilities you need to create modular programs. You can create several modules, each containing some specific routine, and include those modules, as necessary, in your assembly language programs using #include. However, HLA provides a better way: external and public symbols.

One major problem with the #include mechanism is that once you've debugged a routine, including it into a compilation still wastes time because HLA must recompile bug-free code every time you assemble the main program. A much better solution would be to preassemble the debugged modules and link the object code modules together. This is what the @external directive allows you to do.

To use the @external facilities, you must create at least two source files. One file contains a set of variables and procedures used by the second. The second file uses those variables and procedures without knowing how they're implemented. The only problem is that if you create two separate HLA programs, the linker will get confused when you try to combine them. This is because both HLA programs have their own main program. Which main program does the OS run when it loads the program into memory? To resolve this problem, HLA uses a different type of compilation module, the unit, to compile programs without a main program. The syntax for an HLA unit is actually simpler than that for an HLA program; it takes the following form:

 unit unitname;      << declarations >> end unitname; 

With one exception (the var section), anything that can go in the declaration section of an HLA program can go into the declaration section of an HLA unit. Notice that a unit does not have a begin clause and there are no program statements in the unit;[27] a unit only contains declarations.

In addition to the fact that a unit does not contain a main program section, there is one other difference between units and programs. Units cannot have a var section. This is because the var section declares variables that are local to the main program's source code. Because there is no "main program" associated with a unit, var sections are illegal.[28]

To demonstrate, consider the two modules in Listings 5-19 and 5-20.

Listing 5-19: Example of a Simple HLA Unit.

start example
 unit Number1; static      Var1: uns32;      Var2: uns32;      procedure Add1and2;      begin Add1and2;           push( eax );           mov( Var2, eax );           add( eax, Var1 );      end Add1and2; end Number1; 
end example

Listing 5-20: Main Program That References External Objects.

start example
 program main; #include( "stdlib.hhf" ); begin main;      mov( 2, Var2 );      mov( 3, Var1 );      Add1and2();      stdout.put( "Var1=", Var1, nl ); end main; 
end example

The main program references Var1, Var2, and Add1and2, yet these symbols are external to this program (they appear in unit Number1). If you attempt to compile the main program as it stands, HLA will complain that these three symbols are undefined.

Therefore, you must declare them external with the @external option. An external procedure declaration looks just like a forward declaration except you use the reserved word @external rather than @forward. To declare external static variables, simply follow those variables' declarations with the reserved word @external. The program in Listing 5-21 is a modification to the program in Listing 5-20 that includes the external declarations.

Listing 5-21: Modified Main Program with Extrenal Declarations.

start example
 program main; #include( "stdlib.hhf" );      procedure Add1and2; external; static      Var1: uns32; @external;      Var2: uns32; @external; begin main;      mov( 2, Var2 );      mov( 3, Var1 );      Add1and2();      stdout.put( "Var1=", Var1, nl ); end main; 
end example

If you attempt to compile this second version of main, using the typical HLA compilation command "HLA main2.hla" you will be somewhat disappointed. This program will actually compile without error. However, when HLA attempts to link this code it will report that the symbols Var1, Var2, and Add1and2 are undefined. This happens because you haven't compiled and linked in the associated unit with this main program. Before you try that, and discover that it still doesn't work, you should know that all symbols in a unit, by default, are private to that unit. This means that those symbols are inaccessible in code outside that unit unless you explicitly declare those symbols as public symbols. To declare symbols as public, you simply put external declarations for those symbols in the unit before the actual symbol declarations. If an external declaration appears in the same source file as the actual declaration of a symbol, HLA assumes that the name is needed externally and makes that symbol a public (rather than private) symbol. The unit in Listing 5-22 is a correction to the Number1 unit that properly declares the external objects.

Listing 5-22: Correct Number1 Unit with External Declarations.

start example
 unit Number1; static      Var1:      uns32; @external;      Var2:      uns32; @external;      procedure Add1and2; @external; static      Var1:      uns32;      Var2:      uns32;      procedure Add1and2;      begin Add1and2;           push( eax );           mov( Var2, eax );           add( eax, Var1 );      end Add1and2; end Number1; 
end example

It may seem redundant declaring these symbols twice as occurs in Listings 5.21 and 5.22, but you'll soon seen that you don't normally write the code this way.

If you attempt to compile the main program or the Number1 unit using the typical HLA statement, i.e.,

 HLA main2.hla HLA unit2.hla 

you'll quickly discover that the linker still returns errors. It returns an error on the compilation of main2.hla because you still haven't told HLA to link in the object code associated with unit2.hla. Likewise, the linker complains if you attempt to compile unit2.hla by itself because it can't find a main program. The simple solution is to compile both of these modules together with the following single command:

 HLA main2.hla unit2.hla 

This command will properly compile both modules and link together their object code.

Unfortunately, the command above defeats one of the major benefits of separate compilation. When you issue this command it will compile both main2 and unit2 prior to linking them together. Remember, a major reason for separate compilation is to reduce compilation time on large projects. While the above command is convenient, it doesn't achieve this goal.

To separately compile the two modules you must run HLA separately on them. Of course, we saw earlier that attempting to compile these modules separately produced linker errors. To get around this problem, you need to compile the modules without linking them. The "-c" (compile-only) HLA command line option achieves this. To compile the two source files without running the linker, you would use the following commands:

 HLA -c main2.hla HLA -c unit2.hla 

This produces two object code files, main2.obj and unit2.obj, that you can link together to produce a single executable. You could run the linker program directly, but an easier way is to use the HLA compiler to link the object modules together for you:

 HLA main2.obj unit2.obj 

Under Windows, this command produces an executable file named main2.exe;[29] under Linux, this command produces a file named main2. You could also type the following command to compile the main program and link it with a previously compiled unit2 object module:

 HLA main2.hla unit2.obj 

In general, HLA looks at the suffixes of the filenames following the HLA commands. If the filename doesn't have a suffix, HLA assumes it to be ".HLA." If the filename has a suffix, then HLA will do the following with the file:

  • If the suffix is ".HLA", HLA will compile the file with the HLA compiler.

  • If the suffix is ".ASM", HLA will assemble the file with MASM (Windows) or Gas (Linux).

  • If the suffix is ".OBJ" or ".LIB"(Windows), or ".o" or ".a" (Linux), then HLA will link that module with the rest of the compilation.

5.24.1 Behavior of the EXTERNAL Directive

Whenever you declare a symbol using the @external directive, keep in mind several limitations of @external objects:

  • Only one @external declaration of an object may appear in a given source file. That is, you cannot define the same symbol twice as an @external object.

  • Only procedure, static, readonly, and storage variable objects can be external. var, type, const and parameter objects cannot be external.

  • External objects must appear at the global declaration level. You cannot declare @external objects within a procedure or other nested structure.[30]

  • @external objects publish their name globally. Therefore, you must carefully choose the names of your @external objects so they do not conflict with other symbols.

This last point is especially important to keep in mind. As this text is being written, the HLA compiler translates your HLA source code into assembly code. HLA assembles the output by using MASM (the Microsoft Macro Assembler), Gas (Gnu's as), or some other assembler. Finally, HLA links your modules using a linker. At each step in this process, your choice of external names could create problems for you.

Consider the following HLA external/public declaration:

 static           extObj:           uns32; @external;           extObj:           uns32;           localObject:      uns32; 

When you compile a program containing these declarations, HLA automatically generates a "munged" name for the localObject variable that probably isn't ever going to have any conflicts with system-global external symbols.[31] Whenever you declare an external symbol, however, HLA uses the object's name as the default external name. This can create some problems if you inadvertently use some global name as your variable name. Worse still, the assembler will not be able to properly process HLA's output if you happen to choose an identifier that is legal in HLA but is one of the assembler's reserved words. For example, if you attempt to compile the following code fragment as part of an HLA program (producing MASM output), it will compile properly, but MASM will not be able to assemble the code:

 static      c: char; @external;      c: char; 

The reason MASM will have trouble with this is because HLA will write the identifier "c" to the assembly language output file, and it turns out that "c" is a MASM reserved word (MASM uses it to denote C-language linkage).

To get around the problem of conflicting external names, HLA supports an additional syntax for the @external option that lets you explicitly specify the external name. The following example demonstrates this extended syntax:

 static      c: char; @external( "var_c" );      c: char; 

If you follow the @external keyword with a string constant enclosed by parentheses, HLA will continue to use the declared name (c in this example) as the identifier within your HLA source code. Externally (i.e., in the assembly code) HLA will substitute the name var_c whenever you reference c. This feature helps you avoid problems with the misuse of assembler reserved words, or other global symbols, in your HLA programs.

You should also note that this feature of the @external option lets you create aliases. For example, you may want to refer to an object by the name StudentCount in one module while refer to the object as PersonCount in another module (you might do this because you have a general library module that deals with counting people and you want to use the object in a program that deals only with students). Using a declaration like the following lets you do this:

 static      StudentCount: uns32; @external( "PersonCount" ); 

Of course, you've already seen some of the problems you might encounter when you start creating aliases. So you should use this capability sparingly in your programs. Perhaps a more reasonable use of this feature is to simplify certain OS APIs. For example, the Win32 API uses some really long names for certain procedure calls. You can use the @external directive to provide a more meaningful name than the standard one the operating system specifies.

5.24.2 Header Files in HLA

HLA's technique of using the same @external declaration to define public as well as external symbols may seem somewhat counter-intuitive. Why not use a @public reserved word for public symbols and the @external keyword for external definitions? Well, as counterintuitive as HLA's external declarations may seem, they are founded on decades of solid experience with the C/C++ programming language that uses a similar approach to public and external symbols.[32] Combined with a header file, HLA's external declarations make large program maintenance a breeze.

An important benefit of the @external directive (versus separate @public and @external directives) is that it lets you minimize duplication of effort in your source files. Suppose, for example, you want to create a module with a bunch of support routines and variables for use in several different programs (e.g., the HLA Standard Library). In addition to sharing some routines and some variables, suppose you want to share constants, types, and other items as well.

The #include file mechanism provides a perfect way to handle this. You simply create a #include file containing the constants, macros, and @external definitions and include this file in the module that implements your routines and in the modules that use those routines (see Figure 5-10).

click to expand
Figure 5-10: Using Header Files in HLA Programs.

A typical header file contains only const, val, type, static, readonly, storage, and procedure prototypes (plus a few others we haven't looked at yet, like macros). Objects in the static, readonly, and storage sections, as well as all procedure declarations, are always @external objects. In particular, you should not put any var objects in a header file, nor should you put any non-external variables or procedure bodies in a header file. If you do, HLA will make duplicate copies of these objects in the different source files that include the header file. Not only will this make your programs larger, but it will cause them to fail under certain circumstances. For example, you generally put a variable in a header file so you can share the value of that variable among several different modules. However, if you fail to declare that symbol as external in the header file and just put a standard variable declaration there, each module that includes the source file will get its own separate variable: The modules will not share a common variable.

If you create a standard header file, containing const, val, and type declarations, and external objects, you should always be sure to include that file in the declaration section of all modules that need the definitions in the header file. Generally, HLA programs include all their header files in the first few statements after the program or unit header.

This text adopts the HLA Standard Library convention of using an ".hhf" suffix for HLA header files ("HHF" stands for HLA header file).

[27]Of course, units may contain procedures and those procedures may have statements, but the unit itself does not have any executable instructions associated with it.

[28]Of course, procedures in the unit may have their own var sections, but the procedure's declaration section is separate from the unit's declaration section.

[29]If you want to explicitly specify the name of the output file, HLA provides a command line option to achieve this. You can get a menu of all legal command line options by entering the command "HLA -?".

[30]There are a few exceptions, but you cannot declare external procedures or variables except at the global level.

[31]Typically, HLA creates a name like ?001A_localObject out of localObject. This is a legal MASM identifier, but it is not likely it will conflict with any other global symbols when HLA compiles the program with MASM.

[32]Actually, C/C++ is a little different. All global symbols in a module are assumed to be public unless explicitly declared private. HLA's approach (forcing the declaration of public items via external) is a little safer.




The Art of Assembly Language
The Art of Assembly Language
ISBN: 1593272073
EAN: 2147483647
Year: 2005
Pages: 246
Authors: Randall Hyde

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net