Swift UnsafeMutablePointer<Unmanaged<CFString>?> allocation and print
Asked Answered
E

1

9

I'm new to swift and I have some difficulties to deal with pointers of unmanaged CFString (or NSString). I'm working on a CoreMIDI project that implies usage of UnsafeMutablePointer?> as you can see in this function :

func MIDIObjectGetStringProperty(_ obj: MIDIObjectRef,
                           _ propertyID: CFString!,
                           _ str: UnsafeMutablePointer<Unmanaged<CFString>?>) -> OSStatus

My problem is that I want to allocate a buffer to receive the content of the property (_str) then call the function above, and finally print the content in the console by using println.

At the moment I wrote this :

// Get the first midi source (I know it exists)
var midiEndPoint : Unmanaged<MIDIEndpointRef> = MIDIGetSource(0)

//C reate a "constant" of 256
let buf = NSMutableData(capacity: 256) 

// Allocate a string buffer of 256 characters (I'm not even sure this does what I want)
var name = UnsafeMutablePointer<Unmanaged<CFString>?>(buf!.bytes)

// Call the function to fill the string buffer with the display name of the midi device
var err : OSStatus =  MIDIObjectGetStringProperty(&midiEndPoint,kMIDIPropertyDisplayName,name)

// Print the string ... here no surprises I don't know what to write to print the content of the pointer, so it prints the address for the moment
println(name)

I didn't find any sample code to use CoreMIDI functions on apple developper library not on the internet. I really confused because I come from cpp and things are a lot different in swift.

EDIT :

After Rintaro and Martin answers I still have a problem, all my test are done on iOS 8.1 and if I copy the code you brought to me the compiler tells me that I can't write :

let err = MIDIObjectGetStringProperty(midiEndPoint, kMIDIPropertyDisplayName, &property)

Results in 'Unmanaged' is not convertible to 'MIDIObjectRef'. So I added a "&" because MIDIObjectRef is a UnsafeMutablePointer<void>.

let midiEndPoint = MIDIGetSource(0)
var property : Unmanaged<CFString>?
let err = MIDIObjectGetStringProperty(&midiEndPoint, kMIDIPropertyDisplayName, &property)

Now : 'Unmanaged<MIDIEndpoint>' is not convertible to '@lvalue inout $T2'. Finally I had to change the first let to var, without understanding why ?!?

var midiEndPoint = MIDIGetSource(0)
var property : Unmanaged<CFString>?
let err = MIDIObjectGetStringProperty(&midiEndPoint, kMIDIPropertyDisplayName, &property)

The code now compiles and runs but MIDIObjectGetStringProperty returns OSStatus err -50 which corresponds to IOW or from MacErros.h :

paramErr  = -50,  /*error in user parameter list*/

So it seems that the parameters are not the ones that MIDIObjectGetStringProperty is waiting for.

The source "0" does exist on my iPad because MIDIGetNumberOfSources() returns 1. Here's the complete code :

var numDestinations: ItemCount = MIDIGetNumberOfDestinations()
    println("MIDI Destinations : " + String(numDestinations))

    for var i : ItemCount = 0 ; i < numDestinations; ++i{
        var midiEndPoint = MIDIGetDestination(i)

        var property : Unmanaged<CFString>?
        let err = MIDIObjectGetStringProperty(&midiEndPoint, kMIDIPropertyDisplayName, &property)
        if err == noErr {
            let displayName = property!.takeRetainedValue() as String
            println(displayName)
        }else{
            println("error : "+String(err))
        }
   }

Displays :

MIDI Destinations : 1
error : -50

I really don't understand anything ...

UPDATE :

Finally Martin found the solution, it seems that there are two different definitions of MIDIObjectRef in 32 and 64bits architectures. As I run the code on an old iPad 2 my code tried to compile in 32bits mode where MIDIGetSource(i) return value is not convertible into MIDIObjectRef. The solution is to "unsafe cast" the midi endpoint on 32 bits architectures :

#if arch(arm64) || arch(x86_64)
    let midiEndPoint = MIDIGetDestination(i)
#else
    let midiEndPoint = unsafeBitCast(MIDIGetDestination(i), MIDIObjectRef.self)
#endif

... Or to buy a new 64bit device ...

Thank you for the precious help

Eldoria answered 27/11, 2014 at 11:36 Comment(0)
O
13

I have no experience with CoreMIDI and could not test it, but this is how it should work:

let midiEndPoint = MIDIGetSource(0)
var property : Unmanaged<CFString>?
let err = MIDIObjectGetStringProperty(midiEndPoint, kMIDIPropertyDisplayName, &property)
if err == noErr {
    let displayName = property!.takeRetainedValue() as String
    println(displayName)
}

As @rintaro correctly noticed, takeRetainedValue() is the right choice here because it is the callers responsibility to release the string. This is different from the usual Core Foundation memory management rules, but documented in the MIDI Services Reference:

NOTE

When passing a Core Foundation object to a MIDI function, the MIDI function will never consume a reference to the object. The caller always retains a reference which it is responsible for releasing by calling the CFRelease function.

When receiving a Core Foundation object as a return value from a MIDI function, the caller always receives a new reference to the object, and is responsible for releasing it.

See "Unmanaged Objects" in "Working with Cocoa Data Types" for more information.

UPDATE: The above code works only when compiling in 64-bit mode. In 32-bit mode, MIDIObjectRef and MIDIEndpointRef are defined as different kind of pointers. This is no problem in (Objective-)C, but Swift does not allow a direct conversion, an "unsafe cast" is necessary here:

let numSrcs = MIDIGetNumberOfSources()
println("number of MIDI sources: \(numSrcs)")
for srcIndex in 0 ..< numSrcs {
    #if arch(arm64) || arch(x86_64)
    let midiEndPoint = MIDIGetSource(srcIndex)
    #else
    let midiEndPoint = unsafeBitCast(MIDIGetSource(srcIndex), MIDIObjectRef.self)
    #endif
    var property : Unmanaged<CFString>?
    let err = MIDIObjectGetStringProperty(midiEndPoint, kMIDIPropertyDisplayName, &property)
    if err == noErr {
        let displayName = property!.takeRetainedValue() as String
        println("\(srcIndex): \(displayName)")
    } else {
        println("\(srcIndex): error \(err)")
    }
}
Olnton answered 27/11, 2014 at 13:7 Comment(12)
I confirmed this works, but I think we should use takeRetainedValue(), because in this case, we have responsibility to release returned CFString.Prudery
@rintaro: "MIDIObjectGetStringProperty" does not have "Create" or "Copy" in its name. According to the Core Foundation memory management rules that means that the caller is not responsible for releasing the memory. See developer.apple.com/library/mac/documentation/CoreFoundation/…. I think that the "Get Rule" applies here.Olnton
But, in fact, I confirmed it leaks. see: developer.apple.com/library/mac/qa/qa1374/_index.htmlPrudery
Sorry to be a little insistant but I don't know if somebody has seen my other messages, I still have problems to make the code work. I don't know neither what is the best practice between adding a comment or a new "answer" on stackoverflow. Thanks a lotEldoria
@SamT: I have now tested the above code on a Mac with a keyboard connected via USB, and got the output "USB-MIDI". If I disconnect the keyboard then error -50 occurs. – So the code seems to be generally correct.Olnton
Thank you Martin, I've Edited my post with the last tests I made. It doesn't work at the moment.Eldoria
@SamT: See updated answer. I could not test it because I do not have an iOS device with connected MIDI, but it does compile.Olnton
@MartinR I've just tested and now it crashes with : EXC_BAD_ACCESS (code=1, address=0xb13d252e) on the line : let midiEndPoint : MIDIObjectRef = UnsafeMutablePointer(MIDIGetSource(srcIndex).toOpaque()) :-(Eldoria
@SamT: Unfortunately, I cannot test it myself, so I am mainly guessing from the header files how it could work. This is my last (?) attempt: let midiEndPoint = unsafeBitCast(MIDIGetSource(srcIndex), MIDIObjectRef.self)Olnton
@Martin It worked ! This totally crazy and I don't understand the reasons for now. Is this something usual ? Does it comes from an iOS SDK version of something else ? Anyway I can tell you a big "Thanks" because I would certainly never have found this by myself.Eldoria
@SamT: You are welcome! I am glad that this is solved now (and you might consider to "accept" the answer by clicking on the check mark). – I have updated the answer with the working code. I think the reason is that the C MIDI types are not optimally mapped into Swift (in 32-bit mode). It might be worth to file a bug report to Apple.Olnton
Answer accepted, question updated, "bug" reported to Apple ! Thanks againEldoria

© 2022 - 2024 — McMap. All rights reserved.