能否使用Go语言编写原生的Node.js扩展,而不是使用C++?

30

其实,这就是我的问题,但我认为回答这个问题很有意思。

3个回答

20

有了go中对共享库的支持,现在可以实现这一点。

calculator.go:

// package name: calculator
package main

import "C"

//export Sum
func Sum(x, y float64) float64 {
    return x + y
}

func main() {
}

node-calculator.cc:

#include "calculator.h"
#include <node.h>

namespace calc {

  using v8::FunctionCallbackInfo;
  using v8::Isolate;
  using v8::Local;
  using v8::Object;
  using v8::String;
  using v8::Value;
  using v8::Number;
  using v8::Exception;

  void add(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();

    // Check the number of arguments passed.
    if (args.Length() < 2) {
      // Throw an Error that is passed back to JavaScript
      isolate->ThrowException(Exception::TypeError(
          String::NewFromUtf8(isolate, "Wrong number of arguments")));
      return;
    }

    // Check the argument types
    if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
      isolate->ThrowException(Exception::TypeError(
          String::NewFromUtf8(isolate, "Wrong arguments")));
      return;
    }

    // Perform the operation
    Local<Number> num = Number::New(isolate, Sum(args[0]->NumberValue(), args[1]->NumberValue()));

    // Set the return value (using the passed in
    // FunctionCallbackInfo<Value>&)
    args.GetReturnValue().Set(num);
  }

  void init(Local<Object> exports) {
    NODE_SET_METHOD(exports, "add", add);
  }

  NODE_MODULE(calculator, init)
}

binding.gyp:

{
  "targets": [
    {
      "target_name": "node-calculator",
      "sources": [
        "node-calculator.cc"
      ],
      "libraries": [
        "../calculator.a"
      ],
    },
  ],
}

test.js:

const calculator = require('./build/Release/node-calculator');
console.log('4+5=', calculator.add(4, 5));

构建:

go build -buildmode c-archive -o calculator.a calculator.go
node-gyp configure
node-gyp build

输出:

#> node test.js 
4+5= 9

我在使用Go 1.9.3时遇到了以下错误: ld: 警告: 忽略文件../calculator.a,该文件是为不是链接的架构(x86_64)而构建的档案:../calculator.a - user2300902

12

Node.js 的本地模块必须与包含许多 V8 概念(例如垃圾回收、JavaScript 上下文等)的 V8 进程深度交互。

我认为 V8 没有暴露兼容且稳定的 API 以供其他语言进行交互。这就是为什么 node.js 本地插件应该使用 C++ 构建并始终导入 V8 C++ 标头的原因。


但是,您可以通过将 GO 代码封装在 C++ 中来编写 node.js 本地插件:

文件:module.go

package main

func Add(a, b int) int {
    return a + b
}

文件: module.c

#include <node.h>
#include <v8.h>

using namespace v8;

extern int go_add(int, int) __asm__ ("example.main.Add");

void init(Handle<Object> exports) {
    // call go_add
}

NODE_MODULE(module, init)

"如何从C/C++调用GO函数"更多信息:

从C调用Go函数


编辑:

请查看@jdi的评论和链接:https://groups.google.com/forum/#!msg/golang-nuts/FzPbOwbTlPs/dAJVWQHx6m4J

引用:对于像 add(不产生垃圾或需要运行时的简单事物)这样的简单内容,可能是可以做到的,但据我所知,它尚未被任何编译器支持。对于Linux来说已经完成了部分工作(参见golang.org/issue/256),但仍有许多未解决的问题(当您加载两个共享对象时会发生什么等等)


1
我认为Go需要成为入口点,即使您从C中调用它也是如此?就我所知,您无法为其他语言编写“扩展”。 - jdi
我正在安装gccgo。你看过https://dev59.com/RG025IYBdhLWcg3wPzfL#15760986的答案吗?我看到他们可以使用`main`入口编译C程序。 - damphat
直到现在我还没有。我猜我只是没有在golang-nuts邮件列表中看到对这个功能的任何承认。每当这个问题出现时,他们都说你不能编写嵌入到其他语言中的扩展。不确定这种方法的限制是什么。很想知道。 - jdi
请参考golang-nuts邮件列表中的此答案。它目前还没有得到官方支持,并且功能非常有限。正如我所怀疑的那样,运行时不会被正确初始化,您必须执行不生成垃圾或不需要运行时的操作。这似乎使它成为一个几乎没有任何好处的选项。 - jdi
@jdi 谢谢!我不知道那个限制,我认为你对这个问题发表了正确的答案。 - damphat

3

为了将此作为答案而不是评论重新发布...

我向golang-nuts邮件列表跟进关于使用Go编写其他语言的扩展的支持。回复的来源可以在这里找到

对于像add(不生成垃圾或需要运行时的简单事物)之类的简单事物,这可能是可行的,但据我所知,它尚未得到任何编译器的支持。部分工作已经完成了Linux(请参见golang.org/issue/256),但还有许多未解决的问题(当您加载两个共享对象时会发生什么等等)。

因此,实际上,在Go中编写扩展似乎没有太多意义,因为大多数语言功能都不可用,并且您已经在C/C++领域内添加入口点包装器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接