【问题标题】:Getting text data from C++ using JNI through std::ostream into Java使用 JNI 通过 std::ostream 从 C++ 获取文本数据到 Java
【发布时间】:2010-02-24 17:35:13
【问题描述】:

我有一个 C++ 类,它以std::ostream 作为参数,以便连续输出文本(跟踪信息)。我需要尽可能高效地将此文本传递到 Java 端。最好的方法是什么?我正在考虑使用直接缓冲区,但另一种方法是将所有函数调用传递到 Java 并在那里进行所有处理,但似乎我需要大量 JNI 调用。

如果可以显示确切实现方法的示例,那将非常有帮助,或者如果已经存在一些代码来执行此操作(可能是另一个项目的一部分)。另一个帮助是将其直接连接到标准 Java 流结构,这样整个实现对开发人员来说是完全透明的。

(编辑:我发现Sharing output streams through a JNI interface 似乎是重复的,但并没有太大帮助——他似乎没有找到他正在寻找的答案)

【问题讨论】:

    标签: java text java-native-interface stream ostream


    【解决方案1】:

    std::ostream 类的输出需要一个 std::streambuf 对象。这由 fstream 和 stringstream 类使用,它们通过提供 streambuf 类的自定义实现来使用 ostream 的特性。

    因此,您可以使用覆盖溢出方法编写自己的 std::streambuf 实现,将传入的字符缓冲在内部字符串缓冲区中。每个 x 调用或在 eof/newline 上生成一个 java-string 并调用你的 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 page 以不同的方式派生字符串缓冲区。
    很抱歉在没有更好地测试的情况下发布此内容:-(。
    在或多或少不相关的地方对上面的代码进行了小修正:只需使用自定义类实现 InputStream 并推送 byte[] 数组而不是 c++ 中的字符串。
    InputStream 的接口很小,而 BufferedReader 应该可以完成大部分工作。

    最后一次更新这个,因为我无法让它在 linux 上工作,即使 std::streambuf 类上的 cmets 声明只有溢出必须被覆盖。
    此实现将原始字符串推送到输入流中,其他线程可以从该输入流中读取。因为我太愚蠢了,无法让调试器在未经测试的情况下再次工作。

    //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);
        }
    }
    

    抱歉浪费了这么多空间^^(和时间:-()。

    【讨论】:

    • 多线程支持没问题,我很久以前就破解了那个特定的 JNI 坚果 :) 无论如何,这是否容易转换为 std::ostream ?
    • std::ostream 将 streambuf 作为其 ctor 中的参数。 cplusplus.com/reference/iostream/ostream/ostream
    • 另一个想法:将字符放入字节数组并将它们推送到 InputStream 的缓冲区中可能会减少实现的开销,并且 BufferedReader 类将使用当前语言环境转换输入
    • 两个明显的地方需要修复。 :-) 您需要调用 *UTF* 函数之一来将字节转换为 jstring。此外,函数签名应该使用Ljava/lang/String;,而不是java.lang.String
    • @chris_Jester-Young 是的,我首先实现了没有字符串和单个字符的代码,这需要为每个字符调用 java。到目前为止,我认为最好将 byte[] 数组交给 java,因为扩展 InputStream 只需要 int read() 而不是 BufferedInputStreamReader 的所有方法。
    【解决方案2】:

    听起来好像这里的可交付成果是 ostream 的子类。我想明确的直接问题是,这个类是否负责缓冲数据,直到 Java 调用它来检索,或者它是否应该立即(同步?)通过 JNI 调用来传递它?这将是代码如何形成的最有力的指南。

    如果您可以合理地期望文本显示为一系列行,我会考虑在每次调用中将它们呈现给 Java:这似乎是 JNI 调用数量和不过度延迟传递之间的公平折衷在文本上。

    在 Java 方面,我认为您正在考虑创建一个 Reader,以便客户端可以通过熟悉的界面或 BufferedReader 的子类来获取文本。

    【讨论】:

    • 立即传下去没关系,可以等到行完成等等。我确实尝试过使用 ostream 做一些事情,但不知道在这方面从哪里着手。真的,我只是想将文本数据放入 Java 中进行处理 :)
    猜你喜欢
    • 2014-01-30
    • 1970-01-01
    • 2010-09-26
    • 2022-01-24
    • 1970-01-01
    • 1970-01-01
    • 2013-05-30
    • 2011-12-21
    • 1970-01-01
    相关资源
    最近更新 更多