Swift e C: para frente e para trás

Olá!



Uma vez fui designado para uma tarefa para iOS - cliente VPN com criptografia específica.



Nossa empresa tradicionalmente tem sua própria criptografia, existe uma implementação pronta em C.



Neste artigo, contarei como consegui fazer amigos entre C e Swift.



Para maior clareza, por exemplo, escreveremos uma função simples para converter uma string em C e chamá-la de Swift.



A principal dificuldade em tais tarefas é a passagem de parâmetros e valores de retorno. Vamos falar sobre eles. Vamos ter uma função:



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


A função leva um ponteiro para uma matriz de bytes para reverter e o comprimento da string. Retornamos um ponteiro para a matriz de bytes resultante. Mantenha o malloc em mente. Percorra, anote, volte.



Criamos MyCFile.h com um título para nossa função. Adicione Bridging-Header.h, no qual este mesmo MyCFile.h está conectado.



Outros tipos de moldes. Vamos começar com um simples - int é Int32. Em seguida, as dicas. Existem várias dicas no Swift. Estamos interessados ​​no 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}


Crie uma matriz UInt8 (portanto, é um byte) a partir da string fornecida. Criamos um ponteiro para dados de um determinado tamanho. Nós indicamos. Ligue, veja o que não é nulo.



E se tudo parecer simples com o ponteiro passado, então res é atualmente do tipo UnsafeMutablePointer? Em alguns cliques, ignorando StackOverflow, encontramos a propriedade pointee . Usando esta propriedade, você pode acessar a memória por este ponteiro. Tentamos expandir a palavra "qwerty" usando esta propriedade, e aí ... Badum-ts ... "121". Ok, o rufar dos tambores é supérfluo, mas o resultado não é o que eu gostaria de obter.



Embora, se você pensar bem, tudo seja lógico. Em Swift, nosso res (que a função C retornou) é um ponteiro para um array de Int8. Desde os tempos antigos, o ponteiro aponta para o primeiro elemento da matriz. Tão tão. Subimos na mesa ASKII. 121 é o código da letra 'y'. Coincidência? Acho que não. Um personagem foi contado.



Além disso, de acordo com a antiga tradição Sish, você pode percorrer a matriz deslocando o ponteiro e obter os seguintes bytes:



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


Então, temos 116, que é o código 't'.



Em teoria, você pode continuar assim se souber o tamanho da memória alocada. E essa memória é alocada dentro do código C.



No nosso caso, não há problemas, mas em programas um pouco mais sérios você terá que mexer. Foi o que eu fiz.



A solução veio a mim na forma de boas e velhas estruturas C.



O plano é o seguinte: criar uma estrutura, copiar a string invertida e dimensionar nos campos correspondentes, retornar um ponteiro para essa estrutura.



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


Vamos reescrever a função assim:



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


Observe que alocamos memória tanto para a estrutura quanto para a string.



Bem - resta reescrever o desafio. Seguimos nossas mãos.




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
}


Ainda usamos a propriedade pointee, mas agora obtemos o primeiro membro do tipo de estrutura que criamos no código C. A beleza da ideia é que podemos nos referir ao tipo de dados declarado na parte C do código sem conversão desnecessária. A primeira parte já foi desmontada. Mais adiante nas etapas: Obtenha um ponteiro para uma estrutura preenchida em C (structPointer).



Temos acesso à memória dessa estrutura. A estrutura possui dados e tamanho de dados.



Você pode se referir a eles como campos de uma estrutura criada em swift (por meio de um ponto).



Coletamos disso uma matriz de bytes (Dados), que decodificamos em String. Bem, não se esqueça de limpar depois de nós mesmos. Criamos 2 funções:




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


Quando essas funções são chamadas do Swift, passamos os ponteiros recebidos ou os ponteiros da estrutura como parâmetros.



freeMemmory(tmp.result)
freeSMemmory(structPointer)


E voila - está na bolsa!



Claro, não há nada de novo nessa abordagem, mas ela permite que você trabalhe ativamente com funções de plataforma cruzada e é bastante conveniente.



Obrigado a quem o leu.



Link para o projeto no git - aqui

EOF



All Articles