Invoking JavaVM from C or C++ for manipulating Java object


はじめに

ここではC++やC言語を使ったプログラムからJavaのVMを走らせることによって Javaのオブジェクトを扱う方法を検討します。CやC++などの既存のプログラムから Javaの分散オブジェクトを扱うことでより簡単に分散Javaの恩恵に与れます。

Invoking JavaVM for calling HelloWorld Java object

Makefile

このMakefileは単にHello Worldというメッセージを標準出力に出すだけの プログラムHelloWorld.javaをC言語で書かれたプログラムcreateJVM.c から呼ぶためのものです。C言語とJavaを結びつけるためにはJava Native Interface (JNI)が必要です。また、LIBで定義されているようなJavaのライブラリも Cプログラムのリンクには必要になります。
#JDK=/usr/asis.local/i386_redhat60/usr.local/libexec/jdk/1.2.2/
JDK=/usr/jdk1.2

CC = gcc
JAVAC = javac

JNI = -I$(JDK)/include/linux -I$(JDK)/include

#LIB =  -L$(JDK)/jre/lib/i386/classic  -L$(JDK)/jre/lib/i386/native_threads -ljvm -lhpi  -L/usr/lib/ -lpthread -lutil
LIB =  -L$(JDK)/jre/lib/i386/classic  -L$(JDK)/jre/lib/i386/green_threads -ljvm -lhpi

all: createJVM_Hello HelloWorld.class

createJVM.o: createJVM.c
        $(CC) $(JNI) -c  createJVM.c 
createJVM_Hello: createJVM.o
        $(CC) -o createJVM_Hello createJVM.o $(LIB)

HelloWorld.class: HelloWorld.java
        $(JAVAC) HelloWorld.java
clean:
        rm -f createJVM_Hello createJVM.o

createJVM.c

さて、下記のプログラムのミソは
    res = JNI_CreateJavaVM(&jvm,&env,&vm_args);
    (*env)->CallStaticVoidMethod(env, cls, mid, args);
です。前者はJava Virtual Machineを起動しています。後者は HelloWorld.javaをコンパイルして得られたHelloWorld.classを 呼び出して、Javaのプログラムを実行しています。 その他はJavaプログラム実行に必要な準備、たとえばJava classを 見付けたり、引数である文字列をJavaのオブジェクトに変えたりなど です。
#include <jni.h>

#define PATH_SEPARATOR ':'
#define USER_CLASSPATH "." /* where Prog.class is */

main(int argc, char** argv) {
    JNIEnv *env;
    JavaVM *jvm;
    JDK1_1InitArgs vm_args;
    jint res;
    jclass cls;
    jmethodID mid;
    jstring jstr;
    jobjectArray args;
    char classpath[1024];

    /* IMPORTANT: specify vm_args version # if you use JDK1.1.2 and beyond */
    vm_args.version = 0x00010001;

    JNI_GetDefaultJavaVMInitArgs(&vm_args);

    /* Append USER_CLASSPATH to the end of default system class path */
    sprintf(classpath, "%s%c%s",
            vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
    vm_args.classpath = classpath;

    /* Create the Java VM */
    res = JNI_CreateJavaVM(&jvm,&env,&vm_args);
    if (res < 0) {
        fprintf(stderr, "Can't create Java VM\n");
        exit(1);
    }

    cls = (*env)->FindClass(env, "HelloWorld");
    if (cls == 0) {
        fprintf(stderr, "Can't find HelloWorld class\n");
        exit(1);
    }
 
    mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V");
    if (mid == 0) {
        fprintf(stderr, "Can't find HelloWorld.main\n");
        exit(1);
    }

    jstr = (*env)->NewStringUTF(env, (argc==2)? argv[1] : " really from C!");
    if (jstr == 0) {
        fprintf(stderr, "Out of memory\n");
        exit(1);
    }
    args = (*env)->NewObjectArray(env, 1, 
                        (*env)->FindClass(env, "java/lang/String"), jstr);
    if (args == 0) {
        fprintf(stderr, "Out of memory\n");
        exit(1);
    }
    (*env)->CallStaticVoidMethod(env, cls, mid, args);

    (*jvm)->DestroyJavaVM(jvm);
}

HelloWorld.java

これはJavaのプログラムです。説明の必要はないですね。
public class HelloWorld {
    public static void main(String[] args) {
             System.out.println("Hello World : " + args[0]);
    }
}

ENVIRONMENT

プログラムを実行させる前に、動的にライブラリを見付けに行きますので パスを設定してあげる必要があります、LD_LIBRARY_PATHです。
setenv JDK /usr/asis.local/i386_redhat60/usr.local/libexec/jdk/1.2.2/
setenv LD_LIBRARY_PATH  $JDK/jre/lib/i386/:$JDK/jre/lib/i386/classic:$JDK/jre/lib/i386/native_threads:$LD_LIBRARY_PATH

実行例

さてこれは実行例です。CプログラムcreateJVM_Helloが実行されると
下記のようなメッセージがでます。

[yasu@pcatutt1 C-call-JavaHelloWorld]$ createJVM_Hello
Hello World :  really from C!

Invoking JavaVM for calling remote object via HORB

Makefile

このMakefileはHORBデーモン上にあるリモートオブジェクトServer.javaを CプログラムcallHORBDaemon.cから呼び出すためのものです。 horbcはHORBコンパイラでServer.javaからServer_Skeleton.classと Server_Proxy.classを生成します。
CC = gcc
JAVAC = javac
JNI = -I$(JDK)/include/linux -I$(JDK)/include
LIB =  -L$(JDK)/jre/lib/i386/classic  -L$(JDK)/jre/lib/i386/green_threads -ljvm -lhpi

all: callHorbDaemon Server.class Server_Proxy.class Server_Skeleton.class Command.class 

test:
        java Command

clean:
        -rm -f callHorbDaemon Server_Skeleton.java Server_Proxy.java *~

callHorbDaemon.o: callHorbDaemon.c
        $(CC) $(JNI) -c callHorbDaemon.c
callHorbDaemon: callHorbDaemon.o
        $(CC) -o callHorbDaemon callHorbDaemon.o $(LIB)

fullclean: clean
        -rm -f callHorbDaemon callHorbDaemon.o Command.class \
	Server_Skeleton.class Server_Proxy.class \
        Server.class

Command.class: Server.class Command.java
        javac  Command.java
Server_Skeleton.class Server_Proxy.class Server.class: Server.java
        horbc Server.java

callHorbDaemon.c

これは以前のcreateJVM.cと基本的には同じことをしています。
#include <jni.h>

#define PATH_SEPARATOR ':'
#define USER_CLASSPATH ".:/usr/asis.local/i386_redhat61/usr.local/libexec/jdk/1.2.2/jre/lib/i386/:/usr/asis.local/i386_redhat61/usr.local/libexec/jdk/1.2.2/jre/lib/i386/classic:/usr/asis.local/i386_redhat61/usr.local/libexec/jdk/1.2.2/jre/lib/i386/green_thread:/afs/cern.ch/user/y/yasu/java/horb/horb2.0/classes" /* where Prog.class is */

main(int argc, char** argv) {
    JNIEnv *env;
    JavaVM *jvm;
    JDK1_1InitArgs vm_args;
    jint res;
    jclass cls;
    jmethodID mid;
    jstring jstr;
    jobjectArray args;
    char classpath[1024];

    /* IMPORTANT: specify vm_args version # if you use JDK1.1.2 and beyond */
    vm_args.version = 0x00010001;

    JNI_GetDefaultJavaVMInitArgs(&vm_args);

    /* Append USER_CLASSPATH to the end of default system class path */
    sprintf(classpath, "%s%c%s",
            vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
    vm_args.classpath = classpath;

    /* Create the Java VM */
    res = JNI_CreateJavaVM(&jvm,&env,&vm_args);
    if (res < 0) {
        fprintf(stderr, "Can't create Java VM\n");
        exit(1);
    }

    cls = (*env)->FindClass(env, "Server");
    if (cls == 0) {
        fprintf(stderr, "Can't find Server class\n");
        exit(1);
    }
 
    mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V");
    if (mid == 0) {
        fprintf(stderr, "Can't find HelloWorld.main\n");
        exit(1);
    }

    jstr = (*env)->NewStringUTF(env, (argc==2)? argv[1] : " really from C!");
    if (jstr == 0) {
        fprintf(stderr, "Out of memory\n");
        exit(1);
    }
    args = (*env)->NewObjectArray(env, 1, 
                        (*env)->FindClass(env, "java/lang/String"), jstr);
    if (args == 0) {
        fprintf(stderr, "Out of memory\n");
        exit(1);
    }

    printf("module id = %x\n", mid);
    (*env)->CallStaticVoidMethod(env, cls, mid, args);

    (*jvm)->DestroyJavaVM(jvm);
}

Server.java

import horb.orb.*;

public class Server {
  int numClients = 0;

  public static void main(String argv[]) throws HORBException {

    // start a HORBServer
    HORBServer hs = new HORBServer(8887, "horbServer8887", null);

    // register an object as a HORB object
    Server server = new Server();
    HORBServer.registerObject("Server", server,"daemonServer");
  }

  synchronized int command(int num) {
    System.out.println("current command is "+num);
    return ++numClients;
  }
}

Command.java

import horb.orb.*;

class Command {
  public static void main(String argv[]) {

    int num = (argv.length >= 1) ? Integer.valueOf(argv[0]).intValue() : 1;

    HorbURL url = new HorbURL("-", "daemonServer");

    Server_Proxy s = new Server_Proxy(url);

    int result = s.command(num);

    System.out.println(result+" commands have been accpeted to "+s._getObjectURL());
  }
}

実行例

Invoking JavaVM for calling remote object via RMI

Makefile

JAVAC = javac -O
RMIC = rmic -keepgenerated -O
CC = gcc
JAVAC = javac
JNI = -I$(JDK)/include/linux -I$(JDK)/include
LIB =  -L$(JDK)/jre/lib/i386/classic  -L$(JDK)/jre/lib/i386/green_threads -ljvm 
-lhpi

all: callDaemon Server.class ServerImpl.class ServerImpl_Stub.class ServerImpl_S
kel.class Command.class

clean:
        -rm -f callDaemon ServerImpl_Skel.java ServerImpl_Stub.java Server_Skel.
java Server_Stub.java *~

fullclean: clean
        -rm -f callDaemon *.class

callDaemon.o: callDaemon.c
        $(CC) $(JNI) -c callDaemon.c
callDaemon: callDaemon.o
        $(CC) -o callDaemon callDaemon.o $(LIB)
Server.class: Server.java
        $(JAVAC)  Server.java
ServerImpl.class: ServerImpl.java
        $(JAVAC)  ServerImpl.java
ServerImpl_Skel.class ServerImpl_Stub.class: ServerImpl.class
        $(RMIC)  ServerImpl
Command.class: Server.class Command.java
        $(JAVAC) Command.java

callDaemon.c

#include <jni.h>

#define PATH_SEPARATOR ':'

#define USER_CLASSPATH ".:/usr/asis.local/i386_redhat61/usr.local/libexec/jdk/1.2.2/jre/lib/i386/classic/:/usr/asis.local/i386_redhat61/usr.local/libexec/jdk/1.2.2/jre/lib/i386/:/usr/asis.local/i386_redhat61/usr.local/libexec/jdk/1.2.2/jre/lib/i386/green_thread" /* where Prog.class is */

main(int argc, char** argv) {
    JNIEnv *env;
    JavaVM *jvm;
    JDK1_1InitArgs vm_args;
    jint res;
    jclass cls;
    jmethodID mid;
    jstring jstr;
    jobjectArray args;
    char classpath[1024];

    /* IMPORTANT: specify vm_args version # if you use JDK1.1.2 and beyond */
    vm_args.version = 0x00010001;

    JNI_GetDefaultJavaVMInitArgs(&vm_args);

    /* Append USER_CLASSPATH to the end of default system class path */
    sprintf(classpath, "%s%c%s",
            vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
    vm_args.classpath = classpath;

    /* Create the Java VM */
    res = JNI_CreateJavaVM(&jvm,&env,&vm_args);
    if (res < 0) {
        fprintf(stderr, "Can't create Java VM\n");
        exit(1);
    }

    cls = (*env)->FindClass(env, "ServerImpl");
    if (cls == 0) {
        fprintf(stderr, "Can't find Server class\n");
        exit(1);
    }
 
    mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V");
    if (mid == 0) {
        fprintf(stderr, "Can't find HelloWorld.main\n");
        exit(1);
    }

    jstr = (*env)->NewStringUTF(env, (argc==2)? argv[1] : " really from C!");
    if (jstr == 0) {
        fprintf(stderr, "Out of memory\n");
        exit(1);
    }
    args = (*env)->NewObjectArray(env, 1, 
                        (*env)->FindClass(env, "java/lang/String"), jstr);
    if (args == 0) {
        fprintf(stderr, "Out of memory\n");
        exit(1);
    }

    (*env)->CallStaticVoidMethod(env, cls, mid, args);

    (*jvm)->DestroyJavaVM(jvm);
}

ServerImpl.java

import java.rmi.*;
import java.rmi.server.*;

public class ServerImpl extends UnicastRemoteObject implements Server {
  static String serverName;
  static int numCommands;

    public ServerImpl() throws RemoteException {
    super();
    } 

    public int command(int num) throws RemoteException {
        System.out.println("current command is "+num);
        return ++numCommands;
    }

    public static void main(String argv[]) throws Exception {

        if( System.getSecurityManager() == null)
            System.setSecurityManager(new RMISecurityManager());
        try {
            ServerImpl obj = new ServerImpl();
            System.err.println("ServerImpl obj = new ServerImpl():done");
            Naming.bind("//"+"localhost"+"/Server", obj);
            // Naming.rebind("//"+"localhost"+"/Server", obj);
            //      Naming.rebind("//"+serverName+"/Server", obj);
            System.out.println("bind done");
        } catch (Exception e) {
            System.err.println("ServerImpl exception: "+
                               e.getMessage());
            e.printStackTrace();
        }
    }
}

Server.java

import java.rmi.*;

public interface Server extends java.rmi.Remote {
  int command(int num) throws RemoteException;
}

実行例

Invoking JavaVM for calling remote object on JAS via RMI for Geant4

Makefile

# 
# Makefile for linux JavaAnalysisStudio distribution.
#

# point this at a *stable* JDK
JDK=/home/geant4/jdk1.2.2
CERNLIB=../../../ExternalLibraries/linux

cpp=gcc -c
jni=-I$(JDK)/include/linux -I$(JDK)/include
link=gcc
libs=-L$(JDK)/jre/lib/i386/classic -L$(JDK)/jre/lib/i386/native_threads -ljvm -l
hpi 
linkopts=

all: JHistogram1D.o JHistogramFactory.o

aida: JHistogram1D.o JHistogramFactory.o
        $(link) JHistogram1D.o JHistogramFactory.o $(libs) $(linkopts) -o aida

JHistogram1D.o: JHistogram1D.cpp JHistogram1D.h
        $(cpp) $(jni) JHistogram1D.cpp
        
JHistogramFactory.o: JHistogramFactory.cpp JHistogramFactory.h
        $(cpp) $(jni) JHistogramFactory.cpp

clean:
        rm *.o aida

JHistogramFactory.cpp

// JHistogramFactory.cpp: implementation of the JHistogramFactory class.
//
//////////////////////////////////////////////////////////////////////

#include <jni.h>
#include <string.h>
#include <stdlib.h>
#include "JHistogramFactory.h"
#include "JHistogram1D.h"

JNIEnv *env;
JavaVM *jvm;       


JHistogramFactory::JHistogramFactory()
{
        createJVM();

        const char* title = "G4Job";
        // Create a Job to encapsulate the histograms
        cls = env->FindClass("JASG4Driver");
        if (cls == NULL) error("Could not find class JASG4Driver");
        jmethodID constructor = env->GetMethodID(cls,"","(Ljava/lang/String;)V");
        if (constructor == NULL) error("Could not find constructor");
        jstring jtitle = env->NewStringUTF(title);
        job = env->NewObject(cls,constructor,jtitle);
        if (job == NULL) error("Could not create job");
        
        getCommand = env->GetMethodID(cls,"getCommand","()Ljava/lang/String;");
        if (getCommand == NULL) error("Could not find getCommand method");

        log = env->GetMethodID(cls,"log","(Ljava/lang/String;)V");
        if (log == NULL) error("Could not find log method");
}

JHistogramFactory::~JHistogramFactory()
{
        //const char* filename = "aida.javahist";
        //jstring jfilename = env->NewStringUTF(filename);
        //env->CallVoidMethod(job,save,jfilename);
        //env->CallVoidMethod(job,dump);

        destroyJVM();
}
void JHistogramFactory::RegisterImmediateFunction(void* fnptr)
{
        JNINativeMethod nativeMethod;
        nativeMethod.name = "immediateCommand";
        nativeMethod.signature = "(Ljava/lang/String;)V";
        nativeMethod.fnPtr = fnptr;
        int rc = env->RegisterNatives(cls,&nativeMethod,1);
        printf("rc=%d\n",rc);
}
const char* JHistogramFactory::GetCommand()
{
        jboolean isCopy;
        jstring command = (jstring) env->CallObjectMethod(job,getCommand);
        return env->GetStringUTFChars(command,&isCopy);
}
void JHistogramFactory::FreeCommand(const char* c)
{
        env->ReleaseStringUTFChars(command,c);
}
JHistogram1D* JHistogramFactory::create1DHistogram(const char* name)
{
        return new JHistogram1D(this,name);
}
void JHistogramFactory::error(const char* msg)
{
        fprintf(stderr,"Error: %s\n",msg);
        exit(1);
}
JNIEnv* JHistogramFactory::getEnv()
{
        return env;
}
void JHistogramFactory::Log(const char* msg)
{
        jstring jmsg = env->NewStringUTF(msg);
        env->CallVoidMethod(job,log,jmsg);
}
void JHistogramFactory::createJVM()
{
        JavaVMInitArgs vm_args;
        JavaVMOption options[2];

        char* cp = getenv("CLASSPATH");
        //char* cp ="c:\\program files\\java analysis studio\\lib\\hep.jar";
        char* opt = new char[strlen(cp)+100];
        strcpy(opt,"-Djava.class.path=");
        strcat(opt,cp);
        printf("cp=%s\n",opt);
        options[0].optionString = opt;
        options[1].optionString = "-verbose";

        vm_args.version = JNI_VERSION_1_2;
        vm_args.options = options;
        vm_args.nOptions = 1;
        vm_args.ignoreUnrecognized = 1;

        int rc = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
        if (rc < 0)  error("Failed to create Java VM");
        delete opt;
}
void JHistogramFactory::destroyJVM()
{
        jvm->DestroyJavaVM();
}

JHistogramFactory.h

// JHistogramFactory.h: interface for the JHistogramFactory class.
//////////////////////////////////////////////////////////////////////
#ifndef JHISTOGRAMFACTORY
#define JHISTOGRAMFACTORY

class JHistogram1D;

class JHistogramFactory  
{
private:
        jclass cls;
        jobject job;
        jstring command;
        jmethodID getCommand;
        jmethodID log;
        jmethodID save;
        jmethodID dump;
        void createJVM();
        void destroyJVM();
public:
        JHistogramFactory();
        virtual ~JHistogramFactory();
        void RegisterImmediateFunction(void* fnptr);
        JHistogram1D* create1DHistogram(const char* name);
        JNIEnv* getEnv();
        void error(const char* message);
        const char* GetCommand();
        void FreeCommand(const char* command);
        void Log(const char* message);
};
#endif

JASG4Driver.java

import jas.server.*;
import java.io.PrintWriter;
import java.util.Vector;

public class JASG4Driver extends HistogramServer
{
  public JASG4Driver(String name)
  {
    super(name);
  }
  protected void fireMessageReceived(MessageEvent e)
  {
    super.fireMessageReceived(e);
    String command = (String) e.getMessage();
    System.out.println("Message Received!!!"+e.getMessage());
    if (command.startsWith("!"))
    {
        immediateCommand(command);
    }
    else synchronized (queue)
    {
        if (queue.isEmpty()) queue.notifyAll();
        queue.add(command);
    }
  }
  public void log(String msg)
  {
        if (print == null) print = new PrintWriter(getLogStream());
        print.print(msg);
        print.flush();
  }
  public String getCommand()
  { 
    try
    {
      synchronized (queue)
      {
        if (queue.isEmpty()) queue.wait();
        String command = (String) queue.firstElement();
        queue.removeElementAt(0);
        return command;
      }
    }
    catch (InterruptedException x) 
    {
      return "exit";
    }
  }
  private native void immediateCommand(String command);
  private Vector queue = new Vector();
  private PrintWriter print;
}

JASG4Plugin.java

import jas.plugin.*;
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;

public class JASG4Plugin extends Plugin implements ActionListener
{
   public void init()
   {
        JMenu menu = new JMenu("Geant4");

        JMenuItem start = new JMenuItem("BeamOn");
        start.setActionCommand("/run/beamOn 1000000");
        start.addActionListener(this);
        menu.add(start);

        JMenuItem stop = new JMenuItem("BeamOff");
        stop.setActionCommand("!stop");
        stop.addActionListener(this);
        menu.add(stop);
        
        JMenuItem trap = new JCheckBoxMenuItem("Trap Geant4 Output");
        trap.setActionCommand("#trap");
        trap.addActionListener(this);
        menu.add(trap);

        JMenuItem other = new JCheckBoxMenuItem("Show Command Window");
        other.setActionCommand("#console");
        other.addActionListener(this);
        menu.add(other);
        
        addMenu(menu);
    }
    public void actionPerformed(ActionEvent e)
    {
       String command = e.getActionCommand();
       if (command.equals("#console"))
       {
          if (g4console == null)
          {
                g4console = installConsole("G4Console",new G4Console());
          }
          else 
          {
                g4console.close();
                g4console = null;
          }
       }
       else if (command.equals("#trap"))
       {
          boolean trap = ((JCheckBoxMenuItem) e.getSource()).isSelected();
          if (trap) sendJob("!startTrap");
          else sendJob("!stopTrap");
       }
       else sendJob(command);
    }
    private PageContext g4console;
    
    private class G4Console extends JPanel
    {
       G4Console()
        {
                super(new BorderLayout());
                JTextField field = new JTextField();
                add(field,BorderLayout.SOUTH);
                field.addActionListener(JASG4Plugin.this);
        }
    }
}

JHistogram1D.cpp

// JHistogram.cpp: implementation of the JHistogram1D class.
//////////////////////////////////////////////////////////////////////

#include <jni.h>
#include <string.h>
#include <stdlib.h>
#include "JHistogram1D.h"
#include "JHistogramFactory.h"

JHistogram1D::JHistogram1D(JHistogramFactory* factory,  const char* title)
{
        env =  factory->getEnv();
        // Create the corresponding Java histogram
        jclass cls = env->FindClass("hep/analysis/Histogram");
        if (cls == NULL) factory->error("Could not create hep.analysis.Histogram
");
        jmethodID constructor = env->GetMethodID(cls, "", "(Ljava/lang/Str
ing;)V");
        if (constructor == NULL) factory->error("Could not find constructor for 
hep.analysis.Histogram");
        fillMethod = env->GetMethodID(cls,"fill","(D)V");
        if (fillMethod == NULL) factory->error("Could not find fill method for h
ep.analysis.Histogram");
        jstring jtitle = env->NewStringUTF(title);
        jhist = env->NewObject(cls,constructor,jtitle);
}

JHistogram1D::~JHistogram1D()
{
        env->DeleteLocalRef(jhist);
}

void JHistogram1D::fill(double value)
{
        env->CallVoidMethod(jhist,fillMethod,value);
}
//test
//int main(int argc, char** argv)
//{
//      JHistogramFactory factory;
//      JHistogram1D* hist = factory.create1DHistogram("test");
//   for (int i=0; i<1000; i++)
//      {
//              hist->fill(i);
//      }
//      return 0;
//}

JHistogram1D.h

#ifndef JHISTOGRAM1D
#define JHISTOGRAM1D

class JHistogramFactory;

class JHistogram1D
{
private:
        jmethodID fillMethod;
    jobject jhist;
    JNIEnv* env;

public:
        JHistogram1D(JHistogramFactory *factory, const char *title);
        virtual ~JHistogram1D();
        void fill(double value);
};

#endif

実行例