是斯威夫特';如何处理绳车的CVarArg? 在编写一个C++库的C包装器的快速包装时,我偶然发现了Swift的代码> CVARARG 的一些奇怪错误。我已经拥有的C包装器使用可变函数,我使用va_list将其转换为函数作为参数,以便可以导入它们(因为Swift无法导入C可变函数)。向此类函数传递参数时,一旦桥接到Swift,它将使用符合CVarArg类型的私有\u cvarergencode属性对值进行“编码”,然后将这些值作为指向C函数的指针发送。然而,对于SwiftStrings,这种编码似乎是错误的

是斯威夫特';如何处理绳车的CVarArg? 在编写一个C++库的C包装器的快速包装时,我偶然发现了Swift的代码> CVARARG 的一些奇怪错误。我已经拥有的C包装器使用可变函数,我使用va_list将其转换为函数作为参数,以便可以导入它们(因为Swift无法导入C可变函数)。向此类函数传递参数时,一旦桥接到Swift,它将使用符合CVarArg类型的私有\u cvarergencode属性对值进行“编码”,然后将这些值作为指向C函数的指针发送。然而,对于SwiftStrings,这种编码似乎是错误的,swift,interop,variadic-functions,Swift,Interop,Variadic Functions,为了演示,我创建了以下包: 包裹,斯威夫特 //swift工具版本:5.2 导入包说明 让包=包( 名称:“CVarArgTest”, 产品:[ .可执行文件( 名称:“CVarArgTest”, 目标:[“CVarArgTest”]), ], 目标:[ .目标( 名称:“CLib”), .目标( 名称:“CVarArgTest”, 依赖项:[“CLib”]) ] ) 克里布 CTest.h \ifndef CTest\u h #定义CTest_h #包括 ///打印出args中提供的字符串

为了演示,我创建了以下包:

包裹,斯威夫特
//swift工具版本:5.2
导入包说明
让包=包(
名称:“CVarArgTest”,
产品:[
.可执行文件(
名称:“CVarArgTest”,
目标:[“CVarArgTest”]),
],
目标:[
.目标(
名称:“CLib”),
.目标(
名称:“CVarArgTest”,
依赖项:[“CLib”])
]
)
克里布 CTest.h
\ifndef CTest\u h
#定义CTest_h
#包括
///打印出args中提供的字符串
///@param num`args中的字符串数`
///@param args一个字符串的“va_列表”
无效测试参数(整数,变量列表参数);
///打印出args中提供的整数
///@param num`args中的整数数`
///@param args一个整数的“va_列表”
无效测试参数(int num,va_list args);
///只是打印字符串
///@param str字符串
无效测试打印(常量字符*str);
#endif/*CTest_h*/
c测试
#包括“CTest.h”
#包括
无效测试参数(整数,变量列表参数)
{
printf(“打印%i个字符串…\n”,num);
for(int i=0;i
梅因·斯威夫特 <代码>导入基础 导入CLib //文本字符串完美地桥接到函数所期望的CChar指针 测试打印(“你好,世界!”) //按预期打印整数 让argsInt:[CVarArg]=[123456789] withVaList(argsInt){listPtr in test_va_arg_int(Int32(argsInt.count),listPtr) } //错误:线程1:EXC\U错误\U访问(代码=EXC\U I386\U GPFLT) 让argsStr:[CVarArg]=[“测试”、“测试”、“测试”] withVaList(argsStr){listPtr in test_va_arg_str(Int32(argsStr.count),listPtr) } 套餐也有

如上面代码中所述,通过C或包含
Int
s的
va_列表打印
字符串
的工作原理与预期相同,但当转换为
const char*
时,会出现异常(
EXC_BAD_ACCESS(code=EXC_I386_GPFLT)


简言之,是我弄糟了它的C面,还是斯威夫特在这里做错了什么?我已经在Xcode 11.5和12.0b2中对此进行了测试。如果它是一个bug,我很乐意报告它。

这一个有点棘手:您的字符串实际上被桥接到一个Objective-C
NSString*
而不是一个C
char*

(lldb) p str
(const char *) $0 = 0x3cbe9f4c5d32b745 ""
(lldb) p (id)str
(NSTaggedPointerString *) $1 = 0x3cbe9f4c5d32b745 @"Test"
(如果您想知道为什么它是
NSTaggedPointerString
而不仅仅是
NSString
,那么这是一个很好的读取方法——简而言之,字符串足够短,可以直接存储在指针变量的字节中,而不是堆上的对象中

查看,我们看到类型的
va_列表
表示由其协议的
\u cvargencode
属性的实现决定。标准库中有此协议的一些实现,但这里没有
String
的实现。那么谁在将我们的字符串转换为
NSString
?

搜索GitHub上的Swift回购,我们发现:

通俗易懂:任何可以桥接到Objective-C的对象都被编码为vararg,方法是将其转换为Objective-C对象并对指向该对象的指针进行编码。C vararg不是类型安全的,因此您的
test_va_arg_str
仅假设它是
char*
并将其传递给
put
,然后崩溃

那么这是一个bug吗?我不这么认为——我想这种行为可能是为了与比C更常用于Objective-C对象的函数兼容。然而,这确实是一个令人惊讶的陷阱,这可能是Swift不喜欢让你调用C变量函数的原因之一


您可能希望通过手动将字符串转换为C字符串来解决此问题。如果您有一个字符串数组,并且希望在不制作不必要的副本的情况下进行转换,那么这可能会有点难看,但这里有一个函数应该能够做到这一点

extension Collection where Element == String {
    /// Converts an array of strings to an array of C strings, without copying.
    func withCStrings<R>(_ body: ([UnsafePointer<CChar>]) throws -> R) rethrows -> R {
        return try withCStrings(head: [], body: body)
    }
    
    // Recursively call withCString on each of the strings.
    private func withCStrings<R>(head: [UnsafePointer<CChar>],
                                 body: ([UnsafePointer<CChar>]) throws -> R) rethrows -> R {
        if let next = self.first {
            // Get a C string, add it to the result array, and recurse on the remainder of the collection
            return try next.withCString { cString in
                var head = head
                head.append(cString)
                return try dropFirst().withCStrings(head: head, body: body)
            }
        } else {
            // Base case: no more strings; call the body closure with the array we've built
            return try body(head)
        }
    }
}

func withVaListOfCStrings<R>(_ args: [String], body: (CVaListPointer) -> R) -> R {
    return args.withCStrings { cStrings in
        withVaList(cStrings, body)
    }
}

let argsStr: [String] = ["Test", "Testing", "The test"]
withVaListOfCStrings(argsStr) { listPtr in
    test_va_arg_str(Int32(argsStr.count), listPtr)
}

// Output:
// Printing 3 strings...
// Test
// Testing
// The test
扩展集合,其中元素==字符串{
///将字符串数组转换为C字符串数组,无需复制。
带有cstring(uBody:([UnsafePointer])的func抛出->R)再抛出->R{
使用CStrings返回try(头:[],体:体)
}
//对每个字符串递归调用withCString。
带CString的私有函数(头:[未安全指针],
正文:([UnsafePointer])抛出->R)再抛出->R{
如果let next=self.first{
//获取一个C字符串,将其添加到结果数组中,然后在集合的其余部分上递归
返回try next.withCString{cString in
可变水头=水头
head.append(cString)
返回try dropFirst()。使用CString(头:头,体:体)
}
}否则{
//基本情况:没有更多字符串;使用我们构建的数组调用body闭包
返回尝试b
extension Collection where Element == String {
    /// Converts an array of strings to an array of C strings, without copying.
    func withCStrings<R>(_ body: ([UnsafePointer<CChar>]) throws -> R) rethrows -> R {
        return try withCStrings(head: [], body: body)
    }
    
    // Recursively call withCString on each of the strings.
    private func withCStrings<R>(head: [UnsafePointer<CChar>],
                                 body: ([UnsafePointer<CChar>]) throws -> R) rethrows -> R {
        if let next = self.first {
            // Get a C string, add it to the result array, and recurse on the remainder of the collection
            return try next.withCString { cString in
                var head = head
                head.append(cString)
                return try dropFirst().withCStrings(head: head, body: body)
            }
        } else {
            // Base case: no more strings; call the body closure with the array we've built
            return try body(head)
        }
    }
}

func withVaListOfCStrings<R>(_ args: [String], body: (CVaListPointer) -> R) -> R {
    return args.withCStrings { cStrings in
        withVaList(cStrings, body)
    }
}

let argsStr: [String] = ["Test", "Testing", "The test"]
withVaListOfCStrings(argsStr) { listPtr in
    test_va_arg_str(Int32(argsStr.count), listPtr)
}

// Output:
// Printing 3 strings...
// Test
// Testing
// The test