0

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.

4
  • 2
    Redirect it to a pipe that you are reading from. Details will depend to some degree on your platform. Commented Sep 30, 2014 at 14:09
  • 1
    You can read a file as it is written, but a named pipe would be better most likely. Commented Sep 30, 2014 at 14:11
  • Thanks for the answers. I tried to do this you mention (read the file as it is written) using one example I found here. It uses available() method in the Java part reading the file and I used sleep() in the C part to see if it works. It works for this example but not for my shared library. Can you suggest anything on this befo re I switch to pipe solution? Thanks. Commented Sep 30, 2014 at 14:15
  • 1
    @OP; I updated my answer.. it should be faster and better than before. It will write directly to wherever you choose rather than allocating extra memory to store it and write later. It's better and smaller now. Commented Sep 30, 2014 at 18:30

1 Answer 1

3

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: enter image description here

When you reset it: enter image description here

Sign up to request clarification or add additional context in comments.

3 Comments

PS.. you can do this with BOOST iostreams as well.. It's probably easier with boost but meh.. I already wrote the above so you might as well forget about boost for now..
Updated the code.. It is now more optimised and faster than before. Redirected streams will write directly to the Java stuff.
Hello! Really thanks for the very detailed answer/explanation. Because I am searching for this a couple of days now I could not find a so good answer/explanation! I will try to do it like that with JNA as I am working with it otherwise I will take this at this! Thanks very much again! :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.