简体   繁体   English

无效的 memory 访问,JNA 从结构内的结构数组读取

[英]Invalid memory access with JNA reading from an array of structures inside a structure

I'm tasked with migrating a C# app to a java one.我的任务是将 C# 应用程序迁移到 java 应用程序。 The C# app uses couple DLLs to do its works, comunicating with peripheral devices. C# 应用程序使用几个 DLL 来完成其工作,与外围设备通信。

The header for the DLLs looks like this in C# 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);

I tried initially with JNI, but I couldn't manage to load the DLLs (they use .NET, and it was a pain to find the dependencies with DependenciesWalker), so I switched to JNA.我最初尝试使用 JNI,但无法加载 DLL(它们使用 .NET,使用 DependenciesWalker 查找依赖项很痛苦),所以我切换到 JNA。

This is my Java code这是我的 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");
    }
}

And this is the output.这是 output。

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

Line 65 is this第65行是这个

System.out.println(">" + inner.szPort.getPointer().getString(0, "utf8") + "<");

SOMETIMES it doesn't give an error, but the string is empty.有时它不会给出错误,但字符串是空的。 I haven't figured out why this happens.我还没弄清楚为什么会这样。

The presence or absence of the constructors in both classes and the lines paramStructure.write() and inner.read() make no difference.两个类中构造函数的存在与否以及 paramStructure.write() 和 inner.read() 行没有区别。

For whatever is worth, this is how it looks in the debugger from IntelliJ无论如何,这就是它在 IntelliJ 调试器中的外观

debugger调试器

I have tried changing the inner structure like this我试过像这样改变内部结构

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

Or even like this.甚至像这样。

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

In both cases, I get在这两种情况下,我得到

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

You're not correctly mapping the strings.您没有正确映射字符串。

You've defined szPort as a PointerByReference , which is a pointer to a memory location holding a pointer.您已将szPort定义为PointerByReference ,它是指向包含指针的 memory 位置的指针。 Then you are trying to call it with inner.szPort.getPointer() .然后你试图用inner.szPort.getPointer()来调用它。 That's still the same "pointer to the pointer".这仍然是相同的“指向指针的指针”。 A good rule of thumb with JNA is that if you are using a ByReference class and never accessing the getValue() method on it, you're probably doing someting wrong. JNA 的一个好的经验法则是,如果您使用的是ByReference class 并且从不访问其上的getValue()方法,那么您可能做错了。 You probably meant inner.szPort.getValue().getString(...) .您可能的意思是inner.szPort.getValue().getString(...) But that would have failed, because you don't actually have a real pointer.但这会失败,因为您实际上没有真正的指针。 The first 4 bytes that fill the szPort element are actually unicode characters.填充szPort元素的前 4 个字节实际上是 unicode 字符。

You've defined the structure with 3 4-byte elements (3 x 4-byte pointers) totaling 12 bytes (per your debug image).您已经定义了具有 3 个 4 字节元素(3 x 4 字节指针)的结构,总共 12 个字节(根据您的调试图像)。 You actually need a 7-character fixed width string, 4-byte boolean, and 248-character fixed width string.您实际上需要一个 7 个字符的固定宽度字符串、4 个字节的 boolean 和 248 个字符的固定宽度字符串。 either 259 bytes if your encoding is ASCII, or in your case, 514 bytes for Unicode strings.如果您的编码是 ASCII,则为 259 个字节,或者在您的情况下,Unicode 字符串为 514 个字节。

Looking at your debug image, we can see what's happening.查看您的调试图像,我们可以看到发生了什么。 The first 4 bytes contain 0x0043004f which you're treating like a pointer (address 0x4f0043) and trying to read data from.前 4 个字节包含 0x0043004f,您将其视为指针(地址 0x4f0043)并尝试从中读取数据。 But it's really the unicode characters 0x0043 ("C") and 0x004f ("O").但实际上是 unicode 字符 0x0043(“C”)和 0x004f(“O”)。 When you try to read from that address, you don't own the the memory.当您尝试从该地址读取数据时,您并不拥有 memory。 If you're lucky the memory is zero and you never read anything, and your code returns an zero-length null terminated string.如果幸运的话,memory 为零并且您从未读过任何内容,并且您的代码返回一个长度为零的 null 终止的字符串。 But if that memory is anything other than null, you get the error.但是,如果 memory 不是 null,则会出现错误。

The next 4 bytes are 0x004d0035 which you're referring to as an IntByReference "pointer" (0x35004d) but it's really the characters "M" and "5".接下来的 4 个字节是 0x004d0035,您将其称为 IntByReference“指针”(0x35004d),但实际上是字符“M”和“5”。 Since the szName field has a null pointer, it appears the next 4 bytes are 0x00000000 and you've hit the null terminator.由于szName字段有一个 null 指针,因此接下来的 4 个字节似乎是 0x00000000 并且您已经遇到了 null 终止符。 So it looks like your szPort String is "COM5", hiding right there in plain sight!所以看起来你的szPort字符串是“COM5”,就隐藏在那里!

So now we know why your code is breaking.所以现在我们知道为什么你的代码会被破坏。 How do you fix it?你如何解决它?

Your C# mapping defines the type of szPort is UnmanagedType.ByValTStr which, according to MS Docs , is:您的 C# 映射定义szPort的类型是UnmanagedType.ByValTStr根据 MS Docs ,它是:

A fixed-length array of characters;一个固定长度的字符数组; the array's type is determined by the character set of the containing structure.数组的类型由包含结构的字符集决定。

You probably want a byte array in that structure (that you can toss into a String constructor with the appropriate encoding), with a length of PORT_SIZE x character width (1 if your encoding is ASCII, 2 for Unicode).您可能需要该结构中的字节数组(您可以将其放入具有适当编码的String构造函数),长度为PORT_SIZE x 字符宽度(如果您的编码是 ASCII,则为 1,Unicode 为 2)。 You should map szName similarly with charwidth x MAX_NAME_SIZE .您应该 map szName与 charwidth x MAX_NAME_SIZE类似。

Finally, IntByReference is also a pointer (4-bytes on your system) but the original variable in C# is a bool .最后, IntByReference也是一个指针(在您的系统上为 4 字节),但 C# 中的原始变量是一个bool Without marshaling it, it maps to the Windows BOOL which is a plain 4-byte C int .如果不对其进行编组,它会映射到 Windows BOOL ,这是一个普通的 4 字节 C int Java's boolean is also 4 bytes so you can just use that here. Java 的boolean也是 4 个字节,所以你可以在这里使用它。 So you probably want something like:所以你可能想要这样的东西:

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];
...
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM