Pitfalls While Coding With the C++ v3 Proxy Types
There are some mistakes a lot of JMS Courier developers make, particularly when they are new to the product. We have put together a list of the most common ones to help you when you are looking for a solution to a nagging problem related to C++ proxy types.
Please feel free to send us problems you ran into that you think deserve to be on this list.
Click on a pitfall to see its explanation.
I have a valid proxy instance but I keep getting a NullPointerException
thrown from Java.
We'll take a guess at what your code looks like. It's either:
Foo *pFoo = new Foo();
if( pFoo != NULL ) {
// this is where the exception gets thrown
pFoo->doSomething();
}
or:
Foo foo = Foo();
// this is where the exception gets thrown
pFoo.doSomething();
In both cases you have a valid proxy object instance that references Java null
. Just because you
have a proxy object does not mean that it actually references a corresponding Java object. Much like a Java
variable is initialized to null
when you declare it without an explicit value, proxy objects are
initially set to be "empty." Explicitly invoking the C++ default constructor only results in explicitly setting
them to "empty."
Please see the coding tutorial lessons on Constructors and null for more details.
The no-argument constructor of my Java type is not working.
Please see the coding tutorial lessons on Constructors
for a detailed explanation, but to cut a long story short, the Java no-argument constructor and the Java
one-argument-of-declaring-type constructor need to be invoked with a special argument called _use_java_ctor
.
The reason is that their natural proxy type signatures clash with two special C++ constructors, the so-called default constructor and the copy constructor.
To construct a java.lang.Object
using its no-argument constructor from C++, you have to write:
java::lang::Object obj( _use_java_ctor );
To construct a java.lang.String
using its String(String)
constructor from C++, you
have to write:
java::lang::String s1 = "test";
java::lang::String s2( s1, _use_java_ctor );
If you forget the _use_java_ctor
argument you will still have a valid String
reference in s2
but it will reference the same instance that is referenced by s1
because
you invoked the C++ copy constructor rather than the Java constructor that you intended to invoke.
I need to make sure my Java object is not NULL
, but my code does not compile.
Taking a wild guess, you have something like:
Foo foo( 42 );
// this is where I get the compilation error
if( foo != NULL ) {
...
}
NULL
or, in more modern C++, nullptr
is what C++ developers compare their pointers to
when they check them for validity. Your proxy objects are not pointers and they can never be NULL
.
We provide you with a global variable called null
that you should use when checking whether your proxies represent a valid Java object. So your code should look like this:
Foo foo( 42 ); // this now compiles if( foo != null ) { ... }
In Java I write com.myproduct.Foo.class
. How do I do that in C++?
class
is a reserved word in both Java and C++, but Java allows .class
after a type name
to refer to the Class
instance representing the named type. C++ has no corresponding feature and the keyword
class
is only allowed in declarations and some template contexts.
To gain access to a Class
instance for a given type name you need to use Class::forName()
, for example:
Class fooClass = Class::forName("com.myproduct.Foo");
I know I'm getting back a Foo
. I tried casting the returned Object
to Foo
but it does not compile.
You probably have some code like this:
// the factory method is declared to return its results as Objects Object result = MyFactory::getInstance(); // but we know that it really is a Foo instance, yet this // does not compile Foo foo = (Foo)result;
You know that your Java function is returning a Foo
, even though it's declared as returning an Object
.
This does not cause problems because in Java all objects are passed by reference, so you can return a Foo
from a method that only promises to return an Object
.
The C++ proxy method's type is based on the Java method's declared type, so it returns an instance of C++ proxy type Object
.
The big difference to the Java side is that it actually returns an object of type Object
by value, not by reference.
The Java object that it references will be a Foo
, but on the C++ side it is most definitely an Object
,
never anything else.
You cannot just cast an Object
to a Foo
when it is not a Foo
and hope that things
work out. Instead, you need to use the JMS Courier method of giving you a Foo
proxy object for an existing
proxy object of a different type. That method is called dyna_cast()
.
When you have a proxy object of one type and you want to say:
"I know you think you represent just an Object
, but
I know better, so please present yourself as a proxy object for type Foo
instead!"
Simply write:
// the factory method is declared to return its results as Objects Object result = MyFactory::getInstance(); // but we know that it really is a Foo instance Foo foo = Foo::dyna_cast( result );
This is the JMS Courier way of performing a class cast. You can read more about this in the coding tutorial lessons on Casting.
I'm getting garbage or a crash in printf()
.
You are probably using a function from the printf()
family that has
a format string and an ellipsis for the additional arguments. You cannot use a proxy
String instance directly in a place where a "%s"
string argument is expected.
You also cannot use a primitive proxy element with its format code, e.g. "%d"
,
and expect it to work without a cast.
Instead, you have to ask the String instance for a pointer to its characters or make sure that the field is cast to the proper primitive type. The following snippet illustrates:
java::lang::String foo = "I am a Java string"; Bar bar( _use_java_ctor ); printf( "--> %s <--\n", foo.to_chars() ); printf( "--> %d <--\n", (int)bar.intField );
My application just crashes when I try to run it.
There could be many reasons for that, starting with a misconfiguration of your Java environment. We assume that you have all the required pieces (runtime library and other shared libraries) in place or you would probably say that your application does not start.
The first candidate for debugging is always the configured JVM. Very often you will get a crash when the configured JVM cannot be found or when it is incompatible, for example your application is built in 64-bit mode and the JVM is 32-bit, or vice versa.
Then we would look at the runtime configuration. Are you setting JVM options that could cause problems, for example heap sizes that cannot be satisfied or paths with typos?
Finally, are you catching exceptions? We always recommend that you initialize your Java
environment in your main()
method and surround it with a try/catch
block, like this:
try { // use verbose tracing if you're debugging a crash xmog_jvm_loader & loader = xmog_jvm_loader::get_jvm_loader( true, true, TraceAll, TraceVerbose ); ... // configure your JVM xmog_jvm * pJvm = loader.load(); if( pJvm == NULL ) { // handle load error that did not throw exception ... } } catch( xmog_exception & xe ) { // handle load error that threw framework exception const char * message = xe.get_message_chars(); std::cerr << message << std::endl; xmog_java_string::free( message ); }
My application fails with an IncompatibleClassChangeError
.
The most likely cause is that your proxy types were generated for a slightly incompatible
version of the Java API in question. We ran into this issue the first time
when a customer tried running a C++ application that was using
JMS v2 proxy types with a JBoss MQ implementation that was still using JMS v1.1.
At runtime all calls worked,
except for the Connection.close()
call at the very end.
We finally figured out that in JMS v1.1 the close()
method is declared by
the Connection
type and not inherited from any super interfaces. In JMS v2
on the other hand, the close()
method
is inherited from the java.lang.AutoClosable
interface.
Our C++ proxy types that were
generated against the JMS v2 API mirrored this new inheritance. When running with a JMS 1.1 implementation,
they attempted to resolve the connect()
method against the AutoClosable
type and the result was an IncompatibleClassChangeError
because while Java found the
close()
method it did not find the inheritance.
Within pure Java this API change was non-breaking for callers. When using JNI, the declaring type of the method is used when resolving the method and the change is breaking.
This JMS API version issue is just one concrete example of why your proxy types should always be generated against the version of your Java API that you intend to run with. The only Java changes you are safe to ignore are implementation changes. Changes to inheritance or the public APIs should always cause you to regenerate your C++ bindings.