Last active
January 11, 2024 01:17
-
-
Save TekuConcept/e92d10d6585c9d1fd3d507d1918cf77b to your computer and use it in GitHub Desktop.
SWIG Native C++ / NodeJS Callbacks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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