如何在Rust中进行静态链接Node.js?

4
我想在Rust中嵌入node.js。我有兴趣使用NAPI编写node.js插件或从rust内部广泛控制node.js。我所需要的仅仅是node.js的main()启动方法 - 相当于执行node myscript.js
为什么?我正在构建一个自包含的单文件二进制桌面应用程序,希望在其中运行具有嵌入式node.js运行时的node.js脚本。不能保证终端用户计算机上安装了Node.js,并且启动时间很敏感,因此从二进制文件中提取一个自包含的node.js zip文件到文件系统是不可取的。
我认为我在我的Rust(二进制)项目中静态链接node.js时遇到了问题。
我下载了nodejs源代码。
git clone https://github.com/nodejs/node

并将node_main.cc中的主方法重命名。
sed -i .bak "s/int main(/int node_main(/g" ./src/node_main.cc

然后我将 Node.js 构建为静态库。
./configure --enable-static
make -j4

我有一个c++封装文件wrapper.cpp,通过extern c暴露node_main()方法。 :
#include <string>
#include <iostream>

using namespace std;

int node_main(int argc, char **argv);

extern "C" {
  void run_node() {
    cout << "hello there! general kenobi..." << endl;
    char *args[] = { (char*)"tester.js", NULL };
    node_main(1, args);
  }
}

此时,我已经能够成功构建 C++ 封装作为二进制文件,并在静态链接 node.js 库并成功从 C++ 运行 node.js。然而,从 Rust 中...
main.rs:
extern {
  fn run_node();
}

fn main() {
  println!("hey there this is RUST");
  unsafe { run_node(); }
}

build.rs:

extern crate cc;

fn main() {
  println!("cargo:rustc-link-search=native=../node/out/Release");

  println!("cargo:rustc-link-lib=static=node");
  println!("cargo:rustc-link-lib=static=uv");

  println!("cargo:rustc-link-lib=static=v8_base");
  println!("cargo:rustc-link-lib=static=v8_libbase");
  println!("cargo:rustc-link-lib=static=v8_snapshot");
  println!("cargo:rustc-link-lib=static=v8_libplatform");

  println!("cargo:rustc-link-lib=static=icuucx");
  println!("cargo:rustc-link-lib=static=icui18n");
  println!("cargo:rustc-link-lib=static=icudata");
  println!("cargo:rustc-link-lib=static=icustubdata");

  println!("cargo:rustc-link-lib=static=brotli");
  println!("cargo:rustc-link-lib=static=nghttp2");

  cc::Build::new()
    .cpp(true)
    .file("wrapper.cpp")
    .compile("libwrapper.a");
}

请注意,rustc-link-search 路径是相对于上级目录的。
当我运行 cargo build 时,会出现以下错误:
= note: Undefined symbols for architecture x86_64:
          "node_main(int, char**)", referenced from:
              _run_node in libwrapper.a(wrapper.o)
        ld: symbol(s) not found for architecture x86_64
        clang: error: linker command failed with exit code 1 (use -v to see invocation)

我不确定连接顺序是否重要,但我尝试了一些不同的排序组合,并没有成功。我还尝试从node/out/Release链接所有的.lib文件,但没有任何区别。我还尝试将node/out/Release中的所有.lib文件合并成一个.lib文件,但出现了重复符号错误。我尝试了不同版本的node.js(如v11.15.0),但没有任何区别。 我在MacOS 10.14.5上进行此操作。
$ rustc --version
rustc 1.36.0 (a53f9df32 2019-07-03)
$ cargo --version
cargo 1.36.0 (c4fcfb725 2019-05-15)
$ g++ --version
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Devel
oper/CommandLineTools/SDKs/MacOSX10.14.sdk/usr/include/c++/4.2.1
Apple LLVM version 10.0.1 (clang-1001.0.46.4)
Target: x86_64-apple-darwin18.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

Cargo.toml:

[build-dependencies]
cc = "1.0"

如果有好的想法,我愿意尝试更好的将node.js嵌入到Rust中。
1个回答

4
我发现了一些问题,解决这些问题可以成功地在Rust中静态链接node.js。 libnode.a输出中没有包含node_main.cc
我通过使用nm查找符号来发现这个问题。我无法找到node_main()node_main.o在符号列表中的位置。因此我意识到node_main.cc中的任何内容都不会被导出。
解决方法:在另一个文件中公开库入口C函数,例如node.cc。请注意,在此处我们正在添加一个完全新的函数,它调用node::Start()
extern "C" int node_main(int argc, char** argv) {
    return node::Start(argc, argv);
}

需要使用extern "C"因为c++会对函数名进行符号重载

再次使用nm工具搜索libnode.a文件中的所有符号,我发现node_main()函数的符号已被重载。为了在Rust中找到这个符号,它必须没有被重载,这可以通过使用extern "C"来实现。

解决方法:确保要暴露给Rust的函数前缀带有extern "C"

node.cc

extern "C" int node_main()

根据Node.js版本,可能会缺少一些附加符号

根据Node.js Github问题#27431,某些符号存根没有在静态库中输出。我正在使用Node.js v13.x.x标签进行工作,这是一个问题,因此我必须为这些额外的符号创建库,并将它们链接到Rust构建配置中。

解决方法:从存根创建静态库并在build.rs中链接它们。

ar rcs obj/Release/lib_stub_code_cache.a obj/Release/obj.target/cctest/src/node_code_cache_stub.o
ar rcs obj/Release/lib_stub_snapshot.a obj/Release/obj.target/cctest/src/node_snapshot_stub.o

最终结果

构建node.js

git clone https://github.com/nodejs/node
cd node
printf 'extern "C" int node_main(int argc, char** argv) { return node::Start(argc, argv); }' >> src/node.cc
./configure --enable-static
make -j4

# temporary fix: https://github.com/nodejs/node/issues/27431#issuecomment-487288275
REL=obj/Release
STUBS=$REL/obj.target/cctest/src
ar rcs "$REL/lib_stub_code_cache.a $STUBS/node_code_cache_stub.o"
ar rcs "$REL/lib_stub_snapshot.a $STUBS/node_snapshot_stub.o"

wrapper.cpp

#include <string>
#include <iostream>
using namespace std;

extern "C" {
  int node_main(int argc, char** argv);

  void run_node() {
    cout << "hello there! general kenobi...\n";
    char *args[] = { (char*)"tester.js", NULL };
    node_main(1, args);
  }
}

build.rs

extern crate cc;

fn main() {
  cc::Build::new()
    .cpp(true)
    .file("wrapper.cpp")
    .compile("libwrapper.a");

  println!("cargo:rustc-link-search=native=../node/out/Release");

  println!("cargo:rustc-link-lib=static=node");
  println!("cargo:rustc-link-lib=static=uv");

  // temporary fix - https://github.com/nodejs/node/issues/27431#issuecomment-487288275
  println!("cargo:rustc-link-lib=static=_stub_code_cache");
  println!("cargo:rustc-link-lib=static=_stub_snapshot");
  // end temporary fix

  println!("cargo:rustc-link-lib=static=v8_base_without_compiler");
  println!("cargo:rustc-link-lib=static=v8_compiler");
  println!("cargo:rustc-link-lib=static=v8_initializers");
  println!("cargo:rustc-link-lib=static=v8_libbase");
  println!("cargo:rustc-link-lib=static=v8_libplatform");
  println!("cargo:rustc-link-lib=static=v8_libsampler");
  println!("cargo:rustc-link-lib=static=v8_snapshot");

  println!("cargo:rustc-link-lib=static=icuucx");
  println!("cargo:rustc-link-lib=static=icui18n");
  println!("cargo:rustc-link-lib=static=icudata");
  println!("cargo:rustc-link-lib=static=icustubdata");

  println!("cargo:rustc-link-lib=static=zlib");
  println!("cargo:rustc-link-lib=static=brotli");
  println!("cargo:rustc-link-lib=static=cares");
  println!("cargo:rustc-link-lib=static=histogram");
  println!("cargo:rustc-link-lib=static=http_parser");
  println!("cargo:rustc-link-lib=static=llhttp");
  println!("cargo:rustc-link-lib=static=nghttp2");
  println!("cargo:rustc-link-lib=static=openssl");
}

main.rs

extern {
  fn run_node();
}

fn main() {
  println!("a surprise to be sure, but a welcome one");
  unsafe { run_node(); }
}

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