如何在 JNA 中映射结构内的结构数组而不在 C++ 声明中设置数组大小

How to map in JNA an array of structure inside a structure without setting the array size on the C++ declaration

我试图在 JNA 中映射一个包含结构数组的结构。 嵌入式结构的大小不是在结构的 C++ 声明中预先定义的,而是在 java 代码中定义的。 我的问题是我在线程“main”中得到异常java.lang.Error:无效内存访问

C++header文件如下:

typedef struct s_Param
{ 
  char*     key; 
  uint32_t  key_value; 
} Param;

typedef struct s_ParamList {
    Param*    init_param;
    int       param_list_size; //  number of param items in the param List
} ParamList;

C++代码如下:

int Init(
    ParamList*       i_initParamList,   
    logfunction      i__callback,       
    const allocator* i__allocator,      
    void**           o__content) {
    ...
    if (i_initParamList != NULL){
        printf("DLL PRINT init ---- i_initParamList = %p\n", i_initParamList);
        printf("DLL PRINT init ---- i_initParamList->param_list_size = %i\n", i_initParamList->param_list_size);
        if (i_initParamList->init_param == NULL){
            printf("DLL PRINT init ---- i_initParamList->init_param must not be NULL\n");
            returnedCode = 1;
    }else{
        for (int i = 0; i<i_initParamList->param_list_size;i++){
        printf("DLL PRINT init ---- i_initParamList->init_param[i]->key = %s\n",i_initParamList->init_param[i].key);
        printf("DLL PRINT init ---- i_initParamList->init_param[i]->key_value = %i\n",i_initParamList->init_param[i].key_value);
    }
    
    ...
}

javaJNA代码如下:

public interface MyLibrary extends Library {

    @FieldOrder({ "key", "key_value" })
    public static class Param extends Structure {
        public static class ByReference extends Param implements Structure.ByReference {
        }
        public String key;
        public int key_value;

        public Param(){ // NOT sure that this adding constructor makes sense
            super();
            setAlignType(ALIGN_NONE);
        }
    }

    @FieldOrder({ "init_param", "param_list_size" })
    public static class ParamList extends Structure {
        public static class ByReference extends ParamList implements Structure.ByReference {
        }
        public Param[] init_param;
        public int param_list_size;
        
        ParamList(){ // NOT sure that this adding constructor makes sense
            super();
            setAlignType(ALIGN_NONE);
        }
    }
    
    public int Init(ParamList i_initParamList, logfunction i__callback, allocator i__allocator,
            PointerByReference o__content);

...
}

调用JNA库的Sample.java代码如下: 请注意,我没有添加 Init 函数的其他参数的管理方式,因为我对它们没有任何问题。

        int paramListSize = 4;
        MyLibrary.Param[] params = new MyLibrary.Param[paramListSize];

        for (int i = 0; i < paramListSize; i++) {
            params[i] = new MyLibrary.Param();
        }

        params[0].key = "first";
        params[0].key_value = 1;

        params[1].key = "second";
        params[1].key_value = 5;

        params[2].key = "third";
        params[2].key_value = 7;

        params[3].key = "forth";
        params[3].key_value = 9;

        MyLibrary.ParamList paramList = new MyLibrary.ParamList.ByReference();

        paramList.init_param = params;
        paramList.param_list_size = paramListSize;

        logger.debug("params = "+ params);
        logger.debug("paramList = "+ paramList);
        logger.debug("paramList.param_list_size = "+paramList.param_list_size);
        
        int errInit = IFunctions.Init(paramList, logCallback, i__allocator, o__content);

来自C++和Java代码的跟踪结果如下:

10:41:28,303 DEBUG Sample:193 - params = [MyLibrary$Param;@1e67a849
10:41:28,312 DEBUG Sample:194 - paramList = MyLibrary$ParamList$ByReference(auto-allocated@0x1f3d49fe5b0 (52 bytes)) {
  MyLibrary$Param init_param[4]@0x0=[MyLibrary$Param;@1e67a849
  int param_list_size@0x30=0x0004
}
10:41:28,316 DEBUG Sample:195 - paramList.param_list_size = 4
Exception in thread "main" java.lang.Error: Invalid memory access
    at com.sun.jna.Native.invokeInt(Native Method)
    at com.sun.jna.Function.invoke(Function.java:426)
    at com.sun.jna.Function.invoke(Function.java:361)
    at com.sun.jna.MyLibrary$Handler.invoke(MyLibrary.java:265)
    at com.sun.proxy.$Proxy3.init(Unknown Source)
    at Sample.main(Sample.java:216)
DLL PRINT init ---- i_initParamList = 000001F3D49FE5B0
DLL PRINT _init ---- init_param address = 0000021B4ADAF3D0
DLL PRINT init ---- i_initParamList->param_list_size = 1
DLL PRINT init ---- i_initParamList->init_param[i]->key = 

如果我在 C++ header 中声明我的结构如下:

typedef struct s_ParamList {
    Param     init_param[4];
    int       param_list_size; //  number of param items in the param List
} ParamList;

但我不想在 C++ 代码中定义 init_param 数组大小,因为它必须在 java 代码端定义。

关于在 java代码中添加以下代码:

ParamList(){ // NOT sure that this adding constructor makes sense
    super();
    setAlignType(ALIGN_NONE);
    }

我不确定我是否必须在两个结构中添加它,即在 ParamList 和 Param 结构中。 但是无论如何,如果我删除这两个构造函数,我会遇到完全相同的问题。

我看到了另一种管理我的要求的方法(查看 5.8.0 JNA 版本的 https://javadoc.io/doc/net.java.dev.jna/jna/latest/index.html 结构一章中的 toArray(size) 方法。 事实上,我也遵循了 link How to fill an array of structures in JNA? 但我得到了无效的内存访问。如果我不能在结构中包含我的结构数组,我将针对第二种实现创建一个单独的问题。 而且我必须将 Param 数组和数组的大小拆分为 Init 函数的 2 个单独参数,而不是将它们设置为唯一的结构。 由于我的 Init 函数中已经有很多参数,所以我宁愿只有一个结构参数。我想在 JNA 中应该是可能的。

有人知道吗?

感谢您的帮助。

可以考虑下面的数据结构吗?

typedef struct s_ParamList {
    int       param_list_size; //  number of param items in the param List
    Param     init_param[0];
} ParamList;

如果是,请准确分配内存和数组使用,因为这种方法很危险,很容易导致数据溢出

问题出在你对这个结构的映射

typedef struct s_ParamList {
    Param*    init_param;
    int       param_list_size; //  number of param items in the param List
} ParamList;

* 表示这是一个指向您需要定义的别处内存的指针。

结构在结构内部默认被视为 ByValue,在函数参数中默认被视为 ByRefrence。所以这里你需要显式定义结构的 ByReference 版本(或者使用类型安全性较低的普通指针。)

所以你会把它作为结构的主要部分。

@FieldOrder({ "init_param", "param_list_size" })
public static class ParamList extends Structure {
    public Param.ByReference init_param;
    public int param_list_size;    
}

接下来,您已表示要定义此数组并在 Java 中自行分配其内存。这里要记住的重要一点是 C 将数组视为连续内存,因此您实际上只有两个选择:使用 Memory 自己分配一个大块并手动设置偏移量的值;或使用专为这种情况设计的 Structure.toArray():您从一个实例化结构开始,然后告诉 toArray() 方法您需要它的副本数。

因此您的示例代码如下所示:

int paramListSize = 4;
// Note the syntax for allocating a contiguous array
MyLibrary.Param.ByReference[] params =
    (MyLibrary.Param.ByReference[]) new MyLibrary.Param.ByReference().toArray(paramListSize);

// set the values as you've alread done
params[0].key = "first";
params[0].key_value = 1;

// and so on...

// Now instantiate your structure and set its members
MyLibrary.ParamList paramList = new MyLibrary.ParamList();

// The first array member is the pointer to the start of the array
paramList.init_param = params[0];
paramList.param_list_size = paramListSize;

在这里,您将它传递给本机函数。默认是ByReference。

你可以找另一个通用的例子here