友達をJavaとC ++にする方法。 パート1

こんにちは。

おそらくすでにご想像のとおり、JNIについて説明します。 これが何であるかを知らない人のために、私は説明します:JNI(またはJavaネイティブインターフェイス)は、Javaマシンからネイティブコード呼び出しを行うことを可能にするものです。

なぜこれが必要なのでしょうか? いくつかの理由があります:ネイティブプラットフォーム用に既に記述されたコードを使用する必要性、単一のJVMで実行できないものを実装する必要性(たとえば、特定の鉄片での作業)、および重要なコード部分の実行の高速化(ただし、これは非常に物議を醸す点です)。

JNIの仕組み

c ++で記述され、動的にリンクされたライブラリ(たとえば、Windowsではdllになります)にあるメソッドを呼び出す必要のある種類のjavaクラスがあるとします。 これのために何をすべきですか?

まず、あるクラスのメソッドをネイティブとして宣言します。 これは、JVMがこのメソッドを呼び出すときに、制御をネイティブコードに転送することを意味します。

次に、ネイティブライブラリをロードする必要があります。 これを行うには、パラメーターとしてライブラリー名を取るSystem.loadLibrary(String)呼び出すことができます。 この呼び出しの後、ライブラリはJVMアドレス空間にロードされます。

ここで、次のJavaクラスがあるとします。
パッケージ my.mega.pack ;

パブリック クラス NativeCallsClass
{
静的
{
システム loadLibrary "megalib" ;
}

ネイティブ public static void printOne ;
ネイティブ public static void printTwo ;
}
ここでは、便宜上、クラスの静的領域にloadLibrary()を配置します。

NativeCallsClass.printOne()を呼び出すと仮定します。 次に、JVMは、ライブラリでJava_my_mega_pack_NativeCallsClass_printOne(...)という名前のメソッドを探します。

C ++でのJNI関数の宣言

ネイティブとしてマークされたメソッドを持つクラスをjavaで作成しました。 ここで、呼び出すC ++関数の宣言を使用してヘッダーを作成する必要があります。

もちろん、手動で作成することもできます。 しかし、もっと便利な方法があります:

 javac -d bin / src / my / mega / pack / NativeCallsClass.java
 cd bin
 javah my.mega.pack.NativeCallsClass

クラスをコンパイルしてから、javahユーティリティを使用します。 その後、my_mega_pack_NativeCallsClass.hというファイルが作成されます。 これがヘッダーです。 次のようになります。
/ *このファイルは編集しないでください-マシンで生成されます* /
#include <jni.h>
/ *クラスmy_mega_pack_NativeCallsClassのヘッダー* /

#ifndef _Included_my_mega_pack_NativeCallsClass
#define _Included_my_mega_pack_NativeCallsClass
#ifdef __cplusplus
extern "C" {
#endif
/ *
*クラス:my_mega_pack_NativeCallsClass
*メソッド:printOne
*署名:()V
* /
JNIEXPORT void JNICALL Java_my_mega_pack_NativeCallsClass_printOne
JNIEnv * 、jclass ;

/ *
*クラス:my_mega_pack_NativeCallsClass
*メソッド:printTwo
*署名:()V
* /
JNIEXPORT void JNICALL Java_my_mega_pack_NativeCallsClass_printTwo
JNIEnv * 、jclass ;

#ifdef __cplusplus
}
#endif
#endif
ここで最も重要なことは、 Java_my_mega_pack_NativeCallsClass_printOne(JNIEnv *env, jclass myclass)Java_my_mega_pack_NativeCallsClass_printTwo(JNIEnv *env, jclass myclass) Java_my_mega_pack_NativeCallsClass_printOne(JNIEnv *env, jclass myclass)の2つの関数のシグネチャです。

それらを実現する必要があります。 まず、署名を扱います。 envは、仮想マシンへのインターフェースです。 JVMを使用したすべての操作は、JVMを使用して実行されます。 後でこれをより詳細に分析します。 myclassは、この関数で識別されるネイティブメソッドを持つJavaクラスの識別子NativeCallsClassNativeCallsClass 、この例ではNativeCallsClassです。 メソッドが静的として宣言されている場合、jclassは2番目のパラメーターとして渡されることに注意してください。 通常のメソッドの場合、メソッドを呼び出したオブジェクトを識別するジョブジェクトが与えられます(実際、これはこれに類似しています)。

これらの機能のみを実装できます。
#include <iostream>
#include "my_mega_pack_NativeCallsClass.h"


JNIEXPORT void JNICALL Java_my_mega_pack_NativeCallsClass_printOne JNIEnv * env、jclass myclass
{
std :: cout << "One" << std :: endl ;
}

JNIEXPORT void JNICALL Java_my_mega_pack_NativeCallsClass_printTwo JNIEnv * env、jclass myclass
{
std :: cout << "Two" << std :: endl ;
}

データをネイティブコードに、またはその逆に転送します

もっと複雑な動作を実装しましょう。 inputIntとoutputIntの2つのメソッドがあります。 それらの1つはコンソールから番号を読み取り、2つ目は出力します。 javaクラスは次のようになります。
パッケージ my.mega.pack ;

パブリック クラス NativeCallsClass
{
静的
{
システム loadLibrary "megalib" ;
}

ネイティブ public static int inputInt ;
ネイティブ パブリック 静的 void outputInt int v ;
}
javahを起動すると、メソッドシグネチャが少し変更されていることがわかります。 現在は次のとおりです。
JNICALL Java_my_mega_pack_NativeCallsClass_inputInt JNIEnv * 、jclass ;
JNIEXPORT void JNICALL Java_my_mega_pack_NativeCallsClass_outputInt JNIEnv * 、jclass、jint ;
jintはtypedefです。 実際、Javaのintに対応するプリミティブ型(たとえば、int)を示します。 ご覧のとおり、タスクは以前のものよりもそれほど複雑ではありませんでした:)私たちの機能は次のようになります。
#include <iostream>
#include "my_mega_pack_NativeCallsClass.h"

JNIEXPORT jint JNICALL Java_my_mega_pack_NativeCallsClass_inputInt JNIEnv * env、jclass myclass
{
int ret;

std :: cin >> ret;

return ret;
}

JNIEXPORT void JNICALL Java_my_mega_pack_NativeCallsClass_outputInt JNIEnv * env、jclass myclass、jint v
{
std :: cout << v << std :: endl ;
}

まとめると

そのため、第1部では、JNIの仕組み、Javaクラスの作成方法、およびJNIを介して呼び出されるC ++関数を作成するタイミングと方法をネイティブ呼び出しできる方法について説明しました。 次のパートでは、C ++コードからJVMと対話し、クラス、オブジェクト、フィールド、メソッドを操作し、C ++クラスを表すプロキシJavaクラスを作成し、C ++コードからJVMを実行することを検討します。

当然、継続は誰かにとって興味深い場合のみです:)

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


All Articles