首页
统计
留言板
关于
Search
1
Rust 包装 objc Block
86 阅读
2
02. Rust 内存管理 Copy & Clone(上)
85 阅读
3
Flutter 调用 Rust 生成的 共享/静态 库
79 阅读
4
纯 C 写个 iOS App(误)
67 阅读
5
用 Rust 开发 iOS 应用(粗糙版)
64 阅读
默认分类
Rust
Apple
iOS
Swift
Android
emulator
NES
登录
/
注册
Search
标签搜索
Rust
iOS
NES
Swift
Android
杂项
limit
累计撰写
18
篇文章
累计收到
0
条评论
首页
栏目
默认分类
Rust
Apple
iOS
Swift
Android
emulator
NES
页面
统计
留言板
关于
搜索到
3
篇与
的结果
2022-05-30
Flutter 调用 Rust 生成的 共享/静态 库
日常发病前面写过一两篇使用水文, 用 Rust 写点东西, 编译成 shared/static lib 给 Android 或者 iOS 端调用.这篇水文需要用到之前写得 MD5 的那个 Rust 项目. Android 使用 Rust 生成的动态库 用 Rust 开发 iOS 应用(粗糙版) 今天发病的主题是 Flutter FFI 相关.创建 Flutter 项目这里直接通过下面这条命令创建flutter create --platforms=android,ios --template=plugin ffi_demo这里使用的是 plugin 的模板, 如果直接使用 project 模板也是可以的, 现在直接用 Android Studio 打开项目. 我们先来看看项目结构tree -L 1 . ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── example ├── ffi_demo.iml ├── ios ├── lib ├── pubspec.lock ├── pubspec.yaml └── test 5 directories, 7 files处理 Android 端Android 端的比较好处理, 把开头 Android 的那篇文章的 md5 项目生成的动态库拷贝出来, 放到 android/src/main/jniLibs/arm64-v8a 下面, 事实上这里不需要用到 CMakeLists, 所以直接放到 jniLibs 下面, 不过有个问题, Android 项目并不知道你放这边了, 所以还得配置一下 build.gradle 的 sourceSetsandroid { // ... sourceSets { // ... main.jniLibs.srcDirs = ['src/main/jniLibs'] } // ... }这一步完成后, 我们先把 Android 这边的事放一放处理 iOS 端由于之前的 md5 那个项目, 没编译 iOS 的静态库, 所以我们先得把 iOS 静态库生成一下.喜闻乐见的添加对应 target 环节rustup target add aarch64-apple-ios如果你想跑在 Intel 设备的 iOS 仿真器上, 需要添加 x86 的 target, 这里只添加了手机 aarch64 的, 如果你想跑在 M1 设备的 iOS 仿真器上, 你需要添加rustup target add aarch64-apple-ios-sim接着跑到 md5 的项目中运行构造命令cargo build --target aarch64-apple-ios --release构建成功后把 target 目录对应平台的静态库拷贝出来放到 ios/Frameworks 下, 没有 Frameworks 文件夹就自己创建, 文件夹名字随便起, 然后把 ffi_demo.podspec 文件改一下, 总之找到对应的 .podspec 文件, 添加指定静态库的路径Pod::Spec.new do |s| # ... s.vendored_libraries = 'Frameworks/libmd5.a' # ... end处理 Flutter 端上面的准备工作完成后, 我们可以来暴露接口给 Dart 使用啦! 找到 lib/ffi_demo.dart 文件, 修改里面的代码import 'dart:io'; import 'dart:ffi'; // third part package import 'package:ffi/ffi.dart'; typedef LLMD5 = Pointer<Int8> Function(Pointer<Int8>); class FfiDemo { String md5(String str) { final DynamicLibrary nativeLib = Platform.isAndroid ? DynamicLibrary.open("libmd5.so") : DynamicLibrary.process(); LLMD5 md5 = nativeLib.lookupFunction<LLMD5, LLMD5>("ll_md5"); var result = md5(str.toNativeUtf8().cast<Int8>()); return result.cast<Utf8>().toDartString(); } }其实就是把 dart 的字符串转成 C 的字符串传给 md5 函数使用, 返回的结果再转成 dart 的字符串, 我们代码中用到一个第三方包来处理 dart 字符串跟 C 字符串的转换, 直接执行添加第三方包的命令flutter pub add ffi执行 exampleAndroid为了验证我们的插件是否正常工作, 我们把 example 里面的 lib/main.dart 修改一下import 'package:flutter/material.dart'; import 'package:ffi_demo/ffi_demo.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { String _md5Str = ''; final _ffiDemoPlugin = FfiDemo(); @override void initState() { super.initState(); initMD5(); } void initMD5() { String str = ''; try { str = _ffiDemoPlugin.md5("foo"); } on Exception { str = 'MD5 failed'; } if (!mounted) return; setState(() { _md5Str = str; }); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Plugin example app'), ), body: Center( child: Text('$_md5Str\n'), ), ), ); } }然后连接对应 ABI 的 Android 手机, 用 Android Studio 执行一下, 可以看到屏幕中间显示了一串经过 md5 运算后的字符串.iOSiOS 这边, 因为我们修改了 podspec 文件, 所以我们需要在 example/ios 目录下执行pod install然后就是更新 xcworkspace, 完成后, 就可以在 Pod 项目中找到我们的静态库了然后用 Android Studio 打开插件项目(直接用 Xcode 执行 Runner 可能会出现找不到符号的问题), 执行到对应的设备(或者仿真器), 就能看到对应的效果Flutter FFI 使用起来比较简单, 某些情况也很有用, 譬如要使用 FFmpeg 理论上可以通过 FFI 来达成我们的目标.
2022年05月30日
79 阅读
0 评论
0 点赞
2019-01-14
用 Rust 开发 iOS 应用(粗糙版)
把环境搞定在搞事情之前, 我们先把 Rust 环境配好, 这个很简单, 直接用官网的这条命令.curl https://sh.rustup.rs -sSf | sh随便装一个版本, 稳定版也好, 变态版(beta) 也罢. 然后装上一些工具链, 在终端输入rustup target add aarch64-apple-ios x86_64-apple-ios我个人只装了针对 A7 以上 64 位处理器的工具链, x86_64-apple-ios 这个是给模拟器用的. 还有其他几个工具链, 有需要的也可以装上.rustup target add armv7-apple-ios armv7s-apple-ios i386-apple-ios建个 Rust 项目先现在先建个 Rust 项目, 只要使用 cargo 就好了, 直接在终端输入mkdir rust-on-ios && cd rust-on-ios cargo new rs --lib mkdir ios现在可以看到 rust-on-ios 目录下有 ios 和 rs 文件夹. 打开 rs 文件夹 src 目录下的 lib .rs 文件, 先搞个 "hello world" 试一下效果.use std::os::raw::{c_char}; use std::ffi::{CString}; #[no_mangle] pub extern fn say_hello() -> *mut c_char { CString::new("Hello Rust").unwrap().into_raw() }姑且就写这个. 这里的 #[no_mangle] 必须要写, 这个是保证编译后能找到这个函数. 然后我们来建个头文件, 因为之后会把 Rust 项目编译成库文件, 所以搞个 .h 文件提供接口.char *say_hello(void);还差一步, 我们现在要修改一下 Cargo.toml 文件, 到时候把 Rust 源码编译成库.[package] name = "rs" version = "0.1.0" authors = ["limit <747638920@qq.com>"] edition = "2018" publish = false [lib] name = "app" crate-type = ["staticlib"]现在我们到 rs 目录下编译一下这个项目.cargo build --target x86_64-apple-ios --release编译好之后, 你会在 target/x86_64-apple-ios 目录下发现一个 libapp.a 文件. 接下来建个 iOS 项目.创建 iOS 项目现在来创建个 Single Page App 项目. 我图个省事, 这里直接建 Objective-C 项目, 要建 Swift 项目也可以, 不过需要搞桥接. 一路 Next 创建了项目, 然后添加 lib 文件这个 libapp.a 是我们用 Rust 项目编译好的文件, 这个 libresolv.tbd 是拿来做链接用的.要想添加 libapp.a, 直接点这个加号, 然后点 Add Other, 然后选中编译好的 libapp.a 文件. 然后把之前写好的头文件放到项目中. 编译的时候发现出错了. 因为我们虽然把 libapp.a 引入到了项目中, 但是编译的时候, 工具并不清楚 lib 文件在哪, 所以我们得手动设置一下 lib 文件的搜索路径.现在再进行一次编译应该就能成功了.为了演示效果, 在 ViewControll.m 文件中使用一下这个函数吧.#import "ViewController.h" #import "libapp.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; NSString *fromCStr = @(say_hello()); UILabel *label = [[UILabel alloc] initWithFrame: (CGRect) { 100, 100, 100, 100 }]; label.text = fromCStr; [self.view addSubview:label]; } @end然后模拟器上应该显示了 Hello Rust 这段文字.要想用 Rust 开发移动端应用需要 Rust FFI 相关的知识, 后续我应该会写点 Rust FFI 的相关内容, 再配合 Flutter 写 UI, 开发体验肯定 up.
2019年01月14日
64 阅读
0 评论
0 点赞
2016-10-10
纯 C 写个 iOS App(误)
一个 iOS app 首先是由 main.m 内的 main 函数开始的. 现在就先创建 Single View App 项目, 然后把所有的 .m 文件都删掉, 建一个 main.c 文件. 通常我们看到的 main.m 的内的代码是这样的// main.m #import <UIKit/UIKit.h> #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }那先照着这个写就好了, 但是这个 @autoreleasepool 这个怎么处理? 我们晓得这是个语法糖, 在 ARC 出来之后编译器就不让我们使用 NSAutoreleasePool, 原先是这样的NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [pool release];那就仿照着这玩意写成 C 版本的int main(int argc, char **argv) { id pool = objc_msgSend( objc_msgSend((id) objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")), sel_registerName("init")); UIApplicationMain(argc, argv, nil, CFSTR("AppDelegate")); objc_msgSend(pool, sel_registerName("drain")); }CFSTR 这个宏可以从 C 字符串创建一个 CFString 的引用(CFStringRef), 这玩意可以用来代替我们这里的 NSStringFromClass([AppDelegate class]).现在已经抄作业抄了一个 main.c, 不过还有个问题, UIApplicationMain 这个函数从哪里跑出来的. 这个是一个用于创建我们应用实例的函数, 但是我们没法直接使用它, 因为它是在 UIApplication.h 文件, 不过我们可以这样搞(这里顺便把 runtime 那些头文件补上吧)#include <CoreFoundation/CoreFoundation.h> #include <objc/runtime.h> #include <objc/message.h> extern int UIApplicationMain(int, ...); int main(int argc, char **argv) { id pool = objc_msgSend( objc_msgSend((id) objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")), sel_registerName("init")); UIApplicationMain(argc, argv, nil, CFSTR("AppDelegate")); objc_msgSend(pool, sel_registerName("drain")); }这里再讲一下 UIApplicationMain 这个函数, 它虽然有 int 类型的返回值, 但是它永远不会返回.然后这玩意的前两个参数就不管了, 就是处理一下 main 函数传进来的参数, 第三个参数是需要传入 UIApplication 或者其子类的名称, 这里传 nil 就默认用 UIApplication. 我们需要关注的是最后一个参数, 这个参数让我们传一个代理类的字符串, 就是给应用设置个代理, 也就是讲接下来我们要实现一个代理类.所以我们现在来创建个 AppDelegate.c 的文件. 继续照之前的套路走, 先看 AppDelegate.m 代码, AppDelegate 这个类有个 window 的属性, 有下面这个函数- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { return YES; }我们先得实现一个 AppDelegate class 才行. 一般稍微了解过 NSObject 定义的都晓得, 每个类有个 isa 用来标记这个类是什么, 具体怎样就不解释了, 反正很多 runtime 以及 header 文件的定义都能找到.除了搞个 class, 我们还要实现那个 application:didFinishLaunchingWithOptions: 函数// AppDelegate.c #include <objc/runtime.h> #include <objc/message.h> #include <CoreGraphics/CoreGraphics.h> typedef struct AppDelegate { Class isa; id window; } AppDelegate; Class AppDelegateClass; BOOL applicationDidFinishLaunchingWithOptions( AppDelegate *self, SEL _cmd, void *application, void *options) { self->window = objc_msgSend((id) objc_getClass("UIWindow"), sel_getUid("alloc")); self->window = objc_msgSend(self->window, sel_getUid("initWithFrame:"), (struct CGRect) {0, 0, 320, 568}); id viewController = objc_msgSend( objc_msgSend((id) objc_getClass("UIViewController"), sel_getUid("alloc")), sel_getUid("init")); id view = objc_msgSend( objc_msgSend((id) objc_getClass("View"), sel_getUid("alloc")), sel_getUid("initWithFrame:"), (struct CGRect) {0, 0, 320, 568}); objc_msgSend(objc_msgSend(viewController, sel_getUid("view")), sel_getUid("addSubview:"), view); objc_msgSend(self->window, sel_getUid("setRootViewController:"), viewController); objc_msgSend(self->window, sel_getUid("makeKeyAndVisible")); return YES; } __attribute__((constructor)) static void initAppDelegate() { AppDelegateClass = objc_allocateClassPair((Class) objc_getClass("UIResponder"), "AppDelegate", 0); class_addIvar(AppDelegateClass, "window", sizeof(id), 0, "@"); // - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions class_addMethod(AppDelegateClass, sel_registerName("application:didFinishLaunchingWithOptions:"), (IMP) applicationDidFinishLaunchingWithOptions, "i@:@@"); objc_registerClassPair(AppDelegateClass); }这里通过 __attribute__((constructor)) 这个编译属性让这个函数在 main 函数之前走. 通过 runtime 搞了个 AppDelegateClass 出来. 由于我比较穷, 手机还是 iPhone 5s, 所以设了 (struct CGRect) {0, 0,320, 568}).通过引入 CoreGraphics.h 才可以让编译通过 CGRect. 现在了解到创建一个 class 的套路之后, 这里在 applicationDidFinishLaunchingWithOptions 使用到了 View class, 我们就创建一个 View.c 文件来自定义视图什么// View.c #include <objc/runtime.h> #include <CoreGraphics/CoreGraphics.h> Class ViewClass; extern CGContextRef UIGraphicsGetCurrentContext(); void viewDrawRect(id self, SEL _cmd, CGRect rect) { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetFillColor(context, (CGFloat[]) {1, 0, 0, 1}); CGContextAddRect(context, (struct CGRect) {0, 0, 320, 568}); CGContextFillPath(context); } __attribute__((constructor)) static void initView() { ViewClass = objc_allocateClassPair((Class) objc_getClass("UIView"), "View", 0); class_addMethod(ViewClass, sel_getUid("drawRect:"), (IMP) viewDrawRect, "v@:"); objc_registerClassPair(ViewClass); }这里直接用 CoreGraphics 来绘制视图.然后编译执行看看效果, 应该是一个空白的红色视图. 如果编译出错了, 可能是现在的 Xcode 禁止 objc_msgSend 函数的调用, 在 Build Settings 启用它就好了.忘了还有个事要做, 那就是把这几个东西导入到项目中其实就是通过 runtime 来各种调用函数, 这个拿来玩玩就好了. 好吧, 先这样吧.
2016年10月10日
67 阅读
0 评论
0 点赞