zoukankan      html  css  js  c++  java
  • electron/nodejs实现调用golang函数

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

    思路

    golang 支持编译成c shared library, 也就是系统中常见的.so(windows下是dll)后缀的动态链接库文件. c++可以调用动态链接库,所以基本思路是golang开发主要功能, c++开发插件包装golang函数,实现中转调用

    对于类型问题, 为了方便处理, 暴露的golang函数统一接受并返回字符串, 需要传的参数都经过json编码, 返回值亦然. 这里实现了3种调用方式, 同步调用,异步调用和带进度回调的的异步调用.应该能满足大部分需求

    参考

    golang cgo支持: https://golang.org/cmd/cgo/

    实现

    不多说直接上代码, 相关说明都写到注释中了

    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.go 编译得到 gofun.so 的链接库文件
    通过 go tool cgo -- -exportheader gofun.go 可以得到gofun.h头文件, 可以方便在c++中使用

    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
    ", __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!
    ");
    }
    
    // 这个函数传给golang调用, 当golang通知js有进度更新时这里会执行,extra参数是一个GoThreadData, 用来区分是那一次调用的回调, 可以将GoThreadData理解为go函数调用上下文
    void goCallback(char * extra, char * arg) {
        // printf("%s: %d
    ", __FUNCTION__,  (int)uv_thread_self());
        GoThreadData* data = (GoThreadData *) extra;
        delete data->progress;
        data->progress = arg; // 把进度信息放到上下文当中
        // printf("%d:%s---%s----%s
    ",__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
    ", __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
    ", __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
    ", goThreadData->func);
                loadedGoFunc[goThreadData->func] = goFunc;
            }
    
            // 调用go函数
            GoFunc func = loadedGoFunc[goThreadData->func];
            char * result = func(goThreadData->arg);
            // printf("%d:%s
    -----------------------------
    ", __LINE__, result);
            // printf("%d:%s
    -----------------------------
    ", __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
    ", goThreadData->func);
            loadedGoFuncWithProgress[goThreadData->func] = goFunc;
        }
    
        // 调用go函数
        GoFuncWithProgress func = loadedGoFuncWithProgress[goThreadData->func];
        char * result = func(goThreadData->arg, goCallback, (char*) goThreadData);
        // printf("%d:%s
    -----------------------------
    ", __LINE__, result);
        // printf("%d:%s
    -----------------------------
    ", __LINE__, goThreadData->arg);
        goThreadData->result = result;
    }
    
    
    // 暴露给js的,用来调用go的非同步函数(同步只是相对js而言, 实际上go函数还是同步执行的)
    void callGoAsync(const FunctionCallbackInfo<Value>& args) {
        // printf("%s: %d
    ", __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官方文档

    {
        "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)
        })
    })
    
    
  • 相关阅读:
    Eclipse 导入项目乱码问题(中文乱码)
    sql中视图视图的作用
    Java基础-super关键字与this关键字
    Android LayoutInflater.inflate(int resource, ViewGroup root, boolean attachToRoot)的参数理解
    Android View和ViewGroup
    工厂方法模式(java 设计模式)
    设计模式(java) 单例模式 单例类
    eclipse乱码解决方法
    No resource found that matches the given name 'Theme.AppCompat.Light 的完美解决方案
    【转】使用 Eclipse 调试 Java 程序的 10 个技巧
  • 原文地址:https://www.cnblogs.com/answercard/p/11511068.html
Copyright © 2011-2022 走看看