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\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官方文档

{
    "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)
    })
})

原文地址:https://www.cnblogs.com/answercard/p/11511068.html

时间: 2024-08-18 19:23:28

electron/nodejs实现调用golang函数的相关文章

C/C++调用Golang 二

C/C++调用Golang 二 <C/C++调用Golang 一>简单介绍了C/C++调用Golang的方法步骤,只涉及一个简单的函数调用.本文总结具体项目中的使用场景,将介绍三种较复杂的调用方式:一,C++向golang传入复杂结构体:二,C++向golang传入回调函数,在golang中调用C++函数:三,C++调用golang函数,返回复杂的结构体. (本文后面涉及三个例子,省略了编译步骤,仅展示关键代码.具体操作步骤参考<C/C++调用Golang 一>) 一 C++向go

C/C++调用Golang 一

(开发环境: 操作系统: windows 7 32位操作系统 C++: visual studio 2010 Golang:go version go1.9 windows/386   TDM-GCC-32) 用一个简单的例子演示如何在C++中调用golang程序.用golang编写一个简单的函数,编译成动态链接库,然后在C++中调用该go函数. 第一阶段 将Golang代码编译成动态链接库 (涉及2个文件 main.go和godll.def) Golang : main.go  一个简单的Ad

Golang 函数

函数声明 func (p myType ) funcName ( a, b int , c string ) ( r , s int ) { return } func 关键字 (p myType) 表明 函数所属于的类型对象!,即为特定类型定义方法,可以省去不写,即为普通的函数 (这里我们主要讲解 普通的函数) 函数名 参数 (可以不声明) 返回值 (可以不声明) 函数体 调用标准函数 Golang 提供了 大量的包和实用函数 供用户使用,这些函数被称为标准函数.常见的标准包有 fmt, ma

GO开发[四]:golang函数

函数 1.声明语法:func 函数名 (参数列表) [(返回值列表)] {} 2.golang函数特点: a. 不支持重载,一个包不能有两个名字一样的函数 b. 函数是一等公民,函数也是一种类型,一个函数可以赋值给变量 c. 匿名函数 d. 多返回值 定义函数类型type: package main import "fmt" type add_func func(int, int) int func add(a, b int) int { return a + b } func sub

golang 函数和方法

由于自己是搞python开发的,所以在学习go时,当看到函数和方法时,顿时还是挺蒙的,因为在python中并没有明显的区别,但是在go中却是两个完全不同的东西.在官方的解释中,方法是包含了接收者的函数. 定义 函数的格式是固定的Func + 函数名 + 参数 + 返回值(可选) + 函数体 Func main( a, b int) (int) { } 而方法会在方法在func关键字后是接收者而不是函数名,接收者可以是自己定义的一个类型,这个类型可以是struct,interface,甚至我们可以

C#实现百度地图附近搜索&amp;调用JavaScript函数

前一篇文章"C#调用百度地图API入门&解决BMap未定义问题"讲述了如何通过C#调用百度API显示地图,并且如何解决BMap未定义的问题.这篇文章主要更加详细的介绍百度地图的一些功能,包括附近搜索.城市搜索.路线规划.添加覆盖物等等. 希望文章对你有所帮助!如果文章中有不足之处,还请海涵~ 百度官方文档:http://developer.baidu.com/map/jsmobile.htm 官方DEMO例:http://developer.baidu.com/map/jsde

RTX——第19章 SVC 中断方式调用用户函数(后期补历程)

本章节为大家讲解如何采用 SVC 中断方式调用用户函数. 当用户将 RTX 任务设置为工作在非特权级模式时,任务中是不允许访问特权级寄存器的,这个时候使用 SVC 中断,此问题就迎刃而解了. SVC 功能介绍SVC 用于产生系统函数的调用请求.例如,操作系统通常不让用户程序直接访问硬件,而是通过提供一些系统服务函数,让用户程序使用 SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件.因此,当用户程序想要控制特定的硬件时,它就要产生一个 SVC 异常,然后操作系统提供的SVC

QT5.3无法自动调用incomingConnection函数的问题(4.7没有这个问题)

最近将qt4.7的一个工程移到5.3,遇到了几个麻烦事,主要是这个incomingConnection监听后无法自动调用的问题,在4.7上是完全没有问题的,到了5.3就不行,网上也查了下,网友们都是放出问题,然而都没有写出解决. 1.一步解决 我之前是这样写的: void TFTPxServer::incomingConnection(int socketDescriptor) { qDebug() << "incomingConnection..."; TFTPxThre

用户选择,调用相应函数的编写方法

用户选择,调用相应函数的编写方法: 1.先输出Menu菜单,让用户选择. 2.创建一个Menu字典k=序号,v=操作函数 3.调用用户选择的相应序号的函数:menu[option](参数) def account_info(acc_data): print(user_data) def repay(acc_data): pass def withdraw(acc_data): pass def transfer(acc_data): pass def pay_check(acc_data): p