JNI วิธีเรียก java และโทรกลับเนทีฟและวิธีรับ JVM std io

Aug 18 2020

ฉัน dev ใน windows แต่ไม่ได้ใช้ windows lib

โหมด "ความคิดริเริ่ม" JNI ซึ่งเรียกใช้ java ก่อนSysten.load()แล้วใช้เรียกวิธีเนทีฟ หรือโหมด "passive" ตัวเรียกใช้งานสร้าง JVM JNI_CreateJavaVMจากนั้นเรียกใช้เมธอด java

ตอนนี้ผมกำลังพยายามทำให้โปรแกรม C ++ กับ JNI SDK ดังนั้นสิ่งที่จะต้องปฏิบัติการเดียว, ไม่มี dll System.load()สำหรับ

เขียนสวัสดีชาวโลกเป็นครั้งแรก:

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/errและtry catchพบว่าเมืองอิสซัส แต่ได้ผลเหมือนกันว่าสิ่งที่แม้จะไม่สามารถจับโดยยกเว้น Java หรือแม้กระทั่ง try catch (...)C

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

ทำงานได้ดีฉันได้รับค่า 1234 ในคอนโซล C ++

※คำถามแรก:

วิธีรับสตรีม JVM stdio System.out/err.printจะไม่แสดงในคอนโซล "ความคิดริเริ่ม" แต่การพิมพ์ DLL std จะพิมพ์ในคอนโซล java เมื่อโหมด "passive"

※คำถามที่สอง:

เกิดอะไรขึ้นในการเรียกพื้นเมือง? ฉันไม่ควรได้รับ 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เพื่อรับข้อยกเว้นและมีอย่างใดอย่างหนึ่ง:

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

และไม่มีใครมีหรือไม่มีexter "C" {}บล็อกทำงานทั้งหมดล้มเหลวเป็นผล UnsatisfiedLinkError0

ดังนั้นฉันคิดว่าแม้แต่ฟังก์ชันที่จำเป็นดั้งเดิมในไฟล์ exe แต่ jvm ก็หาไม่เจอ

ตอนนี้สถานการณ์คือ:

โปรแกรม C ++ ของฉันคือmainรายการและเขียน java SDK สำหรับนักพัฒนาปลั๊กอิน

ในรันไทม์ C ++ สร้าง JVM, Load java class, Invoke java method when event, and plugin use native to "do something" ดังนั้นจะทำอย่างไร?

และฉันก็ลองดู

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

ฉันได้ 2 ซึ่งใช้งานได้ดี ปัญหาเดียวคือ java call native

คำตอบ

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ข้ออื่น ๆ ทั้งหมด