<--Last Chapter | Table of Contents | Next Chapter--> |
ACT's JGNAT compiler will compile Gnat source files into Java applications or applets. This section introduces JGNAT.
Java is an interpreted language. It is compiled in an artificial machine language for a computer that doesn't exist. This computer is called the Java Virtual Machine (JVM) and the instructions are known as bytecode. The process is similar to the one used by the UCSD Pascal, a popular language from the 1980s that also used a virtual machine language (called P-Code).
When Java is compiled, the source code is converted to a .class file. This file contains the instructions for the JVM.
The JVM language is not directly related to Java. It is possible for other languages to create JVM executables. The JGNAT compiler converts Ada source files into .class files that can be executed by Java.
The official Java Virtual Machine Specification is available at Sun's Java website.
jgnatmake hello
Table: jgnatmake switches
JGnatmake Switch | Description |
-a | Consider all files, even readonly ali files |
-c | Compile only, do not bind and link |
-f | Force recompilations of non predefined units |
-i | In place. Replace existing ali file, or put it with source |
-jnum | Use nnn processes to compile |
-k | Keep going after compilation errors |
-m | Minimal recompilation |
-M | List object file dependences for Makefile |
-n | Check objects up to date, output next file to compile if not |
-o name | Choose an alternate executable name |
-q | Be quiet/terse |
-s | Recompile if compiler switches have changed |
-v | Display reasons for all (re)compilations |
-z | No main subprogram (zero main) |
--GCC=command | Use this jgnat command |
--GNATBIND=command | Use this gnatbind command |
--GNATLINK=command | Use this gnatlink command |
-aLdir | Skip missing library sources if ali in dir |
-Adir | like -aLdir -aIdir |
-aOdir | Specify library/object files search path |
-aIdir | Specify source files search path |
-Idir | Like -aIdir -aOdir |
-I- | Don't look for sources & library files in the default directory |
-Ldir | Look for program libraries also in dir |
-nostdinc | Don't look for sources in the system default directory |
-nostdlib | Don't look for library files in the system default directory |
-cargs opts | opts are passed to the compiler |
-bargs opts | opts are passed to the binder |
-largs opts | opts are passed to the linker |
-g | Generate debugging information |
-Idir | Specify source files search path |
-I- | Do not look for sources in current directory |
-O[0123] | Control the optimization level |
-gnata | Assertions enabled. Pragma Assert/Debug to be activated |
-gnatA | Avoid processing gnat.adc, if present file will be ignored |
-gnatb | Generate brief messages to stderr even if verbose mode set |
-gnatc | Check syntax and semantics only (no code generation) |
-gnatd? | Compiler debug option ? (a-z,A-Z,0-9), see debug.adb |
-gnatD | Debug expanded generated code rather than source code |
-gnate | Error messages generated immediately, not saved up till end |
-gnatE | Dynamic elaboration checking mode enabled |
-gnatf | Full errors. Verbose details, all undefined references |
-gnatF | Force all import/export external names to all uppercase |
-gnatg | GNAT implementation mode (used for compiling GNAT units) |
-gnatG | Output generated expanded code in source form |
-gnath | Output this usage (help) information |
-gnati? | Identifier char set (?=1/2/3/4/8/p/f/n/w) |
-gnatk | Limit file names to nnn characters (k = krunch) |
-gnatl | Output full source listing with embedded error messages |
-gnatL | Use longjmp/setjmp for exception handling |
-gnatmnnn | Limit number of detected errors to nnn (1-999) |
-gnatn | Inlining of subprograms (apply pragma Inline across units) |
-gnato | Enable overflow checking (off by default) |
-gnatO nm | Set name of output ali file (internal switch) |
-gnatp | Suppress all checks |
-gnatP | Generate periodic calls to System.Polling.Poll |
-gnatq | Don't quit, try semantics, even if parse errors |
-gnatR | List representation information |
-gnats | Syntax check only |
-gnatt | Tree output file to be generated |
-gnatTnnn | All compiler tables start at nnn times usual starting size |
-gnatu | List units for this compilation |
-gnatU | Enable unique tag for error messages |
-gnatv | Verbose mode. Full error output with source lines to stdout |
-gnatw? | Warning mode. (?=s/e/l/u for suppress/error/elab/undefined) |
-gnatW | Wide character encoding method (h/u/s/e/8/b) |
-gnatx | Suppress output of cross-reference information |
-gnatX | Language extensions permitted |
-gnaty | Enable all style checks |
-gnatyxxx | Enable selected style checks xxx = list of parameters:
|
-gnatz | Distribution stub generation (r/s for receiver/sender stubs) |
-gnatZ | Use zero cost exception handling |
-gnat83 | Enforce Ada 83 restrictions |
java hello
Table: java switches
Java Interpreter Switch | Description |
-help | Print usage info |
-version | Print version number |
-ss size | Maximum native stack size |
-mx size | Maximum heap size |
-ms size | Initial heap size |
-as size | Heap increment |
-classpath path | Set classpath |
-verify | Verify all bytecode |
-verifyremote | Verify bytecode loaded from network |
-noverify | Do not verify any bytecode |
-Dproperty=value | Set a property |
-verbosegc | Print message during garbage collection |
-noclassgc | Disable class garbage collection |
-v, -verbose | Be verbose |
-verbosejit | Print message during JIT code generation |
-verbosemem | Print detailed memory allocation statistics |
-debug | Trace method calls |
-noasyncgc | Do not garbage collect asynchronously |
-cs, -checksource | Check source against class files |
-oss size | Maximum java stack size |
-jar | Executable is a JAR |
Limitations: Ada streams don't work with Jgnat.
The Ada Semantic Interface Specification (ASIS) is a standard for building development tools for Ada 95. ASIS is implemented as a series of Ada packages and they are included with the Gnat compiler. Debuggers, source code browsers and code checkers can all be written using ASIS.
ASIS works like a database. Different ASIS child packages return different information. A program using ASIS issues queries to the ASIS packages and ASIS returns the query results.
Information and tutorials on ASIS is available from the ASIS Workgroup at http://info.acm.org/sigada/WG/asiswg/asiswg.html.
This section discusses embedding assembly language into an Ada 95 program using Gnat. There is a tutorial on Ada assembly language programming is available at http://www.adapower.com/articles.
Optimizing your programs is more than just rewriting your Ada source code. Gnat performs many basic optimizations for you. For example, Gnat will take these statements
x := 5; y := x+1;
and rewrite them as
x := 5; y := 6;
to eliminate the addition operation.
In order to improve the performance of you source code, you'll have to consider issues that Gnat cannot know beforehand or issues that Gnat does not consider when it compiles:
The latest Pentium processors make great effort to reduce the average length of time it takes to execute an instruction, even when it makes programs difficult to optimize. In a sense, the processors take such drastic measures to improve bad source code that it's difficult to write good, efficient source code. Even in Ada, reversing two statements can measurably improve (or degrade) performance on a Pentium family processor.
However, there are other reasons to use assembly language besides optimization:
The latest Pentium processors are referred to as "IA-32" (32-bit Intel architecture) in the Intel literature. Although the instructions they execute are based on the 80386 processor, you can think of them as hardware emulators that pretend to follow 80386 instructions: internally, the instructions are broken down into different instructions called "microops".
Despite years of extensions, optimizations, and cache increases, the Pentium family is still just a 32-bit 80386 at its core. There are 8 general purpose registers--although they aren't really general purpose since some are more "general" than others. Only six are normally useful:
The remaining two represent the stack.
Instructions can address less than a register's entire contents. For example EAX can be refered to as AX (lower 16-bits) AH (the second (high) byte), and AL (the first (low) byte). The second four can address only the lower 16-bits by dropping the leading "E" (BP, SI, DI, SP).
There are also 6 segment registers (CS, DS, SS, ES, FS, GS), but you don't normally need to work with these.
An additional register, EFLAGS, contains the status bits for the processor. This represents the results of the last operation or modes that the processor is running in:
bit 0 - carry bit 11 - overflow bit 1 - always 1 bit 12 - I/O privilege bit 2 - parity bit 13 - I/O privilege bit 3 - always 0 bit 14 - nested task bit 4 - aux carry (BCD) bit 15 - always 0 bit 5 - always 0 bit 16 - resume flag bit 6 - zero flag bit 17 - virtual-8086 mode bit 7 - sign bit 18 - alignment check bit 8 - trap flag bit 19 - virtual interrupt bit 9 - interrupt enable bit 20 - virtual interrupt pending bit 10 - direction bit 21 - ID flag (has CPUID instruction)
Bits 22-31 are zero.
The actual instruction set for the Pentium family is too large to list here. The manual is available for download from Intel and is nearly 1000 pages long. They are available at Intel's Web Site. (The instruction set documentation on the Pentium II site covers all IA-32 processors, including Pentium III, 4, and so on.)
Floating point instructions use a separate set of registers and their own instructions usually prefaced by a "F".
The MMX, SSE and SSE2 are collectively referred to as SIMD instructions in the Intel documentation. "SIMD" is an acronym simply meaning an array operation. These instructions load, save and perform operations on part of an array. For example, using MMX instructions, you can load 32 16-bit integers, add them to a second set of 32 16-bit integers, and save the results with only 3 instructions. Since they work on fixed-size blocks, your arrays must be sized accordingly.
Some basic instructions for discussion purposes are:
call - call a subroutine clc - clear the carry flag add - add without carry adc - add with carry and - binary and inc - increment jxx - jump on condition where xx is - overflow - O / NO - carry - C, B, NAE, NC / NB /NAE - equal/zero - E / Z / NE / NZ - below and equal / above - BE / NA / A / NBE - negative - S / NS - parity - P / PE / NP / PO - < or >= - L / NGE / GE / NL - <= or > - LE / NG / G / GLE - aux carry - U / NU mov - load and save registers or transfer memory data nop - no operation (do nothing), useful for experimenting or - binary or pop - pop/pull data from the stack push - push data onto the stack pushfl - push EFLAGS onto the stack ret - return from subroutine sal - arithmetic shift left (multiply by 2) sar - arithmetic shift right (divide by 2) sbb - subtract with borrow stc - set carry (set a borrow) sub - subtract without borrow xor - binary exclusive or
Most of these basic instructions can take a length suffix indicating the amount of data: b (byte), w (word), l (long). MOVW moves a 16-bit value. INCL increments a 32-bit value. This suffix is different in Linux than in the Intel documentation because Linux uses the AT&T syntax. For example, PUSHFD (push flags double) is PUSHFL (push flags long) in Linux.
Many of the IA-32 instructions are "CISC" instructions containing an instruction that does the equivalent of two or more simplier instructions at one time. For example, "CMOV" performs a test and then moves the data if the test succeeds--implicitly a jump plus a move. Since the lastest processors are heavily pipelined and paralleled, this type of combined instruction has less of an impact on performance than you might think. The processor sees both CMOV and a jump+MOV as the same sequence of microops internally.
Assembly language instructions take zero or more operands. Here are some examples of operands.
To tie in Ada variables, refer to the variable as %0...%n. The compiler will substitute in the proper operand to access the value of that variable.
In the AT&T assembly language syntax, the order of operands is reversed to that of Intel's literature. For example, to load hex F into EAX, the mov operands would be "$0XF, %eax" not "%eax, $0XF".
To use assemble language, include the System.Machine_Code package in your Ada source file. This package contains a procedure called "asm" for inserting assembly language instructions into a program. This is very similar to the C language function of the same name.
C: The Ada Asm procedure is identical to the C asm
function, except that Ada uses the syntax for a normal procedure:asm( "shrl $8, %0" : "=r" (answer) : "r" (operand) : "cc" );becomes Asm( "shrl $8, %0", Unsigned_32'Asm_Input( "r", operand ), Unsigned_32'Asm_Output( "=r", answer ), "cc"); |
The first and only required parameter (named template =>) is the text, as a string, to be given to the assembler. Expect this to be quite literally saved into a temporary file for the GNU assembler to process. As a result, including any formatting, such as line feeds and tabs, so the assembler will read your instructions properly.
The shortest and safest example of asm is:
asm( "nop" ); -- read instruction but do nothing
In order for Ada to use your assembly code to do something useful, it needs to know how to interface the Ada variables. The "inputs" and "outputs" parameters do this. These parameters use items created by the special 'Asm_Input and 'Asm_Output attributes.
type'asm_input( constraint_string, variable ) type'asm_output( constraint_string, variable )
The constraint string tell Ada how to load/save the variables:
These constraints implicitly let Ada know that these registers will be used by you and if it was using them, it will save them prior to executing your assembly code. You don't need to save them yourself.
In addition, there are general constraints that don't specify a particular register:
These aren't as useful as you might think. When using a general constraint, Gnat doesn't keep track of which registers it has assigned. "r", the constraint for any available register, will likely be the EAX accumulator. Using "r" for two inputs is the same as using "a" twice and will cause one value to overwrite the other.
All output constraints must use a "=" to indicate that it's for output.
For example,result : interfaces.unsigned_32; ... movl %%eax, %0 ; save result
would could be done as
outputs => interfaces.unsigned_32'asm_outputs( "=a", result ) -- save EAX accumulator into variable named "result"
Multiple input/output parameters are specified as "=> ( first, second, ... )".
When counting, the inputs are numbered first. That is, if you have one item in inputs and one item in outputs, the input is %0 and the output is %1.
Another asm parameter, clobber, is a string with the names of the registers that need to be saved besides the ones implicilty referred to in the inputs and outputs. Clobber strings can be a register name, "cc" for processor flag or "memory" for a memory location.
The Asm procedure is treated as a normal Ada procedure. During optimization, Gnat may change the order in which the instructions in your program are executed to improve performance. For example, if your Asm procedure is inside a loop, Gnat may move the procedure outside of the loop if it thinks it is save to do so. This is safe to do for an Ada procedure, but an Asm procedure may suffer side-effects and not function correctly. Use the fourth Asm parameter, volatile, to indicate to Gnat that it is not safe to move your Asm procedure during optimzation.
For the same reason, you should not use two or more Asm procedures in one block of source code because Gnat may attempt to reorder them. Instead, place all your instructions into one Asm procedure to ensure the instructions will execute in the proper order.
with Ada.Text_IO, Interfaces, System.Machine_Code; use Ada.Text_IO, Interfaces, System.Machine_Code; procedure asm4 is -- A demonstration of Pentium assembly language programming. -- -- We'll use the Interface package's unsigned_32 integers for the 32-bit -- values stored in registers. Of course, we could have made our own types -- to do the same thing... function do_math( value1, value2 : unsigned_32 ) return unsigned_32 is -- Do some arbitrary math in assembly language. We'll use -- ( value + 1 ) * 2 - value2 in this example. result : unsigned_32; begin asm( "incl %%eax" & ASCII.LF & ASCII.HT & -- increment by 1 "sall %%eax" & ASCII.LF & ASCII.HT & -- shift left ( * 2 ) "subl %%ebx, %%eax", -- subtract value2 (ebx) -- EAX register := value1; -- EBX register := value2; inputs => ( unsigned_32'asm_input( "a", value1 ), -- value1 in EAX unsigned_32'asm_input( "b", value2 ) -- value2 in EBX ), -- result := EAX register; outputs => unsigned_32'asm_output( "=a", result ), -- The carry flag will be altered in EFLAGS by subl clobber => "cc" ); return result; end do_math; pragma inline( do_math ); value1 : unsigned_32; value2 : unsigned_32; result : unsigned_32; begin value1 := 5; value2 := 8; result := do_math( value1, value2 ); put_line( "do_math will do ( value1 + 1 ) * 2 - value2" ); put( "do_math(" & value1'img & "," & value2'img & ") = " ); put_line( result'img ); end asm4;
do_math will do ( value1 + 1 ) * 2 - value2 do_math( 5, 8) = 4
It is possible to combine Ada 95 with a main program written in C. Using Ada 95 classes, functions and procedures from another language is more difficult than the reverse process. While the GNAT compiler has a lot of support for other languages, the other languages do not supply the same level of support. Be prepared to do some manual chores in order to compile and link your program.
It is best to use the same GCC compiler for all the source files. Both the ACT and ALT versions of GCC have the C language enabled.
As discussed under types in this document, most C types have direct correspondence to Ada types. A C "int" is the same as an Ada "integer". For greater portability, the Interfaces.C package and its children contain the definitions of many standard C types. Arrays and records are directly equivalent to C arrays and structures. Special cases are noted below.
Before calling any Ada 95 subprograms, the C program should call the function adainit which performs the initializations and elaborations for the Ada 95 source code. Before the C program exits, it should call adafinal to perform any cleanup. These functions are created by gnatbind so you cannot create a C test program without creating at least one an Ada source file as well.
Suppose you want to call a single Ada procedure with no parameters. Your C main program would look something like this.
// main.c // #includeextern void adainit( void ); extern void adafinal( void ); int main( int argc, char **argv) { puts("C main() started."); adainit(); ada_subroutine(); adafinal(); puts("adafinal() returned."); return 0; }
Create an Ada package containing the "ada_subroutine" procedure. The procedure should be exported to C using pragma export. Because C is a case sensitive language, pragma export will convert the procedure name to lower case characters. (There's another pragma that can change how the case conversion is performed.) Alternatively, you can explicitly supply a new C name in pragma export.
package Test_Subr is procedure Ada_Subroutine; pragma export(C, Ada_Subroutine); end Test_Subr;
with Ada.Text_IO; use Ada.Text_IO; package body Test_Subr is procedure Ada_Subroutine is begin Put("Ada_Subroutine has been invoked from C."); end Ada_Subroutine; end Test_Subr;
To build the project:
The following example uses the ALT GNAT 3.13p:
$ gnatgcc -c main.c $ gnatgcc -c test_subr.adb $ gnatbind -n test_subr $ gnatgcc -c b~test_subr $ gnatlink -o main main.o test_subr.ali $ ./main C main() started. Ada_Subroutine has been invoked from C. adafinal() returned.
Functions can likewise be exported.
function Times_2( i : integer ) return integer; pragma export(C, Times_2);
Although the exported subprograms don't need to be prototyped (declaring the function headers), all subprograms should be prototyped in the same way that external C functions are prototyped. Prototyping ensures that the functions will be called with the proper parameters.
extern int times_2( int );
Declaring a function in Ada doesn't ensure that the parameters are strongly typed. The parameters are set up by C prior to calling the Ada function.
printf( "3 times 2 is %d\n", times_2( 3.0 ) );will compile and return 6, just as if times_2 was a C function.
If Times_2 is overloaded, exporting it will cause an "already defined" error during linking. You will have to provide pragma export with alternate C names that won't conflict with each other.
function Times_2( i : integer ) return integer; pragma export(C, Times_2, "int_times_2"); function Times_2( f : float ) return float; pragma export( C, Times_2, "float_times_2" );
If the overloaded Ada function differs only in return value, only one version is exported. You will have to use trial-and-error to determine which one. If you use "rename" to create alternate namings of functions for C++, Ada will not allow you to use pragma export with the renamed functions. If the original function was exported, Ada considers the renamed function to be already exported under the old name.
Default parameters are not allowed for non-Ada languages. C++ must explicitly include the optional parameter.
Inline functions can be exported to C++: the inline process doesn't affect their ability to be called by an outside language.
Variables can also be exported.
type a_test_record is record i : integer := 3; f : float := 1.5; end record; test_record : a_test_record; pragma export(C, test_record );
Define the Ada record as a C structure.
struct a_test_record { int i; float j; }; extern struct a_test_record test_record;
You can now access the fields of the Ada record from your C program.
printf( "test_record.i is %d\n", test_record.i ); printf( "test_record.j is %f\n", test_record.j );
test_record.i is 3 test_record.j is 1.500000
Likewise arrays can be exported. Remember that in C, array indices always start at zero.
type a_test_array is array(1..5) of character; test_array : a_test_array := ('a','b','c','d','e'); pragma export(C, test_array );
extern char test_array[5]; ... printf( "test_array[0] is %c\n", test_array[0] ); printf( "test_array[4] is %c\n", test_array[4] );
test_array[0] is a test_array[4] is e
Certain variable types are more difficult to deal with. For example, Ada enumerated types must be declared with "pragma convention( C" to be compatible with C's enumerated type. This is the only case where Gnat stores a type differently for the benefit of another language.
type ada_enum_type is (red, green, blue); pragma convention( C, ada_enum_type ); ada_enum : ada_enum_type := green; pragma export( C, ada_enum );
enum ada_enum_type {red, green, blue}; extern enum ada_enum_type ada_enum; ... if ( ada_enum == green ) printf( "ada_enum is green\n" ); else printf( "ada_enum isn't green\n" );
ada_enum is green
The contents of arrays or records packed with pragma pack are not easily accessible from C. Ada represents these as raw bits.
Ada unconstrained arrays (including unconstrained strings) have no equivalent in C. The easiest work around for strings is to use C strings instead, as defined in the Interfaces.C package. For C to call a subprogram with unbounded Ada strings as parameters, the C program must also pass the string bounds as parameters. [If anyone knows how to do this, email me.--KB]
Ada access types are usually equivalent to C pointers.
Packages
If you want to export a private items, use pragma export in the private section of the package, not at the forward declaration.
If you want to export something from a package, the pragma export must appear in the same package. The names of the items to be exported must be local names and cannot be referred to as "package.item".
For generic packages, if you export items in the generic package package will have the same external name. You can't create unique names by using pragma export in the place where the generic package is instantiated because pragma export can only appear in the same declaration section as the items being exported. For example, suppose you have a generic linked list package and you export the "list" record to C++. If you instantiate a boolean list, an integer list and a string list, only one of those will be available to "C++" as a "list" structure...probably whichever one Ada instantiated last will overwrite the previous definitions of "list".
To work around this problem, you will have have to create "wrapper" types and subprograms. For example, create a record to hold a variable from the generic package and create short subprograms with new names that call instantiated subprograms. For example, if "strlist" is instantiated package for lists of strings:
-- wrapping a strlist.list in a record and exporting the record type C_List is record list : strlist.list; end record; pragma export( C, C_List ); -- wrapping the strlist.sort procedure in another procedure and exporting procedure C_List_Sort( list : C_List ); pragma export( C, C_List_Sort ); ... procedure C_List_Sort( the_list : C_List ) is begin strlist.sort( the_list.list ); -- run on behalf of C end C_List_Sort;
The Ada 95 standard doesn't support interfacing to C++, but Gnat provides extensions so that GNU C++ source code can be used with Ada.
If possible, the same GCC compiler should be used for all source files. If you are using the ACT version of GNAT 3.x or earlier, you should recompile GNAT to enable C++. The ALT version of GNAT 3.x (usually) has C++ enabled. If C++ is not enabled, you will receive a message from GCC about "cc1plus" (the C++ compiler) not being found. However, for the complete example below, I used two different version of GCC and had no problems.
For the most part, calling C++ from Ada is done the same was as calling C from Ada. Instead of using "C" in pragma import, use "CPP" (that is, C++) as the language convention. However, Gnat provides no support for C++ "name mangling": all C++ declarations should use extern "C" to stop the name mangling. (If you are an adventurer, use the dumpobj -t command to determine the symbol names used by C++ and include them explicitly in pragma import.)
If Ada and C++ use different GCC compilers, the linker may not be able to tell which version of libgcc to use. You can check which library is being used with the gnatlink -v -v (very verbose) switches.
Importing C++ objects into Ada is possible but difficult. C++ and Ada implement objects using different Object Oriented Programming models. C++ objects are not identical to tagged records. Gnat has special pragmas for importing C++ objects:
The Gnat C++ interface proposal also has a CPP_Destructor pragma, but this has not been implemented. [Perhaps it is not necessary? --KB]
There are two naming problems. First, name mangling is necessary with C++ classes. You will have to use objdump to determine the C++ method names. Second, if two C++ methods from two different classes have the same name (this is often the case when overridding), you'll have to declare the C classes in separate Ada packages. Otherwise, pragma import will report an error when attempting to import the same name twice.
Suppose you want to import a C++ car class named cpp_car.
type cpp_car is tagged record year : integer; -- year of car weight : integer; -- weight of car length : integer; -- length of car car_vtable : Interfaces.CPP.Vtable_Ptr; -- always the last field end record; pragma CPP_Class( cpp_car ); -- this is a C++ class pragma CPP_Vtable( cpp_car, car_vtable, entry_count => 3 ); -- car_vtable has 3 virtual methods
If a class has no vtable, it cannot be imported in Ada. CPP_Class will report an error.
Only the C++ classes you intend to use need to be imported. If cpp_car has a parent class called cpp_vehicle, it does not have to be declared in Ada if it will not be used. However, any fields in cpp_vehicle will have to be added to the beginning of the cpp_car tagged record or they will be missing.
C++ classes imported into Ada can't be assigned. This has to do with the differences in assignment semantics between the two languages. C++ classes used in Ada should always have a constructor because this is the only way to assign values to the object.
function Default_Constructor return cpp_car'class; pragma CPP_Constructor( Default_Constructor ); -- the default constructor for cpp_car
The name of the constructor is not important. It simply gives the C++ constructor an Ada compatible name.
Other constructors can be imported using different parameters.
function Copy_Constructor( c : cpp_car'class ) return cpp_car'class; pragma CPP_Constructor( Copy_Constructor ); -- construct a copy of a cpp_car object
Methods that are not virtual can be imported without a special pragma.
function get_year( c : cpp_car'class ) return integer; pragma import( CPP, get_year );
C++ has no object parameter--it is implied. In Ada, the object must be declared and it can be declared in any position in the parameter list. When importing C++ objects, always put the object name in the position of the first parameter. This is the parameter used by C++ (even though it is not seen by the programmer).
One of the differences between C++ and Ada objects is that Ada has no equivalent of "virtual methods". In Ada, whether or not a method is virtual is determined by the way the class is declared. Also, Ada doesn't allow a class-wide type to be overridden by any children--a class-wide type is always class-wide with no hidden surprises further down the class tree. In C++, virtual functions must be explicitly declared as "virtual". Ada doesn't require all the methods in a C++ class to be imported.
pragma CPP_Virtual identifies which methods are virtual and the position in the vtable. In the simplest case, the first C++ virtual function is at position 1, the second is at position 2, and so on.
function get_total_weight( c : cpp_car ) return integer; pragma import( CPP, get_total_weight ); pragma CPP_Virtual( get_total_weight, car_vtable, 1 ); -- first virtual function in class -- child tagged records may override this function function get_total_length( c : cpp_car ) return integer; pragma import( CPP, get_total_length ); pragma CPP_Virtual( get_total_length, car_vtable, 2 ); -- second virtual function in class -- child tagged records may override this function
When a virtual function is not overridden, it must be declared in Ada. Use CPP_Virtual to indicate which parent function to use.
type cpp_luxury_car is new cpp_car ... function get_total_weight( c : cpp_luxury_car ) return integer; pragma import( CPP, get_total_weight ); pragma CPP_Virtual( get_total_length, car_vtable, 3 ); -- overridden virtual function function get_total_length( c : cpp_luxury_car ) return integer; pragma import( CPP, get_total_length ); pragma CPP_Virtual( get_total_length, car_vtable, 2 ); -- not overridden virtual function -- in C++, this function does not appear. It is implied. -- for a cpp_luxury_car, use cpp_car get_total_length in vtable at position 2
If necessary, Gnat allows the C++ class to be extended with Ada-specific tagged records. (A multi-language class may make a project unnecessarily complex and difficult to debug.)
Private and protected fields in a C++ object and be simulated using a combination of Ada's private keyword and the information hiding capabilities of Ada packages.
To build the project:
Here is a complete example using two simple C++ objects.
// c_class.h /* Unimaginative class declaration */ class c_root_class { public: int i; c_root_class( void ); int get_value ( void ) const; void set_value( int new_i ); virtual int get_total_value( void ) const; }; class c_extended_class: c_root_class { public: int j; c_extended_class( void ); int get_j_value ( void ) const; void set_j_value( int new_j ); virtual int get_total_value( void ) const; };
// c_class.cc // #include#include "c_class.h" /* c_root_class: Method Bodies */ c_root_class::c_root_class( void ) { i = 0; } int c_root_class::get_value( void ) const { return i; } void c_root_class::set_value( int new_i ) { i = new_i; } int c_root_class::get_total_value( void ) const { return i; } /* c_extended_class: Method Bodies */ c_extended_class::c_extended_class( void ) { j = 0; } int c_extended_class::get_j_value( void ) const { return j; } void c_extended_class::set_j_value( int new_j ) { j = new_j; } int c_extended_class::get_total_value( void ) const { return i+j; }
Now determine the C++ mangled names with objdump.
$ c++ -Wall -c c_class.cc $ objdump -t c_class.o c_class.o: file format elf32-i386 SYMBOL TABLE: 00000000 l df *ABS* 00000000 c_class.cc 00000000 l d .text 00000000 00000000 l d .data 00000000 00000000 l d .bss 00000000 00000000 l .text 00000000 gcc2_compiled. 00000000 l d .gnu.linkonce.d.__vt_16c_extended_class 00000000 00000000 l d .gnu.linkonce.d.__vt_12c_root_class 00000000 00000000 l d .rodata 00000000 00000000 l d .gnu.linkonce.t.__tf12c_root_class 00000000 00000000 l d .gnu.linkonce.t.__tf16c_extended_class 00000000 00000000 l d .note 00000000 00000000 l d .comment 00000000 00000000 g F .text 00000015 __12c_root_class 00000000 w O .gnu.linkonce.d.__vt_12c_root_class 00000010 __vt_12c_root_class 00000018 g F .text 0000000c get_value__C12c_root_class 00000024 g F .text 0000000d set_value__12c_root_classi 00000034 g F .text 0000000c get_total_value__C12c_root_class 00000040 g F .text 00000029 __16c_extended_class 00000000 w O .gnu.linkonce.d.__vt_16c_extended_class 00000010 __vt_16c_extended_class 0000006c g F .text 0000000d get_j_value__C16c_extended_class 0000007c g F .text 0000000e set_j_value__16c_extended_classi 0000008c g F .text 00000011 get_total_value__C16c_extended_class 00000000 w F .gnu.linkonce.t.__tf16c_extended_class 00000034 __tf16c_extended_class 00000000 w F .gnu.linkonce.t.__tf12c_root_class 0000002b __tf12c_root_class 00000008 O *COM* 00000004 __ti12c_root_class 00000010 O *COM* 00000004 __ti16c_extended_class 00000000 *UND* 00000000 __rtti_user 00000000 *UND* 00000000 __rtti_class
Write the corresponding Ada packages containing the C++ class interface. Since virtual methods are used, we'll need to define each C++ class in a separate package to avoid problems with pragma import.
with Interfaces.CPP; package c_root_class_package is type c_root_class is tagged record i : integer; vtable : Interfaces.CPP.VTable_Ptr; -- C++ vtable end record; pragma CPP_Class( c_root_class ); pragma CPP_Vtable( c_root_class, vtable, entry_count => 2 ); function default_constructor return c_root_class'class; pragma import( CPP, default_constructor, "__12c_root_class" ); pragma CPP_Constructor( default_constructor ); function get_value( cr : c_root_class'class ) return integer; pragma import( CPP, get_value, "get_value__C12c_root_class" ); procedure set_value( cr : c_root_class'class; new_i : integer ); pragma import( CPP, set_value, "set_value__12c_root_classi" ); function get_total_value( cr : c_root_class ) return integer; pragma import( CPP, get_total_value, "get_total_value__C12c_root_class" ); pragma CPP_Virtual( get_total_value, vtable, 1 ); end c_root_class_package;
with Interfaces.CPP; with c_root_class_package; use c_root_class_package; package c_extended_class_package is type c_extended_class is new c_root_class with record j : integer; end record; pragma CPP_Class( c_extended_class ); function get_j_value ( ce : c_extended_class'class ) return integer; pragma import( CPP, get_j_value, "get_j_value__C16c_extended_class" ); procedure set_j_value( ce : c_extended_class'class; new_j : integer ); pragma import( CPP, set_j_value, "set_j_value__16c_extended_classi" ); function extended_constructor return c_extended_class'class; pragma import( CPP, extended_constructor, "__16c_extended_class" ); pragma CPP_Constructor( extended_constructor ); function get_total_value( cr : c_extended_class ) return integer; pragma import( CPP, get_total_value, "get_total_value__C16c_extended_class" ); pragma CPP_Virtual( get_total_value, vtable, 2 ); end c_extended_class_package;
The main program will declare two objects and test the methods.
with Interfaces.CPP; with Ada.Text_IO; use Ada.Text_IO; with c_root_class_package; use c_root_class_package; with c_extended_class_package; use c_extended_class_package; procedure main is object : c_root_class; extended_object : c_extended_class; begin put_line( "Ada Using a C++ Class" ); new_line; put_line( "With object (a c_root_class class object):" ); put_line( " object constructor should assign value 0, value is" & get_value( object )'img ); set_value( object, 12 ); put_line( " set_value( object, 12 ), value is" & get_value( object )'img ); put_line( " object total value (virtual method) is" & get_total_value( object )'img ); new_line; put_line( "With extended_object (a c_extended_class class object):" ); put_line( " extended object constructor should assign j value 0, value j is" & get_j_value( extended_object )'img ); set_value( extended_object, 12 ); put_line( " set_value( extended_object, 12 ), value is" & get_value( extended_object )'img ); set_j_value( extended_object, 15 ); put_line( " set_j_value( extended_object, 15 ), j value is" & get_j_value( extended_object )'img ); put_line( " extended object total value (virtual method) is" & get_total_value( extended_object )'img ); new_line; put_line( "Ada finishes" ); end main;
Build and run the project.
$ gnatmake -c main.adb $ gnatbind -x main.ali $ gnatlink main c_class.o -lstdc++ -L/usr/lib/gcc-lib/i386-redhat-linux/2.96 --link=c++ $ ./main Ada Using a C++ Class With object (a c_root_class class object): object constructor should assign value 0, value is 0 set_value( object, 12 ), value is 12 object total value (virtual method) is 12 With extended_object (a c_extended_class class object): extended object constructor should assign j value 0, value j is 0 set_value( extended_object, 12 ), value is 12 set_j_value( extended_object, 15 ), j value is 15 extended object total value (virtual method) is 27 Ada finishes
A C++ program can interface to Ada in the same way as C program.
If possible, the same GCC compiler should be used for all source files. If you are using the ACT version of GNAT 3.x or earlier, you should recompile GNAT to enable C++. The ALT version of GNAT 3.x (usually) has C++ enabled. If C++ is not enabled, you will receive a message from GCC about "cc1plus" (the C++ compiler) not being found. However, the example below was compiled with two different versions of GCC and there were no errors.
For the most part, calling Ada from C++ is done the same was as calling Ada from C. Instead of using "C" in pragma export, use "CPP" (that is, C++) as the language convention. However, Gnat provides no support for CPP "name mangling": all Ada extern declarations in a C++ file should use extern "C".
If Ada and C++ use different GCC compilers, the linker may not be able to tell which version of libgcc to use. You can check which library is being used with the gnatlink -v -v (very verbose) switches.
The following is the same sample C program used above in 19.4, converted to C++.
// main.cc // #includeextern "C" { void adainit(void); void adafinal(void); void ada_subroutine( void ); } int main(int argc, char **argv) { puts("C++ main() started."); adainit(); ada_subroutine(); adafinal(); puts("adafinal() returned."); return 0; }
The Ada package is the same except that convention CPP is used.
package Test_Subr is procedure Ada_Subroutine; pragma export(CPP, Ada_Subroutine ); end Test_Subr;
with Ada.Text_IO; use Ada.Text_IO; package body Test_Subr is procedure Ada_Subroutine is begin Put("Ada_Subroutine has been invoked from C++."); end Ada_Subroutine; end Test_Subr;
The steps to build the project are similar to C:
$ c++ -c test.cc $ gnatgcc -c test_subr $ gnatbind -n test_subr $ gnatgcc -c b~test_subr $ gnatlink -o main test.o test_subr.ali --link=c++ $ ./main C++ main() started. Ada_Subroutine has been invoked from C++. adafinal() returned.
Tagged records cannot be exported directly to C++. Gnat does not understand C++ name mangling and it cannot give the tagged record subprograms names that C++ would recognize. Also, C++ does not understand Ada's Object Oriented Programming model--Ada tagged records are not identical to C++ object classes.
In order to use Ada tagged records from C++, you will have (dynamically) declare the objects in Ada and pass a "handle" (an ID number or a pointer) back to C++ to use in reference. The Ada source must have special subprograms to match the handle to a particular object and call the appropriate Ada subprogram for that object on behalf of C++.
It should be possible to call Ada using Java's C++ importing features.
<--Last Chapter | Table of Contents | Next Chapter--> |