Skip to content

Commit dadcae8

Browse files
committed
Merge pull request #28 from nshintio/source_CFunctionPointers
Hint - CFunction Pointers in Swift
2 parents b8cf090 + 69afada commit dadcae8

File tree

2 files changed

+127
-0
lines changed

2 files changed

+127
-0
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---
2+
layout: post
3+
title: "Working with CFunction pointers in Swift"
4+
date: 2015-10-10 11:03:46 +0200
5+
comments: false
6+
author: marcin
7+
categories:
8+
---
9+
10+
Swift like objC allow us to mix it with other languages, unfotunately when it comes to Swift we can only choose between our good old friend objC or ANIS C, as there is still lack of C++ support. Basically using function pointers allows us to call C functions inside Swift. Swift will automatically convert methods included in Bridge Header into Swift equivalents:
11+
12+
This code:
13+
14+
```C
15+
void InitializeAudioSource(void *userAudioClass);
16+
void PlayAudio();
17+
void SetAudioGain(Float32 gain);
18+
```
19+
20+
Will be converted into:
21+
22+
```Swift
23+
Void InitializeAudioSource(userAudioClass: UnsafeMutablePointer<Void>)
24+
Void PlayAudio(Void)
25+
Void SetAudioGain(gain:Float32)
26+
```
27+
28+
And as far this is all pretty straightforward so now having our C CoreAudio implementation we can use it in our Swift project like that:
29+
30+
```Swift
31+
class KWSBackgroundStreamPlayer: NSObject {
32+
33+
func setGain(gain : Float32) {
34+
35+
SetAudioGain(gain)
36+
}
37+
38+
func play() {
39+
40+
PlayAudio()
41+
}
42+
43+
func pause() {
44+
45+
PauseAudio()
46+
}
47+
48+
func close() {
49+
50+
if self.trackClosed == true {
51+
52+
return
53+
}
54+
55+
self.trackClosed = true
56+
57+
CloseAudio()
58+
}
59+
60+
deinit {
61+
62+
self.close()
63+
}
64+
65+
}
66+
67+
```
68+
69+
But because Swift is very strict when it comes to type checks we are no more able to pass our class to `InitializeAudioSource` function, if you try to do so:
70+
71+
```Swift
72+
InitializeAudioSource( &self )
73+
```
74+
75+
You will end up with this error:
76+
77+
{% img center /images/cfunction_pointers/error_cfunction.png %}
78+
79+
80+
And this is were the bad hacks come in. Unfotunately we need to get a "true" raw pointer to be able to pass it back to C code and to so we need this piece of code:
81+
82+
```Swift
83+
//convert self to unmanaged object
84+
let anUnmanaged = Unmanaged<KWSBackgroundStreamPlayer>.passUnretained(self)
85+
//get raw data pointer
86+
let opaque: COpaquePointer = anUnmanaged.toOpaque()
87+
//convert to Mutable to match Swift safe type check
88+
let voidSelf = UnsafeMutablePointer<Void>(opaque)
89+
90+
InitializeAudioSource( voidSelf )
91+
```
92+
And that's how you pass Swift classes and objects to C code, and you will need to do it every time you need a void or any other fancy pointer on C side.
93+
94+
What happens here is that we first take an `Unmanaged` version of our class object this tells compiler that we are now responsible for memory management, and stops doing retain release magic under the hood for us. But still this is not enough, now we need a raw memory pointer from that unmanaged object and thats where `toOpaque()` comes in. This will return an raw C pointer of our class object. This is the true `void *` pointer.
95+
`COpaquePointer` is memory representation of something that cannot be represented in Swift so you should be very careful when playing with this kind of pointers. Now we just need to make it a proper type in this case `UnsafeMutablePointer` with `<Void>` class type. Every UnsafePointer have constructor that takes `COpaquePointer` so you have to be sure you are choosing proper type when creating it as there is no type check at this point!
96+
97+
Hopefully on C side it's easy to convert void pointer to proper class type, you just need to use `bridge` to do so:
98+
99+
```C
100+
void InitializeAudioSource(void *userAudioClass)
101+
{
102+
//bridge it back to our class
103+
KWSBackgroundStreamPlayer *backgroundPlayer = (__bridge KWSBackgroundStreamPlayer*)userAudioClass;
104+
105+
//get some properties
106+
NSString *path = backgroundPlayer.filePath;
107+
repeatSong = backgroundPlayer.repeatSong;
108+
109+
//...
110+
111+
//we can even pass it as parameter with proper pointer type
112+
OSStatus result = AudioQueueNewOutput(&dataFormat, BufferCallback, (__bridge void *)(backgroundPlayer), nil, nil, 0, &queue);
113+
114+
//...
115+
}
116+
```
117+
118+
I hope this will help people understand a bit more about how danger may be playing with C in Swift and vice versa. Using `Unmanaged` and `COpaquePointer` may lead to crashes and memory leaks, as in some cases you are again responsible for manual memory managment, you are able to get raw pointers with no type that can be casted to whatever you want and so on.
119+
120+
Overall this is a powerful magic you can do in Swift but as someone said: "With Great Power Comes Great Responsibility" ;)
121+
122+
Example usage available on [github](https://github.com/noxytrux/KnightWhoSaidSwift)
123+
124+
125+
126+
127+
Loading

0 commit comments

Comments
 (0)