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

I’m trying to map a structure which contains an array of structure in JNA. The size of the embedded structure is not defined in advanced in the C++ declaration of the structure but in the java code. My issue is that I get Exception in thread “main” java.lang.Error: Invalid memory access

The C++ header file is as follows :

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;

The C++ code is as follows :

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 = %pn", i_initParamList);
        printf("DLL PRINT init ---- i_initParamList->param_list_size = %in", i_initParamList->param_list_size);
        if (i_initParamList->init_param == NULL){
            printf("DLL PRINT init ---- i_initParamList->init_param must not be NULLn");
            returnedCode = 1;
    }else{
        for (int i = 0; i<i_initParamList->param_list_size;i++){
        printf("DLL PRINT init ---- i_initParamList->init_param[i]->key = %sn",i_initParamList->init_param[i].key);
        printf("DLL PRINT init ---- i_initParamList->init_param[i]->key_value = %in",i_initParamList->init_param[i].key_value);
    }
    
    ...
}

The java JNA code is as follows :

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

...
}

The Sample.java code which calls the JNA library is as follows : Note that I did not add the way the other parameters of Init function are managed as I do not have any issues with them.

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

The traces results coming from C++ and Java code are as follows :

10:41:28,303 DEBUG Sample:193 - params = [MyLibrary$Param;@1e67a849
10:41:28,312 DEBUG Sample:194 - paramList = MyLibrary$ParamList$ByReference([email protected] (52 bytes)) {
  MyLibrary$Param init_param[4]@0x0=[MyLibrary$Param;@1e67a849
  int [email protected]=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 = 

It works if I declare my Structure in C++ header as follows :

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

But I do not want to define the init_param array size in the C++ code as it must be defined on java code side.

Regarding added following code in the javacode :

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

I’m not sure that I have to add this in both structures i.e in ParamList and in Param structure. But anyway if I delete both of those constructor I have exactly the same issue.

I saw another way to manage my requirement (looking at toArray(size) method from chapter Structure of https://javadoc.io/doc/net.java.dev.jna/jna/latest/index.html for 5.8.0 JNA version. Indeed I also followed the link How to fill an array of structures in JNA? but I get Invalid Memory access. I’ll create a separate question regarding this second kind of implementation if I cannot have my Structure’s array in a Structure. And I had to split the Param array and the size of the array in 2 separate parameters of Init function instead of setting them into a unique Structure. And as I already have many parameters in my Init function I would prefer to have only one structure parameter. And I guess that It should be possible in JNA.

Does any one have a clue?

Thanx for your help.

Answer

The problem is in your mapping of this structure

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

The * indicates this is a pointer to memory elsewhere that you need to define.

Structures are treated as ByValue by default inside structures, and as ByRefrence by default in function arguments. So here you need to explicitly define the ByReference version of the structure (or use a plain Pointer, which is less type safe.)

So you’ll get this as the main part of your structure.

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

Next, you’ve indicated you want to define this array and allocate its memory yourself in Java. The important thing to remember here is that C treats arrays as contiguous memory, so you really only have two choices: allocate a large block yourself with Memory and set values at the offsets manually; or use Structure.toArray() designed for exactly this case: You start with an instantiated structure and then tell the toArray() method how many copies of it you need.

So your sample code would look like this:

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;

And here, you pass it to the native function. It is ByReference by default.

You can find another general example here