Lesson 1: Project Setup
The topic of this lesson is how to set up your C++ project to use Java types. As described in the Technology overview, your application code usually deals with C++ proxy types, but the C++ proxy types internally rely on the C++ runtime library.
We glossed over this a little bit, but you also deal with the C++ runtime library directly when you configure the Java runtime environment for your C++ application. In fact, you could use just the C++ runtime library without any proxy types to load a JVM into your process.
Therefore, the simplest Java/C++ integration project does not involve any proxy types. It only uses some of the framework APIs from the runtime library to load a JVM. Such a program might look like this:
#include <stdio.h> // this includes all the "xmog_" framework types that you // might normally have to deal with #include "xmog_java_client.h" int main() { // acquire a reference to the singleton JVM loader, configured to // - allow overrides of settings by environment variables // - allow a default JVM if none is specified // - trace all facilities at the most verbose level xmog_jvm_loader & loader = xmog_jvm_loader::get_jvm_loader( true, true, TraceAll, TraceVerbose); try { // have the loader attempt to load a JVM. // this could fail for example if no JVM is installed or if the loader // cannot locate an installed JVM // The JVM instance is owned by the loader and does not need to be deleted. xmog_jvm * pJvm = loader.load(); } catch( xmog_exception xe ) { // xmog_exception is the root exception class for anything // related to the Java side. // Any proxy exception types present are derived from this type as well // but they offer a much richer API for of dealing with exception message // and other exception properties. // we ask the exception for the Java message string. We are given ownership // and need to release the string when we're done with it. char * msg = xe.get_message_chars(); fprintf( stderr, msg ); xmog_java_string::free( msg ); } return 0; }
This program uses the JDBC Courier runtime library's configuration API to acquire a JVM loader
instance and then attempts to load the default Java Virtual Machine using that instance. In a real integration project,
you would follow the JVM loading by using some C++ proxy types before returning from main()
.
All JDBC Courier runtime library type names start with xmog_
as a tribute to
Calvin and Hobbes' "transmogrifier gun." C++ namespaces were still not reliable in all C++ compilers when
we designed the product and we picked this prefix as unlikely to clash with anything else.
Compilation
Were you to try to compile this code with g++, for example by typing:
g++ -o loadjvm minimal.cpp
you would receive several compilation errors related to missing include files and missing types. Clearly you need to
tell the compiler where your xmog_
header files are located so all the xmog_
type declarations
can be found. Thus you add JDBC Courier's include directory to the compiler's include path:
g++ -I "<installdir>/cpp/v3/include" -o loadjvm minimal.cpp
Now your compilation succeeds with g++ but you are getting linker errors. With other compilers you might have to make a few more adjustments:
- Your code should be compiled with multithreading enabled.
The whole point of this exercise is to load a JVM and the JVM is inherently multithreaded. Most modern compilers only require you to link with the proper thread library.
- Your code should be compiled with runtime type information (RTTI) enabled.
This is usually the default with most modern compilers.
- Your code should be compiled with C++ exceptions enabled.
This is usually the default with most modern compilers.
Linking
At a minimum you will receive a link error due to the xmog_
type definitions not being available.
You added the header files but now you need to also add the library that contains the actual type implementations.
The days when there was one C++ compiler per platform are long gone. Most commercial platforms support at least a "native" compiler, often supplied and supported by the platform's manufacturer, as well as the GNU C++ compiler, g++. In the case of Linux, g++ is the native compiler. The picture is further complicated by different compiler versions and the presence of 32 and 64-bit versions. Normally, your goal will be to link with a runtime library that was built for your target platform and processor architecture, using the same compiler type and version that you are using to build your project.
We're saying "normally" because that is not always true. For example:
- many 64-bit processor architectures and operating systems support a 32-bit compatibility mode.
If you are running 64-bit Windows (amd64 processor architecture) for example, you might still wish to build and run 32-bit (x86 processor architecture) applications.
- many compilers are version compatible.
For example, the Microsoft Visual C++ compiler allows the mixing and matching of DLLs built with different compiler versions. You don't need to have the exact same version of the compiler that was used to build the library.
- some compilers are compiler type compatible.
The Intel C++ compiler can generate code that is binary compatible with g++ or Microsoft compilers.
Some combinations never make sense though. For example:
- All modules (libraries and executable) in a process must have been built for the same operating system and
processor architecture.
While you can run 32-bit executables on many 64-bit systems, you can never load an x86 (32-bit) DLL into an amd64 (64-bit) process. By the way: this restriction also applies to the JVM. You will not be able to successfully load a 32-bit JVM into a 64-bit process or vice versa.
- Some combinations of compiler versions might compile, link, and even appear to run, but cause crashes on
certain operations.
For example, you might not be able to successfully catch a C++ exception in a module built with an older version of g++ if it is thrown from a shared library built with a modern version of g++.
Where to Find the Right Runtime Library
JDBC Courier supplies you with a range of different runtime libraries and hopefully one of them matches your build environment. If not, you can always talk to us and we can build a runtime library for you.
The runtime library is always called xmogrt
. Depending on your platform, the full name of the file will be:
xmogrt.lib
/xmogrt.dll
(link with.lib
, run with.dll
) on Windows,libxmogrt.dylib
on MacOS-X,libxmogrt.so
on most Unix and Linux systems
The libraries can be located in subfolders of the cpp/v3/lib
directory.
The screenshot to the right shows a typical directory hierarchy of runtime library directories.
As you can see, the first level after cpp/v3/lib
is an operating system identifier.
The second level is a processor architecture identifier. The processor architecture is used to identify both
the family of processors and the bittedness. 64-bit versions of a processor architecture are represented by their
own code. For example, the 32-bit version of Intel X86 compatible processors is represented by the family code
x86
whereas the 64-bit version is represented by the family code amd64
. We use commonly
known identifiers, so you should not have any trouble locating the proper directory.
The third level is a combined compiler type and compiler version identifier. As mentioned above, you don't necessarily have to match the type and version exactly, but you should try to use the best match that you can find.
Finally, there are debug and release versions of the runtime library. The fourth level distinguishes between these two flavors.