JNI Javaを呼び出してネイティブにコールバックする方法、およびJVM stdioを取得する方法

Aug 18 2020

私はWindowsの開発者ですが、WindowsLibを使用していません。

最初にJavaを実行しSysten.load()、次にネイティブメソッドを呼び出す「イニシアチブ」モードのJNI 。または「パッシブ」モード。実行可能ファイルはJVMJNI_CreateJavaVMを作成してから、Javaメソッドを呼び出します。

今、私はJNISDKを使用してC ++プログラムを作成しようとしています。そのため、これは単一の実行可能ファイルである必要があり、dllはありませんSystem.load()

最初にHelloWorldを作成します。

public static native int nativeSum(int a, int b);

public static int sum(int a, int b) {
    return nativeSum(a, b);
}

実行すると、javah Driverこのヘッダーが定義されます

JNIEXPORT jint JNICALL Java_Driver_nativeSum (JNIEnv *, jclass, jint, jint);

javap -s Driver正しい名前を使用していることを確認して実行します

  public static native int nativeSum(int, int);
    descriptor: (II)I

  public static int sum(int, int);
    descriptor: (II)I

main.cppを書く

#include <iostream>
#include "jni.h"
#include "Driver.h"


JNIEXPORT jint JNICALL Java_Driver_nativeSum(JNIEnv*, jclass, jint a, jint b) {
    std::cout << "Native invoked " << std::endl;
    return a + b;
}


int main() {
    
    JavaVMInitArgs vm_args;
    
    vm_args.version = JNI_VERSION_1_8;
    vm_args.ignoreUnrecognized = true;
    vm_args.nOptions = 1;
    
    auto* options = new JavaVMOption[1];
    
    std::string cmd = "-Djava.class.path=../class/out/production/class";
    
    options[0].optionString = const_cast<char*>(cmd.c_str());
    
    vm_args.options = options;
    
    JavaVM* jvm;
    JNIEnv* env;
    
    jint rc = JNI_CreateJavaVM(&jvm, (void**) &env, &vm_args);

    delete[] options;


    // ==========================================================

    _jclass* jClass_Driver = env->FindClass("Driver");

    _jmethodID* jMethod_Driver_sum = env->GetStaticMethodID(
            jClass_Driver,
            "sum",
            "(II)I"
    );

    std::cout << "Test-sum method id = " << jMethod_Driver_sum << std::endl;

    long jResult_Driver_sum = env->CallStaticIntMethod(
            jClass_Driver,
            jMethod_Driver_sum,
            1, 1
    );

    std::cout << "Test-sum Method called res - "
              << jResult_Driver_sum
              << std::endl;


    // ==========================================================



    jvm->DestroyJavaVM();


    return 0;

}

結果:

VM created
Test-sum method id = 0x1ebf4888
Test-sum Method called res - 0

Process finished with exit code 0

まあ、1 + 1 = 0、それは絶対に意味がありません。

そして、私が使用してみてくださいSystem.out/errtry catchissusを見つけるが、同じ結果を得ると、その事はしても、Javaの例外、あるいはC ++でキャッチすることはできませんtry catch (...)

public static int sum(int a, int b) {
    try {
        return nativeSum(a, b);
    } catch (Exception exception) {
        return -1;
    }
}

次に、他の間違いがないことを確認します。ネイティブをバイパスします。

public static int sum(int a, int b) {
    return 1234;
}

かなりうまく機能して、C ++コンソールで1234の値を取得しました。

※最初の質問:

JVM stdioストリームを取得する方法は?System.out/err.print「イニシアチブ」コンソールには表示されません。ただし、DLL std printは、「パッシブ」モードの場合、Javaコンソールで印刷されます。

※2番目の質問:

ネイティブコールではどうなりますか?0の結果が得られないはずです、どうすれば修正できますか?目標を達成する方法は?

BYW-意味がありませんが、試してみてください。を使用CallObjectMethodすると同じ結果が得られ、を使用GetMethodIDするとID 0が返され、1073741819(0xC0000005)で長いスタック終了が返されます。

アップデート1:

jmethodID jMethod_Driver_nativeSum = env->GetStaticMethodID(
    jClass_Driver,
    "nativeSum",
    "(II)I"
);

std::cout << jMethod_Driver_nativeSum << std::endl;

jint jResult_Driver_nativeSum = env->CallStaticIntMethod(
    jClass_Driver,
    jMethod_Driver_sum,
    1, 1
);

std::cout << jResult_Driver_nativeSum << std::endl;

この出力を得た

method id = 0x1ec97350
method result = 0

アップデート2:

Cやタールを外部に出さないようにするために、関数本体をhで記述します。

#include <jni.h>

/*
 * Class:     Driver
 * Method:    nativeSum
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_Driver_nativeSum(JNIEnv* env, jclass cls, jint a, jint b) {
    std::cout << "Java_Driver_nativeSum invoked" << std::endl;
    return a + b;
}

そして、ツールを使用して、関数名が正しいことを確認します

そしてenv->ExceptionOccurred、例外を取得するために使用します。そして、1つあります。

java.lang.UnsatisfiedLinkError: Driver.nativeSum(II)I

そしてexter "C" {}、ブロックの有無にかかわらず、どれも機能していませんUnsatisfiedLinkError。結果0および。としてすべて失敗しました。

したがって、exeファイル内のネイティブの必須関数でさえ、jvmはそれを見つけることができないと思います。

今の状況は:

私のC ++プログラムがmainエントリであり、プラグイン開発者向けのjavaSDKを作成します。

実行時に、C ++はJVMを作成し、Javaクラスをロードし、イベント時にJavaメソッドを呼び出し、プラグインはネイティブを使用して「何かを行う」ので、どうすればよいですか?

そして私もやってみます

public static int sum(int a, int b) {
    return a + b;
}

私は2を得ました、それはうまく働いています。唯一の問題は、Java呼び出しのネイティブです。

回答

AlexCohn Aug 19 2020 at 14:16

ネイティブメソッドにアクセスするには、引き続きを呼び出す必要がありますSystem.LoadLibrary()。スペックは、あなたのことを説明しDriver.javaが含まれているはずです。

public class Driver {
  static { System.loadLibrary("driver"); } // this name must be matched!
  public static native int nativeSum(int a, int b);

  public static int sum(int a, int b) {
    return nativeSum(a, b);
  }
}

そしてあなたのmain.cppで

extern "C" JNIEXPORT jint JNICALL Java_Driver_nativeSum(JNIEnv*, jclass, jint a, jint b) {
  std::cout << "Native invoked " << std::endl;
  return a + b;
}

extern "C" JNIEXPORT jint JNI_OnLoad_driver // this suffix must match the name used in Java
                                           (JavaVM *vm, void *reserved) {
  std::cout << "Native loaded" << std::endl;
  return JNI_VERSION_1_8;
}

リンカが両方Java_Driver_nativeSumを保持JNI_OnLoad_driverし、バイナリにエクスポートされていることを確認してください。

最初の質問に関しては、個別のJVM stdioストリームはありません。Javaは同じものから読み取り、他のすべてfd=0と同じfd=1ように書き込みます。