https://www.jianshu.com/p/a3be0d206d4c
javascript
golang 支持编译成c shared library, 也就是系统中常见的.so(windows下是dll)后缀的动态连接库文件. c++能够调用动态连接库,因此基本思路是golang开发主要功能, c++开发插件包装golang函数,实现中转调用java
对于类型问题, 为了方便处理, 暴露的golang函数统一接受并返回字符串, 须要传的参数都通过json编码, 返回值亦然. 这里实现了3种调用方式, 同步调用,异步调用和带进度回调的的异步调用.应该能知足大部分需求node
golang cgo支持: https://golang.org/cmd/cgo/python
很少说直接上代码, 相关说明都写到注释中了c++
// gofun.go package main // int a; // typedef void (*cb)(char* data); // extern void callCb(cb callback, char* extra, char* arg); import "C" // C是一个虚包, 上面的注释是c代码, 能够在golang中加 `C.` 前缀访问, 具体参考上面给出的文档 import "time" //export hello func hello(arg *C.char) *C.char { //name := gjson.Get(arg, "name") //return "hello" + name.String() return C.CString("hello peter:::" + C.GoString(arg)) } // 经过export注解,把这个函数暴露到动态连接库里面 //export helloP func helloP(arg *C.char, cb C.cb, extra *C.char) *C.char { C.callCb(cb, extra, C.CString("one")) time.Sleep(time.Second) C.callCb(cb, extra, C.CString("two")) return C.CString("hello peter:::" + C.GoString(arg)) } func main() { println("go main func") }
// bridge.go package main // typedef void (*cb)(char* extra, char* data); // void callCb(cb callback, char* extra , char* arg) { // c的回调, go将经过这个函数回调c代码 // callback(extra,arg); // } import "C"
经过命令go build -o gofun.so -buildmode=c-shared gofun.go bridge.go
编译获得 gofun.so
的连接库文件
经过 go tool cgo -- -exportheader gofun.go
能够获得gofun.h头文件, 能够方便在c++中使用golang
// ext.cpp #include <node.h> #include <uv.h> #include <dlfcn.h> #include <cstring> #include <map> #include "go/gofun.h" #include <stdio.h> using namespace std; using namespace node; using namespace v8; // 调用go的线程所须要的结构体, 把相关数据都封装进去, 同步调用不须要用到这个 struct GoThreadData { char func[128]{}; // 调用的go函数名称 char* arg{}; // 传给go的参数, json编码 char* result{}; // go返回值 bool hasError = false; // 是否有错误 const char *error{}; // 错误信息 char* progress{}; // 进度回调所须要传的进度值 bool isProgress = false; // 是不是进度调用, 用来区分普通调用 Persistent<Function, CopyablePersistentTraits<Function>> onProgress{}; // js的进度回调 Persistent<Function, CopyablePersistentTraits<Function>> callback{}; // js 返回值回调 Persistent<Function, CopyablePersistentTraits<Function>> onError{}; // js的出错回调 Isolate* isolate{}; // js引擎实例 uv_async_t* progressReq;// 因为调用go异步函数会新开启一个进程, 因此go函数不在主进程被调用, 可是v8规定,调用js的函数必须在住线程当中进行,不然报错, 因此这里用到了libuv的接口, 用来在子线程中通知主线程执行回调. }; // 下面的函数会在主线程中执行, 由libuv库进行调用, 这里用来处理go回调过来进度值 void progressCallbackFunc(uv_async_t *handle) { HandleScope handle_scope(Isolate::GetCurrent()); GoThreadData* goThreadData = (GoThreadData *) handle->data; // printf("%s___%d__%s\n", __FUNCTION__, (int)uv_thread_self() , goThreadData->progress); Local<Value> argv[1] = {String::NewFromUtf8(goThreadData->isolate, goThreadData->progress)}; Local<Function>::New(goThreadData->isolate, goThreadData->onProgress)->Call(goThreadData->isolate->GetCurrentContext()->Global(), 1, argv); // 从goThreadData获取进度值并回调给js } // uv异步句柄关闭回调 void close_cb(uv_handle_t* handle) { // printf("close the async handle!\n"); } // 这个函数传给golang调用, 当golang通知js有进度更新时这里会执行,extra参数是一个GoThreadData, 用来区分是那一次调用的回调, 能够将GoThreadData理解为go函数调用上下文 void goCallback(char * extra, char * arg) { // printf("%s: %d\n", __FUNCTION__, (int)uv_thread_self()); GoThreadData* data = (GoThreadData *) extra; delete data->progress; data->progress = arg; // 把进度信息放到上下文当中 // printf("%d:%s---%s----%s\n",__LINE__, arg, data->func, data->progress); uv_async_send(data->progressReq); // 通知主线程, 这里会致使上面的progressCallbackFunc执行 } void * goLib = nullptr; // 打开的gofun.so的句柄 typedef char* (*GoFunc)(char* p0); // go同步函数和不带进度的异步函数 typedef char* (*GoFuncWithProgress)(char* p0, void (*goCallback) (char* extra, char * arg), char * data); // go带进度回调的异步函数 map<string, GoFunc> loadedGoFunc; // 一个map用来存储已经加载啦那些函数 map<string, GoFuncWithProgress> loadedGoFuncWithProgress; // 和上面相似 // 加载 go 拓展, 暴露给js 经过路径加载so文件 void loadGo(const FunctionCallbackInfo<Value>& args) { String::Utf8Value path(args[0]->ToString()); Isolate* isolate = args.GetIsolate(); void *handle = dlopen(*path, RTLD_LAZY); if (!handle) { isolate->ThrowException(Exception::Error( String::NewFromUtf8(isolate, "拓展加载失败, 请检查路径和权限") )); return; } if (goLib) dlclose(goLib); goLib = handle; // 保存到全局变量当中 loadedGoFunc.empty(); // 覆盖函数 args.GetReturnValue().Set(true); // 返回true给js } // 释放go函数调用上下文结构体的内存 void freeGoThreadData(GoThreadData* data) { delete data->result; delete data->progress; delete data->arg; delete data->error; delete data; } // 由libuv在主线程中进行调用, 当go函数返回时,这里会被调用 void afterGoTread (uv_work_t* req, int status) { // printf("%s: %d\n", __FUNCTION__, (int)uv_thread_self()); auto * goThreadData = (GoThreadData*) req->data; HandleScope handle_scope(Isolate::GetCurrent());// 这里是必须的,调用js函数须要一个handle scope if (goThreadData->hasError) { // 若是有错误, 生成一个错误实例并传给js错误回调 Local<Value> argv[1] = {Exception::Error( String::NewFromUtf8(goThreadData->isolate, goThreadData->error) )}; Local<Function>::New(goThreadData->isolate, goThreadData->onError)->Call(goThreadData->isolate->GetCurrentContext()->Global(), 1, argv); return; } // 没有错误, 把结果回调给js Local<Value> argv[1] = {String::NewFromUtf8(goThreadData->isolate, goThreadData->result)}; Local<Function>::New(goThreadData->isolate, goThreadData->callback)->Call(goThreadData->isolate->GetCurrentContext()->Global(), 1, argv); if (goThreadData->isProgress) { // printf(((GoThreadData *)goThreadData->progressReq->data)->result); uv_close((uv_handle_t*) goThreadData->progressReq, close_cb); // 这里须要把通知js进度的事件删除, 否则这个事件会一直存在时间循环中, node进程也不会退出 } // 释放内存 freeGoThreadData(goThreadData); } // 工做线程, 在这个函数中调用go void callGoThread(uv_work_t* req) { // 从uv_work_t的结构体中获取咱们定义的入参结构 auto * goThreadData = (GoThreadData*) req->data; // printf("%s: %d\n", __FUNCTION__, (int)uv_thread_self()); // 检查内核是否加载 if (!goLib) { goThreadData->hasError = true; String::NewFromUtf8(goThreadData->isolate, "请先加载内核"); goThreadData->error = "请先加载内核"; return; } if (!goThreadData->isProgress) { // 检查函数是否加载 if (! loadedGoFunc[goThreadData->func]) { auto goFunc = (GoFunc) dlsym(goLib, goThreadData->func); if(!goFunc) { goThreadData->hasError = true; goThreadData->error = "函数加载失败"; return; } // printf("loaded %s\n", goThreadData->func); loadedGoFunc[goThreadData->func] = goFunc; } // 调用go函数 GoFunc func = loadedGoFunc[goThreadData->func]; char * result = func(goThreadData->arg); // printf("%d:%s\n-----------------------------\n", __LINE__, result); // printf("%d:%s\n-----------------------------\n", __LINE__, goThreadData->arg); goThreadData->result = result; return; } // 有progress回调函数的 // 检查函数是否加载 if (! loadedGoFuncWithProgress[goThreadData->func]) { auto goFunc = (GoFuncWithProgress) dlsym(goLib, goThreadData->func); if(!goFunc) { goThreadData->hasError = true; goThreadData->error = "函数加载失败"; return; } // printf("loaded %s\n", goThreadData->func); loadedGoFuncWithProgress[goThreadData->func] = goFunc; } // 调用go函数 GoFuncWithProgress func = loadedGoFuncWithProgress[goThreadData->func]; char * result = func(goThreadData->arg, goCallback, (char*) goThreadData); // printf("%d:%s\n-----------------------------\n", __LINE__, result); // printf("%d:%s\n-----------------------------\n", __LINE__, goThreadData->arg); goThreadData->result = result; } // 暴露给js的,用来调用go的非同步函数(同步只是相对js而言, 实际上go函数仍是同步执行的) void callGoAsync(const FunctionCallbackInfo<Value>& args) { // printf("%s: %d\n", __FUNCTION__, (int)uv_thread_self()); Isolate* isolate = args.GetIsolate(); // 检查传入的参数的个数 if (args.Length() < 3 || ( !args[0]->IsString() || !args[1]->IsString() || !args[2]->IsFunction() || !args[3]->IsFunction() )) { // 抛出一个错误并传回到 JavaScript isolate->ThrowException(Exception::TypeError( String::NewFromUtf8(isolate, "调用格式: 函数名称, JSON参数, 成功回调, 错误回调"))); return; } // 参数格式化, 构造线程数据 auto goThreadData = new GoThreadData; // 有第5个参数, 说明是调用有进度回调的go函数 if (args.Length() >= 5) { if (!args[4]->IsFunction()) { isolate->ThrowException(Exception::TypeError( String::NewFromUtf8(isolate, "若是有第5个参数, 请传入Progress回调"))); return; } else { goThreadData->isProgress = true; goThreadData->onProgress.Reset(isolate, Local<Function>::Cast(args[4])); } } // go调用上下文的初始化 goThreadData->callback.Reset(isolate, Local<Function>::Cast(args[2])); goThreadData->onError.Reset(isolate, Local<Function>::Cast(args[3])); goThreadData->isolate = isolate; v8::String::Utf8Value arg(args[1]->ToString()); goThreadData->arg = (char*)(new string(*arg))->data(); v8::String::Utf8Value func(args[0]->ToString()); strcpy(goThreadData->func, *func); // 调用libuv实现多线程 auto req = new uv_work_t(); req->data = goThreadData; // 若是是有进度回调的须要注册一个异步事件, 以便在子线程回调js if (goThreadData->isProgress) { goThreadData->progressReq = new uv_async_t(); goThreadData->progressReq->data = (void *) goThreadData; uv_async_init(uv_default_loop(), goThreadData->progressReq, progressCallbackFunc); } // 调用libuv的线程处理函数 uv_queue_work(uv_default_loop(), req, callGoThread, afterGoTread); } // 模块初始化, 注册暴露给js的函数 void init(Local<Object> exports) { NODE_SET_METHOD(exports, "loadCore", loadGo); NODE_SET_METHOD(exports, "callCoreAsync", callGoAsync); } NODE_MODULE(addon, init)
经过 node-gyp build
编译出addon.node原生模块文件,下附配置文件, 请参考nodejs官方文档json
{ "targets": [ { "target_name": "addon", "sources": [ "ext.cpp" ] } ] }
// test.js let addon = require('./build/Release/addon'); let success = function (data) { console.log("leo") console.log(data); } let fail = function (error) { console.log('peter') console.log(error) } addon.loadCore('./go/gofun.1.so') addon.callCoreAsync('hello', JSON.stringify({name: '我爱你'}), success, fail) setTimeout(function () { addon.callCoreAsync('helloP', JSON.stringify({name: '我爱你1'}), success, fail, function (data) { console.log('js log:' + data) }) })