Skip to content

@ByPtrPtr array argument for const pointer needs to be copied back from native call results #776

@atsushieno

Description

@atsushieno

I have been trying to build a JavaCPP binding for libremidi, and had been failing to bind this function:

LIBREMIDI_EXPORT
int libremidi_midi_out_port_name(
    const libremidi_midi_out_port* port, const char** name, size_t* len);

The implementation for this function assigns the actual pointer to the string into name.

Now, when I tried to use the binding (I could manage to build it with some mappings) via byte[] (excuse my Kotlin code):

name = ByteArray(1024)
println("libremidi_midi_out_port_name returned " + library.libremidi_midi_out_port_name(port, name, size))

it failed to get the expected string. It did work when I made changes to the code to use BytePointer though:

val nameBuf = ByteBuffer.allocateDirect(1024)
val namePtr = BytePointer(nameBuf) // we should only need a space for pointer...
println(namePtr.address())
println("libremidi_midi_out_port_name returned " + library.libremidi_midi_out_port_name(port, namePtr, size))
println(namePtr.address())
println("name size: " + size.get())
name = ByteArray(size.get().toInt())
namePtr.asBuffer().get(name)

The thing is that the pointee string at const char** name can be modified, and when we pass byte[] that needs to be copied back to the binding parameter. It seems that JavaCPP works if the C function argument were char**.

The cause of the problem is at Generator.parametersAfter():

            // If const array, then use JNI_ABORT to avoid copying unmodified data back to JVM
            final String releaseArrayFlag;
            if (cast.contains(" const *") || cast.startsWith("(const ")) {
                releaseArrayFlag = "JNI_ABORT";
            } else {
                releaseArrayFlag = "0";
            }

The resulting generated code (jnilibremidi.cpp), especially for libremidi_midi_out_port_name() with byte[], looks like this.

This assumes that the const char ** argument must be used only as an input and not to alter the output. But that's not correct. It would be true only if it is const char * const *. This StackOverflow question describes it well, namely:

const char* the_string : I can change which char the_string points to, but I cannot modify the char to which it points.

const char* const the_string : I cannot change which char the_string points to, nor can I modify the char to which it points.

It is implemented for some optimization, but to ensure the implementation correctness the copying condition at parametersAfter() should rather be like:

            if (cast.contains("* const") || cast.endsWith("const)")) {
                releaseArrayFlag = "JNI_ABORT";
            } else {
                releaseArrayFlag = "0";
            }

I will create a PR based on this suggestion.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions