nodejs php golang,electron/nodejs实现调用golang函数

nodejs 调用golang

最近在用electron开发一个gui程序, 有一些代码不能暴露出来. 但是JavaScript是解释型语言, 混淆什么的都难以保证代码不被泄露.所以就想写一个原生插件, 暴露接口js调用. js原生插件通常用C++开发, 但是本人不会,去学习的话涉及到的知识太多了,短期内肯定是不行的,不过最近正好在学golang, 就想着能不能用golang去写.

本人目前是php程序猿, c++和golang都是入门水平, 若是代码中有任何不妥,还望大神不吝赐教. 见笑了.

思路

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

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

参考

实现

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

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

#include

#include

#include

#include

#include "go/gofun.h"

#include

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> onProgress{}; // js的进度回调

Persistent> callback{}; // js 返回值回调

Persistent> 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 argv[1] = {String::NewFromUtf8(goThreadData->isolate, goThreadData->progress)};

Local::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 loadedGoFunc; // 一个map用来存储已经加载啦那些函数

map loadedGoFuncWithProgress; // 和上面类似

// 加载 go 拓展, 暴露给js 通过路径加载so文件

void loadGo(const FunctionCallbackInfo& 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 argv[1] = {Exception::Error(

String::NewFromUtf8(goThreadData->isolate, goThreadData->error)

)};

Local::New(goThreadData->isolate, goThreadData->onError)->Call(goThreadData->isolate->GetCurrentContext()->Global(), 1, argv);

return;

}

// 没有错误, 把结果回调给js

Local argv[1] = {String::NewFromUtf8(goThreadData->isolate, goThreadData->result)};

Local::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& 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::Cast(args[4]));

}

}

// go调用上下文的初始化

goThreadData->callback.Reset(isolate, Local::Cast(args[2]));

goThreadData->onError.Reset(isolate, Local::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 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)

})

})

输出如下:

a3be0d206d4c

2018-05-14 14-58-35 的屏幕截图.png

踩了不少坑, 主要是网上对于node addon开发的相关文章都过时了, 自己摸索着终于搞完了, 特此分享一下.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值