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

Problem with std::string_view as callback parameter #606

Open
ValentinaBaranova opened this issue Sep 19, 2022 · 26 comments
Open

Problem with std::string_view as callback parameter #606

ValentinaBaranova opened this issue Sep 19, 2022 · 26 comments

Comments

@ValentinaBaranova
Copy link

I have an error during creation jni lib by javacpp in case I have function with callback with std::string_view parameter

greeter-jni.cpp:1858:9: error: no matching function for call to 'testStringView'
        testStringView(*ptr0);

In my case I created several c++ functions in my lib:

void testStringView(std::function<void(std::string_view message)> callback) {
    callback("hello");
}

void testBoolean(std::function<void(bool success)> callback) {
    callback(true);
}

void plainStringView(std::string_view message) {
std::cout << message;
}

Also I created java helper class for javacpp:

public class GreeterHelper extends GreeterConfig {

    public static class BooleanCallback extends FunctionPointer {
        static { Loader.load(); }
        protected BooleanCallback() { allocate(); }
        private native void allocate();

        public native void call(boolean success);
    }

    public static class StringCallback extends FunctionPointer {
        static { Loader.load(); }
        protected StringCallback() { allocate(); }
        private native void allocate();

        public native void call(@StdString String error_message);
    }
}

And config:

@Properties(
        value = @Platform(
                compiler = "cpp11",
                define = {"SHARED_PTR_NAMESPACE std", "UNIQUE_PTR_NAMESPACE std"},
                includepath = {"../greeter/include/acvp/"},
                include = {"make_cloud_agent.h"},
                library = "greeter-jni",
                link = {"acvp-cloud-agent-cpp", "acvp-core"},
                linkpath = {
                        "../greeter/build/",
                        "../greeter/build/module/acvp-core/"
                }
        ),
        target = "arrival.Greeter",
        helper = "arrival.GreeterHelper"
)
public class GreeterConfig implements InfoMapper {
    public void map(InfoMap infoMap) {
        infoMap.put(new Info("std::function<void(boolsuccess)>").pointerTypes("BooleanCallback").define());
        infoMap.put(new Info("std::function<void(std::string_viewmessage)>").pointerTypes("StringCallback").define());
        infoMap.put(new Info("std::string_view").pointerTypes("@StdString String").define());
    }
}

Here is generated file:

public static native void testStringView(@ByVal StringCallback callback);

public static native void testBoolean(@ByVal BooleanCallback callback);

public static native void plainStringView(@ByVal @StdString String message);

public static native void plainBoolean(@Cast("bool") boolean success);

With these settings it works ok for plainStringView function (plain function which receives string_view) and testBoolean function (function which receives callback with bool parameter), but I have an error for testStringView - function which receives callback with string_view parameter. How it can be fixed?
Also this is strange but initial file which generates javacpp contains missed space symbol between callback parameter type and parameter name: std::function<void(boolsuccess)>.

@saudet
Copy link
Member

saudet commented Sep 20, 2022

Adapters don't always work for callback functions like this, and the problem here is that it's not possible to wrap an std::string_view in an std::string, so it's not possible to make your callback function correctly without operating on the std::string_view itself anyway. We need to provide a wrapper for that type. Obviously, we'd want that to be supported somehow by JavaCPP to general something automatically for us, but that's not currently the case. Contributions are welcome though!

Also this is strange but initial file which generates javacpp contains missed space symbol between callback parameter type and parameter name: std::function<void(boolsuccess)>.

Yes, things get really complicated down there with that kind of syntax. That obviously needs to be fixed.

@ValentinaBaranova
Copy link
Author

ValentinaBaranova commented Sep 20, 2022

Thank you for the answer. I changed string_view in callback in our c++ lib to const std::string& and it works (but c++ developers don't like it).
Workaround for problem with missing space is to use infoMap as I did, it works too.

@ValentinaBaranova
Copy link
Author

Maybe there is any workaround to not to change c++ code to fix string_view problem?

@saudet
Copy link
Member

saudet commented Sep 20, 2022

Yes, if you provide a wrapper for that type, and not an adapter or something that you @Cast, it will work.

@ValentinaBaranova
Copy link
Author

Could you give some example or link to explanation in documentation? I thought this is wrapper.

infoMap.put(new Info("std::string_view").pointerTypes("@StdString String").define());

Or I should not use annotation here?

@saudet
Copy link
Member

saudet commented Sep 20, 2022

Something like the StringArray class here:
https://github.com/bytedeco/javacpp-presets/blob/master/tensorflow/src/main/java/org/bytedeco/tensorflow/StringArray.java
But instead of std::string, you'd be mapping std::string_view.

@ValentinaBaranova
Copy link
Author

ValentinaBaranova commented Sep 28, 2022

Hello. I've added file StringArray near my file GreeterConfig:

@Platform(include="<string_view>")
@Name("std::string_view") public class StringArray extends Pointer {
...
}

And I try to check how does it works in function

public static native void plainStringView(@ByVal StringArray message);

jni lib was created successfully but I get an error "no jniStringArray in java.library.path. Could not find jniStringArray in class, module, and library paths."
How can I fix it?

@saudet
Copy link
Member

saudet commented Sep 28, 2022

@ValentinaBaranova
Copy link
Author

Now I have added "@properties(inherit = GreeterConfig.class)" but got "no jniGreeter in java.library.path"

I have 4 classes for now: GreeterConfig, which implements InfoMapper, StringArray, GreeterHelper which contains callback helper classes. And Greeter class, which generated automatically by javacpp.

Should I modify my GreeterConfig somehow like class tensorflow?

@saudet
Copy link
Member

saudet commented Sep 28, 2022

It just sounds like you skipped the build step. You'll need to build the JNI libraries before this can run.

@ValentinaBaranova
Copy link
Author

I have one project with mentioned java files for javacpp, it creates artefact which I use in other project with main method. Here is how I build my lib

cd greeter-javacpp/ && rm src/main/java/arrival/Greeter.java   && mvn clean compile && cd target/classes && java -jar ../../javacpp.jar arrival.GreeterConfig && cp arrival/Greeter.java ../../src/main/java/arrival ;
cd ../../ && mvn clean compile && cd target/classes && java -jar ../../javacpp.jar arrival.Greeter;  cd ../../ ;mvn package install

If I call plainBoolean function in my main function, it works with no error

@saudet
Copy link
Member

saudet commented Sep 28, 2022

It doesn't look from those commands that you're building for "StringArray"...

@ValentinaBaranova
Copy link
Author

Sorry but I don't get it. How to build StringArray?

@saudet
Copy link
Member

saudet commented Sep 28, 2022

From what I understand, you're not using the Maven plugin, so StringArray isn't in your pom.xml file and it's not anywhere on the command line either, and if it's not an inner class or something that JavaCPP can find through arrival.Greeter, it's not going to get built, is my guess. You'll need to give it to JavaCPP by name just like you're doing for arrival.Greeter

BTW, we can probably include that class as a static class inside GreeterConfig.java. That will make things easier.

@ValentinaBaranova
Copy link
Author

ValentinaBaranova commented Sep 28, 2022

I've added to my steps java -jar ../../javacpp.jar arrival.StringArray; and it partially works. When I call plainStringView(StringArray("test")) I see broken symbols image

Hmm, this way works:
StringArray(BytePointer("test"), 4)

@ValentinaBaranova
Copy link
Author

ValentinaBaranova commented Sep 28, 2022

My goal was to use StringArray in callback function. This is cpp function:

void testStringView(std::function<void(std::string_view message)> callback);

This is from my helper class

    public static class StringCallback extends FunctionPointer {

        static { Loader.load(); }
        protected StringCallback() { allocate(); }
        private native void allocate();

        public native void call(StringArray error_message);
    }

This is from mapper:

infoMap.put(new Info("std::function<void(std::string_viewmessage)>").pointerTypes("StringCallback").define());

It was converted to

public static native void testStringView(@ByVal StringCallback callback);

But I get an error

error: no matching function for call to 'testStringView'
       testStringView(*ptr0);
       ^~~~~~~~~~~~~~
make_cloud_agent.h:18:6: note: candidate function not viable: no known conversion from 'JavaCPP_arrival_GreeterHelper_00024StringCallback' to 'std::function<void (std::string_view)>' (aka 'function<void (basic_string_view<char>)>') for 1st argument
void testStringView(std::function<void(std::string_view message)> callback);

@saudet
Copy link
Member

saudet commented Sep 28, 2022

Hmm, this way works:
StringArray(BytePointer("test"), 4)

Yes, a string_view doesn't have storage, we have to provide it, that's normal.

public native void call(StringArray error_message);

You'll need to add a @ByVal there to match the signature.

@ValentinaBaranova
Copy link
Author

Should I create the same wrapper if I have callback which receives enum as parameter?
Here is cpp file:

enum class MyError {
    kOk,
    kServerError
};
void testCallback(std::function<void(MyError error)> callback) {
    callback(MyError.kOk);
}

Here is java callback helper:

    public static class MyErrorCallback extends FunctionPointer {
        // Loader.load() and allocate() are required only when explicitly creating an instance
        static { Loader.load(); }
        protected MyErrorCallback() { allocate(); }
        private native void allocate();

        public native void call(@Cast("MyError") Pointer error);
    }

Mapper:

infoMap.put(new Info().enumerate());
infoMap.put(new Info("std::function<void(MyErrorerror)>").pointerTypes("MyErrorCallback").define());

Generated function:

public enum MyError {
    kOk(0),
    kServerError(1);

    public final int value;
    private MyError(int v) { this.value = v; }
    private MyError(MyError e) { this.value = e.value; }
    public MyError intern() { for (MyError e : values()) if (e.value == value) return e; return this; }
    @Override public String toString() { return intern().name(); }
}

public static native void testCallback(@ByVal MyErrorCallback callback);

In result I get

error: C-style cast from 'char *' to 'MyError' is not allowed
        (*ptr->ptr)((MyError)ptr0);

@saudet
Copy link
Member

saudet commented Sep 30, 2022

You'll need to do

    public native void call(@ByVal @Cast("MyError*") Pointer error);

but this should also work:

    public native void call(MyError error);

@ValentinaBaranova
Copy link
Author

I would like to use

public native void call(MyError error);

But is is not clear for me. I've modified my project to make it works like javacpp preset. So I have 2 dirs: main and gen.
MyError class appears in gen dir after compilation. But this MyErrorCallback class with this function is in main folder. So I can't compile it before generation MyError class.

@saudet
Copy link
Member

saudet commented Oct 6, 2022

Right, that's what helper classes are for:
https://github.com/bytedeco/javacpp/wiki/Mapping-Recipes#writing-additional-code-in-a-helper-class
In the presets, they show up in a separate "helper" package.

@ValentinaBaranova
Copy link
Author

image

Yes, I have MyErrorCallback in TelemetrydHelper file in src/main/java. And MyError appears after generation in src/gen/java. But how can I use MyError class in TelemetrydHelper if other files are not generated yet?

@saudet
Copy link
Member

saudet commented Oct 8, 2022

We don't need to compile it at that stage since it doesn't contain any information useful for generating the files in src/gen/java.

@ValentinaBaranova
Copy link
Author

Finally I moved my TelemetrydHelper class from presets to helper package and it works. Thanks a lot!

@ValentinaBaranova
Copy link
Author

Just to clarify: the only problem for now is missed space symbol between callback parameter type and parameter name after generation: std::function<void(boolsuccess)>
It is not a blocker, but I don't sure should this issue be closed or not.

@saudet
Copy link
Member

saudet commented Oct 10, 2022

Well, we should fix this, yes. But in the end, we'll need to move to a full C++ compiler, at some point, see #51.

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