Swift & C Interop
@attheodo
The marriage of two beasts
Types
Arrays & Structs
//header.h
char name[] = "IAmAString"; 👌
// file.swift
print(name) // (97, 115, 100, 100, 97, 115, 100, 0) 🤪
Arrays
Structs
//header.h
typedef struct {
char name[5];
int value;
int anotherValue;
} MyStruct;
// file.swift
public struct MyStruct {
var name: [UInt8]
var value: Int32
var anotherValue: Int32
init()
init(name: [UInt8], value: Int32, anotherValue: Int32)
}
Enums & Constants
//header.h
typedef enum ConnectionError{
ConnectionErrorCouldNotConnect = 0,
ConnectionErrorDisconnected = 1,
ConnectionErrorResetByPeer = 2
}
// file.swift
struct ConnectionError : RawRapresentable, Equatable{ }
var ConnectionErrorCouldNotConnect: ConnectionError {get}
var ConnectionErrorDisconnected: ConnectionError {get}
var ConnectionErrorResetByPeer: ConnectionError {get}
Enums
Contants
// header.h
#define MY_CONSTANT 42
// file.swift
let MY_CONSTANT 42
More complex macros will get ingored by Swift
Calculating sizes
In Swift you can obtain the data-only or memory size of a specific type (primitive or compound) using the MemoryLayout<T> generic struct and the properties and functions it provides.
print(MemoryLayout<CChar>.stride) // 1 byte
struct AStruct {
let anInt8: Int64
let anInt: Int16
let b: Bool
}
print(MemoryLayout<AStruct>.size)) // 11 (8+2+1) byte
print(MemoryLayout<AStruct>.stride) // 16 (8+4+4) byte
Most of the times you should be working with stride
Null-ability
Thankfully, null gets translated to nil
For methods that expect pointers, you can pass nil directly... it will automatically translate to UnsafeRawPointer?
If you need a typed null pointer:
let p: UnsafeMutablePointer<UInt8>? = nil
Brace yourselves... more pointers coming...
Pointers
Pointers get automatically translated to different kinds of Unsafe*
Pointers
Pointers
-
General rule: mutable pointer instances point to mutable vars.
-
For class objects (i.e NSDate**), pointers to object passed as pointers translate to AutoreleasingUnsafeMutablePointer
-
If the type we are pointing to is not completely defined or cannot be represented in Swift, the pointer will be translated to OpaquePointer.
-
Values pointed to by OpaquePinter cannot be access directly! The pointer variable will have to be converted first.
-
-
Untyped pointers get translated to UnsafeRawPointer
-
All pointer types, except of raw and opaque pointers, are type safe.
-
The compiler will perform type checks on the pointer and its contents
-
The compile will also guarantee address alignment with the Pointee type they point to.
-
-
The Unsafe prefix has to do with how we access the content.
-
Interacting with pointers bypasses the general Swift's safety methods so, yes, you can crash something even if it passes compilation.
-
Pointers
Once you have a non-void Unsafe*
var anInt:Int = myIntPointer.pointee
myIntPointer.pointee = 42
myIntPointer[0] = 42
- pointee is used to access the instance references by the pointers
- You can also access any element in a sequence of pointers using the "subscript" notation
Pointer states
- Unallocated. No pointer reserved for the pointer.
- Allocated. The pointer points to a valid memory location but its value is not initialized yet.
- Initialized. The pointer points to an allocated and initialized location.
Pointer states
var ptr = UnsafeMutablePointer<CChar>.allocate(capacity: 10)
ptr.initialize(from: [CChar](repeating: 0, count: 10))
ptr[3] = 42
ptr.deinitialize()
ptr.deallocate(capacity: 10)
- Swift won't take the last two steps for you...
- You allocated and initialized it...
- ... it's your responsibility to clean up
Pointer Conversion
- Temporarily bind the content pointed by a pointer to a different "view" of the data
let charPtr = ptr.withMemoryRebound(to: CCChar.self, capacity: 11) {
(cptr) -> String in
return String(validatingUTF8: cptr)!
}
- For permanent pointer conversion you need raw types
UnsafeRawPointer(typedPointer).bindMemory(to: UIInt8.self,
capacity: 1024)
Pointer Arithmetic
- Classic C way of moving through sequences
- You can do it in Swift too!
var ptr = UnsafeMutablePointer<CChar>.allocate(capacity: 5)
ptr.initialize(from: [1,2,3,4,5])
print(ptr.successor().pointee) // 2
print(ptr.advanced(by: 3).pointee) // 4
print(ptr.advanced(by: 3).predecessor().pointee) // 3
aptr.deinitialize()
aptr.deallocate(capacity: 5)
Pointer Arithmetic
- You can still use integers for inc/dec pointers
var ptr = UnsafeMutablePointer<CChar>.allocate(capacity: 5)
ptr.initialize(from: [1,2,3,4,5])
print((ptr + 1).pointee) // 2
print((ptr + 3).pointee) // 4
- Note: When you inc/dec an Unsafe(Mutable)Pointer, the actual pointer is moved by multiples of MemoryLayout<Pointee>.alignment
- Raw or opague pointers are just inc/dec by the give number of positions
Working with strings
- When a C function has a string parameter (char pointer), this parameter gets imported to Swift as Unsafe(Mutable)Pointer<Int8>
- Swift automagically converts strings into pointers pointing to UTF8 buffers, so you can actually pass a Swift string directly to function expecting char pointer parameters.
- But you can also turn Swift strings into the C representation for i.e doing any pointer magic
Working with strings
// Passing a Swift string to a libc method
puts("foobar")
var str = "Hello CocoaHeads"
str.withCString { (ptr: UnsafePointer<Int8>) -> Void in
// do something with the pointer
}
// Convert C strings to Swift strings
let swiftString = String(cString: aCString)!
C memory and ARC
- Imagine we pass a Swift reference object to a C function that returns its result in a callback
-
Are we sure that the Swift object will be still allocated when the callback is returned?​
- ​Nope.
-
Say hello to Unmanaged
- ​With Unmanaged you are able to directly alter the retain count of an object and convert it to an OpaquePointer if you need to pass it around
C memory and ARC
// foobar.c
void aCFunctionWithContext(void *ctx, void (*function)(void *ctx)) {
sleep(10);
function(ctx);
}
- Here's a C method that returns it's result in a callback
C memory and ARC
class MyClass: CustomStringConvertible {
var property: Int = 0
var description: String { return "MyClass with property \(property)"
}
var myClass = MyClass()
let unmanaged = Unmanaged.passRetained(myClass)
let opaquePtr = unmanaged.toOpaque()
let ptr = UnsafeMutableRawPointer(opaquePtr)
aCFunctionwithContext(ptr) { (p: UnsafeMutableRawPointer?) -> Void in
var c = Unmanaged<MyClass>.fromOpaque(p!).takeUnretainedValue()
c.property = 2
print(c) // "MyClass with property 2"
}
C memory and ARC
- passRetained allows us to retain a given object by incrementing its reference count
- Since the C callback needs a void pointer, we first obtain an OpaquePointer with the toOpaque() method and then we convert it to UnsafeMutableRawPointer
- Inside the callback, we perform the reverse operation to get back our MyClass instance from the raw pointer
In practice
public enum KafkaClientType {
case consumer
case producer
func toRawType() -> rd_kafka_type_t {
switch self {
case .consumer:
return RD_KAFKA_CONSUMER
case .producer:
return RD_KAFKA_PRODUCER
}
}
}
let kSwiftKafkaCStringSize = 1024
let errString = UnsafeMutablePointer<CChar>.allocate(capacity: kSwiftKafkaCStringSize)
defer {
errString.deallocate(capacity: kSwiftKafkaCStringSize)
}
let handle: OpaquePointer = rd_kafka_new(clientType.toRawType(),
globalConfiguration!.handle,
errString,
kSwiftKafkaCStringSize)
In practice
public func getMetadata(forTopicHandle topicHandle: OpaquePointer? = nil,
timeout: Int32 = 1000) throws -> Metadata
{
guard let h = handle else {
throw KafkaError.unknownError
}
let ppMetadata = UnsafeMutablePointer<UnsafePointer<rd_kafka_metadata>?>.allocate(capacity: 1)
var error: rd_kafka_resp_err_t
if let topicHandle = topicHandle {
error = rd_kafka_metadata(h, 0, topicHandle, ppMetadata, timeout)
} else {
error = rd_kafka_metadata(h, 1, nil, ppMetadata, timeout)
}
guard error == RD_KAFKA_RESP_ERR_NO_ERROR else {
throw KafkaError.coreError(KafkaCoreError(error))
}
guard let rawMetadata = (ppMetadata.pointee)?.pointee else {
throw KafkaError.unknownError
}
rd_kafka_metadata_destroy(ppMetadata.pointee)
ppMetadata.deallocate(capacity: 1)
return Metadata.metadata(fromRawMetadata: rawMetadata)
}
In practice
extension Metadata {
static func metadata(fromRawMetadata data: rd_kafka_metadata) -> Metadata {
let brokers = BrokerMetadata.brokers(fromRawBrokersData: data.brokers,
numOfBrokers: data.broker_cnt)
let topics = TopicMetadata.topics(fromRawTopicsData: data.topics,
numOfTopics: data.topic_cnt)
return Metadata(brokers: brokers,
topics: topics,
originatingBrokerId: Int(data.orig_broker_id),
originatingBrokerName: String(cString: data.orig_broker_name))
}
}
In practice
public struct BrokerMetadata {
public let id: Int
public let host: String
public let port: Int
}
extension BrokerMetadata {
static func brokers(fromRawBrokersData data: UnsafeMutablePointer<rd_kafka_metadata_broker>,
numOfBrokers: Int32) -> [BrokerMetadata]
{
var brokers: [BrokerMetadata] = []
guard numOfBrokers > 0 else {
return brokers
}
for i in 0...numOfBrokers - 1 {
let b = data.advanced(by: Int(i)).pointee
let broker = BrokerMetadata(id: Int(b.id),
host: String(cString: b.host),
port: Int(b.port))
brokers.append(broker)
}
return brokers
}
}
Bonus
- Exposing your C code as a Swift module
// Package.swift
let package = Package(
name: "ckafka",
// Name of the pkg-config (.pc) file to get the
// additional flags for system modules.
pkgConfig: "rdkafka",
providers: [
.Brew("librdkafka"),
.Apt("librdkafka-dev")
]
)
// Module.modulemap
module ckafka [system] {
header "ckafka.h"
link "rdkafka"
export *
}
Questions?
@attheodo
http://attheo.do
Swift & C Interop
By Thanos Theodoridis
Swift & C Interop
- 377