Hi, I'm working on a Swift wrapper to libpng
mostly for my own learning experience and I've gotten quite a bit working.
I would like the code to fail more gracefully though. The default behavior, and the only behavior I've been able to recreate, is a program exiting crash when libpng reaches an error.
In an attempt to side step writing helper functions in C to work with setjmp/longjmp, I decided to try using custom error callbacks in the png_create_write_struct function.
The problem is that libpng just works really well when it can get in and out of dodge with a jumpdef, but I'm not sure how to exit the parent process from a C callback without bringing down the whole house in Swift?
Is that even possible? The code below works as expected, but I'd like to figure out how to do something better than exit() or abort() from a callback.
A heads up that this can't possibly work like this would also be helpful and I'll try a different approach/move on. Thanks in advance.
#if os(Linux)
import Glibc
#else
import Darwin
#endif
import Foundation
import png //<- package wrapper around C library. In the linked repo.
public struct SwiftLIBPNG {
public init() {}
//MARK: Global Error Callbacks
struct PNGErrorInfo {
var png_ptr:OpaquePointer?
var info_ptr:OpaquePointer?
var fileHandle:UnsafeMutablePointer<FILE>?
var testExtraData:UInt32
func print_info() {
print("\(String(describing: png_ptr)), \(String(describing: info_ptr)), \(String(describing: fileHandle)), \(testExtraData)")
}
}
static let writeErrorCallback:@convention(c) (Optional<OpaquePointer>, Optional<UnsafePointer<CChar>>) -> () = { png_ptr, message in
if let error_ptr = png_get_error_ptr(png_ptr) {
print("There was a non nil error pointer set at \(error_ptr)")
var typed_error_ptr = error_ptr.load(as: PNGErrorInfo.self)//error_ptr.assumingMemoryBound(to: PNGErrorInfo.self)
typed_error_ptr.print_info()
//If aborting whole program everything should be freed automatically, but in case not...
precondition(png_ptr == typed_error_ptr.png_ptr)
png_destroy_write_struct(&typed_error_ptr.png_ptr, &typed_error_ptr.info_ptr)
if typed_error_ptr.fileHandle != nil {
fclose(typed_error_ptr.fileHandle)
}
}
if let message {
print("libpng crashed with warning: \(String(cString: message))")
} else {
print("libpng crashed without providing a message.")
}
//Some way to kill png?
//How to leave PNG write...
exit(99) //see also https://en.cppreference.com/w/c/program/atexit
//abort() //terminates the process by raising a SIGABRT signal, possible handler?
//This function MUST NOT go back to the parent caller. Getting here means that code cannot reasonably proceed.
}
static let writeWarningCallback:@convention(c) (Optional<OpaquePointer>, Optional<UnsafePointer<CChar>>) -> () = { png_ptr, message in
if let error_ptr = png_get_error_ptr(png_ptr) {
print("There was a non nil error pointer set at \(error_ptr)")
}
if let message {
print("libpng sends warning: \(String(cString: message))")
} else {
print("libpng sends unspecified warning")
}
//Use the error pointer to set flags, etc.
}
}
extension SwiftLIBPNG {
// EXAMPLE USAGE
// func writeImage() {
// let width = 5
// let height = 3
// var pixelData:[UInt8] = []
//
// for _ in 0..<height {
// for _ in 0..<width {
// pixelData.append(0x77)
// pixelData.append(0x00)
// pixelData.append(UInt8.random(in: 0...UInt8.max))
// pixelData.append(0xFF)
// }
// }
//
// let data = try? SwiftLIBPNG.buildSimpleDataExample(width: 5, height: 3, pixelData: pixelData)
// if let data {
// for item in data {
// print(String(format: "0x%02x", item), terminator: "\t")
// }
// print()
//
// let locationToWrite = URL.documentsDirectory.appendingPathComponent("testImage", conformingTo: .png)
// do {
// try data.write(to: locationToWrite)
// } catch {
// print(error.self)
// }
// }
// }
//NOT using "libpng simplified API"
//takes a width, height and pixel data in RR GG BB AA byte order
public static func buildSimpleDataExample(width:UInt32, height:UInt32, pixelData:[UInt8]) throws -> Data {
var pixelsCopy = pixelData //Could have been an inout
//----------------------------- INTENTIONAL ERROR
let bitDepth:UInt8 = 1 //should be 8 (1 byte, values 1, 2, 4, 8, or 16) (has to be 8 or 16 for RGBA)
//----------------------------- END INTENTIONAL ERROR
let colorType = PNG_COLOR_TYPE_RGBA //UInt8(6), (1 byte, values 0, 2, 3, 4, or 6) (6 == red, green, blue and alpha)
var pngIOBuffer = Data() //:[UInt8] = [] // //
withUnsafePointer(to: pngIOBuffer) { print("io buffer declared: \($0)") }
var pngWriteErrorInfo = PNGErrorInfo(testExtraData: 42)
var png_ptr:OpaquePointer? = png_create_write_struct(PNG_LIBPNG_VER_STRING, &pngWriteErrorInfo, writeErrorCallback, writeWarningCallback)
if (png_ptr == nil) { throw PNGError.outOfMemory }
//Makes the pointer to handle information about how the underlying PNG data needs to be manipulated.
//C:-- png_create_info_struct(png_const_structrp!)
var info_ptr:OpaquePointer? = png_create_info_struct(png_ptr);
if (info_ptr == nil) {
png_destroy_write_struct(&png_ptr, nil);
throw PNGError.outOfMemory;
}
pngWriteErrorInfo.fileHandle = nil
pngWriteErrorInfo.png_ptr = png_ptr
pngWriteErrorInfo.info_ptr = info_ptr
let writeDataCallback: @convention(c) (Optional<OpaquePointer>, Optional<UnsafeMutablePointer<UInt8>>, Int) -> Void = { png_ptr, data_io_ptr, length in
guard let output_ptr:UnsafeMutableRawPointer = png_get_io_ptr(png_ptr) else { return }
guard let data_ptr:UnsafeMutablePointer<UInt8> = data_io_ptr else { return }
//print("callback io output buffer: \(output_ptr)")
//print("callback io data buffer: \(data_ptr)")
let typed_output_ptr = output_ptr.assumingMemoryBound(to: Data.self)
typed_output_ptr.pointee.append(data_ptr, count: length)
}
png_set_write_fn(png_ptr, &pngIOBuffer, writeDataCallback, nil)
// THIS IS WHERE THE INTENTIONAL ERROR WILL FAIL
//---------------------------------------------------------------- IHDR
png_set_IHDR(png_ptr, info_ptr, width, height,
Int32(bitDepth), colorType,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT
)
//--------------------------------------------------------------- IDAT
pixelsCopy.withUnsafeMutableBufferPointer{ pd_pointer in
var row_pointers:[Optional<UnsafeMutablePointer<UInt8>>] = []
for rowIndex in 0..<height {
let rowStart = rowIndex * width * 4
row_pointers.append(pd_pointer.baseAddress! + Int(rowStart))
}
//png_set_rows(png_ptr: png_const_structrp!, info_ptr: png_inforp!, row_pointers: png_bytepp!)
png_set_rows(png_ptr, info_ptr, &row_pointers)
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nil)
}
//-------------------------------------------------------- PNG CLEANUP
png_destroy_write_struct(&png_ptr, &info_ptr);
//---------------------------------------------------------------------
return pngIOBuffer
}
}