代码之家  ›  专栏  ›  技术社区  ›  Jerry Krinock

macOS:根据可用性在运行时加载一个或其他系统框架

  •  7
  • Jerry Krinock  · 技术社区  · 8 年前

    我正在开发一个使用苹果的macOS工具 狩猎 框架在macOS 10.13中运行时,该工具链接到并从中加载

    /System/Library/PrivateFrameworks/Safari.framework
    

    一切都很好。但在macOS 10.12.6中运行时,缺少一些行为。基于对DTrace的一些探索,我认为这是因为我的工具需要加载最新的 分阶段的 框架,如下所示:

    /System/Library/StagedFrameworks/Safari/Safari.framework
    

    这显然是Safari所做的,因为如果我用lldb连接Safari并运行 image list ,在10.13中,该列表仅包括前一条路径,在10.12.6中仅包括后一条路径。

    我尝试了以下方法:

    NSBundle* stagedBundle = [NSBundle bundleWithPath:@"/System/Library/StagedFrameworks/Safari/Safari.framework"];
    

    在10.13中返回nil,因为此时没有这样的目录。然而,在10.12.6中,我得到 stagedBundle ,然后:

    NSBundle* privateBundle = [NSBundle bundleForClass:[BookmarksController class]];
    [privateBundle unload];
    [stagedBundle load];
    

    卸载和装载显然有效,因为如果我记录 -description 在这两个包中,在运行该代码之前 私有的 捆绑是 (已加载) 以及 分阶段的 捆绑是 (尚未加载) ,但在运行该代码后,这些状态将根据需要进行交换。

    但这并不有效。(1) 如果我再次调用 -bundleForClass: ,通过一个已知位于两个框架中的类,它为我提供了 私有的 捆(2) 如果我调用 -respondsToSelector: ,传递一个已知仅存在于 分阶段的 框架,我不知道。

    我试着打电话 _CFBundleFlushBundleCaches() ,如建议的 here ,但这没有帮助。

    我也试着改变我的目标 FRAMEWORK_SEARCH_PATHS ,并安装 分阶段的 我的Mac上的框架并链接到它,但由于这篇文章已经太长了,我只想说这导致了更多的热量而不是光。

    在这种情况下,如何有选择地加载框架?

    更新

    我尝试了另一种方法。重新阅读苹果的 Framework Programming Guide ,尽管它看起来确实过时了,但我决定这个框架需要 弱连接 。这样做:

    • 在代码中,删除了这些NSBundle -load -unload 呼叫
    • 在我的工具目标中,
      • 在里面 构建阶段 &燃气轮机; 将二进制文件链接到库 ,删除了Safari的路径 私有的 框架,因为这是一个强大的链接。
      • 在里面 生成设置 &燃气轮机; 其他链接器标志 已添加 -weak_framework Safari
      • 在里面 生成设置 &燃气轮机; 框架搜索路径 ,列出了两个框架的父目录的路径 分阶段的 前的路径 私有的 路径,因为我希望这一个加载到macOS 10.12.6中,两者都存在。

    对我来说,在10.13和10.12.6中构建和运行都很有意义,但显然 仍在加载不需要的 私有的 10.12.6中的框架 。NSLog报告,作为捆绑包的路径,并且类不响应已知位于中的选择器 分阶段的 仅限框架。

    还有其他想法吗?

    1 回复  |  直到 8 年前
        1
  •  7
  •   bdash    8 年前

    首先,免责声明:我强烈建议您不要依赖于在交付给用户的任何应用程序中加载私有框架。它很脆弱,没有支撑。

    也就是说,如果您真的想这样做,我的建议是使用Safari本身用于在两个框架副本之间进行选择的相同技术,即 dyld DYLD_VERSIONED_FRAMEWORK_PATH 环境变量。

    引用 dyld man page :

    这是一个以冒号分隔的目录列表,其中包含潜在的覆盖框架。动态链接器在这些目录中搜索框架。对于找到的每个框架,dyld查看其 LC_ID_DYLIB 并获得 current_version 和安装名称。Dyld然后在安装名称路径中查找框架。以较大者为准 当前\u版本 每当需要具有该安装名称的框架时,将在该过程中使用值。这类似于 DYLD_FRAMEWORK_PATH 除了不总是重写之外,它只重写所提供的框架较新。注意:dyld不检查框架的信息。plist查找其版本。Dyld仅检查 -current_version 创建框架时提供的编号。

    简而言之,这会导致 dyld公司 在加载的框架和版本化框架路径中的框架之间执行版本检查,并加载更高版本。如果版本化的框架路径不存在或其中不存在有问题的框架,则将使用原始框架路径。

    Safari利用第二个 dyld公司 简化其使用的功能 DYLD\u VERSIONED\u FRAMEWORK\u路径 这个 LC_DYLD_ENVIRONMENT load命令。此load命令允许 DYLD_* 要在链接时指定的环境变量将由应用 dyld公司 在尝试加载任何依赖库之前的运行时。如果没有这个技巧,你需要 DYLD\u VERSIONED\u FRAMEWORK\u路径 作为启动应用程序之前的环境变量,这通常需要繁琐的重新执行才能实现。

    将这两个构建块放在一起,最终会添加如下配置设置:

    OTHER_LDFLAGS = -Wl,-dyld_env -Wl,DYLD_VERSIONED_FRAMEWORK_PATH=/System/Library/StagedFrameworks/Safari;
    

    然后可以静态链接 /S/L/PrivateFrameworks/Safari.framework ,或尝试在运行时动态加载它。这两种方法都会导致在运行时加载适当的框架。


    为了解决您的问题所揭示的一些误解:

    卸载和加载显然是有效的,因为如果我记录这两个包的描述,在运行该代码之前,私有包(已加载)和暂存包(尚未加载),但在运行该代码之后,这些状态会根据需要进行交换。

    不支持卸载包含Objective-C代码的共享库。我怀疑 只有 它所做的事情是在 NSBundle 实例,自 dyld公司 的级别将被忽略。

    内置设置(>);框架搜索路径,列出了两个框架的父目录的路径,分段路径在私有路径之前,因为我想在macOS 10.12.6中加载这个路径,两者都存在。

    框架搜索路径是一个仅在编译时使用的概念。在运行时,库的 安装名称 是什么告诉了我们 dyld公司 查找要加载的二进制文件的位置。