Home · Examples 

The Qt Jambi Generator

The generator is a Qt application which can be used to map C++ based APIs onto equivalent Java APIs, enabling C++ programmers to easily integrate their own Qt code with Java.

The generator supports a selected subset of C++, covering the most common constructs in Qt. It creates the Java Api by parsing the C++ header files and generating Java source files. It also generates code to tie the Java classes to the C++ classes. Based on the Java Native Interface (JNI), this code ensures that method calls made in Java are redirected to the corresponding functions in the C++ library.

The Qt Jambi generator is a command line tool accepting a header file and a type system specification as arguments:
./generator [options] header-file typesystem-file
The header file should include the relevant modules of the C++ based library. The type system specification is a handwritten XML document listing the types that will be made available in the generated Java API (see the type system documentation for details).

See also: Qt Jambi Generator Example

Command Line Options
--debug-level=sparse | medium | full Specifies the debug level of the generator which, for example, is useful to understand how the type system is loaded.
--dump-object-tree Dumps the parsed object tree as plain text which can be helpful when debugging the type system.
--juic-file Specifies the location of the information file that is used together with Qt Jambi's implementation of UIC (juic) to generate code for custom libraries.
--convert-to-jui=[uifile] Converts the given .ui file to a Java user interface file (.jui). The .jui file format's datastructure is based on Java syntax.
--custom-widgets=[files] To be used together with --convert-to-jui in order to specify the names of xml files containing lists of promoted widgets used in the original .ui file. These custom widgets will then be replaced by their specified Java counter part. The class name in Java should be the same as the type to which the widget has been promoted, and there should only be a single class specified for each class name. If several files are required, separate the file names using ';' on Windows and ':' on other platforms.
--no-cpp-h The generator will not generate any C++ header files.
--no-cpp-impl The generator will not generate any C++ source files.
--no-java The generator will not generate any Java source files.
--no-metainfo The generator will not generate any meta-info files.
--include-eclipse-warnings Generate SuppressWarnings annotations that are understood by the Eclipse IDE in particular.
--no-suppress-warnings All warnings are shown even if they are suppressed by the type system specification (see the type system documentation for details).
--output-directory Specifies the output directory for the generated code.
When running the generator, the header files are preprocessed (i.e., all macros and type definitions are expanded). Then enums, namespaces and classes are mapped according to the type system specification. For each C++ class that is encountered, the generator creates a Java source file and a set of C++ implementation files.

Warning: The Qt Jambi generator is written to handle Qt- based source code, and is not intended for mapping C++ libraries in general.

The Java Source File

The Java source file contains one public class with the same name as the original C++ class.

All public and protected members of the C++ class are included in the Java class. For each C++ function, the generator creates a native Java method, and each original member variable generates a set and get method pair since JNI only provides access to native resources through methods. For example, the C++ member variable:

QString text;
String text();
void setText(String text);
in the Java API.

Using the type system specification, it is also possible to rename or remove functions when generating the Java API, as well as changing the access privileges. It is even possible to use the type system to inject arbitrary code into the Java source file, such as an extra member method.

The C++ Implementation Files

The C++ source file contain two different parts: a shell class and the implementation of the functions declared in the Java source file.

The shell class inherits the original class in the C++ based Qt library, and makes it possible to reimplement virtual functions in Java and to call protected functions in the C++ based library from Java. Whenever an instance of a class is constructed in Java, a corresponding object of the shell class is constructed. If a class has been extended by a user's custom Java class, and one or more of the virtual functions in the class have been reimplemented, the shell class ensures that it is the reimplemented Java implementations that are called.

As with the generated Java source file, it is possible to inject code in the reimplemented virtual functions in the shell classes using the type system specification.

The C++ header file is primarily an implementation detail and can in most cases be ignored.

Log files produced by the generator

As it parses C++ headers and generates code, the Qt Jambi generator creates a set of log files that can be useful in determining and anticipating problems in the resulting Java library.

The following is an overview of the log files it produces and how they can be interpreted to tweak a Qt Jambi type system into providing a better mapping into Java.

Rejected declarations

The Qt Jambi generator produces four files to list types and other declarations that have been discovered while parsing the original C++ headers, but that for some reason have not made it into the generated code. These are the mjb_rejected_classes.log, mjb_rejected_enums.log, mjb_rejected_fields.log, and mjb_rejected_functions.log files.

The interesting section of either of these log files is called "Not in type system." This lists the declarations that have been rejected because they do not have any corresponding entry in the type system. Remove them from this list by either rejecting them explicitly (use rejection tags, modify-function tags and modify-field tags) or by giving them appropriate type system entries.

Native pointer API

When the Qt Jambi generator comes over a type declaration for a field or function and it is unable to convert it to Java, it will substitute it with the com.trolltech.qt.QNativePointer class in the generated Java code. This is a Java wrapper for a C++ pointer and can be powerful, but quite hazardous and difficult to use.

Since you might want to provide a more usable API in place of the QNativePointer, the generator will provide you with a log file called mjb_nativepointer_api.log which lists all public or protected functions that have been mapped using native pointers.

A good pattern for tackling such cases is to first divide the list into virtual and final functions, then altering the inconvertible types in the signatures of the virtual functions by using the replace-type and conversion-rule tags.

The problematic types in the signatures of final functions can be handled more easily: Use the modify-function tag to make the original function private in the Java API, and then use the inject-code tag to inject a new method with the same name, but with more user friendly types in its signature. In the injected method, you can convert the types correctly and call the original function with the converted arguments. If needed, you can also use the modify-function tag to rename the original function.

In a few cases, native pointer API cannot be translated into Java, even manually. This is specifically the case when it grants access to internal POD data. You are then left with the choice of either rejecting the function from the API (by using modify-function) or leaving it. The former would be a good choice if the same functionality is provided by other API (the rwidth() function in QSize is an example of this, since the class also has a setWidth() function.)

In some cases, the API is unique (see bits() in QImage), in which case it would be a good idea to provide the low level native pointer API for people who need it.

Candidates for reference counting

Since the code produced by the Qt Jambi generator is simply a thin mapping on top of C++ code, there are certain cases where you need to manually specify how the Java objects should be reference counted by the generated code.

This is true for any C++ function which takes a pointer to an object and retains the pointer for an undetermined length of time. In cases such as these, we need to make sure that the Java mapping of the object is not finalized as long as there is a reference to it in C++, since the finalization would delete the C++ object and could cause erratic behavior or crashes. Note that a special case of this is objects of QObject subclasses which are reparented by the function in question. Giving a QObject a parent will turn ownership of the object over to the parent object, meaning that it will live as long as the parent lives, and be destroyed when the parent is destroyed.

The generator attempts to identify potential candidates for such behaviors by looking for certain naming patterns of functions combined with arguments of object types in the signature. It produces the mjb_reference_count_candidates.log file.

There are two specific ways of handling such problems. If the function takes ownership of the object (meaning that it will delete the C++ object once it is done with it) you can use the define-ownership tag to grant ownership of the Java object to C++. This means that the Java object will not be finalized until the C++ object is deleted by its new owner. If the function does not take ownership, but still saves a reference to the object, you can use the reference-count tag to make the generated code retain a reference to the Java object in order to prevent the garbage collector from collecting it. The idea here is to have a reference counting mechanism in place which follows the same pattern as in the C++ API, which means you can have single references to objects which are overwritten when the function is called again (e.g. setDocument() in QGraphicsTextItem) or you can have several references added and removed from a list (e.g. addAction() in QWidget.)

If you know the default behavior to be safe for the particular function (the function does not retain a reference, or it is handled through the parenting mechanism of QObject) you can use reference-count with the action attribute set to ignore to make sure the function is removed from the log file.

Virtual functions taking arguments of object types

In certain cases, when a user has overridden a virtual function in her Java code, and objects are passed to it, it may be necessary to prevent the objects from being accessible after the function call has terminated. The reason for this is that the object in question may have been created in the C++ code and may be destroyed at some undeterminable point later on.

An example of this is the event handlers in Qt Jambi. Each event handler takes an object of a QEvent subclass, and in cases where the event is triggered by a system event, the C++ object will be created by Qt Jambi prior to calling the event handler, and deleted directly after the call is done. If the user had her event handler save a reference to the event, and later attempts to access it, the application will crash. Note that this does not apply to QObject subclasses, since reference counting of such classes are handled automatically.

The mjb_object_type_usage.log file lists all virtual functions that take one or more object types as arguments, as long as the type is not a subclass of QObject. These can be handled either by use of the define-ownership tag (if the receiver of the object is expected to delete it) or by using the modify-argument tag with the invalidate-after-use attribute set to yes. The latter will cause the generator to produce code which invalidates the Java object after the virtual method call has completed. When a Java object is invalid, it will throw a QNoNativeResourcesException whenever it is accessed, alterting the user that the object is no longer usable. Note that the object will only be invalidated if any part of it is owned by C++ (objects created in Java code and thus owned by Java will not suffer from this problem, as C++ will not attempt to delete it.)

Copyright © 2009 Nokia Corporation and/or its subsidiary(-ies) Trademarks
Qt Jambi 4.5.2_01