15.4 Programming in CC and HLA


15.4 Programming in C/C++ and HLA

Without question, the most popular language used to develop applications is, uh, Visual Basic. We're not going to worry about interfacing Visual Basic to assembly in this text for two reasons: (1) Visual Basic programmers will get better control and performance from their code if they learn Delphi/Kylix, and (2) Visual Basic's interface to assembly is via dynamic linked libraries (which is just beyond the scope of this text, because DLLs are very OS specific). Coming in second as the development language of choice is C/C++. The C/C++ interface to assembly language is a bit different than Pascal/Kylix. That's why this section appears in this text.

Unlike Pascal/Kylix, that has only a single vendor, there are many different C/C++ compilers available on the market. Each vendor (Microsoft, Borland, Watcom, GNU, and so on) has its own ideas about how C/C++ should interface to external code. Many vendors have their own extensions to the C/C++ language to aid in the interface to assembly and other languages. For example, Borland provides a special keyword to let Borland C++ (and C++ Builder) programmers call Pascal code (or, conversely, allow Pascal code to call the C/C++ code). Microsoft, which stopped making Pascal compilers years ago, no longer supports this option. This is unfortunate because HLA, by default, uses the Pascal calling conventions. Fortunately, HLA provides a special interface to code that C/C++ systems generate.

Before we get started discussing how to write HLA modules for your C/C++ programs, you must understand two very important facts:

  1. HLA's exception handling facilities are not directly compatible with C/C++'s exception handling facilities. This means that you cannot use the try..endtry and raise statements in the HLA code you intend to link to a C/C++ program. This also means that you cannot call library functions that contain such statements. Because the HLA Standard Library modules use exception handling statements all over the place, this effectively prevents you from calling HLA Standard Library routines from the code you intend to link with C/C++.[7]

  2. You cannot call console-related functions (e.g., stdin.xxxx or stdout.xxxx) from a GUI application (Windows or Linux). Even if HLA's console and standard input/output routines didn't use exception handling, you wouldn't be able to call them from a GUI C/C++ application. Even if you are writing a console application in C/C++, you still shouldn't call the stdin.xxxx or stdout.xxx routines because they could raise an HLA exception.

Given the rich set of language features that C/C++ supports, it should come as no surprise that the interface between the C/C++ language and assembly language is somewhat complex. Fortunately there are two facts that reduce this problem. First, HLA (1.26 and later) supports C/C++'s calling conventions. Second, the other complex stuff you won't use very often, so you may not have to bother with it.

Note

The following sections assume you are already familiar with C/C++ programming. They make no attempt to explain C/C++ syntax or features other than as needed to explain the C/C++ assembly language interface. If you're not familiar with C/C++, you will probably want to skip this section.

Note

Although this text uses the generic term "C/C++" when describing the interface between HLA and various C/C++ compilers, the truth is that you're really interfacing HLA with the C language. There is a fairly standardized interface between C and assembly language that most vendors follow. No such standard exists for the C++ language and every vendor, if it even supports an interface between C++ and assembly, uses a different scheme. In this text we will stick to interfacing HLA with the C language. Fortunately, all popular C++ compilers support the C interface to assembly, so this isn't much of a problem.

The examples in this text will use the Borland C++ compiler, GNU C++, and Microsoft's Visual C++ compiler. There may be some minor adjustments you need to make if you're using some other C/C++ compiler; please see the vendor's documentation for more details. This text will note differences between Borland's, GNU/FSF's, and Microsoft's offerings, as necessary.

15.4.1 Linking HLA Modules with C/C++ Programs

One big advantage of C/C++ over Pascal/Kylix is that (most) C/C++ compiler vendors' products emit standard object files. So, working with object files and a true linker is much nicer than having to deal with Delphi/Kylix's built-in linker. As nice as the Pascal/Kylix system is, interfacing with assembly language is much easier in C/C++ than in Kylix.

Under Windows, the Visual C++ compiler works with COFF object files, and the Borland C++ compiler works with OMF object files. Both forms of object files use the OBJ extension, so you can't really tell by looking at a directory listing which form you've got. Fortunately, if you need to create a single OBJ file that will work with both, the Visual C++ compiler will also accept OMF files and convert them to a COFF file during the link phase. Of course, most of the time you will not be using both compilers, so you can pick whichever OBJ file format you're comfortable with and use that. Linux users have it a little easier because most Linux compilers work strictly with ELF object files.

By default, HLA tells MASM to produce a COFF file when assembling the HLA output. This means that if you compile an HLA program using a command line like the following, you will not be able to directly link the code with Borland C++ code:

 hla -c filename.hla      // The "-c" option tells HLA to compile and assemble. 

If you want to create an OMF file rather than a COFF file, you can do so by using the following command:

 hla -o:omf filename.hla      // The "-o:omf" option tells HLA to compile to OMF. 

The execution of this command produces an OMF object file that both VC++ and BCC (Borland C++) will accept (though VC++ prefers COFF, it accepts OMF).

Both BCC and VC++ look at the extension of the source filenames you provide on the command line to determine whether they are compiling a C or a C++ program. There are some minor syntactical differences between the external declarations for a C and a C++ program. This text assumes that you are compiling C++ programs that have a ".cpp" extension. The difference between a C and a C++ compilation occurs in the external declarations for the functions you intend to write in assembly language. For example, in a C source file you would simply write:

 extern char* RetHW( void ); 

However, in a C++ environment, you would need the following external declaration:

 extern "C" {      extern char* RetHW( void ); }; 

The ‘extern "C"’ clause tells the compiler to use standard C linkage even though the compiler is processing a C++ source file (C++ linkage is different than C and definitely far more complex; this text will not consider pure C++ linkage because it varies so much from vendor to vendor). If you're going to compile C source files with VC++ or BCC (i.e., files with a ".c" suffix), simply drop the ‘extern "C"’ and the curly braces from around the external declarations. GNU GCC users should always use the "extern" option.

Listing 15-19 demonstrates this external linkage mechanism by writing a short HLA program that returns the address of a string ("Hello World") in the EAX register (like Pascal/Kylix, C/C++ expects functions to return their results in EAX). Then the main C/C++ program prints this string to the console device, as shown in Listing 15-20.

Listing 15-19: Cex1: A Simple Example of a Call to an Assembly Function from C++.

start example
 #include <stdlib.h> #include "ratc.h" extern "C" {     extern char* ReturnHW( void ); }; int main() _begin( main )     printf( "%s\n", ReturnHW() );     _return 0; _end( main ) 
end example

Listing 15-20: RetHW.hla: Assembly Code That Cex1 Calls.

start example
 unit ReturnHWUnit;     procedure ReturnHW; @external( "_ReturnHW" ); // Drop string for GCC.     procedure ReturnHW; nodisplay; noframe; noalignstk;     begin ReturnHW;         lea( eax, "Hello World" );         ret();     end ReturnHW; end ReturnHWUnit; 
end example

There are several new things in both the C/C++ and HLA code that might confuse you at first glance, so let's discuss these things real quick here.

The first strange thing you will notice in the C++ code is the #include "ratc.h" statement. RatC is a C/C++ macro library that adds new features to the C++ language. RatC adds several interesting features and capabilities to the C/C++ language, but a primary purpose of RatC is to help make C/C++ programs a little more readable. Of course, if you've never seen RatC before, you'll probably argue that it's not as readable as pure C/C++, but even someone who has never seen RatC before can figure out 80 percent of RatC within a few minutes. In the preceding example, the _begin and _end clauses clearly map to the "{" and "}" symbols (notice how the use of _begin and _end make it clear what function or statement associates with the braces; unlike the guesswork you've got in standard C). The _return statement is clearly equivalent to the C return statement. As you'll quickly see, all of the standard C control structures are improved slightly in RatC. You'll have no trouble recognizing them because they use the standard control structure names with an underscore prefix. This text promotes the creation of readable programs, hence the use of RatC in the examples appearing in this chapter.[8] The RatC macro package appears on the accompanying CDROM.

The C/C++ program isn't the only source file to introduce something new. If you look at the HLA code you'll notice that the lea instruction appears to be illegal. It takes the following form:

      lea( eax, "Hello World" ); 

The lea instruction is supposed to have a memory and a register operand. This example has a register and a constant; what is the address of a constant, anyway? Well, this is a syntactical extension that HLA provides to 80x86 assembly language. If you supply a constant instead of a memory operand to lea, HLA will create a static (readonly) object initialized with that constant and the lea instruction will return the address of that object. In this example, HLA will output the string data to the constants segment and then load EAX with the address of the first character of that string. Since HLA strings always have a zero terminating byte, EAX will contain the address of a zero terminated string which is exactly what C++ wants. If you look back at the original C++ code, you will see that rethw returns a char* object and the main C++ program displays this result on the console device.

If you haven't figured it out yet, this is a roundabout version of the venerable "Hello World" program.

Microsoft VC++ users can compile this program from the command line by using the following commands:[9]

 hla -c RetHW.hla           // Compiles and assembles RetHW.hla to RetHW.obj cl Cex1.cpp RetHW.obj      // Compiles C++ code and links it with RetHW.obj 

If you are a Borland C++ user, you would use the following command sequence:

 hla -o:omf RetHW.hla           // Compile HLA file to an OMF file. bcc32i Cex1.cpp RetHW.obj      // Compile and link C++ and assembly code.                                // Could also use the BCC32 compiler. 

GCC users can compile this program from the command line by using the following commands:

 hla -c RetHW.hla           // Compile HLA file to an ELF file. gcc Cex1.cpp RetHW.o       // Compile and link C++ and assembly code.                            // Could also use the BCC32 compiler. 

15.4.2 Register Preservation

For C/C++ compilers, there is no single list of registers that you can freely use as scratchpad values within an assembly language function. The list changes by vendor and even changes between versions from the same vendor. However, you can safely assume that EAX is available for scratchpad use because C functions return their result in the EAX register. You should probably preserve everything else. Note that Intel has a standard called the Intel ABI (application binary interface). This standard specifies that procedures may freely use EAX, ECX, and EDX without preservation, but procedures must preserve all other registers across the call. Most C/C++ compilers conform to this ABI. However, there are some compilers that do not, so be sure to check your compiler vendor's documentation to verify this.

15.4.3 Function Results

C/C++ compilers universally seem to return ordinal and pointer function results in AL, AX, or EAX depending on the operand's size. The compilers probably return floating point results on the top of the FPU stack as well. Other than that, check your C/C++ vendor's documentation for more details on function return locations.

15.4.4 Calling Conventions

The standard C/C++ calling convention is probably the biggest area of contention between the C/C++ and HLA languages. VC++ and BCC both support multiple calling conventions. BCC even supports the Pascal calling convention that HLA uses, making it trivial to write HLA functions for BCC programs. However, before we get into the details of these other calling conventions, it's probably a wise idea to first discuss the standard C/C++ calling convention.

Both VC++ and BCC decorate the function name when you declare an external function. For external "C" functions, the decoration consists of an underscore. If you look back at Listing 15-20 you'll notice that the external name the HLA program actually uses is "_RetHW" rather than simply "RetHW". The HLA program itself, of course, uses the symbol "RetHW" to refer to the function, but the external name (as specified by the optional parameter to the @external option) is "_RetHW". In the C/C++ program (Listing 15-19) there is no explicit indication of this decoration; you simply have to read the compiler documentation to discover that the compiler automatically prepends this character to the function name.[10] Fortunately, HLA's @ external option syntax allows us to undecorate the name, so we can refer to the function using the same name as the C/C++ program. Name decoration is a trivial matter, easily fixed by HLA.

Linux users should note that GCC does not, by default, require the prepended underscore decoration. If you're writing code that needs to link with code any of these three C/C++ compilers, you might consider doing the following:

 const     cPrefix :text := "_"; // Set to the empty string for GCC.          .          .          . procedure someProc; @external( cPrefix "someProc" ); 

Remember, when HLA sees two string constants adjacent to one another (as it will once HLA expands the text constant to either "" or "_") it simply concatenates the two strings. By sticking the cPrefix string constant before the procedure's name in all the @external options in your code, you can easily decorate or not decorate all the external names by simply changing one line of code in your program (the constant declaration for cPrefix).

A big problem is the fact that C/C++ pushes parameters on the stack in the opposite direction of just about every other (non–C-based) language on the planet; specifically, C/C++ pushes actual parameters on the stack from right to left instead of the more common left to right. This means that you cannot declare a C/C++ function with two or more parameters and use a simple translation of the C/C++ external declaration as your HLA procedure declaration, i.e., the following are not equivalent:

 extern void CToHLA( int p, unsigned q, double r ); procedure CToHLA( p:int32; q:uns32; r:real64 ); @external( cPrefix "CToHLA" ); 

Were you to call CToHLA from the C/C++ program, the compiler would push the r parameter first, the q parameter second, and the p parameter third — exactly the opposite order that the HLA code expects. As a result, the HLA code would use the L.O. double word of r as p's value, the H.O. double word of r as q's value, and the combination of p and q's values as the value for r. Obviously, you'd most likely get an incorrect result from this calculation. Fortunately, there's an easy solution to this problem: use the @cdecl procedure option in the HLA code to tell it to reverse the parameters:

 procedure CToHLA( p:int32; q:uns32; r:real64 );     @cdecl;     @external( cPrefix "CToHLA" ); 

Now when the C/C++ code calls this procedure, it pushes the parameters on the stack, and the HLA code will retrieve them in the proper order.

There is another big difference between the C/C++ calling convention and HLA: HLA procedures automatically clean up after themselves by removing all parameters pass to a procedure prior to returning to the caller. C/C++, on the other hand, requires the caller, not the procedure, to clean up the parameters. This has two important ramifications: (1) if you call a C/C++ function (or one that uses the C/C++ calling sequence), then your code has to remove any parameters it pushed upon return from that function; (2) your HLA code cannot automatically remove parameter data from the stack if C/C++ code calls it. The @cdecl procedure option tells HLA not to generate the code that automatically removes parameters from the stack upon return. Of course, if you use the @noframe option, you must ensure that you don't remove these parameters yourself when your procedures return to their caller.

One thing HLA cannot handle automatically for you is removing parameters from the stack when you call a procedure or function that uses the @cdecl calling convention; for example, you must manually pop these parameters whenever you call a C/C++ function from your HLA code.

Removing parameters from the stack when a C/C++ function returns to your code is very easy, just execute an "add( constant, esp );" instruction where constant is the number of parameter bytes you've pushed on the stack. For example, the CToHLA function has 16 bytes of parameters (two int32 objects and one real64 object) so the calling sequence (in HLA) would look something like the following:

      CToHLA( pVal, qVal, rVal ); // Assume this is the macro version.      add( 16, esp );             // Remove parameters from the stack. 

Cleaning up after a call is easy enough. However, if you're writing the function that must leave it up to the caller to remove the parameters from the stack, then you've got a tiny problem — by default, HLA procedures always clean up after themselves. If you use the @cdecl option and don't specify the @noframe option, then HLA automatically handles this for you. However, if you use the @noframe option, then you've got to ensure that you leave the parameter data on the stack when returning from a function/procedure that uses the @cdecl calling convention.

If you want to leave the parameters on the stack for the caller to remove, then you must write the standard entry and exit sequences for the procedure that build and destroy the activation record (for details, see the chapter on procedures). This means you've got to use the @noframe and @nodisplay options on your procedures that C/C++ will call. Here's a sample implementation of the CToHLA procedure that builds and destroys the activation record:

 procedure _CToHLA( rValue:real64; q:uns32; p:int32 ); @nodisplay; @noframe; begin _CToHLA;      push( ebp );           // Standard Entry Sequence      mov( esp, ebp );      // sub( _vars_, esp ); // Needed if you have local variables.           .           .      // Code to implement the function's body.           .      mov( ebp, esp );       // Restore the stack pointer.      pop( ebp );            // Restore link to previous activation record.      ret();                 // Note that we don't remove any parameters. end _CToHLA; 

If you're willing to use some vendor extensions to the C/C++ programming language, then you can make the interface to HLA much simpler. For example, if you're using Borland's C++ product, it has an option you can apply to function declarations to tell the compiler to use the Pascal calling convention. Because HLA uses the Pascal calling convention, specifying this option in your BCC programs will make the interface to HLA trivial. In Borland C++ you can specify the Pascal calling convention for an external function using the following syntax:

 extern type _pascal funcname( parameters ) 

Example:

 extern void _pascal CToHLA( int p, unsigned q, double r ); 

The Pascal calling convention does not decorate the name, so the HLA name would not have a leading underscore. The Pascal calling convention uses caseinsensitive names; BCC achieves this by converting the name to all uppercase. Therefore, you'd probably want to use an HLA declaration like the following:

 procedure CToHLA( p:int32; q:uns32; r:real64 ); @external( "CTOHLA" ); 

Procedures using the Pascal calling convention push their parameters from left to right and leave it up to the procedure to clean up the stack upon return, exactly what HLA does by default. When using the Pascal calling convention, you could write the CToHLA function as follows:

 procedure CToHLA( rValue:real64; q:uns32; p:int32 ); @external( "CTOHLA" ); procedure CToHLA( rValue:real64; q:uns32; p:int32 ); @nodisplay; @noalignstack; begin CToHLA;           .           .      // Code to implement the function's body.           . end CToHLA; 

Note that you don't have to supply the standard entry and exit sequences. HLA provides those automatically.

Of course, Microsoft isn't about to support the Pascal calling sequence because they don't have a Pascal compiler. So this option isn't available to VC++ users.

Both Borland and Microsoft (and HLA) support the so-called StdCall calling convention. This is the calling convention that Windows uses, so nearly every language that operates under Windows provides this calling convention. The StdCall calling convention is a combination of the C and Pascal calling conventions. Like C, the functions need to have their parameters pushed on the stack in a right to left order; like Pascal, it is the caller's responsibility to clean up the parameters when the function returns; like C, the function name is case sensitive; like Pascal, the function name is not decorated (i.e., the external name is the same as the function declaration). The syntax for a StdCall function is the same in both VC++ and BCC; it is the following:

 extern void _stdcall CToHLA( int p, unsigned q, double r ); 

HLA supports the StdCall convention using the @stdcall procedure option.. Because the name is undecorated, you could use a prototype and macro like the following:

 procedure CToHLA( p:int32; q:uns32; r:real64 ); @stdcall; @external( "CToHLA" ); procedure CToHLA( p:int32; q:uns32; r:real64 ); @nodisplay; @noalignstack; begin CToHLA;      .      .   // Function body      . end CToHLA;      .      .      . CToHLA( pValue, qValue, rValue ); // Demo of a call to CToHLA. 

15.4.5 Pass by Value and Reference in C/C++

A C/C++ program can pass parameters to a procedure or function using one of two different mechanisms: pass by value and pass by reference. Because pass by reference parameters use pointers, this parameter passing mechanism is completely compatible between HLA and C/C++. The following two lines provide an external declaration in C++ and the corresponding external (public) declaration in HLA for a pass by reference parameter using the calling convention:

 extern void HasRefParm( int& refparm );                    // C++ procedure HasRefParm( var refparm: int32 ); @external;     // HLA 

Like HLA, C++ will pass the 32-bit address of whatever actual parameter you specify when calling the HasRefParm procedure. Don't forget that inside the HLA code, you must dereference this pointer to access the actual parameter data. See the chapter on procedures for more details.

Like HLA, C++ lets you pass untyped parameters by reference. The syntax to achieve this in C++ is the following:

 extern void UntypedRefParm( void* parm1 ); 

Actually, this is not a reference parameter, but a value parameter with an untyped pointer.

In HLA, you can use the var keyword as the data type to specify that you want an untyped reference parameter. Here's the corresponding prototype for the UntypedRefParm procedure in HLA:

 procedure UntypedRefParm( var parm1:var );     @external; 

15.4.6 Scalar Data Type Correspondence Between C/C++ and HLA

When passing parameters between C/C++ and HLA procedures and functions, it's very important that the calling code and the called code agree on the basic data types for the parameters. In this section we will draw a correspondence between the C/C++ scalar data types and the HLA (1.x) data types.

Assembly language supports any possible data format, so HLA's data type capabilities will always be a superset of C/C++'s. Therefore, there may be some objects you can create in HLA that have no counterpart in C/C++, but the reverse is not true. Because the assembly functions and procedures you write are generally manipulating data that C/C++ provides, you don't have to worry too much about not being able to process some data passed to an HLA procedure by C/C++.

C/C++ provides a wide range of different integer data types. Unfortunately, the exact representation of these types is implementation specific. Table 15-5 lists the C/C++ types as currently implemented by Borland C++, GNU's GCC, and Microsoft VC++. These types may very well change as 64-bit compilers become available.

Table 15-5: C/C++ and HLA Integer Types

C/C++

HLA Equivalent

Range


Minimum

Maximum


int

int32

2147483648

2147483647


unsigned

uns32

0

4294967295


signed char

int8

128

127


short

int16

32768

32767


long

int32

2147483648

2147483647


unsigned char

uns8

0

255


unsigned short

uns16

0

65535

In addition to the integer values, C/C++ supports several non-integer ordinal types. Table 15-6 provides their HLA equivalents.

Table 15-6: Non-integer Ordinal Types in C/C++ and HLA

C/C++

HLA

Range


Minimum

Maximum


wchar, TCHAR

wchar

#0

#65535


BOOL

boolean

false (0)

true ( not zero )

Like the integer types, C/C++ supports a wide range of real numeric formats. Table 15-7 presents these types and their HLA equivalents.

Table 15-7: Real Types in C/C++ and HLA

C/C++

HLA

Range


Minimum

Maximum


double

real64

5.0 E-324

1.7 E+308


float

real32

1.5 E-45

3.4 E+38


long double[1]

real80

3.6 E-4951

1.1 E+4932

[1]This data type is 80 bits only in BCC. GCC and VC++ uses 64 bits for the long double type.

The last scalar type of interest is the pointer type. Both HLA and C/C++ use a 32-bit address to represent pointers, so these data types are completely equivalent in both languages.

15.4.7 Passing String Data Between C/C++ and HLA Code

C/C++ uses zero terminated strings. Algorithms that manipulate zero terminated strings are not as efficient as functions that work on length prefixed strings; on the plus side, however, zero terminated strings are very easy to work with. HLA's strings are downward compatible with C/C++ strings because HLA places a zero byte at the end of each HLA string. Because you'll probably not be calling HLA Standard Library string routines, the fact that C/C++ strings are not upward compatible with HLA strings generally won't be a problem. If you do decide to modify some of the HLA string functions so that they don't raise exceptions, you can always translate the str.cStrToStr function that translates zero terminated C/C++ strings to HLA strings.

A C/C++ string variable is typically a char* object or an array of characters. In either case, C/C++ will pass the address of the first character of the string to an external procedure whenever you pass a string as a parameter. Within the procedure, you can treat the parameter as an indirect reference and dereference to pointer to access characters within the string.

15.4.8 Passing Record/Structure Data Between HLA and C/C++

Records in HLA are (mostly) compatible with C/C++ structs. You can easily translate a C/C++ struct to an HLA record. In this section we'll explore how to do this and learn about the incompatibilities that exist between HLA records and C/C++ structures.

For the most part, translating C/C++ records to HLA is a no-brainer. Just grab the "guts" of a structure declaration and translate the declarations to HLA syntax within a record..endrecord block and you're done.

Consider the following C/C++ structure type declaration:

 typedef struct {     unsigned char day;     unsigned char month;     int year;     unsigned char dayOfWeek; } dateType; 

The translation to an HLA record is, for the most part, very straight-forward. Just translate the field types accordingly and use the HLA record syntax and you're in business. The translation is the following:

 type      recType:           record                day: byte;                month: byte;                year:int32;                dayOfWeek:byte;           endrecord; 

There is one minor problem with this example: data alignment. Depending on your compiler and whatever defaults it uses, C/C++ might not pack the data in the structure as compactly as possible. Most C/C++ compilers will attempt to align the fields on double word or other boundaries. With double word alignment of objects larger than a byte, the previous C/C++ typedef statement is probably better modeled by:

 type      recType:           record                day: byte;                month: byte;                padding:byte[2];      // Align year on a four-byte boundary.                year:int32;                dayOfWeek:byte;                morePadding: byte[3]; // Make record an even multiple of four bytes.           endrecord; 

Of course, a better solution is to use HLA's align directive to automatically align the fields in the record:

 type      recType:           record                day: byte;                month: byte;                align( 4 );      // Align year on a four-byte boundary.                year:int32;                dayOfWeek:byte;                align(4);        // Make record an even multiple of four bytes.           endrecord; 

Alignment of the fields is good insofar as access to the fields is faster if they are aligned appropriately. However, aligning records in this fashion does consume extra space (five bytes in the preceding examples) and that can be expensive if you have a large array of records whose fields need padding for alignment.

You will need to check your compiler vendor's documentation to determine whether it packs or pads structures by default. Most compilers give you several options for packing or padding the fields on various boundaries. Padded structures might be a bit faster, while packed structures (i.e., no padding) are going to be more compact. You'll have to decide which is more important to you and then adjust your HLA code accordingly.

Since HLA 1.37 became available, HLA has provided some additional features making it possible for HLA to automatically align fields of a record on various boundaries. These new features in HLA were added specifically to make HLA easier to use with a wide variety of C/C++ compilers. The syntax that supports automatic field alignment is the following:

 type      rType :record [ maxAlign : minAlign ];           << fields >>      endrecord; 

The maxalign and minalign items are constant expressions. HLA will compare the size of each field with these two values. If the size of a particular field is less than minAlign, then HLA will automatically align that field on a boundary that is an even multiple of minAlign. If the size of the field is greater than maxAlign, then HLA will align that field on a boundary that is an even multiple of maxAlign. If the size of the field is between minAlign and maxAlign, then HLA will align the field on a boundary that is an even multiple of the field's size. This is usually sufficient to match the alignment that any given C/C++ compiler will use. For more details on record field alignment, please see the HLA Reference Manual on the accompanying CD-ROM.

Note that by default, C/C++ passes structures by value. A C/C++ program must explicitly take the address of a structure object and pass that address in order to simulate pass by reference. In general, if the size of a structure exceeds about 16 bytes, you should pass the structure by reference rather than by value.

15.4.9 Passing Array Data Between HLA and C/C++

Passing array data between some procedures written in C/C++ and HLA is little different than passing array data between two HLA procedures. First of all, C/C++ can only pass arrays by reference, never by value. Therefore, you must always use pass by reference inside the HLA code. The following code fragments provide a simple example:

      int CArray[128][4]; extern void PassedArrray( int array[128][4] ); 

Corresponding HLA code:

 type      CArray: int32[ 128, 4]; procedure PassedArray( var ary: CArray ); @external; 

As the above examples demonstrate, C/C++'s array declarations are similar to HLA's insofar as you specify the bounds of each dimension in the array.

C/C++ uses row major ordering for arrays. So if you're accessing elements of a C/C++ multidimensional array in HLA code, be sure to use the row major order computation.

[7]Note that the HLA Standard Library source code is available; feel free to modify the routines you want to use and remove any exception handling statements contained therein.

[8]If RatC really annoys you, just keep in mind that you've only got to look at a few RatC programs in this chapter. Then you can go back to the old-fashioned C code and hack to your heart's content!

[9]This text assumes you've executed the VCVARS32.BAT file that sets up the system to allow the use of VC++ from the command line.

[10]Most compilers provide an option to turn this off if you don't want this to occur. We will assume that this option is active in this text because that's the standard for external C names.




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