[C, Swift] Swift и Си: туда и обратно
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Всем привет!
Однажды мне поручили задачу под iOS — VPN-client со специфической криптографией.
Криптография в нашей компании традиционно своя, есть готовая реализация на Си.
В этой статье я расскажу, как мне удалось подружить Си и Swift.
Для наглядности в качестве примера напишем простую функцию преобразования строки на Си и вызовем ее из Swift.
Основная сложность в таких задачах — это передача параметров и возвращаемые значения. О них и поговорим. Пусть у нас есть функция:
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;
}
Функция принимает указатель на массив байт для реверса и длину строки. Возвращаем мы указатель на полученный массив байт. В уме держим malloc. Пробегаемся-записываем-возвращаем.
Создаем MyCFile.h с заголовком для нашей функции. Добавляем Bridging-Header.h, в котором подключен этот самый MyCFile.h.
Далее приведения типов. Начнем с простого — int у нас это Int32. Потом указатели. Указателей в swift несколько. Нас интересует 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}
Создаем массив UInt8(так то это один байт) из данной строки. Создаем указатель на данные определенного размера. Указываем. Вызываем, смотрим что не nil.
И если с передаваемым указателем все вроде бы просто, то res у нас на текущий момент имеет тип UnsafeMutablePointer?. В пару кликов, минуя StackOverflow, находим свойство указателя pointee. При помощи этого свойства можно получить доступ к памяти по этому указателю. Пробуем развернуть слово «qwerty» используя это свойство, а там… Бадум-тс… «121». Ладно, барабанная дробь — лишняя, но результат не тот который хотелось бы получить.
Хотя, если подумать, все логично. В Swift наш res(который вернула си функция) это указатель на массив, состоящий из Int8. Указатель издревле указывает на первый элемент массива. Так-так-так. Лезем в таблицу ASKII. 121 это код буквы 'y'. Совпадение? Не думаю. Один символ считали.
Далее, по старой сишной традиции, можно идти по массиву, смещая указатель, и получать следующие байты:
let p = res+1
print(p.pointee)
Так получаем 116, что есть код 't'.
Теоретически так можно идти и дальше, если знаешь размер выделенной памяти. А память эта выделяется внутри Си кода.
В нашем случаи проблем нет, а вот в чуть более серьезных программах придется повозиться. Чем я и занялся.
Решение пришло ко мне в виде старых добрых структур из Си.
План следующий: создаем структуру, копируем в нее перевернутую строку и размер в соответствующие поля, возвращаем указатель на это структуру.
struct FlipedStringStructure {
void *result;
int resultSize;
};
Функцию перепишем вот в такой вид:
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;
}
Отмечаем, что память мы выделяем и под структуру и под строку.
Что ж — осталось переписать вызов. Следим за руками.
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
}
Мы все так же используем свойство pointee, но теперь мы получаем первый элемент типа структуры, созданной нами в си коде. Прелесть идеи в том, что мы можем обратиться к типу данных, объявленному в си части кода без лишнего приведения типов. Первую часть уже разбирали. Дальше по шагам: Получаем указатель на заполненную в си структуру(structPointer).
Получаем доступ к памяти этой структуры. В структуре есть данные и размер данных.
Обращаться к ним можно как к полям структуры созданной в swift (через точку).
Собираем из этого массив байт (Data), который декодируем в String. Ну и не забываем прибраться за собой. Создаем 2 функции:
void freeMemmory(void* s){
free(s);
}
void freeSMemmory(struct FlipedStringStructure* s){
free(s);
}
Когда эти функции вызываются из Swift, параметрами мы в них передаем либо полученные указатели, либо указатели из структуры.
freeMemmory(tmp.result)
freeSMemmory(structPointer)
И вуаля — дело в шляпе!
Ничего нового в таком подходе, конечно же нет, однако он позволяет активно работать с кроссплатформенными функциями и довольно удобен.
Спасибо тем кто дочитал.
Ссылка на проект в git — тут
EOF
===========
Источник:
habr.com
===========
Похожие новости:
- [DevOps] Масштабирование CI/CD монорепозитория
- [*nix, Open source] FOSS News №25 – обзор новостей свободного и открытого ПО за 13–19 июля 2020 года
- [*nix] Массивы bash
- [Open source, Программирование, Python, Социальные сети и сообщества] Spothiefy: как переехать из Яндекс.Музыки быстро, бесплатно
- В Chrome экспериментируют с прекращением автозаполнения форм, отправляемых без шифрования
- [CSS, Веб-дизайн] Чего я не знал о CSS (перевод)
- Выпуск файлового менеджера Midnight Commander 4.8.25
- [C++, ООП] Аккуратнее с vtable, или как выстрелить себе в ногу обновлением библиотеки
- Релиз системы сборки CMake 3.18
- [C, C++, D, Отладка] Баги, которые разрушили ваш замок (перевод)
Теги для поиска: #_c, #_swift, #_swift, #_c, #_c, #_swift
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 00:30
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Всем привет! Однажды мне поручили задачу под iOS — VPN-client со специфической криптографией. Криптография в нашей компании традиционно своя, есть готовая реализация на Си. В этой статье я расскажу, как мне удалось подружить Си и Swift. Для наглядности в качестве примера напишем простую функцию преобразования строки на Си и вызовем ее из Swift. Основная сложность в таких задачах — это передача параметров и возвращаемые значения. О них и поговорим. Пусть у нас есть функция: 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; } Функция принимает указатель на массив байт для реверса и длину строки. Возвращаем мы указатель на полученный массив байт. В уме держим malloc. Пробегаемся-записываем-возвращаем. Создаем MyCFile.h с заголовком для нашей функции. Добавляем Bridging-Header.h, в котором подключен этот самый MyCFile.h. Далее приведения типов. Начнем с простого — int у нас это Int32. Потом указатели. Указателей в swift несколько. Нас интересует 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} Создаем массив UInt8(так то это один байт) из данной строки. Создаем указатель на данные определенного размера. Указываем. Вызываем, смотрим что не nil. И если с передаваемым указателем все вроде бы просто, то res у нас на текущий момент имеет тип UnsafeMutablePointer?. В пару кликов, минуя StackOverflow, находим свойство указателя pointee. При помощи этого свойства можно получить доступ к памяти по этому указателю. Пробуем развернуть слово «qwerty» используя это свойство, а там… Бадум-тс… «121». Ладно, барабанная дробь — лишняя, но результат не тот который хотелось бы получить. Хотя, если подумать, все логично. В Swift наш res(который вернула си функция) это указатель на массив, состоящий из Int8. Указатель издревле указывает на первый элемент массива. Так-так-так. Лезем в таблицу ASKII. 121 это код буквы 'y'. Совпадение? Не думаю. Один символ считали. Далее, по старой сишной традиции, можно идти по массиву, смещая указатель, и получать следующие байты: let p = res+1
print(p.pointee) Так получаем 116, что есть код 't'. Теоретически так можно идти и дальше, если знаешь размер выделенной памяти. А память эта выделяется внутри Си кода. В нашем случаи проблем нет, а вот в чуть более серьезных программах придется повозиться. Чем я и занялся. Решение пришло ко мне в виде старых добрых структур из Си. План следующий: создаем структуру, копируем в нее перевернутую строку и размер в соответствующие поля, возвращаем указатель на это структуру. struct FlipedStringStructure {
void *result; int resultSize; }; Функцию перепишем вот в такой вид: 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; } Отмечаем, что память мы выделяем и под структуру и под строку. Что ж — осталось переписать вызов. Следим за руками. 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 } Мы все так же используем свойство pointee, но теперь мы получаем первый элемент типа структуры, созданной нами в си коде. Прелесть идеи в том, что мы можем обратиться к типу данных, объявленному в си части кода без лишнего приведения типов. Первую часть уже разбирали. Дальше по шагам: Получаем указатель на заполненную в си структуру(structPointer). Получаем доступ к памяти этой структуры. В структуре есть данные и размер данных. Обращаться к ним можно как к полям структуры созданной в swift (через точку). Собираем из этого массив байт (Data), который декодируем в String. Ну и не забываем прибраться за собой. Создаем 2 функции: void freeMemmory(void* s){
free(s); } void freeSMemmory(struct FlipedStringStructure* s){ free(s); } Когда эти функции вызываются из Swift, параметрами мы в них передаем либо полученные указатели, либо указатели из структуры. freeMemmory(tmp.result)
freeSMemmory(structPointer) И вуаля — дело в шляпе! Ничего нового в таком подходе, конечно же нет, однако он позволяет активно работать с кроссплатформенными функциями и довольно удобен. Спасибо тем кто дочитал. Ссылка на проект в git — тут EOF =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 00:30
Часовой пояс: UTC + 5