1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C言語コードからJava Native Interface(JNI)を使い、Javaメソッドを呼び出す

Last updated at Posted at 2025-07-13

概要

C言語のソースコードからJavaメソッドを実行し、結果を取得します。
取得する戻り値の型は、getArrayメソッドからのString[]をC言語のchar[][30]で、
cowSayメソッドからのString型をC言語のchar*として取得します。

実行環境

  • Windows 11
  • WSL(Ubuntu-24.04)

フォルダ構成

.
├── Moo.class
├── Moo.java
├── c_linux_build.bat
├── c_windows_build.bat
├── java_build.bat
├── javap-exec.bat
├── jni_exec
├── jni_exec.c
├── jni_exec.exe
├── lib
│   └── cowsay-1.1.0.jar
├── start_linux_exec.bat
└── start_windows_exec.bat

Cから呼び出す用のJavaコード

Moo.java
import com.github.ricksbrown.cowsay.Cowsay;
import java.util.Random;

public class Moo {
    /*
     * mainは使用しない
     */
    public static void main(String[] args) {
        String[] arr = getArray();
        System.out.println(String.join(", ", arr));
        var num = new Random().nextInt(arr.length);
        System.out.println(cowSay(arr[num] + "said ,Hello from Java!", arr[num]));
    }

    /*
     * 話す動物の一覧を取得
     */
    public static String[] getArray() {
        String[] args = new String[] { "-l" };
        return Cowsay.say(args).trim().split(" ");
    }

    /*
     * 動物に話させる
     */
    public static String cowSay(String message, String animal) {
        String[] args = new String[] { "-f", animal, message };
        return Cowsay.say(args);
    }
}
  • 外部ライブラリも使えることを確認するために適当にCowsayライブラリを利用

Javaを呼び出すCコード

jni_exec.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <jni.h>

int animal_list(char*, char[][30]);
void cowsay(char*, char*, char*);

int main() {
    char sep = ':';
#ifdef _WIN32
    // Windows用
    sep = ';';
#endif
    char java_classpath[1024];
    char *classpath;
    if ((classpath = getenv("CLASSPATH")) == 0) {
        puts("環境変数CLASSPATHがありません");
        return -1;
    }

    // 外部ライブラリを利用するためには、JVM起動設定に指定する必要がある。
    // ライブラリ情報は、環境変数CLASSPATHの値を利用する。
    sprintf(java_classpath, "-Djava.class.path=.%c%s", sep, classpath);
    printf("CLASSPATH: %s\n", java_classpath);

    char arr[50][30];
    int max_idx = animal_list(java_classpath, arr);

    int i;
    char output[2048];
    srand(time(NULL));
    // ランダムに10個表示
    for (i = 0; i < 10; i++) {
        memset(output, 0, sizeof(output));
        cowsay(java_classpath, arr[rand() % max_idx], output);
        printf("<<Result from Java>>\n%s\n", output);
    }

    return 0;
}

int animal_list(char* classpath, char result[][30]) {
    JavaVM *jvm;
    JNIEnv *env;
    JavaVMInitArgs vm_args;
    JavaVMOption options[3]; // options[5];
    options[0].optionString = classpath;         /* クラスパス指定 */
    options[1].optionString = "-Xss32m";         /* セグメンテーション違反防止 */
    options[2].optionString = "-Xint";           /* JIT無効化 */
    // options[3].optionString = "-Xcheck:jni";  /* オプションのチェック */
    // options[4].optionString = "-verbose:jni"; /* JNI関係のメッセージを表示 */

    vm_args.options = options;
    vm_args.nOptions = 3;    // 5
    vm_args.version = JNI_VERSION_21;
    vm_args.ignoreUnrecognized = JNI_FALSE;

    int ret, nVMs;
    ret = JNI_GetCreatedJavaVMs(&jvm, 1, &nVMs);
    if (ret == JNI_OK && nVMs >= 1) {
        // 生成済みのJMを再利用(JMは一つだけ、Create->Destory->Createは許されない)
        puts("<VM再利用>");
        ret = (*jvm)->AttachCurrentThread(jvm, (void **)&env, &vm_args);
    } else {
        puts("<VM新規作成>");
        ret = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
    }
    if (ret != JNI_OK)
        exit(-1);

    jclass cls;
    // Mooクラスを取得
    if ((cls = (*env)->FindClass(env, "Moo")) == 0) {
        (*env)->ExceptionDescribe(env);
        exit(-2);
    }

    // getArrayメソッドを取得
    jmethodID id = (*env)->GetStaticMethodID(
             env, cls, "getArray",
             "()[Ljava/lang/String;");
    if (id == 0) {
        (*env)->ExceptionDescribe(env);
        exit(-3);
    }

    jarray jobj;
    // メソッドを実行。引数無しの場合はNULL(=(void*)0)を指定する
    if ((jobj = (jarray)(*env)->CallStaticObjectMethod(env, cls, id, 0)) == 0) {
        (*env)->ExceptionDescribe(env);
        exit(-4);
    }

    // 戻り値の配列の要素数を取得
    int array_length = (*env)->GetArrayLength(env, jobj);

    int i;
    for (i = 0; i < array_length; i++) {
        // java.lang.String[]から値を取り出し
        jstring str = (jstring)(*env)->GetObjectArrayElement(env, jobj, i);

        // java.lang.Stringをchar*に変更し取得
        const char *native_string = (*env)->GetStringUTFChars(env, str, 0);
        memset(result[i], 0, strlen(native_string) + 1);
        strncpy(result[i], native_string, strlen(native_string));

        // ネイティブメモリでメモリリークを起こすため解放
        (*env)->ReleaseStringUTFChars(env, str, native_string);
    }
    return array_length;
}

void cowsay(char* classpath, char* animal, char* result) {
    JavaVM *jvm;
    JNIEnv *env;
    JavaVMInitArgs vm_args;
    JavaVMOption options[3];
    options[0].optionString = classpath;         /* クラスパス指定 */
    options[1].optionString = "-Xss32m";         /* セグメンテーション違反防止 */
    options[2].optionString = "-Xint";           /* JIT無効化 */
    // options[3].optionString = "-Xcheck:jni";  /* オプションのチェック */
    // options[4].optionString = "-verbose:jni"; /* JNI関係のメッセージを表示 */

    vm_args.options = options;
    vm_args.nOptions = 3;
    vm_args.version = JNI_VERSION_21;
    vm_args.ignoreUnrecognized = JNI_FALSE;

    int ret, nVMs;
    ret = JNI_GetCreatedJavaVMs(&jvm, 1, &nVMs);
    if (ret == JNI_OK && nVMs >= 1) {
        // 生成済みのJMを再利用(JMは一つだけ、Create->Destory->Createは許されない)
        puts("<VM再利用>");
        ret = (*jvm)->AttachCurrentThread(jvm, (void **)&env, &vm_args);
    } else {
        puts("<VM新規作成>");
        ret = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
    }
    if (ret != JNI_OK)
        exit(-1);

    jclass cls;
    // Mooクラスを取得
    if ((cls = (*env)->FindClass(env, "Moo")) == 0) {
        (*env)->ExceptionDescribe(env);
        exit(-2);
    }

    // getArrayメソッドを取得
    jmethodID id = (*env)->GetStaticMethodID(
             env, cls, "cowSay",
             "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
    if (id == 0) {
        (*env)->ExceptionDescribe(env);
        exit(-3);
    }

    char message[100];
    sprintf(message, "%s said, Hello from JNI!", animal);
    jstring arg1 = (*env)->NewStringUTF(env, message);
    jstring arg2 = (*env)->NewStringUTF(env, animal);

    // メソッド実行。jstringはjvalueに入れることができないため、1つずつ引数に指定する。
    jstring str;
    if ((str = ((jstring)(*env)->CallStaticObjectMethod(env, cls, id, arg1, arg2))) == 0) {
        (*env)->ExceptionDescribe(env);
        exit(-4);
    }

    // java.lang.Stringをchar*に変更
    const char *native_string = (*env)->GetStringUTFChars(env, str, 0);
    strncpy(result, native_string, strlen(native_string));

    // ネイティブメモリでメモリリークを起こすため解放
    (*env)->ReleaseStringUTFChars(env, str, native_string);
}

ビルド手順

共通手順

必要なパッケージインストール

  • JDK

Javaコンパイル用のバッチ

下記のビルドを成功させるためには、JDKのインストール、及び
batファイル用のフォルダ内にlibフォルダを作り、
その中にcowsay-1.1.0.jarを入れる必要があります。

.
└── lib
    └── cowsay-1.1.0.jar

Windows環境側のjavacによりコンパイル

java_build_windows.bat
chcp 65001
set JAVA_HOME=C:\Program Files\Java\jdk-24
set PATH=%JAVA_HOME%\bin;%PATH%
@rem WSL側のOpenJDKが21のため、21を指定
javac --release 21 -proc:none -cp .;lib/cowsay-1.1.0.jar Moo.java
pause
java -cp .;lib/cowsay-1.1.0.jar Moo
pause

WSL環境側のjavacによりコンパイル

java_build_linux.bat
chcp 65001
set command=export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64;^
 export PATH=$JAVA_HOME/bin:$PATH;^
 javac --release 21 -proc:none -cp .:lib/cowsay-1.1.0.jar Moo.java;
wsl -e bash -l -c "%command%"
pause
wsl -e bash -l -c "java -cp .:lib/cowsay-1.1.0.jar Moo;"
pause

bash -lオプションを利用すると~/.bash_profileが読まれるようになります。
今回の例では、どの環境変数を使うかを明示するために全て記載していますが、
~/.bash_profile内でexportすれば、バッチ側に書く必要が無くなります。

Javaを単独で動かした場合の結果
beavis.zen, bud-frogs, bunny, cheese, cower, daemon, default, dragon, dragon-and-cow, elephant, elephant-in-snake, eyes, flaming-sheep, ghostbusters, hellokitty, kiss, kitty, koala, kosh, luke-koala, meow, milk, moofasa, moose, mutilated, ren, satanic, sheep, skeleton, small, squirrel, stegosaurus, stimpy, supermilker, surgery, telebears, three-eyes, turkey, turtle, tux, udder, vader, vader-koala, www
 ____________________________________
< ghostbusterssaid ,Hello from Java! >
 ------------------------------------
          \
           \
            \          __---__
                    _-       /--______
               __--( /     \ )XXXXXXXXXXX\v.
             .-XXX(   O   O  )XXXXXXXXXXXXXXX-
            /XXX(       U     )        XXXXXXX\
          /XXXXX(              )--_  XXXXXXXXXXX\
         /XXXXX/ (      O     )   XXXXXX   \XXXXX\
         XXXXX/   /            XXXXXX   \__ \XXXXX
         XXXXXX__/          XXXXXX         \__---->
 ---___  XXX__/          XXXXXX      \__         /
   \-  --__/   ___/\  XXXXXX            /  ___--/=
    \-\    ___/    XXXXXX              '--- XXXXXX
       \-\/XXX\ XXXXXX                      /XXXXX
         \XXXXXXXXX   \                    /XXXXX/
          \XXXXXX      >                 _/XXXXX/
            \XXXXX--__/              __-- XXXX/
             -XXXXXXXX---------------  XXXXXX-
                \XXXXXXXXXXXXXXXXXXXXXXXXXX/
                  ""VXXXXXXXXXXXXXXXXXXV""


クラス定義を変えた場合のシグニチャー取得バッチ(GetStaticMethodID用)

シグニチャー取得バッチ
javap-exec.bat
javap -s *.class
pause
  • 結果
Compiled from "Moo.java"
public class Moo {
  public Moo();
    descriptor: ()V

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V

  public static java.lang.String[] getArray();
    descriptor: ()[Ljava/lang/String;

  public static java.lang.String cowSay(java.lang.String, java.lang.String);
    descriptor: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
}

Windowsのみの手順

必要なパッケージの配置

  • llvm-mingw

今回はCドライブ配下に配置します。

Cコンパイルのバッチ

c_windows_build.bat
chcp 65001
set JAVA_HOME=C:\Program Files\Java\jdk-24
C:\llvm-mingw-20250709-msvcrt-x86_64\bin\x86_64-w64-mingw32-gcc.exe -Wall ^
-I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" ^
-L"%JAVA_HOME%\bin\server" -ljvm ^
-lpthread ^
-o jni_exec jni_exec.c
pause

Linuxのみの手順

WSLの設定(例)

WSL2とUbuntu 24.04のインストール手順だけで日本語化まではしなくて良いです。

必要なパッケージインストール

sudo apt install gcc openjdk-21-jdk

Cコンパイル用のバッチ

c_linux_build.bat
chcp 65001
set command=export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64;^
 export LD_LIBRARY_PATH=$JAVA_HOME/lib/server;^
 gcc -Wall^
 -I$JAVA_HOME/include -I$JAVA_HOME/include/linux^
 -lpthread^
 -o jni_exec jni_exec.c ^
 -L$LD_LIBRARY_PATH -ljvm;
wsl -e ^
bash -l -c "%command%"
pause

実行手順

Windowsで実行

start_windows_exec.bat
chcp 65001
set CLASSPATH=%~dp0lib\cowsay-1.1.0.jar
set PATH=%JAVA_HOME%\bin\server;%PATH%
jni_exec.exe
echo ERRORLEVEL: %ERRORLEVEL%
pause

WSL(Windows Subsystem for Linux)で実行

start_linux_exec.bat
chcp 65001
set command=export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64;^
 export PATH=$JAVA_HOME/bin:$PATH;^
 export LD_LIBRARY_PATH=$JAVA_HOME/lib/server;^
 export CLASSPATH=$(realpath $(dirname $0))/lib/cowsay-1.1.0.jar;^
 ./jni_exec;^
 echo ERRORLEVEL: $?
wsl -e ^
bash -l -c "%command%"
pause

実行結果(例)

結果
CLASSPATH: -Djava.class.path=.:/mnt/c/path/to/jar-folder/lib/cowsay-1.1.0.jar
<VM新規作成>
<VM再利用>
<<Result from Java>>
 __________________________________
< three-eyes said, Hello from JNI! >
 ----------------------------------
        \  ^___^
         \ (ooo)\_______
           (___)\       )\/\
                ||----w |
                ||     ||

<VM再利用>
<<Result from Java>>
 ___________________________________
< supermilker said, Hello from JNI! >
 -----------------------------------
  \   ^__^
   \  (oo)\_______        ________
      (__)\       )\/\    |Super |
          ||----W |       |Milker|
          ||    UDDDDDDDDD|______|

<VM再利用>
<<Result from Java>>
 _____________________________
< udder said, Hello from JNI! >
 -----------------------------
  \
   \    (__)
        o o\
       ('') \---------
          \           \
           |          |\
           ||---(  )_|| *
           ||    UU  ||
           ==        ==

<VM再利用>
<<Result from Java>>
 _____________________________________
< flaming-sheep said, Hello from JNI! >
 -------------------------------------
  \            .    .     .
   \      .  . .     `  ,
    \    .; .  : .' :  :  : .
     \   i..`: i` i.i.,i  i .
      \   `,--.|i |i|ii|ii|i:
           UooU\.'@@@@@@`.||'
           \__/(@@@@@@@@@@)'
                (@@@@@@@@)
                `YY~~~~YY'
                 ||    ||

<VM再利用>
<<Result from Java>>
 ___________________________________
< vader-koala said, Hello from JNI! >
 -----------------------------------
   \
    \        .
     .---.  //
    Y|o o|Y//
   /_(i=i)K/
   ~()~*~()~
    (_)-(_)

     Darth
     Vader
     koala

<VM再利用>
<<Result from Java>>
 ___________________________________
< stegosaurus said, Hello from JNI! >
 -----------------------------------
\                             .       .
 \                           / `.   .' "
  \                  .---.  <    > <    >  .---.
   \                 |    \  \ - ~ ~ - /  /    |
         _____          ..-~             ~-..-~
        |     |   \~~~\.'                    `./~~~/
       ---------   \__/                        \__/
      .'  O    \     /               /       \  "
     (_____,    `._.'               |         }  \/~~~/
      `----.          /       }     |        /    \__/
            `-.      |       /      |       /      `. ,~~|
                ~-.__|      /_ - ~ ^|      /- _      `..-'
                     |     /        |     /     ~-.     `-. _  _  _
                     |_____|        |_____|         ~ - . _ _ _ _ _>

<VM再利用>
<<Result from Java>>
 ___________________________
< ren said, Hello from JNI! >
 ---------------------------
   \
    \
    ____
   /# /_\_
  |  |/o\o\
  |  \\_/_/
 / |_   |
|  ||\_ ~|
|  ||| \/
|  |||_
 \//  |
  ||  |
  ||_  \
  \_|  o|
  /\___/
 /  ||||__
    (___)_)

<VM再利用>
<<Result from Java>>
 _________________________________
< bud-frogs said, Hello from JNI! >
 ---------------------------------
     \
      \
          oO)-.                       .-(Oo
         /__  _\                     /_  __\
         \  \(  |     ()~()         |  )/  /
          \__|\ |    (-___-)        | /|__/
          '  '--'    ==`-'==        '--'  '

<VM再利用>
<<Result from Java>>
 __________________________________
< three-eyes said, Hello from JNI! >
 ----------------------------------
        \  ^___^
         \ (ooo)\_______
           (___)\       )\/\
                ||----w |
                ||     ||

<VM再利用>
<<Result from Java>>
 ___________________________
< ren said, Hello from JNI! >
 ---------------------------
   \
    \
    ____
   /# /_\_
  |  |/o\o\
  |  \\_/_/
 / |_   |
|  ||\_ ~|
|  ||| \/
|  |||_
 \//  |
  ||  |
  ||_  \
  \_|  o|
  /\___/
 /  ||||__
    (___)_)

ERRORLEVEL: 0

まとめ

・かなり些細なことでも分かり辛いエラーを起こすので、非常に大変でした。

・例えば、Windows向けのllvm-mingwとLinux向けのGNU GCCの挙動が異なり、
 -L"%JAVA_HOME%\bin\server" -ljvmを末尾に書かないと
 JNI_GetCreatedJavaVMsとJNI_CreateJavaVMが見つからないなど...

・環境変数LD_LIBRARY_PATHを書いて、sudo ldconfig -p | grep libjvmでも
 登録済みを確認できるのに、jni.hの2つの関数だけが見つからないという
 不思議な事象の解決には、6時間以上かかりました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?