EQUIP2 C++ Programming
(Or how to write a C++ application that uses EQUIP2)

Chris Greenhalgh, 2007-01-26; last updated 2007-05-30

Contents:

  1. Introduction
  2. Building
  3. Examples
  4. Programming
  5. Changes

1. Introduction

EQUIP2 is developed in Java, and this is the main language in which it is being used at present. However there is also an auto-generated C++ version of EQUIP2. This document explains (some of) how to write a C++ application that uses C++ EQUIP2.

In brief, the C++ version of EQUIP2 is generated by code translation from the Java version. This is done using the tool Javatrans (written for this purpose). In addition, there is a native C++ implementation of a fairly limited subset of the java.lang, java.io and java.util packages which provide run-time support and other standard Java classes and facilities.

Your own C++ application can combine:

The final application does not support dynamic code loading as such (i.e. all classes must be linked directly with the application before it is run), but run-time facilities such as Class.forName() are still available.

At present Microsoft Windows and Posix/Unix (Linux) are the only actively used/supported C++ platform. Most of the support for Symbian C++ is also present (but not tested recently). Support for Sony PSP is intended during 2007.

Also note that there are currently some significant limitations to the C++ version:

2. Building

Note that the current build files require ANT for initial translation and Microsoft Visual Studio (used from the a Visual Studio command shell).

Pre-requisites

Before you can build your own EQUIP2 C++ applications you need to build Javatrans and the C++ translations of (a reimplementation of) Log4j and EQUIP2.

Refer to EQUIP2_CPP_Introduction.html for information on obtaining and building Javatrans and the C++ translations of Log4j and EQUIP2.

Example application

There is a sample EQUIP2 C++ application in ../samples/cpp/.

The first stage of building uses ANT (the shell path, JAVA_HOME and ANT_HOME will need to set up first):

> ant cpp
This will:

Windows

The second stage compiles the generated and any custom C++ (in ../src.cpp). For Windows, in a Visual Studio command prompt:
> set JAVATRANSHOME=... [wherever Javatrans is; windows path]
> set EQUIP2HOME=... [wherever EQUIP2 is; windows path]
> buildequip2cppsample-win32.bat

This will compile all the generated code and link into the executable XX. To run it:

> set PATH=%JAVATRANSHOME%\dist\win32;%EQUIP2HOME%\dist\win32;%PATH%
> dist\win32\equip2cppsample.exe

In this example, no C++ main file is generate - Javatrans can generate one for any java classes which have a main method (use the '-m' flag in the Javatrans generate task in build.xml). The application main is provided by a custom C++ class, ../samples/cpp/src.cpp/main.cpp.

3. Examples

Most available examples are the auto-translations of the Javatrans Java tests/examples and the EQUIP2 tests/examples. If Javatrans is checked out in ../../javatrans/ then the Java source for the examples are in ../../javatrans/src/example/ and the generated C++ translations are in ../../javatrans/generated/.

The EQUIP2 translated tests (once built) are in ../build.cpp/test.src.cpp/

Note that the current translation is based on compiled Java bytecode (class files) rather than source, so the generated code can be somewhat verbose and the structure rather unclear (e.g. heavy use of goto). I hope to change this to a source-based translation at some point, but don't currently have any time to look at this.

4. Programming

Platform dependencies

EQUIP2/C++ is intended in the future to support applications running across a range of operating systems and compilers. Consequently it makes fairly extensive use of macros to abstract over platform and compiler differences. Currently the platform target is specified at compile time by defining exactly one TARGET_... macro:

It also supports unicode and non-unicode builds. Unicode is strongly recommended, since Java itself is natively unicode. Depending on platform, this may require an additional compile define:

See the example build batch file for likely compiler options: ../samples/cpp/buildequip2cppsample-win32.bat

Platform-specific macros, etc. are generally defined in java_platform.h. These include:

Basic types

The following tables lists the Java primitive types, the C++ macro used in the generated C++ code and the actual types used on Windows and Symbian. (see ../src.cpp/include/java_platform.h)

Java type
Description
C++ Macro
Windows type
Unix type
Symbian type
boolean
true/false
JAVA_BOOLEAN
bool
bool TBool
byte
8 bit signed int
JAVA_BYTE char
char TInt8
short
16 bit signed int
JAVA_SHORT short short TInt16
int
32 bit signed int
JAVA_INT int int TInt32
long
64 bit signed int
JAVA_LONG long long
long long TInt64
char
16 bit signed int and unicode character
JAVA_CHAR _TCHAR[*]
wchar_t
TText
float
32 bit float
JAVA_FLOAT float float TReal32
double
64 bit float
JAVA_DOUBLE double double TReal64

[*] _TCHAR compiles to char or WCHAR depending on wether _UNICODE is defined - it should be.

Strings

Strings as used in translated Java code are java_lang_String object references (the native partial re-implementation of java.lang.String) (see java_lang_String.h). Low-level use of strings, e.g. creation of new constant strings, relies on the following cross-platform macros:
A java_lang_String is created using a string literal constructor argument, e.g.
#include "java_lang_String.h"
...
JavaPtr<java_lang_String> aString = new java_lang_String(STRING_LIT("some text"));
//or (more efficient on Symbian, at least)
STRING_LITERAL(textLit,"some more text");
JavaPtr<java_lang_String> anotherString = new java_lang_String(textLit);

Where possible, methods of java_lang_String can then be used as in Java.

The internal text can be obtained from a java_lang_String using the method 'CONST_STRING getChars()', e.g.

CONST_STRING rawString = aString->getChars();

Note that these strings really are constant, and you should not attempt to modify the characters.

Native strings can be compared for equality using the macro STRING_COMPARE(S1,S2), which is comparable to strcmp(s1,s2), returning 0 if the strings are equal.

Java-derived classes

All classes converted from Java extend the C++ class java_lang_Object (see java_lang_Object.h). This supports:

Various marcros are used to make the definition and implementation of subclasses of java_lang_Object more concise - see the source for examples.

Because of Symbian C++ not allowing multiple inheritance or virtual base classes translated java interfaces cannot inherit from java_lang_Object (although any concrete class implementing an interface will). Instead the interface classes support the same set of the top-level methods as java_lang_Object for reference counting and casting and can still be used with smart pointers.

Initialisation

The run-time support for translated Java classes requires some initialisation of each DLL used. To use the classes in the EQUIP2 DLL (which in turn initialise and use the classes in the Log4j port DLL) you would need to do:
#include "equip2.h"
..
equip2_init();

before using any of those classes.

Similarly, if your own application includes translated classes then you will need to initialise those as well (after initialising EQUIP2) - see for example ../samples/cpp/src.cpp/main.cpp (calls to equip2_init() and equip2cppsample_init()). This sets of global (class) variables and registers classes for run-time type information (e.g. Class.forName).

Creating instances

New instances of Java-derived classes are created in an explicit two stage process:
  1. a new instance of the class is created using the C++ built-in operator new,
  2. an initialisation method init(...) is which performs class-specific initialisation with the actual constructor code translated from the Java.
For example, creating a new Vector using its no-argument constructor would become:
#include "java_util_Vector.h"
...
JavaPtr<java_util_Vector> aVector = new java_util_Vector();
aVector->init(); // the no-arg constructor

Smart pointers and garbage collection

As already seen, translated classes are pretty much always used with the JavaPtr<T> smart pointer type. This is defined in java_ptr.h. Each smart pointer instance wraps a reference to the specified class (T) and manages it. In particular it manages reference counting on the object, calling AddRef() and Release() as pointers are assigned and detroyed. Smart pointers should be used, for example, as argument and return types, a member field types and as local variable types. As the smart pointers pass out of scope reference counts are automatically released by the smart pointers destructor.

When a translated object is first created its reference count is 0; the first assignment increases this to one, which is reduced to 0 again when the smart pointer is destroyed. This will trigger the destruction of the object (the smart pointer calling C++ delete which in turn will call the classes virtual destructor method) unless the smart pointer has been copied to another smart pointer in the mean time.

Note that this simple reference counting garbage collection system will NOT cope with circular references - such self-referential object graphs will not current be garbage collected unless the loop is explicitly broken (e.g. by setting one of the smart pointer values to NULL).

C++ delete should never be called directly (it should only ever be called by Release() when the reference count reaches zero).

Casting a smart pointer to (java_lang_top_ptr) gives a smart pointer to an address which is the same as that for the top-level java_lang_Object reference (note that addresses which reference an objects implemented interfaces will be different to this). Assignment of a java_lang_top_ptr value to a smart pointer will cause an run-time error (probably a fatal abort) if the referenced object is not of a compatible type - make sure you don't do this! (i.e. hasSuperclass first if necessary - see below). Do not attempt to use native pointers to the objects themselves as this may take references out of scope of the garbage collection.

Check if a smart pointer is null using (e.g.):

    if (sp.isNull()) ...

Clear a smart point (set to null) using (e.g.):

    sp.clear();

Generate a null-value (e.g. for a funciton return) using (e.g.):

    return java_lang_top_ptr();

Class tests and coercions

An object's class can be checked by the java_lang_Object methods:

Assignment compatibility can be testing using the java_lang_Class methods (see java_lang_Class.h):

E.g.
void someFunction(JavaPtr<java_lang_Object> o) {
// comparable to (o instanceof String)...
if (!o.isNull() && o->getClass()->hasSuperclass(STRING_LIT("java.lang.String"))) {
// cast to type - will abort the program if the cast is not valid!
JavaPtr<java_lang_String> s = (JavaPtr<java_lang_String>)((java_lang_top_ptr)o);

As shown in the above example, to cast unambiguously from one type to another the smart point is first cast to type java_lang_top_ptr and then to the relevant smart pointer type (via the constructor form). This will be subject to a run-time type check which will normally abort the application if the the coercion is not valid. At some point this should become an exception (ClassCastException) as in normal Java, but for now be even more careful than normal.

Methods

The smart pointer class defines the -> operator so that instance methods can be called directly, as you might expect, e.g.:
JavaPtr<java_util_Vector> aVector = new java_lang_Vector();
aVector->init();
...
JavaPtr<java_lang_String> aString = new java_lang_String(STRING_LIT("text"));
// invoke instance method
aVector->addElement(aString);

As noted above, arguments which are (references to) translated Java objects should always be wrapped as JavaPtr types (as with the string, above).

Class methods can be invoked as normal C++ static methods, e.g.:

some_package_SomeClass::classMethod();

Fields (Instance & Class)

Instance fields can be accessed directly, e.g.:
JavaPtr<equip2_samples_cpp_DataBean> db = new equip2_samples_cpp_DataBean();
db->init();
...
db->test = TRUE;
JAVA_BOOLEAN aBoolean = db->test;
Class fields, however, must be accessed more carefully, using the macro defined in java_global.h:
This is required to ensure that any class constructor code has a chance to run before the class fields are accessed. E.g.:
GLOBAL_FIELD(java_lang_System,out)->println(db->test);
// or
GLOBAL_FIELD(equip2_samples_cpp_JavaStuff,classVar) = 14;
JAVA_INT intVal = GLOBAL_FIELD(equip2_samples_cpp_JavaStuff,classVar);

Public, Private, Protected

The translation process takes account of field and method visibility, and attempts to map it to the generated code. However Java is less flexible in this respect:

Java-compatible arrays

Java-compatible arrays are somewhat more complex than native C++ arrays, and consequently a set of template classes are used to provide the translated implementation support. For example, Java arrays can be case to and from java.lang.Object (supporting synchronisation, hashCode, etc.), and have a built-in length pseudo-field.

The translated Java array classes are defined in java_Array.h. The defined array types (classes) are:

For the first two cases, arrays instances are created using a constructor which takes the array length as its sole argument, e.g.:
// array of 10 bytes
JavaPtr<java_Array_1_byte> data = new java_Array_1_byte(10);
// array of 10 java_lang_Objects
JavaPtr<java_Array_1_java_lang_Object> objects = new java_Array_1_java_lang_Object(10);
The class java_ObjectArray is almost a template class, but has only run-time specialisation: an array of some particular object type is created by:
// array of 10 Strings
JavaPtr<java_ObjectArray> strings = new java_ObjectArray( STRING_LIT("[Ljava.lang.String;"), 10 );

This class information is used for run-time type information (e.g. getClass()). However the compiler type of the array elements is only JavaPtr<java_lang_Object>, and so explicit casts are required to when accessing array elements.

To access elements, the array classes define the [] operator, e.g. (continued from above);

// simple for a primitive or java.lang.Object value
JAVA_BYTE b = (*data)[2];
JavaPtr<java_lang_Object> o = (*objects)[2];
// explicit cast for another object class
JavaPtr<java_lang_String> s = JavaPtr<java_lang_String>((void*)((*strings)[2]));
[Sorry about that, but with references to interfaces in C++ being different to references to objects, it makes treating an array of interfaces a real problem (in Java it is also an array of Objects). I didn't have chance to find a resolution for this at the time.]

Function Arguments and Return values

As has already been seen, function argument types should be specified using the primitive type macros (e.g. JAVA_INT), or the appropriate smart pointer type (e.g. JavaPtr<java_lang_String>).

This is particularly important for non-primitive return values, because if simple native pointers are returned then the last reference to the object may lost as the function moves out of scope, causing the object being returned to be deleted.

Exceptions

Exceptions are work-in-progress.

Because of the class mapping checking the class compatibility of exception classes cannot be done by the C++ compiler, but must be done by explicit run-time tests. Consequently, ANSI C++ exceptions cannot use the mapped Java exception classes directly. In addition, Symbian does not have standard ANSI exception handling, but its own more restricted system (only int values can be thrown). Consequently, exception handling is handled in a cross-platform manner using the following macros (shown in a typical example):

int myfunction(int arg1) JAVA_THROWS2(java_lang_RuntimeException,me_MyException) {
JavaPtr<me_MyException> ex = new me_MyException();
ex->init(arg1);
JAVA_THROW(ex);
}
int myfunction2(int arg1) JAVA_THROWS1(java_lang_RuntimeException) {
JAVA_TRY
myfunction(arg1);
JAVA_END_TRY
JAVA_CATCH(me_MyException, me)
JAVA_LOG(STRING_LIT("bang!"));
JAVA_END_CATCH
JAVA_CHECK_HANDLED
}

Threads

The native reimplementation of java_lang_Thread provides compatible thread support (see java_lang_Thread.h); calling translated classes from another natively spawned thread may cause crashes.

In general it is probably safest to use the translated Thread/Runnable classes and methods. However it is also possible to create a java_lang_Thread to directly run a natively defined function:

java_lang_Thread(NATIVE_THREAD_FN fn, void *arg);

The first native thread to make a call into a thread-related function causes the run-time support to initialise and to attach to that thread.

In some OSs (e.g. Symbian) this first thread is the only thread that can perform certain operations; some methods are provided - similar to SwingUtilities - to allow code to be scheduled to run on that particular thread:

static JAVA_BOOLEAN isFirstThread();
static void invokeLater(JavaPtr<java_lang_Runnable> runnable);
static void invokeAndWait(JavaPtr<java_lang_Runnable> runnable);
static void do_invoke_runnable_list();

Finally, to support the normal JVM operation of only exiting when all (non-daemon) threads have completed, the following method is provided for use in main:

static void wait_for_all_threads();

Synchronisation

See java_lang_Object lock() and unlock(), and the convenience class object_sync_autolock_t in java_sync.h to create synchronized code blocks.

java_lang_Object also supports wait, notify and notifyAll.

C++ functions cannot be synchronized as such, but an object_sync_autolock_t at the top of a function gives this kind of effect (see example_SyncTest.cpp, for example).

Logging and errors

EQUIP2 uses the Log4j API to log errors and do debugging; so should you. The EQUIP2 source includes a minimal Java re-implementation of part of the Log4j API, for use both on J2ME and when translated in C++. Currently this is not very configurable in terms of output, but is source-compatible with regular Log4j. (Source is in ../log4j/src)

Known issues and workarounds

Issue: Untested Exception support

Don't program to depend on exceptions if you can avoid it ;-)

Incomplete implementation(s) of java classes

Add class and method implementation as required; check them into Javatrans CVS so everyone can benefit.

Other FAQs

?

5. Changes

2007-05-30

2007-01-26

2007-01-27