【问题标题】:How to create an object with JNI?如何使用 JNI 创建对象?
【发布时间】:2011-11-07 18:58:45
【问题描述】:

我需要使用 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");
    }
}

当我尝试运行代码时,它不起作用。

【问题讨论】:

  • 请解释“不起作用”。
  • 虽然您的意思可能是“分别”,但我确实认为尊重您的对象很重要。 :)
  • @quasimodo 你是对的。 :) 我编辑了错误,谢谢。

标签: java android java-native-interface android-ndk


【解决方案1】:

我用:

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

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

【讨论】:

    【解决方案2】:

    在 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);
          ...
      }
      

      【讨论】:

      • 这很有用,因为它显示了一个简洁的代码示例。但是代码会失败,因为方法签名是错误的。它应该是"(II)V",如其他答案中所列。
      【解决方案4】:

      规范是正确的,但在这种情况下有点误导。 GetMethodID 需要方法名称和方法签名。 specification says

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

      注意它说的是返回类型,而不是签名。尽管void(V) 表面上看起来与签名相似,但规范告诉您签名必须指定一个void(即V)返回类型。

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

      【讨论】:

        【解决方案5】:

        您的代码存在一些问题。

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

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

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

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

        附带说明一下,我发现将原始类型和原始类型数组从 NDK 传递到 Java 更加简洁。对象创建/访问很混乱,很难调试。

        【讨论】:

        • @Hennig Makholm 例如,我创建了自己的 Point 类。它可能是 foo。我将我的班级改为顶级,将“void(V)”改为“(II)V”,现在它可以工作了。
        • 文档说:“这个ID必须通过调用GetMethodID()来获得,作为方法名,void(V)作为返回类型。”当我第一次阅读它时,我也对此感到困惑;我以为它的意思是“使用签名void(V),但实际上它的意思是“在构造签名时使用类型代码V(代表无效)”。当然void(V) 看起来像一个奇怪的签名,但是"&lt;init&gt;" 是一个指定构造函数的奇怪方式。无论如何,当一切都很神奇时,令人困惑的东西真的很令人困惑!docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html
        • 您实际上可以创建非静态嵌套类的实例,该方法只是有点不明显:嵌套类的所有构造函数都有一个外部类类型的隐式第一个参数。所以对于上面的例子,Point 类是非静态的,构造函数签名是"&lt;init&gt;", "(Lcom/example/ndktest/NDKTest;II)V"。您可以通过在构建后从您的类目录中运行 javap -s -p com.example.ndktest.NDKTest$Point 来看到这一点。
        • @benkc 这个评论救了我。一个非常重要的细节。
        【解决方案6】:

        由于Point 是一个内部类,获取它的方法是

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

        内部类的$ 约定在权威规范中并没有真正清楚地记录在案,但它根植于如此多的工作代码中,因此不太可能改变。不过,如果您将 JNI 代码限制为使用顶级类,它会感觉更加健壮。

        您需要一个将两个整数作为参数的构造函数。签名是(II)V,所以:

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

        下一次,在你的代码中加入一些错误处理,这样你就会知道哪一部分不起作用!

        【讨论】:

        • 注意:这个答案不起作用。请参阅下面 Seva 答案中的 cmets 了解原因(构造函数参数必须包含外部类)
        • @Colin:是的,我忽略了Point 不是静态的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-01
        • 2020-07-02
        • 1970-01-01
        相关资源
        最近更新 更多