Javaのスタンドアロンクロスプラットフォームモノリシックプログラム

デスクトップアプリケーションが大好きです。 今日これを認めることは、外国のintelligence報機関との関係よりも恥ずかしいようですが、そうです。 いいえ、これはインターネット技術が好きではないという意味ではありません。 さらに、一部の人は尊敬するだけでなく、多かれ少なかれ知っています。 しかし、それにもかかわらず、プログラムが1台のコンピューターで作成され、それがコンパイルされて別のコンピューターで実行されたときは懐かしく思います。 その後、どこにでも(ほぼ)1つのシステムがありました-同じAPIを備えたWindows、アプリケーションレベルでの互換性の問題はほとんどありませんでした。ブラウザ開発者のソースは誰もいませんでした。 もちろん、これは皮肉なことですが、真剣に-今でもデスクトップアプリケーションのみを作成して、すべての一般的なシステムで動作するようにしたいことがあります。 難しいですか? 考えて掘るなら、そうではありません。

また、きちんとしたアーキテクチャと強力な型付けを備えた高級言語も大好きです。 私のお気に入りはJavaとC#です。 どちらも、開発者にC ++と比較して多くの利点を提供し、多くの心配を軽減します。 費用はいくらですか? Oracle JVM、.NETまたはmonoと呼ばれる重いデッキを運ぶことにより。 3つのデッキすべての重さは数百メガバイトであり、ライセンスは各ユーザーが自分のコンピューターの容量を混乱させることなくこのものをダウンロードすることを余儀なくされるほどのものであり、最も重要なことには、JavaプログラムはJVMのすべてのバージョンと一度に互換性がありませんか? そして今-私たちは、友人(または数百万人の友人)にプログラムを投げて、それが彼と一緒に開始しないことを気にしないだけではうまくいかないという事実に来ます。 私はトリッキーなセットアップを行い、松葉杖を運転しなければならず、.NETについてはまだ言及していません-一度友人に3つのインストールされたバージョンを一度見て、3つすべてが異なるアプリケーションに必要でした...

やめて! Javaでプログラムを作成しましょう。ただし、マシンにJVMをインストールする必要はありません。そのため、Windows、Linux、およびOS Xでワンタッチでビルドでき、同時に少し時間がかかります。 誰もそれがCで書かれていないことを理解していないように。 まったく逆です! (いや、私はgcjを意味するわけではありません。gcjはJavaのすべての魅力を奪います。リフレクションは動作し、サードパーティのjarでさえ実行できます)。


もちろん、私はウィザードではありません。 魔法のアーティファクトを1つ見つけました。 これはoss.readytalk.com/avianにあるAvianと呼ばれ、軽量でありながら本格的なサードパーティJVM実装であり、Oracleには聞いたことがないかもしれません。 多数のプラットフォームとアーキテクチャをサポートし、「好きなことをして、やりたい」ライセンスがあります-いいえ、私はこのプロジェクトとは何の関係もありません、私は貢献者でもない、それを使用する方法を学び、尊敬してこの強力な知識を共有したいですハブルの住民。 また、JITコンパイラーであることに注意してください。つまり、競争力のある高いパフォーマンスを備えています(まだ測定していません)。

Avianは、非常に細かく機能的なクラスライブラリ標準とともにアプリケーションに埋め込むことができ、プログラムはフック付きのメガバイトのみで「より重い」ものになります。 そのようなプログラムをまとめましょう。

0.水曜日


ビルドするには、最初にunix-developerコマンドラインユーティリティ、特にg ++コンパイラが必要です。 Windowsユーザーにとって最も難しいこと。 Unix端末をエミュレートする素晴らしいMSYS環境を備えたWindowsでMinGW32を使用していました。 MinGW32に含まれるコンパイラは32ビットであり、何らかの方法で結果のプログラムを制限します。 コメントで、彼らは長い間便利なmingw-w64があり、それは定期的に更新され、MSYSだけでなくgitもあると私に言った。

以降、便宜上、スポイラーの下にプラットフォーム固有の指示を隠しました。

MinGWをダウンロードするには、 sourceforge.net / projects / mingwbuildsにアクセスして、2つのアーカイブをダウンロードします:x64- XXX -release-posix-seh-rev X .7zおよびexternal-binary-packages / msys + 7za + wget + svn + git + mercurial + cvs-rev X .7z

それらがダウンロードされた後、私たちは便利なディレクトリにそれらの両方を解凍します。 次に、MSYSにMinGWの場所を伝える必要があります。 これを行うには、 msys/etc移動し、 fstab.sampleファイルに示されているようにfstabファイルに/mingwへのパスを書き込みますfstab.sample
 c:/mingw64 /mingw 


上記のすべてをインストールした後、MSYSターミナルを開きます。 これを行うには、 msys\msys.bat実行しmsys\msys.bat (その隣には2つのアイコンがあります)。 このコマンドは、まずUNIXコマンドとUNIXパスの形式をサポートし、次に必要なすべての環境パラメーターが含まれているため、このターミナルからすべてのアクションを実行します。

OS X
OS Xでは、Xcode 4をダウンロードし、その設定の[ダウンロード]セクションでコマンドラインツールをインストールする必要があります。 次に、Launcherを介してターミナルウィンドウを開くだけです。

Linux
DebianベースのLinuxでは、ターミナルを開いて次のように記述します。

 > sudo apt-get install build-essential 

その後、ターミナルウィンドウを閉じて再度開き、新しい環境設定が読み込まれるようにする必要があります。


すべてのオペレーティングシステムの最終行は、コマンドラインで入力したものでなければなりません。

 > g++ 

それに応じて、次のようなものが表示されます

 g++: fatal error: no input files 

このようなメッセージは、コンパイラーが戦いの準備ができており、ソースファイルを取得しようとしていることを意味します。

1.鳥類


まず、g ++ビルドAvianを提案します。
オープン: oss.readytalk.com/avian ステータスリンクを選択します 。 開いたページで、 Avian 0.6をダウンロードします。 控えめなバージョン番号にもかかわらず、プログラムは完全に安定しています(いずれにしても、私はそれを落とすことができませんでした。また、バグトラッカーには非常にトリッキーなバグが表示されます。

ダウンロードしたAvianソースファイルを特定のフォルダーに解凍します(〜/ Projectsにします)。

 > cd ~/Projects 

Windowsでは、MSYSの〜フォルダーはユーザーのホームフォルダーではなく、C:\ MinGW64 \ msys \ home \ usernameフォルダーに添付されます。 私たちの場合、プラットフォームから可能な限り遠くに離れたいので、これは利点ですらあります。

ダウンロードしたアーカイブの名前がavian-0.6.tar.bz2で、〜/ Downloadsにあるとし、次に入力して現在のフォルダーに解凍します。

 > tar -xjf ~/Downloads/avian-0.6.tar.bz2 

Windowsでは、パスは、/ c / Users / username / Downloads / avian-0.6.tar.bz2の形式でスラッシュを含むmingw形式で指定する必要があります。 もちろん、あなたはアーカイブを解凍するための百の代替方法のいずれかを使用することができます-主なことは、それが現在のフォルダに分類されることです。 その結果、アーカイブから展開された鳥類のサブフォルダーがそこに表示されます。 それに行きましょう:

 > cd avian 

これでmakeコマンドを実行できますが、今すぐアセンブリを起動すると、zlibが見つからなかったというメッセージが表示される可能性が高くなります。 zlib.hのようなもの:そのようなファイルやディレクトリはありません。

Windowsの場合、Avianの著者が提案した方法、つまりzlibライブラリを特別なwin64補助リポジトリからスリップする必要があります。 これを行うには、システムにgitをインストールする必要があります。 幸い、gitはインストールしたmsysビルドの一部です。 残念ながら、この記事を書いている時点では、アセンブリは曲がっています。msys-crypto-0.9.8.dllファイルがありません。このファイルは、Googleで見つけて、役に立たない兄弟の横にmsys-crypto-1.0.0.dllを置く必要があります。このアセンブリは完了です。

次に、鳥類のフォルダーからコマンドを実行する必要があります

 > git clone git://oss.readytalk.com/win64.git ../win64 

これにより、特にzlibで鳥類が必要とする可能性のあるすべてのライブラリを含む鳥類フォルダーの隣にwin64フォルダーが配置されます。

OS X
OS Xでは、私が覚えている限りでは、このライブラリは自動的にインストールされます(おそらくインストール済みの開発者ツールを使用して)。

Linux
Linuxでは、この問題は簡単に解決されます

 > sudo apt-get install zlib1g-dev 


ただし、zlibをインストールしてmake再度入力makeと、別のエラーが発生します-Avianコレクターは/ bin / javacプログラムを見つけられません。 Java開発者はこのプログラムを認識する可能性があります-これはJavaコンパイラです。 Avianは仮想マシンにすぎないため、Oracleの公式コンパイラを引き続き使用します。 VM自体を構築する場合、ソースJavaファイルからSystemArrayListまたはHashMapなどの小さなAvian標準ライブラリのクラスをコンパイルするために必要です。 したがって、ADKを構築するときと、それを使用するアプリケーションを構築するときの両方で、JDKは開発者のマシン上にある必要があります。 そして、Avian 0.6と互換性のあるJDK7をインストールすることが望ましいです。 JREのようなアプリケーションのユーザーは、それをもう必要としません(実際、これを試みています)。

Windowsで、Oracle Webサイトにアクセスし、目的のディストリビューションをダウンロードしてインストールします。

OS X
OS Xでは、Windowsと同様に、Oracle Webサイトにアクセスして目的のディストリビューションをダウンロードし、インストールします。

Linux
Linuxでは、通常のマントラを使用します

 > sudo apt-get install openjdk-7-jdk 

(おそらく、ディストリビューションにOpenJDKが含まれていないか、パッケージの名前が他の方法で付けられている可能性があります。しかし、LinuxはLinuxです-見て見つけてください。)

Java開発環境を配置した場所を確認するために、makeはJAVA_HOME環境変数を読み取ります。この環境変数を正しく設定する必要があります。 独自のプロジェクトを組み立てるために、後で同じ値を持つこの同じ変数が必要になります。

Windows上のMinGWへのパスは、おそらく/ c / Program \ Files / Java / jdk1.7.0_07 /(JDK7のインストール時に指定した)のようなものになります。

OS X
OS Xでは、JDK7は/Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Homeにインストールされます

Linux
Linuxでは、次のようになります。

 > update-java-alternatives -l java-1.7.0-openjdk-amd64 1071 /usr/lib/jvm/java-1.7.0-openjdk-amd64 > export JAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk-amd64 


そして最後に、最高の時間-私たちはAvianを収集します:

 > make 

すべてを正しく行った場合、build / <プラットフォーム名> / <ファイル>をコンパイルし、次にbuild / <プラットフォーム名> / <ファイル>をリンクする形式の一連の行が表示されます。 アセンブリプロセスの終了時に、build / <your platform_name>フォルダーに多くのファイルを受け取りますが、ここでのみ関心があります。



2. Javaのクロスプラットフォームに依存しないモノリシックhello


必要なすべてのサードパーティコードをコンパイルしました。 では、独自に作成しましょう。

タスクは、Javaで書かれたプログラムを作成することです。プログラムは、できるだけプラットフォームに依存しないブックマークをできるだけ少なく含み、単独で特別なインストールを必要とせず、「クリーン」システムで動作する単一のexeファイルにアセンブルされます依存関係のインストール。

2.1。 JNIについて少し

理論から始めましょう。 JVMがシステムと対話する方法について説明しましょう。 まず、外部環境から抽象化するために、仮想マシンが作成されます。 したがって、VMの実装のボトルネックがシステム関数の呼び出しだけであることは驚くことではありません。 最新のプログラムは、OSの参加なしには「くしゃみ」さえできません。 ディスクへの読み取り/書き込みはシステム機能です。 コンソールへのテキスト出力はシステム関数です。 画面にウィンドウを描画します-あなたはどう思いますか?

実際、アプリケーションが「内部で」実行できるのは、計算と意思決定だけです。 VMの機能は、これらのアクション(算術演算とロジック)です。 他の何かをする必要があるとすぐに、彼女は外部環境を呼び出します。 しかし、どのように? Javaの場合、このためのJNI(Java Native Interface)があります。 その本質は非常にシンプルです。 Javaで記述されたプログラムには、ネイティブ修飾子でマークされた関数ヘッダーが含まれています。 例えば

 package packagename; { class ClassName { void native foo(); } } 

このような関数は、Javaコンパイラーによって、通常の(非仮想)コードのロードされたライブラリーから呼び出される関数として理解されます。 これらのライブラリの1つは次のようになります

 extern "C" JNIEXPORT void JNICALL Java_packagename_ClassName_foo(JNIEnv * env, jobject caller) { … } 

Javaコードでfoo()関数を呼び出す場合、実際にはネイティブライブラリから関数を呼び出し、 JNIEnv環境へのポインターを渡しますJNIEnv内のデータとコードと「通信」できるオブジェクト、および関数が呼び出されるオブジェクトへのポインター- jobject caller (関数が静的である場合、オブジェクトの記述子の代わりにクラス記述子jclass caller_class )。 Javaに精通しているがJNIを学んでいない人にとって、この相互作用の原理は次のように説明できます。JNIは外部ネイティブコードがJavaプログラムを反映できるようにします。 このテクノロジーをさらに詳しく調べたい場合は、オラクルの公式Webサイトの特別なセクションにようこそ。

2.2。 JNIダイヤリング

なぜこの教育プログラムがすべてあったのですか? 次に、非常に興味深い、ほぼ逆のタスクに直面しています。 libavian.aライブラリに静的にリンクされている、ネイティブ実行可能ファイルを実行する必要があります。このファイルには、JVMが直接含まれます。 さらに、「エントリポイント」を含むすべての必要なJavaクラスが含まれます-フォームのクラス

 class Application { public static void main(String... args) { … } } 

それはすべてかなり怖いように聞こえますが、タスクは非常に簡単です。 独自のバイナリファイル内からAvianクラスライブラリ( Applicationクラスが追加されている)を引き出し、同じJNIを使​​用してコマンドラインパラメーターと共にJVMに供給する、かなり単純なCコードを記述する必要があります。 次に、このCファイルを特別な方法でリンクして、すべてがその場所にあるようにし、結果を楽しみます。

2.3。 新しいプロジェクトとライブラリ

次に、さらに作業を進めるために必要なすべてのコンポーネントを用意して整理します。 ここで説明するのは、私自身のアプローチです。 もちろん、あなたは望むように、物事を自由に変えることができます。 しかし、GitHubに投稿した内容を正確に取得したい場合(リンクは最後になります)、すべてを正確に実行してください。

クロスベースフォルダーを好きな場所に作成します(鳥類とwin32の隣のプロジェクトで作成しました)

 > mkdir crossbase && cd crossbase 

内部で、libsのサブフォルダーを作成します

 > mkdir lib && cd lib 

内部で、現在のOSの名前でサブフォルダーを作成します。 linux、win32、またはosxである必要があります。

 > mkdir win-x86_64 && cd win-x86_64 

このフォルダに、先ほど収集したlibavian.aをコピーする必要があります。 私にとってはこのように見えます:

 > cp ../../../avian/build/windows-i386/libavian.a ./ 

さらに、zlibがないWindowsシステムでは、libz.aも同じフォルダーにコピーする必要があります。

 > cp ../../../win-x86_64/lib/libz.a ./ 

したがって、必要な最小限のライブラリを収集しました。 これは単純なプログラムには十分です。

ライブラリに加えて、classpath.jarも必要になります。これもavianでビルドされました。

 > cd .. > mkdir java && cd java > cp ../../../avian/build/windows-i386/classpath.jar ./ 

そして、今度は神秘的なbinaryToObjectの目的を明らかにします。 jarファイルを特別なオブジェクトファイルに変換し、リンカーに転送してプログラムに追加するために必要です。 この手順は各アセンブリ中に実行する必要があるため、新しいプロジェクトにドラッグする必要もあります。

 > cd ../.. 

(libを作成したcrossbaseフォルダーに戻ります)

 > mkdir -p tools/win-x86_64 && cd tools/win-x86_64 

名前win-x86_64は、前回と同じ方法で内部フォルダーに割り当てられます。 ここにbinaryToObjectをスローします。 (Windowsでは、もちろんexe拡張子が付いています)

 > cp ../../../avian/build/windows-i386/binaryToObject/binaryToObject.exe ./ 

実行して使用方法を確認できます。

 usage: c:\Users\imizus\Projects\crossbase\crossbase\tools\win32\binaryToObject.exe <input file> <output file> <start name> <end name> <platform> <architecture> [<alignment> [{writable|executable}...]] 


2.4。 プログラムコード

それでは、コードを書き始めましょう。 C ++で新しいソースファイルを作成しましょう(お好みのテキストエディターを使用できます。Eclipseを使用します。同じプロジェクト内でC ++とJavaの両方を編集できますが、少し構成する必要があります)。

 > mkdir -p src/cpp && cd src/cpp 

内部では、次の内容でmain.cppファイルを作成します(その全体を説明し、次に何を説明します)。

 #include <stdint.h> #include <string.h> #ifdef __MINGW32__ #include <windows.h> #endif #include <jni.h> #if (defined __MINGW32__) # define EXPORT __declspec(dllexport) #else # define EXPORT __attribute__ ((visibility("default"))) \ __attribute__ ((used)) #endif #if (! defined __x86_64__) && (defined __MINGW32__) # define SYMBOL(x) binary_boot_jar_##x #else # define SYMBOL(x) _binary_boot_jar_##x #endif extern "C" { extern const uint8_t SYMBOL(start)[]; extern const uint8_t SYMBOL(end)[]; EXPORT const uint8_t* bootJar(unsigned* size) { *size = SYMBOL(end) - SYMBOL(start); return SYMBOL(start); } } // extern "C" int main(int argc, const char** argv) { #ifdef __MINGW32__ // For Windows: Getting command line as a wide string int wac = 0; wchar_t** wav; wav = CommandLineToArgvW(GetCommandLineW(), &wac); #else // For other OS: Getting command line as a plain string (encoded in UTF8) int wac = argc; const char** wav = argv; #endif JavaVMInitArgs vmArgs; vmArgs.version = JNI_VERSION_1_2; vmArgs.nOptions = 1; vmArgs.ignoreUnrecognized = JNI_TRUE; JavaVMOption options[vmArgs.nOptions]; vmArgs.options = options; options[0].optionString = const_cast<char*>("-Xbootclasspath:[bootJar]"); JavaVM* vm; void* env; JNI_CreateJavaVM(&vm, &env, &vmArgs); JNIEnv* e = static_cast<JNIEnv*>(env); jclass c = e->FindClass("crossbase/Application"); if (not e->ExceptionCheck()) { jmethodID m = e->GetStaticMethodID(c, "main", "([Ljava/lang/String;)V"); if (not e->ExceptionCheck()) { jclass stringClass = e->FindClass("java/lang/String"); if (not e->ExceptionCheck()) { jobjectArray a = e->NewObjectArray(wac - 1, stringClass, 0); if (not e->ExceptionCheck()) { for (int i = 1; i < wac; ++i) { #ifdef __MINGW32__ // For Windows: Sending wide string to Java int arglen = wcslen(wav[i]); jstring arg = e->NewString((jchar*) (wav[i]), arglen); #else // For other OS: Sending UTF8-encoded string to Java int arglen = strlen(wav[i]); jstring arg = e->NewStringUTF((char*) (wav[i])); #endif e->SetObjectArrayElement(a, i - 1, arg); } e->CallStaticVoidMethod(c, m, a); } } } } int exitCode = 0; if (e->ExceptionCheck()) { exitCode = -1; e->ExceptionDescribe(); } vm->DestroyJavaVM(); return exitCode; } 


__MINGW32__は、(予想外に!)MinGW32環境内で自動的に設定されるプリプロセッサシンボルです。 これにより、Windowsを区別することができます。Windowsは、皆さんが考えているように、他のすべてのシステムとはまったく異なることに気づいています。 特に、Windowsでのみ特別なシステムAPIが必要になります。これは、 #include <windows.h>行で接続します。 他のプラットフォームでは、標準のPOSIXおよびANSI C ++ライブラリは不要です。 なぜ必要なのですか? それは少し後に明らかになります。 コードを順番に表示します。

 #if (defined __MINGW32__) # define EXPORT __declspec(dllexport) #else # define EXPORT __attribute__ ((visibility("default"))) \ __attribute__ ((used)) #endif 

このコードは、gccを使用してクロスプラットフォームの動的ライブラリを作成したすべての人になじみやすく理解しやすいものです。 その本質は、異なるオペレーティングシステムでは、ライブラリからエクスポートする必要のある関数の記述が異なることです。 「そして、ここに動的ライブラリがあります。実行可能ファイルを収集しているからですか?」 それに応じて、Avianはライブラリから関数を呼び出すことを含むJNIメカニズムを介してプラットフォーム固有のコードと対話することを思い出します。 つまり、Javaコードでは、実行可能ファイルはスタートアッププログラムだけでなく、関数の動的ライブラリでもあります。

次の部分は奇妙な魔法です。
 #if (! defined __x86_64__) && (defined __MINGW32__) # define SYMBOL(x) binary_boot_jar_##x #else # define SYMBOL(x) _binary_boot_jar_##x #endif extern "C" { extern const uint8_t SYMBOL(start)[]; extern const uint8_t SYMBOL(end)[]; EXPORT const uint8_t* bootJar(unsigned* size) { *size = SYMBOL(end) - SYMBOL(start); return SYMBOL(start); } } // extern "C" 

それを理解しましょう。 特定のエクスポート関数を宣言します( extern "C"と入力したばかりのEXPORTディレクティブを見てください。関数名はbootJarです。この名前をbootJar 、それが何をするかを見てください。プリプロセッサディレクティブを精神的に解析すると、距離を計算することがわかりますいくつかの_binary_boot_jar_startと_binary_boot_jar_endの間(MinGW32では、先頭にアンダースコアはありません)これらの文字自体はexternとして宣言されています。つまり、リンカで置換する必要があります。

実際、以下で説明するように、何をすべきかを知っていれば、すべてが非常に簡単です。 Avianはアプリケーションに埋め込まれるように設計されているため、作成者はクラスライブラリを実行可能ファイルに直接追加し、そこからダウンロードする可能性を提供しました。 これを行うには、ライブラリをオブジェクトファイルに変換するだけです。 はい、はい、私も最初は驚きましたが、これは非常にエレガントなアイデアです。 jarを含むオブジェクトファイルでは、作成すると2文字が宣言され、このjarファイルの開始( _binary_boot_jar_start )と終了( _binary_boot_jar_end )を示します。 そして、 bootJarbootJar関数を使用して、開始場所とその期間を調べます。 先を見て、この関数の名前は文字列として渡されると言います

 options[0].optionString = const_cast<char*>("-Xbootclasspath:[bootJar]"); 

最後に、エントリポイント- main関数に到達しました。 彼女の仕事は次のとおりです。


関数の最初から行きましょう:

 #ifdef __MINGW32__ // For Windows: Getting command line as a wide string int wac = 0; wchar_t** wav; wav = CommandLineToArgvW(GetCommandLineW(), &wac); #else // For other OS: Getting command line as a plain string (encoded in UTF8) int wac = argc; const char** wav = argv; #endif 

ここで、いつものように、Windowsは優れていました。 古い不便なシングルバイトエンコーディングからより複雑なエンコーディングに切り替えるという決定が至る所で行われたとき、すべてのOSは便利なUTF-8に切り替わり、Microsoftのお気に入りのブレインチャイルドは固定2バイトエンコーディングに切り替わりました。 ただし、ファイル名などで使用されるエンコーディングについてはまったく気にしませんでした。 しかし、エンコーディングは今ではあまり気にしません。 Javaでパラメーター文字列を渡す必要があります(これは2バイトcharも受け入れます)。 したがって、Windowsの場合、API関数(windows.hをドラッグしたもの)を呼び出します。これにより、正しい2バイトエンコーディングのパラメーター文字列が提供されます。 そのため、たとえば、名前にキリル文字が含まれるファイルを開くことができます。 他のすべてのシステムでは、 main関数の引数からパラメーターを読み取るだけです。

以下は、Java仮想マシンの作成です。

 JavaVMInitArgs vmArgs; vmArgs.version = JNI_VERSION_1_2; vmArgs.nOptions = 1; vmArgs.ignoreUnrecognized = JNI_TRUE; JavaVMOption options[vmArgs.nOptions]; vmArgs.options = options; options[0].optionString = const_cast<char*>("-Xbootclasspath:[bootJar]"); JavaVM* vm; void* env; JNI_CreateJavaVM(&vm, &env, &vmArgs); JNIEnv* e = static_cast<JNIEnv*>(env); 

また、 JNIEnvオブジェクトへのポインターを引き出します。これを使用して、新しく作成されたJavaマシンにコマンドをJNIEnvます。

さらにコードは、Mayakovskyの詩として読み取られます(少しのJNIを知っている場合のみ)。

 jclass c = e->FindClass("crossbase/Application"); if (not e->ExceptionCheck()) { jmethodID m = e->GetStaticMethodID(c, "main", "([Ljava/lang/String;)V"); if (not e->ExceptionCheck()) { jclass stringClass = e->FindClass("java/lang/String"); if (not e->ExceptionCheck()) { jobjectArray a = e->NewObjectArray(wac - 1, stringClass, 0); if (not e->ExceptionCheck()) { for (int i = 1; i < wac; ++i) { #ifdef __MINGW32__ // For Windows: Sending wide string to Java int arglen = wcslen(wav[i]); jstring arg = e->NewString((jchar*) (wav[i]), arglen); #else // For other OS: Sending UTF8-encoded string to Java int arglen = strlen(wav[i]); jstring arg = e->NewStringUTF((char*) (wav[i])); #endif e->SetObjectArrayElement(a, i - 1, arg); } e->CallStaticVoidMethod(c, m, a); } } } } int exitCode = 0; if (e->ExceptionCheck()) { exitCode = -1; e->ExceptionDescribe(); } 

crossbase/Applicationクラスをcrossbase/Application 。 可能であれば、その中にシグネチャ([Ljava/lang/String;)V持つ静的メソッドmainを見つけます([Ljava/lang/String;)V 可能であれば、標準ライブラリからjava/lang/Stringクラスを取得します。 可能であれば、このクラスのオブジェクトの配列を作成します(これらはパラメーターになります)。可能であれば、すべてのオペレーティングシステムでencoding UTF-8指定された各パラメーターからjava文字列を作成し、Windowsでは2バイト表現を使用して直接作成します。

何もできなかった場合、ユーザーにエラーを発行します。

それは実際、全体の「トリガー」です。次に、Javaプログラムを作成する必要があります。少なくとも、crossbase.Applicationmethodを持つクラス含める必要がありpublic static void main(String... args)ます。

crossbase / srcフォルダーにjavaサブフォルダーを作成し、その中にクロスベースサブフォルダー(これはパッケージ名です)を作成し、内部に次の内容のApplication.javaファイルを作成します。

 package crossbase; public class Application { public static void main(String... args) { System.out.println("This is a crossplatform monolith application with Java code inside. Freedom to Java apps!"); for (int i = 0; i < args.length; i++) { System.out.println("args[" + i + "] = " + args[i]); } } } 

少しJavaを知っているなら、ここでのコメントは不要だと思います。標準のAvianクラスライブラリには、文字列をフォーマットする手段がありません(たとえば、OpenJDKから静かに引き離す人はいません)。

2.5。 組立


それでは、プロジェクトを組み立てるタスクに移りましょう。makeを使用するのは、gccが常にどこにでもあるからです。そして彼は、ほぼすべての自動ビルドシステムに書き込むことができるほど強力です。いやいや。あなたが指で私が作ることができなかったことをリストすることができます、そして、これらはほとんど重要なものでした。Makefileは、クロスベースフォルダーに直接配置され、次のようになります。

 UNAME := $(shell uname) ARCH := $(shell uname -m) SRC = src BIN = bin OBJ = obj JAVA_SOURCE_PATH = $(SRC)/java JAVA_CLASSPATH = $(BIN)/java CPP_SOURCE_PATH = $(SRC)/cpp OBJECTS = $(OBJ) DEBUG_OPTIMIZE = -O3 #-O0 -g ifeq ($(UNAME), Darwin) # OS X PLATFORM_ARCH = darwin x86_64 PLATFORM_LIBS = osx-x86_64 PLATFORM_GENERAL_INCLUDES = -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/darwin" PLATFORM_GENERAL_LINKER_OPTIONS = -framework Carbon PLATFORM_CONSOLE_OPTION = EXE_EXT= STRIP_OPTIONS=-S -x RDYNAMIC=-rdynamic else ifeq ($(UNAME) $(ARCH), Linux x86_64) # linux on PC PLATFORM_ARCH = linux x86_64 PLATFORM_LIBS = linux-x86_64 PLATFORM_GENERAL_INCLUDES = -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/linux" PLATFORM_GENERAL_LINKER_OPTIONS = -lpthread -ldl PLATFORM_CONSOLE_OPTION = EXE_EXT= STRIP_OPTIONS=--strip-all RDYNAMIC=-rdynamic else ifeq ($(OS), Windows_NT) # Windows PLATFORM_ARCH = windows x86_64 PLATFORM_LIBS = win-x86_64 PLATFORM_GENERAL_INCLUDES = -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/win32" PLATFORM_GENERAL_LINKER_OPTIONS = -static -lmingw32 -lmingwthrd -lws2_32 -mwindows -static-libgcc -static-libstdc++ PLATFORM_CONSOLE_OPTION = -mconsole EXE_EXT=.exe STRIP_OPTIONS=--strip-all RDYNAMIC= endif JAVA_FILES = $(shell cd $(JAVA_SOURCE_PATH); find . -name \*.java | awk '{ sub(/.\//,"") }; 1') JAVA_CLASSES := $(addprefix $(JAVA_CLASSPATH)/,$(addsuffix .class,$(basename $(JAVA_FILES)))) CPP_FILES = $(shell cd $(CPP_SOURCE_PATH); find . -name \*.cpp | awk '{ sub(/.\//,"") }; 1') CPP_OBJECTS := $(addprefix $(OBJECTS)/,$(addsuffix .o,$(basename $(CPP_FILES)))) all: $(BIN)/crossbase $(JAVA_CLASSPATH)/%.class: $(JAVA_SOURCE_PATH)/%.java @echo $(PLATFORM_GENERAL_INCLUDES) if [ ! -d "$(dir $@)" ]; then mkdir -p "$(dir $@)"; fi "$(JAVA_HOME)/bin/javac" -sourcepath "$(JAVA_SOURCE_PATH)" -classpath "$(JAVA_CLASSPATH)" -d "$(JAVA_CLASSPATH)" $< $(OBJ)/%.o: $(SRC)/cpp/%.cpp @echo $(PLATFORM_GENERAL_INCLUDES) mkdir -p $(OBJ) g++ $(DEBUG_OPTIMIZE) -D_JNI_IMPLEMENTATION_ -c $(PLATFORM_GENERAL_INCLUDES) $< -o $@ $(BIN)/crossbase: $(JAVA_CLASSES) $(CPP_OBJECTS) mkdir -p $(BIN); @echo $(PLATFORM_GENERAL_INCLUDES) # Extracting libavian objects ( \ cd $(OBJ); \ mkdir -p libavian; \ cd libavian; \ ar x ../../lib/$(PLATFORM_LIBS)/libavian.a; \ ) # Making the java class library cp lib/java/classpath.jar $(BIN)/boot.jar; \ ( \ cd $(BIN); \ "$(JAVA_HOME)/bin/jar" u0f boot.jar -C java .; \ ) # Making an object file from the java class library tools/$(PLATFORM_LIBS)/binaryToObject $(BIN)/boot.jar $(OBJ)/boot.jar.o _binary_boot_jar_start _binary_boot_jar_end $(PLATFORM_ARCH); \ g++ $(RDYNAMIC) $(DEBUG_OPTIMIZE) -Llib/$(PLATFORM_LIBS) $(OBJ)/boot.jar.o $(CPP_OBJECTS) $(OBJ)/libavian/*.o $(PLATFORM_GENERAL_LINKER_OPTIONS) $(PLATFORM_CONSOLE_OPTION) -lm -lz -o $@ strip $(STRIP_OPTIONS) $@$(EXE_EXT) clean: rm -rf $(OBJ) rm -rf $(BIN) .PHONY: all 

注意してください!タブとスペースを混同しないでください; makeタブでは、アセンブリルール内のコマンドが強調表示され、スペースは構文要素ではありません。それがどのように機能するかを詳しく見てみましょう。多かれ少なかれ唯一の脳の構造は、これらの変数の目的です:

 JAVA_FILES = $(shell cd $(JAVA_SOURCE_PATH); find . -name \*.java | awk '{ sub(/.\//,"") }; 1') JAVA_CLASSES := $(addprefix $(JAVA_CLASSPATH)/,$(addsuffix .class,$(basename $(JAVA_FILES)))) CPP_FILES = $(shell cd $(CPP_SOURCE_PATH); find . -name \*.cpp | awk '{ sub(/.\//,"") }; 1') CPP_OBJECTS := $(addprefix $(OBJECTS)/,$(addsuffix .o,$(basename $(CPP_FILES)))) 

ここでは.java、フォルダ内のすべてのファイルを検索するためにfind unixコマンドを使用しています$(JAVA_SOURCE_PATH)これらのファイルをコンパイルする必要があります。次に、それらから拡張子をかみ、それを.classに置き換え$(JAVA_CLASSPATH)、取得する必要があるクラスファイルの名前を取得します。ターゲット名。ファイル.cppとにも同じことを行います.oさらにmakefileには、次のビルドルールがあります。

 $(JAVA_CLASSPATH)/%.class: $(JAVA_SOURCE_PATH)/%.java @echo $(PLATFORM_GENERAL_INCLUDES) if [ ! -d "$(dir $@)" ]; then mkdir -p "$(dir $@)"; fi "$(JAVA_HOME)/bin/javac" -sourcepath "$(JAVA_SOURCE_PATH)" -classpath "$(JAVA_CLASSPATH)" -d "$(JAVA_CLASSPATH)" $< $(OBJ)/%.o: $(SRC)/cpp/%.cpp @echo $(PLATFORM_GENERAL_INCLUDES) mkdir -p $(OBJ) g++ $(DEBUG_OPTIMIZE) -D_JNI_IMPLEMENTATION_ -c $(PLATFORM_GENERAL_INCLUDES) $< -o $@ 

これらのルールは、ソースファイルをターゲットにコンパイルする方法を説明しています。そして最後に、ターゲットを見てみましょう

 $(BIN)/crossbase: $(JAVA_CLASSES) $(CPP_OBJECTS) ... 

ここでは、見つかったすべてのファイルへの依存関係を確認します。つまり、makefileは、提供されるすべてのjavaおよびcppファイルを正しいフォルダーに収集するように作成されます。

その他の重要な点:



アセンブリ規則のカジュアルな規則を見てみましょう- $(BIN)/crossbase: $(JAVA_CLASSES) $(CPP_OBJECTS)。最初に、libavian.aからすべてのオブジェクトファイルを解凍し、名前でリンカーに転送します。動作は奇妙ですが、無意味ではありません。 Windowsでは、これにより何らかの奇妙なリンクの問題が解決されます(十分に理解できませんでした)。次に、classpath.jarを取得し、コンパイル済みのクラスをbin / javaから追加して、すべてをbin / boot.jarにまとめます。次に、binaryToObjectを呼び出します。これは、シンボル_binary_boot_jar_startおよび_binary_boot_jar_end(main.oにインポートした)を使用してboot.jarからobj / boot.jar.oオブジェクトファイルを作成します。そして最後に、この不名誉をすべて結び付けます。そして最後に、マジックストリップコマンドを実行します。このパラメーターでは、今回はOS XがMinGWやLinuxとは異なる独自のパラメーターを使用しています。このコマンドの目的は、実行可能ファイルから左の文字を捨てることです。開発前は、クロスベースの重量は9メガバイトを超えていました。1.5未満でした。

3.勝利の瞬間


crossbase / binフォルダーに入ったら、コンソールからクロスベースを実行し、パラメーターを渡します。

 > ./crossbase  ! This is a crossplatform monolith application with Java code inside. Freedom to Java apps! args[0] =  args[1] = ! 


結果のプロジェクトは私のGitHubにあります

4.結果と意味


この記事の利点を評価することは難しいと思います。少なくとも私が彼女に招待されるなら、それは彼女が少なくとも面白くないということを意味します。複雑そうに見えるだけで、この方法は、たとえば純粋なC ++でプログラムを作成するのに比べて、十分に成果を上げていると言えます。プロジェクトが少なくとも数十のクラスに成長すると、Javaは非常に便利になります。 C ++でコードを書くときに非常に注意を払っても、エラーをキャッチするのが非常に難しいための抜け穴が残っています。したがって、Javaで制御コード(超高性能を必要としない)を作成することをすべての人に勧めます。最高速度を必要とするコードはC ++で記述でき、C ++ Javaクラスをクラスでラップするのは非常に簡単できれいです。たぶん、私はそれを美しくする方法を書いて、熊手にぶつからないようにします。

当初、この「サンドイッチ」にクロスプラットフォームユーザーインターフェイスSWT(Eclipseで使用される)を追加することに専念する記事の章を作成することを計画していましたが、長すぎて重すぎると判断しました。紳士が興味のある読者である場合、私はこれについて別に書きます。ご清聴ありがとうございました!

PS
ハブロフスク市民から多くのレビューを受けて、記事とプログラムを完成させました。アドバイスと訂正をしてくれたみんなに感謝します。

Source: https://habr.com/ru/post/J176237/


All Articles