Javaのプログラムをexe化 (windows) する

はじめに

つくったJavaアプリをWindows環境向けにexe化していきます。私が選んだものをメモしておきます。

何種類か存在しますが、下記を試しました。

  • launch4j
  • exewrap
  • jpackage

launch4j

Launch4j - Cross-platform Java executable wrapper
Cross-platform Java executable wrapper for creating lightweight Windows native EXEs. Provides advanced JRE search, appli...

もともとMabiIccoの1.0系はこれをつかっていました。

ツールの起動自体にJava 1.8 系を必要とするようで、ちょっと うーん という感じです。せっかくJava8からJava17にあげたのに・・・。

このツールでEXE化した場合、EXEのアイコンは変更できるのですが、タスクマネージャーのところまでは変更されていないようでした。

exewrap

https://exewrap.osdn.jp/

JARファイルをEXEへ変換してくれるツールのようです。ドキュメントをみるといろいろなオプションがありますが、通常のアプリなのでとりあえず作成してみます。

-a が、アプリケーションに指定するVM引数ということなので、下記のようにしてみました。

exewrap.exe -g -e NOLOG; -i icon2.ico -v 1.2.67 -l APPDIR -a "--add-exports java.desktop/com.sun.media.sound=ALL-UNNAMED --add-exports java.desktop/sun.swing=ALL-UNNAMED" -t 17 MabiIcco.jar

が、、、。

–add-exportsの指定が効いてなく、起動できません。

モジュールシステムに対応していればいいのですが、JARで起動するのはちょっとできない?かなということで断念しました。

jpackage

最近のJDKについているコマンドです。

jpackageコマンド

ドキュメントをみると、MSIなどのインストーラにもできるようですが、EXEでとめておきます。

jpackage -t app-image -n MabiIcco -d package -i make-package --main-jar mabiicco.jar --java-options "--add-exports java.desktop/com.sun.media.sound=ALL-UNNAMED --add-exports java.desktop/sun.swing=ALL-UNNAMED" --add-modules java.base,java.datatransfer,java.desktop --icon icon3.ico --app-version 1.2.70

できました。

アイコンも反映されていますし、タスクマネージャーの表示もアプリケーション名&アイコン反映されるようです。EXE化するだけの用途でとりあえず使っていきたいと思います。

ただ、launch4のときには日本語環境で韓国語のファイル名を引数で渡せていたのですが、jpackageで作ったexeだと文字化けしてファイルが開けないという問題が発生しました。

JPackageでexe化したアプリケーション引数が文字化けする

検索してみると既知の?問題らしいですね。

Attention Required! | Cloudflare

ためしにと、最新のJava18でやってみたりしましたが、だめでした。(JDK21でもだめ)

結論としては、JNAでやることにしました。
コマンドライン全体をとれるようなので、これなら日本語環境での韓国語ファイル名も問題なく渡せました。コマンド全部とってくるようなので、後ろのところだけ切り取るようにしています。

GitHub - java-native-access/jna: Java Native Access
Java Native Access. Contribute to java-native-access/jna development by creating an account on GitHub.
	interface Kernel32 extends StdCallLibrary {
		Kernel32 INSTANCE = (Kernel32) Native.load("kernel32", Kernel32.class);
		WString GetCommandLineW();
		Pointer LocalFree(Pointer pointer);
	}
	interface Shell32 extends StdCallLibrary {
		Shell32 INSTANCE = (Shell32) Native.load("shell32", Shell32.class);
		Pointer CommandLineToArgvW(WString command_line, IntByReference argc);
	}
	public static String[] getCommandLineArgs(int n) {
		IntByReference argc = new IntByReference();
		var ptr = Shell32.INSTANCE.CommandLineToArgvW(Kernel32.INSTANCE.GetCommandLineW(), argc);
		String[] argv = ptr.getWideStringArray(0, argc.getValue());
		Kernel32.INSTANCE.LocalFree(ptr);
		if (argv.length > 0) {
			return Arrays.copyOfRange(argv, argv.length-n, argv.length);
		}
		return null;
	}

まとめ

結果としては、jpackageでexe化することにしました。ただ引数に関しては文字コードが適切に扱えないようなので注意です。

MabiIcco 1.0 系:launch4j

MabiIcco 1.2 系:jpackage