From 0192562ce71078ce91cf02458ee92bc4603dc8ad Mon Sep 17 00:00:00 2001
From: Kenneth Russel
-
Chapter 1 - Introduction
@@ -1960,16 +1955,300 @@ is no longer reachable, as it is backed by a direct New I/O
ByteBuffer
. The fields of the struct are exposed as
methods which supply both getters and setters.
This example, taken from JOGL's X11 binding, illustrates how to
+return an array of structs from C to Java. The
+XGetVisualInfo
function from the X library has the
+following signature:
+ XVisualInfo *XGetVisualInfo( + Display* display, + long vinfo_mask, + XVisualInfo* vinfo_template, + int* nitems_return + ); ++ +
Note that the XVisualInfo
data structure itself
+contains many elements, including a pointer to the current visual. We
+use the following trick in the header file to cause GlueGen to treat
+the Display*
in the above signature as well as the
+Visual*
in the XVisualInfo
as opaque
+pointers:
+ typedef struct {} Display; + typedef struct {} Visual; + typedef unsigned long VisualID; + + typedef struct { + Visual *visual; + VisualID visualid; + int screen; + int depth; + int c_class; /* C++ */ + unsigned long red_mask; + unsigned long green_mask; + unsigned long blue_mask; + int colormap_size; + int bits_per_rgb; + } XVisualInfo; ++ +
XGetVisualInfo
returns all of the available pixel
+formats in the form of XVisualInfo
s which match a given
+template. display
is the current connection to the X
+server. vinfo_mask
indicates which fields from the
+template to match against. vinfo_template
is a partially
+filled-in XVisualInfo
specifying the characteristics to
+match. nitems_return
is a pointer to an integer
+indicating how many XVisualInfo
s were returned. The
+return value, rather than being a pointer to a single
+XVisualInfo
, is a pointer to the start of an array of
+XVisualInfo
data structures.
There are two basic steps to being able to return this array
+properly to Java using GlueGen. The first is creating a direct
+ByteBuffer of the appropriate size in the autogenerated JNI code. The
+second is slicing up this ByteBuffer appropriately in order to return
+an XVisualInfo[]
at the Java level.
In the autogenerated JNI code, after the call to
+XGetVisualInfo
is made, the outgoing
+nitems_return
value points to the number of elements in
+the returned array, which indicates the size of the direct ByteBuffer
+which would need to wrap these elements. However, if we look at the
+implementation of one of the generated glue code variants for this
+method (specifically, the one taking an int[]
as the
+third argument), we can see a problem in trying to access this value
+in the C code:
+JNIEXPORT jobject JNICALL +Java_testfunction_TestFunction_XGetVisualInfo1__Ljava_nio_ByteBuffer_2JLjava_nio_ByteBuffer_2Ljava_lang_Object_2I( + JNIEnv *env, jclass _unused, jobject arg0, jlong arg1, jobject arg2, jobject arg3, jint arg3_byte_offset) { + ... + int * _ptr3 = NULL; + ... + if (arg3 != NULL) { + _ptr3 = (int *) (((char*) (*env)->GetPrimitiveArrayCritical(env, arg3, NULL)) + arg3_byte_offset); + } + _res = XGetVisualInfo((Display *) _ptr0, (long) arg1, (XVisualInfo *) _ptr2, (int *) _ptr3); + if (arg3 != NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, arg3, _ptr3, 0); + } + if (_res == NULL) return NULL; + return (*env)->NewDirectByteBuffer(env, _res, ??? What to put here ???); +} ++ +
Note that at the point of the statement "What to put here?" the
+pointer to the storage of the int[]
, _ptr3
,
+has already been released via
+ReleasePrimitiveArrayCritical
. This means that it may not
+be referenced at the point needed in the code.
To solve this problem we use the TemporaryCVariableDeclaration +and TemporaryCVariableAssignment +directives. We want to declare a persistent integer variable down in +the C code and assign the returned array length to that variable +before the primitive array is released. While in order to do this we +unfortunately need to know something about the structure of the +autogenerated JNI code, at least we don't have to hand-edit it +afterward. We add the following directives to the configuration file:
+ ++ # Get returned array's capacity from XGetVisualInfo to be correct + TemporaryCVariableDeclaration XGetVisualInfo int count; + TemporaryCVariableAssignment XGetVisualInfo count = _ptr3[0]; ++ +
Now in the autogenerated JNI code the variable "count" will +contain the number of elements in the returned array. We can then +reference this variable in a ReturnValueCapacity directive:
+ ++ ReturnValueCapacity XGetVisualInfo count * sizeof(XVisualInfo) ++ +
At this point the XGetVisualInfo
binding will return
+a Java-side XVisualInfo
object whose backing ByteBuffer
+is the correct size. We now have to inform GlueGen that the underlying
+ByteBuffer represents not a single XGetVisualInfo
struct,
+but an array of them, using the ReturnedArrayLength directive. This
+conversion is performed on the Java side of the autogenerated code.
+Here, the first element of either the passed IntBuffer
or
+int[]
contains the number of elements in the returned
+array. (Alternatively, we could examine the length of the ByteBuffer
+returned from C to Java and divide by
+XVisualInfo.size()
.) Because there are two overloadings
+produced by GlueGen for this method, if we reference the
+nitems_return
argument in a ReturnedArrayLength directive, we need
+to handle not only the differing data types properly
+(IntBuffer
vs. int[]
), but also the fact
+that both the integer array and its offset value are substituted for
+any reference to the fourth argument.
To solve this problem, we define a pair of private helper +functions whose purpose is to handle this overloading.
+ ++ CustomJavaCode TestFunction private static int getFirstElement(IntBuffer buf) { + CustomJavaCode TestFunction return buf.get(buf.position()); + CustomJavaCode TestFunction } + CustomJavaCode TestFunction private static int getFirstElement(int[] arr, + CustomJavaCode TestFunction int offset) { + CustomJavaCode TestFunction return arr[offset]; + CustomJavaCode TestFunction } ++ +
Now we can simply write for the returned array length:
+ ++ ReturnedArrayLength XGetVisualInfo getFirstElement({3}) ++ +
That's all that is necessary. GlueGen will then produce the +following Java-side overloadings for this function:
+ ++ public static XVisualInfo[] XGetVisualInfo(Display arg0, + long arg1, + XVisualInfo arg2, + java.nio.IntBuffer arg3); + public static XVisualInfo[] XGetVisualInfo(Display arg0, + long arg1, + XVisualInfo arg2, + int[] arg3, int arg3_offset); ++ +
As it happens, we don't really need the Display and Visual data
+structures to be produced; they can be treated as long
s
+on the Java side. Therefore we can add the following directives to the
+configuration file:
+ # We don't need the Display and Visual data structures to be + # explicitly exposed + Opaque long Display * + Opaque long Visual * + # Ignore the empty Display and Visual data structures (though made + # opaque, the references from XVisualInfo and elsewhere are still + # traversed) + Ignore Display + Ignore Visual ++ +
The final generated Java API is the following:
++ public static XVisualInfo[] XGetVisualInfo(long arg0, + long arg1, + XVisualInfo arg2, + java.nio.IntBuffer arg3); + public static XVisualInfo[] XGetVisualInfo(long arg0, + long arg1, + XVisualInfo arg2, + int[] arg3, int arg3_offset); ++
As with the example above, this +example is taken from JOGL's X11 binding. Here we show how to expose +to Java a C routine returning an array of pointers to a data +structure.
+The declaration of the function we are binding is as follows:
++ typedef struct __GLXFBConfigRec *GLXFBConfig; + GLXFBConfig *glXChooseFBConfig( Display *dpy, int screen, + const int *attribList, int *nitems ); ++
This function is used during allocation of a hardware-accelerated
+off-screen surface ("pbuffer") on X11 platforms; its exact meaning is
+not important. The semantics of the arguments and return value are as
+follows. As in the previous example, it
+accepts a connection to the current X display as one argument. The
+screen of this display is the second argument. The
+attribList
is a zero-terminated list of integer
+attributes; because it is zero-terminated, the length of this list is
+not passed to the function. As in the previous example, the
+nitems
argument points to an integer into which the
+number of returned GLXFBConfig
objects is placed. The
+return value is an array of GLXFBConfig
objects.
Because the GLXFBConfig
data type is typedefed as a
+pointer to an opaque (undefined) struct, the construct
+GLXFBConfig*
is implicitly a "pointer-to-pointer" type.
+GlueGen automatically assumes this is convertible to a Java-side array
+of accessors to structs. The only configuration necessary is to tell
+GlueGen the length of this array.
As in the previous example, we use the TemporaryCVariableDeclaration +and TemporaryCVariableAssignment +directives to capture the length of the returned array:
+TemporaryCVariableDeclaration glXChooseFBConfig int count; +TemporaryCVariableAssignment glXChooseFBConfig count = _ptr3[0]; + +The structure of the generated glue code for the return value is +subtly different than in the previous example. The question in this +case is not whether the return value is a pointer to a single object +vs. a pointer to an array of objects; it is what the length of the +returned array is, since we already know that the return type is +pointer-to-pointer and is therefore an array. We use the ReturnValueLength directive for this +case:
+ ++ ReturnValueLength glXChooseFBConfig count ++ +We add similar Opaque directives to the previous example to yield the +resulting Java bindings for this function: + +
+ public static GLXFBConfig[] glXChooseFBConfig(long dpy, + int screen, + java.nio.IntBuffer attribList, + java.nio.IntBuffer nitems); + public static GLXFBConfig[] glXChooseFBConfig(long dpy, + int screen, + int[] attribList, int attribList_offset, + int[] nitems, int nitems_offset); ++Note that because the GLXFBConfig data type is returned as an element +of an array, we can not use the Opaque directive to erase this data +type to
long
as we did with the Display
data
+type.