gpt4 book ai didi

swift - Swift 对 CVarArg for String 的处理是否存在问题?

转载 作者:行者123 更新时间:2023-12-03 19:08:52 27 4
gpt4 key购买 nike

在为 C++ 库的 C 包装器编写 Swift 包装器时,我偶然发现了一些关于 Swift 的 CVarArg 的奇怪错误。我已经拥有的 C 包装器使用可变参数函数,我将其转换为使用 va_list 作为参数的函数,以便可以导入它们(因为 Swift 无法导入 C 可变参数函数)。当将参数传递给这样的函数时,一旦桥接到 Swift,它就会使用符合 _cVarArgEncoding 的类型的私有(private) CVarArg 属性来“编码”值,然后将其作为指向 C 函数的指针发送。然而,对于 Swift String 来说,这种编码似乎是错误的。
为了演示,我创建了以下包:
包.swift

// swift-tools-version:5.2

import PackageDescription

let package = Package(
name: "CVarArgTest",
products: [
.executable(
name: "CVarArgTest",
targets: ["CVarArgTest"]),
],
targets: [
.target(
name: "CLib"),
.target(
name: "CVarArgTest",
dependencies: ["CLib"])
]
)


CTest.h
#ifndef CTest_h
#define CTest_h

#include <stdio.h>

/// Prints out the strings provided in args
/// @param num The number of strings in `args`
/// @param args A `va_list` of strings
void test_va_arg_str(int num, va_list args);

/// Prints out the integers provided in args
/// @param num The number of integers in `args`
/// @param args A `va_list` of integers
void test_va_arg_int(int num, va_list args);

/// Just prints the string
/// @param str The string
void test_str_print(const char * str);

#endif /* CTest_h */


CTest.c
#include "CTest.h"
#include <stdarg.h>

void test_va_arg_str(int num, va_list args)
{
printf("Printing %i strings...\n", num);
for (int i = 0; i < num; i++) {
const char * str = va_arg(args, const char *);
puts(str);
}
}

void test_va_arg_int(int num, va_list args)
{
printf("Printing %i integers...\n", num);
for (int i = 0; i < num; i++) {
int foo = va_arg(args, int);
printf("%i\n", foo);
}
}

void test_str_print(const char * str)
{
puts(str);
}

main.swift
import Foundation
import CLib

// The literal String is perfectly bridged to the CChar pointer expected by the function
test_str_print("Hello, World!")

// Prints the integers as expected
let argsInt: [CVarArg] = [123, 456, 789]
withVaList(argsInt) { listPtr in
test_va_arg_int(Int32(argsInt.count), listPtr)
}

// ERROR: Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
let argsStr: [CVarArg] = ["Test", "Testing", "The test"]
withVaList(argsStr) { listPtr in
test_va_arg_str(Int32(argsStr.count), listPtr)
}
该软件包也可用 here
如上面代码中所述,通过 C 打印 String 或包含 va_listInt 可以按预期工作,但是当转换为 const char * 时,会出现异常( EXC_BAD_ACCESS (code=EXC_I386_GPFLT) )。
所以,简而言之:是我搞砸了它的 C 方面还是 Swift 在这里做错了什么?我已经在 Xcode 11.5 和 12.0b2 中对此进行了测试。如果这是一个错误,我很乐意报告它。

最佳答案

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

(lldb) p str
(const char *) $0 = 0x3cbe9f4c5d32b745 ""
(lldb) p (id)str
(NSTaggedPointerString *) $1 = 0x3cbe9f4c5d32b745 @"Test"
(如果您想知道为什么它是 NSTaggedPointerString 而不仅仅是 NSStringthis article 是一个很好的阅读 - 简而言之,字符串足够短,可以直接存储在指针变量的字节中,而不是在堆上的一个对象中。
the source code for withVaList ,我们看到一个类型的 va_list表示由其 _cVarArgEncoding 的实现决定。 CVarArg 的属性(property)协议(protocol)。标准库为 some basic integer and pointer types 提供了该协议(protocol)的一些实现。 ,但 String 没有任何内容这里。那么谁将我们的字符串转换为 NSString ?
在 GitHub 上搜索 Swift 存储库,我们发现 Foundation is the culprit :
//===----------------------------------------------------------------------===//
// CVarArg for bridged types
//===----------------------------------------------------------------------===//
extension CVarArg where Self: _ObjectiveCBridgeable {
/// Default implementation for bridgeable types.
public var _cVarArgEncoding: [Int] {
let object = self._bridgeToObjectiveC()
_autorelease(object)
return _encodeBitsAsWords(object)
}
}
简而言之:任何可以桥接到 Objective-C 的对象都通过转换为 Objective-C 对象并编码指向该对象的指针来编码为可变参数。 C 可变参数不是类型安全的,所以你的 test_va_arg_str只是假设它是 char*并将其传递给 puts , 崩溃。
那么这是一个错误吗?我不这么认为——我想这种行为可能是为了与 NSLog 之类的函数兼容。 Objective-C 对象比 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

关于swift - Swift 对 CVarArg for String 的处理是否存在问题?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62885399/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com