/** * This is a simple mechanism to bind Inkscape to Java, and thence * to all of the nice things that can be layered upon that. * * Authors: * Bob Jamison * * Copyright (C) 2007-2008 Bob Jamison * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #ifdef __WIN32__ #include #else #include #include #endif #if HAVE_SYS_STAT_H #include #endif #include "javabind.h" #include "javabind-private.h" #include #include #include /** * Note: We must limit Java or JVM-specific code to this file * and to dobinding.cpp. It should be hidden from javabind.h * * This file is mostly about getting things up and running, and * providing the basic C-to-Java hooks. * * dobinding.cpp will have the rote and repetitious * class-by-class binding */ namespace Inkscape { namespace Bind { //######################################################################## //# DEFINITIONS //######################################################################## typedef jint (*CreateVMFunc)(JavaVM **, JNIEnv **, void *); //######################################################################## //# UTILITY //######################################################################## /** * Normalize path. Java wants '/', even on Windows */ String normalizePath(const String &str) { String buf; for (unsigned int i=0 ; iGetFieldID(env->GetObjectClass(obj), name, "I"); return env->GetIntField(obj, fid); } void setInt(JNIEnv *env, jobject obj, const char *name, jint val) { jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "I"); env->SetIntField(obj, fid, val); } jlong getLong(JNIEnv *env, jobject obj, const char *name) { jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J"); return env->GetLongField(obj, fid); } void setLong(JNIEnv *env, jobject obj, const char *name, jlong val) { jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "J"); env->SetLongField(obj, fid, val); } jfloat getFloat(JNIEnv *env, jobject obj, const char *name) { jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F"); return env->GetFloatField(obj, fid); } void setFloat(JNIEnv *env, jobject obj, const char *name, jfloat val) { jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "F"); env->SetFloatField(obj, fid, val); } jdouble getDouble(JNIEnv *env, jobject obj, const char *name) { jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D"); return env->GetDoubleField(obj, fid); } void setDouble(JNIEnv *env, jobject obj, const char *name, jdouble val) { jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "D"); env->SetDoubleField(obj, fid, val); } String getString(JNIEnv *env, jobject obj, const char *name) { jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;"); jstring jstr = (jstring)env->GetObjectField(obj, fid); const char *chars = env->GetStringUTFChars(jstr, JNI_FALSE); String str = chars; env->ReleaseStringUTFChars(jstr, chars); return str; } void setString(JNIEnv *env, jobject obj, const char *name, const String &val) { jstring jstr = env->NewStringUTF(val.c_str()); jfieldID fid = env->GetFieldID(env->GetObjectClass(obj), name, "Ljava/lang/String;"); env->SetObjectField(obj, fid, jstr); } //######################################################################## //# CONSTRUCTOR/DESTRUCTOR //######################################################################## static JavaBinderyImpl *_instance = NULL; JavaBindery *JavaBindery::getInstance() { return JavaBinderyImpl::getInstance(); } JavaBinderyImpl *JavaBinderyImpl::getInstance() { if (!_instance) { _instance = new JavaBinderyImpl(); } return _instance; } JavaBinderyImpl::JavaBinderyImpl() { jvm = NULL; env = NULL; } JavaBinderyImpl::~JavaBinderyImpl() { } //######################################################################## //# MESSAGES //######################################################################## void err(const char *fmt, ...) { #if 0 va_list args; fprintf(stderr, "JavaBinderyImpl err:"); va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); fprintf(stderr, "\n"); #else va_list args; g_warning("JavaBinderyImpl err:"); va_start(args, fmt); g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, args); va_end(args); g_warning("\n"); #endif } void msg(const char *fmt, ...) { #if 0 va_list args; fprintf(stdout, "JavaBinderyImpl:"); va_start(args, fmt); vfprintf(stdout, fmt, args); va_end(args); fprintf(stdout, "\n"); #else va_list args; g_message("JavaBinderyImpl:"); va_start(args, fmt); g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args); va_end(args); g_message("\n"); #endif } //######################################################################## //# W I N 3 2 S T Y L E //######################################################################## #ifdef __WIN32__ #define DIR_SEPARATOR "\\" #define PATH_SEPARATOR ";" static bool getRegistryString(HKEY root, const char *keyName, const char *valName, char *buf, int buflen) { HKEY key; DWORD bufsiz = buflen; RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &key); int ret = RegQueryValueEx(key, TEXT(valName), NULL, NULL, (BYTE *)buf, &bufsiz); if (ret != ERROR_SUCCESS) { err("Key '%s\\%s not found\n", keyName, valName); return false; } RegCloseKey(key); return true; } static String cleanPath(const String &s) { String buf; for (unsigned int i=0 ; i=0) { //msg("found"); libname = jpath; found = true; break; } } } //not at JAVA_HOME. check the registry if (!found) { char verbuf[16]; char regpath[80]; strcpy(regpath, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); bool ret = getRegistryString(HKEY_LOCAL_MACHINE, regpath, "CurrentVersion", verbuf, 15); if (!ret) { msg("JVM CurrentVersion not found in registry at '%s'", regpath); } else { strcat(regpath, "\\"); strcat(regpath, verbuf); //msg("reg path: %s\n", regpath); char valbuf[80]; ret = getRegistryString(HKEY_LOCAL_MACHINE, regpath, "RuntimeLib", valbuf, 79); if (ret) { found = true; libname = valbuf; } else { msg("JVM RuntimeLib not found in registry at '%s'", regpath); } } } if (!found) { err("JVM not found at JAVA_HOME or in registry"); return NULL; } /** * If we are here, then we seem to have a valid path for jvm.dll * Give it a try */ msg("getCreateVMFunc: Loading JVM: %s", libname.c_str()); HMODULE lib = LoadLibrary(libname.c_str()); if (!lib) { err("Java VM not found at '%s'", libname.c_str()); return NULL; } CreateVMFunc createVM = (CreateVMFunc)GetProcAddress(lib, "JNI_CreateJavaVM"); if (!createVM) { err("Could not find 'JNI_CreateJavaVM' in shared library '%s'", libname.c_str()); return NULL; } return createVM; } static void getJavaRoot(String &javaroot) { char exeName[80]; GetModuleFileName(NULL, exeName, 80); char *slashPos = strrchr(exeName, '\\'); if (slashPos) *slashPos = '\0'; javaroot = exeName; javaroot.append("\\"); javaroot.append(INKSCAPE_JAVADIR); } //######################################################################## //# U N I X S T Y L E //######################################################################## #else /* !__WIN32__ */ #define DIR_SEPARATOR "/" #define PATH_SEPARATOR ":" /** * Recursively descend into a directory looking for libjvm.so */ static bool findJVMRecursive(const String &dirpath, std::vector &results) { DIR *dir = opendir(dirpath.c_str()); if (!dir) return false; bool ret = false; while (true) { struct dirent *de = readdir(dir); if (!de) break; String fname = de->d_name; if (fname == "." || fname == "..") continue; String path = dirpath; path.push_back('/'); path.append(fname); if (fname == "libjvm.so") { ret = true; results.push_back(path); continue; } struct stat finfo; if (lstat(path.c_str(), &finfo)<0) { break; } if (finfo.st_mode & S_IFDIR) { ret |= findJVMRecursive(path, results); } } closedir(dir); return ret; } static const char *commonJavaPaths[] = { "/usr/java", "/usr/local/java", "/usr/lib/jvm", "/usr/local/lib/jvm", NULL }; /** * Look for a Java VM (libjvm.so) in several Unix places */ static bool findJVM(String &result) { std::vector results; int found = false; /* Is there one specified by the user? */ const char *javaHome = getenv("JAVA_HOME"); if (javaHome && findJVMRecursive(javaHome, results)) found = true; else for (const char **path = commonJavaPaths ; *path ; path++) { if (findJVMRecursive(*path, results)) { found = true; break; } } if (!found) { return false; } if (results.size() == 0) return false; //Look first for a Client VM for (unsigned int i=0 ; id_name; if (fname == "." || fname == "..") continue; if (fname.size()<5) //x.jar continue; if (fname.compare(fname.size()-4, 4, ".jar") != 0) continue; String path = libdir; path.append(DIR_SEPARATOR); path.append(fname); cp.append(PATH_SEPARATOR); cp.append(path); } closedir(dir); result = cp; } //======================================================================== // SCRIPT RUNNER //======================================================================== /** * These methods are used to allow the ScriptRunner class to * redirect its stderr and stdout streams to here, to be caught * by two string buffers. We can then use those buffers how we * want. These native methods are only those needed for running * a script. For the main C++/Java bindings, see dobinding.cpp */ void JNICALL stdOutWrite(JNIEnv */*env*/, jobject /*obj*/, jlong ptr, jint ch) { JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr; bind->stdOut(ch); } void JNICALL stdErrWrite(JNIEnv */*env*/, jobject /*obj*/, jlong ptr, jint ch) { JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr; bind->stdErr(ch); } void JNICALL logWrite(JNIEnv */*env*/, jobject /*obj*/, jlong ptr, jint ch) { JavaBinderyImpl *bind = (JavaBinderyImpl *)ptr; bind->log(ch); } static JNINativeMethod scriptRunnerMethods[] = { { (char *)"stdOutWrite", (char *)"(JI)V", (void *)stdOutWrite }, { (char *)"stdErrWrite", (char *)"(JI)V", (void *)stdErrWrite }, { (char *)"logWrite", (char *)"(JI)V", (void *)logWrite }, { NULL, NULL, NULL } }; /** * This sets up the 'ScriptRunner' java class for execution of * scripts. The class's constructor takes a jlong. This java long * is used to store the pointer to 'this'. When ScriptRunner makes * native calls, it passes that jlong back, so that it can call the * methods of this C++ class. */ bool JavaBinderyImpl::setupScriptRunner() { String className = "org/inkscape/cmn/ScriptRunner"; if (!registerNatives(className, scriptRunnerMethods)) { return false; } jclass cls = env->FindClass(className.c_str()); if (!cls) { err("setupScriptRunner: cannot find class '%s' : %s", className.c_str(), getException().c_str()); return false; } jmethodID mid = env->GetMethodID(cls, "", "(J)V"); if (!mid) { err("setupScriptRunner: cannot find constructor for '%s' : %s", className.c_str(), getException().c_str()); return false; } jobject obj = env->NewObject(cls, mid, ((jlong)this)); if (!obj) { err("setupScriptRunner: cannot construct '%s' : %s", className.c_str(), getException().c_str()); return false; } msg("ScriptRunner ready"); return true; } //======================================================================== // End SCRIPT RUNNER //======================================================================== /** * This is used to grab output from the VM itself. See 'options' below. */ static int JNICALL vfprintfHook(FILE* /*f*/, const char *fmt, va_list args) { g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, args); } /** * This is the most important part of this class. Here we * attempt to find, load, and initialize a java (or mlvm?) virtual * machine. * * @return true if successful, else false */ bool JavaBinderyImpl::loadJVM() { if (jvm) return true; CreateVMFunc createVM = getCreateVMFunc(); if (!createVM) { err("Could not find 'JNI_CreateJavaVM' in shared library"); return false; } String javaroot; getJavaRoot(javaroot); String cp; populateClassPath(javaroot, cp); String classpath = "-Djava.class.path="; classpath.append(normalizePath(cp)); msg("Class path is: '%s'", classpath.c_str()); String libpath = "-Djava.library.path="; libpath.append(javaroot); libpath.append(DIR_SEPARATOR); libpath.append("libm"); libpath = normalizePath(libpath); msg("Lib path is: '%s'", libpath.c_str()); JavaVMInitArgs vm_args; JavaVMOption options[10];//should be enough int nOptions = 0; options[nOptions++].optionString = (char *)classpath.c_str(); options[nOptions++].optionString = (char *)libpath.c_str(); //options[nOptions++].optionString = (char *)"-verbose:jni"; options[nOptions ].optionString = (char *)"vfprintf"; options[nOptions++].extraInfo = (void *)vfprintfHook; vm_args.version = JNI_VERSION_1_4; vm_args.options = options; vm_args.nOptions = nOptions; vm_args.ignoreUnrecognized = true; if (createVM(&jvm, &env, &vm_args) < 0) { err("JNI_CreateJavaVM() failed"); return false; } //get jvm version jint vers = env->GetVersion(); int versionMajor = (vers>>16) & 0xffff; int versionMinor = (vers ) & 0xffff; msg("Loaded JVM version %d.%d", versionMajor, versionMinor); if (!setupScriptRunner()) return false; return true; } /** * This is a difficult method. What we are doing is trying to * call a static method with a list of arguments. Similar to * a varargs call, we need to marshal the Values into their * Java equivalents and make the proper call. * * @param type the return type of the method * @param className the full (package / name) name of the java class * @param methodName the name of the method being invoked * @param signature the method signature (ex: "(Ljava/lang/String;I)V" ) * that describes the param and return types of the method. * @param retval the return value of the java method * @return true if the call was successful, else false. This is not * the return value of the method. */ bool JavaBinderyImpl::callStatic(int type, const String &className, const String &methodName, const String &signature, const std::vector ¶ms, Value &retval) { jclass cls = env->FindClass(className.c_str()); if (!cls) { err("Could not find class '%s' : %s", className.c_str(), getException().c_str()); return false; } jmethodID mid = env->GetStaticMethodID(cls, methodName.c_str(), signature.c_str()); if (!mid) { err("Could not find method '%s:%s/%s' : %s", className.c_str(), methodName.c_str(), signature.c_str(), getException().c_str()); return false; } /** * Assemble your parameters into a form usable by JNI */ jvalue *jvals = new jvalue[params.size()]; for (unsigned int i=0 ; iNewStringUTF(v.getString().c_str()); break; } default: { err("Unknown value type: %d", v.getType()); return false; } } } switch (type) { case Value::BIND_VOID: { env->CallStaticVoidMethodA(cls, mid, jvals); break; } case Value::BIND_BOOLEAN: { jboolean ret = env->CallStaticBooleanMethodA(cls, mid, jvals); if (ret == JNI_TRUE) //remember, don't truncate retval.setBoolean(true); else retval.setBoolean(false); break; } case Value::BIND_INT: { jint ret = env->CallStaticIntMethodA(cls, mid, jvals); retval.setInt(ret); break; } case Value::BIND_DOUBLE: { jdouble ret = env->CallStaticDoubleMethodA(cls, mid, jvals); retval.setDouble(ret); break; } case Value::BIND_STRING: { jobject ret = env->CallStaticObjectMethodA(cls, mid, jvals); jstring jstr = (jstring) ret; const char *str = env->GetStringUTFChars(jstr, JNI_FALSE); retval.setString(str); env->ReleaseStringUTFChars(jstr, str); break; } default: { err("Unknown return type: %d", type); return false; } } delete jvals; String errStr = getException(); if (errStr.size()>0) { err("callStatic: %s", errStr.c_str()); return false; } return true; } /** * Fetch the last exception from the JVM, if any. Clear it to * continue processing * * @return the exception's descriptio,if any. Else "" */ String JavaBinderyImpl::getException() { String buf; jthrowable exc = env->ExceptionOccurred(); if (!exc) return buf; jclass cls = env->GetObjectClass(exc); jmethodID mid = env->GetMethodID(cls, "toString", "()Ljava/lang/String;"); jstring jstr = (jstring) env->CallObjectMethod(exc, mid); const char *str = env->GetStringUTFChars(jstr, JNI_FALSE); buf.append(str); env->ReleaseStringUTFChars(jstr, str); env->ExceptionClear(); return buf; } /** * Convenience method to call the static void main(String argv[]) * method of a given class * * @param className full name of the java class * @return true if successful, else false */ bool JavaBinderyImpl::callMain(const String &className) { std::vector parms; Value retval; return callStatic(Value::BIND_VOID, className, "main", "([Ljava/lang/String;)V", parms, retval); } /** * Used to register an array of native methods for a named class * * @param className the full name of the java class * @param the method array * @return true if successful, else false */ bool JavaBinderyImpl::registerNatives(const String &className, const JNINativeMethod *methods) { jclass cls = env->FindClass(className.c_str()); if (!cls) { err("Could not find class '%s'", className.c_str()); return false; } //msg("registerNatives: class '%s' found", className.c_str()); /** * hack for JDK bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6493522 */ jmethodID mid = env->GetMethodID(env->GetObjectClass(cls), "getConstructors", "()[Ljava/lang/reflect/Constructor;"); if (!mid) { err("Could not get reflect mid for 'getConstructors' : %s", getException().c_str()); return false; } jobject res = env->CallObjectMethod(cls, mid); if (!res) { err("Could not get constructors : %s", getException().c_str()); return false; } /** * end hack */ jint nrMethods = 0; for (const JNINativeMethod *m = methods ; m->name ; m++) nrMethods++; jint ret = env->RegisterNatives(cls, methods, nrMethods); if (ret < 0) { err("Could not register %d native methods for '%s' : %s", nrMethods, className.c_str(), getException().c_str()); return false; } return true; } } // namespace Bind } // namespace Inkscape //######################################################################## //# E N D O F F I L E //########################################################################