C语言调用Java

说明

这是一个从【C➡️Java】的文章,并非是从【Java➡️C】。

编写Java

随便使用IDEA创建一个gradle的kotlin程序,写下一个方法

package org.example

object Main {
    @JvmStatic
    fun diff(str1: String, str2: String): String {
        return "Hello";
    }
}

修改gradle.build使用全量编译:

jar {
    from { configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
}

编写C++

注意:这里的java.hjava.cpp是为自定义的文件名称,不是系统文件!

java.h

#include <jni.h>
#include <stdio.h>
#include <string>

/**
 * 初始化Java环境,只能调用一次
 * 
 * 
 */
JNIEnv* _java_create_jvm(JavaVM **jvm);

/**
 * 字符串进行比对
 * 
 */
std::string _java_diff(JNIEnv* env, std::string str1, std::string str2); 

/**
 * jstring 转换为 std::string
 * 
 */
std::string _java_jstring2string(JNIEnv *env, jstring jstr);

java.cpp

#include "java.h"

JNIEnv *_java_create_jvm(JavaVM **jvm)
{
    JNIEnv *env;
    JavaVMInitArgs args;
    JavaVMOption options;
    // ⬇️ 这里为上面gradlew程序构建的jar文件
    // ⬇️ 使用 [gradlew build] 构建在 build/libs/ 下面
    options.optionString = (char *)"-Djava.class.path=untitled2-1.0.0-SNAPSHOT.jar";
    args.version = JNI_VERSION_1_8;
    args.nOptions = 1;
    args.options = &options;
    args.ignoreUnrecognized = 0;
    JNI_CreateJavaVM(jvm, (void **)&env, &args);
    return env;
}

std::string _java_diff(JNIEnv *env, std::string pdf, std::string img)
{
    // 查找Java中的类,具体就是jni
    jclass clazz = env->FindClass("org/example/Main");
    jmethodID method = env->GetStaticMethodID(clazz, "diff", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
    jstring jstr = (jstring)env->CallStaticObjectMethod(clazz, method, env->NewStringUTF(pdf.c_str()), env->NewStringUTF(img.c_str()));
    return _java_jstring2string(env, jstr);
}


std::string _java_jstring2string(JNIEnv *env, jstring jstr) {
    const jclass stringClass = env->GetObjectClass(jstr);
    const jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B");
    const jbyteArray stringJbytes = (jbyteArray) env->CallObjectMethod(jstr, getBytes, env->NewStringUTF("UTF-8"));

    size_t length = (size_t) env->GetArrayLength(stringJbytes);
    jbyte* pBytes = env->GetByteArrayElements(stringJbytes, NULL);

    std::string ret = std::string((char *)pBytes, length);
    env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT);

    env->DeleteLocalRef(stringJbytes);
    env->DeleteLocalRef(stringClass);
    return ret;
}

main.cpp

#include "java.h"

int main()
{
    JavaVM *jvm;
    JNIEnv *env = _java_create_jvm(&jvm);
    if (env == NULL)
        return 1;
    std::string result = _java_diff(env, "1.pdf", "2.jpg");
    printf(result.c_str());
    printf("\n");
}

编译C++

我这里使用的是cmake来编译,具体编译脚本如下:

# 最低cmake版本
cmake_minimum_required(VERSION 3.7)

# 项目名称
project(main)

# 设置C++标准为 C++ 14
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS  "-Wall -static-libstdc++")

# 查找Java和Jni依赖
# 此处可不引用Java,因为没有用Java功能
find_package(Java 1.8 REQUIRED)
find_package(JNI 1.8 REQUIRED)

# 打印JNI相关信息 可删除
if (JNI_FOUND)
    message (STATUS "JNI_INCLUDE_DIRS=${JNI_INCLUDE_DIRS}")
    message (STATUS "JNI_LIBRARIES=${JNI_LIBRARIES}")
endif()

# 导入Java和Jni头文件
include(UseJava)
include_directories(${JNI_INCLUDE_DIRS})

# 需要编译的源码文件
set(SOURCE_FILES main.cpp java.cpp)

# 使用 -ljli 代替 -ljvm 避免提示使用Java6
set(JLI_LIBRARY "$ENV{JAVA_HOME}/jre/lib/jli/libjli.dylib")

# 构建可执行程序
add_executable(${PROJECT_NAME} ${SOURCE_FILES})

# !! 连接jni库 !!
target_link_libraries(${PROJECT_NAME} ${JLI_LIBRARY} ${JNI_LIBRARIES})

运行

使用cmake编译之后,可以在build目录下生出mian的执行文件,将jar文件复制到同目录下,执行./mian就可看到效果。

MacBook-Pro:build sollyu$ ./main 
Hello

代码无需开源了,上面就是全部了。

问题

整体文章已经结束了,下面内容是过程中碰到的问题,也随手记录下来,希望能帮助后来人。

问题1

因为对cmake文件的不熟悉,刚开始出现了下面的报错信息:

[build] Undefined symbols for architecture x86_64:
[build]   "_JNI_CreateJavaVM", referenced from:
[build]       _java_create_jvm in java.c.o
[build] ld: symbol(s) not found for architecture x86_64
[build] collect2: error: ld returned 1 exit status
[build] ninja: build stopped: subcommand failed.

百度和谷歌了半天也没有找到解决办法,都是在说丢失libjvm.dylib,但是我明明已经连接了,却还是报错。最后还是在stackoverflow里找到了解决办法。

target_link_libraries(<example> ${JNI_LIBRARIES})

问题2

在执行build/main的时候出现下面的错误:

# 执行出现错误
$ build/main
No Java runtime present, requesting install.

# 查看Java版本
$ java -version
openjdk version "1.8.0_212-release"
OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)
OpenJDK 64-Bit Server VM (build 25.212-b4-5784211, mixed mode)

# 查看JAVA_HOME
# 因为我想用Java8 偷懒直接用AndroidStudio里自带的JDK
$ echo $JAVA_HOME
/Users/sollyu/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/192.6392135/Android Studio.app/Contents/jre/jdk/Contents/Home

同时电脑还弹出一个对话框:

您需要安装旧Java SE 6运行环境才能打开“此Java应用程序”。
点按“更多信息…”来访问旧Java SE 6下载网站。

尝试点击「更多信息…」,会下载一个安装包,但是依然无法安装:


最后在这里找到了怎么解决stackoverflow,使用-ljli代替-ljvm,上面的cmake中写了。

参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。