electron/nodejs实现调用golang函数
https://www.jianshu.com/p/a3be0d206d4c
思路
golang 支持編譯成c shared library, 也就是系統(tǒng)中常見的.so(windows下是dll)后綴的動(dòng)態(tài)鏈接庫文件. c++可以調(diào)用動(dòng)態(tài)鏈接庫,所以基本思路是golang開發(fā)主要功能, c++開發(fā)插件包裝golang函數(shù),實(shí)現(xiàn)中轉(zhuǎn)調(diào)用
對(duì)于類型問題, 為了方便處理, 暴露的golang函數(shù)統(tǒng)一接受并返回字符串, 需要傳的參數(shù)都經(jīng)過json編碼, 返回值亦然. 這里實(shí)現(xiàn)了3種調(diào)用方式, 同步調(diào)用,異步調(diào)用和帶進(jìn)度回調(diào)的的異步調(diào)用.應(yīng)該能滿足大部分需求
參考
golang cgo支持:?https://golang.org/cmd/cgo/
實(shí)現(xiàn)
不多說直接上代碼, 相關(guān)說明都寫到注釋中了
golang部分
// gofun.go package main// int a; // typedef void (*cb)(char* data); // extern void callCb(cb callback, char* extra, char* arg); import "C" // C是一個(gè)虛包, 上面的注釋是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注解,把這個(gè)函數(shù)暴露到動(dòng)態(tài)鏈接庫里面 //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的回調(diào), go將通過這個(gè)函數(shù)回調(diào)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;// 調(diào)用go的線程所需要的結(jié)構(gòu)體, 把相關(guān)數(shù)據(jù)都封裝進(jìn)去, 同步調(diào)用不需要用到這個(gè) struct GoThreadData {char func[128]{}; // 調(diào)用的go函數(shù)名稱char* arg{}; // 傳給go的參數(shù), json編碼char* result{}; // go返回值bool hasError = false; // 是否有錯(cuò)誤const char *error{}; // 錯(cuò)誤信息char* progress{}; // 進(jìn)度回調(diào)所需要傳的進(jìn)度值bool isProgress = false; // 是否是進(jìn)度調(diào)用, 用來區(qū)分普通調(diào)用Persistent<Function, CopyablePersistentTraits<Function>> onProgress{}; // js的進(jìn)度回調(diào)Persistent<Function, CopyablePersistentTraits<Function>> callback{}; // js 返回值回調(diào)Persistent<Function, CopyablePersistentTraits<Function>> onError{}; // js的出錯(cuò)回調(diào)Isolate* isolate{}; // js引擎實(shí)例uv_async_t* progressReq;// 由于調(diào)用go異步函數(shù)會(huì)新開啟一個(gè)進(jìn)程, 所以go函數(shù)不在主進(jìn)程被調(diào)用, 但是v8規(guī)定,調(diào)用js的函數(shù)必須在住線程當(dāng)中進(jìn)行,否則報(bào)錯(cuò), 所以這里用到了libuv的接口, 用來在子線程中通知主線程執(zhí)行回調(diào). };// 下面的函數(shù)會(huì)在主線程中執(zhí)行, 由libuv庫進(jìn)行調(diào)用, 這里用來處理go回調(diào)過來進(jìn)度值 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獲取進(jìn)度值并回調(diào)給js }// uv異步句柄關(guān)閉回調(diào) void close_cb(uv_handle_t* handle) {// printf("close the async handle!\n"); }// 這個(gè)函數(shù)傳給golang調(diào)用, 當(dāng)golang通知js有進(jìn)度更新時(shí)這里會(huì)執(zhí)行,extra參數(shù)是一個(gè)GoThreadData, 用來區(qū)分是那一次調(diào)用的回調(diào), 可以將GoThreadData理解為go函數(shù)調(diào)用上下文 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; // 把進(jìn)度信息放到上下文當(dāng)中// printf("%d:%s---%s----%s\n",__LINE__, arg, data->func, data->progress);uv_async_send(data->progressReq); // 通知主線程, 這里會(huì)導(dǎo)致上面的progressCallbackFunc執(zhí)行 }void * goLib = nullptr; // 打開的gofun.so的句柄typedef char* (*GoFunc)(char* p0); // go同步函數(shù)和不帶進(jìn)度的異步函數(shù) typedef char* (*GoFuncWithProgress)(char* p0, void (*goCallback) (char* extra, char * arg), char * data); // go帶進(jìn)度回調(diào)的異步函數(shù)map<string, GoFunc> loadedGoFunc; // 一個(gè)map用來存儲(chǔ)已經(jīng)加載啦那些函數(shù) 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, "拓展加載失敗, 請(qǐng)檢查路徑和權(quán)限")));return;}if (goLib) dlclose(goLib);goLib = handle; // 保存到全局變量當(dāng)中l(wèi)oadedGoFunc.empty(); // 覆蓋函數(shù)args.GetReturnValue().Set(true); // 返回true給js }// 釋放go函數(shù)調(diào)用上下文結(jié)構(gòu)體的內(nèi)存 void freeGoThreadData(GoThreadData* data) {delete data->result;delete data->progress;delete data->arg;delete data->error;delete data; }// 由libuv在主線程中進(jìn)行調(diào)用, 當(dāng)go函數(shù)返回時(shí),這里會(huì)被調(diào)用 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());// 這里是必須的,調(diào)用js函數(shù)需要一個(gè)handle scopeif (goThreadData->hasError) { // 如果有錯(cuò)誤, 生成一個(gè)錯(cuò)誤實(shí)例并傳給js錯(cuò)誤回調(diào)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;}// 沒有錯(cuò)誤, 把結(jié)果回調(diào)給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進(jìn)度的事件刪除, 不然這個(gè)事件會(huì)一直存在時(shí)間循環(huán)中, node進(jìn)程也不會(huì)退出}// 釋放內(nèi)存freeGoThreadData(goThreadData); }// 工作線程, 在這個(gè)函數(shù)中調(diào)用go void callGoThread(uv_work_t* req) {// 從uv_work_t的結(jié)構(gòu)體中獲取我們定義的入?yún)⒔Y(jié)構(gòu)auto * goThreadData = (GoThreadData*) req->data;// printf("%s: %d\n", __FUNCTION__, (int)uv_thread_self());// 檢查內(nèi)核是否加載if (!goLib) {goThreadData->hasError = true;String::NewFromUtf8(goThreadData->isolate, "請(qǐng)先加載內(nèi)核");goThreadData->error = "請(qǐng)先加載內(nèi)核";return;}if (!goThreadData->isProgress) {// 檢查函數(shù)是否加載if (! loadedGoFunc[goThreadData->func]) {auto goFunc = (GoFunc) dlsym(goLib, goThreadData->func);if(!goFunc){goThreadData->hasError = true;goThreadData->error = "函數(shù)加載失敗";return;}// printf("loaded %s\n", goThreadData->func);loadedGoFunc[goThreadData->func] = goFunc;}// 調(diào)用go函數(shù)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回調(diào)函數(shù)的// 檢查函數(shù)是否加載if (! loadedGoFuncWithProgress[goThreadData->func]) {auto goFunc = (GoFuncWithProgress) dlsym(goLib, goThreadData->func);if(!goFunc){goThreadData->hasError = true;goThreadData->error = "函數(shù)加載失敗";return;}// printf("loaded %s\n", goThreadData->func);loadedGoFuncWithProgress[goThreadData->func] = goFunc;}// 調(diào)用go函數(shù)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的,用來調(diào)用go的非同步函數(shù)(同步只是相對(duì)js而言, 實(shí)際上go函數(shù)還是同步執(zhí)行的) void callGoAsync(const FunctionCallbackInfo<Value>& args) {// printf("%s: %d\n", __FUNCTION__, (int)uv_thread_self());Isolate* isolate = args.GetIsolate();// 檢查傳入的參數(shù)的個(gè)數(shù)if (args.Length() < 3 || (!args[0]->IsString()|| !args[1]->IsString()|| !args[2]->IsFunction()|| !args[3]->IsFunction())) {// 拋出一個(gè)錯(cuò)誤并傳回到 JavaScriptisolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "調(diào)用格式: 函數(shù)名稱, JSON參數(shù), 成功回調(diào), 錯(cuò)誤回調(diào)")));return;}// 參數(shù)格式化, 構(gòu)造線程數(shù)據(jù)auto goThreadData = new GoThreadData;// 有第5個(gè)參數(shù), 說明是調(diào)用有進(jìn)度回調(diào)的go函數(shù)if (args.Length() >= 5) {if (!args[4]->IsFunction()) {isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "如果有第5個(gè)參數(shù), 請(qǐng)傳入Progress回調(diào)")));return;} else {goThreadData->isProgress = true;goThreadData->onProgress.Reset(isolate, Local<Function>::Cast(args[4]));}}// go調(diào)用上下文的初始化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);// 調(diào)用libuv實(shí)現(xiàn)多線程auto req = new uv_work_t();req->data = goThreadData;// 如果是有進(jìn)度回調(diào)的需要注冊一個(gè)異步事件, 以便在子線程回調(diào)jsif (goThreadData->isProgress) {goThreadData->progressReq = new uv_async_t();goThreadData->progressReq->data = (void *) goThreadData;uv_async_init(uv_default_loop(), goThreadData->progressReq, progressCallbackFunc);}// 調(diào)用libuv的線程處理函數(shù)uv_queue_work(uv_default_loop(), req, callGoThread, afterGoTread);}// 模塊初始化, 注冊暴露給js的函數(shù) 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原生模塊文件,下附配置文件, 請(qǐng)參考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) }) })轉(zhuǎn)載于:https://www.cnblogs.com/answercard/p/11511068.html
總結(jié)
以上是生活随笔為你收集整理的electron/nodejs实现调用golang函数的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SDNU 1178.能量项链(区间dp)
- 下一篇: DDD领域驱动实践记录