问题
I'm trying to do a simple DNS lookup in swift. So far, here is the code that I have:
let hostRef = CFHostCreateWithName(kCFAllocatorDefault, "google.com").takeRetainedValue()
var resolved = CFHostStartInfoResolution(hostRef, CFHostInfoType.Addresses, nil)
let addresses = CFHostGetAddressing(hostRef, &resolved).takeRetainedValue() as NSArray
At this point, each element in the "addresses" NSArray is a CFDataRef object wrapping a sockaddr struct.
Since CFDataRef can be toll-free bridged to NSData, I can loop through them like so:
for address: AnyObject in addresses {
println(address) // address is of type NSData.
}
So far so good (I think). This prints out valid looking data when I run it in a unit test. Here is where I get stuck though. For the life of me, I can't figure out how to convert the bytes in the NSData object into a sockaddr struct.
How can I convert address.bytes, which is of type COpaquePointer?, into a c struct? Any help appreciated. I'm banging my head against the wall trying to figure this out.
回答1:
You can use the NSData method getBytes(_, length:)
method and pass the sockaddr struct to the inout parameter using the prefix &
operator:
var data: NSData ...
var address: sockaddr ...
data.getBytes(&address, length: MemoryLayout<sockaddr>.size)
Updated for Swift 3:
let host = CFHostCreateWithName(kCFAllocatorDefault, "google.com" as CFString).takeRetainedValue()
var resolved = DarwinBoolean(CFHostStartInfoResolution(host, .addresses, nil))
let addresses = CFHostGetAddressing(host, &resolved)?.takeUnretainedValue() as! [NSData]?
if let data = addresses?.first {
var storage = sockaddr_storage()
data.getBytes(&storage, length: MemoryLayout<sockaddr_storage>.size)
if Int32(storage.ss_family) == AF_INET {
let addr4 = withUnsafePointer(to: &storage) {
$0.withMemoryRebound(to: sockaddr_in.self, capacity: 1) {
$0.pointee
}
}
// prints 74.125.239.132
print(String(cString: inet_ntoa(addr4.sin_addr), encoding: .ascii))
}
}
Updated 6/3/2015:
Now that C structs can be easily zero-initialized, this becomes much simpler:
let host = CFHostCreateWithName(kCFAllocatorDefault, "google.com").takeRetainedValue()
var resolved = CFHostStartInfoResolution(host, .Addresses, nil)
let addresses = CFHostGetAddressing(host, &resolved)?.takeUnretainedValue() as! [NSData]?
if let data = addresses?.first {
var storage = sockaddr_storage()
data.getBytes(&storage, length: sizeof(sockaddr_storage))
if Int32(storage.ss_family) == AF_INET {
let addr4 = withUnsafePointer(&storage) { UnsafePointer<sockaddr_in>($0).memory }
// prints 74.125.239.132
println(String(CString: inet_ntoa(addr4.sin_addr), encoding: NSASCIIStringEncoding))
}
}
Unfortunately this requires sockaddr
to be initialized first. To avoid that, you could do something like this:
func makeWithUnsafePointer<T>(body: UnsafePointer<T> -> ()) -> T {
let ptr = UnsafePointer<T>.alloc(sizeof(T))
body(ptr)
return ptr.move()
}
let addr: sockaddr = makeWithUnsafePointer {
data.getBytes($0 as UnsafePointer<sockaddr>, length: sizeof(sockaddr))
}
Or this:
func makeWithUninitialized<T>(body: inout T -> ()) -> T {
let ptr = UnsafePointer<T>.alloc(sizeof(T))
body(&ptr.memory)
return ptr.move()
}
let addr = makeWithUninitialized { (inout addr: sockaddr) in
data.getBytes(&addr, length: sizeof(sockaddr))
}
For more discussion, see Swift: Pass Uninitialized C Structure to Imported C function
来源:https://stackoverflow.com/questions/24794997/convert-nsdata-to-sockaddr-struct-in-swift