JNA 从结构内的结构数组中读取无效的内存访问
Invalid memory access with JNA reading from an array of structures inside a structure
我的任务是将 C# 应用程序迁移到 java 应用程序。 C# 应用程序使用几个 DLL 来完成它的工作,与外围设备通信。
DLL 的 header 在 C#
中看起来像这样
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct InnerStructure
{
/// COM port used by the device
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = PORT_SIZE)] //7
public string szPort;
/// Specifies whether the device is activated.
public bool fActivated;
/// Name of the device
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_NAME_SIZE)] //248
public string szName; //COM
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct ParamStructure
{
/// COM port used by the devices
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public InnerStructure[] USB;
}
[DllImport("PCLService.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool startPclService();
[DllImport("PCLService.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool stopPclService();
[DllImport("PclUtilities.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
public static extern int getUSBDevices(ref ParamStructure pStructure, ref int pdwSize, ref int pdwEntries);
我最初尝试使用 JNI,但我无法加载 DLL(他们使用 .NET,并且很难找到 DependenciesWalker 的依赖项),所以我切换到 JNA。
这是我的Java代码
public class DllDemo {
@Structure.FieldOrder({ "szPort", "fActivated", "szName" })
public static class InnerStructure extends Structure {
public PointerByReference szPort;
public IntByReference fActivated;
public PointerByReference szName;
public InnerStructure() {};
public InnerStructure(Pointer p) {
super(p);
read();
};
}
@Structure.FieldOrder({ "USB" })
public static class ParamStructure extends Structure implements Structure.ByReference {
// In the C# code, the array is size 10
public InnerStructure[] USB = (InnerStructure[])new InnerStructure().toArray(10);
public ParamStructure() {};
public ParamStructure(Pointer p) {
super(p);
read();
};
}
public interface MyService extends Library {
MyService INSTANCE = (MyService) Native.load("C:\IRD\Documentacion\VisaNet\App PCL Demo VisaNet - V2.11\x32\PCLService.dll", MyService.class);
boolean startPclService();
boolean stopPclService();
}
public interface MyUtilities extends Library {
MyUtilities INSTANCE = (MyUtilities) Native.load("C:\IRD\Documentacion\VisaNet\App PCL Demo VisaNet - V2.11\x32\PclUtilities", MyUtilities.class);
int getUSBDevices(ParamStructure paramStructure, IntByReference pdwSize, IntByReference pdwEntries);
}
public static void main(String args[]) {
System.out.println("start");
MyService.INSTANCE.startPclService();
ParamStructure paramStructure = new ParamStructure();
paramStructure.write();
//This value is copied straightforward from the original code as well
int size = (248 + 8 + 7) * 10;
IntByReference pdwSize = new IntByReference(size);
IntByReference pdwEntries = new IntByReference(0);
int Ret2 = MyUtilities.INSTANCE.getUSBDevices(paramStructure, pdwSize, pdwEntries);
System.out.println("Ret2 = " + Ret2 + ", pdwEntries = " + pdwEntries.getValue());
if (pdwEntries.getValue() > 0) {
for (int i = 0 ; i < pdwEntries.getValue() ; i++) {
InnerStructure inner = paramStructure.USB[i];
inner.read();
System.out.println(i + " => " + inner.toString());
System.out.println("toString 1 => " + inner.szPort.toString());
System.out.println("toString 2 => " + inner.szPort.getPointer().toString());
System.out.println(">" + inner.szPort.getPointer().getString(0, "utf8") + "<");
}
}
paramStructure.clear();
MyService.INSTANCE.stopPclService();
System.out.println("stop");
}
}
这是输出。
start
Ret2 = 0, pdwEntries = 1
0 => DllDemo$InnerStructure(allocated@0x59b550 (12 bytes) (shared from auto-allocated@0x59b550 (120 bytes))) {
PointerByReference szPort@0x0=native@0x4f0043 (com.sun.jna.ptr.PointerByReference@4f0043)
IntByReference fActivated@0x4=native@0x35004d (com.sun.jna.ptr.IntByReference@35004d)
PointerByReference szName@0x8=null
}
toString 1 => native@0x4f0043 (com.sun.jna.ptr.PointerByReference@4f0043)
toString 2 => native@0x4f0043
Exception in thread "main" java.lang.Error: Invalid memory access
at com.sun.jna.Native.getStringBytes(Native Method)
at com.sun.jna.Native.getString(Native.java:2224)
at com.sun.jna.Pointer.getString(Pointer.java:681)
at com.ingenico.DllDemo.main(DllDemo.java:65)
Process finished with exit code 1
第 65 行是这样的
System.out.println(">" + inner.szPort.getPointer().getString(0, "utf8") + "<");
有时它不会给出错误,但字符串为空。我还没弄明白为什么会这样。
类 和行 paramStructure.write() 和 inner.read() 中是否存在构造函数没有区别。
无论如何,这就是它在 IntelliJ 调试器中的样子
debugger
我试过像这样改变内部结构
public static class InnerStructure extends Structure implements Structure.ByReference {
public PointerByReference szPort;
public IntByReference fActivated;
public PointerByReference szName;
public InnerStructure() {};
public InnerStructure(Pointer p) { super(p); };
}
甚至这样。
public static class InnerStructure extends Structure implements Structure.ByReference {
public String szPort;
public int fActivated;
public String szName;
public InnerStructure() {};
public InnerStructure(Pointer p) { super(p); };
}
在这两种情况下,我得到
Exception in thread "main" java.lang.Error: Invalid memory access
at com.sun.jna.Native._getPointer(Native Method)
at com.sun.jna.Native.getPointer(Native.java:2211)
at com.sun.jna.Pointer.getPointer(Pointer.java:642)
at com.sun.jna.Pointer.getValue(Pointer.java:390)
at com.sun.jna.Structure.readField(Structure.java:732)
at com.sun.jna.Structure.read(Structure.java:591)
at com.sun.jna.Structure.autoRead(Structure.java:2141)
at com.sun.jna.Structure.conditionalAutoRead(Structure.java:561)
at com.sun.jna.Structure.updateStructureByReference(Structure.java:690)
at com.sun.jna.Pointer.readArray(Pointer.java:492)
at com.sun.jna.Pointer.getValue(Pointer.java:450)
at com.sun.jna.Structure.readField(Structure.java:732)
at com.sun.jna.Structure.read(Structure.java:591)
at com.sun.jna.Structure.autoRead(Structure.java:2141)
at com.sun.jna.Function.invoke(Function.java:381)
at com.sun.jna.Library$Handler.invoke(Library.java:265)
at com.sun.proxy.$Proxy3.getUSBDevices(Unknown Source)
at com.ingenico.DllDemo.main(DllDemo.java:49) <- getUSBDevices
您没有正确映射字符串。
您已将 szPort
定义为 PointerByReference
,它是指向包含指针的内存位置的指针。然后你试图用 inner.szPort.getPointer()
调用它。那还是一样"pointer to the pointer"。 JNA 的一个好的经验法则是,如果您正在使用 ByReference
class 并且从不访问它的 getValue()
方法,那么您可能做错了什么。你的意思可能是 inner.szPort.getValue().getString(...)
。但这会失败,因为您实际上没有真正的指针。填充 szPort
元素的前 4 个字节实际上是 unicode 字符。
您已经定义了包含 3 个 4 字节元素(3 x 4 字节指针)的结构,总计 12 个字节(根据您的调试图像)。您实际上需要一个 7 个字符的固定宽度字符串、4 个字节的布尔值和 248 个字符的固定宽度字符串。如果您的编码是 ASCII,则为 259 个字节,或者在您的情况下,Unicode 字符串为 514 个字节。
查看您的调试图像,我们可以了解发生了什么。前 4 个字节包含 0x0043004f,您将其视为指针(地址 0x4f0043)并尝试从中读取数据。但它实际上是 unicode 字符 0x0043 ("C") 和 0x004f ("O")。当您尝试从该地址读取时,您并不拥有内存。如果幸运的话,内存为零并且您从未读过任何东西,并且您的代码 returns 是一个零长度的空终止字符串。但是,如果该内存不是 null,则会出现错误。
接下来的 4 个字节是 0x004d0035,您将其称为 IntByReference "pointer" (0x35004d),但它实际上是字符 "M" 和“5”。由于 szName
字段有一个空指针,接下来的 4 个字节似乎是 0x00000000 并且您已经命中了空终止符。所以看起来你的 szPort
字符串是 "COM5",隐藏在众目睽睽之下!
现在我们知道您的代码为什么会出错了。你是怎么解决的?
您的 C# 映射定义 szPort
的类型是 UnmanagedType.ByValTStr
,其中 according to MS Docs 是:
A fixed-length array of characters; the array's type is determined by
the character set of the containing structure.
您可能需要该结构中的字节数组(您可以使用适当的编码将其放入 String
构造函数中),长度为 PORT_SIZE
x 字符宽度(如果您的编码为 1是 ASCII,2 表示 Unicode)。您应该使用 charwidth x MAX_NAME_SIZE
.
类似地映射 szName
最后,IntByReference
也是一个指针(在您的系统上是 4 个字节),但 C# 中的原始变量是 bool
。如果不对其进行编组,它会映射到 Windows BOOL
,这是一个普通的 4 字节 C int
。 Java 的 boolean
也是 4 个字节,所以你可以在这里使用它。所以你可能想要这样的东西:
int CHAR_WIDTH = W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE ? 2 : 1;
int PORT_SIZE = 7;
int MAX_NAME_SIZE = 248;
class InnerStructure ... {
public byte[] szPort = new byte[CHAR_WIDTH * PORT_SIZE];
public boolean fActivated;
public byte[] szName = new byte[CHAR_WIDTH * MAX_NAME_SIZE];
...
}
我的任务是将 C# 应用程序迁移到 java 应用程序。 C# 应用程序使用几个 DLL 来完成它的工作,与外围设备通信。
DLL 的 header 在 C#
中看起来像这样 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct InnerStructure
{
/// COM port used by the device
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = PORT_SIZE)] //7
public string szPort;
/// Specifies whether the device is activated.
public bool fActivated;
/// Name of the device
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_NAME_SIZE)] //248
public string szName; //COM
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct ParamStructure
{
/// COM port used by the devices
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public InnerStructure[] USB;
}
[DllImport("PCLService.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool startPclService();
[DllImport("PCLService.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool stopPclService();
[DllImport("PclUtilities.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
public static extern int getUSBDevices(ref ParamStructure pStructure, ref int pdwSize, ref int pdwEntries);
我最初尝试使用 JNI,但我无法加载 DLL(他们使用 .NET,并且很难找到 DependenciesWalker 的依赖项),所以我切换到 JNA。
这是我的Java代码
public class DllDemo {
@Structure.FieldOrder({ "szPort", "fActivated", "szName" })
public static class InnerStructure extends Structure {
public PointerByReference szPort;
public IntByReference fActivated;
public PointerByReference szName;
public InnerStructure() {};
public InnerStructure(Pointer p) {
super(p);
read();
};
}
@Structure.FieldOrder({ "USB" })
public static class ParamStructure extends Structure implements Structure.ByReference {
// In the C# code, the array is size 10
public InnerStructure[] USB = (InnerStructure[])new InnerStructure().toArray(10);
public ParamStructure() {};
public ParamStructure(Pointer p) {
super(p);
read();
};
}
public interface MyService extends Library {
MyService INSTANCE = (MyService) Native.load("C:\IRD\Documentacion\VisaNet\App PCL Demo VisaNet - V2.11\x32\PCLService.dll", MyService.class);
boolean startPclService();
boolean stopPclService();
}
public interface MyUtilities extends Library {
MyUtilities INSTANCE = (MyUtilities) Native.load("C:\IRD\Documentacion\VisaNet\App PCL Demo VisaNet - V2.11\x32\PclUtilities", MyUtilities.class);
int getUSBDevices(ParamStructure paramStructure, IntByReference pdwSize, IntByReference pdwEntries);
}
public static void main(String args[]) {
System.out.println("start");
MyService.INSTANCE.startPclService();
ParamStructure paramStructure = new ParamStructure();
paramStructure.write();
//This value is copied straightforward from the original code as well
int size = (248 + 8 + 7) * 10;
IntByReference pdwSize = new IntByReference(size);
IntByReference pdwEntries = new IntByReference(0);
int Ret2 = MyUtilities.INSTANCE.getUSBDevices(paramStructure, pdwSize, pdwEntries);
System.out.println("Ret2 = " + Ret2 + ", pdwEntries = " + pdwEntries.getValue());
if (pdwEntries.getValue() > 0) {
for (int i = 0 ; i < pdwEntries.getValue() ; i++) {
InnerStructure inner = paramStructure.USB[i];
inner.read();
System.out.println(i + " => " + inner.toString());
System.out.println("toString 1 => " + inner.szPort.toString());
System.out.println("toString 2 => " + inner.szPort.getPointer().toString());
System.out.println(">" + inner.szPort.getPointer().getString(0, "utf8") + "<");
}
}
paramStructure.clear();
MyService.INSTANCE.stopPclService();
System.out.println("stop");
}
}
这是输出。
start
Ret2 = 0, pdwEntries = 1
0 => DllDemo$InnerStructure(allocated@0x59b550 (12 bytes) (shared from auto-allocated@0x59b550 (120 bytes))) {
PointerByReference szPort@0x0=native@0x4f0043 (com.sun.jna.ptr.PointerByReference@4f0043)
IntByReference fActivated@0x4=native@0x35004d (com.sun.jna.ptr.IntByReference@35004d)
PointerByReference szName@0x8=null
}
toString 1 => native@0x4f0043 (com.sun.jna.ptr.PointerByReference@4f0043)
toString 2 => native@0x4f0043
Exception in thread "main" java.lang.Error: Invalid memory access
at com.sun.jna.Native.getStringBytes(Native Method)
at com.sun.jna.Native.getString(Native.java:2224)
at com.sun.jna.Pointer.getString(Pointer.java:681)
at com.ingenico.DllDemo.main(DllDemo.java:65)
Process finished with exit code 1
第 65 行是这样的
System.out.println(">" + inner.szPort.getPointer().getString(0, "utf8") + "<");
有时它不会给出错误,但字符串为空。我还没弄明白为什么会这样。
类 和行 paramStructure.write() 和 inner.read() 中是否存在构造函数没有区别。
无论如何,这就是它在 IntelliJ 调试器中的样子
debugger
我试过像这样改变内部结构
public static class InnerStructure extends Structure implements Structure.ByReference {
public PointerByReference szPort;
public IntByReference fActivated;
public PointerByReference szName;
public InnerStructure() {};
public InnerStructure(Pointer p) { super(p); };
}
甚至这样。
public static class InnerStructure extends Structure implements Structure.ByReference {
public String szPort;
public int fActivated;
public String szName;
public InnerStructure() {};
public InnerStructure(Pointer p) { super(p); };
}
在这两种情况下,我得到
Exception in thread "main" java.lang.Error: Invalid memory access
at com.sun.jna.Native._getPointer(Native Method)
at com.sun.jna.Native.getPointer(Native.java:2211)
at com.sun.jna.Pointer.getPointer(Pointer.java:642)
at com.sun.jna.Pointer.getValue(Pointer.java:390)
at com.sun.jna.Structure.readField(Structure.java:732)
at com.sun.jna.Structure.read(Structure.java:591)
at com.sun.jna.Structure.autoRead(Structure.java:2141)
at com.sun.jna.Structure.conditionalAutoRead(Structure.java:561)
at com.sun.jna.Structure.updateStructureByReference(Structure.java:690)
at com.sun.jna.Pointer.readArray(Pointer.java:492)
at com.sun.jna.Pointer.getValue(Pointer.java:450)
at com.sun.jna.Structure.readField(Structure.java:732)
at com.sun.jna.Structure.read(Structure.java:591)
at com.sun.jna.Structure.autoRead(Structure.java:2141)
at com.sun.jna.Function.invoke(Function.java:381)
at com.sun.jna.Library$Handler.invoke(Library.java:265)
at com.sun.proxy.$Proxy3.getUSBDevices(Unknown Source)
at com.ingenico.DllDemo.main(DllDemo.java:49) <- getUSBDevices
您没有正确映射字符串。
您已将 szPort
定义为 PointerByReference
,它是指向包含指针的内存位置的指针。然后你试图用 inner.szPort.getPointer()
调用它。那还是一样"pointer to the pointer"。 JNA 的一个好的经验法则是,如果您正在使用 ByReference
class 并且从不访问它的 getValue()
方法,那么您可能做错了什么。你的意思可能是 inner.szPort.getValue().getString(...)
。但这会失败,因为您实际上没有真正的指针。填充 szPort
元素的前 4 个字节实际上是 unicode 字符。
您已经定义了包含 3 个 4 字节元素(3 x 4 字节指针)的结构,总计 12 个字节(根据您的调试图像)。您实际上需要一个 7 个字符的固定宽度字符串、4 个字节的布尔值和 248 个字符的固定宽度字符串。如果您的编码是 ASCII,则为 259 个字节,或者在您的情况下,Unicode 字符串为 514 个字节。
查看您的调试图像,我们可以了解发生了什么。前 4 个字节包含 0x0043004f,您将其视为指针(地址 0x4f0043)并尝试从中读取数据。但它实际上是 unicode 字符 0x0043 ("C") 和 0x004f ("O")。当您尝试从该地址读取时,您并不拥有内存。如果幸运的话,内存为零并且您从未读过任何东西,并且您的代码 returns 是一个零长度的空终止字符串。但是,如果该内存不是 null,则会出现错误。
接下来的 4 个字节是 0x004d0035,您将其称为 IntByReference "pointer" (0x35004d),但它实际上是字符 "M" 和“5”。由于 szName
字段有一个空指针,接下来的 4 个字节似乎是 0x00000000 并且您已经命中了空终止符。所以看起来你的 szPort
字符串是 "COM5",隐藏在众目睽睽之下!
现在我们知道您的代码为什么会出错了。你是怎么解决的?
您的 C# 映射定义 szPort
的类型是 UnmanagedType.ByValTStr
,其中 according to MS Docs 是:
A fixed-length array of characters; the array's type is determined by the character set of the containing structure.
您可能需要该结构中的字节数组(您可以使用适当的编码将其放入 String
构造函数中),长度为 PORT_SIZE
x 字符宽度(如果您的编码为 1是 ASCII,2 表示 Unicode)。您应该使用 charwidth x MAX_NAME_SIZE
.
szName
最后,IntByReference
也是一个指针(在您的系统上是 4 个字节),但 C# 中的原始变量是 bool
。如果不对其进行编组,它会映射到 Windows BOOL
,这是一个普通的 4 字节 C int
。 Java 的 boolean
也是 4 个字节,所以你可以在这里使用它。所以你可能想要这样的东西:
int CHAR_WIDTH = W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE ? 2 : 1;
int PORT_SIZE = 7;
int MAX_NAME_SIZE = 248;
class InnerStructure ... {
public byte[] szPort = new byte[CHAR_WIDTH * PORT_SIZE];
public boolean fActivated;
public byte[] szName = new byte[CHAR_WIDTH * MAX_NAME_SIZE];
...
}