Java JNA数组和指针
我现在正在做一个项目,它要求我接收来自C库的Java调用。基本上我调用一个接受函数指针的C函数,然后C函数使用函数指针作为回调。我使用JNA传递一个Java对象(从现在起我将称之为回调对象)作为回调函数。回调对象有一个方法,该方法接收包含16个元素字节数组和其他变量的C结构(称为Frame)。我以标准JNA方式将此结构包装到Java类中,如下所示:Java JNA数组和指针,java,c,jna,Java,C,Jna,我现在正在做一个项目,它要求我接收来自C库的Java调用。基本上我调用一个接受函数指针的C函数,然后C函数使用函数指针作为回调。我使用JNA传递一个Java对象(从现在起我将称之为回调对象)作为回调函数。回调对象有一个方法,该方法接收包含16个元素字节数组和其他变量的C结构(称为Frame)。我以标准JNA方式将此结构包装到Java类中,如下所示: class Frame extends Structure { public short port; public short fl
class Frame extends Structure {
public short port;
public short flags;
public Pointer name // this is a pointer to the byte array
public int rateDivisor;
}
回调机制工作正常!回调对象从C接收一个帧对象,但当我尝试使用name.getByteArray(0,16)
从指针获取字节数组时,应用程序崩溃,出现访问冲突异常。但是,如果我用字节数组替换指针:
class Frame extends Structure {
public short port;
public short flags;
public byte[] name = new byte[16];
public int rateDivisor;
}
那么代码就可以正常工作了!但是,我有一个很好的理由不想使用此代码。每次调用函数时,返回的帧实际上是同一个对象(它只是被重用),但字节数组是一个新对象。这个回调函数每秒被调用多次,这会导致垃圾收集器疯狂地吞噬数千个临时数组。这个项目对性能至关重要,所以我真的不希望在应用程序的这一部分中有任何临时对象
我的猜测是,通过在Frame类中使用字节数组,我使JNA创建了C字节数组的新副本。我想做的是有一个指向C字节数组的指针,这样就不需要复制数据,也就不用进行JNA指针的实验了
我的问题是为什么我不能从指针获取字节数组?任何帮助都将不胜感激:)
(注意:让这更困难的是我没有访问C源代码的权限!!)原因可能很简单:name不是C结构中的指针 让我举两个例子: C: 映射到Java:
class MyJStruct extends Structure{
Pointer name;
}
class MyJStruct extends Structure{
byte[] name = new byte[16];
}
但在另一个场景中(我认为你陷入其中):
C:
映射到Java:
class MyJStruct extends Structure{
Pointer name;
}
class MyJStruct extends Structure{
byte[] name = new byte[16];
}
在第一种情况下,C结构保存一个指针,它的sizeof是指针的大小(4或8字节,取决于32/64位),在第二种情况下,它保存整个数组,这意味着它的大小是16字节
这解释了两个Java映射之间的差异:JNA需要知道如何读取结构字段,并且还解释了在执行以下操作时调用name.getByteArray(0,16)
时出错的原因:
标志
字段后的前4个字节sizeof(struct Frame)
是24,那么该结构包含整个数组如果它是12(或16),那么它包含一个32位指针(或64位指针)
关于这件事的官方文件非常清楚,但很难找到,所以你可以这样做:
您可以在“嵌套数组”下找到它,我鼓励您阅读“可变大小结构”下面的部分
希望这有帮助
问候(满足StackTrace期望的新答案;请参阅对我先前答案的评论)
嗯,我不确定是否有一个好的和干净的方式来做这件事。。。因此,我将指出一个很好的游戏,叫做内存和指针游戏
请记住,您可能需要以下方法(C):
在我看来,最糟糕的情况是第三种情况,因为如果没有前面解释的法线映射,就无法实现。下一个最坏的情况是第一个,因为您不会在RAM中获得位置,而是在堆栈顶部获得直接结果,因此读取它需要您欺骗JNA,以便能够获取指针并读取要跳过的内存区域(Java)之后的内容:
我想你明白了。如果你的结构更大,只需将它分成几个小部分,避免你想跳过的区域,然后在第一部分指向的内存中手动导航,重新连接它们
以这种方式工作时要记住的事项:
指针大小32/64位--4或8字节请参见com.sun.jna.Native.Pointer\u size
我还没有检查这个答案
将回调的输入参数更改为指针。维护您自己的私有指针->帧映射(有或没有弱引用),并且仅在没有输入指针的情况下基于输入指针创建新帧
e、 g
Map frames=newhashmap();
无效回调(指针p){
Frame f=frames.get(p);
如果(f==null){
f=新帧(p);
帧。put(p,f);
}
//做任何事。。。
}
谢谢你的文档,它非常有用。您的答案解释了为什么Frame的字节数组版本可以工作,但我认为C数组是用指针实现的,因此尝试使用指针?不管怎样,您知道有什么方法可以阻止JNA每次C向Java发送帧对象时创建一个新数组吗?这才是我真正的问题。
Frame nativeLibFunc(...); //1
Frame * nativeLibFunc(...); //2
void nativeLibFunc(Frame f); //3
void nativeLibFunc(Frame * fptr);//4
class MyMicroFrame extends Structure {
public short port;
public short flags;
//public byte[] name = new byte[16]; ==> commented out, ignored
//public int rateDivisor; ==> commented out, read manually, see below
}
//...
MyMicroFrame mmf = NativeLib.INSTANCE.nativeLibFunc(...);
int rateDivisor = mmf.getPointer().getInt(20);
Map frames = new HashMap<Pointer,Frame>();
void callback(Pointer p) {
Frame f = frames.get(p);
if (f == null) {
f = new Frame(p);
frames.put(p, f);
}
// do whatever...
}