React Native (Android): не удается вернуть строку в проекте через JNI - PullRequest
0 голосов
/ 17 июня 2020

Я пытаюсь (буквально) преодолеть разрыв между JavaScript (React Native 0.61.5) и C ++ для Android, чтобы использовать OpenCV (3.2.0). Примечательно, что я использую устаревший NDK (16), потому что в противном случае я не могу использовать gnustl_static.

Проблема

Всякий раз, когда я пытаюсь вернуть String из моего родного C ++ код на Java, приложение вылетает на собственной стороне. Однако возврат простых типов данных (в моем примере int) не является проблемой.


Dev Environment

  • macOS Catalina Version 10.15.4 (19E266)
  • Версия React Native 0.61.5
  • Android Версия Studio 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, которое можно использовать как Object в JavaScript.

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;
  }
}

Любая помощь приветствуется, я действительно застрял здесь. Спасибо!

1 Ответ

0 голосов
/ 17 июня 2020

testWithStringResult.testString является членом структуры и имеет тип символьного массива. Память для переменной еще не выделена. Сначала вам нужно выделить память, а затем выполнить операцию strcpy (). Вот блок кода, который вы можете попробовать, заменив вызывающую проблемы строку.

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

Надеюсь, это решит проблему. Если нет, предоставьте журнал cra sh.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...