0

Before I start I would like to apologise if I say something crazy.

I am working on an app that implements a c library. Among others, It shares idArrays.

I have the part decodes an idArray and it was given to me:

func decodeArrayID(aArray:UnsafeMutablePointer<CChar>, aTokenLen:UInt32)->([UInt32], String){

    let arrayCount = Int(aTokenLen / 4)
    var idArrayTemp = [UInt32]()
    var idArrayStringTemp = ""

    for i in 0..<arrayCount{

        let idValue = decodeArrayIDItem(index: i, array: aArray)

        idArrayTemp.append(idValue)
        idArrayStringTemp += "\(idValue) "

    }

    return (idArrayTemp, idArrayStringTemp)
}

func decodeArrayIDItem(index:Int, array:UnsafeMutablePointer<CChar>) -> UInt32{

    var value:UInt32 = UInt32(array[index * 4]) & 0xFF

    value <<= 8
    value |= UInt32(array [index * 4 + 1]) & 0xFF
    value <<= 8
    value |= UInt32(array [index * 4 + 2]) & 0xFF
    value <<= 8
    value |= UInt32(array [index * 4 + 3]) & 0xFF


    return value

}

As we can see the idArray is send through UnsafeMutablePointer AKA UnsafeMutablePointer.

Now I am working with the encoding part. The function will take an array of UInt32 values and will try to convert it into byte array and will convert into a sting for sending it through the library.

So far I have the following code but it doesn't work:

func encodeIDArray(idArray:[UInt32])->String{

    var aIDArray8:[UInt8] = [UInt8]()

    for var value in idArray{

        let count = MemoryLayout<UInt32>.size
        let bytePtr = withUnsafePointer(to: &value) {
            $0.withMemoryRebound(to: UInt8.self, capacity: count) {
                UnsafeBufferPointer(start: $0, count: count)
            }
        }

        aIDArray8 += Array(bytePtr)
    }

    let stringTest = String(data: Data(aIDArray8), encoding: .utf8)

    return stringTest!

}

A test result for the input [1,2] returns "\u{01}\0\0\0\u{02}\0\0\0" and something tells is not quite right...

Thank you

Edited The c functions are

DllExport void STDCALL DvProviderAvOpenhomeOrgPlaylist1EnableActionIdArray(THandle aProvider, CallbackPlaylist1IdArray aCallback, void* aPtr);

where CallbackPlaylist1IdArray is

typedef int32_t (STDCALL *CallbackPlaylist1IdArray)(void* aPtr, IDvInvocationC* aInvocation, void* aInvocationPtr, uint32_t* aToken, char** aArray, uint32_t* aArrayLen);

and the value to aArray is the value that get the Byte array

6
  • Data(aIDArray8) contains the correct bytes (01 00 00 00 02 00 00 00), but converting that to a string makes no sense (and can easily fail for arbitrary data). Commented Oct 9, 2018 at 11:55
  • ok. I understand. The array needs to be send as a UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>? How can I achieve that in swift? Commented Oct 9, 2018 at 12:00
  • So you need to pass the data to a C function? How is that function defined? Commented Oct 9, 2018 at 14:07
  • Yes, please see my updated answer Commented Oct 9, 2018 at 15:20
  • So you call DvProviderAvOpenhomeOrgPlaylist1EnableActionIdArray(), and have to implement the callback in Swift? Commented Oct 9, 2018 at 18:12

4 Answers 4

2

I believe you are in the right way

func encodeIDArray(idArray:[UInt32])->String{

    var aIDArray8:[UInt8] = [UInt8]()

    for var value in idArray{

        let count = MemoryLayout<UInt32>.size
        let bytePtr = withUnsafePointer(to: &value) {
            $0.withMemoryRebound(to: UInt8.self, capacity: count) { v in
                //Just change it to don't return the pointer itself, but the result of the rebound
                UnsafeBufferPointer(start: v, count: count)
            }
        }

        aIDArray8 += Array(bytePtr)
    }

    let stringTest = String(data: Data(aIDArray8), encoding: .utf8)

    return stringTest!

}

Change your test to a some valid value in ASCII Table like this

encodeIDArray(idArray: [65, 66, 67])   // "ABC"

I hope it help you... Good luck and let me know it it works on your case.

Sign up to request clarification or add additional context in comments.

5 Comments

Thank you for answer, it still prints "\u{01}\0\0\0\u{02}\0\0\0\u{03}\0\0\0" for [1,2] .
This values aren't valid values for string. Check the ASCII table or change your strategy if you're transferring an array of raw bytes instead of an array of string convertible bytes.
Sorry maybe was my fault but the idea is to get an array of UInt32 ID's a transform them into a Byte array. These ID's go from 1 to 1024
If your IDs go from 1 to 1024 why don't you use an array of UInt16s instead? That would let you save values from 0 to 65535 (0 -> 2¹⁶-1)
I use this method to turn UInt16 array to UInt8 array. It gave some non alphaNumeric characters. After I remove them I reached desired string.
1

You can copy the [UInt32] array values to the allocated memory without creating an intermediate [Int8] array, and use the bigEndian property instead of bit shifting and masking:

func writeCArrayValue(from pointer:UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?,
                      withUInt32Values array: [UInt32]){

    pointer?.pointee = UnsafeMutablePointer<Int8>.allocate(capacity: MemoryLayout<UInt32>.size * array.count)
    pointer?.pointee?.withMemoryRebound(to: UInt32.self, capacity: array.count) {
        for i in 0..<array.count {
            $0[i] = array[i].bigEndian
        }
    }
}

In the same way you can do the decoding:

func decodeArrayID(aArray:UnsafeMutablePointer<CChar>, aTokenLen:UInt32)->[UInt32] {

    let arrayCount = Int(aTokenLen / 4)
    var idArrayTemp = [UInt32]()

    aArray.withMemoryRebound(to: UInt32.self, capacity: arrayCount) {
        for i in 0..<arrayCount {
            idArrayTemp.append(UInt32(bigEndian: $0[i]))
        }
    }
    return idArrayTemp
}

4 Comments

Thank you for your solution. Although it is great, is no compatible with the decode function.
Sorry. It works! I had a typo. I will make some test and I will let you know and possible i will accept
Great answer! Do you know if the decode function can be do by doing the same principle?
Thank you very much. Nice and simple ;)
1

You can't convert a binary buffer to a string and expect it to work. You should base64 encode your binary data. That IS a valid way to represent binary data as strings.

Consider the following code:

//Utility function that takes a typed pointer to a data buffer an converts it to an array of the desired type of object
func convert<T>(count: Int, data: UnsafePointer<T>) -> [T] {
    let buffer = UnsafeBufferPointer(start: data, count: count);
    return Array(buffer)
}

//Create an array of UInt32 values
let intArray: [UInt32] = Array<UInt32>(1...10)
print("source arrray = \(intArray)")
let arraySize = MemoryLayout<UInt32>.size * intArray.count

//Convert the array to a Data object
let data = Data(bytes: UnsafeRawPointer(intArray),
                count: arraySize)

//Convert the binary Data to base64
let base64String = data.base64EncodedString()

print("Array as base64 data = ", base64String)
if let newData = Data(base64Encoded: base64String) {
    newData.withUnsafeBytes { (bytes: UnsafePointer<UInt32>)->Void in
        let newArray = convert(count:10, data: bytes)
        print("After conversion, newArray = ", newArray)
    }
} else {
    fatalError("Failed to base-64 decode data!")
}

The output of that code is:

source arrray =[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Array as base64 data =  AQAAAAIAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAA==
After conversion, newArray =  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Program ended with exit code: 0

3 Comments

Use the Data function Data.base64EncodedString() to convert to base64 encoding, and Data(base64Encoded:) to convert from a base64 string into a Data object
See the edit to my answer. I provided working code that will convert an array of UInt32 values back and forth to a Base64 string.
Note that the binary representation of any scalar value bigger than Int8/UInt8 will be different on little-endian or big-endian devices, so it is hardware-dependent.
0

Although I really appreciate all the answers I have finally figured out what was happening. I have to say that Duncan's answer was the closest to my problem.

So far I have interpreted char** as String. Turns out that it can be also a pointer to an array (Correct me if I am Wrong!). Converting the array as String gave a format that the library didn't like and it could not be decode on the other end.

The way I ended up doing is:

func encodeIDArray(idArray:[UInt32])->[Int8]{

    var aIDArray8 = [UInt8].init(repeating: 0, count: idArray.count*4)

    for i in 0..<idArray.count{

        aIDArray8[i * 4] = UInt8(idArray[i] >> 24) & 0xff
        aIDArray8[i * 4 + 1] = UInt8(idArray[i] >> 16) & 0xff
        aIDArray8[i * 4 + 2] = UInt8(idArray[i] >> 8) & 0xff
        aIDArray8[i * 4 + 3] = UInt8(idArray[i]) & 0xff

    }

    return aIDArray8.map { Int8(bitPattern: $0) }

}

and then I am assigning the value of the C Variable in swift like that:

let myArray = encodeIDArray(idArray:theArray)
writeCArrayValue(from: aArrayPointer, withValue: myArray)

func writeCArrayValue(from pointer:UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?, withValue array:[Int8]){

    pointer?.pointee = UnsafeMutablePointer<Int8>.allocate(capacity: array.count)
    memcpy(pointer?.pointee, array, array.count)

}

aArrayPointer is a the char** used by the library.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.