Chris Greenhalgh, 2007-01-26; last updated 2007-05-30
Contents:
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:
Note that the current build files require ANT for initial translation and Microsoft Visual Studio (used from the a Visual Studio command shell).
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.
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 cppThis will:
> 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.
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.
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:
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:
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.
#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.
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.
#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).
#include "java_util_Vector.h"
...
JavaPtr<java_util_Vector> aVector = new java_util_Vector();
aVector->init(); // the no-arg constructor
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();
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):
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.
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();
JavaPtr<equip2_samples_cpp_DataBean> db = new equip2_samples_cpp_DataBean();Class fields, however, must be accessed more carefully, using the macro defined in java_global.h:
db->init();
...
db->test = TRUE;
JAVA_BOOLEAN aBoolean = db->test;
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);
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:
// array of 10 bytesThe class java_ObjectArray is almost a template class, but has only run-time specialisation: an array of some particular object type is created by:
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);
// 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[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.]
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]));
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 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
}
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();
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).
Don't program to depend on exceptions if you can avoid it ;-)
2007-05-30
2007-01-26