Получение текстовых данных из C++ с использованием JNI через std::ostream в Java

StackOverflow https://stackoverflow.com/questions/2328090

Вопрос

У меня есть класс на C++, который принимает std::ostream в качестве аргумента для непрерывного вывода текста (информации трассировки).Мне нужно как можно эффективнее передать этот текст на сторону Java.Как лучше всего это сделать?Я думал об использовании прямого буфера, но другим методом было бы перенести все вызовы функций на Java и выполнить всю обработку там, но, похоже, мне понадобится много вызовов JNI.

Если бы можно было показать пример точного метода реализации, это было бы очень полезно, или если бы уже существовал какой-то код для этого (возможно, часть другого проекта).Другой помощью было бы подключение непосредственно к стандартной конструкции потоковой передачи Java, чтобы вся реализация была полностью прозрачна для разработчика.

(Редактировать:я нашел Совместное использование выходных потоков через интерфейс JNI это похоже на дубликат, но на самом деле это не особо помогает - похоже, он не нашел ответа, который искал)

Это было полезно?

Решение

Классу std::ostream для вывода требуется объект std::streambuf.Это используется классами fstream и stringstream, которые используют возможности ostream, предоставляя специальную реализацию классаstreambuf.

Таким образом, вы можете написать свою собственную реализацию std::streambuf с перезаписанным методом переполнения, буферизовать входящие символы во внутреннем строковом буфере.Каждый вызов x или eof/newline генерирует Java-строку и вызывает метод печати вашего 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 для установки потока печати.

На стороне Java вам нужен класс для преобразования PrintStream в BufferedReader.

Должно быть, там есть какие-то ошибки, я не потратил достаточно времени на их исправление.
Класс требует, чтобы весь доступ осуществлялся из потока, в котором он был создан.

Надеюсь это поможет

Примечание
Я заставил его работать с Visual Studio, но не могу заставить его работать с g++, попробую отладить это позже.
РедактироватьКажется, мне следовало поискать более официальное руководство по этому вопросу, прежде чем публиковать свой ответ. страница MSDN в этой теме строковый буфер извлекается другим способом.
Извините, что опубликовал это, не протестировав лучше :-(.
Небольшая поправка к приведенному выше коду в более-менее несвязанном месте:Просто реализуйте InputStream с помощью специального класса и помещайте массивы byte[] вместо строк из 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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top