javacpp icon indicating copy to clipboard operation
javacpp copied to clipboard

How to deal with user-define-type array

Open egolearner opened this issue 3 years ago • 10 comments

Here is a demo

#include <iostream>
class MyType {
    public:
        MyType() : a(0) {}
        MyType(int i) : a(i) {}
        int a;
};

class MyTypeUser {
    public:
        void use(MyType* a, int count) {
            for (int i = 0; i < count; i++) {
                std::cout<<a[i].a<<std::endl;
            }
        }
};

MyTypeUser::use expects an array of MyType. The generated code is only able to hold one MyType actually.

generated MyTypeUser.use definition

public native void use(MyType a, int count);

generated MyType

public class MyType extends Pointer {
    static { Loader.load(); }
    /** Pointer cast constructor. Invokes {@link Pointer#Pointer(Pointer)}. */
    public MyType(Pointer p) { super(p); }

public MyType() { super((Pointer)null); allocate(); }
private native void allocate();
public MyType(int i) { super((Pointer)null); allocate(i); }
private native void allocate(int i);
public native int a(); public native MyType a(int setter);
}

I tried to add the following mapping info following guide here(https://github.com/bytedeco/javacpp/wiki/Mapping-Recipes#specifying-names-to-use-in-java)

        infoMap.put(new Info("MyType").pointerTypes("MyTypePointer"));
        infoMap.put(new Info("MyTypePointer").valueTypes("MyTypePointer").pointerTypes("@Cast(\"MyType*\") PointerPointer", "@ByPtrPtr MyTypePointer").base("IntPointer"));

The generated code is almost the same.

        public native void use(MyTypePointer a, int count);
public class MyTypePointer extends Pointer {
    static { Loader.load(); }
    /** Pointer cast constructor. Invokes {@link Pointer#Pointer(Pointer)}. */
    public MyTypePointer(Pointer p) { super(p); }

        public MyTypePointer() { super((Pointer)null); allocate(); }
        private native void allocate();
        public MyTypePointer(int i) { super((Pointer)null); allocate(i); }
        private native void allocate(int i);
        public native int a(); public native MyTypePointer a(int setter);
}

Is it possible to define UDT pointer similarly to IntPointer, capable of holding one or more elements? @saudet

egolearner avatar Jun 03 '21 07:06 egolearner

All subclasses of Pointer can be used for native arrays, if that's what you mean?

saudet avatar Jun 03 '21 07:06 saudet

All subclasses of Pointer can be used for native arrays, if that's what you mean?

Yes we are on the same page. But I have problem creating array from java side. Could you provide any guide? Is this the right way?

public class Test {
    public static void main(String[] args) {
        System.out.println(new MyTypePointer().sizeof());
        Pointer p = Pointer.malloc(Pointer.sizeof(MyTypePointer.class) * 4);
        for (int i = 0; i < 4; i++) {
            MyTypePointer myp = new MyTypePointer(p.getPointer(i));
            myp.a(i);
        }
        MyTypeUser u = new MyTypeUser();
        u.use(new MyTypePointer(p), 4);
    }
}

Besides, run the following throws exception. Is this expected behaviour?

public class Test {
    public static void main(String[] args) {
        System.out.println(Pointer.sizeof(MyTypePointer.class));
        System.out.println(new MyTypePointer().sizeof());
}
Exception in thread "main" java.lang.ClassCastException: class java.lang.Object
	at java.lang.Class.asSubclass(Class.java:3404)
	at org.bytedeco.javacpp.Loader.offsetof(Loader.java:1915)
	at org.bytedeco.javacpp.Loader.sizeof(Loader.java:1928)
	at org.bytedeco.javacpp.Pointer.sizeof(Pointer.java:814)

egolearner avatar Jun 03 '21 08:06 egolearner

We can usually call something like new MyTypePointer(long) to allocate an array, but since that type already has an int constructor, the parser is skipping over the array allocator, that's all. We can still declare native void allocateArray(long size); and call it however we want though.

saudet avatar Jun 03 '21 11:06 saudet

Is there any guide on how to declare allocateArray for parse generated class?

egolearner avatar Jun 03 '21 12:06 egolearner

It's custom Java code: https://github.com/bytedeco/javacpp/wiki/Mapping-Recipes#mapping-a-declaration-to-custom-code

saudet avatar Jun 03 '21 14:06 saudet

Hi @saudet , I get it to work with the following First use javaText

        infoMap.put(new Info("MyType").javaText("\n"
+ "        @NoOffset @Properties(inherit = com.mycompany.myproject2.presets.myproject.class) \n"
+ "        public class MyType extends Pointer { \n"
+ "            static { Loader.load(); } \n"
+ "            /** Pointer cast constructor. Invokes {@link Pointer#Pointer(Pointer)}. */ \n"
+ "            public MyType(Pointer p) { super(p); } \n"
+ "           public MyType(long size) { super((Pointer)null); allocateArray(size); }\n"
+ " \n"
+ "            public MyType() { super((Pointer)null); allocate(); } \n"
+ "            private native void allocate(); \n"
+ "            private native void allocateArray(long size); \n"
+ "            @Override public MyType position(long position) {\n"
+ "                return (MyType)super.position(position);\n"
+ "            }\n"
+ "            public MyType(int i) { super((Pointer)null); allocate(i); } \n"
+ "            private native void allocate(int i); \n"
+ "            public native int a(); public native com.mycompany.myproject2.MyType a(int setter); \n"
+ "            public native double padding(int i); public native com.mycompany.myproject2.MyType padding(int i, double setter); \n"
+ "            @MemberGetter public native DoublePointer padding(); \n"
+ "        } \n"
        ));

Test code as below

    public static void main(String[] args) {
        MyType mt = new MyType((long) 4);
        for (long i = 0; i < 4; i++) {
            MyType each = mt.position(i);
            each.a((int) i);
        }
        MyTypeUser u = new MyTypeUser();
        mt.position(0);
        u.use(mt, 4);
    }

Let me known if there is anything wrong or there is any better way, especially regarding with the following

  1. After traverse the array, I have to rewind it with mt.position(0);. Is there a better way?
  2. Is it necessary to provide all the definition of MyType? Is it possible to add or overwrite one or more functions on demand?

egolearner avatar Jun 04 '21 03:06 egolearner

We can use Pointer.getPointer() for something easier to use than position(), and of course we can provide Info.javaText only for some methods, including constructors.

saudet avatar Jun 04 '21 04:06 saudet

As we all known, no Info.javaText will not generate MyType(long) constructor. However adding the following will generate compiling error

        infoMap.put(new Info("MyType::MyType").javaText("public MyType(long size) { allocateArray(size); }"));

Error message

constructor MyType(long) is already defined in class com.mycompany.myproject2.MyType

Generated class is below. MyType(long size) is defined twice.

public class MyType extends Pointer {
    static { Loader.load(); }
    /** Pointer cast constructor. Invokes {@link Pointer#Pointer(Pointer)}. */
    public MyType(Pointer p) { super(p); }
    /** Native array allocator. Access with {@link Pointer#position(long)}. */
    public MyType(long size) { super((Pointer)null); allocateArray(size); }
    private native void allocateArray(long size);
    @Override public MyType position(long position) {
        return (MyType)super.position(position);
    }
    @Override public MyType getPointer(long i) {
        return new MyType((Pointer)this).offsetAddress(i);
    }

        public MyType(long size) { allocateArray(size); }
        public native int a(); public native MyType a(int setter);
        public native double padding(int i); public native MyType padding(int i, double setter);
        @MemberGetter public native DoublePointer padding();
}

Then I tried the following

        infoMap.put(new Info("MyType::MyType").javaText("public MyType(long size) { allocateArray(size); }").skip());

It worked. Is this the expected trick?

egolearner avatar Jun 04 '21 06:06 egolearner

That's a bit weird, it should not output the default code when supplied with javaText, yes.

saudet avatar Jun 04 '21 07:06 saudet

Actually, you're going to get the same thing just by setting Info.skip. What we put in Info.javaText doesn't get used when Info.skip is set. What is happening is that it also doesn't get picked up by the parser to figure out what's missing, so it just outputs the defaults. I've added a check for Info.skipDefaults to allow us to skip those manually when we need it, so something like this works now:

.put(new Info("MyType").skipDefaults())
.put(new Info("MyType::MyType").javaText("public MyType(long size) { allocateArray(size); }\n" 
                                       + "private native void allocateArray(long size);"))

saudet avatar Jun 30 '21 08:06 saudet