I'm using SWIG 2.0.10 in Ubuntu to call C++ code in Java.
My C++ code is:
//ImgPro.h:
#include <vector>
typedef struct _bin
{
char* name;
float value;
} Bin;
typedef struct imgprops
{
std::vector<Bin> color;
int width;
int height;
char *print;
} ImageProperties;
class ImgPro
{
public:
ImgPro();
ImageProperties *processImage(char* imagePath);
};
The processImage function definition is:
ImageProperties* ImgPro::processImage(char *imagePath)
{
ImageProperties* imgProp = new ImageProperties();
imgProp->width = 200;
imgProp->height = 200;
char* fp = new char(5);
strcpy(fp, "abc!");
imgProp->print = fp;
Bin outputBin1;
char *name1 = new char(strlen("red")+1);
strcpy(name1, "red");
outputBin1.name = name1;
outputBin1.value = 0.125;
Bin outputBin2;
char *name2 = new char(strlen("blue")+1);
strcpy(name2, "blue");
outputBin2.name = name1;
outputBin2.value = 0.27;
vector<Bin> tempVec;
tempVec.push_back(outputBin1);
tempVec.push_back(outputBin2);
imgProp->color = tempVec;
return imgProp;
}
So, to generate the jni code using swig, i've used the following swig file (note: the vector.i file was created using this example ) :
%module CBIR
// to handle char** has String_Array in Java
%include <various.i>
%include "vector.i"
%{
#include "ImgPro.h"
%}
// to handle char** has String_Array in Java
%apply char **STRING_ARRAY { char ** };
// memory release
%extend imgprops {
~imgprops(){
if($self != NULL)
{
// releasing print element
if($self->print != NULL)
delete[] $self->print;
// releasing vector elements
for(uint x = 0; x < $self->color.size(); x++)
{
Bin currentBin = $self->color[x];
if(currentBin.name != NULL)
delete[] currentBin.name;
}
// releasing stuct Pointer
delete $self;
}
}
}
%include "ImgPro.h"
%template(BinVec) std::vector<Bin>;
And this generates in the swig_wrap file the next function:
SWIGINTERN void delete_imgprops(imgprops *self){
if(self != NULL)
{
// releasing print element
if(self->print != NULL)
delete[] self->print;
// releasing vector elements
for(uint x = 0; x < self->color.size(); x++)
{
Bin currentBin = self->color[x];
if(currentBin.name != NULL)
delete[] currentBin.name;
}
// releasing stuct Pointer
delete self;
}
}
which is called in the delete ImageProperties c++ function.
However, running the following code in Java never releases the memory (calling the function delete_imgprops) allocated in C++:
ImgPro imgObject = new ImgPro();
ImageProperties propObject = imgObject.processImage("imagem123-jpg");
int width = propObject.getWidth();
int height = propObject.getHeight();
String fingerPrint = propObject.getPrint();
propObject.delete();
imgObject.delete();
So, after analyzing the code flow, i found the reason why the memory isn't released. The ImageProperties.Java file generated by SWIG contains, among others, the delete function:
public synchronized void delete() {
if (swigCPtr != 0) {
if (swigCMemOwn) {
swigCMemOwn = false;
CBIRJNI.delete_ImageProperties(swigCPtr);
}
swigCPtr = 0;
}
}
The line "CBIRJNI.delete_ImageProperties(swigCPtr);" is never called because the var swigCMemOwn is always false.
I understand that because the Java side doesn't alloc memory so it also doesn't release it, so what can i do to ensure that java releases memory without any modification on java files generated by swig?
The solution that i found to release the memory is to comment the if(swigCMemOwn) test on delete() function, but i don't think that it's the best way of do it!
Thanks, Sérgio
You could check out the
%newobject
directive (in swig 2.0) with a factory method. It tells swig that a particular function will return a new object and that the java proxy class should be responsible for cleaning up the c++ memory as well. The generated code will set swigCMemOwn to true resulting in the c++ destructor being called.
If you do call the delete method yourself, that's fine - you just have to change your style of programming to think of the swig'ed object as something like a file handle or a db connection. You call close() on those objects when you're done because you don't want to wait for java's GC to kick in at some unknown later point and collect this expensive resource- you want to manage it manually.
But you also obviously must remember to exercise good code discipline to make sure you don't use the java object anywhere after calling delete()
You should never call delete() manually. If you correctly implemented C++ destructor (or wherever you memory is freed) the memory will be released as soon as the Java wrapper object is released, SWIG wrapper code then will call appropriate method automatically. Read more at SWIG doc for Java.
Related
I'm doing a Java program that calls some C functions via the JNI. I have a situation where I gather a bunch of computer energy readings in C and want to write them to a file. Ideally, I want to use fprintf(), instead of formatting all the data as a string, passing it up to Java through the JNI interface, and then writing it to file the Java way, but that seems a lot less efficient.
The only problem is that when I do fprintf() in C, the output file I get has a garbage name. Definitely not the file name I provided it.
alejandro#alejandro-ThinkPad-E15:~/throwawayfiles$ ls
''$'\360\210\025\032\a'
The contents of the file are what I expect it to be, though. I've also called this function in C and it worked just fine, the only problem is when I facilitate calling it from a Java program. Is there a way that I can make sure that the output file has the name I actually want? Or is this just one of the problems of JNI stuff that I have to deal with. Like I said, plan B is to put all the data in a String, send it to java, and filewrite from there, but that's slow, and also code I'd rather not write :)
Thank you!
The code used to write to file. I'm handling a data structure AsyncEnergyMonitor that gathers energy readings. All of the reading and data storage is done in C, but it's all faciliated in the larger context of a Java program.
private native static void writeToFileFromC(String filePath);
public void writeToFile(String filePath)
{
writeToFileFromC(filePath);
}
JNIEXPORT void JNICALL
Java_jrapl_AsyncEnergyMonitorCSide_writeToFileFromC(JNIEnv* env,
jclass jcls, const char* filepath)
{
writeToFile(monitor, filepath);
}
Here is where the file is initially opened and I write in the header line.
void writeToFile(AsyncEnergyMonitor *monitor, const char* filepath){
FILE * outfile = (filepath) ? fopen(filepath,"w") : stdout;
fprintf(outfile,"samplingRate: %d milliseconds\n",monitor->samplingRate);
fprintf(outfile,"socket,dram,gpu,core,pkg,timestamp(usec since epoch)\n");
if (USING_DYNAMIC_ARRAY)
writeToFile_DynamicArray(outfile, monitor->samples_dynarr);
if (USING_LINKED_LIST)
writeToFile_LinkedList(outfile, monitor->samples_linklist);
if (filepath) fclose(outfile);
}
And here are the two functions I use to write the rest of the data, depending on whether the data is stored in a linked list or a dynamic array.
void
writeToFile_DynamicArray(FILE* outfile, DynamicArray* a) {
for (int i = 0; i < a->nItems; i++) {
EnergyStats current = a->items[i];
char csv_string[512];
energy_stats_csv_string(current, csv_string);
fprintf(outfile,"%s\n",csv_string);
}
}
void
writeToFile_LinkedList(FILE* outfile, LinkedList* l) {
LinkNode* current = l->head;
while(current != NULL) {
int upperbound = (current == l->tail) ?
(l->nItemsAtTail) : (NODE_CAPACITY);
for (int i = 0; i < upperbound; i++) {
char ener_string[512];
energy_stats_csv_string(current->items[i], ener_string);
fprintf(outfile,"%s\n",ener_string);
}
current = current->next;
}
}
I forgot to explicitly convert the filepath name from a Java string to a C string. Had nothing to do with file writing from C. Just made a garbage string name because I didn't convert
JNIEXPORT void JNICALL
Java_jrapl_AsyncEnergyMonitorCSide_writeToFileFromC(JNIEnv* env,
jclass jcls, const char* filepath)
{
writeToFile(monitor, filepath);
}
Fixed it to
JNIEXPORT void JNICALL
Java_jrapl_AsyncEnergyMonitorCSide_writeToFileFromC(JNIEnv* env, jclass jcls,
jstring jstringFilepath)
{
const char* filepath = (*env)->GetStringUTFChars(env, jstringFilepath, NULL);
writeToFile(monitor, filepath);
(*env)->ReleaseStringUTFChars(env, jstringFilepath, filepath);
}
All good now.
This question already has answers here:
Returning a Renderscript struct from a Renderscript kernel
(2 answers)
Closed 6 years ago.
I have a problem. I would like to retrieve a struct from a renderscript kernel. What I wanted was that I would get an input a struct element... I would modify it and then return it modified. But there is no such a way in the reflected layer. I tryied to manually deserialize the data from the buffer but I am not even able to copy the buffer to a ByteBuffer because the Allocation has validation in the copyTo on a type so I have no idea what am I supposed to do...
RenderScript supports custom elements. To create one, declare a custom typedef struct like the following one, inside a RS script:
typedef struct MyElement {
int x;
int y;
bool simpleBool;
} MyElement_t;
After the build process, a ScriptField_MyElement Java class will appear, mirroring the RS struct. You will be able to use this class to create a custom Allocation that uses your own Element:
// Declares a new Allocation, based upon the custom struct Element
Element myElement = ScriptField_MyElement.createElement(mRS);
Allocation myElementsAllocation = Allocation.createSized(mRS, myElement, 5);
// Or
Allocation myElementsAllocation = ScriptField_MyElement.create1D(mRS, sizeX).getAllocation();
You can find an example of this process inside the CustomElementExample sample project.
Also, inside the SurfaceRenderExample sample project you can see how a custom element can be used to model a mathematical structure (in this case a particle, falling with some acceleration).
Inside RenderScript scripts:
To get a custom element from an allocation:
MyElement_t el = * (MyElement_t *) rsGetElementAt(aIn, index);
To change a custom element member:
el.x = 10;
To set a custom element in an allocation:
rsSetElementAt(myAlloc, (void *)&el);
Reference: RenderScript: parallel computing on Android, the easy way
Edit:
For now, there is no direct way to copy a custom struct element to the Java side.
The CustomStructElementCopyToJava sample project provides an example of the process.
Short explanation of the example
Note: the following process is EXPERIMENTAL and not performant at all! If you plan to heavily use this process, please use the Android NDK to access the allocation.
Also, in future versions of the Android SDK, this code may break because it relies on Java reflection; some normally hidden methods can change without any notice in the Android SDK.
Let's assume using the following custom struct element:
typedef struct Point {
int x;
int y;
} Point_t;
When looking at the generated code of the struct (which can be seen, in Android Studio, by pressing CTRL+B while focusing on a ScriptField_Point element on the Java side), the following elements can be seen:
public static Element createElement(RenderScript rs) {
Element.Builder eb = new Element.Builder(rs);
eb.add(Element.I32(rs), "x");
eb.add(Element.I32(rs), "y");
return eb.create();
}
You can map the contents of the custom struct in a hacky way:
1) Define the destination byte array:
byte destinationArray[] = new byte[allocationGrayPointOrdered.getBytesSize()];
2) Use Java reflection to access the hidden Allocation.copyTo method:
private static Method getCopyToWithoutValidationMethod(){
// private void copyTo(Object array, Element.DataType dt, int arrayLen)
Method allocationHiddenCopyToMethod = null;
try {
allocationHiddenCopyToMethod = Allocation.class.getDeclaredMethod("copyTo", Object.class, Element.DataType.class, int.class);
allocationHiddenCopyToMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Could not find allocationHiddenCopyToMethod");
}
return allocationHiddenCopyToMethod;
}
3) Perform the copy:
// Gets reflected method
Method copyToWithoutValidationMethod = getCopyToWithoutValidationMethod();
// Tries to copy contents
try {
copyToWithoutValidationMethod.invoke(allocationGrayPointOrdered, destinationArray,
Element.DataType.UNSIGNED_8, destinationArray.length);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
4) Once the array is filled with source data, it is then possible to map its content to a human-readable struct.
// Defines the destination array
ScriptField_Point.Item mappedItems[][] = new ScriptField_Point.Item[sizeX][sizeY];
// Wraps array contents
ByteBuffer byteBuffer = ByteBuffer.wrap(destinationArray);
// Sets byte order to be Android-like
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
// Iterates on every column and row
for (int x = 0; x < sizeX; x++) {
for (int y = 0; y < sizeY; y++) {
// Allocates a new item
ScriptField_Point.Item currentItem = new ScriptField_Point.Item();
// Calculate the offset in the source array
int currentOffset = (x + y * sizeX) * ScriptField_Point.Item.sizeof;
// Gets data from the byte array
currentItem.x = byteBuffer.getInt(currentOffset);
currentItem.y = byteBuffer.getInt(currentOffset + 4);
mappedItems[x][y] = currentItem;
}
}
For the complete explanation, please refer to the book.
I got a problem for a couple of weeks now and I need to know if my intuition is right. My Android app is using a c++ library and SIGSEGV errors are triggered every once and awhile.
I got a NetworkThread written in c++, it receives Update object from a server. I got a WorkerThread written in Java, it asks the NetworkThread if there's new updates every 0.5 s.
I used JNI wrappers generated with SWIG to communicate between c++ and Java.
In the NetworkThread we have a std::vector containing all new updates.
The WorkerThread (Java) use this line to get a new Update object :
Update u = nwt.nextUpdate();
Then this trigger in the NetworkThread (c++) the following code :
Update NetworkThread::nextUpdate() {
pthread_mutex_lock(&nwt_mutex);
pthread_mutex_lock(&inQ_mutex); // RACING CONDITION
Update c = inQ.front();
inQ.erase(inQ.begin());
if (inQ.size() <= QUEUE_SIZE) {
pthread_cond_broadcast(&inQ_threshold_cv);
}
pthread_mutex_unlock(&inQ_mutex);
pthread_mutex_unlock(&nwt_mutex);
return c;
}
The line Update c = inQ.front(); make a shallow copy of the object (Update doesn't override the = operator). I think this is bad because Update contains reference to other object (and vectors of Objects).
Then the line inQ.erase(inQ.begin()); is called, from std::vector's documentation the element removed are destroyed. Does that mean at this point the references to the objects and the vectors inside c are not valid anymore ?
After the Update object is sent back to Java with this JNI snippet :
// Retrieve the current JNIEnv* with the cached JVM
int status;
JNIEnv* env;
bool isAttached = false;
status = gCachedJVM->GetEnv((void **) &env, JNI_VERSION_1_2);
if(status < 0) {
status = gCachedJVM->AttachCurrentThread(&env, NULL);
if(status < 0) {
return;
}
isAttached = true;
}
jmethodID update = env->GetMethodID(gClazzUpdateListenerWrapper, "update", "(J)V"); // J stands for Java long type
// Call Java method update from jUpdateListener object
env->CallVoidMethod(jUpdateListener, update, (jlong)(intptr_t)&u); // Pointer as agument, we'll build the Update object in Java
if (isAttached) {
gCachedJVM->DetachCurrentThread();
}
Here I send to Java an address (long), and I build the object like this (I use the default constructor generated with SWIG) :
Player p1;
public void update(long ptrUpdate) {
final Update u = new Update(ptrUpdate, false);
p1 = u.getEntity(0).toPlayer();
...
}
Is it a bad practice to do this like I do ? I think doing this with a long is bad but if the address is allocated and valid there shouldn't be any problem right ?
Anyway this seems to work well until I got a SIGSEGV error (no further informations) which I suspect is because I keep using reference to objects that have been destroyed and collected.
I have found the clone() method of Object in Java:
protected native Object clone() throws CloneNotSupportedException;
Is the source of this method available? Maybe in OpenJDK?
From jdk/src/share/native/java/lang/Object.c
static JNINativeMethod methods[] = {
{"hashCode", "()I", (void *)&JVM_IHashCode},
{"wait", "(J)V", (void *)&JVM_MonitorWait},
{"notify", "()V", (void *)&JVM_MonitorNotify},
{"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll},
{"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone},
};
Meaning its a function pointer(probably done so they could implement platform-specific native code)
doing a grep for JVM_Clone produces, among other things:
(from hotspot/src/share/vm/prims/jvm.cpp)
JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle))
JVMWrapper("JVM_Clone");
Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
const KlassHandle klass (THREAD, obj->klass());
JvmtiVMObjectAllocEventCollector oam;
#ifdef ASSERT
// Just checking that the cloneable flag is set correct
if (obj->is_javaArray()) {
guarantee(klass->is_cloneable(), "all arrays are cloneable");
} else {
guarantee(obj->is_instance(), "should be instanceOop");
bool cloneable = klass->is_subtype_of(SystemDictionary::Cloneable_klass());
guarantee(cloneable == klass->is_cloneable(), "incorrect cloneable flag");
}
#endif
// Check if class of obj supports the Cloneable interface.
// All arrays are considered to be cloneable (See JLS 20.1.5)
if (!klass->is_cloneable()) {
ResourceMark rm(THREAD);
THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());
}
// Make shallow object copy
const int size = obj->size();
oop new_obj = NULL;
if (obj->is_javaArray()) {
const int length = ((arrayOop)obj())->length();
new_obj = CollectedHeap::array_allocate(klass, size, length, CHECK_NULL);
} else {
new_obj = CollectedHeap::obj_allocate(klass, size, CHECK_NULL);
}
// 4839641 (4840070): We must do an oop-atomic copy, because if another thread
// is modifying a reference field in the clonee, a non-oop-atomic copy might
// be suspended in the middle of copying the pointer and end up with parts
// of two different pointers in the field. Subsequent dereferences will crash.
// 4846409: an oop-copy of objects with long or double fields or arrays of same
// won't copy the longs/doubles atomically in 32-bit vm's, so we copy jlongs instead
// of oops. We know objects are aligned on a minimum of an jlong boundary.
// The same is true of StubRoutines::object_copy and the various oop_copy
// variants, and of the code generated by the inline_native_clone intrinsic.
assert(MinObjAlignmentInBytes >= BytesPerLong, "objects misaligned");
Copy::conjoint_jlongs_atomic((jlong*)obj(), (jlong*)new_obj,
(size_t)align_object_size(size) / HeapWordsPerLong);
// Clear the header
new_obj->init_mark();
// Store check (mark entire object and let gc sort it out)
BarrierSet* bs = Universe::heap()->barrier_set();
assert(bs->has_write_region_opt(), "Barrier set does not have write_region");
bs->write_region(MemRegion((HeapWord*)new_obj, size));
// Caution: this involves a java upcall, so the clone should be
// "gc-robust" by this stage.
if (klass->has_finalizer()) {
assert(obj->is_instance(), "should be instanceOop");
new_obj = instanceKlass::register_finalizer(instanceOop(new_obj), CHECK_NULL);
}
return JNIHandles::make_local(env, oop(new_obj));
JVM_END
I found these files, but all they do is import the function from elsewhere. I can't find the actual source (try asking a Hotspot dev).
Object.c
jvm.h
Edit: Here's a link to the actual source online (Thanks user439407 for finding which file it was in)
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)
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 :-().
It sounds as though the deliverable here is a subclass of ostream. The immediate question I'd want to be clear about is, will this class be responsible for buffering data until Java calls into it to retrieve, or is it expected to immediately (synchronously?) call via JNI to pass it on? That will be the strongest guide to how the code will shape up.
If you can reasonably expect the text to appear as a series of lines, I'd think about presenting them to Java in one line per call: this seems a fair compromise between the number of JNI calls and not unduly delaying the passing on of the text.
On the Java side I think you're looking at creating a Reader so that clients can pick up the text via a familiar interface, or perhaps a subclass of BufferedReader.