Получение текстовых данных из C++ с использованием JNI через std::ostream в Java
У меня есть класс в C++, который принимает std::ostream
в качестве аргумента для непрерывного вывода текста (информация трассировки). Мне нужно максимально эффективно передать этот текст на сторону Java. Какой лучший способ сделать это? Я думал об использовании прямого буфера, но другим методом было бы перевести все вызовы функций в Java и выполнить всю их обработку, но, похоже, мне понадобится много вызовов JNI.
Если можно было бы показать пример точного метода реализации, это было бы очень полезно, или если какой-то код уже существует для этого (возможно, является частью другого проекта). Другая помощь может заключаться в непосредственном подключении к стандартной потоковой конструкции Java, чтобы вся реализация была полностью прозрачной для разработчика.
(Правка: я нашел общий доступ к выходным потокам через интерфейс JNI, который кажется дубликатом, но не очень помогает - он, похоже, не нашел ответ, который искал)
Ура,
Крис
2 ответа
Класс std::ostream требует для вывода объект std::streambuf. Это используется классами fstream и stringstream, которые используют функции ostream, предоставляя пользовательскую реализацию класса streambuf.
Таким образом, вы можете написать свою собственную реализацию std::streambuf с перезаписанным методом переполнения, буферизуя входящие символы во внутренний строковый буфер. Каждый вызов x или eof/newline генерирует строку java и вызывает метод print вашего java PrintStream.
Неполный пример класса:
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-метод для установки printstream.
На стороне Java вам нужен класс для преобразования PrintStream в BufferedReader.
Там должны быть некоторые ошибки, не тратить достаточно времени, чтобы поработать над ними.
Класс требует, чтобы весь доступ был из потока, в котором он был создан.
Надеюсь это поможет
Заметка
Я получил его для работы с Visual Studio, но я не могу заставить его работать с g++, попробую отладить это позже.
Редактировать Похоже, что я должен был найти более официальный учебник по этому поводу, публикуя мой ответ, страница MSDN по этой теме выводит строковый буфер другим способом.
Извините за публикацию без проверки лучше:-(.
Небольшое исправление приведенного выше кода в более или менее не связанной точке: просто реализуйте InputStream с помощью пользовательского класса и вставьте массивы byte[] вместо Strings из C++.
InputStream имеет небольшой интерфейс, и BufferedReader должен выполнять большую часть работы.
Последнее обновление на этом, так как я не могу заставить его работать на Linux, даже с комментариями к классу std::streambuf, заявляющими, что только переполнение должно быть перезаписано.
Эта реализация помещает необработанные строки во входной поток, который может быть прочитан другим потоком. Так как я слишком глуп, чтобы заставить отладчик работать снова, еще раз.
//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);
}
}
Извините за тратить так много места ^^(и время:-().
Похоже, что результат здесь является подклассом ostream. Непосредственный вопрос, который я хотел бы прояснить, заключается в том, будет ли этот класс отвечать за буферизацию данных до тех пор, пока Java не вызовет их для извлечения, или ожидается, что он немедленно (синхронно?) Вызовет через JNI для его передачи? Это будет самое сильное руководство по формированию кода.
Если вы можете разумно ожидать, что текст будет отображаться в виде последовательности строк, я бы подумал о том, чтобы представить их Java одной строкой на вызов: это кажется справедливым компромиссом между количеством вызовов JNI и не приводит к чрезмерной задержке передачи текст.
Что касается Java, я думаю, вы смотрите на создание Reader, чтобы клиенты могли получать текст через знакомый интерфейс или, возможно, подкласс BufferedReader.