欢迎加入开源鸿蒙PC社区

欢迎加入开源鸿蒙PC社区:Harmony PC 开发者社区
欢迎在PC社区平台申请新建项目:OpenHarmony PC Developer - 开源代码托管,代码协作 - AtomGit
如有项目源码,可上传至 AtomGit 仓库,并在博文内附上仓库链接。

鸿蒙PC桌面端适配 libXext 1.3.7:用真实 ABI 消费者验证 X11 扩展库

为什么桌面端还需要 libXext

很多鸿蒙PC桌面端程序并不直接绘制窗口,却会通过 X11 兼容层或跨平台工具链间接使用 X 扩展协议。libXext 1.3.7 提供扩展注册、错误处理以及 XSync 值运算等公共接口,是一类典型的“底层小库”:源码规模不大,但头文件依赖、Libtool 元数据和受限文件系统会影响最终消费者。本文记录一个可复核的 Conan 配方适配过程,重点是让安装后的公共 ABI 在 HarmonyOS PC 上能被真正编译、链接和执行。

版本选择依据 X.Org 官方发布索引。1.3.7 是当时可核对到的最高稳定版本,官方归档 SHA-256 写入 conandata.yml。适配代码和配方放在 AtomGit 仓库 中;上游源码仍应从 X.Org 官方地址下载,代码托管品牌在本文和仓库链接中统一写作 AtomGit。

依赖与构建边界

libXext 采用 Autotools,直接依赖 libx11/1.8.13 和 xorgproto/2025.1,libxcb 等库由 libX11 的传递关系带入。第一步应该在隔离的 Conan home 中执行 --build=never 预检,确认两个依赖的 recipe、package ID 和 PREV 都能从目标远端下载。不能用 conan list 的空结果推断二进制不存在,也不能在依赖未发布时让下游静默回退到本地源码构建。

这个配方保留官方 release 源码,不直接修改 configure。HarmonyOS PC 的 HMDFS 可能拒绝 umask 077 创建的临时目录,因此构建阶段复制 configure 到任务私有目录,并在副本中做受计数约束的权限修复;原始文件哈希必须保持不变。config.guess 也通过任务私有 helper 调用编译器的 -dumpmachine,从结果得到 aarch64-unknown-linux-ohos,而不是把平台名称写成编译期宏。这样既能适应工具链变化,也避免给上游源码增加只针对某个系统名的分支。

另一个实际问题来自 Libtool。发布的 libX11.la 可能包含旧的 Conan 缓存绝对路径,直接链接会继续寻找已经不存在的 libxcb.la。适配流程在任务目录创建完整的 Libtool shadow:复制依赖闭包中的真实 .la 和归档,重写传递路径与 libdir,再把生成的 x11.pc 指向 shadow。原始依赖包只读不改,shadow 在构建结束后随任务根清理。这个步骤解决的是链接元数据,不是通过额外 -L 参数掩盖缺库。

公共头文件顺序很重要

libXext 的 extutil.h 会使用 Display、xEvent、xError 等 Xlib/Xproto 类型。上游内部文件往往先包含私有 Xlibint.h,而安装后的消费者不能依赖这个私有顺序。最终测试明确先包含 <X11/Xlib.h> 和 <X11/Xproto.h>,再包含扩展头,保证公共包可以独立使用。

下面的 C 程序先覆盖 XSync 的基础比较与加减,不需要连接 X server,适合在鸿蒙PC设备上验证头文件、链接器和运行时 ABI。完整包测试还会额外检查 32 位拆分、高低位、极值溢出,以及扩展注册和错误处理器往返;对应的两个源文件可在仓库 test_package/ 中查看。

#include <X11/Xlib.h>
#include <X11/extensions/sync.h>
#include <stdio.h>

int main(void) {
    XSyncValue one, two, result, negative;
    int overflow = -1;

    XSyncIntToValue(&one, 1);
    XSyncIntToValue(&two, 2);
    XSyncIntToValue(&negative, -1);
    if (!XSyncValueIsPositive(one) ||
        !XSyncValueGreaterThan(two, one) ||
        !XSyncValueIsNegative(negative)) return 1;

    XSyncValueAdd(&result, one, one, &overflow);
    if (overflow != 0 || !XSyncValueEqual(result, two)) return 2;

    overflow = -1;
    XSyncValueSubtract(&result, two, one, &overflow);
    if (overflow != 0 || !XSyncValueEqual(result, one)) return 3;

    puts("LIBXEXT_TEST sync_value_contract PASS");
    return 0;
}

另一个消费者检查扩展注册和错误处理器的生命周期:

#include <X11/Xlib.h>
#include <X11/Xproto.h>
#include <X11/extensions/Xext.h>
#include <X11/extensions/extutil.h>

#include <stdio.h>

static int test_handler(Display *display, const char *name, const char *reason)
{
    (void)display;
    (void)name;
    (void)reason;
    return 17;
}

int main(void)
{
    XExtensionInfo *extension = XextCreateExtension();
    XextErrorHandler previous;
    XextErrorHandler replaced;

    if (extension == NULL)
        return 1;
    XextDestroyExtension(extension);

    previous = XSetExtensionErrorHandler(test_handler);
    replaced = XSetExtensionErrorHandler(previous);
    if (replaced != test_handler)
        return 2;

    puts("LIBXEXT_TEST registry_contract PASS");
    return 0;
}

将两个程序分别保存为 sync_value_contract.c 和 registry_contract.c,再用 CMakeDeps 导出的目标编译:

cmake_minimum_required(VERSION 3.15)
project(libxext_consumer LANGUAGES C)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
find_package(libxext CONFIG REQUIRED)

add_executable(libxext_sync_value_contract sync_value_contract.c)
add_executable(libxext_registry_contract registry_contract.c)
target_link_libraries(libxext_sync_value_contract PRIVATE libxext::libxext)
target_link_libraries(libxext_registry_contract PRIVATE libxext::libxext)

同一目录还需要一个最小的 consumer conanfile.py:

from conan import ConanFile


class LibxextConsumer(ConanFile):
    settings = "os", "arch", "compiler", "build_type"
    generators = "CMakeDeps", "CMakeToolchain", "VirtualRunEnv"

    def requirements(self):
        self.requires("libxext/1.3.7")

在该目录执行以下命令,确认依赖闭包已发布后再配置:

OHOS_PROFILE=ohos-aarch64  # replace with an existing OHOS/AArch64 profile
conan install . -pr:h="$OHOS_PROFILE" -pr:b=default --output-folder=build --build=never
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake
cmake --build build --parallel 2

若直接使用 pkg-config --cflags --libs xext,PKG_CONFIG_PATH 必须指向当前包前缀,不能混入系统 X11。目标 ELF 在 HarmonyOS PC 上运行前还要完成签名,签名后的临时文件通过 section 检查确认恰好包含一个 .codesign,再原子替换原文件。

怎样解释测试结果

上游 1.3.7 release 没有注册 Automake TESTS、check_PROGRAMS 或 test() 条目,真实分母是 0/0;这不是“忘了测试”,而是上游清单的事实。适配包新增两个安装后消费者:libxext_sync_value_contract 覆盖 16 个公开的 XSync display-independent 操作,libxext_registry_contract 覆盖扩展注册销毁和错误处理器往返。每个进程只在所有断言完成后输出一个唯一 PASS 标记,因此分母稳定为 2/2,不把编译器日志行数当测试数。

历史记录中曾出现 HMDFS 临时目录权限、config.guess 平台分类、Libtool 旧路径和公共头缺失等连续失败。每次修复都建立新的验证事务,旧的失败结果保留为不可变历史,不能把不同候选树的 2/2 拼成当前 PR 的完整发布结论。当前归档状态是 recipe 和实质消费者已验证,完整 finish、平台 CI、Review 和提交仍需以当前候选的独立证据为准。

新手适配教程:环境、过程、结论与 FAQ

环境:小库也有完整的系统闭包

构建机需要 Conan 2、Autotools/CMake、Ninja、Python 和 HarmonyOS SDK,host profile 固定为 OHOS/AArch64。libXext 还要求精确匹配 libx11/1.8.13 与 xorgproto/2025.1;如果 Libtool .la 指向旧的 Conan 缓存,编译器即使找到头文件也会在链接阶段失败。目标设备需要签名工具和可写任务目录,消费者要从安装后的公共头和库开始编译,不能偷偷引用系统 X11。

过程:把依赖、头文件和元数据逐层排除

  1. 先执行依赖 --build=never 预检并核对源码摘要,确认 libX11 和 xorgproto 的目标包已发布。
  2. 运行 Autotools 配置,让 config.guess 通过编译器得到 aarch64-unknown-linux-ohos;遇到 HMDFS 临时目录问题时,只在任务私有副本处理权限。
  3. 生成 Libtool shadow,重写 .la 和 x11.pc 的传递路径,确保链接不依赖构建机缓存。
  4. 用先包含 Xlib/Xproto、再包含扩展头的消费者编译两个程序,分别验证 XSync 值运算和扩展注册/错误处理器生命周期。
  5. 签名后在鸿蒙PC运行两个消费者,记录两行 LIBXEXT_TEST ... PASS、上游 0/0 和 consumer 2/2,最后才绑定全屏桌面截图。

结论:高难度来自“公共 ABI 可独立消费”

libXext 源码规模小,但它把 Xlib 私有头顺序、Xproto 类型、Libtool 传递元数据、HMDFS 文件语义和 HarmonyOS ELF 签名集中到一个看似普通的底层库中。2/2 不是把上游 0/0 改写成有测试,而是新增两个安装后消费者来验证公共 ABI;没有 X server 也可以验证 display-independent 的 XSync 合同,但不能借此宣称 X11 图形会话已经打通。把这些边界写清楚,读者才能理解它为什么不是“几行配置就完成”的低难任务。

FAQ

Q:为什么上游是 0/0? A:1.3.7 release 没有注册 Automake 测试入口,这是清单事实,不应虚构上游测试。Q:没有 X server 能测试吗? A:可以测试不依赖显示连接的值运算和注册生命周期,但不能推导窗口显示能力。Q:为什么要做 Libtool shadow? A:它消除了 .la 对旧缓存绝对路径的依赖,单纯增加 -L 不能解决传递元数据错误。Q:pkg-config 找到 xext 就算通过吗? A:还要确认 PKG_CONFIG_PATH 指向当前包前缀,并由签名后的消费者实际运行。

运行截图

以下图片来自同一台 HUAWEI MateBook Pro(HAD-W32)的 HarmonyOS 6.1.0.117 图形会话。第一张先回读设备运行目录中的 libX11.so.6、libxcb.so.1、libXau.so.6 与 libXdmcp.so.6,证明消费者不是借用主机依赖:

libXext 1.3.7 在鸿蒙PC上的运行时依赖闭包

第二张单独执行同步值契约。终端显示 sync_value_contract PASS 和返回码 0:

libXext 1.3.7 在鸿蒙PC通过 XSync ABI 契约

第三张单独执行扩展注册表契约,覆盖另一组公开 ABI,并同样回读返回码 0:

libXext 1.3.7 在鸿蒙PC通过扩展注册表 ABI 契约

最后一张将两个消费者放在同一画面中汇总,并保留设置页、HiShell 和鸿蒙PC任务栏。

libXext 1.3.7 在鸿蒙PC真机完成两个公共 ABI 消费者

这组图依次证明依赖闭包、装载和两组真实调用,而不是只检查符号名称。sync_value_contract 覆盖 64 位同步值运算及溢出语义,registry_contract 覆盖扩展描述对象和错误处理器替换,因此能直接暴露头文件顺序、ABI 或依赖闭包错误。

结语

libXext 1.3.7 的鸿蒙PC桌面端适配说明了底层库的验证方法:先锁定官方源和依赖闭包,再处理受限文件系统与 Libtool 元数据,最后用安装后消费者检查公共 ABI。0/0 的上游测试分母、2/2 的消费者结果和后续发布门禁必须分栏记录。这样的报告既能帮助桌面应用接入 X11 兼容层,也能让后续维护者知道哪些结论已经在真机上成立,哪些仍需新的候选和平台证据。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐