如何使用JNI创建一个对象?

59

我需要使用NDK和JNI将一些函数实现到Android应用程序中。

以下是我编写的C代码及其相关问题:

#include <jni.h>
#include <stdio.h>

jobject
Java_com_example_ndktest_NDKTest_ImageRef(JNIEnv* env, jobject obj, jint width, jint height, jbyteArray myArray)
{
    jint i;
    jobject object;
    jmethodID constructor;
    jobject cls;
    cls = (*env)->FindClass(env, "com/example/ndktest/NDKTest/Point");

//what should put as the second parameter? Is my try correct, according to what
//you can find in .java file? I used this documentation: http://download.oracle.com/javase/6/docs/technotes/guides/jni/spec/functions.html#wp16027

    constructor = (*env)->GetMethodID(env, cls, "<init>", "void(V)");
//http://download.oracle.com/javase/6/docs/technotes/guides/jni/spec/functions.html#wp16660
//Again, is the last parameter ok?

    object = (*env)->NewObject(env, cls, constructor, 5, 6);
//I want to assign "5" and "6" to point.x and point.y respectively.
    return object;
}    

我的问题在代码中已经或多或少得到了解释。也许还有:函数的返回类型(jobject)是否正确?

现在是NDKTest.java文件:

package com.example.ndktest;

import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle;

public class NDKTest extends Activity {
    /** Called when the activity is first created. */
    public native Point ImageRef(int width, int height, byte[] myArray);
    public class Point
    {

        Point(int myx, int myy)
        {
            x = myx;
            y = myy;
        }

        int x;
        int y;
    }

    @Override
    public void onCreate(Bundle savedInstanceState)
    {

         super.onCreate(savedInstanceState);
         TextView tv = new TextView(this);
         byte[] anArray = new byte[3];
         for (byte i = 0; i < 3; i++)
             anArray[i] = i;
         Point point = ImageRef(2, 3, anArray);
         tv.setText(String.valueOf(point.x));
            setContentView(tv);     
    }



    static
    {
       System.loadLibrary("test");
    }
}

当我尝试运行这段代码时,它无法工作。


3
请解释“doesn't work”。 - hmakholm left over Monica
6个回答

91

由于Point是内部类,获取它的方法如下:

jclass cls = (*env)->FindClass(env, "com/example/ndktest/NDKTest$Point");

内部类使用$约定并没有在权威规范中得到明确的文档说明,但已经深入到了很多代码中,因此不太可能改变。尽管如此,如果您将JNI代码限制为与顶层类一起使用,则会感觉更加稳健。

您想要一个接受两个整数参数的构造函数。其签名为(II)V,因此:

constructor = (*env)->GetMethodID(env, cls, "<init>", "(II)V");

下次在你的代码中加入一些错误处理,这样你就可以知道哪部分代码出了问题!


4
注意:此答案无效。请参阅Seva答案下面的评论,了解为什么(构造函数参数必须包括外部类)。 - Colin
1
@Colin:是的,我忽略了Point不是静态的。 - hmakholm left over Monica
在使用FindClass或GetMethodID返回的值时,是否有必要关闭/释放它们? - zomega

14
该规范是正确的,但在这种情况下有点误导人。 GetMethodID 需要一个方法名称和一个方法签名。 规范说明

要获取构造函数的方法 ID,请将<init>作为方法名称提供,并将 void (V) 作为返回类型。

请注意,它说的是返回类型,而不是签名。虽然void(V)表面上与签名相似,但规范告诉您,签名必须指定void(即V)返回类型。

无参构造函数的正确签名是()V。如果构造函数有参数,则应在括号内描述,正如其他评论者所指出的那样。


7

你的代码存在一些问题。

首先,为什么要创建自己的Point类而不使用提供的android.graphics.Point库?

其次,嵌套类的类规范不同 - 应该是“com/example/ndktest/NDKTest$Point”。类的嵌套与包不同。

第三,我认为JNI不允许您创建非静态嵌套类的实例。您需要在对象创建时传递嵌套类对象的“this”指针 - 没有这样的参数。

最后,虽然我看到了使用“void(V)”作为构造方法签名的指导,但这与其他方法签名不符;通常,具有两个int参数和void返回类型的方法将是“(II)V”。

顺便说一下,我发现从NDK到Java传递基本类型和基本类型数组更加清晰。对象的创建/访问很麻烦,难以调试。


我为了举例子创建了自己的Point类。它本来可以叫做_foo_。我将我的类改为顶级类,将"void(V)"改为"(II)V",现在它可以正常工作了。 - pmichna
2
文档中说:“此ID必须通过使用<init>作为方法名和void(V)作为返回类型调用GetMethodID()来获取。” 当我第一次阅读时,我也感到困惑; 我以为它的意思是“使用签名void(V)”,但实际上它的意思是“在构造签名时使用类型代码V(表示void)”。 确实,void(V)看起来像一个奇怪的签名,但是"<init>"是指定构造函数的奇怪方式。 当一切都是魔法时,混淆的东西确实很令人困惑! http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html - steveha
13
实际上,您可以创建非静态嵌套类的实例,方法有点不明显:所有嵌套类的构造函数都有一个隐含的第一个参数,即外部类类型。因此,在上面的示例中,如果Point类是非静态的,则构造函数签名将为"<init>", "(Lcom/example/ndktest/NDKTest;II)V"。构建后从您的类目录运行javap -s -p com.example.ndktest.NDKTest$Point即可查看。 - benkc
1
@benkc 这个评论救了我。非常重要的细节。 - boiler96

5

在JNI中,您可以始终使用 javap 工具来查找方法签名。只需运行 javap -s com.example.ndktest.NDKTest 并从输出中复制方法签名即可。


3

使用JNI创建Point对象需要三个步骤:

jobject
Java_com_example_ndktest_NDKTest_ImageRef(JNIEnv* env, jobject obj, jint width, jint height, jbyteArray myArray)
{
    ...
    jclass cls = (*env)->FindClass(env, "com/example/ndktest/NDKTest$Point");
    jmethodID constructor = (*env)->GetMethodID(env, cls, "<init>", "void(V)");
    jobject object = (*env)->NewObject(env, cls, constructor, obj, 5, 6);
    ...
}

2
这很有用,因为它展示了一个简洁的代码示例。但是,代码会失败,因为方法签名是错误的。它应该是(II)V,就像其他答案中列出的那样。 - ephemer
也就是说 obj, 5, 6... obj 不应该出现在这里,我认为(假设签名为 (II)V 就像问题中一样)。 - Asad-ullah Khan

0

我使用:

jclass clazz = env->FindClass ( "ISOInfoEx" );
jobject obj = env->AllocObject ( clazz );

这将创建一个类ISOInfoEx的对象。在我的情况下,我稍后在代码中将其用作结构。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接