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 scopeif (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;}// 没有错误, 把结果回调给jsLocal<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())) {// 抛出一个错误并传回到 JavaScriptisolate->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;// 如果是有进度回调的需要注册一个异步事件, 以便在子线程回调jsif (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

electron/nodejs实现调用golang函数相关推荐

  1. Electron使用NodeJS扩展模块调用DLL

    在windows平台,用vs2015开发一个最简单的dll. 由于安装的electron是64位的,所以需要配置编译生成64位的DLL. 把生成的FaceRecognition.dll拷贝到elect ...

  2. Golang实践录:调用C++函数的优化

    趁着五一放假,趁着有时间,把欠的一些技术集中研究研究,写写文章,好给自己一个交待. 本文继续介绍如何在 Golang 中调用 C++ 函数. 起因 前面文章介绍的方式,在运行时需要指定动态库位置,或将 ...

  3. Golang实践录:调用C++函数

    趁着五一放假,趁着有时间,把欠的一些技术集中研究研究,写写文章,好给自己一个交待. 本文介绍如何在 Golang 中调用 C++ 函数. 起因 因工作需求,需要将一个工具由终端行的运行方式迁移到 we ...

  4. 打造跳跃音波播音乐放器(Electron+Nodejs+React)

    Electron可以让我们使用html,css,javascript来搭建跨平台(Windows.macOS.Linux)的桌面应用.下面通过Electron+Nodejs+React来实现一个支持播 ...

  5. Golang函数,包笔记

    一.函数 函数的基本概念 为完成某一功能的程序指令(语句)的集合,称为函数. 在 Go 中,函数分为: 自定义函数.系统函数(查看 Go 编程手册) 函数的基本语法 快速入门案例 二.包 为什么需要包 ...

  6. python 调用golang_python调用golang编写的动态链接库

    楔子 我们都知道python的效率很低,但是好在可以和C语言无缝结合,C语言写好动态链接库之后再让python去调用简直不要太方便.但是使用C编写动态链接库也不是件容易的事情,于是笔者想到了go,go ...

  7. NodeJS学习笔记 —— 回调函数与callback hell

    什么是回调函数 编程分为两类:系统编程(system programming)和应用编程(application programming).所谓系统编程,简单来说,就是编写库:而应用编程就是利用写好的 ...

  8. Go语言自学系列 | golang函数的返回值

    视频来源:B站<golang入门到项目实战 [2021最新Go语言教程,没有废话,纯干货!持续更新中...]> 一边学习一边整理老师的课程内容及试验笔记,并与大家分享,侵权即删,谢谢支持! ...

  9. golang Linux桌面程序,舍弃Electron,使用HTML5 + Golang创建桌面应用程序

    Electron软件框架,允许使用JavaScript创建桌面GUI应用程序接口,并依赖于一个绑定的Chromium + Node.js运行时在大多数Linux桌面用户中名声不是很好,因为它的资源很重 ...

最新文章

  1. Linux命令cat
  2. php中register_global,PHP安全之register_globals的on和off的区别
  3. PHP安装zip拓展,以及libzip安装问题
  4. 第2章-神经网络的数学基础(笔记)
  5. 个人工作日报模板_2020最新销售店长个人年度工作计划模板精选3篇
  6. 电热水器工作过程 c语言,热水器工作流程图
  7. combobox is not a function 问题解决
  8. 由软件工程看中国人急功近利的心态
  9. Wireshark实战分析之ARP协议(二)
  10. html相册魔方代码,魔方相册制作方法现成的魔方相册代码:
  11. 数据处理——偏最小二乘法
  12. ASC19启动:在培养超算人才的路上砥砺前行
  13. 常用国际贸易术语汇总
  14. 智安新闻丨智安网络与“南滨路国家级文化数字产业中心”就等保云业务展开成功签署战略合作协议
  15. Unity二维平面上物体的移动(十一)-位移和推动力移动的区别
  16. 【题目】pyCharm 专业版 和 社区版的区别以及如何查看其版本
  17. 自己动手丰衣足食之移动端日期选择插件(强烈推荐)
  18. PHP实现谷歌验证器二次验证
  19. 端口汇聚和端口聚合的区别
  20. Java声母_声母r是浊擦音,n是浊鼻音,l是浊边音

热门文章

  1. Alwayson--问题总结二
  2. 终端链接操作redis
  3. 前后端分离之JWT用户认证
  4. scrapy 动态IP、随机UA、验证码
  5. AD账号创建日期、最近一次登录时间、最近一次重置密码时间查询
  6. 常用开源工具、框架收藏
  7. React-native键盘遮挡输入框问题的解决
  8. 深入分析Php处理浮点数的问题
  9. Excel 公式(细节若干)
  10. iOS开发笔记[18/50]:在Mac OS X Lion系统中访问~/Library目录都需要点技巧