React Native (Android):无法通过 JNI 在 jobject 中返回字符串

React Native (Android): Can't return String in a jobject via JNI

本文关键字:JNI jobject 字符串 返回 Native Android React      更新时间:2023-10-16

我试图(字面意思(弥合JavaScript(React Native 0.61.5(和Android C++之间的差距,以便使用OpenCV(3.2.0(。可能值得注意的是,我使用了已弃用的 NDK (16(,否则我无法使用gnustl_static.

问题所在

每当我尝试将String从本机C++代码返回到 Java 时,应用程序都会在本地崩溃。返回简单的数据类型(在我的示例中,int(不是问题。


开发环境

  • macOS 卡特琳娜版本10.15.4 (19E266)
  • React Native Version0.61.5
  • 安卓工作室版本4.0
    • NDK 版本16.1.4479499
    • CMake 版本3.6.4111459

源文件

Application.mk

APP_ABI := all
APP_OPTIM := release
APP_PLATFORM := android-8
APP_STL := gnustl_static
APP_CPPFLAGS := -std=c++11

index.js

JavaScript方面的事情,调用本机方法test()testWithString()

import {NativeModules, Platform} from "react-native";
const {RNSKOpenCVModule} = NativeModules // RNSKOpenCVModule.java
// success
RNSKOpenCVModule.test(Platform.OS)
.then((result) => console.warn (`RNSKOpenCVModule.test(${Platform.OS}) result: ` + JSON.stringify(result, null, 2)))
.catch(console.error)
// error
RNSKOpenCVModule.testWithString(Platform.OS)
.then((result) => console.warn (`RNSKOpenCVModule.testWithString(${Platform.OS}) result: ` + JSON.stringify(result, null, 2)))
.catch(console.error)

RNSKOpenCVModule.java

调用实际的C++方法并返回一个WritableMap,该可以用作JavaScript中的Object

package com.skizzo.opencv;
// imports
public class RNSKOpenCVModule extends ReactContextBaseJavaModule {
private static ReactApplicationContext reactContext;
RNSKOpenCVModule(ReactApplicationContext context) {
super(context);
reactContext = context;
}
static {
System.loadLibrary("native-lib"); // native-lib.cpp
}
@Override
public String getName() {
Log.w ("opencvtest", "RNSKOpenCVModule.java getName() called");
return "RNSKOpenCVModule"; // NativeModules.RNSKOpenCVModule
}
@ReactMethod
public void test(final String platform, final Promise promise) {
Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
promise.reject("Activity doesn't exist.");
return;
}
Log.w("RNSKOpenCVModule", "RNSKOpenCVModule.java.test()");
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
StructTestResult testResult = RNSKOpenCVNativeClass.test(platform);
WritableMap res = testResult.toWritableMap(); 
promise.resolve(res);
}
catch (Exception e) {
promise.reject("ERR", e);
}
}
};
AsyncTask.execute(runnable);
}
@ReactMethod
public void testWithString(final String platform, final Promise promise) {
Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
promise.reject("Activity doesn't exist.");
return;
}
Log.w("RNSKOpenCVModule", "RNSKOpenCVModule.java.testWithString()");
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
StructTestWithStringResult testWithStringResult = RNSKOpenCVNativeClass.testWithString(platform);
WritableMap res = testWithStringResult.toWritableMap(); 
promise.resolve(res);
}
catch (Exception e) {
promise.reject("ERR", e);
}
}
};
AsyncTask.execute(runnable);
}
}

native-lib.cpp

#include <android/log.h>
#include <jni.h>
#include <string>
#include <stdio.h>
#include <string.h>
#include <stdexcept>
#include <dirent.h>
#include <unistd.h>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <cmath>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
#define LOG_TAG "native-lib"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define PLATFORM_OPENCV_ANDROID
#ifdef PLATFORM_OPENCV_ANDROID
#define printf(...) __android_log_print (ANDROID_LOG_DEBUG, "SKOpenCv", __VA_ARGS__);
#endif
typedef struct {
int testInt;
} StructTestResult, *pStructTestResult;
typedef struct {
int testInt;
char* testString;
} StructTestWithStringResult, *pStructTestWithStringResult;
extern "C"
{
// StructTestResult
jclass STRUCT_TEST_RESULT;
jmethodID STRUCT_TEST_RESULT_CONSTRUCTOR;
jfieldID STRUCT_TEST_RESULT_TESTINT;
// StructTestWithStringResult
jclass STRUCT_TEST_WITH_STRING_RESULT;
jmethodID STRUCT_TEST_WITH_STRING_RESULT_CONSTRUCTOR;
jfieldID STRUCT_TEST_WITH_STRING_RESULT_TESTINT;
jfieldID STRUCT_TEST_WITH_STRING_RESULT_TESTSTRING;
jclass JAVA_EXCEPTION;
jint JNI_OnLoad (JavaVM *vm, void *reserved) { // called once onLoad
JNIEnv *env;
if (vm->GetEnv((void **) (&env), JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
// StructTestResult
STRUCT_TEST_RESULT              = (jclass) env->NewGlobalRef (env->FindClass("com/skizzo/opencv/StructTestResult"));
STRUCT_TEST_RESULT_CONSTRUCTOR  = env->GetMethodID (STRUCT_TEST_RESULT, "<init>", "(I)V");
STRUCT_TEST_RESULT_TESTINT      = env->GetFieldID (STRUCT_TEST_RESULT, "testInt", "I");
// StructTestWithStringResult
STRUCT_TEST_WITH_STRING_RESULT              = (jclass) env->NewGlobalRef (env->FindClass("com/skizzo/opencv/StructTestWithStringResult"));
STRUCT_TEST_WITH_STRING_RESULT_CONSTRUCTOR  = env->GetMethodID (STRUCT_TEST_WITH_STRING_RESULT, "<init>", "(ILjava/lang/String;)V");
STRUCT_TEST_WITH_STRING_RESULT_TESTINT      = env->GetFieldID (STRUCT_TEST_WITH_STRING_RESULT, "testInt", "I");
STRUCT_TEST_WITH_STRING_RESULT_TESTSTRING   = env->GetFieldID (STRUCT_TEST_WITH_STRING_RESULT, "testString", "Ljava/lang/String;");
// Exception
JAVA_EXCEPTION = (jclass)env->NewGlobalRef (env->FindClass("java/lang/Exception"));
return JNI_VERSION_1_6;
}
jobject JNICALL Java_com_skizzo_opencv_RNSKOpenCVNativeClass_test (JNIEnv *env, jobject instance, jstring platform) {
const char* platformStr = env->GetStringUTFChars (platform, NULL);
LOGD("platformStr: %s", platformStr); // "android"
StructTestResult testResult;
testResult.testInt = 123; // int -> OK
// turn the C struct back into a jobject and return it
return env->NewObject(STRUCT_TEST_RESULT, STRUCT_TEST_RESULT_CONSTRUCTOR, testResult.testInt);
}
jobject JNICALL Java_com_skizzo_opencv_RNSKOpenCVNativeClass_testWithString (JNIEnv *env, jobject instance, jstring platform) {
const char* platformStr = env->GetStringUTFChars (platform, NULL);
LOGD("platformStr: %s", platformStr); // "android"
StructTestWithStringResult testWithStringResult;
testWithStringResult.testInt = 456; // int -> OK
char* openCvVersion = CV_VERSION;
LOGD("openCvVersion: %s", openCvVersion);
testWithStringResult.testString = CV_VERSION; // adding this line produces the crash
// turn the C struct back into a jobject and return it
return env->NewObject(STRUCT_TEST_WITH_STRING_RESULT, STRUCT_TEST_WITH_STRING_RESULT_CONSTRUCTOR, testWithStringResult.testInt, testWithStringResult.testString);
}
}

StructTestResult.java

package com.skizzo.opencv;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
public class StructTestResult {
int testInt;
public StructTestResult () {}
public StructTestResult (int testInt) {
this.testInt = testInt;
}
public StructTestResult (ReadableMap map) {
this (
(int)map.getDouble("testInt")
);
}
public WritableMap toWritableMap() {
WritableMap map = Arguments.createMap();
map.putInt("testInt", this.testInt);
return map;
}
}

StructTestWithStringResult.java

package com.skizzo.opencv;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
public class StructTestWithStringResult {
int testInt;
String testString;
public StructTestWithStringResult () {}
public StructTestWithStringResult (int testInt, String testString) {
this.testInt = testInt;
// this.testString = "testFromJavaConstructor"; // success
this.testString = testString; // error
}
public StructTestWithStringResult (ReadableMap map) {
this (
(int)map.getDouble("testInt"),
map.getString("testString")
);
}
public WritableMap toWritableMap() {
WritableMap map = Arguments.createMap();
map.putInt("testInt", this.testInt);
map.putString("testString", this.testString);
return map;
}
}

非常感谢任何帮助,我真的被困在这里。谢谢!

testWithStringResult.testString是结构体的成员,属于字符数组类型。尚未分配针对变量的内存。您必须先分配内存,然后执行 strcpy(( 操作。这是您可以通过替换麻烦的行来尝试的代码块。

testWithStringResult.testString = new char(strlen(CV_VERSION));
strcpy(testWithStringResult.testString, CV_VERSION);

希望它能解决问题。如果没有,请提供崩溃日志。