EXC_BAD_ACCESS 从 C 调用 Swift 闭包时出现异常

EXC_BAD_ACCESS exception when calling a Swift closure from C

我正在尝试从 C 调用 Swift 闭包。

以下代码段将代表我当前正在处理的内容。

首先,在Swift中,我初始化了一个静态常量,即应该稍后调用的闭包。

然后将此闭包传递给存储块指针的 C 函数 (api_set_callback_block)。

之后的某个时间,C 函数 api_trigger_block 被调用。这个函数应该调用 Swift 闭包。而不是这样做,它总是抛出一个运行时错误:EXC_BAD_ACCESS 试图访问 cb_block_cb() 时(另见下文)。

通常,这应该意味着尝试访问先前存储的变量的某些内容已被释放。但是,我不明白这是怎么回事,因为我传递的是静态常量。

我在访问时仔细检查了 cb_block_cb 是否为 NULL。

void (^cb_block_cb)(int, int) = NULL;
void api_set_callback_block(void (^cb_block)(int, int))
{
    if (cb_block == NULL)
    {
        puts("error: when setting callback block: cb_block is null");
        return;
    }
    cb_block_cb = cb_block;
}

void api_trigger_block()
{
    if (cb_block_cb == NULL)
    {
        puts("error: when triggering callback block: cb_block_cb is null");
        return;
    }
    cb_block_cb(3,3); // <-- This is where the exception gets thrown
}
class CustomClass: NSObject {
    public static let callback: (Int32, Int32) -> Swift.Void = { (cid, aid) in
        print("Callback block called!")
    }
}

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        api_set_callback_block(CustomClass.callback)

        DispatchQueue.main.asyncAfter(deadline: .now() + 7) {
            api_trigger_block()
        }
        return true
    }

    // ...
}

感谢您提出有趣的问题。

我看到了几个选项。

选项 1:

如果您可以控制 C API,只需将扩展名从 .c 更改为 .m 即可使其(部分)变为 Objective-C。然后你的 Swift 代码应该可以很好地与它互操作。至少它直接与 Swift 代码交互的部分应该做成 Objective-C。 Objective-C 部分有望与其余 C 部分(.c 文件中的部分)互操作。

选项 2:

围绕 C API 编写一个 Objective-C 包装器(在 .m 文件中)并在 Swift 代码中使用该包装器。基于您的示例的示例包装器:

// This is where we store the block passed in from Swift
void (^cb_block_cb2)(int, int) = NULL;

// This wrapper will be used in the block passed to C API
void block_wrapper(int i1, int i2) {
    cb_block_cb2(i1, i2);
}

// Obj-C wrapper around C API block setter
// Make this available to Swift, e.g. via bridging header.
void api_set_callback_block2(void (^cb_block)(int, int))
{
    if (cb_block == NULL)
    {
        puts("error: when setting callback block: cb_block is null");
        return;
    }
    cb_block_cb2 = cb_block;
    api_set_callback_block(^(int a, int b){ block_wrapper(a, b);});
}

// Obj-C wrapper around C API block trigger
// Make this available to Swift, e.g. via bridging header.
void api_trigger_block2()
{
    puts("Entered api_trigger_block2()");
    if (cb_block_cb2 == NULL)
    {
        puts("error: when triggering callback block: cb_block_cb2 is null");
        return;
    }
    api_trigger_block();
    puts("Returned from callback in api_trigger_block2()!!!!");
}

从概念上讲,上述选项是相似的。您也许可以按照相同的思路想出一些其他选项。在这一点上,我无法就上述工作原理给出一个很好的技术解释。据我从实验中得知, Swift functions/closures 不能总是传递给用 C 编译器编译并采用兼容块的 C 函数。但是,如果将相同的函数编译为 Objective-C,则它可以正常工作。 Objective-C 文件中定义的块可以很好地传递给 C 代码。

郑重声明,我还尝试在 Swift 代码中使用 @objc@convention(c) 注释来尝试解决此问题,但无济于事。

希望对您有所帮助。