In Java you can change the out and err output streams using System.setOut() and System.setErr() respectively. You could, for example, redirect that output to a log file. If you have native code wrapped in JNI, however, these calls do not affect the native standard output and standard error file descriptors. If your native code writes log lines to standard output there is no way to capture and redirect that output from within Java. First we’ll demonstrate the problem by creating a simple JNI method that writes output using the printf function.
package iocapture;
public class NativePrinter {
/** Write message to native stdout file descriptor */
public static native void print(String msg);
}
And the native code to implement our print method:
#include <stdio.h>
#include <jni.h>
#include "NativePrinter.h"
JNIEXPORT void JNICALL Java_iocapture_NativePrinter_print(JNIEnv *env, jclass class, jstring msg) {
const char *nativeString = (*env)->GetStringUTFChars(env, msg, 0);
printf("native[%s]\n", nativeString);
fflush(stdout);
(*env)->ReleaseStringUTFChars(env, msg, nativeString);
}
To demonstrate we can’t capture the native output, we’ll set System.out to a ByteArrayOutputStream and write to System.out and native standard output:
package iocapture;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
public class MainFail {
static {
System.load("/Users/tabbott/NativeIoCapture/dist/Debug/GNU-MacOSX/libNativeIoCapture.dylib");
}
public static final void main(String[] args) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
System.setOut(new PrintStream(os));
System.out.println("Hi from java code");
System.err.println("Got \"" + new String(os.toByteArray()) + "\" from System.out");
os.reset();
NativePrinter.print("Hi from native code");
System.err.println("Got \"" + new String(os.toByteArray()) + "\" from System.out");
}
}
You can see that the output to System.out went to our ByteArrayOutputStream but the output from the native code (in black) was not affected.
In order to capture that output, we can extend InputStream and use the old trick of creating a pipe and using dup2 to duplicate the output descriptor over STDOUT. First the Java class:
package iocapture;
import java.io.IOException;
import java.io.InputStream;
public class NativeInputStream extends InputStream {
public static final int STDOUT = 1;
public static final int STDERR = 2;
public NativeInputStream(int nativeFileNo) throws IOException {
init(nativeFileNo);
}
/** Create a pipe to capture output from native stdout file descriptor into this InputStream */
private native void init(int nativeFileNo) throws IOException;
@Override
public native int read() throws IOException;
@Override
public native void close() throws IOException;
}
The native C code:
#include <stdio.h>
#include <unistd.h>
#include <jni.h>
#include "NativeInputStream.h"
int filedes[2];
JNIEXPORT void JNICALL Java_iocapture_NativeInputStream_init(JNIEnv *env, jobject obj, jint fileno) {
if (-1 == pipe(filedes))
(*env)->ThrowNew(env, (*env)->FindClass(env, "java/io/IOException"), "pipe failed");
if (-1 == dup2(filedes[1], fileno)) {
close(filedes[0]);
close(filedes[1]);
(*env)->ThrowNew(env, (*env)->FindClass(env, "java/io/IOException"), "dup2 failed");
}
}
JNIEXPORT jint JNICALL Java_iocapture_NativeInputStream_read(JNIEnv *env, jobject obj) {
char buf;
ssize_t res = read(filedes[0], &buf, 1);
switch (res) {
case 0:
return -1;
case -1:
(*env)->ThrowNew(env, (*env)->FindClass(env, "java/io/IOException"), "read failed");
break;
default:
return buf;
}
}
JNIEXPORT void JNICALL Java_iocapture_NativeInputStream_close(JNIEnv *env, jobject obj) {
close(filedes[0]);
close(filedes[1]);
}
And finally we can see it in action:
package iocapture;
import java.io.IOException;
import java.io.InputStream;
public class Main {
static {
System.load("/Users/tabbott/NativeIoCapture/dist/Debug/GNU-MacOSX/libNativeIoCapture.dylib");
}
public static void main(String[] args) throws IOException {
// Capture output to native stdout file descriptor.
// System.out is written to the native stdout file descriptor so all captured output
// is written to System.err in order to avoid creating a loop!
final InputStream is = new NativeInputStream(NativeInputStream.STDOUT);
Thread t = new Thread() {
@Override
public void run() {
// Tempting to wrap it in a BufferedReader and InputStreamReader and just use readLine()
// but InputStreamReader seems to have a large internal buffer which it must fill before
// you get anything out of it.
StringBuilder s = new StringBuilder();
while (true) {
try {
int ch = is.read();
if (-1 == ch)
break;
if ('\n' == ch) {
System.err.println("Read from native handle: \"" + s + "\"");
s.setLength(0);
} else {
s.append((char)ch);
}
}
catch (IOException ex) {
ex.printStackTrace();
}
}
System.err.println("EOF");
}
};
t.setDaemon(true);
t.start();
// and finally some output to capture
NativePrinter.print("Hello, world!");
}
}

This example was written and tested using Java 1.6 on MacOS X and should work on any UN*X platform. I’m not familiar with the Windows APIs but I’m sure something similar is possible there.
The 
