electron / nodejs implementation calls golang function

https://www.jianshu.com/p/a3be0d206d4c

Thinking

golang support compiled c shared library, which is the system common .so (the windows are dll) dynamic link library file suffix. c ++ can be called dynamic link library, so the basic idea is golang develop the main functions, c ++ development of plug-in package golang function to achieve transit call

For the types of issues, in order to facilitate the processing, exposure and acceptance of unity golang function returns a string, you need to pass parameters have been json encoded, the return value is no different. Here implements three kinds of call mode, synchronous calls, asynchronous calls and callbacks with progress the asynchronous call should be able to meet most needs

reference

golang cgo Support:  https://golang.org/cmd/cgo/

achieve

Not much to say directly on the code, the instructions are written in a comment

golang part

// 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" 

Command go build -o gofun.so -buildmode=c-shared gofun.go bridge.go compiled from  gofun.so link library file
by  go tool cgo -- -exportheader gofun.go can get gofun.h header files, you can easily use the c ++

c ++ part

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

By  node-gyp build compiling a addon.node native module file, under the attached configuration file, please refer to the official documentation nodejs

{
    "targets": [
        { "target_name": "addon", "sources": [ "ext.cpp" ] } ] } 

Test js code

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

Guess you like

Origin www.cnblogs.com/answercard/p/11511068.html