File descriptors and file pointers are not the same thing. It's confusing, and made even more frustrating by the fact that FILE *
is really hard to Google because of the symbol.
You need to fdopen
the file descriptor (pipe.fileHandleForWriting.fileDescriptor
), to receive a FILE *
(UnsafeMutablePointer<FILE>
in Swift). This is what you then pass to agwrite
.
It's important to fclose
the file pointer when you're done writing to it, otherwise .readDataToEndOfFile()
will never terminate. I made a helper function to ensure the fclose
can't be forgetten. It's possible that agwrite
closes the file pointer itself, internally. If that's the case, you should delete this code and just give it the result of fdopen
, plain and simple.
import Foundation
public typealias Agraph_t = Int // Dummy value
public struct AGWriteWrongEncoding: Error { }
func agwrite(_: UnsafeMutablePointer<Agraph_t>, _ filePointer: UnsafeMutablePointer<FILE>) {
let message = "This is a stub."
_ = message.withCString { cString in
fputs(cString, stderr)
}
}
@discardableResult
func use<R>(
fileDescriptor: Int32,
mode: UnsafePointer<Int8>!,
closure: (UnsafeMutablePointer<FILE>) throws -> R
) rethrows -> R {
// Should prob remove this `!`, but IDK what a sensible recovery mechanism would be.
let filePointer = fdopen(fileDescriptor, mode)!
defer { fclose(filePointer) }
return try closure(filePointer)
}
public extension UnsafeMutablePointer where Pointee == Agraph_t {
func asString() throws -> String {
let pipe = Pipe()
use(fileDescriptor: pipe.fileHandleForWriting.fileDescriptor, mode: "w") { filePointer in
agwrite(self, filePointer)
}
let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let output = String(data: data, encoding: .utf8) else {
throw AGWriteWrongEncoding()
}
return output
}
}
let ptr = UnsafeMutablePointer<Agraph_t>.allocate(capacity: 1) // Dummy value
print(try ptr.asString())
Several other things:
- Throwing an error is probably a better choice than returning
""
. Empty strings aren't a good error handling mechanism. Returning an optional would also work, but it's likely to always be force unwrapped, anyway.
readDataToEndOfFile
is a blocking call, which can lead to a bad use experience. It's probably best that this code be run on a background thread, or use a FileHandle.readabilityHandler
to asynchronously consume the data as it comes in.