Home > Blockchain >  Redirecting stdout from C lib in Java in JNA
Redirecting stdout from C lib in Java in JNA

Time:12-19

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 %d\n", 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 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.

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.

CodePudding user response:

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.

CodePudding user response:

If you have no control over the source code of your C library, I suggest writing a separate CLI C app that uses it, and capturing its stdout.

If you do have control over the source code of your C library, consider restructuring its API so that it's not necessary to capture stdout.

Doing what you're attempting right now is fragile and it will not pay off.

CodePudding user response:

Daniel Widdis was right. If I change the code to:

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(48));

      MyCLibrary mylib = Native.load("TestPrintf", MyCLibrary.class);
      mylib.callPrintf();
   }

   public static void main(String[] args) {
      JNATest test = new JNATest();
      test.run();
   }
}

The file content is correct.

  • Related