Swift et C: aller et retour

salut!



Une fois, on m'a assigné une tâche pour iOS - client VPN avec cryptographie spécifique.



Notre entreprise a traditionnellement sa propre cryptographie, il existe une implémentation prête à l'emploi en C.



Dans cet article je vais vous raconter comment j'ai réussi à me faire des amis entre C et Swift.



Pour plus de clarté, à titre d'exemple, nous allons écrire une fonction simple pour convertir une chaîne en C et l'appeler depuis Swift.



La principale difficulté de ces tâches est la transmission de paramètres et les valeurs de retour. Parlons-en. Ayons une fonction:



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 fonction prend un pointeur vers un tableau d'octets à inverser et la longueur de la chaîne. Nous renvoyons un pointeur vers le tableau d'octets résultant. Gardez malloc à l'esprit. Courez, notez, revenez.



Nous créons MyCFile.h avec un titre pour notre fonction. Ajoutez Bridging-Header.h, dans lequel ce même MyCFile.h est connecté.



Autres types de moulages. Commençons par un simple - int est Int32. Puis les pointeurs. Il y a plusieurs pointeurs dans swift. Nous sommes intéressés par 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}


Créez un tableau UInt8 (donc un octet) à partir de la chaîne donnée. Nous créons un pointeur vers des données d'une certaine taille. Nous indiquons. Appelez, voyez ce qui n'est pas nul.



Et si tout semble simple avec le pointeur passé, res est actuellement du type UnsafeMutablePointer ?. En quelques clics, en contournant StackOverflow, nous trouvons la propriété pointee . En utilisant cette propriété, vous pouvez accéder à la mémoire par ce pointeur. Nous essayons d'élargir le mot "qwerty" en utilisant cette propriété, et là ... Badum-ts ... "121". Bon, le roulement de tambour est superflu, mais le résultat n'est pas ce que j'aimerais obtenir.



Bien que, si vous y réfléchissez, tout est logique. Dans Swift, notre res (que la fonction C a renvoyée) est un pointeur vers un tableau de Int8. Depuis les temps anciens, le pointeur pointe vers le premier élément du tableau. Bon bon bon. Nous montons dans la table ASKII. 121 est le code de la lettre «y». Coïncidence? Je ne pense pas. Un caractère a été compté.



De plus, selon l'ancienne tradition Sish, vous pouvez parcourir le tableau en déplaçant le pointeur et obtenir les octets suivants:



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


Nous obtenons donc 116, qui est le code «t».



En théorie, vous pouvez continuer ainsi si vous connaissez la taille de la mémoire allouée. Et cette mémoire est allouée à l'intérieur du code C.



Dans notre cas, il n'y a pas de problèmes, mais dans des programmes un peu plus sérieux, vous devrez bricoler. C'est ce que j'ai fait.



La solution m'est venue sous la forme de bonnes vieilles structures en C.



Le plan est le suivant: créer une structure, copier la chaîne inversée et la taille dans les champs correspondants, renvoyer un pointeur vers cette structure.



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


Réécrivons la fonction comme ceci:



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


Nous notons que nous allouons de la mémoire à la fois pour la structure et la chaîne.



Eh bien, il reste à réécrire le défi. Nous suivons nos mains.




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
}


Nous utilisons toujours la propriété pointee, mais nous obtenons maintenant le premier élément du type de structure que nous avons créé en code C. La beauté de l'idée est que nous pouvons faire référence au type de données déclaré dans la partie C du code sans conversion inutile. La première partie a déjà été démontée. Étape par étape supplémentaire: Obtenez un pointeur sur la structure remplie en C (structPointer).



Nous avons accès à la mémoire de cette structure. La structure a des données et une taille de données.



Vous pouvez les désigner comme des champs d'une structure créée en swift (via un point).



Nous collectons à partir de cela un tableau d'octets (Data), que nous décodons en String. Eh bien, n'oubliez pas de nettoyer après nous-mêmes. Nous créons 2 fonctions:




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


Lorsque ces fonctions sont appelées depuis Swift, nous leur passons soit les pointeurs reçus, soit les pointeurs de la structure en tant que paramètres.



freeMemmory(tmp.result)
freeSMemmory(structPointer)


Et voila - c'est dans le sac!



Bien sûr, il n'y a rien de nouveau dans cette approche, mais elle vous permet de travailler activement avec des fonctions multiplateformes et est assez pratique.



Merci à ceux qui l'ont lu.



Lien vers le projet dans git - ici

EOF



All Articles