Today, WHEN I was working on FFMPEG framework, I met a lot of interfaces about C. At first, I didn’t know how to use them. Finally, I found this article on the Internet, and it was written in detail, so I tried to read it.


Swift’s type system has strict rules that allow us to write less code to accomplish more complex functions. But when using c-based libraries, you’ll find that it’s all sorts of inconvenient. In fact, many C libraries are cumbersome to use on the Swift compiler. Although the Swift team has made great efforts to use C, there are still many problems with using C on Swift. Below we explore how to solve these problems.

Before we begin, it is important to note that most of the operations in this article are inherently insecure when bypassing the Swift compiled type system. So I encourage you to read and do not copy and paste the code in this article. This is not Stack Overflow, these code snippet can corrupt memory, cause a memory leak, or, at worst, cause your program to crash.

basis

The C pointer is expressed in Swift in two ways

UnsafePointer<T>
Copy the code

or

UnsafeMutablePointer<T>
Copy the code

T refers to the type of primitive C in Swift’s type. In C, a pointer declared const is UnsafePointer, otherwise UnsafeMutablePointer

Here’s an example

C:
void myFunction(const int *myConstIntPointer);

Swift:
func myFunction(myConstIntPointer: UnsafePointer<Int32>)

C:
void myOtherFunction(unsigned int *myUnsignedIntPointer);

Swift:
func myOtherFunciton(myUnsignedIntPointer: UnsafeMutablePointer<UInt32>)

C:
void iTakeAvoidPointer(void *aVoidPointer);

Swift:
func iTakeAvoidPointer(aVoidPointer: UnsafeMutablePointer<Void>)
Copy the code

If Swift doesn’t know the type of the pointer, say a pre-declaration, it can use COpaquePointer

C:
struct SomeThing
void iTakeAnOpaquePointer(struct SomeThing *someThing);

Swift:
func iTakeAnOpaquePointer(someThing: COpaquePointer)
Copy the code

Pass a pointer to the Swift object

In most cases, you will simply use the inout operator, similar to address-of in C

let myInt = 42
myFunction(&myInt)

var myUnsignedInt: UInt = 7
myOtherFunction(&myUnsignedInt)
Copy the code

There are two important and subtle details:

  1. In the use ofinoutWhen usingvarDeclared variables and constants are converted toUnsafePointerandUnsafeMutablePointer. It’s easy to forget if you don’t pay attention to the original type, but when you willUnsafePointerPassed to theUnsafeMutablePointerError is displayed in the parameter type of
  2. This operator only takes effect when Swift values and reference function parameters are passed in the context. You cannot get Pointers in other contexts. For example, the following code will cause a compiler error:
let x = 42
let y = &x
Copy the code

From time to time you need to work with using or returning null Pointers instead of other types of apis. This is unusual in C, which specifies common types.

void takesAnObject(void *theObject);
Copy the code

If you know the type of argument a function needs, you can force an object to use withUnsafePointer and unsafeBitCast to become a pointer. For example, a takesAnObject needs a pointer to an int

var test = 42
withUnsafePointer(&test, { (ptr: UnsafePointer<Int>) -> Void in
	var voidPtr: UnsafePointer<Void> = unsafeBitCast(ptr, UnsafePointer<Void>.self)
	takesAnObject(voidPtr)
})
Copy the code

So let’s analyze this. First, we call withUnsafeMutablePointer. This function takes two arguments. The first argument is T for inout and the second is a closure of type (UnsafePointer) -> ResultType. This closure takes the pointer to the function’s first argument as its only argument. The function returns the result of processing the closure, or in the case of the above example, Void, which returns nothing. So, we can

let ret = withUnsafePointer(&test, { (ptr: UnsafePointer<Int- > >)Int32 in
    var voidPtr: UnsafePointer<Void> = unsafeBitCast(ptr, UnsafePointer<Void>.self)
    return takesAnObjectAndReturnsAnInt(voidPtr)
})
print(ret)
Copy the code

** Note :** If you need to modify the pointer itself, you can use withUnsafeMutablePointer

Swift also has methods that can pass two parameters

var x: Int = 7
var y: Double = 4
withUnsafePointers(&x, &y, { ptr1: UnsafePointer<Int>, ptr2: UnsafePointer<Double>) -> Void in
	var voidPtr1: UnsafePointer<Void> = unsafeBitCast(ptr1, UnsafePointer<Void>.self)
	var voidPtr2: UnsafePointer<Void> = unsafeBitCast(ptr2, UnsafePointer<Void>.self)
	takesTwoPointers(voidPtr1, voidPtr2)
})
Copy the code

About unsafeBitCast

UnsafeBitCast is an extremely dangerous operation. The document’s description refers to “the cruel forcible conversion of something into something else of the same size”. We can safely use it because we are simply converting pointer types and all Pointers are of the same size. This is why we need to call withUnsafePointer to get the type of UnsafePointer before converting.

The easy mistake to make is

var x: Int = 7
let xPtr = unsafeBitCast(x, UnsafePointer<Void>.self)
Copy the code

The code snippet above wants to get a pointer to x. But that would be misleading. Because it will compile and run, it will get 0x7 or a garbage pointer, not the pointer to X we want.

Because the unsafeBitCast conversion must be equal in size.

var x: Int8 = 7
let xPtr = unsafeBitCast(x, UnsafePointer<Void>.self)
Copy the code

Interacting with the structure of C

Suppose you want to get information about the system your computer is running. With C, there is an APIuname(2) that gets information about your system, such as OS name and version. That’s the word for Swift

struct utsname {
	var sysname: (Int8, Int8, ..253.times... , Int8) var nodename: (Int8, Int8, ..253.times... , Int8) var release: (Int8, Int8, ..253.times... , Int8) var version: (Int8, Int8, ..253.times... , Int8) var machine: (Int8, Int8, ..253.times... , Int8) }Copy the code

In Swift, that is

var name = utsname(sysname:(0.0.0. .0), nodename: :(0.0.0. .0), etc)
utsname(&name)

var machine = name.machine
print(machine)
Copy the code

Another problem with this is that machine is a tuple, so it prints 256 Int8 values, but only a few of the ASCII values we want to fetch.

So, what to do?

Swift’s UnsafeMutablePointer supports two methods, alloc(Int) and dealloc(Int).

let name = UnsafeMutablePointer<utsname>.alloc(1)
uname(name)

let machine = withUnsafePointer(&name.memory.machine, { (ptr) -> String? in
	let int8Ptr = unsafeBitCast(ptr, UnsafePointer<Int8>.self)
	return String.fromCString(int8Ptr)
})

name.dealloc(1)
if let m = machine {
	print(m)
}

Copy the code

Call withUnsafePointer and pass the tuple as an argument and the closure returns an optional String

In the closure, we use UnsafePointer to convert the pointer. Because Swift’s String has class methods that cast CChar, you can pass the new pointer to the initialization operation and get the return value.

conclusion

With the Unsafe API, remember that this is an unsafe operation.

(The rest is nothing to read.)

Using Legacy C APIs with Swift