Uh oh, we're going Unsafe

Now that I have a handle on writing a bit of C again, how to actually have Swift talk to it? Enter the Unsafe apis.

I wrote https://github.com/carlynorama/UnsafeWrapCSampler to test it out.

Like the NoiseMaker and SwiftLIBPNG, the package file specifies two targets, a target for the C and one for the Swift. The Swift target uses the C target as a dependency. Unlike those two libraries (to date), I actually wrote some working Swift code!!

There is a lot of code in package, so I made a companion App: UnsafeExplorer. I’ll write more about the UI code in that next week.

This companion project never imports the C target, so it cannot call the C functions directly. It is worth noting that it can recognize (but not create) the more complex C types that are fully defined in the header even without the import. (See RandomColorsView, testColorFunctions(), where c_color is allowed to exist as a CColorRGBA, but a new one cannot be created there. )

References

The bulk of the repo’s code was made by following along with the two WWDC videos and making examples

Using the UnsafeWrapCSample repo

This repo is not designed to import into production code as much as to use as a reference. To quickly get a sense of what it can do, download the companion project mentioned above. It has Views for each of the major concepts.

Lessons Learned

Top take-a-ways from the exercise:

Use the closure syntax

There is a great closure syntax for many of the APIS which means not needing to manually allocate and deallocate pointers. The closures can return any type wanted. This example uses a special Array initializer which I thought was pretty cool in and of itself. It’s a little trick because initializedCount absolutely needs to be set to tell Swift how big the array ended up being.

    public func makeArrayOfRandomIntClosure(count:Int) -> [Int] {
        //Count for this initializer is really MAX count possible, function may return an array with fewer items defined.
        //both buffer and initializedCount are inout
        let tmp = Array<CInt>(unsafeUninitializedCapacity: count) { buffer, initializedCount in
            //C:-- void random_array_of_zero_to_one_hundred(int* array, const size_t n);
            random_array_of_zero_to_one_hundred(buffer.baseAddress, count)
            initializedCount = count // if initializedCount is not set, Swift assumes 0, and the array returned is empty.
        }
        return tmp.map { Int($0) }
    }

Use the .load function whenever possible.

If bytes bound to one memory type need to look like something else to the code, .load(as:) is your friend.

    func quadTupleToInt32(_ tuple:(UInt8,UInt8,UInt8,UInt8)) -> UInt32? {
        withUnsafeBytes(of: tuple, { bytesPointer in
            return bytesPointer.baseAddress?.load(as: UInt32.self)
        })
    }

There is now even a .loadUnaligned(fromByteOffset:,as:) that will come in super handy for parsing data protocols in which the data may not be (aligned)[https://developer.ibm.com/articles/pa-dalign/].

    public func processUnalignedData<T>(data:Data, as type:T.Type, offsetBy offset:Int = 0) -> T {
        let result = data.withUnsafeBytes { buffer -> T in
            return buffer.loadUnaligned(fromByteOffset: offset, as: T.self)
        }
        print(result)
        return result
    }

Use const in C function definitions

A const in the C function definition makes a difference to the Swift Unsafe pointer type. If a pointer is marked as const, then Swift only requires an UnsafePointer, which can be made from variables defined with let. If no const in the function parameter definition, Swift will require a var to create UnsafeMutablePointer.

To be honest, I went a little overboard and const’d the values as well. I have since confirmed that swift does NOT need that to pass in let values after all. I left them in b/c working code works. Modern C compilers are probably smart enough to write code that doesn’t copy-on pass but on change, but historically some compilers did not make a new copy of a variable for functions that promised to be safe in their declarations. I have to look into this more if I ever decide to try to compile Swift for something tiny.

More about const and its usage (C++ discussion)

Examples

baseInt and cappingAt don’t need to have temporary vars made for them.

    public func addRandom(to baseInt:CInt, cappingAt:CInt = CInt.max) -> CInt {
        withUnsafePointer(to: baseInt) { (min_ptr) -> CInt in
            withUnsafePointer(to: cappingAt) { (max_ptr) -> CInt in
                //C:-- int random_number_in_range(const int* min, const int* max);
                return random_number_in_range(min_ptr, max_ptr);
            }
        }
    }

baseArray does need a copy made since it is not passed in as an inout variable. Strictly speaking this is safer, but depending on the buffer size may not be the desired behavior. Note the super swank temporary implicit UnsafeMutableBufferPointer created. So so nice.

   public func addRandomWithCap(_ baseArray:[UInt32], newValueCap:UInt32) -> [UInt32] {
        var arrayCopy = baseArray
        //C:-- void add_random_to_all_capped(unsigned int* array, const size_t n, unsigned int cap);
        add_random_to_all_capped(&arrayCopy, arrayCopy.count, newValueCap)
        return arrayCopy
        
    }

Don’t for get about byte direction

Mac OS and many other systems are little endian, but “The Network” and many protocols are not. A well written API will not rely on the endianness of the system, but not all APIs are well written.

For example, in my code I wrote a union that would let me enter #RRGGBBAA to encode color information into a UINT32. It is NOT actually compliant with the OpenGL and PNG format RGBA32, because that data specification assumes the numbers are encoded with RED at byte[0], not byte[4]. Little endian systems will load the UInt32 #FFCC9966 into memory as [66, 99, CC, FF] instead.

Little Endian systems should implement #AABBGGRR style numbers but that is the opposite of how I’m used to writing hex colors, so I did not for this code.

To check a given system, try one of the following:

Swift Unsafe API names

Typed Pointers

Raw Pointers

Misc other functions

Try to Avoid

These rebind memory types. Better to do non-rebinding casting withUnsafeBytes or rawPointer.load(as) Since working with C anyway, pass the pointer into a void* and do what you need to do. YOLO.

(See UnsafeBufferView for the better way.)

TODO: