WebIDL callback from C++ to JS

216 views
Skip to first unread message

Kevin O'Connor

unread,
Jul 18, 2018, 6:24:19 PM7/18/18
to emscripten-discuss
Hi all,

I'm having some challenges getting callbacks to JS from C++ using WebIDL where the callback interface is declared in C++.  

I'm using emscripten 1.38.6.  

The challenge seems to be that the pointer to the JS class has no recollection that it is of the JS class and the virtual C++ implementation is called.

I have tried to follow the format in Box2d, but with little luck.

The WebIDL for the test is simple:
interface CallbackListener {

};

[JSImplementation="CallbackListener"]
interface JSCallbackListener {

void JSCallbackListener();
void sessionConnectResult(long nResult, DOMString strSessionId);

};

interface Client {

void Client([Ref, Const] DOMString strName);

void startSession();
    
void registerCallbackListener(JSCallbackListener listener);

void unregisterCallbackListener(JSCallbackListener listener);

DOMString strName();
};


Here is the CallbackListener.hpp
#pragma once

#include <cstdint>
#include <experimental/optional>
#include <string>

#include <emscripten.h>

#define NM_NOT_USED(x) ((void)(x))

class CallbackListener {
  public:
    virtual ~CallbackListener() {}

    virtual void sessionConnectResult(int32_t nResult, const std::experimental::optional<std::string> & strSessionId) {
   EM_ASM({
      console.log('in C++ sessionConnectResult with ' + $0 + ' and ' + UTF8ToString($1) + '!');
   }, nResult, strSessionId->c_str());
    NM_NOT_USED(nResult);
    NM_NOT_USED(strSessionId);
    };

};

The EM_ASM is just for debugging.

In the Client class in C++ the registerCallbackListener takes a CallbackListener * and saves it in an ivar.  When startSession is called, it calls a protected notifySessionConnectResult method. In that method, if the ivar is set, sessionConnectResult is invoked.  Here is the definition of that method:
    void Client::notifySessionConnectResult(int32_t nResult, const std::experimental::optional<std::string> & strSessionId) {
    if (m_listener) {
    EM_ASM({
        console.log(' about to call sessionConnectResult on ' + $0);
        var l = wrapPointer($0, JSCallbackListener);
        l.sessionConnectResult($1, UTF8ToString($2));
        console.log(' after call sessionConnectResult on ' + $0);
  }, m_listener, nResult, strSessionId ? strSessionId->c_str() : ""); 
  m_listener->sessionConnectResult(nResult, strSessionId);
}
    }


The line l.sessionConnectResult(...) inside the EM_ASM block does invoke the JSCallbackListener instance's implementation of sessionConnectResult, but the m_listener->sessionConnectResult(...) invokes the CallbackListener method.  I expected that call to also invoke the JSCallbackListener implementation.  Could someone point me to something I have missed? 
Perhaps something needs to be added to the wrapper?

I have a zip of the project with an example index.html file to demonstrate, but I seem to be having trouble attaching files, so here is a dropbox link:

Any idea would be appreciated.  If my expectations are incorrect that the m_listener->sessionConnectResult(...) will never invoke the JSCallbackListener method, please let me know. If I've simply missed something in the wrapper or WebIDL that would fix it that would be excellent.

Thanks
Kevin

Kevin O'Connor

unread,
Jul 23, 2018, 2:04:17 PM7/23/18
to emscripten-discuss
So, the issue was expecting a bit too much magic.  
The complex const std::experimental::optional<std::string> & strSessionId would not map to the  char *arg1 of the JSCallbackListener, so I had to add a method to the glue c++ definition of JSCallbackListener that would receive the call from CallbackListener::sessionConnectResult(int33_t nResult, const std::experimental::optional<std::string> & strSessionId) and then call the JSCallbackListener::sessionConnectResult(int arg0, char* arg1).  If the callback had had no parameters or simple parameters, it just would have worked.

Here is the method added to the glue c++ (TestSDK.cpp in the zip) as a public method of JSCallbackListener.
void sessionConnectResult(int32_t nResult, const std::experimental::optional<std::string> & strSessionId) {
const char *b1 = strSessionId ? ::string2char(*strSessionId) : "";
sessionConnectResult(nResult, b1);
if (strSessionId) delete[] b1;
};


Kevin
Reply all
Reply to author
Forward
0 new messages