Sharing output streams through a JNI interface

2019-03-24 20:31发布

问题:

I am writing a Java application that uses a C++ library through a JNI interface. The C++ library creates objects of type Foo, which are duly passed up through JNI to Java.

Suppose the library has an output function

    void Foo::print(std::ostream &os)

and I have a Java OutputStream out. How can I invoke Foo::print from Java so that the output appears on out? Is there any way to coerce the OutputStream to a std::ostream in the JNI layer? Can I capture the output in a buffer the JNI layer and then copy it into out?

回答1:

I would implement a C++ ostream that buffers writes (Up to some set size) before flushing those writes to a java OutputStream via JNI.

On the java side, you can either use a regular OutputStream instance, or you can implement queueing of the buffer blocks (essentially byte[]) to avoid any possible locking contention between threads. The real output stream is used only by a task on another thread that pulls blocks from the queue and writes them to the OutpuStream. I can't say if this is necessary or not at this level of detail - you may well find writing directly to the output stream from JNI works.

I don't share the other posters concerns with JNI, and don't see any problem with using JNI for this. Sure, your maintainers will have to know their stuff, but that's about it, and the complexity of the Java/C++ layer can be managed with documentation, examples and test cases. In the past, I've implemented a Java<>COM bridge with quite a chatty interface - no problem with performance, threading or maintainance.

Given a totally free choice, there'd be no JNI, but for me, it has saved the day by making possible the tight integration of otherwise incompatible systems.



回答2:

I've posted a writeup on my blog detailing my recent experience with this very same problem. In general, you want to avoid trying to connect an input or output stream to a client in any language as it implies threads. You can incrementally deliver the data using callbacks.



回答3:

How can I invoke Foo::print from Java so that the output appears on out?

Conceptually speaking, the way to get Foo::print(...) to write to an existing Java OutputStream instance is to write a C++ std::ostream implementation that actually does a callback into Java to do output.

That sounds possible, but I wouldn't want to write / maintain the code. At runtime, you'll have calls going from Java -> C++ -> Java, and there are lots of opportunities for making mistakes that will randomly crash your JVM.

Is there any way to coerce the OutputStream to a std::ostream in the JNI layer?

AFAIK no.

Can I capture the output in a buffer the JNI layer and then copy it into out?

Do you mean something roughly like this?

    MyJNIThing m = ...
    int myOstream = m.createMemoryBackedOStream(...); // native method
    ...
    m.someMethodWrapper(... myOStream); // native method
    ...
    byte[] data = m.getCapturedData(myOStream); // native method
    out.write(data);

You can probably make something like that work ... on a good day with a following wind.

But I think you should really be aiming to eliminate the C++ code rather than trying to do increasingly complicated things across JNI. IMO, JNI should only be used as a last resort, and not a short cut to avoid recoding stuff in Java.