/* * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistribution of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistribution in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed or intended for use * in the design, construction, operation or maintenance of any nuclear * facility. * * Sun gratefully acknowledges that this software was originally authored * and developed by Kenneth Bradley Russell and Christopher John Kline. */ package net.java.games.gluegen; import java.util.*; import java.io.*; import java.text.MessageFormat; import net.java.games.gluegen.cgram.types.*; /** Emits the C-side component of the Java<->C JNI binding. */ public class CMethodBindingEmitter extends FunctionEmitter { protected static final CommentEmitter defaultCommentEmitter = new DefaultCommentEmitter(); protected static final String arrayResLength = "_array_res_length"; protected static final String arrayRes = "_array_res"; protected static final String arrayIdx = "_array_idx"; private MethodBinding binding; /** Name of the package in which the corresponding Java method resides.*/ private String packageName; /** Name of the class in which the corresponding Java method resides.*/ private String className; /** * Whether or not the Java<->C JNI binding for this emitter's MethodBinding * is overloaded. */ private boolean isOverloadedBinding; /** * Whether or not the Java-side of the Java<->C JNI binding for this * emitter's MethodBinding is static. */ private boolean isJavaMethodStatic; /** * Optional List of Strings containing temporary C variables to declare. */ private List/**/ temporaryCVariableDeclarations; /** * Optional List of Strings containing assignments to temporary C variables * to make after the call is completed. */ private List/**/ temporaryCVariableAssignments; /** * Capacity of the return value in the event that it is encapsulated in a * java.nio.Buffer. Is ignored if binding.getJavaReturnType().isNIOBuffer() * == false; */ private MessageFormat returnValueCapacityExpression = null; /** * Length of the returned array. Is ignored if * binding.getJavaReturnType().isArray() is false. */ private MessageFormat returnValueLengthExpression = null; // Note: the VC++ 6.0 compiler emits hundreds of warnings when the // (necessary) null-checking code is enabled. This appears to just // be a compiler bug, but would be good to track down exactly why it // is happening. When the null checking is enabled for just the // GetPrimitiveArrayCritical calls, there are five warnings // generated for several thousand new if tests added to the code. // Which ones are the ones at fault? The line numbers for the // warnings are incorrect. private static final boolean EMIT_NULL_CHECKS = true; /** * Constructs an emitter for the specified binding, and sets a default * comment emitter that will emit the signature of the C function that is * being bound. */ public CMethodBindingEmitter(MethodBinding binding, boolean isOverloadedBinding, String javaPackageName, String javaClassName, boolean isJavaMethodStatic, PrintWriter output) { super(output); assert(binding != null); assert(javaClassName != null); assert(javaPackageName != null); this.binding = binding; this.packageName = javaPackageName; this.className = javaClassName; this.isOverloadedBinding = isOverloadedBinding; this.isJavaMethodStatic = isJavaMethodStatic; setCommentEmitter(defaultCommentEmitter); } public final MethodBinding getBinding() { return binding; } public String getName() { return binding.getName(); } /** * Get the expression for the capacity of the returned java.nio.Buffer. */ public final MessageFormat getReturnValueCapacityExpression() { return returnValueCapacityExpression; } /** * If this function returns a void* encapsulated in a * java.nio.Buffer, sets the expression for the capacity of the * returned Buffer. * * @param expression a MessageFormat which, when applied to an array * of type String[] that contains each of the arguments names of the * Java-side binding, returns an expression that will (when compiled * by a C compiler) evaluate to an integer-valued expression. The * value of this expression is the capacity of the java.nio.Buffer * returned from this method. * * @throws IllegalArgumentException if the * binding.getJavaReturnType().isNIOBuffer() == false * */ public final void setReturnValueCapacityExpression(MessageFormat expression) { returnValueCapacityExpression = expression; if (!binding.getJavaReturnType().isNIOBuffer()) { throw new IllegalArgumentException( "Cannot specify return value capacity for a method that does not " + "return java.nio.Buffer: \"" + binding + "\""); } } /** * Get the expression for the length of the returned array */ public final MessageFormat getReturnValueLengthExpression() { return returnValueLengthExpression; } /** * If this function returns an array, sets the expression for the * length of the returned array. * * @param expression a MessageFormat which, when applied to an array * of type String[] that contains each of the arguments names of the * Java-side binding, returns an expression that will (when compiled * by a C compiler) evaluate to an integer-valued expression. The * value of this expression is the length of the array returned from * this method. * * @throws IllegalArgumentException if the * binding.getJavaReturnType().isNIOBuffer() == false * */ public final void setReturnValueLengthExpression(MessageFormat expression) { returnValueLengthExpression = expression; if (!binding.getJavaReturnType().isArray()) { throw new IllegalArgumentException( "Cannot specify return value length for a method that does not " + "return an array: \"" + binding + "\""); } } /** * Returns the List of Strings containing declarations for temporary * C variables to be assigned to after the underlying function call. */ public final List/**/ getTemporaryCVariableDeclarations() { return temporaryCVariableDeclarations; } /** * Sets up a List of Strings containing declarations for temporary C * variables to be assigned to after the underlying function call. A * null argument indicates that no manual declarations are to be made. */ public final void setTemporaryCVariableDeclarations(List/**/ arg) { temporaryCVariableDeclarations = arg; } /** * Returns the List of Strings containing assignments for temporary * C variables which are made after the underlying function call. A * null argument indicates that no manual assignments are to be * made. */ public final List/**/ getTemporaryCVariableAssignments() { return temporaryCVariableAssignments; } /** * Sets up a List of Strings containing assignments for temporary C * variables which are made after the underlying function call. A * null argument indicates that no manual assignments are to be made. */ public final void setTemporaryCVariableAssignments(List/**/ arg) { temporaryCVariableAssignments = arg; } /** * Get the name of the class in which the corresponding Java method * resides. */ public String getJavaPackageName() { return packageName; } /** * Get the name of the package in which the corresponding Java method * resides. */ public String getJavaClassName() { return className; } /** * Is the Java<->C JNI binding for this emitter's MethodBinding one of * several overloaded methods with the same name? */ public final boolean getIsOverloadedBinding() { return isOverloadedBinding; } /** * Is the Java side of the Java<->C JNI binding for this emitter's * MethodBinding a static method?. */ public final boolean getIsJavaMethodStatic() { return isJavaMethodStatic; } protected void emitReturnType(PrintWriter writer) { writer.print("JNIEXPORT "); writer.print(binding.getJavaReturnType().jniTypeName()); writer.print(" JNICALL"); } protected void emitName(PrintWriter writer) { writer.println(); // start name on new line writer.print("Java_"); writer.print(jniMangle(getJavaPackageName())); writer.print("_"); writer.print(jniMangle(getJavaClassName())); writer.print("_"); if (isOverloadedBinding) { writer.print(jniMangle(binding)); //System.err.println("OVERLOADED MANGLING FOR " + binding.getName() + // " = " + jniMangle(binding)); } else { writer.print(jniMangle(binding.getName())); //System.err.println(" NORMAL MANGLING FOR " + binding.getName() + // " = " + jniMangle(binding.getName())); } } protected int emitArguments(PrintWriter writer) { writer.print("JNIEnv *env, "); int numEmitted = 1; // initially just the JNIEnv if (isJavaMethodStatic && !binding.hasContainingType()) { writer.print("jclass"); } else { writer.print("jobject"); } writer.print(" _unused"); ++numEmitted; if (binding.hasContainingType()) { // "this" argument always comes down in argument 0 as direct buffer writer.print(", jobject " + JavaMethodBindingEmitter.javaThisArgumentName()); } for (int i = 0; i < binding.getNumArguments(); i++) { JavaType javaArgType = binding.getJavaArgumentType(i); // Handle case where only param is void if (javaArgType.isVoid()) { // Make sure this is the only param to the method; if it isn't, // there's something wrong with our parsing of the headers. assert(binding.getNumArguments() == 1); continue; } if (javaArgType.isJNIEnv() || binding.isArgumentThisPointer(i)) { continue; } writer.print(", "); writer.print(javaArgType.jniTypeName()); writer.print(" "); writer.print(binding.getArgumentName(i)); ++numEmitted; } return numEmitted; } protected void emitBody(PrintWriter writer) { writer.println(" {"); emitBodyVariableDeclarations(writer); emitBodyUserVariableDeclarations(writer); emitBodyVariablePreCallSetup(writer, false); emitBodyVariablePreCallSetup(writer, true); emitBodyCallCFunction(writer); emitBodyUserVariableAssignments(writer); emitBodyVariablePostCallCleanup(writer, true); emitBodyVariablePostCallCleanup(writer, false); emitBodyReturnResult(writer); writer.println("}"); writer.println(); } protected void emitBodyVariableDeclarations(PrintWriter writer) { // Emit declarations for all pointer and String conversion variables if (binding.hasContainingType()) { emitPointerDeclaration(writer, binding.getContainingType(), binding.getContainingCType(), CMethodBindingEmitter.cThisArgumentName(), null); } boolean emittedDataCopyTemps = false; for (int i = 0; i < binding.getNumArguments(); i++) { JavaType type = binding.getJavaArgumentType(i); if (type.isJNIEnv() || binding.isArgumentThisPointer(i)) { continue; } if (type.isArray() || type.isNIOBuffer()) { String convName = pointerConversionArgumentName(i); // handle array/buffer argument types boolean needsDataCopy = emitPointerDeclaration(writer, binding.getJavaArgumentType(i), binding.getCArgumentType(i), convName, binding.getArgumentName(i)); if (needsDataCopy && !emittedDataCopyTemps) { // emit loop counter and array length variables used during data // copy writer.println(" jobject _tmpObj;"); writer.println(" int _copyIndex;"); writer.println(" jsize _tmpArrayLen;"); emittedDataCopyTemps = true; } } else if (type.isString()) { writer.print(" const char* _UTF8"); writer.print(binding.getArgumentName(i)); writer.println(" = NULL;"); } } // Emit declaration for return value if necessary Type cReturnType = binding.getCReturnType(); JavaType javaReturnType = binding.getJavaReturnType(); String capitalizedComponentType = null; if (!cReturnType.isVoid()) { writer.print(" "); // Note we must respect const/volatile for return argument writer.print(binding.getCSymbol().getReturnType().getName(true)); writer.println(" _res;"); if (javaReturnType.isArray()) { if (javaReturnType.isNIOByteBufferArray()) { writer.print(" int "); writer.print(arrayResLength); writer.println(";"); writer.print(" int "); writer.print(arrayIdx); writer.println(";"); writer.print(" jobjectArray "); writer.print(arrayRes); writer.println(";"); } else { writer.print(" int "); writer.print(arrayResLength); writer.println(";"); Class componentType = javaReturnType.getJavaClass().getComponentType(); if (componentType.isArray()) { throw new RuntimeException("Multi-dimensional arrays not supported yet"); } String javaTypeName = componentType.getName(); capitalizedComponentType = "" + Character.toUpperCase(javaTypeName.charAt(0)) + javaTypeName.substring(1); String javaArrayTypeName = "j" + javaTypeName + "Array"; writer.print(" "); writer.print(javaArrayTypeName); writer.print(" "); writer.print(arrayRes); writer.println(";"); } } } } /** Emits the user-defined C variable declarations from the TemporaryCVariableDeclarations directive in the .cfg file. */ protected void emitBodyUserVariableDeclarations(PrintWriter writer) { if (temporaryCVariableDeclarations != null) { for (Iterator iter = temporaryCVariableDeclarations.iterator(); iter.hasNext(); ) { String val = (String) iter.next(); writer.print(" "); writer.println(val); } } } /** * Code to init the variables that were declared in * emitBodyVariableDeclarations(), PRIOR TO calling the actual C * function. */ protected void emitBodyVariablePreCallSetup(PrintWriter writer, boolean emittingPrimitiveArrayCritical) { if (!emittingPrimitiveArrayCritical) { // Convert all Buffers to pointers first so we don't have to // call ReleasePrimitiveArrayCritical for any arrays if any // incoming buffers aren't direct if (binding.hasContainingType()) { emitPointerConversion(writer, binding, binding.getContainingType(), binding.getContainingCType(), JavaMethodBindingEmitter.javaThisArgumentName(), CMethodBindingEmitter.cThisArgumentName()); } for (int i = 0; i < binding.getNumArguments(); i++) { JavaType type = binding.getJavaArgumentType(i); if (type.isJNIEnv() || binding.isArgumentThisPointer(i)) { continue; } if (type.isNIOBuffer()) { emitPointerConversion(writer, binding, type, binding.getCArgumentType(i), binding.getArgumentName(i), pointerConversionArgumentName(i)); } } } // Convert all arrays to pointers, and get UTF-8 versions of jstring args for (int i = 0; i < binding.getNumArguments(); i++) { JavaType javaArgType = binding.getJavaArgumentType(i); if (javaArgType.isJNIEnv() || binding.isArgumentThisPointer(i)) { continue; } if (javaArgType.isArray()) { boolean needsDataCopy = javaArgTypeNeedsDataCopy(javaArgType); Class subArrayElementJavaType = javaArgType.getJavaClass().getComponentType(); // We only defer the emission of GetPrimitiveArrayCritical // calls that won't be matched up until after the function // we're calling if ((!needsDataCopy && !emittingPrimitiveArrayCritical) || (needsDataCopy && emittingPrimitiveArrayCritical)) { continue; } if (EMIT_NULL_CHECKS) { writer.print(" if ("); writer.print(binding.getArgumentName(i)); writer.println(" != NULL) {"); } Type cArgType = binding.getCArgumentType(i); String cArgTypeName = cArgType.getName(); String convName = pointerConversionArgumentName(i); if (!needsDataCopy) { writer.print(" "); writer.print(convName); writer.print(" = ("); if (javaArgType.isArray() && javaArgType.getJavaClass().getComponentType() == java.lang.String.class) { // java-side type is String[] cArgTypeName = "jstring *"; } writer.print(cArgTypeName); writer.print(") (*env)->GetPrimitiveArrayCritical(env, "); writer.print(binding.getArgumentName(i)); writer.println(", NULL);"); } else { // Handle the case where the array elements are of a type that needs a // data copy operation to convert from the java memory model to the C // memory model (e.g., int[][], String[], etc) // // FIXME: should factor out this whole block of code into a separate // method for clarity and maintenance purposes if (cArgType.toString().indexOf("const") == -1) { // FIXME: if the arg type is non-const, the sematics might be that // the function modifies the argument -- we don't yet support // this. // // Note: the check for "const" in the CVAttributes string isn't // truly checking the constness of the target types at both // pointer depths. However, it's a quick approximation, and quite // often C code doesn't get the constness right anyhow. throw new RuntimeException( "Cannot copy data for ptr-to-ptr arg type \"" + cArgType + "\": support for non-const ptr-to-ptr types not implemented."); } writer.println(); writer.println(" /* Copy contents of " + binding.getArgumentName(i) + " into " + convName + "_copy */"); // get length of array being copied String arrayLenName = "_tmpArrayLen"; writer.print(" "); writer.print(arrayLenName); writer.print(" = (*env)->GetArrayLength(env, "); writer.print(binding.getArgumentName(i)); writer.println(");"); // allocate an array to hold each element if (cArgType.pointerDepth() != 2) { throw new RuntimeException( "Could not copy data for type \"" + cArgType + "\"; copying only supported for types of the form " + "ptr-to-ptr-to-type."); } PointerType cArgPtrType = cArgType.asPointer(); if (cArgPtrType == null) { throw new RuntimeException( "Could not copy data for type \"" + cArgType + "\"; currently only pointer types supported."); } PointerType cArgElementType = cArgPtrType.getTargetType().asPointer(); emitMalloc( writer, convName+"_copy", cArgElementType.getName(), arrayLenName, "Could not allocate buffer for copying data in argument \\\""+binding.getArgumentName(i)+"\\\""); // process each element in the array writer.println(" for (_copyIndex = 0; _copyIndex < "+arrayLenName+"; ++_copyIndex) {"); // get each array element writer.println(" /* get each element of the array argument \"" + binding.getArgumentName(i) + "\" */"); String subArrayElementJNITypeString = jniType(subArrayElementJavaType); writer.print(" _tmpObj = ("); writer.print(subArrayElementJNITypeString); writer.print(") (*env)->GetObjectArrayElement(env, "); writer.print(binding.getArgumentName(i)); writer.println(", _copyIndex);"); if (subArrayElementJNITypeString == "jstring") { writer.print(" "); emitGetStringUTFChars(writer, "(jstring) _tmpObj", convName+"_copy[_copyIndex]"); } else if (isNIOBufferClass(subArrayElementJavaType)) { emitGetDirectBufferAddress(writer, "_tmpObj", cArgElementType.getName(), convName + "_copy[_copyIndex]"); } else { // Question: do we always need to copy the sub-arrays, or just // GetPrimitiveArrayCritical on each jobjectarray element and // assign it to the appropriate elements at pointer depth 1? // Probably depends on const-ness of the argument. // Malloc enough space to hold a copy of each sub-array writer.print(" "); emitMalloc( writer, convName+"_copy[_copyIndex]", cArgElementType.getTargetType().getName(), // assumes cArgPtrType is ptr-to-ptr-to-primitive !! "(*env)->GetArrayLength(env, _tmpObj)", "Could not allocate buffer during copying of data in argument \\\""+binding.getArgumentName(i)+"\\\""); // FIXME: copy the data (use matched Get/ReleasePrimitiveArrayCritical() calls) if (true) throw new RuntimeException( "Cannot yet handle type \"" + cArgType.getName() + "\"; need to add support for copying ptr-to-ptr-to-primitiveType subarrays"); } writer.println(" }"); writer.println(); } // end of data copy if (EMIT_NULL_CHECKS) { writer.println(" }"); } } else if (javaArgType.isString()) { if (emittingPrimitiveArrayCritical) { continue; } if (EMIT_NULL_CHECKS) { writer.print(" if ("); writer.print(binding.getArgumentName(i)); writer.println(" != NULL) {"); } emitGetStringUTFChars(writer, binding.getArgumentName(i), "_UTF8" + binding.getArgumentName(i)); if (EMIT_NULL_CHECKS) { writer.println(" }"); } } else if (javaArgType.isArrayOfCompoundTypeWrappers()) { // FIXME throw new RuntimeException("Outgoing arrays of StructAccessors not yet implemented"); } } } /** * Code to clean up any variables that were declared in * emitBodyVariableDeclarations(), AFTER calling the actual C function. */ protected void emitBodyVariablePostCallCleanup(PrintWriter writer, boolean emittingPrimitiveArrayCritical) { // Release primitive arrays and temporary UTF8 strings if necessary for (int i = 0; i < binding.getNumArguments(); i++) { JavaType javaArgType = binding.getJavaArgumentType(i); if (javaArgType.isJNIEnv() || binding.isArgumentThisPointer(i)) { continue; } if (javaArgType.isArray()) { boolean needsDataCopy = javaArgTypeNeedsDataCopy(javaArgType); Class subArrayElementJavaType = javaArgType.getJavaClass().getComponentType(); if ((!needsDataCopy && !emittingPrimitiveArrayCritical) || (needsDataCopy && emittingPrimitiveArrayCritical)) { continue; } if (EMIT_NULL_CHECKS) { writer.print(" if ("); writer.print(binding.getArgumentName(i)); writer.println(" != NULL) {"); } String convName = pointerConversionArgumentName(i); if (!needsDataCopy) { // Release array writer.print(" (*env)->ReleasePrimitiveArrayCritical(env, "); writer.print(binding.getArgumentName(i)); writer.print(", "); writer.print(convName); writer.println(", JNI_ABORT);"); } else { // clean up the case where the array elements are of a type that needed // a data copy operation to convert from the java memory model to the // C memory model (e.g., int[][], String[], etc) // // FIXME: should factor out this whole block of code into a separate // method for clarity and maintenance purposes Type cArgType = binding.getCArgumentType(i); String cArgTypeName = cArgType.getName(); if (cArgType.toString().indexOf("const") == -1) { // FIXME: handle any cleanup from treatment of non-const args, // assuming they were treated differently in // emitBodyVariablePreCallSetup() (see the similar section in that // method for details). throw new RuntimeException( "Cannot clean up copied data for ptr-to-ptr arg type \"" + cArgType + "\": support for cleaning up non-const ptr-to-ptr types not implemented."); } writer.println(" /* Clean up " + convName + "_copy */"); // Only need to perform cleanup for individual array // elements if they are not direct buffers if (!isNIOBufferClass(subArrayElementJavaType)) { // Re-fetch length of array that was copied String arrayLenName = "_tmpArrayLen"; writer.print(" "); writer.print(arrayLenName); writer.print(" = (*env)->GetArrayLength(env, "); writer.print(binding.getArgumentName(i)); writer.println(");"); // free each element PointerType cArgPtrType = cArgType.asPointer(); if (cArgPtrType == null) { throw new RuntimeException( "Could not copy data for type \"" + cArgType + "\"; currently only pointer types supported."); } PointerType cArgElementType = cArgPtrType.getTargetType().asPointer(); // process each element in the array writer.println(" for (_copyIndex = 0; _copyIndex < " + arrayLenName +"; ++_copyIndex) {"); // get each array element writer.println(" /* free each element of " +convName +"_copy */"); String subArrayElementJNITypeString = jniType(subArrayElementJavaType); writer.print(" _tmpObj = ("); writer.print(subArrayElementJNITypeString); writer.print(") (*env)->GetObjectArrayElement(env, "); writer.print(binding.getArgumentName(i)); writer.println(", _copyIndex);"); if (subArrayElementJNITypeString == "jstring") { writer.print(" (*env)->ReleaseStringUTFChars(env, "); writer.print("(jstring) _tmpObj"); writer.print(", "); writer.print(convName+"_copy[_copyIndex]"); writer.println(");"); } else { if (true) throw new RuntimeException( "Cannot yet handle type \"" + cArgType.getName() + "\"; need to add support for cleaning up copied ptr-to-ptr-to-primitiveType subarrays"); } writer.println(" }"); } // free the main array writer.print(" free((void*) "); writer.print(convName+"_copy"); writer.println(");"); } // end of cleaning up copied data if (EMIT_NULL_CHECKS) { writer.println(" }"); } } else if (javaArgType.isString()) { if (emittingPrimitiveArrayCritical) { continue; } if (EMIT_NULL_CHECKS) { writer.print(" if ("); writer.print(binding.getArgumentName(i)); writer.println(" != NULL) {"); } writer.print(" (*env)->ReleaseStringUTFChars(env, "); writer.print(binding.getArgumentName(i)); writer.print(", _UTF8"); writer.print(binding.getArgumentName(i)); writer.println(");"); if (EMIT_NULL_CHECKS) { writer.println(" }"); } } else if (javaArgType.isArrayOfCompoundTypeWrappers()) { // FIXME throw new RuntimeException("Outgoing arrays of StructAccessors not yet implemented"); } } } protected void emitBodyCallCFunction(PrintWriter writer) { // Make the call to the actual C function writer.print(" "); // WARNING: this code assumes that the return type has already been // typedef-resolved. Type cReturnType = binding.getCReturnType(); if (!cReturnType.isVoid()) { writer.print("_res = "); } if (binding.hasContainingType()) { // Call through function pointer writer.print(CMethodBindingEmitter.cThisArgumentName() + "->"); } writer.print(binding.getCSymbol().getName()); writer.print("("); for (int i = 0; i < binding.getNumArguments(); i++) { if (i != 0) { writer.print(", "); } JavaType javaArgType = binding.getJavaArgumentType(i); // Handle case where only param is void. if (javaArgType.isVoid()) { // Make sure this is the only param to the method; if it isn't, // there's something wrong with our parsing of the headers. assert(binding.getNumArguments() == 1); continue; } if (javaArgType.isJNIEnv()) { writer.print("env"); } else if (binding.isArgumentThisPointer(i)) { writer.print(CMethodBindingEmitter.cThisArgumentName()); } else { writer.print("("); Type cArgType = binding.getCSymbol().getArgumentType(i); writer.print(cArgType.getName()); writer.print(") "); if (binding.getCArgumentType(i).isPointer() && binding.getJavaArgumentType(i).isPrimitive()) { writer.print("(intptr_t) "); } if (javaArgType.isArray() || javaArgType.isNIOBuffer()) { writer.print(pointerConversionArgumentName(i)); if (javaArgTypeNeedsDataCopy(javaArgType)) { writer.print("_copy"); } } else { if (javaArgType.isString()) { writer.print("_UTF8"); } writer.print(binding.getArgumentName(i)); } } } writer.println(");"); } /** Emits the user-defined C variable assignments from the TemporaryCVariableAssignments directive in the .cfg file. */ protected void emitBodyUserVariableAssignments(PrintWriter writer) { if (temporaryCVariableAssignments != null) { for (Iterator iter = temporaryCVariableAssignments.iterator(); iter.hasNext(); ) { String val = (String) iter.next(); writer.print(" "); writer.println(val); } } } // FIXME: refactor this so that subclasses (in particular, // net.java.games.gluegen.opengl.CGLPAWrapperEmitter) don't have to copy the whole // method protected void emitBodyReturnResult(PrintWriter writer) { // WARNING: this code assumes that the return type has already been // typedef-resolved. Type cReturnType = binding.getCReturnType(); // Return result if necessary if (!cReturnType.isVoid()) { JavaType javaReturnType = binding.getJavaReturnType(); if (javaReturnType.isPrimitive()) { writer.print(" return "); if (cReturnType.isPointer()) { // Pointer being converted to int or long: cast this result // (through intptr_t to avoid compiler warnings with gcc) writer.print("(" + javaReturnType.jniTypeName() + ") (intptr_t) "); } writer.println("_res;"); } else if (javaReturnType.isNIOBuffer()) { writer.println(" if (_res == NULL) return NULL;"); writer.print(" return (*env)->NewDirectByteBuffer(env, _res, "); // See whether capacity has been specified if (returnValueCapacityExpression != null) { String[] argumentNames = new String[binding.getNumArguments()]; for (int i = 0; i < binding.getNumArguments(); i++) { argumentNames[i] = binding.getArgumentName(i); } writer.print( returnValueCapacityExpression.format(argumentNames)); } else { int sz = 0; if (cReturnType.isPointer() && cReturnType.asPointer().getTargetType().isCompound()) { sz = cReturnType.asPointer().getTargetType().getSize(); if (sz == -1) { throw new InternalError( "Error emitting code for compound return type "+ "for function \"" + binding + "\": " + "Structs to be emitted should have been laid out by this point " + "(type " + cReturnType.asPointer().getTargetType().getName() + " / " + cReturnType.asPointer().getTargetType() + " was not)" ); } } else { sz = cReturnType.getSize(); } writer.print(sz); System.err.println( "WARNING: No capacity specified for java.nio.Buffer return " + "value for function \"" + binding + "\";" + " assuming size of equivalent C return type (" + sz + " bytes): " + binding); } writer.println(");"); } else if (javaReturnType.isString()) { writer.print(" if (_res == NULL) return NULL;"); writer.println(" return (*env)->NewStringUTF(env, _res);"); } else if (javaReturnType.isArray()) { if (javaReturnType.isNIOByteBufferArray()) { writer.println(" if (_res == NULL) return NULL;"); if (returnValueLengthExpression == null) { throw new RuntimeException("Error while generating C code: no length specified for array returned from function " + binding); } String[] argumentNames = new String[binding.getNumArguments()]; for (int i = 0; i < binding.getNumArguments(); i++) { argumentNames[i] = binding.getArgumentName(i); } writer.println(" " + arrayResLength + " = " + returnValueLengthExpression.format(argumentNames) + ";"); writer.println(" " + arrayRes + " = (*env)->NewObjectArray(env, " + arrayResLength + ", (*env)->FindClass(env, \"java/nio/ByteBuffer\"), NULL);"); writer.println(" for (" + arrayIdx + " = 0; " + arrayIdx + " < " + arrayResLength + "; " + arrayIdx + "++) {"); Type retType = binding.getCSymbol().getReturnType(); Type baseType; if (retType.isPointer()) { baseType = retType.asPointer().getTargetType().asPointer().getTargetType(); } else { baseType = retType.asArray().getElementType().asPointer().getTargetType(); } int sz = baseType.getSize(); if (sz < 0) sz = 0; writer.println(" (*env)->SetObjectArrayElement(env, " + arrayRes + ", " + arrayIdx + ", (*env)->NewDirectByteBuffer(env, _res[" + arrayIdx + "], " + sz + "));"); writer.println(" }"); writer.println(" return " + arrayRes + ";"); } else { // FIXME: must have user provide length of array in .cfg file // by providing a constant value, input parameter, or // expression which computes the array size (already present // as ReturnValueCapacity, not yet implemented / tested here) throw new RuntimeException( "Could not emit native code for function \"" + binding + "\": array return values for non-char types not implemented yet"); // FIXME: This is approximately what will be required here // //writer.print(" "); //writer.print(arrayRes); //writer.print(" = (*env)->New"); //writer.print(capitalizedComponentType); //writer.print("Array(env, "); //writer.print(arrayResLength); //writer.println(");"); //writer.print(" (*env)->Set"); //writer.print(capitalizedComponentType); //writer.print("ArrayRegion(env, "); //writer.print(arrayRes); //writer.print(", 0, "); //writer.print(arrayResLength); //writer.println(", _res);"); //writer.print(" return "); //writer.print(arrayRes); //writer.println(";"); } } } } protected static String cThisArgumentName() { return "this0"; } // Mangle a class, package or function name protected String jniMangle(String name) { return name.replaceAll("_", "_1").replace('.', '_'); } protected String jniMangle(MethodBinding binding) { StringBuffer buf = new StringBuffer(); buf.append(jniMangle(binding.getName())); buf.append("__"); for (int i = 0; i < binding.getNumArguments(); i++) { JavaType type = binding.getJavaArgumentType(i); Class c = type.getJavaClass(); if (c != null) { jniMangle(c, buf); } else { // FIXME: add support for char* -> String conversion throw new RuntimeException("Unknown kind of JavaType: name="+type.getName()); } } return buf.toString(); } protected void jniMangle(Class c, StringBuffer res) { if (c.isArray()) { res.append("_3"); jniMangle(c.getComponentType(), res); } else if (c.isPrimitive()) { if (c == Boolean.TYPE) res.append("Z"); else if (c == Byte.TYPE) res.append("B"); else if (c == Character.TYPE) res.append("C"); else if (c == Short.TYPE) res.append("S"); else if (c == Integer.TYPE) res.append("I"); else if (c == Long.TYPE) res.append("J"); else if (c == Float.TYPE) res.append("F"); else if (c == Double.TYPE) res.append("D"); else throw new InternalError("Illegal primitive type"); } else { res.append("L"); res.append(c.getName().replace('.', '_')); res.append("_2"); } } private String jniType(Class javaType) { if (javaType.isPrimitive()) { return "j" + javaType.getName(); } else if (javaType == java.lang.String.class) { return "jstring"; } else if (isNIOBufferClass(javaType)) { return "jobject"; } else { throw new RuntimeException( "Could not determine JNI type for Java class \"" + javaType.getName() + "\"; was not String, primitive or direct buffer"); } } private void emitOutOfMemoryCheck(PrintWriter writer, String varName, String errorMessage) { writer.print(" if ("); writer.print(varName); writer.println(" == NULL) {"); writer.println(" (*env)->ThrowNew(env, (*env)->FindClass(env, \"java/lang/OutOfMemoryError\"),"); writer.print(" \"" + errorMessage); writer.print(" in native dispatcher for \\\""); writer.print(binding.getName()); writer.println("\\\"\");"); writer.print(" return"); if (!binding.getJavaReturnType().isVoid()) { writer.print(" 0"); } writer.println(";"); writer.println(" }"); } private void emitMalloc(PrintWriter writer, String targetVarName, String elementTypeString, String numElementsExpression, String mallocFailureErrorString) { writer.print(" "); writer.print(targetVarName); writer.print(" = ("); writer.print(elementTypeString); writer.print(" *) malloc("); writer.print(numElementsExpression); writer.print(" * sizeof("); writer.print(elementTypeString); writer.println("));"); // Catch memory allocation failure if (EMIT_NULL_CHECKS) { emitOutOfMemoryCheck( writer, targetVarName, mallocFailureErrorString); } } private void emitGetStringUTFChars(PrintWriter writer, String sourceVarName, String receivingVarName) { writer.print(" if ("); writer.print(sourceVarName); writer.println(" != NULL) {"); writer.print(" "); writer.print(receivingVarName); writer.print(" = (*env)->GetStringUTFChars(env, "); writer.print(sourceVarName); writer.println(", (jboolean*)NULL);"); // Catch memory allocation failure in the event that the VM didn't pin // the String and failed to allocate a copy if (EMIT_NULL_CHECKS) { emitOutOfMemoryCheck( writer, receivingVarName, "Failed to get UTF-8 chars for argument \\\""+sourceVarName+"\\\""); } writer.println(" } else {"); writer.print(" "); writer.print(receivingVarName); writer.println(" = NULL;"); writer.println(" }"); } private void emitGetDirectBufferAddress(PrintWriter writer, String sourceVarName, String receivingVarTypeString, String receivingVarName) { if (EMIT_NULL_CHECKS) { writer.print(" if ("); writer.print(sourceVarName); writer.println(" != NULL) {"); } writer.print(" "); writer.print(receivingVarName); writer.print(" = ("); writer.print(receivingVarTypeString); writer.print(") (*env)->GetDirectBufferAddress(env, "); writer.print(sourceVarName); writer.println(");"); if (EMIT_NULL_CHECKS) { writer.println(" } else {"); writer.print(" "); writer.print(receivingVarName); writer.println(" = NULL;"); writer.println(" }"); } } // Note: if the data in the Type needs to be converted from the Java memory // model to the C memory model prior to calling any C-side functions, then // an extra variable named XXX_copy (where XXX is the value of the // cVariableName argument) will be emitted and TRUE will be returned. private boolean emitPointerDeclaration(PrintWriter writer, JavaType javaType, Type cType, String cVariableName, String javaArgumentName) { String ptrTypeString = null; boolean needsDataCopy = false; // Emit declaration for the pointer variable. // // Note that we don't need to obey const/volatile for outgoing arguments // if (javaType.isNIOBuffer()) { ptrTypeString = cType.getName(); } else if (javaType.isArray()) { needsDataCopy = javaArgTypeNeedsDataCopy(javaType); // It's an array; get the type of the elements in the array Class elementType = javaType.getJavaClass().getComponentType(); if (elementType.isPrimitive()) { ptrTypeString = cType.getName(); } else if (elementType == java.lang.String.class) { ptrTypeString = "jstring"; } else if (elementType.isArray()) { Class subElementType = elementType.getComponentType(); if (subElementType.isPrimitive()) { // type is pointer to pointer to primitive ptrTypeString = cType.getName(); } else { // type is pointer to pointer of some type we don't support (maybe // it's an array of pointers to structs?) throw new RuntimeException("Unsupported pointer type: \"" + cType.getName() + "\""); } } else if (isNIOBufferClass(elementType)) { // type is an array of direct buffers of some sort ptrTypeString = cType.getName(); } else { // Type is pointer to something we can't/don't handle throw new RuntimeException("Unsupported pointer type: \"" + cType.getName() + "\""); } } else if (javaType.isArrayOfCompoundTypeWrappers()) { // FIXME throw new RuntimeException("Outgoing arrays of StructAccessors not yet implemented"); } else { ptrTypeString = cType.getName(); } if (!needsDataCopy) { // declare the pointer variable writer.print(" "); writer.print(ptrTypeString); writer.print(" "); writer.print(cVariableName); writer.println(" = NULL;"); } else { // Declare a variable to hold a copy of the argument data in which the // incoming data has been properly laid out in memory to match the C // memory model //writer.print(" const "); Class elementType = javaType.getJavaClass().getComponentType(); if (javaType.isArray() && javaType.getJavaClass().getComponentType() == java.lang.String.class) { writer.print(" const char **"); } else { writer.print(ptrTypeString); } writer.print(" "); writer.print(cVariableName); writer.print("_copy = NULL; /* copy of data in "); writer.print(javaArgumentName); writer.println(", laid out according to C memory model */"); } return needsDataCopy; } private void emitPointerConversion(PrintWriter writer, MethodBinding binding, JavaType type, Type cType, String incomingArgumentName, String cVariableName) { emitGetDirectBufferAddress(writer, incomingArgumentName, cType.getName(), cVariableName); /* if (EMIT_NULL_CHECKS) { writer.print(" if ("); writer.print(incomingArgumentName); writer.println(" != NULL) {"); } writer.print(" "); writer.print(cVariableName); writer.print(" = ("); writer.print(cType.getName()); writer.print(") (*env)->GetDirectBufferAddress(env, "); writer.print(incomingArgumentName); writer.println(");"); if (EMIT_NULL_CHECKS) { writer.println(" }"); } */ } protected String pointerConversionArgumentName(int i) { return "_ptr" + i; } /** * Class that emits a generic comment for CMethodBindingEmitters; the comment * includes the C signature of the native method that is being bound by the * emitter java method. */ protected static class DefaultCommentEmitter implements CommentEmitter { public void emit(FunctionEmitter emitter, PrintWriter writer) { emitBeginning((CMethodBindingEmitter)emitter, writer); emitEnding((CMethodBindingEmitter)emitter, writer); } protected void emitBeginning(CMethodBindingEmitter emitter, PrintWriter writer) { writer.println(" Java->C glue code:"); writer.print(" * Java package: "); writer.print(emitter.getJavaPackageName()); writer.print("."); writer.println(emitter.getJavaClassName()); writer.print(" * Java method: "); MethodBinding binding = emitter.getBinding(); writer.println(binding); writer.println(" * C function: " + binding.getCSymbol()); } protected void emitEnding(CMethodBindingEmitter emitter, PrintWriter writer) { } } protected boolean javaArgTypeNeedsDataCopy(JavaType javaArgType) { if (javaArgType.isArray()) { Class subArrayElementJavaType = javaArgType.getJavaClass().getComponentType(); return (subArrayElementJavaType.isArray() || subArrayElementJavaType == java.lang.String.class || isNIOBufferClass(subArrayElementJavaType)); } return false; } protected static boolean isNIOBufferClass(Class c) { return java.nio.Buffer.class.isAssignableFrom(c); } }