8

I am playing with Swift and noticed that Swift does not allow to create CFFunctionPointers. It can only pass around and reference existing ones.

As for example CoreAudio requires CFunctionPointer to certain callbacks therefore I cannot use pure Swift.

So I need to use some Objective-C trampoline or wrapper here that takes a Swift Closure as a parameter as well as the original callback prototype and then can be assigned to be the callback, but the actually action happens in Swift and not Objective-C.

How do I do this?

Some example code for such a wrapper would help me to understand how I can use Swift code from objective C for such purposes in a flexible way to work around Swift not being able to create CFunctionPointers.

Yes, I know I can just write stuff when needed in Objective-C. I want to do it in pure Swift as a learning exercise porting one of my apps to Swift (uses a lot of CoreAudio/CoreVideo framework).

scythe42
  • 483
  • 2
  • 6

2 Answers2

8

I needed to define this callback:

typedef void (*MIDIReadProc) ( const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon );

and I wanted to use Objective-C as least as possible.

This was my approach:

MIDIReadProcCallback.h

#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>

typedef void (^OnCallback)(const MIDIPacketList *packetList);

@interface MIDIReadProcCallback : NSObject

+ (void (*)(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon))midiReadProc;
+ (void)setOnCallback:(OnCallback)onCallback;

@end

MIDIReadProcCallback.m

#import "MIDIReadProcCallback.h"

static OnCallback _onCallback = nil;

static void readProcCallback(const MIDIPacketList *pktlist, void *refCon, void *connRefCon) {
    if (_onCallback) {
        _onCallback(pktlist);
    }
}

@implementation MIDIReadProcCallback

+ (void (*)(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon))midiReadProc {
    return readProcCallback;
}

+ (void)setOnCallback:(OnCallback)onCallback {
    _onCallback = onCallback;
}

@end

Then you can register MIDIReadProcCallback.midiReadProc as callback and set handler MIDIReadProcCallback.setOnCallback({ (packetList: MIDIPacketList) in ... })

Kirsteins
  • 27,065
  • 8
  • 76
  • 78
  • Thanks. That looks exactly what I need and I understand it now that I see it in code. Will try out on the weekend and let you know how it worked out for me. – scythe42 Oct 01 '14 at 20:19
  • I get: 'UnsafePointer' is not a subtype of 'MIDIPacketList' – Gene De Lisa Nov 02 '14 at 21:16
  • That is because handler's parameter is pointer. You can change it to `MIDIPacketList packetList` and then call handler with `_onCallback(*pktlist);`. Keep in mind that this is not complete solution, just an example on how it can be done. – Kirsteins Nov 03 '14 at 07:57
  • @Kirsteins has this changed with Swift 1.2 by any chance? – Sam Feb 24 '15 at 14:26
  • @Sam Unfortunately nothing new regarding this issue. – Kirsteins Feb 24 '15 at 15:00
  • @Kirsteins What about http://swiftdoc.org/type/CFunctionPointer/? I haven't explored it but it certainly appears relevant. – Sam Feb 27 '15 at 08:12
  • @Sam `CFunctionPointer` is there to hold references to C function. However it doesn't help the issue as you cannot point to anything else than a function declared in C. – Kirsteins Feb 27 '15 at 08:16
-1

Well, you can create a function pointer.

var ump = UnsafeMutablePointer<((UnsafePointer<MIDIPacketList>, UnsafeMutablePointer<Void>, UnsafeMutablePointer<Void> ) -> Void)>.alloc(1)
ump.initialize(MyMIDIReadProc)
let cp = COpaquePointer(ump)
let fp = CFunctionPointer<((UnsafePointer<MIDIPacketList>, UnsafeMutablePointer<Void>, UnsafeMutablePointer<Void> ) -> Void)>(cp)

status = MIDIDestinationCreate(midiClient,
        name,
        fp,
etc.

It doesn't work though with Core MIDI.

thread #7: tid = 0x713b7, 0x7a1541f0, stop reason = EXC_BAD_ACCESS (code=2, address=0x7a1541f0)
frame #0: 0x7a1541f0
frame #1: 0x00159295 CoreMIDI`LocalMIDIReceiverList::HandleMIDIIn(void*, OpaqueMIDIEndpoint*, void*, MIDIPacketList const*) + 117

BTW., you cannot have a bridging header if your MIDI code is in a framework you're writing.

Gene De Lisa
  • 3,628
  • 1
  • 21
  • 36
  • Part of being a mature software engineer is knowing what NOT to do. That is why I posted this code. As explicitly stated, it does not work. My intention is to save others the time of trying this. For those giving me downvotes, you are misunderstanding. – Gene De Lisa Jun 01 '15 at 13:52