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 :-().