Как отобразить в JNA массив структуры внутри структуры, не устанавливая размер массива в объявлении C++

Я пытаюсь отобразить структуру, содержащую массив структур в JNA. Размер встроенной структуры определяется не в расширенном объявлении структуры C++, а в коде Java. Моя проблема в том, что я получаю исключение в потоке main java.lang.Error: Invalid memory access

Заголовочный файл C++ выглядит следующим образом:

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;

Код С++ выглядит следующим образом:

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

Код java JNA выглядит следующим образом:

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

...
}

Код Sample.java, который вызывает библиотеку JNA, выглядит следующим образом: Обратите внимание, что я не добавлял способ управления другими параметрами функции 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++ следующим образом:

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

Но я не хочу определять размер массива init_param в коде C++, поскольку он должен быть определен на стороне кода Java.

Что касается добавленного следующего кода в javacode:

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

Я не уверен, что мне нужно добавить это в обе структуры, то есть в ParamList и в структуру Param. Но в любом случае, если я удалю оба этих конструктора, у меня будет точно такая же проблема.

Я увидел другой способ управления моим требованием (посмотрев на метод toArray(size) из главы Структура https://javadoc.io/doc/net.java.dev.jna/jna/latest/index.html для версии 5.8.0 JNA. Действительно, я также перешел по ссылке Как заполнить массив структур в JNA? но я получаю недействительный доступ к памяти.Я создам отдельный вопрос относительно этого второго вида реализации, если я не могу иметь массив моей структуры в структуре.И мне пришлось разделить массив Param и размер массива на 2 отдельных параметра Функция инициализации вместо того, чтобы устанавливать их в уникальную структуру.И поскольку у меня уже есть много параметров в моей функции инициализации, я бы предпочел иметь только один параметр структуры.И я думаю, что это должно быть возможно в JNA.

У кого-нибудь есть ключ?

Спасибо за вашу помощь.


person cknelle    schedule 30.03.2021    source источник
comment
Наличие фиксированного поля после поля переменной длины является большим признаком того, что что-то не так. Но это не переменная длина, поле init_param является указателем. Это особый случай, когда вам нужно использовать версию структуры по ссылке, Param.ByReference. Настройка типа выравнивания необязательна. В вашем вопросе отсутствует информация о том, где выделена память для массива параметров. Вы не показываете это в своем коде C++. Обычно API сообщает вам, когда выделяется память и как ее освободить. Но ваша структура просто имеет указатель на память в другом месте и целое число, говорящее, сколько   -  person Daniel Widdis    schedule 31.03.2021
comment
Моя цель - сделать размер таблицы init_param редактируемым на стороне java (вид malloc на стороне java и избежать любых будущих обновлений на стороне C++). Таким образом, я попробовал указатель на стороне C вместо таблицы фиксированного размера. Это конкретный случай, когда вам нужно использовать версию структуры по ссылке, Param.ByReference=›это то, что я сделал. По умолчанию Param управляется ByReference. com.sun.jna.Native.malloc() на стороне java для выделения размера таблицы, потому что я думал, что JNA выполняет это выделение с помощью params[i] = new MyLibrary.Param();   -  person cknelle    schedule 31.03.2021
comment
Если я должен сделать malloc таблицы на стороне C++, это означает, что я должен сделать это в 3 шага: ШАГ 1 => создать метод с размером таблицы set_param_size(int param_size). Выполните malloc для размера C++. ШАГ 2 => замените первый параметр Init следующим образом int Init(Param* inOut_initParam,...) и заполните содержимое первого параметра на стороне java, используя адрес, указанный на стороне C++. ШАГ 3 => Выполните бесплатное выделение таблицы структуры inOut_initParam после использования на стороне C++.   -  person cknelle    schedule 31.03.2021
comment
Вы поместили туда интерфейс ByReference, но не определили его таким образом в структуре. Элементы структуры по умолчанию ByValue; параметры метода ByReference по умолчанию. Ваше первое поле структуры ParamList должно быть либо Param.ByReference (указывающим на первый элемент массива, который вы выделяете), либо вы можете использовать там Pointer. Вам придется вручную выполнить read(), так что это то, что вы поместите в свой конструктор... прочитайте размер, а затем прочитайте количество байтов. Я напишу это в полном ответе позже.   -  person Daniel Widdis    schedule 31.03.2021


Ответы (2)


Проблема в вашем отображении этой структуры

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.

Другой общий пример можно найти здесь

person Daniel Widdis    schedule 02.04.2021

Можете ли вы рассмотреть следующую структуру данных?

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

Если да, пожалуйста, будьте точны с распределением памяти и использованием массива, так как такой подход довольно опасен и может легко привести к переполнению данных.

person Kola73    schedule 30.03.2021
comment
Это решение не работает, поскольку оно вызывает следующую ошибку при компиляции C++ => ошибка C2229: структура 's_ParamList' имеет недопустимый массив нулевого размера. - person cknelle; 31.03.2021
comment
Если init_param[0] объявлен последним членом структуры, это должно работать. В противном случае это должно вызвать ошибку C2229. - person Kola73; 31.03.2021
comment
Спасибо, действительно мне удалось скомпилировать по вашей рекомендации. Не объясняю почему... Но на стороне JNA у меня следующая ошибка => Exception in thread main java.lang.Error: Invalid memory access. Моя цель - сделать размер таблицы init_param редактируемым на стороне java (т.е. своего рода malloc на стороне java и избежать любых будущих обновлений на стороне C++). - person cknelle; 31.03.2021
comment
В C выделение должно выглядеть как malloc(sizeof(ParamList)+list_size*sizeof(Param)). Не знаю, как это сделать на Java - person Kola73; 31.03.2021