Как использовать структуру со структурой в JNR FFI

У меня есть следующий код c:

#include <stdio.h>

struct Second {
    int a_number;
};

struct Top {
    struct Second second;
};

void lets_go(struct Top *top) {
    printf("The number is %d\n", top->second.a_number);
}

И я хочу сделать это, кроме как с Java:

int main(void) {
    struct Top top = {{8}};
    lets_go(&top);
}

Я также хочу использовать очень круто выглядящий материал jnr-ffi, поэтому я посмотрел на тесты, поработал и получил следующее:

package structs.playing;

import structs.playing.Program.Test.Top;
import structs.playing.Program.Test.Second;
import jnr.ffi.LibraryLoader;
import jnr.ffi.Runtime;
import jnr.ffi.Struct;

public final class Program {

    public static interface Test {

        void lets_go(Top top);

        public static final class Second extends Struct {               
            public final Signed32 a_number = new Signed32();                
            public Second(final Runtime runtime) {
                super(runtime);
            }
        }

        public static final class Top extends Struct {              
            public Second second;                           
            public Top(final Runtime runtime) {
                super(runtime);
            }
        }
    }

    public static void main(final String[] args) {

        Test test = LibraryLoader.create(Test.class).load("test");
        Runtime runtime = Runtime.getRuntime(test);         
        Top top = new Top(runtime);
        Second second = new Second(runtime);
        top.second = second;
        second.a_number.set(7);         
        test.lets_go(top);
    }    
}

Проблема в том, что значение a_number вообще не установлен, поэтому я получаю нежелательное значение в выводе, например:

The number is 46645760

Так как же получить то же, что и в моем C-коде?

3 ответа

Решение

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

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

package structs.playing;

import structs.playing.Program.Test.Top;
import jnr.ffi.LibraryLoader;
import jnr.ffi.Runtime;
import jnr.ffi.Struct;

public final class Program {

    public static interface Test {

        void lets_go(Top top);

        public static final class Second extends Struct {

            public final Signed32 a_number = new Signed32();

            public Second(final Runtime runtime) {
                super(runtime);
            }           
        }

        public static final class Top extends Struct {

            private final Pointer secondPointer = new Pointer();            
            public final Second second;

            public Top(final Runtime runtime) {
                super(runtime);                             
                second = new Second(runtime); 
                second.useMemory(secondPointer.getMemory());
            }           
        }
    }

    public static void main(final String[] args) {

         Test test = LibraryLoader.create(Test.class).load("test");
         Runtime runtime = Runtime.getRuntime(test);         
         Top top = new Top(runtime);
         top.second.a_number.set(8);         
         test.lets_go(top);
    }
}

РЕДАКТИРОВАТЬ: я потратил немного больше времени на просмотр кода, и мое понимание создания структур немного изменилось.

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

В структуре есть несколько вспомогательных функций для каждого варианта использования. Перегруженный метод array() позволяет вам зарегистрировать массив членов или структур. Метод inner() позволяет зарегистрировать одну структуру. В противном случае вы просто определяете новые объекты Member, и они автоматически регистрируются.

Например:

struct Second {
    int a_number;
};

struct Top {
    struct Second second;
    struct Second seconds[5];
    int another_number;
    int more_numbers[5];
};

Представляется как:

public final class Second extends Struct {
    public final Signed32 a_number = new Signed32();
    public Second(final Runtime runtime) {
        super(runtime);
    }
}

public final class Top extends Struct {              
    public final Second second = inner(new Second(getRuntime()));  
    public final Second[] seconds = array(new Second[5]);
    public final Signed32 another_number = new Signed32();
    public final Signed32[] more_numbers = array(new Signed32[5]);
    public Top(final Runtime runtime) {
        super(runtime);
    }
}

ОРИГИНАЛ: Я считаю, что правильный способ сделать это - использовать перегруженный конструктор Struct, который принимает (Runtime, Struct). https://github.com/jnr/jnr-ffi/blob/master/src/main/java/jnr/ffi/Struct.java#L129

protected Struct(Runtime runtime, Struct enclosing) {
    this(runtime);
    __info.alignment = enclosing.__info.alignment;
}

Этот конструктор заставляет включающую структуру совместно использовать свою память. Так что в вашем примере я думаю, что это будет выглядеть примерно так:

package structs.playing;

import structs.playing.Program.Test.Top;
import structs.playing.Program.Test.Second;
import jnr.ffi.LibraryLoader;
import jnr.ffi.Runtime;
import jnr.ffi.Struct;

public final class Program {

    public static interface Test {

        void lets_go(Top top);

        public static final class Second extends Struct {               
            public final Signed32 a_number = new Signed32();                
            public Second(final Runtime runtime, final Struct enclosing) {
                super(runtime, enclosing);
            }
        }

        public static final class Top extends Struct {              
            public Second second;                           
            public Top(final Runtime runtime) {
                super(runtime);
            }
        }
    }

    public static void main(final String[] args) {

        Test test = LibraryLoader.create(Test.class).load("test");
        Runtime runtime = Runtime.getRuntime(test);         
        Top top = new Top(runtime);
        Second second = new Second(runtime, top);
        top.second = second;
        second.a_number.set(7);         
        test.lets_go(top);
    }    
}

Обратите внимание на изменения в конструкторе Second и обратите внимание, что я передал объект Top объекту Second, чтобы он знал, что top его окружает. Это не проверено, просто делюсь тем, что я нашел, пытаясь понять код.

Я думаю, что в вашем примере произошло то, что Второй объект выделяет свою собственную память, о которой Топ ничего не знает.

Если это не работает, я бы порекомендовал заняться чем-то вроде этого:

public static final class Top extends Struct {              
    public Second second = new Second(getRuntime(), this);                           
    public Top(final Runtime runtime) {
        super(runtime);
    }
}

Когда структуры назначены, как в строке

top.second = second;

в вашем коде Java, структура копируется из second в top.second, так что они становятся отдельными объектами в разных областях памяти. Позже, когда вы назначаете 7 a_number собственностью second в следующей строке:

second.a_number.set(7);

соответствующее свойство top.second остается неизменным, потому что они не один и тот же объект.

Чтобы получить те же результаты, что и ваш код на C, попробуйте изменить main метод к этому:

public static void main(final String[] args) {

    Test test = LibraryLoader.create(Test.class).load("test");
    Runtime runtime = Runtime.getRuntime(test);         
    Top top = new Top(runtime);
    top.second.a_number.set(8);         
    test.lets_go(top);
}

Инициализация нового Second объект не нужен, потому что память уже была выделена для top.second как часть инициализации Top объект.

Другие вопросы по тегам