在 Swift 中转换为不同的 C 结构不安全指针

Cast to different C struct unsafe pointer in Swift

我想从 Swift 调用 Posix 套接字函数 socketbindsocket 非常简单——需要 Int32s,但是 bind 导致了问题,因为我有一个 sockaddr_in 指针,但它需要一个 sockaddr 指针.在 C 中,这将是一个转换,例如:

bind(sock, (struct sockaddr *)&sockAddress, sizeof(sockAddress))

这是 Swift 中的一次尝试:

let sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)
var sockAddress = sockaddr_in()        
bind(sock, &sockAddress, UInt32(MemoryLayout<sockaddr_in>.size))

bind 行编译失败:无法将类型 'sockaddr_in' 的值转换为预期的参数类型 'sockaddr'

如何投射指针?

你可以这样写:

withUnsafePointer(to: &sockAddress) {sockaddrInPtr in
    sockaddrInPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) {sockaddrPtr in
        bind(sock, sockaddrPtr, UInt32(MemoryLayout<sockaddr_in>.stride))
    }
}

或者有人建议这可能更好:

withUnsafePointer(to: &sockAddress) {sockaddrInPtr in
    let sockaddrPtr = UnsafeRawPointer(sockaddrInPtr).assumingMemoryBound(to: sockaddr.self)
    bind(sock, sockaddrPtr, UInt32(MemoryLayout<sockaddr_in>.stride))
}

This article 可能会有一些帮助。


(更新) 如 中所述,现在 MemoryLayout<T>.strideMemoryLayout<T>.size return 与 C 的 sizeof 一致的相同值,其中 T 是导入的 C 结构。我会在这里保留我的 stride 版本的答案,但现在 "required" 在这种情况下不是这样。

在 Swift 3 你必须 "rebind" 指针 (比较 SE-0107 UnsafeRawPointer API):

let sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)
var sockAddress = sockaddr_in()

let result = withUnsafePointer(to: &sockAddress) {
    [=10=].withMemoryRebound(to: sockaddr.self, capacity: 1) {
        bind(sock, [=10=], socklen_t(MemoryLayout<sockaddr_in>.stride))
    }
}

备注:

  • 不需要let sock: Int32var sockAddress: sockaddr_in中的类型注释。

  • 不需要memset()因为sockaddr_in()初始化 所有结构成员归零。

  • C sizeof 的 Swift 等效为 stride(其中包括 可能的结构填充),而不是 size(不包括 struct padding).(这个"problem"不存在了。 对于从 C 导入的结构,stridesize 具有相同的值。)

Swift 5 废弃了 withUnsafeBytes(UnsafePointer<sockaddr>),下面是我用 Swift 5:

做的
        var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
    return withUnsafeBytes { (p: UnsafeRawBufferPointer) -> String? in
        let addr = p.baseAddress?.assumingMemoryBound(to: sockaddr.self)
        guard getnameinfo(addr, socklen_t(self.count), &hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST) == 0 else {
            return nil
        }
        return String(cString: hostname)
    }