I want to redirect the stdout
and stderr
C streams to Java, but I am struggling to do so. I used the result of this thread: https://users.jna.dev.java.narkive.com/VdgNgCIb/jna-solutions-to-catch-stdout-stderr-of-dll but it still does not work as intended.
Here is my C code (I compiled it as a TestPrintf.dll
library):
#include <stdio.h> #include "main.h" #include <windows.h> void callPrintf() { printf("Values %dn", 2); }
And my Java code:
The interface for catching the stdout
stream:
import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.Platform; import com.sun.jna.Pointer; public interface CLibrary extends Library { CLibrary INSTANCE = (CLibrary) Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"), CLibrary.class); Pointer freopen(String filename, String mode, Pointer stream); Pointer __iob_func(); }
The interface to access my callPrintf()
function:
import com.sun.jna.Library; public interface MyCLibrary extends Library { public void callPrintf(); }
And now my Java code:
import com.sun.jna.Native; import com.sun.jna.NativeLibrary; import com.sun.jna.Pointer; import java.io.File; public class JNATest { public void run() { Native.setProtected(true); File dir = new File("D:/Java/JNATest/native"); NativeLibrary.addSearchPath("TestPrintf", dir.getPath()); CLibrary clib = CLibrary.INSTANCE; Pointer io = clib.__iob_func(); File file = new File(dir, "stdout.txt"); clib.freopen(file.getPath(), "w", io.share(64)); MyCLibrary mylib = Native.load("TestPrintf", MyCLibrary.class); mylib.callPrintf(); } public static void main(String[] args) { JNATest test = new JNATest(); test.run(); } }
I have no exception, the C code is called correctly, the stdout.txt
file is created, but there is nothing in it.
- If I comment the
clib.freopen
line, I see the result correctly in my IDE (in my case Netbeans) output - And nothing changes if I call
fflush(stdout)
at the end of my C function - In the result of the JNA discussion I referred to above, they use
io.share(32)
rather thanio.share(64)
, but if I do that, my app crash, probably because they were still on a 32 bit platform, and mine is 64 bit.
What did I do wrong? Plus initially I did not want to create a file, but I wanted to show the catched stdout output in a TextArea in my Java app.
Advertisement
Answer
The problem is this assumption:
In the result of the JNA discussion I referred to above, they use io.share(32) rather than io.share(64), but if I do that, my app crash, probably because they were still on a 32 bit platform, and mine is 64 bit.
The 32 isn’t a number of bits, it’s a number of bytes. You’re correct in identifying that the change in bitness is causing you to point to an invalid pointer, but not correct in how you fixed it.
In JNA, share()
gives a pointer offset. You are dealing with an array of structures (one each for stdin, stdout, and stderr), so the io
pointer itself is to stdin, and the 32-byte offset in the example you linked to points to the second structure in the array (stdout). You could double that offset to get the output from stderr.
The FILE
structure documented at the link you quoted is:
typedef struct _iobuf { char* _ptr; int _cnt; char* _base; int _flag; int _file; int _charbuf; int _bufsiz; char* _tmpfname; } FILE;
The size of this structure on a 32-bit OS is 32 bytes (8 x 4-byte fields), the source of the share(32)
.
On 64-bit systems, the three pointers (char *
) double in size from 4 bytes to 8 bytes, adding 12 to the size of the structure in a 64-bit implementation, but the int
fields remain at 4 bytes.
With no alignment that extra 12 bytes would make a 44 byte total. However, on LP64 your pointer fields will be aligned to 8 bytes, so there’s 4 bytes of padding after _cnt
and the structure size is 48 bytes.
So changing your code to use io.share(48)
should solve the problem (at least on that operating system).
Note from this answer discussing the FILE
structure that comments around the code state:
Some believe that nobody in their right mind should make use of the internals of this structure.
That is sage advice, since there’s evidence that the structure fields are platform dependent. As @alexey-veleshko stated in their answer,
Doing what you’re attempting right now is fragile
One improvement you could make to make your code both 32- and 64- bit compatible is to let JNA calculate the native structure size for you by mapping it:
@FieldOrder ({"_ptr", "_cnt", "_base", "_flag", "_file", "_charbuf", "_bufsiz", "_tmpfname"}) class FILE extends Structure { public Pointer _ptr; public int _cnt; public Pointer _base; public int _flag; public int _file; public int _charbuf; public int _bufsiz; public Pointer _tmpfname; }
Calculating the size()
of this structure will give you the value you need (e.g., new FILE().size()
would return 48).
The mapping assumes FILE
is mapped the same as documented above. It’s probably a better idea to use the actual C headers, and edit your C code to make sizeof(FILE)
available to you from a function in your own DLL.