C/C++ из Python (Kivy, ctypes) на iOS

от автора

main

Ранее я писал статьи C/C++ из Python (ctypes), C/C++ из Python (Kivy, ctypes) на Android. В них описывается процесс запуска на Linux и Android. Теперь поговорим как тоже самое сделать на iOS. В этой статье речь пойдет о сборке, необходимых инструментах, механизмах отладки и установки.

Код на C/C++ ни каких изменений не претерпел. Подробнее ознакомиться с описанием кода можно по ссылке на статью приведенной в начале данного материала.

Инструменты сборки

Сборка проводилась на виртуальной машине под управлением Mac OS Ventura 13, собрать на не Mac OS системах не получится. MacBook или iMac у меня нет. Статья по установке macOS на VMware Workstation.

Инструменты:

  • Xcode
  • App Developer
  • brew

Xcode & App Developer устанавливаем через App Store.

Далее выполнить:

xcode-select --install  # Xcode is not fully installed. Run this to complete the install: xcodebuild -runFirstLaunch

Установка brew:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

И доустанавливаем все необходимые инструменты:

brew install autoconf automake libtool pkg-config sdl2 sdl2_image sdl2_ttf sdl2_mixer gstreamer brew link libtool

C

test.c:

#include "test.h"  int a = 5; double b = 5.12345; char c = 'X';  int  func_ret_int(int val) {      return val; }   double  func_ret_double(double val) {      return val; }   char * func_ret_str(char *val) {      return val; }   char func_many_args(int val1, double val2, char val3, short val4) {      return val3; }   test_st_t * func_ret_struct(test_st_t *test_st) {              return test_st; }  

test.h:

#ifndef _TEST_H_ #define _TEST_H_  #ifdef  __cplusplus extern "C" { #endif  typedef struct test_st_s test_st_t;  extern int a; extern double b; extern char c;  int func_ret_int(int val); double func_ret_double(double val); char *func_ret_str(char *val); char func_many_args(int val1, double val2, char val3, short val4); test_st_t *func_ret_struct(test_st_t *test_st);  struct test_st_s {     int val1;     double val2;     char val3; };  #ifdef  __cplusplus } #endif  #endif  /* _TEST_H_ */

Как компилировать :

clang

Для устройств на архитектуре arm64:

clang -c -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk -arch arm64 -fPIC -I./src/c  -o src/python/ios/libs/arm64//test.o ./src/c/test.c clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk -arch arm64 -shared -o src/python/ios/libs/arm64//libtest.a src/python/ios/libs/arm64//test.o strip -x src/python/ios/libs/arm64//libtest.a

Можно собрать под более старые архитектуры armv6, armv7.

Для эмулятора, архитектура x86_64:

clang -c -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk -arch x86_64 -fPIC -I./src/c  -o src/python/ios/libs/x86_64//test.o ./src/c/test.c clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk -arch x86_64 -shared -o src/python/ios/libs/x86_64//libtest.a src/python/ios/libs/x86_64//test.o  strip -x src/python/ios/libs/x86_64//libtest.a

Далее соберем одну библиотеку которая будет работать на всех нужных нам архитектурах:

lipo -arch armv7 src/python/ios/libs/armv7/libtest.a -arch arm64 src/python/ios/libs/arm64/libtest.a  -arch x86_64 src/python/ios/libs/x86_64/libtest.a  -create -output src/python/ios/libs/libtest.a

Посмотрим что получилось

bash-3.2$ lipo -info src/python/ios/libs/libtest.a  Architectures in the fat file: src/python/ios/libs/libtest.a are: armv7 x86_64 arm64  bash-3.2$ 

На этом работа с библиотекой на C закончена.

C++

test.cpp:

#include "test.hpp"  /**  * Методы класса  **/ std::string test::ret_str(std::string val) {     std::cout << "C get ret_str: " << val << std::endl;     return val; }  int test::ret_int(int val) {     std::cout << "C get ret_int: " << val << std::endl;     return val; }  double test::ret_double(double val) {     std::cout << "C get ret_double: " << val << std::endl;     return val; }  /**  * Обвязка C для методов класса C++  **/  // Создаем класс test, и получаем указатель на него. test *test_new() {     return new test(); }  // Удаляем класс test. void test_del(test *test) {     delete test; }  /*  * Вызов методов класса.  */  // Обертка над методом ret_str char *test_ret_str(test *test, char *val) {     // char * к std::string     std::string str = test->ret_str(std::string(val));      // std::string к char *     char *ret = new char[str.length() + 1];     strcpy(ret, str.c_str());      return ret; }  // Обертка над методом ret_int int test_ret_int(test *test, int val) {     return test->ret_int(val); }  // Обертка над методом ret_double double test_ret_double(test *test, double val) {     return test->ret_double(val); }  /*  * Получение переменных класса.  */  // Обертка для получения a int test_get_a(test *test) {     return test->a; }  // Обертка для получения b double test_get_b(test *test) {     return test->b; }  // Обертка для получения c char test_get_c(test *test) {     return test->c; } 

test.hpp:

#ifndef _TEST_HPP_ #define _TEST_HPP_  #include <iostream> #include <string>  class test { public:     int a = 5;     double b = 5.12345;     char c = 'X';      std::string ret_str(std::string val);     int ret_int(int val);     double ret_double(double val); };  #ifdef __cplusplus extern "C" { #endif      test *test_new();     void test_del(test *test);     char *test_ret_str(test *test, char *val);     int test_ret_int(test *test, int val);     double test_ret_double(test *test, double val);      int test_get_a(test *test);     double test_get_b(test *test);     char test_get_c(test *test);  #ifdef __cplusplus } #endif  #endif

Как компилировать :

clang++

Для устройств на архитектуре arm64:

clang++ -c -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk -arch arm64  -fPIC -I./src/c  -o src/python/ios/libs/arm64//test.pp.o ./src/c/test.cpp clang++ -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk -arch arm64  -shared -o src/python/ios/libs/arm64//libtestpp.a src/python/ios/libs/arm64//test.pp.o  strip -x src/python/ios/libs/arm64//libtestpp.a

Можно собрать под более старые архитектуры armv6, armv7.

Для эмулятора, архитектура x86_64:

clang++ -c -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk -arch x86_64  -fPIC -I./src/c  -o src/python/ios/libs/x86_64//test.pp.o ./src/c/test.cpp clang++ -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk -arch x86_64  -shared -o src/python/ios/libs/x86_64//libtestpp.a src/python/ios/libs/x86_64//test.pp.o  strip -x src/python/ios/libs/x86_64//libtestpp.a

Далее соберем одну библиотеку которая будет работать на всех нужных нам архитектурах:

lipo -arch armv7 src/python/ios/libs/armv7/libtestpp.a -arch arm64 src/python/ios/libs/arm64/libtestpp.a  -arch x86_64 src/python/ios/libs/x86_64/libtestpp.a  -create -output src/python/ios/libs/libtestpp.a

Посмотрим что получилось

bash-3.2$ lipo -info src/python/ios/libs/libtestpp.a  Architectures in the fat file: src/python/ios/libs/libtestpp.a are: armv7 x86_64 arm64  bash-3.2$ 

Python

Здесь нам понадобится фреймворк Kivy.

Установка kivy

pip3 install kivy-ios Cython

Установив kivy приступим к созданию тестовой программы. Создадим папку под нее:

mkdir ios cd ios

Теперь создадим main.py, это точка запуска нашей программы.

touch main.py

И заполним его:

#!/usr/bin/python3 #-*- coding: utf-8 -*-  import os import sys import ctypes, ctypes.util  import kivy kivy.require("1.9.1") from kivy.app import App from kivy.uix.button import Button  # class in which we are creating the button class ButtonApp(App):      def build(self):         # use a (r, g, b, a) tuple         btn = Button(text ="Push Me !",                    font_size ="20sp",                    background_color = (1, 1, 1, 1),                    color = (1, 1, 1, 1),                    size_hint = (.2, .1),                    pos_hint = {'x':.4, 'y':.45})          # bind() use to bind the button to function callback         btn.bind(on_press = self.callback)         return btn      # callback function tells when button pressed     def callback(self, event):         exit(0)  ## #  Старт. ## if __name__ == "__main__":      test = None     # Загрузка библиотеки     try:         test = ctypes.CDLL('libs/libtest.a')     except OSError as e:         print(str(e))         exit(0)      ###     ## C     ###      print("ctypes\n")     print("C\n")      ##     # Работа с функциями     ##      # Указываем, что функция возвращает int     test.func_ret_int.restype = ctypes.c_int     # Указываем, что функция принимает аргумент int     test.func_ret_int.argtypes = [ctypes.c_int, ]      # Указываем, что функция возвращает double     test.func_ret_double.restype = ctypes.c_double     # Указываем, что функция принимает аргумент double     test.func_ret_double.argtypes = [ctypes.c_double]      # Указываем, что функция возвращает char *     test.func_ret_str.restype = ctypes.c_char_p     # Указываем, что функция принимает аргумент char *     test.func_ret_str.argtypes = [ctypes.POINTER(ctypes.c_char), ]      # Указываем, что функция возвращает char     test.func_many_args.restype = ctypes.c_char     # Указываем, что функция принимает аргументы int, double. char, short     test.func_many_args.argtypes = [ctypes.c_int, ctypes.c_double, ctypes.c_char, ctypes.c_short]      print('Работа с функциями:')     print('ret func_ret_int: ', test.func_ret_int(101))     print('ret func_ret_double: ', test.func_ret_double(12.123456789))     # Необходимо строку привести к массиву байтов, и массив байтов к строке.     print('ret func_ret_str: ', test.func_ret_str('Hello!'.encode('utf-8')).decode("utf-8"))     print('ret func_many_args: ', test.func_many_args(15, 18.1617, 'X'.encode('utf-8'), 32000).decode("utf-8"))      ##     # Работа с переменными     ##      print('\nРабота с переменными:')     # Указываем, что переменная типа int     a = ctypes.c_int.in_dll(test, "a")     print('ret a: ', a.value)      # Изменяем значение переменной.     a.value = 22     a = ctypes.c_int.in_dll(test, "a")     print('new a: ', a.value)      # Указываем, что переменная типа double     b = ctypes.c_double.in_dll(test, "b")     print('ret b: ', b.value)      # Указываем, что переменная типа char     c = ctypes.c_char.in_dll(test, "c")     print('ret c: ', c.value.decode("utf-8"))      ##     # Работа со структурами     ##      print('\nРабота со структурами:')      # Объявляем структуру в Python аналогичную в C     class test_st_t(ctypes.Structure):         _fields_ = [('val1', ctypes.c_int),                     ('val2', ctypes.c_double),                     ('val3', ctypes.c_char)]      # Указываем, что функция возвращает test_st_t *     test.func_ret_struct.restype = ctypes.POINTER(test_st_t)     # Указываем, что функция принимает аргумент void *     test.func_ret_struct.argtypes = [ctypes.c_void_p]      # Создаем структуру     test_st = test_st_t(19, 3.5, 'Z'.encode('utf-8'))      # Python None == Null C     # ret = test.func_ret_struct(None)     # print('ret func_ret_struct: ', ret) # Если передали None, то его и получим назад     ret = test.func_ret_struct(ctypes.byref(test_st))      # Полученные данные из C     print('ret val1 = {}\nret val2 = {}\nret val3 = {}'.format(ret.contents.val1, ret.contents.val2,                                                                ret.contents.val3.decode("utf-8")))      ###     ## C++     ###      print("\n\nC++\n")      # Загрузка библиотеки     try:         testpp = ctypes.CDLL('libs/libtestpp.a')     except OSError as e:         print(str(e))         exit(0)      # Указываем, что функция возвращает указатель     testpp.test_new.restype = ctypes.c_void_p     # Создание класса test     test = testpp.test_new()      ##     # Работа с методами     ##      # Указываем, что функция возвращает char *     testpp.test_ret_str.restype = ctypes.c_char_p     # Указываем, что функция принимает аргумент void * и char *     testpp.test_ret_str.argtypes = [ctypes.c_void_p, ctypes.c_char_p]      # Указываем, что функция возвращает int     testpp.test_ret_int.restype = ctypes.c_int     # Указываем, что функция принимает аргумент void * и int     testpp.test_ret_int.argtypes = [ctypes.c_void_p, ctypes.c_int]      # Указываем, что функция возвращает double     testpp.test_ret_double.restype = ctypes.c_double     # Указываем, что функция принимает аргумент void * и double     testpp.test_ret_double.argtypes = [ctypes.c_void_p, ctypes.c_double]      print('Работа с методами:')     # В качестве 1-ого аргумента передаем указатель на наш класс     print('ret test_ret_str: ', testpp.test_ret_str(test, 'Hello!'.encode('utf-8')).decode("utf-8"))     print('ret test_ret_int: ', testpp.test_ret_int(test, 123))     print('ret test_ret_double: ', testpp.test_ret_double(test, 9.87654321))      ##     # Работа с переменными     ##      # Указываем, что функция возвращает int     testpp.test_get_a.restype = ctypes.c_int     # Указываем, что функция принимает аргумент void *     testpp.test_get_a.argtypes = [ctypes.c_void_p]     # Указываем, что функция возвращает double     testpp.test_get_b.restype = ctypes.c_double     # Указываем, что функция принимает аргумент void *     testpp.test_get_b.argtypes = [ctypes.c_void_p]     # Указываем, что функция возвращает char     testpp.test_get_c.restype = ctypes.c_char     # Указываем, что функция принимает аргумент void *     testpp.test_get_c.argtypes = [ctypes.c_void_p]      print('\nРабота с переменными:')     print('ret test_get_a: ', testpp.test_get_a(test))     print('ret test_get_b: ', testpp.test_get_b(test))     print('ret test_get_c: ', testpp.test_get_c(test).decode("utf-8"))      # Указываем, что функция принимает аргумент void *     testpp.test_del.argtypes = [ctypes.c_void_p]     # Удаляем класс     testpp.test_del(test)      ButtonApp().run()

Здесь создается простая графическая программа с одной кнопкой при нажатии которой произойдет закрытие приложения. Основная задача статьи показать как запускать C библиотеки, результат работы увидим в консоли.

Далее создаем папку под проект xcode.

mkdir ios-build cd ios-build

Очень важно, исходники проекта и проект xcode должны находиться в разных невложенных в друг друга папках.

Создаем проект xcode:

toolchain build python3 kivy openssl # very long operation toolchain create test ~/workspace/c_from_python/src/python/ios #<full_path_to_my_app_source_directory>

Все файлы находящиеся в ~/workspace/c_from_python/src/python/ios будут взяты и собраны в установочном пакете (для Android в buildozer.spec все надо было прописывать). В корне ~/workspace/c_from_python/src/python/ios обязательно должен быть файл main.py, содержащий запуск программы.

Теперь открываем проект xcode:

open test-ios/test.xcodeproj

Выбираем эмулятор и нажимаем собрать:

Телефон

Запускал на эмуляторе, т.к. iPhone в железе у меня нет. Приложение стартует автоматически после сборки и получаем следующее — C/C++ отработал без проблем:

На экране телефона видим следующую картинку:

main

Все отработало как надо. Нажатие на кнопку закрывает приложение. Больше оно ни чего не умеет делать 😉

Спасибо за внимание.

Ссылки


ссылка на оригинал статьи https://habr.com/ru/post/720310/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *