4

C++ calls the JS function, JsFunc(), passsing a C-function, MyCFunc(), as a parameter. JsFunc() calls MyCFunc() passsing a JS callback function as parameter.

How do I save in MyCFunc() the JS callback function parameter so that I can call it later from somewhere else in C++?

main.cpp

#include <duktape/src/duktape.h>
#include <cassert>

duk_ret_t MyCFunc(duk_context* ctx) {
    assert(duk_is_function(ctx, -1) );
    (void) duk_require_function(ctx, -1);

    // 1.- How to save the callback function parameter
    //     so that it can be used later on, say in main()?
    return 0; // nothing returned
}

int main() {
    duk_context* ctx = duk_create_heap_default();
    assert(ctx != nullptr);

    if (duk_peval_file(ctx, "../../src/jscallback_forum/test.js") != 0) {
        printf("Error: %s\n", duk_safe_to_string(ctx, -1));
        exit(1);
    }
    duk_pop(ctx);  /* ignore result */

    duk_push_global_object(ctx);
    duk_bool_t isSuccess = duk_get_prop_string(ctx, -1 , "JsFunc");
    assert(isSuccess != false);

    // pass  MyCFunc as parameter to JsFunc
    duk_push_c_function(ctx, &MyCFunc, 1); // MyCFunc expects Js callback

    if (duk_pcall(ctx, 1) != 0) { // JsFunc call failed
        printf("Error: %s\n", duk_safe_to_string(ctx, -1));
    }

    duk_pop(ctx);  /* pop duk_pcall result/error */
    duk_pop(ctx);  /* pop duk_push_global_object */

// 2. How do I retrieve the JS callback function 
    //    saved in MyCFunc() and run it?

    duk_destroy_heap(ctx);

    return 0;
}

test.js

function JsFunc(cfunc) {
    print("Entering testCFunc" );

    cfunc(function () {
        print("In lambda function()");
    });

    print("Exiting testCFunc");

}
Alex Net
  • 164
  • 12
  • https://wiki.duktape.org/howtonativepersistentreferences https://duktape.org/api.html#concepts.10 – Andrew Jun 18 '21 at 06:06

1 Answers1

4

There's no difference to a similar Ecmascript function in principle: the C function accepting a callback (MyCFunc in your example) needs to store the argument callback to a more persistent location before returning so that it can be looked up later.

There are several options for that storage location; an equivalent Ecmascript function would probably store a reference into the global object or some data structure held in the global object (such as a callbacks array). When using Duktape from C you can also use one of the "stash" objects provided by Duktape (see http://duktape.org/api.html#duk_push_global_stash) which are not visible to Ecmascript code.

As a concrete example, here's how to store the callback into the global object, assuming only one callback will be stored at a time:

duk_ret_t MyCFunc(duk_context *ctx) {
    /* Value stack index 0 has callback function. */

    /* Equivalent to Ecmascript code: globalObject._my_callback = arg; */
    duk_dup(ctx, 0);
    duk_put_global_string(ctx, "_my_callback");
    return 0;
}

Then later on when you want to call it:

duk_int_t rc;

/* ... */

duk_get_global_string(ctx, "_my_callback");
rc = duk_pcall(ctx, 0);  /* no arguments in this example */
if (rc != 0) {
    printf("Callback failed: %s\n", duk_safe_to_string(ctx, -1));
} else {
    printf("Callback success\n");
}
duk_pop(ctx);  /* pop result */
Sami Vaarala
  • 606
  • 4
  • 5
  • 1
    Thank you for your reply. Soon after I posted the question I found a solution that matches what you suggested, i.e. using duk_put_global_string() and duk_get_global_string(). However as you also pointed out, this solution is only applicable if only one callback is performed at a time. In my actual application, JsFunc() is invoked by many clients each on a different thread. I found that the best solution is to use thread stash (and not global stash) don't you agree? How would you use it? – Alex Net Mar 10 '16 at 23:05
  • There's no real difference in approach to what one would do in Ecmascript code (other than a few additional capabilities like stashes). A typical solution is to assign an identifier to each callback and maintain them in e.g. an array or an object. That handle, for example a numeric ID, is tracked so that when the callback needs to happen the ID is available for looking up the callback. – Sami Vaarala Mar 11 '16 at 09:32
  • Regarding which stash to use, just to be clear: the "thread stash" scope is a Duktape thread (also referred to as a "context"), not a native thread. If the callbacks indeed happen via different Duktape threads then the thread stash would be a workable solution. But a more general solution is to have some sort of callback ID which is tracked in the "pending callbacks" state so that it is available when the callback is needed. – Sami Vaarala Mar 11 '16 at 10:14
  • Thank you for your time in making things clearer. Yes, a thread stash is a context and not a native thread. However, somehow I do prefer the "thread stash" solution over the use global unique IDs for callbacks stored in one place (pressumably the more entries there are, the longer the sarch for an ID will take). By using a "thread stash" associated with the context and where the callback is stored, the ID does not have to be unique, the stash is small and therefore the search fast. I have already tried this and seems to work. I wish that there was C++ class wrappers for the duktape API!! – Alex Net Mar 11 '16 at 19:14
  • Why do you call `duk_dup()`? What's the point? – Andrew Jun 18 '21 at 06:07
  • Also can you explain what `duk_push_heap_stash()` is actually **doing**? I can't find the answer to this exact question documented anywhere. I can't use it properly unless I know what it does. – Andrew Jun 18 '21 at 06:19
  • Ohhhhhhhh okay, I didn't read the docs literally enough: "A stash is an **object**..." So you use it to create/recall a specially scoped object which goes onto the stack that you can interact with via. e.g. `duk_put/get_prop_string()`. I think combining this with `duk_get_heapptr()` and stashing that heapptr into the stash via. `duk_put_prop_heapptr()` is what I'm looking for. Permanent storage, quick recall for c++. – Andrew Jun 18 '21 at 07:06