Skip to content

Instantly share code, notes, and snippets.

@seraphy
Last active March 27, 2020 00:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seraphy/76d03c4e3b8a912056ca6f103c411634 to your computer and use it in GitHub Desktop.
Save seraphy/76d03c4e3b8a912056ca6f103c411634 to your computer and use it in GitHub Desktop.
Launch4jのjava起動部(head)をカスタマイズする方法

本稿の概要

Launch4jはjavaを起動するexeを作成する。

このjava起動部のコードは「head」と呼ばれる部分であり、これはMinGWのgccでコンパイルされている。

Launch4jのツールでexeを作成する際には、このコンパイル済みの*.o, *.aファイルをリンクしてexeが作成される。

この*.o, *.aファイルは何も指定しなければ標準のものが使われるが、明示的に自分がビルドしたものに差し替えることができる。

本稿では、Launch4jのver3.12の標準のheadをカスタマイズし、独自のheadによるjava起動を実現する方法を示すものである。

なぜ、この記事を書いたのか?

Launch4jのheadのカスタマイズは、カスタマイズの仕組みが用意されており、それが可能であることは明かであるが、実際の手順については説明がなく、ネット上にも、それを説明した文書がみつからなかったためである。

おそらく、headのカスタマイズにはC言語とWin32を少し知っていれば決して難解ではないためであろうが、しかし、それを行うためには、まず開発環境の準備が必要で、その準備には、いつかの注意ポイントがある。

  • C言語の処理系とバージョン = MinGWの32版のgcc 4.6.1
  • 想定している開発環境(IDE) = Dev-C++ 5.0あたり
  • Maven Pluginでheadのビルドをサポートするバージョン = com.akathist.maven.plugins.launch4:launch4j-maven-plugin の 1.7.25

これらについて説明するものである。

また、Launch4jのバージョンが上がって、headに使うgcc処理系が変わったのであれば、これらの開発環境も変わるものと思われる。

ここで想定しているのは、Launch4j 3.12 (このバージョンに対応するmavenプラグインとしては、com.akathist.maven.plugins.launch4:launch4j-maven-plugin の 1.7.25) である。

MinGW 2.22と、Dev-C++ 5.0.0.9

Launch4j ver3.12のhead部(Java起動部)は、MinGWを使ってコンパイルされている。(VC++系ではない)

Launch4j本体は、*.o, *.aファイルをリンクしたり、各種設定値をリソースファイルに格納するため、MinGWのツールセットのうち、

  • ld.exe (リンカー)
  • windres.exe (リソースコンパイラ)

の2つを同梱している。

このMinGWは、binutils 2.22 である。1

このMinGWのバージョンに対する、gccは、おそらく、gcc 4.6.2あたりである。

また、Launch4jが生成するexeは常に32ビットである。2

開発ツールとしては、Dev-C++ 5.0.0.9 あたりの32ビット版gcc4.6.2入りを使うのが良い。

(ポータブル版のDev-C++ 5.0.0.9 あたりを使うとレジストリやAPPDATAフォルダを汚さない。)

https://sourceforge.net/projects/orwelldevcpp/files/Portable%20Releases/

Launch4jのソースの取得と、headのソースの場所

launch4jのソースを入手し、展開する。

git clone https://git.code.sf.net/p/launch4j/git launch4j

そのうち、以下のフォルダがheadに関連するフォルダである。

\launch4j
├─head
├─head_src
│  ├─consolehead
│  └─guihead
└─w32api

head_src がソースの位置となる。

  • head コンパイル済み*.oファイル (*.devのプロジェクトの出力先)
  • head_src ヘッド部のソース
  • w32api コンパイル済み*.oファイルとリンクされるライブラリ*.aファイル (コンパイル時のgccのバージョンと一致したもの)

これを自分の作業フォルダを作ってコピーする。

(なお、Launch4jのheadはMITライセンスで公開されている。)

ソースフォルダは以下のようになっている。

(ここではベータ版であるjniヘッドは使わないので無視している)

\launch4j\head_src
│  head.c
│  head.h
│  LICENSE.txt
│  resource.h
│
├─consolehead
│      .gitignore
│      consolehead.c
│      consolehead.dev
│      Makefile.win
│
└─guihead
        .gitignore
        guihead.c
        guihead.dev
        guihead.h
        Makefile.win

このうち、*.dev がDev-C++のプロジェクトファイルである。

たとえば、GUIヘッド をビルドしたいのであれば、head_src\guihead\guihead.dev を開く。

Consoleヘッドであれば、head_src\consolehead\consolehead.dev となる。

しかし、どちらで開いても共通部は、head_src\head.c に記述されている。

Windowsアプリとして起動する部分(WinMain)があるのが、GUIヘッドのguihead.c で、 コンソールアプリとして起動する部分(main)があるのが、CONSOLEヘッドのconsolehead.c である。

設定値の読み込みやJAVAの起動といった主要な機能は、すべてhead.c に記載されている。

プロジェクト設定は*.devファイルに設定済みであり、また、ビルド手順であるMakefile.win も、そのまま使える。3

まずは、この*.devをDev-C++で開いて、そのままコンパイルできることを確認する。

Launch4jにカスタムヘッドを使用させる

ビルドした*.oファイルを、Mavenの com.akathist.maven.plugins.launch4:launch4j-maven-plugin プラグインで使う場合は、以下のように感じで指定できる。

この場合、必ず、バージョン1.7.25 以降のプラグインを使うこと。4

<plugin>
	<!-- Launch4jによるjarファイルのexe化を行う. http://launch4j.sourceforge.net/docs.html -->
	<groupId>com.akathist.maven.plugins.launch4j</groupId>
	<artifactId>launch4j-maven-plugin</artifactId>
	<version>1.7.25</version>
	<executions>
		<execution>
			<id>l4j-gui</id>
			<phase>package</phase>
			<goals>
				<goal>launch4j</goal>
			</goals>
			<configuration>
				<headerType>gui</headerType>
				<outfile>target/${project.artifactId}.exe</outfile>
				<jar>target/${project.artifactId}.jar</jar>
				<errTitle>Failed to execute the ${project.artifactId}</errTitle>
				<downloadUrl>https://adoptopenjdk.net/</downloadUrl>
				<supportUrl>https://github.com/seraphy/Launch4jHead</supportUrl>
				<objs>
					<obj>src/Launch4JStub/w32api/crt2.o</obj>
					<obj>src/Launch4JStub/head/head.o</obj>
					<obj>src/Launch4JStub/head/guihead.o</obj>
				</objs>
				<libs>
					<lib>src/Launch4jStub/w32api/libmingw32.a</lib>
					<lib>src/Launch4jStub/w32api/libgcc.a</lib>
					<lib>src/Launch4jStub/w32api/libmsvcrt.a</lib>
					<lib>src/Launch4jStub/w32api/libkernel32.a</lib>
					<lib>src/Launch4jStub/w32api/libuser32.a</lib>
					<lib>src/Launch4jStub/w32api/libadvapi32.a</lib>
					<lib>src/Launch4jStub/w32api/libshell32.a</lib>
					<lib>src/Launch4jStub/w32api/libshfolder.a</lib>
				</libs>
				<jre>
					<path>jre</path>
k					<minVersion>1.8.0_60</minVersion>
					<initialHeapSize>64</initialHeapSize>
					<maxHeapSize>72</maxHeapSize>
					<runtimeBits>64/32</runtimeBits>
				</jre>
				<versionInfo>
					<fileVersion>${project.version}</fileVersion>
					<txtFileVersion>${project.version}</txtFileVersion>
					<fileDescription>${project.artifactId} ${project.version}</fileDescription>
					<copyright><![CDATA[${maven.build.timestamp} ${project.developers[0].id}]]></copyright>
					<productVersion>${project.version}</productVersion>
					<txtProductVersion>${project.version}</txtProductVersion>
					<productName>${project.artifactId}</productName>
					<internalName>${project.artifactId}</internalName>
					<originalFilename>${project.artifactId}.exe</originalFilename>
				</versionInfo>
			</configuration>
		</execution>
	</executions>
</plugin>

この例では、src\Launch4jStubフォルダの下にheadw32apiフォルダをコピーしてきている。

launch4j-maven-pluginのドキュメントには、headのカスタマイズのためのobjs, libsタグの説明がほとんどないが、ソースコード上にはLaunch4jにパラメータを引き渡す処理が記述されており、上記例のように、objs, libsタグによって指定する。ここで指定されたファイルは、このmavenプラグインによって、Launch4jの作業ディレクトリにコピーされる。(Launch4j側では自分のワーク下にある*.o, *.a以外は不正と判定する処理が入っているため、必ず作業ディレクトリにコピーしなければならない。1.7.24までは、この処理に不備があるため、1.7.25以降を使う必要がある。)

src\Launch4jStub\head フォルダには、先のDev-C++でビルドした結果得られた、*.oファイルと、共通ランタイムの```crt2.o``をいれる。

src\Launch4jStub\w32api フォルダには、Dev-C++で参照しているgccのバージョンのライブラリ*.aのうち、必要なものをコピーする。

コンパイルに使ったバージョンと異なるライブラリとリンクさせると、リンクに失敗するか、リンクできたとしても動きがおかしくなるので、必ず、コンパイルに使ったgccのものでコピーしなおすこと。

標準では

  • libgcc.a
  • libmingw32.a
  • libmsvcrt.a
  • libkernel32.a
  • libuser32.a
  • libadvapi32.a
  • libshell32.a

が使われており、head.cで使うWin32APIによっては、必要なライブラリは増減する。

なお、libgcc.a はWin32のライブラリではなく、gccのライブラリであり、*.aの場所も異なる。

MinGW32\lib\gcc\mingw32\4.6.1\libgcc.a

Win32のライブラリとlibmingwの*.aファイルは、だいたい以下にある。

MinGW32\lib\*.a

これでMavenを実行してexeを作成させてみる。

リンクに失敗した場合は、*.aファイルと*.oファイルのgccバージョン不一致等々が考えられる。

headのカスタマイズそのものは比較的容易である

上記の手順で自分で用意した開発環境でのヘッドのビルドと、Launch4jでのexe化が成功したら、自分の好きなようにheadをカスタマイズをできる準備が整ったことになる。

この開発環境の準備さえできれば、あとは簡単だといえる。

head.cに、ほとんどの処理がかかれており、コード行数は1600行前後にすぎないので、俯瞰するのに、それほど手間はかからない。

また、書かれているコードもつとめて平易に書かれており、トリッキーなことはなく、どこかを叩くと、どこかが飛び出るような不可思議なコードではないので、安心してカスタマイズできるはずである。

実装・カスタマイズ例

Java11時代になり、OracleからのJRE/JDKの無償配布が終了したり、クライアント向けJREの配布がなくなったりして、従来のように、ユーザーがJAVAランタイムをインストールするとレジストリにJRE/JDKの在処が記録される、というような運用が期待できなくなりつつある。

Launch4jはバンドルJRE、レジストリによるJRE/JDKの探索、といった方法でJAVAランタイムを探しに行くが、第3の方法として、いずれも見つからなかった場合には、ユーザーに直接、JAVA_HOMEの場所を選択してもらう という処理を入れたくなったので、ヘッドをカスタマイズしてみた。

https://github.com/seraphy/Launch4jHead

このほかにも、起動するjava.exe, javaw.exeが32/64ビットのいずれかであるかを示す、特殊変数%JRE_ARCH% を用意して、javaから参照する外部ソフトのdllの参照時に32ビット版のdllを使うか、64ビット版のdllを使うかを分けられるようにする、といった処理ができるようにもしている。5

以上、メモ終了

Footnotes

  1. http://launch4j.sourceforge.net/ の Info の節参照

  2. head.cで自身が64ビットOSではwow64で動作していることを前提としたコードがある。また、Launch4jが生成するexeは32ビットだが、javaを起動するときにはhead.cでCreateProcess APIを使っているため、32/64ビット版どちらのJavaも起動できる。

  3. Dev-C++の標準ではexeファイルを作成するビルド手順を自動的に作成するが、Launch4j用ヘッドは*.oを得るのが目的であるため、独自のMakefile.winを用意してexeは生成しないようになっている。

  4. 1.7.24以前は、このobjs, libsタグで指定したソースを作業ディレクトリにコピーする処理に不具合があり、カスタムビルドを実質的に使用できなかったため。プルリクエストして修正してもらいました。(Launch4j付属のAntタスクでビルドする場合には特に注意点はありません。)

  5. たとえば、環境変数PATHと、システムプロパティjava.library.pathに、それらの情報を入れないと動かないようなレガシーなソフトであっても、Launch4jプラグインのvarsタグとjre/optsタグと、カスタム化で追加した特殊変数%JRE_ARCH%を併用することで、うまくPATHやjava.library.pathの設定ができるようになる。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment