Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting text data from C++ using JNI through std::ostream into Java

I have a class in C++ which takes an std::ostream as an argument in order to continuously output text (trace information). I need to get this text over to the Java side as efficiently as possible. What's the best way to do this? I was thinking of using a direct buffer, but another method would be to take all the function calls across to Java and do all the processing there, but it seems that I'd need a lot of JNI calls.

If an example could be shown of the exact implementation method, it would be very helpful, or if some code exists already to do this (perhaps part of another project). Another help would be to connect it up directly to a standard Java streaming construct, such that the entire implementation was completely transparent to the developer.

(Edit: I found Sharing output streams through a JNI interface which seems to be a duplicate, but not really of much help -- he didn't seem to find the answer he was looking for)

like image 935
Chris Dennett Avatar asked Feb 24 '10 17:02

Chris Dennett


1 Answers

The std::ostream class requires a std::streambuf object for its output. This is used by the fstream and stringstream classes, which use the features of ostream by providing a custom implementation of the streambuf class.

So you can write your own std::streambuf implementation with an overwritten overflow method, buffer the incomming chars in an internal stringbuffer. Every x calls or on eof/newline generate an java-string and call the print method of your java PrintStream.

An incomplete example class:

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

Missing:

  • Multithread support (the env pointer is only valid for the jvm thread)

  • Error handling (checking for java exceptions thrown)

  • Testing(written within the last 70 min)

  • Native java method to set the printstream.

On the java side you need a class to convert the PrintStream to a BufferedReader.

There have to be some bugs there, haven't spend enough time to work on them.
The class requires all access to be from the thread it was created in.

Hope this helps

Note
I got it to work with visual studio but I can't get it to work with g++, will try to debug that later.
Edit Seems that I should have looked for a more official tutorial on this bevore posting my answer, the MSDN page on this topic derives the stringbuffer in a different way.
Sorry for posting this without testing it better :-(.
A small correction to the code above in a more or less unrelated point: Just implement InputStream with a custom class and push byte[] arrays instead of Strings from c++.
The InputStream has a small interface and a BufferedReader should do most of the work.

Last update on this one, since im unable to get it to work on linux, even with the comments on the std::streambuf class stating that only overflow has to be overwritten.
This implementation pushes the raw strings into an inputstream, which can be read from by an other thread. Since I am too stupid to get the debugger working its untested, again.

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

Sorry for wasting so much space^^(and time :-().

like image 182
josefx Avatar answered Sep 28 '22 16:09

josefx