SWIG Native C++ / NodeJS Callbacks
// SWIG's %native directive is now supported for JavaScript since SWIG release (4.0.1+) | |
// This allows us to not only wrap our C/C++ code with ease, but also add our own | |
// specialized wrapper functionality to our projects - such as callbacks! | |
// | |
// Below are three callback implementations shared for convinience (for Node/V8 engines) | |
// 1. one-off, inline callback | |
// 2. one-off, async callback | |
// 3. persistent async callback | |
%module "native_callbacks" | |
%native(funcA) void _wrap_funcA(); | |
%native(funcB) void _wrap_funcB(); | |
%native(funcC) void _wrap_funcC(); | |
%header %{ | |
#include <array> | |
#include <vector> | |
#include <memory> | |
#include <uv.h> | |
// these are used to help demonstrate (3) | |
#include <future> | |
std::future<void> thread; | |
%} | |
%wrapper %{ | |
// base class, which provides a way to access | |
// different data types in one vector (Pre-C++17) | |
class arg_t { | |
public: | |
arg_t() : data(NULL) {} | |
virtual ~arg_t() = default; | |
bool valid() const { return data != NULL; } | |
template <typename T> | |
T& get() const { return *(T*)(data); } | |
protected: | |
void* data; | |
}; | |
// templated parent class to hold the actual data | |
template <typename T> | |
class type_arg : public arg_t { | |
public: | |
type_arg(T t) : value(t) { arg_t::data = &value; } | |
private: | |
T value; | |
}; | |
// generic worker packet, which saves the context | |
// for almost all variations of async tasks | |
typedef struct worker_packet { | |
bool done; // teardown flag | |
uv_work_t request; // uv_queue_work | |
uv_async_t async; // uv_async_init | |
uv_loop_t* loop; // (save this so it can be freed later) | |
uv_rwlock_t lock; | |
// v8::Persistent<v8::Function> do not play nice with std::vector | |
std::array<v8::Persistent<v8::Function>,3> callbacks; | |
std::vector<std::shared_ptr<arg_t>> args; | |
std::shared_ptr<arg_t> return_value; | |
std::shared_ptr<arg_t> parent; // hold the class object if any | |
} worker_packet; | |
// convinience function for converting args to persistent functions | |
int SWIG_AsVal_function( | |
v8::Handle<v8::Value> valRef, | |
v8::Persistent<v8::Function>* val) | |
{ | |
if (!valRef->IsFunction()) return SWIG_TypeError; | |
if (val) { | |
auto iso = v8::Isolate::GetCurrent(); | |
v8::Local<v8::Function> callback = v8::Local<v8::Function>::Cast(valRef); | |
val->Reset(iso, callback); | |
} | |
return SWIG_OK; | |
} | |
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *\ | |
One-Off, Inline Callback | |
\* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | |
static SwigV8ReturnValue | |
_wrap_funcA(const SwigV8Arguments &args) | |
{ | |
SWIGV8_HANDLESCOPE(); | |
auto iso = v8::Isolate::GetCurrent(); | |
if (args.Length() != 1) SWIG_exception_fail(SWIG_ERROR, | |
"Illegal number of arguments for _wrap_funcA."); | |
if (!args[0]->IsFunction()) SWIG_exception_fail(SWIG_ERROR, | |
"Invalid argument; expecting function for _wrap_funcA."); | |
{ | |
v8::Local<v8::Function> callback = v8::Local<v8::Function>::Cast(args[0]); | |
const int argc = 1; | |
v8::Handle<v8::Value> argv[] = { SWIGV8_STRING_NEW("hello world") }; | |
callback->Call(iso->GetCurrentContext()->Global(), argc, argv); | |
} | |
fail: | |
SWIGV8_RETURN(SWIGV8_UNDEFINED()); | |
} | |
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *\ | |
One-Off, Async Callback | |
\* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | |
static void | |
_cb_funcB_async(uv_work_t* req) | |
{ | |
worker_packet* worker = static_cast<worker_packet*>(req->data); | |
worker->parent->get<FooClass*>()->bar(worker->args[0]->get<std::string>()); | |
} | |
static void | |
_cb_funcB_complete(uv_work_t *req, int status) | |
{ | |
SWIGV8_HANDLESCOPE(); | |
v8::Isolate* iso = v8::Isolate::GetCurrent(); | |
worker_packet* worker = static_cast<worker_packet*>(req->data); | |
int argc = 1; | |
v8::Handle<v8::Value> argv[] = | |
{ SWIGV8_BOOLEAN_NEW(work->return_value->get<bool>()) }; | |
v8::Local<v8::Function>::New(iso, worker->callbacks[0]) | |
->Call(iso->GetCurrentContext()->Global(), argc, argv); | |
worker->callback.Reset(); | |
worker->request.data = NULL; | |
uv_loop_close(worker->loop); | |
delete work; | |
} | |
static SwigV8ReturnValue | |
_wrap_funcB(const SwigV8Arguments &args) | |
{ | |
SWIGV8_HANDLESCOPE(); | |
WorkerPacket* worker = new WorkerPacket(); | |
int success = 0 ; | |
if (args.Length() != 2) SWIG_exception_fail(SWIG_ERROR, | |
"Illegal number of arguments for _wrap_funcB."); | |
success = SWIG_ConvertPtr(args[0], &argp1, SWIGTYPE_p_FooClass, 0 | 0 ); | |
if (!SWIG_IsOK(success)) | |
SWIG_exception_fail(SWIG_ArgError(success), | |
"in method '_wrap_funcB', argument 1 of type 'FooClass*'"); | |
worker->parent = std::make_shared<type_arg< FooClass* >>( | |
reinterpret_cast< FooClass* >(argp1) | |
success = SWIG_AsVal_function(args[1], &worker->callbacks[0]); | |
if (!SWIG_IsOK(success)) | |
SWIG_exception_fail(SWIG_ArgError(success), | |
"in method '_wrap_funcB', argument 2 of type 'function'"); | |
worker->args.push_back( | |
std::make_shared<type_arg<std::string>>("hello world")); | |
worker->return_value = std::make_shared<type_arg<bool>>(false); | |
worker->request.data = worker; | |
worker->loop = uv_default_loop(); | |
uv_queue_work( | |
worker->loop, &worker->request, | |
_cb_funcB_async, _cb_funcB_complete); | |
fail: | |
SWIGV8_RETURN(SWIGV8_UNDEFINED()); | |
} | |
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *\ | |
Persistent Async Callback | |
\* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | |
void | |
recuring_work(worker_packet* packet) | |
{ | |
thread = std::async(std::launch::async, [](worker_packet* worker) { | |
for (int i = 0; i < 3; i++) { | |
uv_rwlock_wrlock(&worker->lock); | |
worker->args.push_back(std::make_shared<type_arg<std::string>>(std::to_string(i))); | |
uv_rwlock_wrunlock(&worker->lock); | |
uv_async_send(&worker->async); | |
std::cout << "native " << i << "\n"; | |
std::this_thread::sleep_for(std::chrono::seconds(1)); | |
} | |
uv_rwlock_wrlock(&worker->lock); | |
worker->done = true; | |
uv_rwlock_wrunlock(&worker->lock); | |
uv_async_send(&worker->async); | |
std::cout << "native done\n"; | |
}, packet); | |
} | |
void | |
_cp_funcC_async(uv_async_t* async) | |
{ | |
SWIGV8_HANDLESCOPE(); | |
v8::Isolate* iso = v8::Isolate::GetCurrent(); | |
worker_packet* worker = static_cast<worker_packet*>(async->data); | |
uv_rwlock_wrlock(&worker->lock); | |
auto args = worker->args; | |
bool done = worker->done; | |
worker->args.clear(); | |
uv_rwlock_wrunlock(&worker->lock); | |
for (auto& arg : args) { | |
const int argc = 1; | |
v8::Handle<v8::Value> argv[] = | |
{ SWIGV8_STRING_NEW(arg->get<std::string>().c_str()) }; | |
v8::Local<v8::Function>::New(iso, work->callbacks[0]) | |
->Call(iso->GetCurrentContext()->Global(), argc, argv); | |
} | |
if (done) { | |
uv_close(reinterpret_cast<uv_handle_t*>(&worker->async), nullptr); | |
uv_loop_close(worker->loop); | |
uv_rwlock_destroy(&worker->lock); | |
for (auto& callback : worker->callbacks) | |
callback.Reset(); | |
delete worker; | |
} | |
} | |
static SwigV8ReturnValue | |
_wrap_funcC(const SwigV8Arguments &args) | |
{ | |
SWIGV8_HANDLESCOPE(); | |
worker_packet* worker = new worker_packet(); | |
int success = 0 ; | |
int status = 0 ; | |
if(args.Length() != 1) SWIG_exception_fail(SWIG_ERROR, | |
"Illegal number of arguments for _wrap_funcC."); | |
{ | |
success = SWIG_AsVal_function(args[0], &worker->callbacks[0]); | |
if (!SWIG_IsOK(success)) | |
SWIG_exception_fail(SWIG_ArgError(success), | |
"in method '_wrap_funcC', argument 1 of type 'function'"); | |
} | |
worker->async.data = worker; | |
worker->loop = uv_default_loop(); | |
worker->done = false; | |
uv_rwlock_init(&worker->lock); | |
uv_async_init(worker->loop, &worker->async, _cb_funcC_async); | |
recuring_work(worker); | |
fail: | |
SWIGV8_RETURN(SWIGV8_UNDEFINED()); | |
} | |
%} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment