access element of fixed length C array in swift
Asked Answered
E

4

6

I'm trying to convert some C code to swift. (Why? - to use CoreMIDI in OS-X in case you asked)

The C code is like this

void printPacketInfo(const MIDIPacket* packet) {
   int i;
   for (i=0; i<packet->length; i++) {
      printf("%d ", packet->data[i]);
   }
 }

And MIDIPacket is defined like this

struct MIDIPacket
 {
    MIDITimeStamp           timeStamp;
    UInt16                  length;
    Byte                    data[256];
 };

My Swift is like this

func printPacketInfo(packet: UnsafeMutablePointer<MIDIPacket>){
    // print some things
    print("length", packet.memory.length)
    print("time", packet.memory.timeStamp)
    print("data[0]", packet.memory.data.1)
    for i in 0 ..< packet.memory.length {
      print("data", i, packet.memory.data[i])
    }
  }

But this gives a compiler error

error: type '(UInt8, UInt8, .. cut .. UInt8, UInt8, UInt8)' has no subscript members

So how can I dereference the I'th element of a fixed size array?

Eustatius answered 13/1, 2016 at 20:35 Comment(3)
Which line # is the compiler complaining about?Forage
It's the packet.memory.data[i] line.Eustatius
packet.memory.data represents a tuple, not an array. see my attempt how to solve you trouble in my answer.Spectacled
S
3

in your case you could try to use something like this ...

// this is tuple with 8 Int values, in your case with 256  Byte (UInt8 ??) values
var t = (1,2,3,4,5,6,7,8)
t.0
t.1
// ....
t.7
func arrayFromTuple<T,R>(tuple:T) -> [R] {
    let reflection = Mirror(reflecting: tuple)
    var arr : [R] = []
    for i in reflection.children {
        // better will be to throw an Error if i.value is not R
        arr.append(i.value as! R)
    }
    return arr
}

let arr:[Int] = arrayFromTuple(t)
print(arr) // [1, 2, 3, 4, 5, 6, 7, 8]

...

let t2 = ("alfa","beta","gama")
let arr2:[String] = arrayFromTuple(t2)
arr2[1] // "beta"
Spectacled answered 13/1, 2016 at 21:14 Comment(0)
S
1

The error message is a hint: it shows that MIDIPacket.data is imported not as an array, but as a tuple. (Yes, that's how all fixed length arrays import in Swift.) You seem to have noticed this in the preceding line:

print("data[0]", packet.memory.data.1)

Tuples in Swift are very static, so there isn't a way to dynamically access a tuple element. Thus, in some sense the only "safe" or idiomatic way to print your packet (in the way that you're hinting at) would be 256 lines of code (or up to 256, since the packet's length field tells you when it's safe to stop):

print("data[1]", packet.memory.data.2)
print("data[2]", packet.memory.data.3)
print("data[3]", packet.memory.data.4)
/// ...
print("data[254]", packet.memory.data.255)
print("data[255]", packet.memory.data.256)

Clearly that's not a great solution. Using reflection, per user3441734's answer, is one (cumbersome) alternative. Unsafe memory access, per your own answer (via jckarter), is another (but as the name of the API says, it's "unsafe"). And, of course, you can always work with the packet through (Obj)C.

If you need to do something beyond printing the packet, you can extend the UnsafePointer-based solution to convert it to an array like so:

extension MIDIPacket {
    var dataBytes: [UInt8] {
        mutating get {
            return withUnsafePointer(&data) { tuplePointer in
                let elementPointer = UnsafePointer<UInt8>(tuplePointer)
                return (0..<Int(length)).map { elementPointer[$0] }
            }
        }
    }
}

Notice that this uses the packet's existing length property to expose an array that has only as many valid bytes as the packet claims to have (rather than filling up the rest of a 256-element array with zeroes). This does allocate memory, however, so it might not be good for the kinds of real-time run conditions you might be using CoreMIDI in.

Smart answered 13/1, 2016 at 20:53 Comment(1)
"there isn't a way to dynamically access a tuple element ...". I tried to do it for his specific needs, where all values in tuple have the same type. see my answer.Spectacled
E
1

This was suggested by https://gist.github.com/jckarter/ec630221890c39e3f8b9

func printPacketInfo(packet: UnsafeMutablePointer<MIDIPacket>){
     // print some things
    print("length", packet.memory.length)
    print("time", packet.memory.timeStamp)
    let len = Int(packet.memory.length)
    withUnsafePointer(&packet.memory.data) { p in
       let p = UnsafeMutablePointer<UInt8>(p)
       for i:Int in 0 ..< len {
          print(i, p[i])
     }
   }
}

This is horrible - I hope the compiler turns this nonsense into some good code

Eustatius answered 13/1, 2016 at 22:17 Comment(2)
I've amended my answer to expand on this and other options in ways that might be useful. But the best way to tell Apple you'd like to see the importing of fixed length arrays improved is to file a bug, or contribute to the Swift open-source community.Smart
There is a proposal on the Swift evolution list for dealing with this. mail-archive.com/[email protected]/msg00885.htmlMetametabel
F
0

Should this: for i in 0 ..< packet.memory.length

Be this? for i in 0 ..< packet.memory.data.length

Forage answered 13/1, 2016 at 20:53 Comment(1)
packet.memory.data represents a tuple ... see my answer, i tried to convert it to an array.Spectacled

© 2022 - 2024 — McMap. All rights reserved.