| |
|
|
||||
![]() |
||||||
| |
|
|||||
|
Talking to the Natives Game coders usually do not trust cross-platform APIs based on layers of abstraction. Interpreted bytecode does not typically appeal to an industry that still counts on assembly to get a performance edge. Windows-based games sell, period, and portability is not really an issue. Compare this to the holy grail of "100 percent pure Java." Mainstream Java technology is seemingly meant for tiny "gamelets," not serious games. Besides, despite the bloat, there are bits and pieces missing from the Java core classes: access to certain devices and system services is simply not available (and might never be, for design and security reasons), and politics sometimes gets in the way (witness the lack of Java OpenGL bindings). However, if you take a closer look, it turns out that there is always native code at the very heart of all that "pure Java": there is a JVM written in native code, and core classes partly implemented as native code. Here reigns the Java Native Interface, gluing together Java and native C/C++ code, and it is the key to combining your native code with Java technology. JNI is part of the Java specification, and it's a mandatory part of all Java implementations. Sun was recently granted a court injunction forcing Microsoft to add JNI to the Microsoft Java implementation. Ideally, .DLLs and binaries using JNI should be byte-compatible for a given platform. The 1997 JNI specification is available online, and there are also books on the subject, so this article includes only a brief summary of it before we get into its applicability in games. The first task JNI must solve is getting the JVM and user-written native code to agree on built-in types and memory layout to exchange data (see Figure 1). Only some of the core classes are represented for the native code (see Figure 2) — most of them arrays of the built-in types. What about jclass and jobject? JNI will not hand you the memory layout of a Java object, but it must provide you a handle. It even preserves the relationship between java.lang.Object and java.lang.Class. A jclass object can be cast to jobject safely in any JNI that complies to the Java specification (non-compliant implementations have been found). JNI is foremost aimed at C (the C++ bindings are just inlined wrappers), and no support for object-oriented programming on the native side is offered. With the exception of Throwable, String, and arrays, all classes have to be squeezed through the jobject and jclass representation. Arrays of arbitrary classes (including String) will always be mapped to jobjectArray. JNI defines JNI_FALSE/TRUE, a jvalue union type, and a jsize for your convenience. Further, handles have access to fields, and also call methods of classes and objects. It might look like java.lang.reflect.Field and java.lang.reflect.Method are the Java equivalents to JNI's jfieldID and jmethodID, but JNI predated the Reflection API, and actual reflection support was added only to the latest JNI revision. Caching field and method IDs is a good idea, as retrieval involves a string lookup. Be warned that caching can get tricky in applications with multiple threads and class loaders. You will have to keep an eye on the garbage collector as well — without a strong reference acquired by NewGlobalRef(), the garbage collector might remove the object your native code is still referring to. Likewise, dangling references not removed by DeleteGlobalRef() can keep obsolete Java objects from being collected. Use DeleteLocalRef() to avoid accumulating temporary references within loops. JDK 1.2 offers limited support for weak references, too. Within your native code, all will revolve around the JNIEnv interface function table — your door to the Java side. It provides methods to handle references, create objects, load classes, access fields and call methods. Further, you get utility functions to iterate arrays, throw exceptions, and perform monitoring to make the native code threadsafe. Finally, an executable can also register functions as native methods, making code known to the JVM without dynamic linkage. Listing 1 is a small example showing how native code can use Java to get things done, and how a native callback is registered with the JVM. Listing 3, discussed later on, does the opposite: it shows how Java calls native code. |
|
|