Swift y C: ida y vuelta

¡Hola!



Una vez me asignaron una tarea para iOS: cliente VPN con criptografía específica.



Nuestra empresa tradicionalmente tiene su propia criptografía, hay una implementación lista para usar en C.



En este artículo te contaré cómo logré hacer amigos entre C y Swift.



Para mayor claridad, como ejemplo, escribiremos una función simple para convertir una cadena en C y la llamaremos desde Swift.



La principal dificultad en tales tareas es el paso de parámetros y los valores de retorno. Hablemos de ellos. Tengamos una función:



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;
}


La función toma un puntero a una matriz de bytes para invertir y la longitud de la cadena. Devolvemos un puntero a la matriz de bytes resultante. Tenga malloc en mente. Corre, escribe, vuelve.



Creamos MyCFile.h con un título para nuestra función. Agregue Bridging-Header.h, en el que este mismo MyCFile.h está conectado.



Otros tipos de yesos. Comencemos con uno simple: int es Int32. Luego los punteros. Hay varios indicadores en Swift. Estamos interesados ​​en 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}


Cree una matriz UInt8 (por lo que es un byte) a partir de la cadena dada. Creamos un puntero a datos de cierto tamaño. Indicamos. Llama, mira lo que no es nulo.



Y si todo parece simple con el puntero pasado, entonces res es actualmente de tipo UnsafeMutablePointer ?. En un par de clics, sin pasar por StackOverflow, encontramos la propiedad pointee . Con esta propiedad, puede acceder a la memoria mediante este puntero. Intentamos expandir la palabra "qwerty" usando esta propiedad, y ahí ... Badum-ts ... "121". De acuerdo, el redoble de tambores es superfluo, pero el resultado no es el que me gustaría obtener.



Aunque, si lo piensas bien, todo es lógico. En Swift, nuestra res (que devolvió la función C) es un puntero a una matriz de Int8. Desde la antigüedad, el puntero apunta al primer elemento de la matriz. Tan tan tan. Subimos a la mesa ASKII. 121 es el código de la letra 'y'. ¿Coincidencia? No lo creo. Se contó un carácter.



Además, de acuerdo con la antigua tradición de Sish, puede recorrer la matriz, cambiar el puntero y obtener los siguientes bytes:



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


Entonces obtenemos 116, que es el código 't'.



En teoría, puede continuar así si conoce el tamaño de la memoria asignada. Y esta memoria se asigna dentro del código C.



En nuestro caso, no hay problemas, pero en programas un poco más serios tendrás que retocar. Que es lo que hice.



La solución me llegó en forma de viejas estructuras en C.



El plan es el siguiente: cree una estructura, copie la cadena invertida y el tamaño en los campos correspondientes, devuelva un puntero a esta estructura.



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


Reescribamos la función así:



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;
}


Tenga en cuenta que asignamos memoria tanto para la estructura como para la cadena.



Bueno, queda por reescribir el desafío. Seguimos nuestras manos.




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
}


Todavía usamos la propiedad pointee, pero ahora obtenemos el primer elemento del tipo de estructura que creamos en código C. La belleza de la idea es que podemos hacer referencia al tipo de datos declarado en la parte C del código sin conversiones innecesarias. La primera parte ya se ha desmontado. Más en pasos: obtenga un puntero a una estructura rellena en C (structPointer).



Obtenemos acceso a la memoria de esta estructura. La estructura tiene datos y tamaño de datos.



Puede referirse a ellos como campos de una estructura creada en swift (a través de un punto).



Recopilamos de esto una matriz de bytes (Datos), que decodificamos en String. Bueno, no te olvides de limpiar nosotros mismos. Creamos 2 funciones:




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


Cuando estas funciones se llaman desde Swift, les pasamos los punteros recibidos o los punteros de la estructura como parámetros.



freeMemmory(tmp.result)
freeSMemmory(structPointer)


Y listo, ¡está en la bolsa!



Por supuesto, no hay nada nuevo en este enfoque, pero le permite trabajar activamente con funciones multiplataforma y es bastante conveniente.



Gracias a quienes lo leyeron.



Enlace al proyecto en git - aquí

EOF



All Articles