Einmal wurde mir eine Aufgabe für iOS zugewiesen - VPN-Client mit spezifischer Kryptographie.
Unser Unternehmen hat traditionell eine eigene Kryptographie, es gibt eine vorgefertigte Implementierung in C.
In diesem Artikel werde ich Ihnen erzählen, wie ich es geschafft habe, Freunde zwischen C und Swift zu finden.
Zur Verdeutlichung schreiben wir als Beispiel eine einfache Funktion zum Konvertieren eines Strings in C und rufen ihn von Swift aus auf.
Die Hauptschwierigkeit bei solchen Aufgaben ist die Übergabe von Parametern und die Rückgabe von Werten. Reden wir über sie. Lassen Sie uns eine Funktion haben:
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;
}
Die Funktion verwendet einen Zeiger auf ein Array von Bytes, die umgekehrt werden sollen, und die Länge der Zeichenfolge. Wir geben einen Zeiger auf das resultierende Byte-Array zurück. Denken Sie an Malloc. Durchlaufen, aufschreiben, zurückkehren.
Wir erstellen MyCFile.h mit einem Titel für unsere Funktion. Fügen Sie Bridging-Header.h hinzu, in dem dieselbe MyCFile.h verbunden ist.
Weitere Typabgüsse. Beginnen wir mit einer einfachen Sache - int ist Int32. Dann die Zeiger. Es gibt mehrere Zeiger in Swift. Wir sind an UnsafeMutablePointer interessiert.
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}
Erstellen Sie aus der angegebenen Zeichenfolge ein UInt8-Array (also ein Byte). Wir erstellen einen Zeiger auf Daten einer bestimmten Größe. Wir geben an. Rufen Sie an, sehen Sie, was nicht Null ist.
Und wenn mit dem übergebenen Zeiger alles einfach zu sein scheint, ist res derzeit vom Typ UnsafeMutablePointer?. Mit ein paar Klicks unter Umgehung von StackOverflow finden wir die Eigenschaft pointee . Mit dieser Eigenschaft können Sie mit diesem Zeiger auf den Speicher zugreifen. Wir versuchen, das Wort "qwerty" mit dieser Eigenschaft zu erweitern, und dort ... Badum-ts ... "121". Okay, Drum Roll ist überflüssig, aber das Ergebnis ist nicht das, was ich gerne hätte.
Wenn Sie darüber nachdenken, ist alles logisch. In Swift ist unser res (das die C-Funktion zurückgegeben hat) ein Zeiger auf ein Array von Int8. Seit der Antike zeigt der Zeiger auf das erste Element des Arrays. So so so. Wir klettern in den ASKII-Tisch. 121 ist der Code für den Buchstaben 'y'. Zufall? Ich glaube nicht. Ein Zeichen wurde gezählt.
Gemäß der alten Sish-Tradition können Sie das Array durch Verschieben des Zeigers durchlaufen und die folgenden Bytes abrufen:
let p = res+1
print(p.pointee)
Wir erhalten also 116, was der Code 't' ist.
Theoretisch können Sie so weitermachen, wenn Sie die Größe des zugewiesenen Speichers kennen. Und dieser Speicher ist im C-Code zugeordnet.
In unserem Fall gibt es keine Probleme, aber in etwas ernsteren Programmen müssen Sie basteln. Welches ist, was ich getan habe.
Die Lösung kam zu mir in Form guter alter C-Strukturen.
Der Plan lautet wie folgt: Erstellen Sie eine Struktur, kopieren Sie die invertierte Zeichenfolge und Größe in die entsprechenden Felder und geben Sie einen Zeiger auf diese Struktur zurück.
struct FlipedStringStructure {
void *result;
int resultSize;
};
Schreiben wir die Funktion folgendermaßen um:
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;
}
Wir stellen fest, dass wir sowohl der Struktur als auch der Zeichenfolge Speicher zuweisen.
Nun - es bleibt, die Herausforderung neu zu schreiben. Wir folgen unseren Händen.
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
}
Wir verwenden immer noch die Pointee-Eigenschaft, aber jetzt erhalten wir das erste Typelement der Struktur, die wir in C-Code erstellt haben. Das Schöne an der Idee ist, dass wir ohne unnötiges Casting auf den im C-Teil des Codes deklarierten Datentyp verweisen können. Der erste Teil wurde bereits zerlegt. Weitere Schritte: Abrufen eines Zeigers auf eine in C ausgefüllte Struktur (structPointer).
Wir erhalten Zugriff auf die Erinnerung an diese Struktur. Die Struktur hat Daten und Datengröße.
Sie können sie als Felder einer Struktur bezeichnen, die schnell (durch einen Punkt) erstellt wurde.
Wir sammeln daraus ein Array von Bytes (Daten), die wir in String dekodieren. Vergessen Sie nicht, nach uns selbst aufzuräumen. Wir erstellen 2 Funktionen:
void freeMemmory(void* s){
free(s);
}
void freeSMemmory(struct FlipedStringStructure* s){
free(s);
}
Wenn diese Funktionen von Swift aufgerufen werden, übergeben wir entweder die empfangenen Zeiger oder die Zeiger aus der Struktur als Parameter.
freeMemmory(tmp.result)
freeSMemmory(structPointer)
Und voila - es ist in der Tasche!
Natürlich ist dieser Ansatz nichts Neues, aber er ermöglicht Ihnen die aktive Arbeit mit plattformübergreifenden Funktionen und ist sehr praktisch.
Vielen Dank an diejenigen, die es gelesen haben.
Link zum Projekt in Git - hier
EOF