思路

golang 支持編譯成c shared library, 也就是系統中常見的.so(windows下是dll)後綴的動態連接庫文件. c++能夠調用動態連接庫,因此基本思路是golang開發主要功能, c++開發插件包裝golang函數,實現中轉調用java

對於類型問題, 爲了方便處理, 暴露的golang函數統一接受並返回字符串, 須要傳的參數都通過json編碼, 返回值亦然. 這裏實現了3種調用方式, 同步調用,異步調用和帶進度回調的的異步調用.應該能知足大部分需求node

參考

實現

很少說直接上代碼, 相關說明都寫到註釋中了c++

golang部分

// 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.gogofun.sogo tool cgo -- -exportheader gofun.go

c++部分

// 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
{ "targets": [ { "target_name": "addon", "sources": [ "ext.cpp" ] } ] } 

測試的js代碼

// 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) }) })