Using Legacy C APIs with Swift
2015-12-09 10:56
375 查看
Swift’s type system is designed to make our lives easier by enforcing strict rules around what we can and cannot do in our code. This is undoubtedly a great thing and it encourages programmers to write better, more correct code. However, it can seem extremely
prohibitive when interacting with legacy code bases, particularly C-based libraries. It is a reality that many C libraries abuse types in such a way that does not play nicely with the Swift compiler. It’s true that the Swift team at Apple has gone out of their
way to support some of the more basic features of C, such as C strings, but there are still many issues when working with a legacy C library in Swift. Here’s how to deal with them.
Before we begin, I would like to note that many of the operations in this article are inherently unsafe as they bypass the Swift compiler’s type system altogether. I encourage you to read carefully and refrain from copy and pasting code from this article. This
is not Stack Overflow, these snippets have a real chance of corrupting memory, causing memory leaks, or simply crashing your application if used improperly.
Most of the time, C pointers are imported into Swift in one of two ways:
or
Where T is the equivalent Swift type for the original C type. Pointers declared const in C are imported as
pointers that are not declared const are imported as
Here are a few examples
If the type of the pointer isn’t known to Swift, for example because it is a forward declaration, it is represented using a
In many cases, this is as simple as using the
There are two very important, but subtle details here.
When using the
into
to pass an
This operator only works in the context of passing Swift values and references as function arguments that expect an
cannot get the pointer in any other context. For example, this is invalid and will result in a compiler error:
From time to time, you will need to interoperate with an API that takes or returns a void pointer in place of an explicit type. This is unfortunately common in C, where there isn’t a way to specify a generic type.
If you know the expected type taken by the function, you can coerce an object into a void pointer using
say
Let’s break this down. First, we make a call to
The first is an
calls the closure by taking a pointer to the first argument of the function, and passing it as the sole argument of the closure. The function then returns the result of the closure. In the example above, the closure is typed to return
and therefore doesn’t return anything. We could just as easily do something like this:
Note: Should you need to modify the pointer itself, there is a
For convenience, Swift also has variants that pass two pointers:
simply casting between pointers of different types, and all pointers are the same size on any given platform. This is why we must call
casting that to an
This can be confusing at first, especially when working with a type that is the same size as a pointer, such as an
and 1 Word is the size of an
It is easy to make a mistake like this:
With the intention of obtaining a pointer to x. This is incredibly misleading because it will compile and run, but lead to unexpected errors because instead of a pointer to x, the C APIs will receive a pointer with location 0x7,
or garbage.
Because
or a one byte integer.
This will simply cause
Let’s tackle this one with a concrete example. You want to retrieve information about the system your computer is running on. There’s a C API for that,
out the information in the supplied object with the systems information, such as OS name and version or its hardware identifier. There’s a catch though, the struct is imported into Swift as:
Oh no! Swift imports C array literals as tuples! On top of that, the default initializer requires values for each and every field. So if you were to do this the normal Swift way, it would be like this:
That’s not a good idea! But there’s another problem. The
going to print out 256
So, how can we fix this?
Swift’s
deallocating respectively. The argument the amount of
The first step is making our call to
Inside the closure we take the provided pointer and cast it to
a class method for initializing from
So we can pass our new pointer to the initializer and return the value.
After capturing the result of
result. For me, this yields “x86_64” as expected.
A bit of a disclaimer. Using unsafe APIs in Swift should be a last resort because, they’re inherently not safe! As we transition from legacy C and Objective-C code to Swift, it’s likely that we’ll continue to need these APIs to be compatible with our existing
tools. However, one should always be skeptical when their first resort is to break out withUnsafePointer and unsafeBitCast.
New code should strive to be as idiomatic as possible, which explicitly prohibits the use of Swift’s unsafe APIs. As software developers, it’s just as important to know how to use your tools as it is to know when and where not to use them. Swift brings with
it the benefit of modernizing a large subset of software development and we must respect its ideologies in order for it to really make an impact.
prohibitive when interacting with legacy code bases, particularly C-based libraries. It is a reality that many C libraries abuse types in such a way that does not play nicely with the Swift compiler. It’s true that the Swift team at Apple has gone out of their
way to support some of the more basic features of C, such as C strings, but there are still many issues when working with a legacy C library in Swift. Here’s how to deal with them.
Before we begin, I would like to note that many of the operations in this article are inherently unsafe as they bypass the Swift compiler’s type system altogether. I encourage you to read carefully and refrain from copy and pasting code from this article. This
is not Stack Overflow, these snippets have a real chance of corrupting memory, causing memory leaks, or simply crashing your application if used improperly.
The Basics
Most of the time, C pointers are imported into Swift in one of two ways:UnsafePointer<T>
or
UnsafeMutablePointer<T>
Where T is the equivalent Swift type for the original C type. Pointers declared const in C are imported as
UnsafePointerand
pointers that are not declared const are imported as
UnsafeMutablePointer.
Here are a few examples
C: void myFunction(const int *myConstIntPointer); Swift: func myFunction(myConstIntPointer: UnsafePointer<Int32>) C: void myOtherFunction(unsigned int *myUnsignedIntPointer); Swift: func myOtherFunction(myUnsignedIntPointer: UnsafeMutablePointer<UInt32>) C: void iTakeAVoidPointer(void *aVoidPointer); Swift: func iTakeAVoidPointer(aVoidPointer: UnsafeMutablePointer<Void>)
If the type of the pointer isn’t known to Swift, for example because it is a forward declaration, it is represented using a
COpaquePointer.
C: struct SomeThing; void iTakeAnOpaquePointer(struct SomeThing *someThing); Swift: func iTakeAnOpaquePointer(someThing: COpaquePointer)
Passing Pointers to Swift Objects
In many cases, this is as simple as using the inoutoperator, which is the same as the familiar
address-ofoperator in C, the ampersand.
Swift: let myInt: = 42 myFunction(&myInt) var myUnsignedInt: UInt = 7 myOtherFunction(&myUnsignedInt)
There are two very important, but subtle details here.
When using the
inoutoperator, variables declared with
varand constants declared with
letare transformed
into
UnsafePoinerand
UnsafeMutablePointerrespectively. This is very easy to miss if you do not pay close attention to the original type in the code. Trying
to pass an
UnsafePointerwhere an
UnsafeMutablePointeris expected results in a seemingly cryptic compiler error so be wary.
This operator only works in the context of passing Swift values and references as function arguments that expect an
UnsafePointeror
UnsafeMutablePointer. You
cannot get the pointer in any other context. For example, this is invalid and will result in a compiler error:
Swift: let x = 42 let y = &x
From time to time, you will need to interoperate with an API that takes or returns a void pointer in place of an explicit type. This is unfortunately common in C, where there isn’t a way to specify a generic type.
C: void takesAnObject(void *theObject);
If you know the expected type taken by the function, you can coerce an object into a void pointer using
withUnsafePointerand
unsafeBitCast. For example, let’s
say
takesAnObjectis actually expecting 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) })
Let’s break this down. First, we make a call to
withUnsafeMutablePointer. This generic function takes two arguments.
The first is an
inoutof type
T, and the second is a closure of type
(UnsafePointer) -> ResultType. This function
calls the closure by taking a pointer to the first argument of the function, and passing it as the sole argument of the closure. The function then returns the result of the closure. In the example above, the closure is typed to return
Void,
and therefore doesn’t return anything. We could just as easily do something like this:
let ret = withUnsafePointer(&test, { (ptr: UnsafePointer<Int>) -> Int32 in var voidPtr: UnsafePointer<Void> = unsafeBitCast(ptr, UnsafePointer<Void>.self) return takesAnObjectAndReturnsAnInt(voidPtr) }) println(ret)
Note: Should you need to modify the pointer itself, there is a
withUnsafeMutablePointervariant.
For convenience, Swift also has variants that pass two pointers:
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) })
About unsafeBitCast
unsafeBitCastis an extremely dangerous operation. The documentation describes it as a “brutal bit-cast of something to anything of the same size.” The reason we are able to use it safely above is because we’re
simply casting between pointers of different types, and all pointers are the same size on any given platform. This is why we must call
withUnsafePointerto obtain a typed
UnsafePointerfirst before
casting that to an
UnsafePointeras the C API is defined.
This can be confusing at first, especially when working with a type that is the same size as a pointer, such as an
Intin Swift (on all currently available platforms anyway, where the size of a pointer is 1
Word,
and 1 Word is the size of an
Int).
It is easy to make a mistake like this:
var x: Int = 7 let xPtr = unsafeBitCast(x, UnsafePointer<Void>.self)
With the intention of obtaining a pointer to x. This is incredibly misleading because it will compile and run, but lead to unexpected errors because instead of a pointer to x, the C APIs will receive a pointer with location 0x7,
or garbage.
Because
unsafeBitCastrequires that the size of the types be equal, it is less insidious when attempting to cast something other than an
Int, such as
Int8,
or a one byte integer.
var x: Int8 = 7 let xPtr = unsafeBitCast(x, UnsafePointer<Void>.self)
This will simply cause
unsafeBitCastto throw an exception and crash your program!
Interacting With C Structs
Let’s tackle this one with a concrete example. You want to retrieve information about the system your computer is running on. There’s a C API for that, uname(2), which takes a pointer to a structure and fills
out the information in the supplied object with the systems information, such as OS name and version or its hardware identifier. There’s a catch though, the struct is imported into Swift as:
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) }
Oh no! Swift imports C array literals as tuples! On top of that, the default initializer requires values for each and every field. So if you were to do this the normal Swift way, it would be like this:
var name = utsname(sysname: (0, 0, 0, ..., 0), nodename: (0, 0, 0, ..., 0), etc) utsname(&name) var machine = name.machine println(machine)
That’s not a good idea! But there’s another problem. The
machinefield of
utsnameis a tuple so that
printlnis
going to print out 256
Int8’s, with only the first few representing the ASCII values of the characters in the string we actually want.
So, how can we fix this?
Swift’s
UnsafeMutablePointersupplies two methods,
alloc(Int)and
dealloc(Int), for manually allocating and
deallocating respectively. The argument the amount of
T’s to allocate or deallocate. We can use these APIs to simplify our code.
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 { println(m) }
The first step is making our call to
withUnsafePointer, passing it the machine tuple and telling it that our closure is going to return an optional String.
Inside the closure we take the provided pointer and cast it to
UnsafePointer, a mostly equivalent representation of the same value. Except that Swift’s
Stringhas
a class method for initializing from
UnsafePointerwhere
CCharis a typealias for
Int8!
So we can pass our new pointer to the initializer and return the value.
After capturing the result of
withUnsafePointer(which, remember, forwards the return value of the supplied closure) we can test to see if we obtained a value using a conditional-let statement, and print the
result. For me, this yields “x86_64” as expected.
Conclusion
A bit of a disclaimer. Using unsafe APIs in Swift should be a last resort because, they’re inherently not safe! As we transition from legacy C and Objective-C code to Swift, it’s likely that we’ll continue to need these APIs to be compatible with our existingtools. However, one should always be skeptical when their first resort is to break out withUnsafePointer and unsafeBitCast.
New code should strive to be as idiomatic as possible, which explicitly prohibits the use of Swift’s unsafe APIs. As software developers, it’s just as important to know how to use your tools as it is to know when and where not to use them. Swift brings with
it the benefit of modernizing a large subset of software development and we must respect its ideologies in order for it to really make an impact.
相关文章推荐
- Swift第一天学习
- 《 Swift UITabBarController 的使用和自定义TabBar,和部分属性和代理的使用》
- [ios][swift]swift混编
- 《从零开始学Swift》学习笔记(Day 46)——下标重写
- 《从零开始学Swift》学习笔记(Day 46)——下标重写
- 新书《iOS8 Swift编程指南》货架
- NSDate教程(Swift版)
- mutating的使用(swift2.0)
- as在swift中的应用
- swift 学习之UISegmentedControl
- 一步一步学swift之:自己写Api接口-PHP
- 如何创建swift工程
- swift 创建UICollectionView
- swift -懒加载创建view
- swift实际使用中遇到的问题及解决(2)
- Swift学习笔记 -- 流程控制
- swift UITabelVIew - 纯代码自定义tabelViewCell
- swift编码出现Call can throw, but it is not marked with 'try' and the error is not handled错误的解决
- Swift学习之路 -- 枚举和结构体
- 【菜鸟初学Swift】IOS平台常用传感器的使用方式