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)
注释来尝试解决此问题,但无济于事。
希望对您有所帮助。
我正在尝试从 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)
注释来尝试解决此问题,但无济于事。
希望对您有所帮助。