我需要帮助,我正在尝试调用Swift 5函数中的C函数。
我有一个无法解决的错误:
无法使用'evolis_get_devices':瓦里迪克
功能不可用
你知道我该怎么解决吗?
C函数:
/// Get device list. Takes a list of model ended by zero.
EVOLIS_LIB int evolis_get_devices(evolis_device_t** devices, ...);
SWIFT功能:
public static func getDevices() -> [Device] {
var devices: UnsafeMutablePointer<evolis_device_t>?
var count: Int32 = 0
evolis_get_devices(&devices, count) // <- 'evolis_get_devices' is unavailable: Variadic
var devicesList: [Device] = []
if count > 0 {
for i in 0...count - 1 {
let device = Device(device_c: devices![Int(i)])
devicesList.append(device)
}
}
evolis_free_devices(devices)
return devicesList
}
1条答案
按热度按时间w46czmvw1#
我在评论中链接的Apple doc特别指出:
Swift只导入使用va_list作为参数的C变量函数,不导入使用...语法作为变量参数的C函数,因此不能使用CVarArg参数调用。
因此,您会看到以下错误:
无法使用'evolis_get_devices':瓦里迪克
理想情况下,您的C函数应该具有
va_list
等效项,类似于:具有匹配的:
但不能保证它确实存在,即使在纯C语言中,一个变量函数也不能通过“传递”参数来调用另一个变量函数,参见问题here。
让我们考虑一下可能的解决方法:
1.*C Package 函数 *,带有预定义数量的参数,用于调用变量函数。这些参数可以暴露给Swift。我们可以有多个带有不同数量参数的定义,但从长远来看,这是不可维护的,并且会用冗余条目污染Swift符号空间。
1.使用Swift的
convention(c)
unsafeBitCast
调用带有任意数量参数的C。就像我的答案here和here一样。本质上,这将问题转移到Swift,我们仍然只能有预定义数量的参数,不能再多一个。大致看起来如下:1.* 假设 * 我们可以得到一个 *C函数 Package 器 *,其中包含
va_list
参数,如NSLogv
中的参数,该参数将调用变元真函数。让我们考虑一下
va_list
实际上是什么,它是一个指向动态集合的指针,原始参数存储在该集合中,问题是,每个参数的确切位置信息并没有存储在va_list
中,编译器有责任在调用变量函数时遵守cpu平台和操作系统的ABI规则。但是还有更多的挑战,C语言没有假设如何处理这些参数,甚至没有假设何时停止处理。为了说明问题,让我们比较一下C函数的签名:对比
printf
使用格式字符串来知道在哪里停止,但是根据文档,你的函数需要一个终止的NULL
参数。仅仅通过查看va_list
,没有办法知道这一点,信息不存在。只有基于规范,人们才能假设函数如何工作以及如何使用,但是C语言本身没有办法弄清楚这一点。这给我们留下了一个重要的结论--我们的 *C函数 Package 器 *(带有
va_list
)需要做这个假设。让我们再退一步来分析变量函数被调用时究竟发生了什么。参数处理实际上是如何工作的?为了我们的分析,我想出了一个函数来模仿你的(有点),我不知道
evolis_device_t**
到底是什么,所以我选择了void**
。所以变量参数
void** devices, ...
通过va_start(pargs, devices)
的方式变成了va_list
。我们实际上不需要详细说明它是如何工作的。在我的测试函数中,我打印出到达的指针变量,返回值是到达的参数总数。我想象你的真实的函数实际上可能会返回一些状态代码作为int
。然而,我们的测试样本主要是关于有一种方法来检查什么是去。过去,在栈上传递参数是在可用硬件上的标准方式。古老的C语言根本没有指定参数签名,作为一个副作用,参数的数量也没有限制。由于每个编译器都可以这样做,它就一直存在,后来成为一种语言特性。
现在,现代的CPU都配备了硬件寄存器,相比之下,堆栈操作不再那么快了。这就是现代ABI定义混合方法的原因--从寄存器开始,当我们用完硬件寄存器时,使用推到堆栈上的参数。
CPU之间明显不同,例如,Intel与Arm 64相比有不同的寄存器集和指令。但这些细微差别并不止于此,甚至操作系统偶尔也会引入自己的规则。
这让我们得出另一个非常重要的结论--无论我们做什么可移植性都会受到影响,因为我们的目标是一些连C语言自己都无法表达的东西。因此,它伴随着硬件和操作系统特定的内联 * 汇编 *。
我按照stackoverflow的标准写了这篇冗长的介绍,因为我想让你清楚地知道我在说什么。下面是我用
va_list
或int evolis_get_devices(void** devices, ...)
写的一个C函数 Package 器。我做了以下假设:NULL
,例如evolis_get_devices(ptr, NULL)
void**
作为arg类型,因为C中所有指针的处理完全相同,所以evolis_device_t**
与void**
没有区别这个C代码是XCode兼容的。这是Swift的桥接头最终需要的
wrapper.h
。这就是我在
wrapper.c
中的int evolis_get_devices_using_va_list(va_list args)
函数:这是我在Swift中调用的测试代码:
当然,也可以使用Swift可变参数函数:
在Swift代码中,注意最后一个参数
NULL
是通过看起来很奇怪的Int(0)
传递的。