从 JNA Java 中的 C lib 重定向标准输出
Redirecting stdout from C lib in Java in JNA
我想将 stdout
和 stderr
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
文件,但里面什么都没有。
- 如果我注释
clib.freopen
行,我会在我的 IDE(在我的例子中是 Netbeans)输出中看到正确的结果
- 如果我在 C 函数的末尾调用
fflush(stdout)
,则不会发生任何变化
- 在我上面提到的 JNA 讨论的结果中,他们使用
io.share(32)
而不是 io.share(64)
,但如果我这样做,我的应用程序崩溃,可能是因为他们仍在 32位平台,我的是64位。
我做错了什么?另外,最初我不想创建文件,但我想在我的 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()
函数只需要调用一次。
我想将 stdout
和 stderr
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
文件,但里面什么都没有。
- 如果我注释
clib.freopen
行,我会在我的 IDE(在我的例子中是 Netbeans)输出中看到正确的结果 - 如果我在 C 函数的末尾调用
fflush(stdout)
,则不会发生任何变化 - 在我上面提到的 JNA 讨论的结果中,他们使用
io.share(32)
而不是io.share(64)
,但如果我这样做,我的应用程序崩溃,可能是因为他们仍在 32位平台,我的是64位。
我做错了什么?另外,最初我不想创建文件,但我想在我的 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()
函数只需要调用一次。