JNI を使用して std::ostream 経由で C++ からテキスト データを Java に取得する
-
22-09-2019 - |
質問
C++ でクラスを持っています。 std::ostream
引数としてテキスト(トレース情報)を連続出力します。このテキストをできるだけ効率的に Java 側に渡す必要があります。これを行うための最良の方法は何ですか?直接バッファーを使用することを考えていましたが、別の方法では、すべての関数呼び出しを Java に渡し、そこですべての処理を実行することになりますが、大量の JNI 呼び出しが必要になるようです。
正確な実装方法の例を示すことができれば、非常に役立ちます。または、これを行うためのコードが既に存在する場合 (おそらく別のプロジェクトの一部)。もう 1 つの助けとなるのは、実装全体が開発者にとって完全に透過的になるように、標準 Java ストリーミング構造に直接接続することです。
(編集:見つけました JNI インターフェースを介した出力ストリームの共有 これは重複しているようですが、実際にはあまり役に立ちません -- 彼は探していた答えを見つけられなかったようです)
解決
std::ostream クラスには、出力用の std::streambuf オブジェクトが必要です。これは fstream クラスと stringstream クラスによって使用され、streambuf クラスのカスタム実装を提供することで ostream の機能を使用します。
したがって、上書きされたオーバーフロー メソッドを使用して独自の std::streambuf 実装を作成し、受信文字を内部文字列バッファにバッファリングできます。すべての x 呼び出しまたは eof/newline で Java 文字列が生成され、Java PrintStream の print メソッドが呼び出されます。
不完全なクラス例:
class JavaStreamBuff : std::streambuf
{
std::stringstream buff;
int size;
jobject handle;
JNIEnv* env
//Ctor takes env pointer for the working thread and java.io.PrintStream
JavaStreamBuff(JNIEnv* env, jobject jobject printStream, int buffsize = 50)
{
handle = env->NewGlobalRef(printStream);
this->env = env;
this->size = size;
}
//This method is the central output of the streambuf class, every charakter goes here
int overflow(int in)
{
if(in == eof || buff.size() == size)
{
std::string blub = buff.str();
jstring do = //magic here, convert form current locale unicode then to java string
jMethodId id = env->(env->GetObjectClass(handle),"print","(java.lang.String)V");
env->callVoidMethod(id,handle,do);
buff.str("");
}
else
{buff<<in;}
}
virtual ~JavaStreamBuff()
{
env->DeleteGlobalRef(handle);
}
}
ない:
マルチスレッドのサポート (env ポインターは jvm スレッドに対してのみ有効です)
エラー処理 (スローされた Java 例外のチェック)
テスト(過去70分以内に書かれたもの)
プリントストリームを設定するネイティブ Java メソッド。
Java 側では、PrintStream を BufferedReader に変換するクラスが必要です。
いくつかのバグがあるはずですが、それらに取り組むのに十分な時間を費やしていません。
このクラスでは、すべてのアクセスがそのクラスが作成されたスレッドから行われる必要があります。
お役に立てれば
注記
Visual Studio では動作させることができましたが、g++ では動作させることができません。後でデバッグしてみます。
編集私の答えを投稿する前に、これに関するもっと公式のチュートリアルを探すべきだったようです。 MSDNページ このトピックでは、別の方法で文字列バッファを取得します。
よくテストせずにこれを投稿して申し訳ありません:-(。
多かれ少なかれ無関係な点で上記のコードを少し修正します。カスタム クラスで InputStream を実装し、C++ の文字列の代わりに byte[] 配列をプッシュするだけです。
InputStream には小さなインターフェイスがあり、BufferedReader がほとんどの作業を実行します。
これに関する最後の更新は、オーバーフローのみを上書きする必要があると述べている std::streambuf クラスのコメントがあっても、Linux 上で動作させることができないためです。
この実装は、生の文字列を入力ストリームにプッシュし、他のスレッドから読み取ることができます。私はあまりにも愚かなので、デバッガーを動作させることはできませんが、テストされていません。
//The c++ class
class JavaStreamBuf :public std::streambuf
{
std::vector<char> buff;
unsigned int size;
jobject handle;
JNIEnv* env;
public:
//Ctor takes env pointer for the working thread and java.io.PrintStream
JavaStreamBuf(JNIEnv* env, jobject cppstream, unsigned int buffsize = 50)
{
handle = env->NewGlobalRef(cppstream);
this->env = env;
this->size = size;
this->setbuf(0,0);
}
//This method is the central output of the streambuf class, every charakter goes here
virtual int_type overflow(int_type in = traits_type::eof()){
if(in == std::ios::traits_type::eof() || buff.size() == size)
{
this->std::streambuf::overflow(in);
if(in != EOF)
buff.push_back(in);
jbyteArray o = env->NewByteArray(buff.size());
env->SetByteArrayRegion(o,0,buff.size(),(jbyte*)&buff[0]);
jmethodID id = env->GetMethodID(env->GetObjectClass(handle),"push","([B)V");
env->CallVoidMethod(handle,id,o);
if(in == EOF)
env->CallVoidMethod(handle,id,NULL);
buff.clear();
}
else
{
buff.push_back(in);
}
return in;
}
virtual ~JavaStreamBuf()
{
overflow();
env->DeleteGlobalRef(handle);
}
//The java class
/**
*
*/
package jx;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author josefx
*
*/
public class CPPStream extends InputStream {
List<Byte> data = new ArrayList<Byte>();
int off = 0;
private boolean endflag = false;
public void push(byte[] d)
{
synchronized(data)
{
if(d == null)
{
this.endflag = true;
}
else
{
for(int i = 0; i < d.length;++i)
{
data.add(d[i]);
}
}
}
}
@Override
public int read() throws IOException
{
synchronized(data)
{
while(data.isEmpty()&&!endflag)
{
try {
data.wait();
} catch (InterruptedException e) {
throw new InterruptedIOException();
}
}
}
if(endflag)return -1;
else return data.remove(0);
}
}
たくさんのスペースを無駄にしてごめんなさい^^(そして時間:-()。
他のヒント
これが鳴ります。私は明確な程度にしたいと思いすぐに質問には、Javaを取得するためにそれを呼び出す、またはそれはすぐに(同期?)と予想されるまで、このクラスは、データをバッファリングするための責任を負うことになる、あるJNI経由で通話が上にそれを渡す方法は?つまり、コードはシェイプアップする方法に最強のガイドとなります。
あなたは合理的にテキストが一連の線として現れることを期待することができた場合は、、私は、コールごとに1行でJavaにそれらを提示する考えたい:これは、JNIの数との間の公正な妥協は、呼び出し、不当通過を遅らせないようですテキストの上ます。
Java側では、私はあなたがそのクライアントがBufferedReaderのの使い慣れたインターフェイス、またはおそらくサブクラスを経由してテキストを拾うことができるようにリーダーを作る見ていると考えています。