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)
})
})
electron/nodejs实现调用golang函数的更多相关文章
- C/C++调用Golang 二
C/C++调用Golang 二 <C/C++调用Golang 一>简单介绍了C/C++调用Golang的方法步骤,只涉及一个简单的函数调用.本文总结具体项目中的使用场景,将介绍三种较复杂的 ...
- C/C++调用Golang 一
C/C++调用Golang 一 (开发环境: 操作系统: windows 7 32位操作系统 C++: visual studio 2010 Golang:go version go1.9 windo ...
- GO开发[四]:golang函数
函数 1.声明语法:func 函数名 (参数列表) [(返回值列表)] {} 2.golang函数特点: a. 不支持重载,一个包不能有两个名字一样的函数 b. 函数是一等公民,函数也是一种类型,一个 ...
- nodejs记录1——async函数
其实手动配置babel环境并不难,记录下步骤: 1.首先npm init创建一个nodejs项目 2.全局安装babel-cli处理工具:npm i babel-cli -g 3.cd到项目下安装ba ...
- golang 函数和方法
由于自己是搞python开发的,所以在学习go时,当看到函数和方法时,顿时还是挺蒙的,因为在python中并没有明显的区别,但是在go中却是两个完全不同的东西.在官方的解释中,方法是包含了接收者的函数 ...
- gohook 一个支持运行时替换 golang 函数的库实现
运行时替换函数对 golang 这类静态语言来说并不是件容易的事情,语言层面的不支持导致只能从机器码层面做些奇怪 hack,往往艰难,但如能成功,那挣脱牢笼带来的成就感,想想就让人兴奋. gohook ...
- Golang 函数以及函数和方法的区别
在接触到go之前,我认为函数和方法只是同一个东西的两个名字而已(在我熟悉的c/c++,python,java中没有明显的区别),但是在golang中者完全是两个不同的东西.官方的解释是,方法是包含了接 ...
- Golang函数-递归函数
Golang函数-递归函数 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.
- Golang函数-不定参函数
Golang函数-不定参函数 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.
随机推荐
- canal
https://github.com/alibaba/canal/wiki/QuickStart https://github.com/alibaba/canal/releases/download/ ...
- linux-centos安装图解及配置IP远程连接
本次安装使用vm软件的15版本,系统为centos7.6(1810) 系统安装图解>配置IP信息联网>真实机是无线网络状态,虚拟机如何联网>远程工具连接虚拟机 一,vm安装cento ...
- SourceTree 免登录
SourceTree 是 Windows 和Mac OS X 下免费的 Git 和 Hg 客户端,拥有可视化界面,容易上手操作.同时它也是Mercurial和Subversion版本控制系统工具.支持 ...
- 缓存雪崩、穿透如何解决,如何确保Redis只缓存热点数据?
缓存雪崩如何解决? 缓存穿透如何解决? 如何确保Redis缓存的都是热点数据? 如何更新缓存数据? 如何处理请求倾斜? 实际业务场景下,如何选择缓存数据结构 缓存雪崩 缓存雪崩简单说就是所有请求都从缓 ...
- .net core ajax使用EPPlus上传excle导入总结
前端 <form class="layui-form" id="div_imp" style="display:none;"> ...
- 一种优化操作list、数组的多线程解决方案。
这几天接触到了一些操作list的功能,由于list太长,加上每条数据的处理时间,导致性能下降,正好利用学来的多线程知识和网上的资料结合实践一番,写出了一个通用类如下. /** * 操作数组的线程 * ...
- 2019 博盾习言java面试笔试题 (含面试题解析)
本人5年开发经验.18年年底开始跑路找工作,在互联网寒冬下成功拿到阿里巴巴.今日头条.博盾习言等公司offer,岗位是Java后端开发,因为发展原因最终选择去了博盾习言,入职一年时间了,也成为了面 ...
- 操作系统与进程.md
目录 1. 操作系统 1.1 作用 1.2 操作系统的发展 2. 进程的理论 2.1 相关名词 2.2 进程的创建 2.3 进程的状态: 1. 操作系统 管理.控制.协调计算机硬件与软件资源的计算 ...
- English--比较结构
English|比较结构 接下来让我们一起来透析,英语中的比较结构.各位同学,带上小板凳,要认真哦~ 前言 目前所有的文章思想格式都是:知识+情感. 知识:对于所有的知识点的描述.力求不含任何的自我感 ...
- OO第四单元作业小结
一.本单元两次作业的架构设计 1.第一次作业 整体思路:以class为核心,建立MyInterface.MyAttribute.MyOperation.MyAssociation四个类分别储存每个类的 ...