NAG Technical Report 3/2000

Calling the NAG C Library from Java


Start of report   Skip to Example 1   Skip to Example 2

4.3. Example 3

A numerical integration routine, function d01ajc

Here we show how to call a NAG C Library function which takes a user-supplied evaluation function as an argument: the function d01ajc. We also show how to pass data back from C to Java properties.

Contents

  1. Function prototype from the NAG C Library Manual
  2. Declaring the native function in our Java program
  3. Compiling the Java program
  4. Generating a header file for use by C
  5. Implementing the native function in C code
  6. Building the shareable library or DLL
  7. Running the program
  8. Quick summary of how to build the linear equation solver example

  1. Function prototype from the NAG C Library Manual
  2. According to the C Library Manual, the prototype for function d01ajc looks like this:

      #include <nag.h>
      #include <nagd01.h>
    
      void d01ajc(double (*f)(double x), double a, double b,
                  double epsabs, double epsrel, Integer max_num_subint,
                  double *result, double *abserr, Nag_QuadProgress *qp,
                  NagError *fail);
    
    The function d01ajc is designed to numerically integrate f(x), where f(x) is a function supplied by the user. The limits of integration are the finite interval [a,b].

    The function f(x) to be integrated is supplied as the first argument to d01ajc, and is declared as

      double (*f)(double x)
    
    i.e. a function of one double argument which returns a value of type double.

    The arguments epsabs and epsrel are used to control the accuracy to which the integral is computed. Argument max_num_subint is the limit on the number of subintervals that d01ajc may split the interval [a,b] into during computation. The integral result is returned via argument result, and an estimate of the absolute error in the computed result is returned in abserr.

    Argument qp, of type Nag_QuadProgress, is used to return further information about the computation, including the actual number of sub-intervals used and the number of calls made to f(x).

  3. Declaring the native function in our Java program
  4. As with Example 1 and Example 2, we will not attempt to pass the contents of the NagError structure back to Java. In our Java program, we will declare the function like this:

      // Declaration of the Native (C) function
      private native int d01ajc(String funName,
                                double a, double b,
                                double epsabs, double epsrel,
                                int max_num_subint);
    
    i.e. a method with return type int. Since we are not bothering to use the fail argument, we will use the int return value to send back any error code.

    Note that we cannot pass a function argument directly from Java to C, and so we here we just pass the name of a function via the String argument funName. Later on we will see how we can use this name to get hold of the actual function f(x) written in Java.

    Note also that our Java declaration does not contain any of the output arguments of d01ajc. Instead, we will need to find another way to pass the information that the output arguments contain back to Java.

  5. Compiling the Java program
  6. Here is the complete source code of our Java program Quadrature.java.
    public class Quadrature
    {
    
      // Declaration of the Native (C) function
      private native int d01ajc(String funName,
                                double a, double b,
                                double epsabs, double epsrel,
                                int max_num_subint);
    
      // Member variables to hold results returned by d01ajc
      double result, abserr;
      int nFun, nSubInt;
    
      static
        {
          // The runtime system executes a class's static
          // initializer when it loads the class.
          System.loadLibrary("nagCJavaInterface");
        }
    
      // An example function to be integrated.
      private double myFunction(double x)
        {
          double ret;
          ret = x * x * x;
          return ret;
        }
    
      // Another example function to be integrated (this one is from
      // the example program for d01ajc in the NAG C Library manual).
      private double myFunction2(double x)
        {
          double ret;
          ret = x * Math.sin(x * 30.0) /
                   Math.sqrt(1.0 - x * x / (Math.PI * Math.PI * 4.0));
          return ret;
        }
    
      // The main program
      public static void main(String[] args)
        {
          double a, b;
    
          // Create an object of class Quadrature
          Quadrature quad = new Quadrature();
    
          System.out.println();
          System.out.println("Calls of NAG quadrature routine d01ajc");
          System.out.println();
    
          // Integrate the first example function
          a = 0.0;
          b = 1.0;
          System.out.println("Integral of x*x*x");
          quad.Integrate("myFunction", a, b);
    
          // Integrate the second example function
          a = 0.0;
          b = Math.PI * 2.0;
          System.out.println("Integral of x*sin(30*x) / sqrt(1-x*x/(4*PI*PI))");
          quad.Integrate("myFunction2", a, b);
    
        }
    
      // A routine to integrate by calling the native (C) function
      private void Integrate(String functionName, double a, double b)
        {
          double epsabs, epsrel;
          int max_num_subint, resCode;
    
          epsabs = 0.0;
          epsrel = 0.0001;
          max_num_subint = 200;
    
          resCode = d01ajc(functionName, a, b, epsabs, epsrel, max_num_subint);
    
          // Check the result code returned by the C function.
          if (resCode == -1)
            System.out.println("Cannot load library nagc.dll / libnagc.so");
          else if (resCode == -2)
            System.out.println("Cannot find function d01ajc in nagc.dll / libnagc.so");
          else if (resCode == -3)
            System.out.println("Cannot find method " + functionName +
                               " with signature (D)D");
          else if (resCode > 0)
            {
              System.out.print("NAG function d01ajc returned non-zero exit code: ");
              System.out.println(resCode);
            }
          else
            {
              // resCode = 0 - we got some results.
              System.out.print("Lower limit of integration = ");
              System.out.println(a);
              System.out.print("Upper limit of integration = ");
              System.out.println(b);
              System.out.print("Requested relative error = ");
              System.out.println(epsrel);
              System.out.print("Integral value = ");
              System.out.println(result);
              System.out.print("Estimate of absolute error = ");
              System.out.println(abserr);
              System.out.print("Number of function evaluations = ");
              System.out.println(nFun);
              System.out.print("Number of subintervals used = ");
              System.out.println(nSubInt);
            }
          System.out.println();
        }
    }
    
    Some points to note about this program:

    We can compile our Java program with the following command:

      % javac Quadrature.java
    

  7. Generating a header file for use by C
  8. Having compiled Quadrature.java, we can use javah to create a C header file:

      % javah -jni Quadrature
    
    The generated header file, Quadrature.h, contains this function prototype:
      JNIEXPORT jint JNICALL Java_Quadrature_d01ajc
        (JNIEnv *, jobject, jstring, jdouble, jdouble, jdouble, jdouble, jint);
    

  9. Implementing the native function in C code
  10. Now that we have created the header file Quadrature.h, we can write our C code implementation of Java_Quadrature_d01ajc.

    5.1 Source code for the C interface library

    Here is the C source code, from file QuadratureImp.c:

    #include <jni.h>         /* Java Native Interface headers */
    #include "Quadrature.h"  /* Auto-generated header created by javah -jni */
    #include <stdio.h>
    #include <math.h>
    
    #include <nag.h>      /* NAG C Library headers */
    #include <nagd01.h>
    
    /* Nasty global variables; they are global because they are
       required by the function evaluator intFun, but come from
       Java via the native interface routine Java_Quadrature_d01ajc. */
    JNIEnv *globalJavaEnv;
    jobject globalJavaObject;
    jmethodID globalMid;
    
    /* This is the interface to the Java function which is to
       be integrated. */
    double intFun(double x)
    {
      jdouble res;
    
      /* Here's where we call back to the user's function in the Java code */
      res = (*globalJavaEnv)->CallDoubleMethod(globalJavaEnv, globalJavaObject,
                                               globalMid, (jdouble)x);
      return (double)res;
    }
    
    
    /* Our C definition of the function d01ajc declared in Quadrature.java.
       The return value is an error code.
       Other results are returned via direct JNI access to Java object
       member variables. */
    JNIEXPORT jint JNICALL
      Java_Quadrature_d01ajc(JNIEnv *env, jobject obj,
                             jstring funName,
                             jdouble a, jdouble b,
                             jdouble epsabs, jdouble epsrel,
                             jint max_num_subint)
    {
      static NagError fail;
      Nag_QuadProgress qp;
      double result, abserr;
      jclass cls;
      const char *functionName;
      jfieldID fid;
      int retVal;
    
      fail.print = FALSE;
    
      /* Copy the Java env pointers to global space
         so that intFun can access them. */
      globalJavaEnv = env;
      globalJavaObject = obj;
    
      /* Get hold of the name of the user's Java evaluation function. */
      functionName = (*env)->GetStringUTFChars(env, funName, 0);
    
      /* Now we have the Java evaluation function name we
         can use it to get hold of a handle (method ID) to the function.
         Once more, the method ID is stored globally so that intFun
         can use it. Note that the Java function signature must be
         "(D)D" (i.e. function with double argument, returning double). */
      cls = (*env)->GetObjectClass(env, obj);
      globalMid = (*env)->GetMethodID(env, cls, functionName, "(D)D");
    
      /* Free up the Java string argument so we don't leak memory. */
      (*env)->ReleaseStringUTFChars(env, funName, functionName);
    
      if (globalMid == 0)
        /* Cannot find method "functionName" with signature (D)D */
        return -1;
      else
        {
          /* Now call the function we're interested in from the NAG C Library.
             intFun is the function that we want to integrate. */
          d01ajc(intFun, (double)a, (double)b,
                 (double)epsabs, (double)epsrel,
                 (Integer)max_num_subint,
                 &result, &abserr, &qp, &fail);
    
          if (fail.code == 0)
            {
              /* Put the results back to Java. */
              /* Get the ID of the Java Quadrature class member variable
                 "result" (which is of type double, hence the "D" signature). */
              fid = (*env)->GetFieldID(env, cls, "result", "D");
              /* Set the result value via the ID */
              (*env)->SetDoubleField(env, obj, fid, result);
              /* Repeat for other results */
              fid = (*env)->GetFieldID(env, cls, "abserr", "D");
              (*env)->SetDoubleField(env, obj, fid, abserr);
              fid = (*env)->GetFieldID(env, cls, "nFun", "I");
              (*env)->SetIntField(env, obj, fid, qp.fun_count);
              fid = (*env)->GetFieldID(env, cls, "nSubInt", "I");
              (*env)->SetIntField(env, obj, fid, qp.num_subint);
            }
        }
    
      /* Return any fail code that the nagc.dll function d01ajc returned. */
      return fail.code;
    }
    

    5.2 Description of the C code

    As before, our C source file must include the appropriate NAG C Library header files. The function named Java_Quadrature_d01ajc is our C implementation of the Java-declared method d01ajc.

    We cannot pass the Java method which evaluates f(x) directly to the NAG C Library function d01ajc, so we need to wrap it in a C function. This C function we name intFun:

      double intFun(double x)
    
    and it has argument type and return type required by the NAG Library function. Inside intFun we do nothing but call the Java method to evaluate the function. The trick is in knowing how to make this call to Java.

    We do this using the JNI function CallDoubleMethod, which is declared in jni.h (there are similar functions named CallVoidMethod, CallIntMethod and others, for methods with different return types).

    CallDoubleMethod needs several arguments, including the JNIEnv pointer argument env and the Java object argument, both of which were passed to Java_Quadrature_d01ajc. It also needs an argument named methodID, the method id of the Java method to be called. All three of these arguments are known or can be obtained by our function Java_Quadrature_d01ajc, but are not directly known by our function intFun. Instead, we need to give these arguments to intFun via global variables, which are declared like this in our C source code:

      JNIEnv *globalJavaEnv;
      jobject globalJavaObject;
      jmethodID globalMid;
    
    The three variables are global so that they can be accessed both by Java_Quadrature_d01ajc and by intFun.

    Besides the three arguments mentioned above, intFun must also pass to CallDoubleMethod the actual arguments that the Java method needs to evaluate our quadrature function f(x) (and that is the function we are really interested in!). CallDoubleMethod can accept any number of these arguments, but in this case there is only one argument, x.

    Note that instead of calling the Java method from intFun to evaluate the function f(x), we could have written the evaluation code in C. Then there would have been no need to use CallDoubleMethod and the other routines associated with it. There is an advantage to the method we used: once the interface library has been built, we never need to rebuild it even if our evaluation function changes – we only need to supply a different Java evaluation function.

    5.3 Finding the Java method which evaluates f(x)

    Our function Java_Quadrature_d01ajc first copies its arguments env and obj to global variables globalJavaEnv and globalJavaObject.

    Next, we take the name of the Java method passed as jstring argument funName and convert it into a method id. We use JNI function GetStringUTFChars to convert the jstring into a C char pointer named functionName because the jstring cannot be accessed safely directly. Then the JNI functions GetObjectClass and GetMethodID are used to get hold of the method ID of the Java evaluation function:

      functionName = (*env)->GetStringUTFChars(env, funName, 0);
        ...
      cls = (*env)->GetObjectClass(env, obj);
      globalMid = (*env)->GetMethodID(env, cls, functionName, "(D)D");
    
    (recall that in our example program the evaluation function will be the method named "myFunction" or "myFunction2"). Notice in particular the arguments to GetMethodID. The second argument, of type jclass, is the class containing the method; the third argument is the name of the method, and the fourth argument is the signature of the method. In this case, the signature "(D)D" means a method with one double argument and which returns a value of type double.

    Now that we have obtained the method ID to be used by intFun, we no longer need the C string functionName, so we free it via a call to the JNI function ReleaseStringUTFChars to avoid memory leaks.

    5.4 Run-time scheme

    At this point we have everything we need to call the NAG C Library function d01ajc. This is a diagram of what happens at run time:


    Java, calls C interface, calls NAG C Library, calls C interface, calls Java

    5.5 Returning results to Java

    After return from the NAG Library, we want to return the results to Java. Once again, we must call JNI functions to do it. Recall that our Java class contained properties (variables) named result, abserr, nFun and nSubInt. The JNI function GetFieldID, given the name and signature of one of these variables, will return its field ID which we can pass to another JNI function to set its value. For example, the function SetDoubleField sets the value of a double property given its field ID. The lines

      fid = (*env)->GetFieldID(env, cls, "result", "D");
      /* Set the result value via the ID */
      (*env)->SetDoubleField(env, obj, fid, result);
    
    get the field ID of property result, and set its value to that of the variable contained in the C code which is also named result.
    Similarly, the lines
      fid = (*env)->GetFieldID(env, cls, "nFun", "I");
      (*env)->SetIntField(env, obj, fid, qp.fun_count);
    
    get the field ID of int property nFun, with signature "I", and set its value to qp.fun_count. (qp is a structure of type Nag_QuadProgress, and component fun_count is the number of calls made to the evaluation function).

  11. Building the shareable library or DLL
  12. This step is operating-system dependent.

    The compiler flags used were described in Section 7 of Example 1.

  13. Running the program
  14. Assuming that all has gone well, we can run the program using the command

      % java Quadrature
    
    The expected output looks like this:

        Calls of NAG quadrature routine d01ajc
    
        Integral of x*x*x
        Lower limit of integration = 0.0
        Upper limit of integration = 1.0
        Requested relative error = 1.0E-4
        Integral value = 0.25
        Estimate of absolute error = 1.387778780781446E-15
        Number of function evaluations = 21
        Number of subintervals used = 1
    
        Integral of x*sin(30*x) / sqrt(1-x*x/(4*PI*PI))
        Lower limit of integration = 0.0
        Upper limit of integration = 6.283185307179586
        Requested relative error = 1.0E-4
        Integral value = -2.543259618925085
        Estimate of absolute error = 1.2751911290909135E-5
        Number of function evaluations = 777
        Number of subintervals used = 19
    

    (If you get an error message saying that a library cannot be located, see the tip given in Example 1).

  15. Quick summary of how to build the linear equation solver example
  16. Given the two source files Quadrature.java and QuadratureImp.c, issue the following commands:

Start of report   Skip to Example 1   Skip to Example 2
Copyright 2000 Numerical Algorithms Group
This page last modified Wednesday 10 May 2000 10:05:17
[NP3489]