Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to deal with user-define-type array #493

Open
egolearner opened this issue Jun 3, 2021 · 10 comments
Open

How to deal with user-define-type array #493

egolearner opened this issue Jun 3, 2021 · 10 comments

Comments

@egolearner
Copy link
Contributor

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

@saudet
Copy link
Member

saudet commented Jun 3, 2021 via email

@egolearner
Copy link
Contributor Author

egolearner commented Jun 3, 2021

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)

@saudet
Copy link
Member

saudet commented Jun 3, 2021

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.

@egolearner
Copy link
Contributor Author

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

@saudet
Copy link
Member

saudet commented Jun 3, 2021

@egolearner
Copy link
Contributor Author

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?

@saudet
Copy link
Member

saudet commented Jun 4, 2021

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.

@egolearner
Copy link
Contributor Author

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?

@saudet
Copy link
Member

saudet commented Jun 4, 2021

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

saudet added a commit that referenced this issue Jun 30, 2021
@saudet
Copy link
Member

saudet commented Jun 30, 2021

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants