bytedeco / javacpp-presets

The missing Java distribution of native C++ libraries
Other
2.65k stars 736 forks source link

What to do when an API depends on a large sub-API? #520

Open beligum opened 6 years ago

beligum commented 6 years ago

Hi,

I'm confronted with a library (the Linux API for The Imaging Source cameras) that depends on GStreamer (that in turn depends on GLib). When writing a preset for it, the API complains it can't find the GstElement class, which is part of the GStreamer framework.

What's the appropriate way to deal with these situations? Should I dig deep and create a preset for the entire GStreamer (and Glib?) API first, or do I have other options?

best,

b.

beligum commented 6 years ago

Is there a way to keep it after successful compilation?

saudet commented 6 years ago

Actually, something like mvn -Djavacpp.deleteJniFiles=false ...

beligum commented 6 years ago

This is where the vector of buffers is passed to the driver (after which the buffers sometimes already get GC):

JNIEXPORT jboolean JNICALL Java_org_bytedeco_javacpp_TISCamera_00024DeviceInterface_initialize_1buffers_1ptr(JNIEnv* env, jobject obj, jobject arg0) {
    ::tcam::DeviceInterface* ptr = (::tcam::DeviceInterface*)jlong_to_ptr(env->GetLongField(obj, JavaCPP_addressFID));
    if (ptr == NULL) {
        env->ThrowNew(JavaCPP_getClass(env, 6), "This pointer address is NULL.");
        return 0;
    }
    jlong position = env->GetLongField(obj, JavaCPP_positionFID);
    ptr += position;
    ::std::vector<tcam::MemoryBuffer*>* ptr0 = arg0 == NULL ? NULL : (::std::vector<tcam::MemoryBuffer*>*)jlong_to_ptr(env->GetLongField(arg0, JavaCPP_addressFID));
    if (ptr0 == NULL) {
        env->ThrowNew(JavaCPP_getClass(env, 6), "Pointer address of argument 0 is NULL.");
        return 0;
    }
    jlong position0 = arg0 == NULL ? 0 : env->GetLongField(arg0, JavaCPP_positionFID);
    ptr0 += position0;
    jboolean rarg = 0;
    jthrowable exc = NULL;
    try {
        bool rvalue = (bool)ptr->initialize_buffers_ptr(*ptr0);
        rarg = (jboolean)rvalue;
    } catch (...) {
        exc = JavaCPP_handleException(env, 8);
    }

    if (exc != NULL) {
        env->Throw(exc);
    }
    return rarg;
}

this is the callback function:

void JavaCPP_org_bytedeco_javacpp_TISCamera_00024sink_1callback::operator()(tcam::MemoryBuffer* arg0, void* arg1) {
    jthrowable exc = NULL;
    JNIEnv* env;
    bool attached = JavaCPP_getEnv(&env);
    if (env == NULL) {
        goto end;
    }
{
    jvalue args[2];
    jobject obj0 = NULL;
    ::tcam::MemoryBuffer* ptr0 = NULL;
    ptr0 = (::tcam::MemoryBuffer*)arg0;
    if (ptr0 != NULL) { 
        obj0 = JavaCPP_createPointer(env, 20);
    }
    if (obj0 != NULL) { 
        env->SetLongField(obj0, JavaCPP_addressFID, ptr_to_jlong(ptr0));
    }
    args[0].l = obj0;
    jobject obj1 = NULL;
    void* ptr1 = NULL;
    ptr1 = (void*)arg1;
    if (ptr1 != NULL) { 
        obj1 = JavaCPP_createPointer(env, 0);
    }
    if (obj1 != NULL) { 
        env->SetLongField(obj1, JavaCPP_addressFID, ptr_to_jlong(ptr1));
    }
    args[1].l = obj1;
    if (obj == NULL) {
        obj = JavaCPP_createPointer(env, 42);
        obj = obj == NULL ? NULL : env->NewGlobalRef(obj);
        if (obj == NULL) {
            JavaCPP_log("Error creating global reference of org.bytedeco.javacpp.TISCamera.sink_callback instance for callback.");
        } else {
            env->SetLongField(obj, JavaCPP_addressFID, ptr_to_jlong(this));
        }
        ptr = &JavaCPP_org_bytedeco_javacpp_TISCamera_00024sink_1callback_allocate_callback;
    }
    if (mid == NULL) {
        mid = JavaCPP_getMethodID(env, 42, "call", "(Lorg/bytedeco/javacpp/TISCamera$MemoryBuffer;Lorg/bytedeco/javacpp/Pointer;)V");
    }
    if (env->IsSameObject(obj, NULL)) {
        JavaCPP_log("Function pointer object is NULL in callback for org.bytedeco.javacpp.TISCamera.sink_callback.");
    } else if (mid == NULL) {
        JavaCPP_log("Error getting method ID of function caller \"public native void org.bytedeco.javacpp.TISCamera$sink_callback.call(org.bytedeco.javacpp.TISCamera$MemoryBuffer,org.bytedeco.javacpp.Pointer)\" for callback.");
    } else {
        env->CallVoidMethodA(obj, mid, args);
        if ((exc = env->ExceptionOccurred()) != NULL) {
            env->ExceptionClear();
        }
    }
    env->DeleteLocalRef(obj0);
    env->DeleteLocalRef(obj1);
}
end:
    if (exc != NULL) {
        jstring str = (jstring)env->CallObjectMethod(exc, JavaCPP_toStringMID);
        env->DeleteLocalRef(exc);
        const char *msg = JavaCPP_getStringBytes(env, str);
        JavaCPP_exception e(msg);
        JavaCPP_releaseStringBytes(env, str, msg);
        env->DeleteLocalRef(str);
        JavaCPP_detach(attached);
        throw e;
    } else {
        JavaCPP_detach(attached);
    }
}
saudet commented 6 years ago

Yup, the pointer is just passed along, as an integer, and that's it.

beligum commented 6 years ago

So where does the deallocation come from then?

saudet commented 6 years ago

Like I said, if anything is happening along those lines, it's in your code in Java. But if it crashes even with an empty callback, then the problem is somewhere else.

beligum commented 6 years ago

It crashes because the created frame buffers get garbage collected... Anyway, if I keep them around in a simple java list, they are kept from GC and everything is stable, so I guess that's a good solution.

saudet commented 6 years ago

It sounds more like OpenCV or something is incorrectly freeing the memory. For example, it's probably not a good idea to change the data field of a Mat after it has been allocated.

beligum commented 6 years ago

I've found that the reason the data buffers get freed out of my control is that they are grouped in a (mapped) vector structure. When you just keep a reference to the vector, it's not enough to keep the GC from freeing the content of the vector. However, if you keep a parallel vector in Java, all works well. It's weird behavior, because it's counterintuitive.

saudet commented 6 years ago

Yeah, that's a bit unpolished, but I'm not sure what to do about it... In any case, usually we use a native data structure only when it's required for native calls. In your case, that doesn't sound like that's what you need, and we can just use a Java List or something so that all works as expected.