Reimond Hill
Reimond Hill

Reputation: 4760

UInt32 array to String Byte Array in Swift

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

Upvotes: 0

Views: 1260

Answers (4)

Martin R
Martin R

Reputation: 539745

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
}

Upvotes: 1

Reimond Hill
Reimond Hill

Reputation: 4760

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.

Upvotes: 0

Duncan C
Duncan C

Reputation: 131418

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

Upvotes: 1

dede.exe
dede.exe

Reputation: 1310

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.

Upvotes: 2

Related Questions