Как использовать файловый дескриптор для перенаправления записи в файл в swift? - PullRequest
0 голосов
/ 08 января 2020

Я хотел бы использовать код C, который использует дескриптор файла. Фоном является то, что я хотел бы прочитать некоторые данные из библиотеки cgraph.

public extension UnsafeMutablePointer where Pointee == Agraph_t {
   func saveTo(fileName: String)  {
      let f = fopen(cString(fileName), cString("w"))
      agwrite(self,f)
      fsync(fileno(f))
      fclose(f)
   }
}

Я хотел бы получить вывод файла, но без записи во временный файл. Следовательно, я хотел бы сделать что-то вроде этого:

public extension UnsafeMutablePointer where Pointee == Agraph_t {
    var asString: String  {
        let pipe = Pipe()
        let fileDescriptor = UnsafeMutablePointer<Int32>.allocate(capacity: 1)
        fileDescriptor.pointee = pipe.fileHandleForWriting.fileDescriptor
        agwrite(self, fileDescriptor)
        let data = pipe.fileHandleForReading.readDataToEndOfFile()
        if let output = String(data: data, encoding: .utf8) {
            return output  
        }  
        return ""
    }
}

Но это не работает, что приводит к EXC_BAD_ACCESS внутри agwrite (,). Что мне нужно сделать вместо этого? Большое спасибо заранее!

1 Ответ

2 голосов
/ 09 января 2020

Файловые дескрипторы и файловые указатели - это не одно и то же . Это сбивает с толку, и еще более расстраивает тот факт, что FILE * действительно сложно для Google из-за символа.

Вам нужно fdopen дескриптор файла (pipe.fileHandleForWriting.fileDescriptor), чтобы получить FILE * (UnsafeMutablePointer<FILE> в Swift). Это то, что вы затем передаете на agwrite.

Важно fclose указатель файла, когда вы закончите запись в него, иначе .readDataToEndOfFile() никогда не прекратит работу. Я сделал вспомогательную функцию, чтобы гарантировать, что fclose не может быть забыт. Возможно, что agwrite закрывает сам указатель файла, внутренне. Если это так, вы должны удалить этот код и просто дать ему результат fdopen, простой и понятный.

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())

Несколько других вещей:

  1. Выдача ошибки вероятно, лучший выбор, чем возвращение "". Пустые строки не являются хорошим механизмом обработки ошибок. Возврат необязательного также будет работать, но, в любом случае, он всегда будет принудительно развернут.
  2. readDataToEndOfFile - это блокирующий вызов, который может привести к неправильному использованию. Вероятно, лучше всего, чтобы этот код выполнялся в фоновом потоке или использовал FileHandle.readabilityHandler для асинхронного потребления данных по мере их поступления.
...