如何确定已编译本机模块的 ABI 版本(和其他详细信息)?

How can I determine the ABI version (and other details) of a compiled native module?

我正在合作开发 VSCode 扩展,其中使用了本机模块 (serialport)。 为了使解决方案 运行 在所有平台上稳定,并且随着时间的推移 VScode 更改电子版本,我想包括预构建本机模块。

现在看来,这些预编译模块中的一些(不是全部)并不是他们声称的版本。

为了测试/验证,我想以编程方式确定 ABI 版本,如果可能的话,运行时间、平台(darwin、linux、win32)和每个本机模块的体系结构通过 install-prebuild 下载

即当我尝试在 electron 5.0.10 (ABI-70) 中加载模块时抛出以下错误:

Uncaught Error: The module '\?\C:\develop\NodeJS\electron-serialport\node_modules\@serialport\bindings\lib\binding\node-v70-win32-x64\bindings.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 47. This version of Node.js requires
NODE_MODULE_VERSION 70. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).

因此它在通过预构建安装下载时报告 ABI 47 为 ABI 70。 注意:将电子绑定存储在节点绑定位置以允许 'bindings' 检测 ABI、平台和体系结构

## npx prebuild-install 
Download prebuild native binding for runtime electron : 5.0.5, abi: 70, win32, x64
prebuild-install info begin Prebuild-install version 5.3.0
prebuild-install info looking for cached prebuild @ C:\Users\josverl\AppData\Roaming\npm-cache\_prebuilds\bde028-bindings-v2.0.8-electron-v70-win32-x64.tar.gz
prebuild-install info found cached prebuild
prebuild-install info unpacking @ C:\Users\josverl\AppData\Roaming\npm-cache\_prebuilds\bde028-bindings-v2.0.8-electron-v70-win32-x64.tar.gz
prebuild-install info unpack resolved to C:\develop\NodeJS\electron-serialport\node_modules\@serialport\bindings\build\Release\bindings.node
prebuild-install info install Successfully installed prebuilt binary!

Copy to : 
 -> C:\develop\NodeJS\electron-serialport\noded_modules\@serialport\bindings\lib\binding\node-v70-win32-x64\bindings.node

process.versions.modules 应该为给定节点或电子运行时提供正确的 ABI。

您自己的解决方案是在不添加本机节点模块的情况下执行此操作的唯一方法,并且 parsing/reading .node 文件不会直接以字符串或整数值的形式为您提供信息 (用十六进制编辑器检查).

可能的原因是:

  • 你应该确保模块的安装过程不会触发你的本地模块的构建,例如:运行 npm install 没有 --ignore-scripts.,因为这使用你本地安装的如果包含的包具有自定义安装脚本,则节点版本重建任何依赖项。
  • 您的预构建被其他安装步骤忽略
  • 程序包上传了错误的 .node 预构建文件。

找到了一个不错但部分的解决方案,因为我还没有找到 API 或真正的跨平台解决方案。

我能得到的最接近的方法是直接使用 process.dlopen 但这仍然只会成功或引发错误。 底层 loader 确实有信息 mp->nm_version,但它只是通过错误报告。

所以到目前为止找到的唯一方法是解析该错误消息。限制是这只能在与当前运行时相同的平台和 CPU 架构上工作,但总比没有好。

下面将通过捕获错误消息并从中提取 ABI 来提取本机模块的 ABI 版本。

// try to determine the ABI version for a native module
const getNativeABI = (filename) => {
    var moduleVersion = 0
    try {
        var test = new Module(filename, null);
        process.dlopen(module, filename) //,os.constants.dlopen.RTLD_NOW);
        // if this works the node version is the same 
        moduleVersion = process.versions['modules']
        // but now we need to unload it :-( 
        return moduleVersion
    } catch (error) {
        var match
        var versionRegexp = /NODE_MODULE_VERSION (\d*)./gm
        var platformRegexp = /(is not a valid Win32 application|invalid ELF header|wrong ELF class)/g
        // check for ABI version mismatch 
            // Uncaught Error: The module '..\bindings.node'
            // was compiled against a different Node.js version using
            // NODE_MODULE_VERSION 47. This version of Node.js requires
            // NODE_MODULE_VERSION 70. Please try re-compiling or re-installing
        match = versionRegexp.exec(error.message)
        if (match != null){
            return match[1] // first version is that of the module 
        } 
        // not for valid on this win32 / linux 
        match = platformRegexp.exec(error.message)
        if (match != null){
            // todo: @linux : use error for elfclass to determine architecture :: wrong ELF class: ELFCLASS32
            return 0 // can't validate cross platform
        } 
        // other error 
        console.debug( error.message)
    }
    return moduleVersion // just in case
}

您需要传入一个虚拟模块结构。

/// dummy copy of  internal function
function Module(id, parent) {
    this.id = id;
    this.exports = {};
    this.parent = parent;
    // updateChildren(parent, this, false);
    this.filename = null;
    this.loaded = false;
    this.children = [];
 }