19 Java Byte Code and Mixing Languages

 

  <--Last Chapter Table of Contents Next Chapter-->  

 

19.1 Ada Meets Java

ACT's JGNAT compiler will compile Gnat source files into Java applications or applets. This section introduces JGNAT.

19.1.1 The Java Virtual Machine

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.

19.1.2 JGnat

Most of the Gnat tools have a corresponding JGnat version including gnatmake. To compile an Ada program into a Java byte-code program use jgnatmake:
  jgnatmake hello

Table: jgnatmake switches

JGnatmake Switch Description
-aConsider all files even readonly ali files
-cCompile only do not bind and link
-fForce recompilations of non predefined units
-iIn place. Replace existing ali file or put it with source
-jnumUse nnn processes to compile
-kKeep going after compilation errors
-mMinimal recompilation
-MList object file dependences for Makefile
-nCheck objects up to date output next file to compile if not
-o nameChoose an alternate executable name
-qBe quiet/terse
-sRecompile if compiler switches have changed
-vDisplay reasons for all (re)compilations
-zNo main subprogram (zero main)
--GCC=commandUse this jgnat command
--GNATBIND=commandUse this gnatbind command
--GNATLINK=commandUse this gnatlink command
-aLdirSkip missing library sources if ali in dir
-Adirlike -aLdir -aIdir
-aOdirSpecify library/object files search path
-aIdirSpecify source files search path
-IdirLike -aIdir -aOdir
-I-Don't look for sources & library files in the default directory
-LdirLook for program libraries also in dir
-nostdincDon't look for sources in the system default directory
-nostdlibDon't look for library files in the system default directory
-cargs optsopts are passed to the compiler
-bargs optsopts are passed to the binder
-largs optsopts are passed to the linker
-gGenerate debugging information
-IdirSpecify source files search path
-I-Do not look for sources in current directory
-O[0123]Control the optimization level
-gnataAssertions enabled. Pragma Assert/Debug to be activated
-gnatAAvoid processing gnat.adc if present file will be ignored
-gnatbGenerate brief messages to stderr even if verbose mode set
-gnatcCheck syntax and semantics only (no code generation)
-gnatd?Compiler debug option ? (a-z A-Z 0-9) see debug.adb
-gnatDDebug expanded generated code rather than source code
-gnateError messages generated immediately not saved up till end
-gnatEDynamic elaboration checking mode enabled
-gnatfFull errors. Verbose details all undefined references
-gnatFForce all import/export external names to all uppercase
-gnatgGNAT implementation mode (used for compiling GNAT units)
-gnatGOutput generated expanded code in source form
-gnathOutput this usage (help) information
-gnati?Identifier char set (?=1/2/3/4/8/p/f/n/w)
-gnatkLimit file names to nnn characters (k = krunch)
-gnatlOutput full source listing with embedded error messages
-gnatLUse longjmp/setjmp for exception handling
-gnatmnnnLimit number of detected errors to nnn (1-999)
-gnatnInlining of subprograms (apply pragma Inline across units)
-gnatoEnable overflow checking (off by default)
-gnatO nmSet name of output ali file (internal switch)
-gnatpSuppress all checks
-gnatPGenerate periodic calls to System.Polling.Poll
-gnatqDon't quit try semantics even if parse errors
-gnatRList representation information
-gnatsSyntax check only
-gnattTree output file to be generated
-gnatTnnnAll compiler tables start at nnn times usual starting size
-gnatuList units for this compilation
-gnatUEnable unique tag for error messages
-gnatvVerbose mode. Full error output with source lines to stdout
-gnatw?Warning mode. (?=s/e/l/u for suppress/error/elab/undefined)
-gnatWWide character encoding method (h/u/s/e/8/b)
-gnatxSuppress output of cross-reference information
-gnatXLanguage extensions permitted
-gnatyEnable all style checks
-gnatyxxxEnable selected style checks xxx = list of parameters:
  • 1-9 check indentation
  • b check no blanks at end of lines
  • c check comment format
  • e check end labels present
  • f check no form feeds/vertical tabs in source
  • h check no horizontal tabs in source
  • i check if-then layout
  • k check casing rules for keywords identifiers
  • m check line length <= 79 characters
  • Mnnn check line length <= nnn characters
  • r check RM column layout
  • s check separate subprogram specs present
  • t check token separation rules
-gnatzDistribution stub generation (r/s for receiver/sender stubs)
-gnatZUse zero cost exception handling
-gnat83Enforce Ada 83 restrictions
 
jgnatmake will create two files: hello.class and ada_hello.class. To run the program under the Java interpreter type
  java hello

Table: java switches

Java Interpreter Switch Description
-help Print usage info
-versionPrint version number
-ss sizeMaximum native stack size
-mx sizeMaximum heap size
-ms sizeInitial heap size
-as sizeHeap increment
-classpath pathSet classpath
-verifyVerify all bytecode
-verifyremoteVerify bytecode loaded from network
-noverifyDo not verify any bytecode
-Dproperty=valueSet a property
-verbosegcPrint message during garbage collection
-noclassgcDisable class garbage collection
-v -verboseBe verbose
-verbosejitPrint message during JIT code generation
-verbosememPrint detailed memory allocation statistics
-debugTrace method calls
-noasyncgcDo not garbage collect asynchronously
-cs -checksourceCheck source against class files
-oss sizeMaximum java stack size
-jarExecutable is a JAR

Limitations: Ada streams don't work with Jgnat.

 

19.2 ASIS

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.

 

19.3 Assembly Language

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:

 

19.3.1 Pentium Family Processors

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.

19.3.2 Instruction Set

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.

 

19.3.3 Operands

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".

 

19.3.4 System.Machine_Code.Asm

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.

 

19.3.5 Other Asm Flags

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.

 

19.3.6 A Complete Example

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
 

19.4 Calling Ada from C

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
//

#include 

extern 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:

  1. Compile the C and Ada files separately.
  2. Because the project contains Ada source it will have to be bound using gnatbind -n. The -n switch indicates there is no Ada main program.
  3. Compile file generated by gnatbind. (This contains adainit() and adafinal().)
  4. Link with gnatlink.

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;
 

19.5 Calling C++ from Ada

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:

  1. Compile the C++ files with the c++ command
  2. Make the Ada project with gnatmake
  3. Bind the Ada project with gnatbind -x
  4. Link the project using gnatlink. Include the C++ library (-lstdc++) and direct Gnat to use the c++ linker (--link=c++). Use -L if necessary to specify the stdc++ directory

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
 

19.6 Calling Ada from C++

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
//
#include 

extern "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:

  1. Compile the C++ and Ada files separately.
  2. Because the project contains Ada source it will have to be bound using gnatbind -n. The -n switch indicates there is no Ada main program.
  3. Compile file generated by gnatbind. adafinal()).
  4. Link with gnatlink and use --link=c++ switch. This switch ensures that the C++ elaboration (such as calling constructors on global objects) is done correctly.
$ 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++.

 

19.7 Calling Ada from Java

It should be possible to call Ada using Java's C++ importing features.

 

  <--Last Chapter Table of Contents Next Chapter-->