ネイティブコードからのJavaメソッドの消化可能な呼び出し

C ++とJavaコードを組み合わせたAndroidアプリケーションはかなりあります。 Javaがラッパー/レイヤーとして機能し、C ++がすべての汚い仕事をします。 おそらく、鮮やかな例はゲームです。 この点に関して、システムが提供するシステムプロパティとバンにアクセスするために、ネイティブコードからJavaコードを呼び出す必要があることがよくあります(別のアクティビティへの切り替え、インターネットからの送信またはダウンロード)。 多くの理由がありますが、問題は同じです。毎回最高5行のコードを記述し、関数のどのシグネチャをパラメーターに入れる必要があるかを覚える必要があります。 その後、これらのパラメーターを目的のタイプに変換する必要があります。 チュートリアルの標準的な例:



long f (int n, String s, float g);
      
      





このメソッドの署名文字列は(ILjava / lang / String; F)Jです。



これをすべて覚えておくと便利ですか? jstringのC文字列を翻訳しますか? しません 書きたい:



 CallStaticMethod<long>(className, “f”, 1, 1.2f);
      
      







問題の声明



まず、必要なものを理解します。 本質的に、これらは4つのことです。



  1. メソッドを呼び出します。
  2. パラメーターから、署名行を引き出す必要があります。 はい、はい、これ(ILjava / lang / String; F)J;
  3. パラメータを目的のタイプに変換します。
  4. クラスのユーザーが見たいデータ型を返します。


これですべてです。 簡単そうです。 始めましょうか?



メソッド呼び出し



ここで、ラッパー関数を呼び出す方法に注意する価値があります。 異なる数のパラメーター(0個以上)が存在する可能性があるため、標準ライブラリにはprintのような関数が必要ですが、パラメーターの型とパラメーター自体を拡張するのに便利です。 C ++ 11では、可​​変パターンが登場しました。 それらを使用します。



 template <typename MethodType, typename... Args> MethodType CallStaticMethod(Args... args);
      
      





署名する



開始するには、このタイプのドキュメントにリストされている文字列を取得する必要があります。 次の2つのオプションがあります。

  1. ... elseの場合、typeidとチェーンを使用します。 次のようなものが得られるはずです。

     if (typeid(arg) == typeid(int)) return “I”; else if (typeid(arg) == typeid(float)) return “F”;
          
          





    そして、あなたが必要とするすべてのタイプに対して。
  2. テンプレートとその部分的な類型化を使用します。 このメソッドは、関数が1行で表示され、不要な型比較が行われないという点で興味深いものです。 さらに、これらはすべてテンプレートのインスタンス化の段階になります。 すべては次のようになります。

     template <typename T> std::string GetTypeName(); // int template <> std::string GetTypeName<int>() { return “I”; } // string template <> std::string GetTypeName<const char*>() { return “Ljava/lang/String;”; }
          
          







署名文字列を作成する方法には、再帰的な方法と配列による方法の2つがあります。 最初に、再帰呼び出しを検討します。



 void GetTypeRecursive(std::string&) { } template <typename T, typename... Args> void GetTypeRecursive(std::string& signatureString, T value, Args... args) { signatureString += GetTypeName<T>(); GetTypeRecursive(signatureString, args...); }
      
      





このすべてのinの挑戦:



 template <typename MethodType, typename... Args> MethodType CallStaticMethod(const char* className, const char* mname, Args... args) { std::string signature_string = "("; GetTypeRecursive(signature_string, args...); signature_string += ")"; signature_string += GetTypeName<MethodType>(); return MethodType(); //    }
      
      





再帰は教育目的には適していますが、可能であれば回避することを好みます。 そのような機会があります。 引数は順番に移動し、引数の数を調べることができるため、C ++ 11標準で提供される便利さを使用できます。 コードは次のように変換されます。



 template <typename MethodType, typename... Args> MethodType CallStaticMethod(const char* className, const char* mname, Args... args) { const size_t arg_num = sizeof...(Args); std::string signatures[arg_num] = { GetType(args)... }; std::string signature_string; signature_string.reserve(15); signature_string += "("; for (size_t i = 0; i < arg_num; ++i) signature_string += signatures[i]; signature_string += ")"; signature_string += GetTypeName<MethodType>(); return MethodType(); //    }
      
      





コードはもっと多いようですが、より速く動作します。 少なくとも、必要以上に関数を呼び出さないという事実のため。



データ型変換



CallStaticMethod を呼び出すためのいくつかのオプションがあります



 NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...); NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args); NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
      
      





拷問の試みとトリックの後、CallStaticMethodA(JNIEnv *、jclass、jmethodID、jvalue *)を使用することが決定されました。 ここで、すべてのパラメーターをjvalueにキャストする必要があります。 jvalue自体は結合であり、お気に入りのユーザーから渡されたデータの種類に応じて、目的のフィールドを設定する必要があります。 賢明ではなく、必要な型のコンストラクターでJniHolder構造(またはクラス、好みの問題)を作成します。



ジュニホルダー
 struct JniHolder { jvalue val; JObjectHolder jObject; // bool explicit JniHolder(JNIEnv *env, bool arg) : jObject(env, jobject()) { val.z = arg; } // byte explicit JniHolder(JNIEnv *env, unsigned char arg) : jObject(env, jobject()) { val.b = arg; } // char explicit JniHolder(JNIEnv *env, char arg) : jObject(env, jobject()) { val.c = arg; } // short explicit JniHolder(JNIEnv *env, short arg) : jObject(env, jobject()) { val.s = arg; } // int explicit JniHolder(JNIEnv *env, int arg) : jObject(env, jobject()) { val.i = arg; } // long explicit JniHolder(JNIEnv *env, long arg) : jObject(env, jobject()) { val.j = arg; } // float explicit JniHolder(JNIEnv *env, float arg) : jObject(env, jobject()) { val.f = arg; } // double explicit JniHolder(JNIEnv *env, double arg) : jObject(env, jobject()) { val.d = arg; } // string explicit JniHolder(JNIEnv *env, const char* arg) : jObject(env, env->NewStringUTF(arg)) { val.l = jObject.get(); } // object explicit JniHolder(JNIEnv *env, jobject arg) : jObject(env, arg) { val.l = jObject.get(); } //////////////////////////////////////////////////////// operator jvalue() { return val; } jvalue get() { return val; } };
      
      







JObjectHolderは、ジョブジェクトを保持および削除するためのラッパーです。



JObjectHolder
 struct JObjectHolder { jobject jObject; JNIEnv* m_env; JObjectHolder() : m_env(nullptr) {} JObjectHolder(JNIEnv* env, jobject obj) : jObject(obj) , m_env(env) {} ~JObjectHolder() { if (jObject && m_env != nullptr) m_env->DeleteLocalRef(jObject); } jobject get() { return jObject; } };
      
      







JniHolderオブジェクトが作成され、JNIEnv *と値が渡されます。 コンストラクターでは、jvalueに設定するフィールドがわかります。 コンパイラが気付かないうちに型をキャストしないように、すべてのコンストラクタを明示的にします。 チェーン全体は1行になります。



 jvalue val = static_cast<jvalue>(JniHolder(env, 10));
      
      





しかし、一つだけあります。 変換が発生すると、jvalueを返しますが、jObjectは削除され、val.lは無効なアドレスを指します。 したがって、java関数呼び出し中にホルダーを保存する必要があります。



 JniHolder holder(env, 10) jvalue val = static_cast<jvalue>(holder);
      
      





いくつかのパラメーターを渡す場合、初期化リストを使用します。



 JniHolder holders[size] = { std::move(JniHolder(env, args))... }; jvalue vals[size]; for (size_t i = 0; i < size; ++i) vals[i] = static_cast<jvalue>(holders[i]);
      
      





目的のデータ型を返します



私は状況を解決して見た方法をいくつか書きたいと思います:



 template <typename MethodType, typename... Args> MethodType CallStaticMethod(Args... args) { MethodType result = ...; …. return reesult; }
      
      





ただし、JNIには不快な機能があります。返される型ごとに特定のメソッドがあります。 つまり、intにはCallStaticIntMethodが必要であり、floatにはCallStaticFloatMethodなどが必要です。 テンプレートの部分的な類型化に来ました。 まず、必要なインターフェイスを宣言します。



 template <typename MethodType> struct Impl { template <typename... Args> static MethodType CallMethod(JNIEnv* env, jclass clazz, jmethodID method, Args... args); };
      
      





次に、タイプごとに実装を作成します。 整数(int)の場合:



 template <> struct Impl <int> { template <typename... Args> static int CallStaticMethod(JNIEnv* env, jclass clazz, jmethodID method, Args... args) { const int size = sizeof...(args); if (size != 0) { jvalue vals[size] = { static_cast<jvalue>(JniHolder(env, args))... }; return env->CallStaticIntMethodA(clazz, method, vals); } return env->CallStaticIntMethod(clazz, method); } };
      
      





パラメータがゼロの場合、CallStaticMetodAではなくCallStaticMethodを呼び出す必要があります。 さて、次元ゼロの配列を作成しようとすると、コンパイラはそれについて考えていることをすべて伝えます。



ファイナル



呼び出しメソッド自体は次のようになります。



 template <typename MethodType, typename... Args> MethodType CallStaticMethod(const char* className, const char* mname, Args... args) { const size_t arg_num = sizeof...(Args); std::string signatures[arg_num] = { GetType(args)... }; std::string signature_string; signature_string.reserve(15); signature_string += "("; for (size_t i = 0; i < arg_num; ++i) signature_string += signatures[i]; signature_string += ")"; signature_string += GetTypeName<MethodType>(); JNIEnv *env = getEnv(); JniClass clazz(env, className); jmethodID method = env->GetStaticMethodID(clazz.get(), mname, signature_string.c_str()); return Impl<MethodType>::CallStaticMethod(env, clazz.get(), method, args...); }
      
      





次に、javaからメソッドを呼び出します。



Javaコード
 class Test { public static float TestMethod(String par, float x) { mOutString += "float String: " + par + " float=" + x + "\n"; return x; } };
      
      







ネイティブコードのどこか:



 float fRes = CallStaticMethod<float>("Test", "TestMethod", "TestString", 4.2f);
      
      





以前は、コードは
 JNIEnv* env = getEnv(); // -     jclass clazz = env->FindClass(“Test”); jmethodID method = env->GetStaticMethodID(“Test”, “TestMethod”, “(Ljava/lang/String;F)Ljava/lang/String;); jstring str = env->NewStringUTF(“TestString”); float fRes = env->CallStaticFloatMethod(clazz, method, str, 4.2f); env->DeleteLocalRef(clazz); env->DeleteLocalRef(str);
      
      





結論



メソッド呼び出しは便利なものに変わり、署名を覚えて値を変換してリンクを削除する必要はありません。 クラス、メソッド、および引数の名前を渡すだけで十分です。



また、興味深いタスクであることが判明しました。おかげで、新しい言語のパン(少し気に入った)を少し整理し、テンプレートを覚えました。



読んでくれてありがとう。 まあ、またはあなたの注意のために、すべてを読んでいない場合。 私は、作品の改善と批判のための提案を読んでうれしいです。 また、質問に答えます。



All Articles