Swift and C: back and forth

Hello!



Once I was assigned a task for iOS - VPN-client with specific cryptography.



Our company traditionally has its own cryptography, there is a ready-made implementation in C.



In this article I will tell you how I managed to make friends between C and Swift.



For clarity, as an example, we will write a simple function for converting a string in C and call it from Swift.



The main difficulty in such tasks is parameter passing and return values. Let's talk about them. Let's have a function:



uint8_t* flipString(uint8_t* str, int strlen){
  uint8_t* result = malloc(strlen);
  int i;
  int j=0;
  for(i = strlen-1; i>=0; --i){
      result[j] = str[i];
      j++;
  }
  return result;
}


The function takes a pointer to an array of bytes to reverse and the length of the string. We return a pointer to the resulting byte array. Keep malloc in mind. Run through, write down, return.



We create MyCFile.h with a title for our function. Add Bridging-Header.h, in which this same MyCFile.h is connected.



Further type casts. Let's start with a simple one - int is Int32. Then the pointers. There are several pointers in swift. We are interested in UnsafeMutablePointer.



let str = "qwerty"
var array: [UInt8] = Array(str.utf8)
let stringPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: array.count)
stringPointer.initialize(from: &array, count: array.count)
guard let res = flipString(stringPointer, Int32(array.count)) else {return}


Create a UInt8 array (so it's one byte) from the given string. We create a pointer to data of a certain size. We indicate. Call, see what is not nil.



And if everything seems to be simple with the passed pointer, then res is currently of the UnsafeMutablePointer? Type. In a couple of clicks, bypassing StackOverflow, we find the pointee property . Using this property, you can access memory by this pointer. We try to expand the word "qwerty" using this property, and there ... Badum-ts ... "121". Okay, drum roll is superfluous, but the result is not what I would like to get.



Although, if you think about it, everything is logical. In Swift, our res (which the C function returned) is a pointer to an array of Int8. Since ancient times, the pointer points to the first element of the array. So so so. We climb into the ASKII table. 121 is the code for the letter 'y'. Coincidence? I don’t think so. One character was counted.



Further, according to the old Sish tradition, you can go through the array by shifting the pointer and get the following bytes:



let p = res+1
print(p.pointee)


So we get 116, which is the code 't'.



In theory, you can go on like this if you know the size of the allocated memory. And this memory is allocated inside the C code.



In our case, there are no problems, but in a little more serious programs you will have to tinker. Which is what I did.



The solution came to me in the form of good old C structures.



The plan is as follows: create a structure, copy the inverted string and size into the corresponding fields, return a pointer to this structure.



struct FlipedStringStructure {
    void *result;
    int resultSize;
};


Let's rewrite the function like this:



struct FlipedStringStructure* flipStringToStruct(uint8_t* str, int strlen){
    uint8_t* result = malloc(strlen);
    int i;
    int j=0;
    for(i = strlen-1; i>=0; --i){
        result[j] = str[i];
        j++;
    }
    struct FlipedStringStructure* structure;
    structure = malloc(sizeof(struct FlipedStringStructure));
    structure->resultSize=j;
    structure->result = malloc(j);
    memcpy(structure->result,result,j);
    free(result);
    return structure;
}


We note that we allocate memory for both the structure and the string.



Well - it remains to rewrite the challenge. We follow our hands.




func flip(str:String)->String?{
    var array: [UInt8] = Array(str.utf8)
    let stringPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: array.count)
    stringPointer.initialize(from: &array, count: array.count)

    let structPointer = flipStringToStruct(stringPointer, Int32(array.count))
    guard structPointer != nil else{return nil}
    let tmp = structPointer!.pointee
    let res = Data(bytes: tmp.result, count: Int(tmp.resultSize))
    let resStr = String(decoding: res, as: UTF8.self)
    freeMemmory(tmp.result)
    freeSMemmory(structPointer)
    return resStr
}


We still use the pointee property, but now we get the first type element of the structure we created in C code. The beauty of the idea is that we can refer to the data type declared in the C part of the code without unnecessary casting. The first part has already been disassembled. Further step by step: Get a pointer to the structure filled in C (structPointer).



We get access to the memory of this structure. The structure has data and data size.



You can refer to them as fields of a structure created in swift (through a dot).



We collect from this an array of bytes (Data), which we decode into String. Well, do not forget to clean up after ourselves. We create 2 functions:




void freeMemmory(void* s){
    free(s);
}
void freeSMemmory(struct FlipedStringStructure* s){
    free(s);
}


When these functions are called from Swift, we pass either the received pointers or the pointers from the structure as parameters.



freeMemmory(tmp.result)
freeSMemmory(structPointer)


And voila - it's in the bag!



Of course, there is nothing new in this approach, but it allows you to actively work with cross-platform functions and is quite convenient.



Thanks to those who read it.



Link to the project in git - here

EOF



All Articles