[Java How To Programming] [Home on 246net] [Home on Alles net]
Powered by SmartDoc

JNI:Java Native Interface

TAKAHASHI, Toru
torutk'@'gmail.com
JavaからC/C++言語を呼び出す、またはその逆の方法として、JNI(Java Native Interface)というAPIが提供されています。本記事では、JavaからC/C++言語をネイティブメソッドとして呼び出すコードとそのコンパイル・リンク・実行手順、およびC/C++言語からJavaを呼び出すコードとそのコンパイル・リンク・実行手順を記します。

目次

JNIに必要なもの

JNIは、Javaで記述する部分とC/C++で記述する部分があります。Java側はJDKがあればよいのですが、C/C++側はC/C++コンパイラが別途必要となります。

Windows環境で必要なもの

Windows環境のJavaVMはネイティブメソッドをDLL(Dynamic Link Library)という形で用意されていないといけないので、DLLを構築できるC/C++コンパイラが必要となります。

Windows環境で使用できる代表的なコンパイラを以下にリストアップします。

Windows環境で使用できるC/C++コンパイラ
コンパイラ Ver 開発元 価格 JNI対応
Visual C++ 2010 10 Microsoft 有償 OK
Visual C++ 2010 Express Edition 10 Microsoft 無償 OK
Visual C++ 2005 Express Edition 8.0 Microsoft 無償 OK
Visual C++ 2003 7.1 Microsoft 有償 OK
Visual C++ 6.0 Microsoft OK
C++ Builder 5 Borland 68000円 未確認
CodeWarrior 未確認
Borand C++ Compiler 5.5.1 Borland フリー 未確認
Cygwin gcc 4.5.3 GNU/Cygwin フリー(GPL) コンパイルエラー[注1]
Cygwin gcc 2.95.2 GNU/Cygwin フリー(GPL) 未確認
  • [注1]Windows SDK特有の定義があり、回避コードを追加しないとコンパイルエラーとなる

はじめてのJNI(Windows編)

  1. nativeメソッド宣言を含むJavaプログラムを記述する
  2. Javaプログラムを通常通りコンパイルする
  3. ヘッダファイルを生成する
  4. C側のプログラムを記述する
  5. Cのソースファイルをコンパイルする
  6. 共有ライブラリファイルを作成する
  7. 実行する

nativeメソッド宣言を含むJavaプログラムを記述する

HelloJniWorld.java

Javaプログラムを通常通りコンパイルする

c:\home\torutk\prog\hellojni>javac \
    jp/gr/java_conf/torutk/exp/jni/hello/HelloJniWorld.java

ヘッダファイルを生成する

c:\home\torutk\prog\hellojni>javah -jni \
    jp.gr.java_conf.torutk.exp.jni.hello.HelloJniWorld

カレントディレクトリにヘッダファイルが生成されます。ヘッダファイル名は、

jp_gr_java_conf_torutk_exp_jni_hello_HelloJniWorld.h

元になったJavaクラス名(FQCN)のピリオドを'_'に置き変えたファイル名になっているようです。

C側のプログラムを記述する

HelloJniWorldImpl.c

Cのソースファイルをコンパイルする

c:\home\torutk\prog\hellojni>cl /I"C:\Program \
    Files\Java\jdk1.7.0\include" /I"C:\Program \
    Files\java\jdk1.7.0\include\win32" /c HelloJniWorldImpl.c

javahで生成されたヘッダファイルは、Java SE Development Kitに含まれるjni.h、jni_md.hをインクルードしているので、コンパイル時にはJava SE Development Kitの中にあるパスを指定します。

共有ライブラリファイルを作成する

前の手順で作成したオブジェクトファイルを、共有ライブラリファイルにまとめます。

c:\home\torutk\prog\hellojni>link /dll HelloJniWorldImpl.obj

実行する

Javaのクラスを実行するときは、CLASSPATHに指定します。しかし共有ライブラリファイルは、環境変数PATHあるいはシステムプロパティjava.library.pathで指定します。

環境変数PATHで共有ライブラリパスを指定
c:\home\torutk\prog\hellojni>set \
    PATH=c:\home\torutk\prog\hellojni;%PATH%
c:\home\torutk\prog\hellojni>java \
    jp.gr.java_conf.torutk.exp.jni.hello.HelloJniWorld
Hello JNI World
c:\home\torutk\prog\hellojni>
システムプロパティjava.library.pathで共有ライブラリパスを指定
c:\home\torutk\prog\hellojni>java \
    -Djava.library.path=C:\home\torutk\prog\hellojni \
    jp.gr.java_conf.torutk.exp.jni.hello.HelloJniWorld
Hello JNI World
c:\home\torutk\prog\hellojni>

Linux環境で必要なもの

Linux環境のJavaVMはネイティブメソッドを共有ライブラリファイル(Shared Library)という形で用意しなければならないので、共有ライブラリファイルを構築できるC/C++コンパイラが必要となります。

Linux環境では、GCCが標準で使用できます。

はじめてのJNI(Linux編)

  1. nativeメソッド宣言を含むJavaプログラムを記述する
  2. Javaプログラムを通常通りコンパイルする
  3. ヘッダファイルを生成する
  4. C側のプログラムを記述する
  5. Cのソースファイルをコンパイルする
  6. 共有ライブラリファイルを作成する
  7. 実行する

nativeメソッド宣言を含むJavaプログラムを記述する

HelloJniWorld.java

Javaプログラムを通常通りコンパイルする

$ javac jp/gr/java_conf/torutk/exp/jni/hello/HelloJniWorld.java

ヘッダファイルを生成する

$ javah -jni jp.gr.java_conf.torutk.exp.jni.hello.HelloJniWorld
$ ls
jp
jp_gr_java_conf_torutk_exp_jni_hello_HelloJniWorld.h
$ 

カレントディレクトリにヘッダファイルが生成されます。ヘッダファイル名は、元になったJavaクラス名(FQCN)のピリオドを'_'に置き変えたファイル名になっているようです。

C側のプログラムを記述する

HelloJniWorldImpl.c

Cのソースファイルをコンパイルする

$ gcc -fPIC -g -I/usr/java/jdk1.7.0/include \
      -I/usr/java/jdk1.7.0/include/linux \
      -c HelloJniWorldImpl.c

共有ライブラリファイルは、コードが複数のプロセスで共有されるため、特定のプロセスとのリンク時にアドレスを置き換えることができない。そこで、PIC(Position Independend Code)と呼ばれる、コードのアドレスに依存せずに実行できる形式で作成する。なお、PICを指定しなくても動いてしまいますが、多分その場合はライブラリがプロセス同士で共有されず、プロセス毎に別なメモリに置かれるため、共有ライブラリの意味が薄れてしまいます。

javahで生成されたヘッダファイルは、Java SE Development Kitに含まれるjni.h、jni_md.hをインクルードしているので、コンパイル時にはJava SE Development Kitの中にあるパスを指定します。

共有ライブラリファイルを作成する

前の手順で作成したPICオブジェクトファイルを、共有ライブラリファイルにまとめます。

$ gcc -shared -o libHelloJniWorldImpl.so HelloJniWorldImpl.o

今回は、ユーザ個別の場所に共有ライブラリを置くため、簡易な作成を行っています。システムのライブラリ領域に置く場合は、バージョン番号等を付与して管理するため、もっと複雑な手順となります。

実行する

Javaのクラスを実行するときは、CLASSPATHに指定します。しかし共有ライブラリファイルは、環境変数LD_LIBRARY_PATHで指定します。例えば、/home/torutk/prog/hellojniに共有ライブラリファイルとJavaクラスファイルの起点を置いていた場合、

$ export \
    LD_LIBRARY_PATH=/home/torutk/prog/hellojni:$LD_LIBRARY_PATH
$ cd /home/torutk/prog/hellojini
$ java jp.gr.java_conf.torutk.exp.jni.hello.HelloJniWorld
Hello JNI World
$

Solaris環境で必要なもの

Solaris環境のJavaVMは、ネイティブメソッドを共有ライブラリファイル(Shared Library)の形で用意します。したがって、共有ライブラリファイルを構築できるC/C++コンパイラが必要となります。

Solaris環境の場合は、GCCまたはSunStudioコンパイラが無償で利用可能です。

はじめてのJNI(Solaris SunStudioコンパイラ編)

Javaに関するコンパイル・実行は他のプラットフォームと同じですので、C/C++のソースファイルをコンパイルする手順だけを以下に示します。

SunStudio C++コンパイラでのネイティブコードのコンパイル
$ CC -KPIC -mt -I/usr/jdk/jdk1.7.0/include \
    -I/usr/jdk/jdk1.7.0/include/solaris -c HelloJniWorldImpl.c

コンパイラ・オプションについては、次のURLに解説があります。

  • "Compilation of JNI Code" by Kelly O'Hair

    簡単にまとめると以下の表になります。

    SunStudioC++コンパイラのオプション指定について
    オプション項目 Intel x86 Intel x64 SPARC 32bit SPARC 64bit 備考
    -xarch -xarch=pentium -xarch=amd64 -xarch=v8 -xarch=v9 JDKのネイティブ・コードとリンクするので同じオプションとする
    -KPIC 共有ライブラリファイル生成時にリロケータブルなバイナリを生成
    -mt マルチスレッドセーフ
    -xregs -xregs=no%frameptr -xregs=no%appl
    SunStudio C++コンパイラでのネイティブコードのリンク
    $ CC -G HelloWorldImpl.o -o libHelloJniWorldImpl.so \
        HelloJniWorldImpl.o
    

    はじめてのJNI(Solaris GCC編)

    JNIプログラミング

    nativeコードから例外をスローする

    nativeメソッドで例外をスローする場合、C/C++側でどのように例外オブジェクトを生成してスローすればよいのかを見ていきます。

    Javaのクラス

    まず、以下のようなnativeメソッドをJava側で定義します。

    例外をスローするnativeメソッドを持つJavaクラスHelloCalc
    package jp.gr.java_conf.torutk.exp.jni.hello;
    
    public class HelloCalc {
        static {
            System.loadLibrary("HelloCalcImpl");
        }
    
        public native int calc(int a, int b) throws CalculateException;
    }
    

    例外CalculateExceptionの定義を以下に示します。

    nativeからスローする例外を定義したJava例外クラスCalculateException
    package jp.gr.java_conf.torutk.exp.jni.hello;
    
    public class CalculateException extends Exception {
        public CalculateException() {
        }
    
        public CalculateException(String message) {
            super(message);
        }
    
        public CalculateException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public CalculateException(Throwable cause) {
            super(cause);
        }
    }
    

    Exceptionクラスで定義されるコンストラクタと同じ引数パターンのものを用意しています。それ以外には特に機能はないシンプルなアプリケーション定義例外クラスです。

    ネイティブ・コード

    上記クラスからjniコマンドで生成されるC/C++のヘッダーファイルは次のようになります。

    jniで生成されるヘッダーファイル
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class jp_gr_java_conf_torutk_exp_jni_hello_HelloCalc */
    
    #ifndef _Included_jp_gr_java_conf_torutk_exp_jni_hello_HelloCalc
    #define _Included_jp_gr_java_conf_torutk_exp_jni_hello_HelloCalc
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     jp_gr_java_conf_torutk_exp_jni_hello_HelloCalc
     * Method:    calc
     * Signature: (II)I
     */
    JNIEXPORT jint JNICALL \
        Java_jp_gr_java_1conf_torutk_exp_jni_hello_HelloCalc_calc
      (JNIEnv *, jobject, jint, jint);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    JNIで定義されるC/C++側の関数宣言には、例外については何も登場しません。

    ネイティブ・メソッドのC++での実装を記述します。

    HelloCalcImpl.cpp
    #include <jni.h>
    #include "jp_gr_java_conf_torutk_exp_jni_hello_HelloCalc.h"
    
    JNIEXPORT jint JNICALL \
        Java_jp_gr_java_1conf_torutk_exp_jni_hello_HelloCalc_calc(JNIEnv* env, \
        jobject obj, jint value1, jint value2)
    {
        if (value1 < 0 || value2 < 0) {
    	jclass clazz = env->FindClass(
    	    "jp/gr/java_conf/torutk/exp/jni/hello/CalculateException");
    	env->ThrowNew(clazz, "Argument should not be negative");
    	return -1; // 例外をスローするので実際にはJava側にはリターンされない
        }
        jint result = value1 + value2;
        return result;
    }
    

    jintは、JDK 6ではlong型のtypedefによる定義型となっています。なので、そのまま32bit整数値として扱われます。

    ネイティブ・メソッドの中から例外をスローする場合は、JNIEnvのThrowまたはThrowNewメンバ関数を使用します。記述が簡単なのは、ThrowNewの方ですが、コンストラクタとして文字列を引数に取るものにしか対応できません。

    ThrowまたはThrowNewメンバ関数を呼んでも制御がその場で飛ぶことはありません。C/C++側での処理は次の行に継続します。そこで、ここではThrowNew呼び出し直後にreturn文を入れて、関数そのものを終了されます。

    ThrowNewで日本語文字列を指定する方法(Windows)

    ThrowNewメンバ関数の第2引数は、例外オブジェクトを生成する際にコンストラクタへ渡す引数となります。この第2引数の型はconst char*型となっています。ASCII文字の範囲であればそのまま使用できますが、非ASCII文字の場合はUTF-8形式にする必要があります。

    以下は、3番目のネイティブ(C/C++側)で文字コードをMS-932からUTF-8に変換する場合のコードとなります。

    ネイティブ側でMS-932からUTF-8に変換
        if (value1 < 0 || value2 < 0) {
    	jclass clazz = env->FindClass(
    	    "jp/gr/java_conf/torutk/exp/jni/hello/CalculateException");
    	const char* message = "引数は正の整数でなくてはなりません"; 
    
    	// MS932 -> Unicode変換後の長さを算出
    	int unicodeLength = MultiByteToWideChar(
    	    CP_ACP, 0, message, strlen(message), NULL, 0);
    	WCHAR* unicodeBuffer = new WCHAR[unicodeLength];
    	// MS932 -> Unicode変換
    	MultiByteToWideChar(
    	    CP_ACP, 0, message, strlen(message), unicodeBuffer, unicodeLength);
    	// Unicode -> UTF-8変換後の長さを算出
    	int utf8Length = WideCharToMultiByte(
    	    CP_UTF8, 0, unicodeBuffer, unicodeLength, NULL, 0, NULL, NULL);
    	// Unicode -> UTF-8変換
    	char* utf8Buffer = new char[utf8Length + 1];
    	WideCharToMultiByte(
    	    CP_UTF8, 0, unicodeBuffer, unicodeLength, utf8Buffer, utf8Length,
    	    NULL, NULL);
    	utf8Buffer[utf8Length] = 0;
    
    	env->ThrowNew(clazz, utf8Buffer);
    	delete unicodeBuffer;
    	delete utf8Buffer;
    	return -1; // 例外をスローするので実際にはJava側にはリターンされない
        }
    

    まずMS-932からUnicodeへ変換後の文字列(WCHAR[])格納サイズを取得するために、第6引数に0を指定してMulitByteToWideCharを呼び出します。

    次に、MS-932からUnicodeへ変換するために、MultitByteToWideCharを呼び出します。

    それから、UnicodeからUTF-8へ変換後の文字列(char[])格納サイズを取得するために、第6引数に0を指定してWideCharToMultiByteを呼び出します。

    そして、UnicodeからUTF-8へ変換するために、WideCharToMultiByteを呼び出します。

    UTF-8へ変換した文字列(char *)をThrowNewの引数に渡して例外オブジェクトを生成しスローするよう設定し、動的にアロケートしたメモリをクリアしてからリターンします。

    ThrowNewで日本語文字列を指定する方法(Linux/Solaris)

    昨今のLinuxおよびSolarisは、デフォルトの日本語ロケールの文字コードがUTF-8なので、C/C++のソースファイルをUTF-8形式で保存しコンパイルするので、日本語文字列は問題ないようです。

    EUC-JPを日本語ロケールに設定している場合でも、UTF-8環境がインストールされていれば、実行時にロケールをUTF-8に設定すれば大丈夫です。

    日本語ロケール(UTF-8)に設定
    $ export LANG=ja_JP.UTF-8
    $
    

    UTF-8環境がない場合、文字列をEUC-JPからUTF-8へ変換するのにLinux/Solaris上でのC/C++では、iconvライブラリかまたはIBMのICUライブラリを使用するのが一般的と思います。

    C/C++からJavaを起動する

    JNIを用いると、C/C++アプリケーションからJavaVMを起動し、JavaのクラスをC/C++から利用することができます。

    JavaVMを起動する

    JNIのドキュメントに記載があります。Java Native Interfaceの仕様 5.呼び出し(Invocation) API

    かんたんなサンプル

    まずは、JavaVM自体をC/C++アプリケーション側から起動する方法を見てみます。

    launch.cc
    #include <jni.h>
    #include <iostream>
    
    int main(int argc, char* argv[]) {
        JavaVMOption options[3];
        options[0].optionString = "-Xmx128m";
        options[1].optionString = "-verbose:gc";
        options[2].optionString = "-Djava.class.path=C:/home/torutk/java/jni";
    
        JavaVMInitArgs vm_args;
        vm_args.version = JNI_VERSION_1_6;
        vm_args.options = options;
        vm_args.nOptions = 3;
    
        std::cout << "creating Java VM" << std::endl;
        JNIEnv* env;
        JavaVM* jvm;
        int res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
        if (res < 0) {
    	std::cerr << "JNI_CreateJavaVM Error: " << res << std::endl;
    	return -1;
        }
    
        std::cout << "finding Java class `Hello`" << std::endl;
        jclass clazz = env->FindClass("Hello");
        if (clazz == 0) {
    	std::cerr << "FindClass Error for `Hello`" << std::endl;
    	return -1;
        }
    
        std::cout << "getting static method ID of `main`" << std::endl;
    jmethodID mid = env->GetStaticMethodID(clazz, "main", \
        "([Ljava/lang/String;)V");
        if (mid == 0) {
    	std::cerr << "GetStaticMethodID Error for `main` `([Ljava/lang/String;)V`" \
        << std::endl;
    	return -1;
        }
    
        std::cout << "invoking Hello#main" << std::endl;
        env->CallStaticVoidMethod(clazz, mid, NULL);
    
        std::cout << "destroying Java VM" << std::endl;
        jvm->DestroyJavaVM();
    
        return 0;
    }
    

    JavaVM起動オプション

    JavaVMを起動するオプションは、JNIの構造体JavaVMInitArgsおよび構造体JavaVMOptionで定義します。JavaVMOptionには、通常コマンドラインでJavaVMに与えるオプションと同じ文字列で指定します。

    ここで起動したJavaVM上でクラスを実行させる(ロードさせる)には、JavaVMに対してクラスパスを指定しておく必要があります。通常javaコマンド(java.exe)で起動するときは、コマンドラインオプション-cpや-jarを使って指定しますが、JavaVM自身に渡すときは、システムプロパティjava.class.pathに設定します。

    コンパイル・リンク(Windows)

    コンパイルは、JavaからC/C++を呼び出す場合と同じく、jni.hをインクルードするためのインクルードパスを指定します。

    Visual Studio 2010のC++での実行例です。コンパイルは次のコマンドです。/cオプションを付けると、コンパイルのみ実行しリンクは実行しません。

    C:\Users\torutk\Documents\work\launch> cl /I"C:\Program \
        Files\Java\jdk1.7.0\include" /I"C:\Program \
        Files\java\jdk1.7.0\include\win32" /c launch.cc
    
    C:\Users\torutk\Documents\work\launch> 
    

    リンクは次のコマンドです。このサンプルではJavaVMに対して「明示的リンク」をするので、リンク時にJavaVMのライブラリを指定します。リンク時に指定するライブラリは、インポートライブラリファイルで、<JDKインストールディレクトリ>\libにjvm.libのファイルとして置かれています。

    C:\Users\torutk\Documents\work\launch> link launch.obj \
        /libpath:"C:\Program Files\Java\jdk1.7.0\lib" jvm.lib
    
    C:\Users\torutk\Documents\work\launch> 
    

    これで、実行時にjvm.dllとリンクするlaunch.exeが生成されます。

    実行

    Windows OSでは、実行時リンクするDLLファイルをカレントディレクトリおよび環境変数PATHに指定されたディレクトリから検索します。そこで、環境変数PATHに設定を追記して実行します。

    jvm.dllは、Java SE Development Kit 7u6(32bit版)の場合、client VMとserver VMの2つがあり、それぞれ

    にあります。使用したい方を環境変数PATHに設定し、プログラムを実行します。

    C:\Users\torutk\Documents\work\launch> PATH=%PATH%;"C:\Program \
        Files\Java\jdk1.7.0\jre\bin\server"
    C:\Users\torutk\Documents\work\launch> launch
    Hello world!
    C:\Users\torutk\Documents\work\launch> 
    

    黙示的リンク

    先のサンプルでは、実行時に使用するJavaVM(jvm.dll)のパスを環境変数PATHに設定しておく必要があります。

    Windows OSでOracle JDKをインストールすると、レジストリにそのマシンにインストールされているJavaのバージョンとそのパスが登録されます。JavaVMを起動するプログラム内でこのパスをレジストリから読み出し、そのパスから動的にjvm.dllをロードすることで、PATH設定は不要になります。

    レジストリは次になります。

    HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment\CurrentVersion
    このマシンにインストールされているJava実行環境の現行(最新)バージョンが格納
    
    HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment\1.7\RuntimeLib
    Java実行環境のバージョン1.7のjvm.dllのパスが格納
    

    説明のため、一切エラー処理を省いたサンプルコードを次に示します。実際に使用する際は、各APIの戻り値を取得し成否判定をするといった処理が不可欠です。

    #include <jni.h>
    #include <windows.h>
    #include <string>
    
    namespace {
    const char* key_jre = "Software\\JavaSoft\\Java Runtime Environment";
    }
    
    int main()
    {
        // レジストリキー"HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment"を
        // オープンし、そのキーの中から名前"CurrentVersion"の値を読み取る
        HKEY key;
        RegOpenKeyEx(HKEY_LOCAL_MACHINE, key_jre, NULL, KEY_READ, &key);
        BYTE buffer[256];
        DWORD size = sizeof(buffer);
        RegQueryValueEx(key, "CurrentVersion", NULL, NULL, buffer, &size);
        RegCloseKey(key);
    
        // レジストリキー"HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\<N.N>"
        // (ここで<N.N>は、上記で読み込んだCurrentVersionの値)をオープンし、そのキーの中から
        // 名前"RuntimeLib"の値を読み取る
        std::string key_jre_ver(key_jre);
        key_jre_ver.append("\\").append(reinterpret_cast<char*>(buffer));
        RegOpenKeyEx(HKEY_LOCAL_MACHINE, key_jre_ver.c_str(), NULL, KEY_READ, &key);
        size = sizeof(buffer);
        RegQueryValueEx(key, "RuntimeLib", NULL, NULL, buffer, &size);
        RegCloseKey(key);
    
        // 読み取った値をパスとして動的にjvm.dllライブラリーをロード
        HMODULE module = LoadLibrary(reinterpret_cast<char*>(buffer));
        // 動的にロードしたjvm.dllライブラリから関数JNI_CreateJavaVMのアドレス取得
        auto func_CreateJavaVM = reinterpret_cast<decltype(JNI_CreateJavaVM)*>(
            GetProcAddress(module, "JNI_CreateJavaVM")
        );
        // JNI_CreateJavaVMを呼び出し
        JavaVMOption options[2];
        options[0].optionString = "-Xmx64m";
        options[1].optionString = "-Djava.class.path=.";
        JavaVMInitArgs vm_args;
        vm_args.version = JNI_VERSION_1_6;
        vm_args.options = options;
        vm_args.nOptions = 2;
        JNIEnv* env;
        JavaVM* jvm;
        (*func_CreateJavaVM)(&jvm, (void**)&env, &vm_args);
        // Helloクラスのロード
        jclass clazz = env->FindClass("Hello");
        // Helloクラスのmainメソッド取得
        jmethodID mid = env->GetStaticMethodID(clazz, "main", "([Ljava/lang/String;)V");
        // mainメソッド実行
        env->CallStaticVoidMethod(clazz, mid, NULL);
        // JVM破棄
        jvm->DestroyJavaVM();
    }
    

    レジストリを取得するためにWin32 APIを使用しています。レジストリの読み方は、まず値を格納しているキーをRegOpenKeyEx関数で取得します。次に、キーに属する値をRegQueryValueEx関数で取得します。

    動的にjvm.dllライブラリを黙示的リンク(実行中にロード)するためにWin32 APIのLoadLibrary関数を呼びます。

    ロードしたライブラリが提供する関数のアドレスを取得するためにWin32 APIのGetProcAddress関数を呼びます。関数ポインタは型宣言が複雑なのですが、このコードではC++11(2011年のC++標準規格改訂)で新しく追加された言語仕様のautoおよびdecltypeを使って簡潔に記述しています。

    この例では、レジストリのCurrentVersionを使用していますが、コマンドラインオプションでJava実行環境のバージョンを指定したり、インストールされているJava実行環境のバージョン一覧を表示し選択されたものを実行するといった応用も可能です。

    コンパイル・リンク

    コンパイルは前と同じです。

    C:\Users\torutk\Documents\work\launch> cl /I"C:\Program \
        Files\Java\jdk1.7.0\include" /I"C:\Program \
        Files\java\jdk1.7.0\include\win32" /c launch.cc
    
    C:\Users\torutk\Documents\work\launch> 
    

    リンクは、Win32 APIのレジストリ操作APIを指定します。jvm.libの指定は黙示的リンクの場合は不要です。

    C:\Users\torutk\Documents\work\launch> link launch.obj \
        advapi32.lib
    
    C:\Users\torutk\Documents\work\launch> 
    

    実行

    実行時に環境変数PATHの設定追加は不要です。

    C:\Users\torutk\Documents\work\launch> launch
    Hello world!
    C:\Users\torutk\Documents\work\launch>