Linux WPS FFI.LoadLibrary调用libc.so.6库,错误:未找到库解决方法

官方文档说在WPS 12.1.0.23542 及以后版本加入了jside ffi,如果在linux系统wps中直接用以下代码调用ffi加载libc.so.6,有可能会得到错误:错误:未找到库如下图

function test(){ const libc = ffi.LoadLibrary("libc.so.6", { "access": { "returnType": "int32", "parameters": ["string", "int32"] }}); }

根据官方文档https://open.wps.cn/documents/dynamic.html?link=/app-integration-dev/wps365/client/wpsoffice/wps-integration-mode/wps-macro-editor-development/run-macro/jside-ffi.html

【金山文档 | WPS云文档】 jside ffi 使用文档

https://www.kdocs.cn/l/chPoECaMqzhw

动态库查找

动态库名可以是绝对路径,也可以是文件名。如果使用文件名, wps按照各操作系统的路径查找规则查早动态库,获取绝对路径。

文件名会被自动加上当前系统的动态库文件后缀。(目前看很有可能没有自动加后缀,或仅当你没有加后缀时它才自己加后缀,自己用程序判断下)

  • linux系统:.so

如果动态库不是绝对路径,wps会按照以下顺序查找动态库文件:

  • linux系统:

  1. LD_LIBRARY_PATH环境变量中包含的目录

  1. /lib

  1. /usr/lib

如果你的Linux发行版libc.so.6没有在/lib/或/usr/lib/中,wps默认路径找不到,当然就会报错(比如Ubuntu和银河麒麟默认在/usr/lib/{$arch}-linux-gnu/中,且具体位置与CPU架构有关),没有在默认搜索路径中,当然找不到。解决方案有几个,分别如下:

  1. 手动创建有效个libc.so.6的软链接(快捷方式)到/lib/ 或/usr/lib/中(注意CPU架构和Linux发行版不同,libc.so.6的位置不同),如果WPS不支持.so.6等数字结尾的库,你可以创建链接时不加.6。我创建原文件名链接时示例有:

sudo ln -s /usr/lib/aarch64-linux-gnu/libc.so.6 /usr/lib/libc.so.6

注意:如果你链接时没用.6,你用ffi.LoadLibrary调用时,也不要加。

  1. 直接在ffi.LoadLibrary中使用绝对路径(当然兼容性就差了很多)

function test(){ const libc = ffi.LoadLibrary("/usr/lib/aarch64-linux-gnu/libc.so.6", { "access": { "returnType": "int32", "parameters": ["string", "int32"] }}); }

  1. 用穷举尝试法。当ffi.LoadLibrary加载libc.so.6不出错时,说明就是正确的libc.so.6。世面上常见的发行版就那几个,常见的CPU架构更少了,套路就是穷举路径,只要不报错,就找到了正确的绝对路径。兼容性当然好了。示例代码如下

function getLibcPath() {
    // 检查缓存
    if (getLibcPath._cache !== undefined) {
        return getLibcPath._cache;
    }

    const loadLib = (path) => {
        if (typeof ffi === "undefined") throw new Error("FFI不可用");
        if (typeof ffi.load === "function") return ffi.load(path);
        if (typeof ffi.LoadLibrary === "function") return ffi.LoadLibrary(path);
        throw new Error("无法加载动态库");
    };

    const candidates = [
        "/lib/libc.so.6",
        "/lib64/libc.so.6",
        "/usr/lib/libc.so.6",
        "/usr/lib/x86_64-linux-gnu/libc.so.6",
        "/lib/x86_64-linux-gnu/libc.so.6",
        "/usr/lib/i386-linux-gnu/libc.so.6",
        "/lib/i386-linux-gnu/libc.so.6",
        "/usr/lib/aarch64-linux-gnu/libc.so.6",
        "/lib/aarch64-linux-gnu/libc.so.6",
        "/usr/lib/arm-linux-gnueabihf/libc.so.6",
        "/lib/arm-linux-gnueabihf/libc.so.6",
        "/usr/lib/loongarch64-linux-gnu/libc.so.6",
        "/lib/loongarch64-linux-gnu/libc.so.6",
        "/usr/lib64/libc.so.6",
        "/usr/local/lib/libc.so.6",
        "/lib/ld-musl-x86_64.so.1",
        "/lib/ld-musl-i386.so.1",
        "/lib/ld-musl-aarch64.so.1",
        "/lib/ld-musl-armv7l.so.1",
        "/lib/ld-musl-armhf.so.1"
    ];

    for (let i = 0; i < candidates.length; i++) {
        const libPath = candidates[i];
        try {
            const lib = loadLib(libPath);
            if (lib) {
                getLibcPath._cache = libPath; 
                return libPath;
            }
        } catch (e) {
            continue;
        }
    }

    getLibcPath._cache = null;
    return null;
}
function isFile(path) {
    if (!path) return false;
    try {
        var attrs = GetAttr(path);    
        if (attrs === 0xFFFFFFFF || attrs ===-1) return false; 
        return (attrs & 16) === 0;           // 不是目录(日常视为文件)    
    } catch (e) {
        return false;
    }
}

// 调用示例
let libcPath = getLibcPath();

// 自动获取失败则弹窗让用户
if (!libcPath) {
    libcPath = Application.InputBox(
        "请输入 libc.so.6 或 ld-musl-*.so.1 文件的完整路径:\n" +
        "Debian/Ubuntu: /usr/lib/x86_64-linux-gnu/libc.so.6\n" +
        "Fedora/RHEL/CentOS: /lib64/libc.so.6\n" +
        "Arch Linux: /usr/lib/libc.so.6\n" +
        "Alpine Linux: /lib/ld-musl-x86_64.so.1\n" +
        "其他发行版可能位于 /lib 或 /usr/lib",
        "请输入 libc.so.6 路径",
        "/usr/lib/x86_64-linux-gnu/libc.so.6"
    );

    if (!libcPath) return false;      // 用户取消
    if (!isFile(libcPath)) return false; // 路径不是文件
}

最后还有一个解决方案:让用户自己想办法。比如你的用户很专业,你可以给他弹个InputBox窗或Applicaiton.FileDialog,让它自己输入或选择正确的libc.so.6,这种方法听着就不专业。

最后还有个想法:用ExecuteExcel4Macro()调用libc.so.6时不需要路径,就能直接成功,那能不能调用libc.so.6中的system等函数来获取libc.so.6的绝对路径呢,这个技术路径太复杂,不适合我这种小白,我也写不出来。或者在将来支持ffi.load了呢?

或许还有一种解决方案:

ExecuteExcel4Macro(`CALL("libc.so.6", "system", "JC", "ldd /bin/bash | grep 'libc.so.6' | cut -d ' ' 3 > /tmp/libc_path.txt")`);然后再用FileSystem读取这个文件。

function test() { var str = 'echo "export LD_LIBRARY_PATH=$(dirname $(ldd /bin/bash | awk \'/libc.so.6/{print $3}\'))${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" >> ~/.bashrc'; var cmd = 'CALL("libc.so.6","system","JC",' + macroStringParam(str) + ')'; ExecuteExcel4Macro(cmd); const libc = ffi.LoadLibrary("/lib/aarch64-linux-gnu/libc.so.6", { "getenv": { "returnType": "string", "parameters": ["string"] }}); let path=libc.getenv("LD_LIBRARY_PATH"); Debug.Print(path);}// 将字符串包装为ExecuteExcel4Macro()宏的字符串参数 ("...")function macroStringParam(s) { return '"' + xlEscape(s) + '"';}// ExecuteExcel4Macro()宏参数字符串转义:双引号需加倍function xlEscape(str) { if (!str) return ""; return str.replace(/"/g, '""');}

加入环境变量竟然也不行

浏览 166
2
12
分享
12 +1
1
2 +1
全部评论 1
 
user_19042982
不错
·
1
回复