Skip to content

Instantly share code, notes, and snippets.

@earonesty
Last active June 22, 2021 18:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save earonesty/eb793917fed648cf5c2d5bfff3915a38 to your computer and use it in GitHub Desktop.
Save earonesty/eb793917fed648cf5c2d5bfff3915a38 to your computer and use it in GitHub Desktop.
Convert a djinni input file to a node cpp bindings file. Load the resulting lib directly in node.

USAGE:

python3 node_bindings.py djinni.yml <namespace> node/node.cpp
yarn
mkdir -p build-node
cd build-node; \
    cmake .. -DCMAKE_BUILD_TYPE=Release -DNODE=yes; \
    cmake --build . -j
node node/test.js
kata:
aes_256_gcm_encrypt: [key: string, iv: string, data: string, aad: string, _: string]
aes_256_gcm_decrypt: [key: string, data: string, aad: string, _: string]
aes_256_gcm_decrypt_file: [key: binary, data: string, _: string]
ec_pem_encode_public: [curve: string, pubkey: binary, _: string]
ec_pem_decode: [pem: string, _: binary]
ec_compress: [curve: string, pt: binary, _: binary]
ec_uncompress: [curve: string, pt: binary, _: binary]
ec_generate: [curve: string, _: binary]
ec_generate_pow: [bits: i32, curve: string, _: binary]
ec_generate_offline: [bits: i32, curve: string, _: binary]
ec_generate_derive_from_data: [curve: string, data: string, _: binary]
ec_restore_offline: [curve: string, data: binary, _: binary]
ec_verify_pow: [bits: i32, curve: string, pubk: binary, _: bool]
ec_pt_mul: [curve: string, point: binary , num: binary, output_compressed: bool, _: binary]
ec_pub_from_priv: [curve: string, privkey: binary, _: binary]
import sys
import yaml
import re
lib_ns = "::"
outf = None
def main():
global lib_ns
global outf
fin = sys.argv[1]
lib_ns = sys.argv[2]
out_path = sys.argv[3]
outf = open(out_path, "w")
idl = yaml.load(open(fin))
prev = None
for ns, funcs in idl.items():
if prev:
raise Exception("one ns at a time")
gen_ns(ns)
for func, args in funcs.items():
gen_func(func, args)
end_ns(ns, funcs.keys())
prev = ns
def output(s):
print(s, file=outf)
def gen_func(func, args):
output(f"NAN_METHOD({func}) {{")
index = 0
arg_names=[]
for arg in args:
name, type = list(arg.items())[0]
template_args = []
if "map" in type:
template_args = type["map"]
type = "map"
if "list" in type:
template_args = [type["list"]]
type = "list"
for i, arg in enumerate(template_args):
if arg == "string":
template_args[i] = "std::string"
if template_args:
template_args = ", " . join(template_args) + ", "
else:
template_args = ""
if (name != "_"):
output(f" NAN_ARG_{type}({template_args}{name}, {index});")
index += 1
arg_names.append(name)
else:
args = ", " . join(arg_names)
output(f" auto ret = {lib_ns}::{func}({args});")
output(f" NAN_RET_{type}({template_args}ret);");
output("}\n")
def end_ns(ns, funcs):
output("NAN_MODULE_INIT(Init) {")
for func in funcs:
output(f"NAN_EXPORT(target, {func});")
output("}")
output("NODE_MODULE(NODE_GYP_MODULE_NAME, Init);")
output("}")
def gen_ns(ns):
output(f"""
#include <nan.h>
#include <{ns}.hpp>
#include <vector>
#include <map>
void NAN_convert(std::vector<std::string> &out, Nan::TypedArrayContents<std::string> &in) {{
for (int i=0;i<in.length();++i) {{
out.push_back((*in)[i]);
}}
}}
template<typename T>
void NAN_tovec(std::vector<T> &out, v8::Local<v8::Value> in) {{
v8::Local<v8::Array> ary = in.As<v8::Array>();
for (int i=0;i<ary->Length();++i) {{
out.push_back(Nan::To<T>(Nan::Get(ary, i)).FromMaybe(T()));
}}
}}
template<>
void NAN_tovec(std::vector<std::string> &out, v8::Local<v8::Value> in) {{
v8::Local<v8::Array> ary = in.As<v8::Array>();
for (int i=0;i<ary->Length();++i) {{
out.push_back(*Nan::Utf8String(in));
}}
}}
template<typename FROM, typename TO>
v8::Local<v8::Object> NAN_map(const std::unordered_map<FROM, TO> &in) {{
v8::Local<v8::Object> obj = Nan::New<v8::Object>();
for (auto it=in.begin(); it!=in.end(); ++it) {{
Nan::Set(obj, Nan::New(it->first).ToLocalChecked(),Nan::New(it->second).ToLocalChecked());
}}
return obj;
}}
template<typename T>
v8::Local<v8::Object> NAN_list(const std::vector<T> &in) {{
v8::Local<v8::Array> obj = Nan::New<v8::Array>();
int i = 0;
for (auto it=in.begin(); it!=in.end(); ++it) {{
Nan::Set(obj, i, Nan::New(*it).ToLocalChecked());
i+=1;
}}
return obj;
}}
#define NAN_ARG_string(vname, index) Nan::Utf8String vname##_tmp(info[index]); const char *vname=*vname##_tmp;
#define NAN_ARG_binary(vname, index) Local<Object> vname##_tmp(info[index]->ToObject()); \
unsigned char *vname##_dat = (unsigned char *) node::Buffer::Data(vname##_tmp); \
std::vector<unsigned char> vname(vname##_dat, vname##_dat + node::Buffer::Length(vname##_tmp));
#define NAN_ARG_i32(vname, index) int vname = info[index]->Uint32Value();
// #define NAN_ARG_list(type, vname, index) Nan::TypedArrayContents<type> vname##_tmp(info[index]); std::vector<type> vname; NAN_convert(vname, vname##_tmp);
#define NAN_ARG_list(type, vname, index) std::vector<type> vname; NAN_tovec(vname, info[index]);
#define NAN_RET_binary(ret) info.GetReturnValue().Set(Nan::CopyBuffer((char*)ret.data(), ret.size()).ToLocalChecked())
#define NAN_RET_string(ret) info.GetReturnValue().Set(New(ret).ToLocalChecked())
#define NAN_RET_bool(ret) info.GetReturnValue().Set(New(ret))
#define NAN_RET_map(from, to, ret) info.GetReturnValue().Set(NAN_map<from, to>(ret))
#define NAN_RET_list(type, ret) info.GetReturnValue().Set(NAN_list<type>(ret))
namespace {ns}_node {{
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::NewStringType;
using v8::Object;
using v8::String;
using v8::Value;
using v8::FunctionTemplate;
using Nan::GetFunction;
using Nan::New;
using Nan::Set;
""")
if __name__ == "__main__":
main()
{
"name": "example",
"version": "1.0.0",
"description": "example-lib",
"main": "index.js",
"directories": {
"test": "tests"
},
"dependencies": {
},
"devDependencies": {
"node-cmake": "^2.5.1",
"nan": "^2.12.1"
},
"license": "MIT"
}
~
@paulocoutinhox
Copy link

Hi, this is working?
What i need to work?
Can i use my cpp djinni code on CEF (chromium embedded framework)?

@earonesty
Copy link
Author

earonesty commented Jun 22, 2021

added an example yml. we also generate the djinni file from the yml. the idea (for us) is to eventually ditch djinni and go with a simplified/subset bindings generator. we're part-way there. needs callback support and that's about it. don't support objects/classes (on purpose)... because language-of-choice should handle things at that level.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment