8

在 java 6 中,我能够在 Scala 中使用 JNI 就好了。我会有如下代码:

package mypackage
object MyClass {
    System.loadLibrary("myclass-native")
    @native def foo(): Int = sys.error("")
}

然后我会跑:

javah -classpath target/scala-2.9.1/classes -d target/jni mypackage.MyClass$

我会得到我的头文件就好了。

在 java 7 中,我收到以下错误:

Exception in thread "main" java.lang.IllegalArgumentException: Not a valid class name: mypackage.MyClass.
at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:177)
at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:68)
at com.sun.tools.javah.JavahTask.run(JavahTask.java:509)
at com.sun.tools.javah.JavahTask.run(JavahTask.java:335)
at com.sun.tools.javah.Main.main(Main.java:46)

就像 javah 不再接受类名中的美元符号一样,但我需要在 Scala 中使用美元符号来获得等效于静态方法的方法。

参考 java 6:

$ java -version
java version "1.6.0_29"
Java(TM) SE Runtime Environment (build 1.6.0_29-b11)
Java HotSpot(TM) 64-Bit Server VM (build 20.4-b02, mixed mode)
$ javah -version
javah version "1.6.0_29"

使用 Java 7:

$ java -version
java version "1.7.0_03"
OpenJDK Runtime Environment (IcedTea7 2.1.1pre) (7~u3-2.1.1~pre1-1ubuntu2)
OpenJDK 64-Bit Server VM (build 22.0-b10, mixed mode)
$ javah -version
javah version "1.7.0_03"

有没有人在java 7中使用javah for JNI和Scala?

编辑

作为错误发布在http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7185778

4

1 回答 1

5

了解正在发生的事情的最佳方法是直接通过 OpenJDK 网站访问源代码。如果我们看com.sun.tools.javac.api.JavacTool

 public JavacTask getTask(Writer out,
                         JavaFileManager fileManager,
                         DiagnosticListener<? super JavaFileObject> diagnosticListener,
                         Iterable<String> options,
                         Iterable<String> classes,
                         Iterable<? extends JavaFileObject> compilationUnits)
{
    try {
        Context context = new Context();
        ClientCodeWrapper ccw = ClientCodeWrapper.instance(context);

    final String kindMsg = "All compilation units must be of SOURCE kind";
    if (options != null)
        for (String option : options)
            option.getClass(); // null check
    if (classes != null) {
        for (String cls : classes)
            if (!SourceVersion.isName(cls)) // implicit null check
                throw new IllegalArgumentException("Not a valid class name: " + cls);
    }
    if (compilationUnits != null) {
        compilationUnits = ccw.wrapJavaFileObjects(compilationUnits); // implicit null check
        for (JavaFileObject cu : compilationUnits) {
            if (cu.getKind() != JavaFileObject.Kind.SOURCE)
                throw new IllegalArgumentException(kindMsg);
        }
    }

    if (diagnosticListener != null)
        context.put(DiagnosticListener.class, ccw.wrap(diagnosticListener));

    if (out == null)
        context.put(Log.outKey, new PrintWriter(System.err, true));
    else
        context.put(Log.outKey, new PrintWriter(out, true));

    if (fileManager == null)
        fileManager = getStandardFileManager(diagnosticListener, null, null);
    fileManager = ccw.wrap(fileManager);
    context.put(JavaFileManager.class, fileManager);
    processOptions(context, fileManager, options);
    Main compiler = new Main("javacTask", context.get(Log.outKey));
    return new JavacTaskImpl(compiler, options, context, classes, compilationUnits);
} catch (ClientCodeException ex) {
    throw new RuntimeException(ex.getCause());
}

}

您可以看到有问题的行:

  if (!SourceVersion.isName(cls)) // implicit null check
                        throw new IllegalArgumentException("Not a valid class name: " + cls);

所以现在让我们看看 javax.lang.model.SourceVersion

   /**
     *  Returns whether or not {@code name} is a syntactically valid
     *  qualified name in the latest source version.  Unlike {@link
     *  #isIdentifier isIdentifier}, this method returns {@code false}
     *  for keywords and literals.
     *
     * @param name the string to check
     * @return {@code true} if this string is a
     * syntactically valid name, {@code false} otherwise.
     * @jls 6.2 Names and Identifiers
     */
    public static boolean isName(CharSequence name) {
        String id = name.toString();

        for(String s : id.split("\\.", -1)) {
            if (!isIdentifier(s) || isKeyword(s))
                return false;
        }
        return true;
    }

所以你可以看到我们期望返回true(但返回false)的方法是:

  public static boolean isIdentifier(CharSequence name) {
        String id = name.toString();

        if (id.length() == 0) {
            return false;
        }
        int cp = id.codePointAt(0);
        if (!Character.isJavaIdentifierStart(cp)) {
            return false;
        }
        for (int i = Character.charCount(cp);
                i < id.length();
                i += Character.charCount(cp)) {
            cp = id.codePointAt(i);
            if (!Character.isJavaIdentifierPart(cp)) {
                return false;
            }
        }
        return true;
    }

问题是 !Character.isJavaIdentifierPart(cp)

现在如果我们看一下 1.6 版本:

public static boolean isJavaIdentifierPart(int codePoint) {
        boolean bJavaPart = false;

        if (codePoint >= MIN_CODE_POINT && codePoint <= FAST_PATH_MAX) {
            bJavaPart = CharacterDataLatin1.isJavaIdentifierPart(codePoint);
        } else {
            int plane = getPlane(codePoint);
            switch(plane) {
            case(0):
                bJavaPart = CharacterData00.isJavaIdentifierPart(codePoint);
                break;
            case(1):
                bJavaPart = CharacterData01.isJavaIdentifierPart(codePoint);
                break;
            case(2):
                bJavaPart = CharacterData02.isJavaIdentifierPart(codePoint);
                break;
            case(3): // Undefined
            case(4): // Undefined
            case(5): // Undefined
            case(6): // Undefined
            case(7): // Undefined
            case(8): // Undefined
            case(9): // Undefined
            case(10): // Undefined
            case(11): // Undefined
            case(12): // Undefined
            case(13): // Undefined
                bJavaPart = CharacterDataUndefined.isJavaIdentifierPart(codePoint);
                break;
            case(14): 
                bJavaPart = CharacterData0E.isJavaIdentifierPart(codePoint);
                break;
            case(15): // Private Use
            case(16): // Private Use
                bJavaPart = CharacterDataPrivateUse.isJavaIdentifierPart(codePoint);
                break;
            default:
                // the argument's plane is invalid, and thus is an invalid codepoint
                // bJavaPart remains false;
                break;
            }
        }
        return bJavaPart;
    }

和 1.7 版本:

  public static boolean isJavaIdentifierPart(int codePoint) {
        return CharacterData.of(codePoint).isJavaIdentifierPart(codePoint);
    }

这里发生了一些重构,如果你查看 CharacterData,你会发现它返回了一些类,这些类是在/openjdk/make/tools/GenerateCharacter/CharacterData**.java.template构建 java 分发时从模板中动态生成的:

// Character <= 0xff (basic latin) is handled by internal fast-path
    // to avoid initializing large tables.
    // Note: performance of this "fast-path" code may be sub-optimal
    // in negative cases for some accessors due to complicated ranges.
    // Should revisit after optimization of table initialization.

static final CharacterData of(int ch) {
    if (ch >>> 8 == 0) {     // fast-path
        return CharacterDataLatin1.instance;
    } else {
        switch(ch >>> 16) {  //plane 00-16
        case(0):
            return CharacterData00.instance;
        case(1):
            return CharacterData01.instance;
        case(2):
            return CharacterData02.instance;
        case(14):
            return CharacterData0E.instance;
        case(15):   // Private Use
        case(16):   // Private Use
            return CharacterDataPrivateUse.instance;
        default:
            return CharacterDataUndefined.instance;
        }
    }
}

我认为您可以尝试在调试模式下运行 javah 并查看在这两种情况下会发生什么,然后将准确的错误报告发送给 OpenJDK 人员,因为此重构显然已经引入了错误。

于 2012-07-19T08:51:53.387 回答