I am using a C++ .so with JNA. When I run it through Netbeans the output from C++ cout is directly printed in the Netbeans console. The thing is that I want to redirect this output to a JFrame for example. As suggested in other similar topics I found on the web, I used freopen() to redirect stdout to a file. It works fine but I have one problem with this solution. The data can be read and then redirected to a JFrame as I want only after the execution of C++ code has stopped (or at least this I understand - maybe I am wrong). So I want to ask is there really any way of redirecting C++ ouput somethere that can be accessed from my Java code in real time? I mean during the execution of the C++ code and not after the end of the operation in the C++ part. Thanks.
Oh god.. redirecting to a pipe is one of the worst ideas I can think of.. Writing it to a file and then reading it in is even worst than that..
It doesn't fit your requirement of "real-time" or "live" data IMO..
Those options should only be used in "C" not "C++".. C++ has better ways of doing what you want..
What you can do is create your own stream that writes directly to wherever you want via JNI calls.. First you need to inherit from std::streambuf
then create an std::ostream
from your custom made class..
Redirect std::cout
's rdbuf to your custom stream.
Here is what I wrote:
#include "jni.h"
#include <tuple>
#include <vector>
#include <iostream>
#include <cstring>
#if defined _WIN32 || defined _WIN64
#include <windows.h>
#else
#include <sys/types.h>
#endif
#if defined _WIN32 || defined _WIN64
#define JAVA_EXPORT __declspec(dllexport)
#else
#define JAVA_EXPORT
#endif
std::vector<std::tuple<std::ostream*, std::ios*, std::streambuf*>> streams;
extern "C" {
JAVA_EXPORT void Java_natives_Natives_RedirectOutput(JNIEnv* env, jclass cls, jobject component);
JAVA_EXPORT void Java_natives_Natives_ResetOutput(JNIEnv* env, jclass cls);
}
class redir : public std::streambuf
{
private:
JNIEnv* env;
jobject comp;
jmethodID mID;
int_type overflow(int_type c = traits_type::eof());
public:
redir(JNIEnv* e, jobject comp, jmethodID m) : env(env), comp(comp), mID(mID) {}
~redir() {}
redir(const redir& other) = delete;
redir& operator = (const redir& other) = delete;
};
redir::int_type redir::overflow(redir::int_type c)
{
if (c != traits_type::eof())
{
jstring str = env->NewStringUTF((char*)&c);
env->CallVoidMethod(comp, mID, str);
}
return c;
}
class rdstream : public std::ostream
{
public:
rdstream(JNIEnv* env, jobject comp, jmethodID mID) : std::ostream(0), sbuf(env, comp, mID) {init(&sbuf);}
private:
redir sbuf;
};
void Java_natives_Natives_RedirectOutput(JNIEnv* env, jclass cls, jobject component)
{
if (streams.empty())
{
jclass txtcls = env->FindClass("Ljavax/swing/JTextArea;");
if (txtcls)
{
jmethodID app = env->GetMethodID(txtcls, "append", "(Ljava/lang/String;)V");
rdstream* ctrd = new rdstream(env, component, app);
rdstream* crrd = new rdstream(env, component, app);
streams.push_back(std::make_tuple(ctrd, &std::cout, std::cout.rdbuf()));
streams.push_back(std::make_tuple(crrd, &std::cerr, std::cerr.rdbuf()));
std::cout.rdbuf(ctrd->rdbuf());
std::cerr.rdbuf(crrd->rdbuf());
std::cout<<"TESTING OUTPUT REDIRECTION\n";
}
}
}
void Java_natives_Natives_ResetOutput(JNIEnv* env, jclass cls)
{
for (std::vector<std::tuple<std::ostream*, std::ios*, std::streambuf*>>::iterator it = streams.begin(); it != streams.end(); ++it)
{
std::get<1>(*it)->rdbuf(std::get<2>(*it));
delete std::get<0>(*it);
}
streams.clear();
std::cout<<"TESTING OUTPUT RESET\n"<<std::flush;
}
void __attribute__((constructor)) load()
{
//Onload..
}
void __attribute__((destructor)) unload()
{
//OnUnload..
if (!streams.empty())
{
Java_natives_Natives_ResetOutput(NULL, NULL);
}
}
Then on the Java side I did:
package natives;
import java.awt.Component;
/**
*
* @author Brandon
*/
public class Natives {
static {
System.loadLibrary("Redirector");
}
public native static void RedirectOutput(Component comp);
public native static void ResetOutput();
}
and a test class:
package windowhandle;
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JTextArea;
/**
*
* @author Brandon
*/
public class WindowHandle {
public static void main(String[] args) {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
JTextArea t = new JTextArea();
t.setPreferredSize(new Dimension(500, 100));
f.setLayout(new BorderLayout(0, 0));
f.getContentPane().add(t, BorderLayout.CENTER);
f.pack();
natives.Natives.RedirectOutput(t);
natives.Natives.ResetOutput();
}
}
Now the C++ code can be customised any way that you like. You can create a method on the Java side that appends to whatever component you want and then let C++ call that instead of finding the text area and appending to it specifically.. If you do it that way, then it could be used for any project and any component and not just a textarea.
This can even be used for files (std::fstream
).
Anyway.. the result is:..
When you redirect it:
When you reset it: