bytedeco / javacpp

The missing bridge between Java and native C++
Other
4.47k stars 582 forks source link

Passing data between languages with no leaks #351

Open Vasniktel opened 4 years ago

Vasniktel commented 4 years ago

Hi everyone. The issue is the following. I have to transfer ownership of an allocated data (an array) from C++ to Java. How would I do that?

As a side note: I've tried to work something out with @ArrayAllocator annotation but haven't quite understood the idea of how it works. I got a couple of NullPointerExceptions during my experiments that look like this:

javac -cp javacpp.jar:/home/vasniktel/javacpp-test test/Lib.java 
Generating /home/vasniktel/javacpp-test/jnijavacpp.cpp
Generating /home/vasniktel/javacpp-test/test/jniLib.cpp
Warning: Method "public static native void test.Lib.allocateArray(int)" cannot behave like a "ArrayAllocator". No code will be generated.
Exception in thread "main" java.lang.NullPointerException
        at org.bytedeco.javacpp.tools.Generator.methods(Generator.java:1748)
        at org.bytedeco.javacpp.tools.Generator.classes(Generator.java:1645)
        at org.bytedeco.javacpp.tools.Generator.generate(Generator.java:206)
        at org.bytedeco.javacpp.tools.Builder.generateAndCompile(Builder.java:604)
        at org.bytedeco.javacpp.tools.Builder.build(Builder.java:1107)
        at org.bytedeco.javacpp.tools.Builder.main(Builder.java:1341)

The question is how those annotations work and whether this exception is an intended behaviour?

saudet commented 4 years ago

You'll need to provide more information about the API you're trying to map if you'd like specific help about that.

Vasniktel commented 4 years ago

I'm just experimenting with the library right now. I have the following class on the Java side.

package test;

import org.bytedeco.javacpp.*;
import org.bytedeco.javacpp.annotation.*;

@Platform(include="../test.hpp")
@Namespace("test")
public class Lib {
  static { Loader.load(); }

  // Should return a pointer to the allocated array
  // IntPointer should take ownership of the returned data
  public static native IntPointer bar();

  public static void main(String[] args) {
    bar();
  }
}

Which uses this C++ function

namespace test {
  int* bar() {
    return new int[4]{1, 2, 3, 4};
  }
}

As indicated in the comments, IntPointer should take ownership of the array, returned from bar function.

As for NullPointerException, I'm guessing that method should be non-static to use @ArrayAllocator or @Allocator.

saudet commented 4 years ago

That's just a raw pointer, with no clue as to how to deallocate it. You can't expect any framework to be able to do anything about it automatically. First, you'll need to provide some function to deallocate it, something like this:

namespace test {
  int* bar() {
    return new int[4]{1, 2, 3, 4};
  }
  void foo(int* bar) {
    delete[] bar;
  }
}

Then we can use that function...

Vasniktel commented 4 years ago

Thanks for the reply!

First, you'll need to provide some function to deallocate it

That is clear. The question that I'm struggling to answer, is how to make IntPointer result aware of it. Is there any way to do it?

saudet commented 4 years ago

What you're looking for is probably a custom deallocator. The typical pattern looks like this: https://github.com/bytedeco/javacpp/wiki/Mapping-Recipes#writing-additional-code-in-a-helper-class Applying that to this case, we could have something like this:

    public static native MyIntPointer bar();
    public static native void foo(MyIntPointer bar);

    public class MyIntPointer extends IntPointer {
        protected static class MyDeallocator extends MyIntPointer implements Deallocator {
            MyDeallocator(MyIntPointer p) { super(p); }
            @Override public void deallocate() { foo(this); }
        }
        public MyIntPointer(Pointer p) { super(p); }

        public static MyIntPointer create() {
            MyIntPointer p = bar();
            if (p != null) {
                p.deallocator(new MyDeallocator(p));
            }
            return p;
        }
    }
saudet commented 4 years ago

Obviously this could be enhanced somehow using annotations...

Vasniktel commented 4 years ago

Hey, @saudet, thanks for the reply. This solves the problem! I have another question though. I've noticed that javacpp calls javac during execution even though I need to only generate .cpp files in my application. Is there any way to disable it?

saudet commented 4 years ago

Sure, there's a -nocompile command line option and a compile flag for Maven we can set to false: http://bytedeco.org/javacpp/apidocs/org/bytedeco/javacpp/tools/BuildMojo.html#compile

Vasniktel commented 4 years ago

-nocompile only disables compilation of .cpp files for me. I wanted to disable compilation of .java files as well. I just want javacpp to generate c++ sources. Is this possible?

saudet commented 4 years ago

No, JavaCPP needs some Java code to work. Your question doesn't make sense.

Vasniktel commented 4 years ago

Yep, my mistake, sorry for the confusion :)

saudet commented 4 years ago

If you're talking about what happens when you give it a .java file, that's just for convenience. Don't give it .java files, just give it some class names, and it will use those.