从 JNA Java 中的 C lib 重定向标准输出

Redirecting stdout from C lib in Java in JNA

我想将 stdoutstderr C 流重定向到 Java,但我很难做到这一点。我使用了这个线程的结果:https://users.jna.dev.java.narkive.com/VdgNgCIb/jna-solutions-to-catch-stdout-stderr-of-dll 但它仍然没有按预期工作。

这是我的 C 代码(我将其编译为 TestPrintf.dll 库):

#include <stdio.h>
#include "main.h"
#include <windows.h>

void callPrintf()
{
    printf("Values %d\n", 2);
}

还有我的 Java 代码:

抓取stdout流的接口:

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

访问我的callPrintf()函数的接口:

import com.sun.jna.Library;

public interface MyCLibrary extends Library {
   public void callPrintf();
}

现在我的 Java 代码:

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

我没有异常,C代码调用正确,创建了stdout.txt文件,但里面什么都没有。

我做错了什么?另外,最初我不想创建文件,但我想在我的 Java 应用程序的 TextArea 中显示捕获的标准输出输出。

如果您无法控制 C 库的源代码,我建议编写一个单独的 CLI C 应用程序来使用它,并捕获它的标准输出。

如果您确实可以控制 C 库的源代码,请考虑重组其 API,这样就没有必要捕获标准输出了。

做你现在正在尝试的事情是脆弱的,不会有回报。

问题是这个假设:

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.

32 不是位数,而是字节数。您确定位数的变化导致您指向无效指针是正确的,但您修复它的方式不正确。

在JNA中,share()给出了一个指针偏移量。您正在处理一组结构(stdin、stdout 和 stderr 各一个),因此 io 指针本身指向 stdin,而示例中的 32 字节偏移量 linked指向数组中的第二个结构 (stdout)。您可以将该偏移量加倍以从 stderr 获取输出。

您引用的 link 中记录的 FILE 结构是:

typedef struct _iobuf
{
    char* _ptr;
    int _cnt;
    char* _base;
    int _flag;
    int _file;
    int _charbuf;
    int _bufsiz;
    char* _tmpfname;
} FILE;

此结构在 32 位 OS 上的大小为 32 字节(8 x 4 字节字段),share(32).

的来源

在 64 位系统上,三个指针 (char *) 的大小从 4 字节增加到 8 字节,在 64 位实现中将结构的大小增加 12,但是 int 字段保持在 4 个字节。

在没有对齐的情况下,额外的 12 个字节总共为 44 个字节。但是,在 LP64 上,您的指针字段将对齐到 8 个字节,因此 _cnt 之后有 4 个字节的填充,结构大小为 48 个字节。

因此更改您的代码以使用 io.share(48) 应该可以解决问题(至少在该操作系统上)。

来自 this answer 的注释讨论了围绕代码状态注释的 FILE 结构:

Some believe that nobody in their right mind should make use of the internals of this structure.

这是明智的建议,因为有证据表明结构字段依赖于平台。正如@alexey-veleshko 在

中所述

Doing what you're attempting right now is fragile

为了让您的代码同时兼容 32 位和 64 位,您可以进行的一项改进是让 JNA 通过映射为您计算本机结构大小:

@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;
}

计算此结构的 size() 将为您提供所需的值(例如,new FILE().size() 将 return 48)。

映射假定 FILE 的映射与上面记录的相同。使用实际的 C 头文件并编辑您的 C 代码以使 sizeof(FILE) 可从您自己的 DLL 中的函数使用可能是更好的主意。

Daniel Widdis 是对的。如果我将代码更改为:

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

文件内容正确。

最后一个代码考虑了 Daniel Widdis 的最后一个建议:

C代码:

#include <stdio.h>
#include "main.h"
#include <windows.h>

void callPrintf()
{
    printf("Values %d\n", 2);
}

int getFileSize()
{
    return sizeof(FILE);
}

Java 中库的新表示:

import com.sun.jna.Library;

public interface MyCLibrary extends Library {
   public void callPrintf();
   public int getFileSize();
}

并且使用库的代码更改为从 FILE 结构中获取大小:

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;
      MyCLibrary mylib = Native.load("TestPrintf", MyCLibrary.class);
      Pointer io = clib.__iob_func();
      int fileSize = mylib.getFileSize();
      File file = new File(dir, "stdout.txt");
      clib.freopen(file.getPath(), "w", io.share(fileSize));

      mylib.callPrintf();
   }

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

在这个例子中,我在我正在使用的同一个库中添加了 getFileSize() 函数,但我可以在支持库中这样做,所以我仍然没有义务修改我的客户端库。 getFileSize()函数只需要调用一次。