首页
统计
留言板
关于
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
页面
统计
留言板
关于
搜索到
9
篇与
的结果
2023-03-29
用 Metal 画一个三角形(Swift 函数式风格)
用 Metal 画一个三角形(Swift 函数式风格)由于今年换了一份工作,平时上班用得语言换成 Rust/OCaml/ReScript 啦,所以导致我现在写代码更倾向于写函数式风格的代码。 顺便试试 Swift 在函数式方面能达到啥程度。主要是我不会 Swift,仅仅为了好玩。创建工程随便创建个工程,小玩具就不打算跑在手机上了,因为我的设备是 ARM 芯片的,所以直接创建个 Mac 项目,记得勾上包含测试。构建 MTKView 子类现在来创建个 MTKView 的子类,其实我现在已经不接受这种所谓的面向对象,开发者用这种方式,就要写太多篇幅来描述一个上下文结构跟函数就能实现的动作。import MetalKit class MetalView: MTKView { required init(coder: NSCoder) { super.init(coder: coder) device = MTLCreateSystemDefaultDevice() render() } } extension MetalView { func render() { // TODO: 具体实现 } }我们这里给 MetalView extension 了一个 render 函数,里面是后续要写得具体实现。普通的方式画一个三角形先用常见的方式来画一个三角形class MetalView: MTKView { required init(coder: NSCoder) { super.init(coder: coder) device = MTLCreateSystemDefaultDevice() render() } } extension MetalView { func render() { guard let device = device else { fatalError("Failed to find default device.") } let vertexData: [Float] = [ -1.0, -1.0, 0.0, 1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0 ] let dataSize = vertexData.count * MemoryLayout<Float>.size let vertexBuffer = device.makeBuffer(bytes: vertexData, length: dataSize, options: []) let library = device.makeDefaultLibrary() let renderPassDesc = MTLRenderPassDescriptor() let renderPipelineDesc = MTLRenderPipelineDescriptor() if let currentDrawable = currentDrawable, let library = library { renderPassDesc.colorAttachments[0].texture = currentDrawable.texture renderPassDesc.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.5, blue: 0.5, alpha: 1.0) renderPassDesc.colorAttachments[0].loadAction = .clear renderPipelineDesc.vertexFunction = library.makeFunction(name: "vertexFn") renderPipelineDesc.fragmentFunction = library.makeFunction(name: "fragmentFn") renderPipelineDesc.colorAttachments[0].pixelFormat = .bgra8Unorm let commandQueue = device.makeCommandQueue() guard let commandQueue = commandQueue else { fatalError("Failed to make command queue.") } let commandBuffer = commandQueue.makeCommandBuffer() guard let commandBuffer = commandBuffer else { fatalError("Failed to make command buffer.") } let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDesc) guard let encoder = encoder else { fatalError("Failed to make render command encoder.") } if let renderPipelineState = try? device.makeRenderPipelineState(descriptor: renderPipelineDesc) { encoder.setRenderPipelineState(renderPipelineState) encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0) encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1) encoder.endEncoding() commandBuffer.present(currentDrawable) commandBuffer.commit() } } } }然后是我们需要注册的 Shader 两个函数#include <metal_stdlib> using namespace metal; struct Vertex { float4 position [[position]]; }; vertex Vertex vertexFn(constant Vertex *vertices [[buffer(0)]], uint vid [[vertex_id]]) { return vertices[vid]; } fragment float4 fragmentFn(Vertex vert [[stage_in]]) { return float4(0.7, 1, 1, 1); }在运行之前需要把 StoryBoard 控制器上的 View 改成我们写得这个 MTKView 的子类。自定义操作符函数式当然不是指可以定义操作符,但是没有这些操作符,感觉没有魂灵,所以先定义个管道符代码实现precedencegroup SingleForwardPipe { associativity: left higherThan: BitwiseShiftPrecedence } infix operator |> : SingleForwardPipe func |> <T, U>(_ value: T, _ fn: ((T) -> U)) -> U { fn(value) }测试管道符因为创建项目的时候,勾上了 include Tests,直接写点测试代码,执行测试。final class using_metalTests: XCTestCase { // ... func testPipeOperator() throws { let add = { (a: Int) in return { (b: Int) in return a + b } } assert(10 |> add(11) == 21) let doSth = { 10 } assert(() |> doSth == 10) } }目前随便写个测试通过嘞。Functional Programming现在需要把上面的逻辑分割成小函数,事实上,因为 Cocoa 的基础是建立在面向对象上的,我们还是没法完全摆脱面向对象,目前先小范围应用它。生成 MTLBuffer先理一下逻辑,代码开始是创建顶点数据,生成 bufferfileprivate let makeBuffer = { (device: MTLDevice) in let vertexData: [Float] = [ -1.0, -1.0, 0.0, 1.0, 1.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0 ] let dataSize = vertexData.count * MemoryLayout<Float>.size return device.makeBuffer(bytes: vertexData, length: dataSize, options: []) }创建 MTLLibrary接着是创建 MTLLibrary 来注册两个 shader 方法,还创建了一个 MTLRenderPipelineDescriptor 对象用于创建 MTLRenderPipelineState,但是创建的 MTLLibrary 对象是一个 Optional 的,所以其实得有两步,总之先提取它再说吧fileprivate let makeLib = { (device: MTLDevice) in device.makeDefaultLibrary() }抽象 map 函数根据我们有限的函数式编程经验,像 Optional 这种对象大概率有一个 map 函数,所以我们自家实现一个,同时还要写成柯里化的(建议自动柯里语法糖化入常),因为这里有逃逸闭包,所以要加上 @escapingfunc map<T, U>(_ transform: @escaping (T) throws -> U) rethrows -> (T?) -> U? { return { (o: T?) in return try? o.map(transform) } }处理 MTLRenderPipelineState这里最终目的就是 new 了一个 MTLRenderPipelineState,顺带处理把程序的一些上下文给渲染管线描述器(MTLRenderPipelineDescriptor),譬如我们用到的着色器(Shader)函数,像素格式。最后一行直接 try! 不处理错误啦,反正出问题直接会抛出来的fileprivate let makeState = { (device: MTLDevice) in return { (lib: MTLLibrary) in let renderPipelineDesc = MTLRenderPipelineDescriptor() renderPipelineDesc.vertexFunction = lib.makeFunction(name: "vertexFn") renderPipelineDesc.fragmentFunction = lib.makeFunction(name: "fragmentFn") renderPipelineDesc.colorAttachments[0].pixelFormat = .bgra8Unorm return (try! device.makeRenderPipelineState(descriptor: renderPipelineDesc)) } }暂时收尾已经不想再抽取函数啦,其实还能更细粒度地处理,因为函数式有个纯函数跟副作用的概念,像 Haskell 里是可以用 Monad 来处理副作用的情况,这个主题留给后续吧。先把 render 改造一下fileprivate let render = { (device: MTLDevice, currentDrawable: CAMetalDrawable?) in return { state in let renderPassDesc = MTLRenderPassDescriptor() if let currentDrawable = currentDrawable { renderPassDesc.colorAttachments[0].texture = currentDrawable.texture renderPassDesc.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.5, blue: 0.5, alpha: 1.0) renderPassDesc.colorAttachments[0].loadAction = .clear let commandQueue = device.makeCommandQueue() guard let commandQueue = commandQueue else { fatalError("Failed to make command queue.") } let commandBuffer = commandQueue.makeCommandBuffer() guard let commandBuffer = commandBuffer else { fatalError("Failed to make command buffer.") } let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDesc) guard let encoder = encoder else { fatalError("Failed to make render command encoder.") } encoder.setRenderPipelineState(state) encoder.setVertexBuffer(device |> makeBuffer, offset: 0, index: 0) encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1) encoder.endEncoding() commandBuffer.present(currentDrawable) commandBuffer.commit() } } }然后再调用,于是就变成下面这副鸟样子class MetalView: MTKView { required init(coder: NSCoder) { super.init(coder: coder) device = MTLCreateSystemDefaultDevice() device |> map { makeLib($0) |> map(makeState($0)) |> map(render($0, self.currentDrawable)) } } }最后执行出这种效果
2023年03月29日
34 阅读
0 评论
0 点赞
2022-11-23
Swift 学习
介绍最近打算重新学习一下 Swift,因为之前一直使用 objc 的思维模式写 Swift,另外想进一步了解 Swift 语言的设计实现,应该能丰富我的认知。特意找了个旧设备装上 macOS 10.14.6 系统,并安装上 Xcode 11 ,新版本语法特性太多,暂时先不考虑,只从早期 ABI 稳定又稍微升级过的 5.1 版本开始。swift -version # Apple Swift version 5.1.3 (swiftlang-1100.0.282.1 clang-1100.0.33.15) # Target: x86_64-apple-darwin18.7.0Swift 编译流程开始之前,先了解下 Swift 语言的编译流程图中命令其实是独立的,这样画只是说明它经历过哪些环节,先是从 Swift 代码开始,编译器会对代码进行分词,解析,生成抽象语法树,这期间会进行语义分析,譬如类型检查之类的操作。接着就是把生成的 ast 转成 sil,既然有 sil,那自然就是优化 sil,完了就生成 LLVM 的 IR 码,然后通过 LLVM 把得到的 IR 码转成机器代码(期间会进行编译,链接库等操作,然后才生成对应平台的二进制文件)。Swift 基本命令swiftc 这个是编译器的命令,可以通过 swiftc --help 来查看有哪些参数。OVERVIEW: Swift compiler USAGE: swiftc MODES: -dump-ast Parse and type-check input file(s) and dump AST(s) -dump-parse Parse input file(s) and dump AST(s) -dump-scope-maps <expanded-or-list-of-line:column> Parse and type-check input file(s) and dump the scope map(s) -dump-type-info Output YAML dump of fixed-size types from all imported modules -dump-type-refinement-contexts Type-check input file(s) and dump type refinement contexts(s) -emit-assembly Emit assembly file(s) (-S) -emit-bc Emit LLVM BC file(s) -emit-executable Emit a linked executable -emit-imported-modules Emit a list of the imported modules -emit-ir Emit LLVM IR file(s) -emit-library Emit a linked library -emit-object Emit object file(s) (-c) -emit-sibgen Emit serialized AST + raw SIL file(s) -emit-sib Emit serialized AST + canonical SIL file(s) -emit-silgen Emit raw SIL file(s) -emit-sil Emit canonical SIL file(s) -index-file Produce index data for a source file -parse Parse input file(s) -print-ast Parse and type-check input file(s) and pretty print AST(s) -resolve-imports Parse and resolve imports in input file(s) -typecheck Parse and type-check input file(s) OPTIONS: -api-diff-data-dir <path> Load platform and version specific API migration data files from <path>. Ignored if -api-diff-data-file is specified. -api-diff-data-file <path> API migration data is from <path> -application-extension Restrict code to those available for App Extensions -assert-config <value> Specify the assert_configuration replacement. Possible values are Debug, Release, Unchecked, DisableReplacement. -continue-building-after-errors Continue building, even after errors are encountered -debug-info-format=<value> Specify the debug info format type to either 'dwarf' or 'codeview' -debug-info-store-invocation Emit the compiler invocation in the debug info. -debug-prefix-map <value> Remap source paths in debug info -disable-autolinking-runtime-compatibility-dynamic-replacements Do not use autolinking for the dynamic replacement runtime compatibility library -disable-autolinking-runtime-compatibility Do not use autolinking for runtime compatibility libraries -disable-migrator-fixits Disable the Migrator phase which automatically applies fix-its -driver-time-compilation Prints the total time it took to execute all compilation tasks -dump-migration-states-dir <path> Dump the input text, output text, and states for migration to <path> -dump-usr Dump USR for each declaration reference -D <value> Marks a conditional compilation flag as true -embed-bitcode-marker Embed placeholder LLVM IR data as a marker -embed-bitcode Embed LLVM IR bitcode as data -emit-dependencies Emit basic Make-compatible dependencies files -emit-loaded-module-trace-path <path> Emit the loaded module trace JSON to <path> -emit-loaded-module-trace Emit a JSON file containing information about what modules were loaded -emit-module-interface-path <path> Output module interface file to <path> -emit-module-interface Output module interface file -emit-module-path <path> Emit an importable module to <path> -emit-module Emit an importable module -emit-objc-header-path <path> Emit an Objective-C header file to <path> -emit-objc-header Emit an Objective-C header file -emit-tbd-path <path> Emit the TBD file to <path> -emit-tbd Emit a TBD file -enable-library-evolution Build the module to allow binary-compatible library evolution -enforce-exclusivity=<enforcement> Enforce law of exclusivity -fixit-all Apply all fixits from diagnostics without any filtering -framework <value> Specifies a framework which should be linked against -Fsystem <value> Add directory to system framework search path -F <value> Add directory to framework search path -gdwarf-types Emit full DWARF type info. -gline-tables-only Emit minimal debug info for backtraces only -gnone Don't emit debug info -g Emit debug info. This is the preferred setting for debugging with LLDB. -help Display available options -import-underlying-module Implicitly imports the Objective-C half of a module -index-file-path <path> Produce index data for file <path> -index-ignore-system-modules Avoid indexing system modules -index-store-path <path> Store indexing data to <path> -I <value> Add directory to the import search path -j <n> Number of commands to execute in parallel -L <value> Add directory to library link search path -l<value> Specifies a library which should be linked against -migrate-keep-objc-visibility When migrating, add '@objc' to declarations that would've been implicitly visible in Swift 3 -migrator-update-sdk Does nothing. Temporary compatibility flag for Xcode. -migrator-update-swift Does nothing. Temporary compatibility flag for Xcode. -module-cache-path <value> Specifies the Clang module cache path -module-link-name <value> Library to link against when using this module -module-name <value> Name of the module to build -nostdimport Don't search the standard library import path for modules -num-threads <n> Enable multi-threading and specify number of threads -Onone Compile without any optimization -Osize Compile with optimizations and target small code size -Ounchecked Compile with optimizations and remove runtime safety checks -output-file-map <path> A file which specifies the location of outputs -O Compile with optimizations -o <file> Write output to <file> -parse-as-library Parse the input file(s) as libraries, not scripts -parse-sil Parse the input file as SIL code, not Swift source -parseable-output Emit textual output in a parseable format -profile-coverage-mapping Generate coverage data for use with profiled execution counts -profile-generate Generate instrumented code to collect execution counts -profile-use=<profdata> Supply a profdata file to enable profile-guided optimization -remove-runtime-asserts Remove runtime safety checks. -require-explicit-availability-target <target> Suggest fix-its adding @available(<target>, *) to public declarations without availability -require-explicit-availability Require explicit availability on public declarations -Rpass-missed=<value> Report missed transformations by optimization passes whose name matches the given POSIX regular expression -Rpass=<value> Report performed transformations by optimization passes whose name matches the given POSIX regular expression -runtime-compatibility-version <value> Link compatibility library for Swift runtime version, or 'none' -sanitize-coverage=<type> Specify the type of coverage instrumentation for Sanitizers and additional options separated by commas -sanitize=<check> Turn on runtime checks for erroneous behavior. -save-optimization-record-path <value> Specify the file name of any generated YAML optimization record -save-optimization-record Generate a YAML optimization record file -save-temps Save intermediate compilation results -sdk <sdk> Compile against <sdk> -serialize-diagnostics Serialize diagnostics in a binary format -static-executable Statically link the executable -static-stdlib Statically link the Swift standard library -static Make this module statically linkable and make the output of -emit-library a static library. -suppress-warnings Suppress all warnings -swift-version <vers> Interpret input according to a specific Swift language version number -target-cpu <value> Generate code for a particular CPU variant -target-variant <value> Generate code that may run on a particular variant of the deployment target -target <value> Generate code for the given target -tools-directory <directory> Look for external executables (ld, clang, binutils) in <directory> -track-system-dependencies Track system dependencies while emitting Make-style dependencies -use-ld=<value> Specifies the linker to be used -verify-debug-info Verify the binary representation of debug output. -version Print version information and exit -vfsoverlay <value> Add directory to VFS overlay file -v Show commands to run and use verbose output -warn-implicit-overrides Warn about implicit overrides of protocol members -warn-swift3-objc-inference-complete Warn about deprecated @objc inference in Swift 3 for every declaration that will no longer be inferred as @objc in Swift 4 -warn-swift3-objc-inference-minimal Warn about deprecated @objc inference in Swift 3 based on direct uses of the Objective-C entrypoint -warnings-as-errors Treat warnings as errors -whole-module-optimization Optimize input files together instead of individually -working-directory <path> Resolve file paths relative to the specified directory -Xcc <arg> Pass <arg> to the C/C++/Objective-C compiler -Xlinker <value> Specifies an option which should be passed to the linker先写一段简单的 Swift 代码import Foundation print("hello, world")然后用 -dump-ast 导出它的语法树,应该就能看到打印出来的抽象语法树。swiftc -dump-ast main.swift再来看看 Swift 中间代码长啥样,使用 -emit-sil 参数。swiftc -emit-sil main.swift至于其他命令,也是通过类似方式执行就能得到对应的结果。基础语法Constants 及 VariablesSwift 用 let 表示常量, var 表示变量,代码长下面这样。我看官方文档说 let 表示常量,我个人认为 let 表示的是一个变量不可变,当然还是以官方为准比较好。 下面第一行代码,如果 let a 但是并不指定类型也不赋值,就会报错let a: Int a = 11 let b = 12 var c = 11 c = 14编译器会提示这样的错误let a // Type annotation missing in pattern标识符标识符也就是 identifier,譬如下面的 a 跟 test 就是标识符let a = 10 func test() {}Swift 语言的标识符不能使用数字、空白字符(譬如 tab、换行符)、箭头之类的特殊字符,不能用数字开头的这很好理解,因为编译器会很难区分当前读取的一连串字符到底是数字(譬如语言中的整型,浮点型)还是单纯的标识符。不过像下面这种代码是支持的func 🐂🍺() -> Int { return 666 } print(🐂🍺())值类型IntegersSwift 的整型是 Int 表示,也有对应精度的 Int8、Int16、Int32、Int64、UInt8、UInt16、UInt32、UInt64,Int 对应 32 位平台上就会自动使用 Int32,对应 64 位平台上就会使用 Int64,观感上类似 objc 的 NSInteger,如果要写一些早期 8bit 的游戏平台仿真器(譬如 NES)就会用到 UInt8 这个类型,通常在 iOS 开发中直接用 Int 就好。Swift 也可以使用类似下面这样的数值表示let a = 16 // 十进制的 16 let b = 0b1_0000 // 二进制的 16 let c = 0o20 // 八进制的 16 let d = 0x10 // 十六进制的 16Floating-Point NumbersSwift 的浮点型也是可以套用其他语言的认知,分 Float、Double 两种,另外浮点型支持科学计数法let a = 12.0 let b = 1.2e1 // 科学计数法 a == b // true let c = 0x06p1 // 6 * (2 ^ 1) a == c // true let d = 0x06p-1 // 6 * (2 ^ -1) d == 3.0 // true let e = 0x1.1p0 // (1 + 1.0 / 16.0) * (2 ^ 0)Numeric Type ConversionSwift 的数字(譬如 Int16 跟 Int8,Int 跟 Double)之间做运算,需要转成相同的类型。其实这些在其他语言看来是基本类型的东西(譬如 Int)在 Swift 看来是结构体,它们的 + 号也是 Int/Double 结构体上定义的静态方法,然后这个静态方法限定了左右两边相加的类型,所以如果要把一个整型跟一个浮点型相加,那就把其中一个数的值给另一个数相同类型的构造函数构造出实例,于是下面就变成了 Double(a) + blet a = 3 let b = 0.14 // let result = a + b // Binary operator '+' cannot be applied to operands of type 'Int' and 'Double' let result = Double(a) + b // 3.14如果不指定类型,直接把两个 literal(字面量)相加,就不需要写构造let result = 3 + 0.14 // 3.14Tuples苹果官方文档上有下面一段代码,因为我学过 Rust,一看这个东西,就感觉它跟 Rust 用法应该差不多。let http404Error = (404, "Not Found")应该是通过 http404Error.0 、http404Error.1 这种方式访问数据,然后之前用过一段时间 Swift,知道它也有模式匹配,所以肯定也能这样取数据let (code, message) = http404Error因为很多 OCaml 风格的语言(譬如 Rust, ReScript)都用 _ 的符号表示不使用某个值,所以下面这段代码理解当然地从脑海中冒出来。let (code, _) = http404Error当然官方文档还给出了另一种构造跟访问元组的方式let http200Status = (statusCode: 200, description: "OK") print(http200Status.statusCode) print(http200Status.1)StringSwift 的字符串字面量直接用双引号包裹就行,这玩意还支持 + 之类的运算符,还有类似模板字符串的东西,多行字符串也是可以的。Swift 字符串操作太多了,后面再细看一下。var str = "some str" str += " end" str = "\(str)." // "some str end." str = """ line break """Character如果给一个字符串指定 Character 类型,就得到一个字符了,当然当前字符串超出一个字符,就会收到编译器错误let char: Character = "🍺" print(char) // 🍺 let c: Character = "123" // Cannot convert value of type 'String' to specified type 'Character'未完待续
2022年11月23日
46 阅读
0 评论
0 点赞
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 点赞
2022-04-25
Rust 包装 objc Block
Block 简介使用 objc 开发 App 时, 经常会使用到 Block, 这个语法糖是 Clang 给 C 语言实现的一个拓展. Block 是可以被编译成 C 语言的代码的. 如果有想法可以直接看 Clang 官方关于 Block 的文档 Block-ABI-Applerewrite-objc 生成 cpp 代码先来用 Clang 把一个普通的 objc 文件生成到 Cpp 代码, 看看 Block 生成的 C 语言代码长啥样. 先写个简单的 hello world 程序#import <stdio.h> int main(void) { @autoreleasepool { void (^test)(void) = ^{ printf("hello, world!\n"); }; test(); } return 0; }然后再用 clang 程序把上面的代码生成到 cpp 代码clang -rewrite-objc ./test.m然后会生成一堆代码, 我们找里面的关键内容struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("hello, world!\n"); } static struct __main_block_desc_0 { size_t reserved; size_t Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; int main(void) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; void (*test)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); ((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test); } return 0; }从代码上基本可以肯定static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("hello, world!"); }表示的是^{ printf("hello, world!"); };因为 __main_block_impl_0 包含 __block_impl 这个结构体, 所以struct __main_block_impl_0 { struct __block_impl impl; /* void *isa; int Flags; int Reserved; void *FuncPtr; */ struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };接着看 main 函数里, 把 __main_block_impl_0 构造函数用指针指向它// void (*test)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA); void (*test)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));结构体的构造函数执行后把 fp 指针传给 FuncPtr, fp 指针就是 __main_block_func_0, 也就是那个 hello world 代码.__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { // ... impl.FuncPtr = fp; // ... }使用外部变量的 BlockBlock 具备使用外部变量的能力, 有些类似其他语言的闭包, 对于变量的使用分为局部变量跟全局变量, 先来看局部变量局部变量局部变量的处理, 又分别针对 auto 变量跟 static 变量有对应的实现.auto 变量上面只是简单的 hello world 的 Block, 现在来使用一个 Block 之外的 auto 变量, rewrite 后会发生什么.#import <stdio.h> int main(void) { @autoreleasepool { int number = 10; void (^test)(void) = ^{ printf("hello, world!, number = %d\n", number); }; test(); } return 0; }// ... struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int number; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number, int flags=0) : number(_number) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int number = __cself->number; // bound by copy printf("hello, world!, number = %d\n", number); } // ...这次我们发现, 其他东西没啥变化, 不过 __main_block_impl_0 跟 __main_block_func_0 多了个跟 int 类型的 number, 其中还能看出 __main_block_impl_0 赋值给 __cself, 直接通过 __cself 使用 __main_block_impl_0 的 number.static 变量再来看看 static 变量的情况#import <stdio.h> int main(void) { @autoreleasepool { int number = 10; static int b = 10; void (^test)(void) = ^{ printf("hello, world!, number = %d, b = %d\n", number, b); }; test(); } return 0; }// ... struct __main_block_impl_0 { // ... int *b; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number, int *_b, int flags=0) : number(_number), b(_b) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int number = __cself->number; // bound by copy int *b = __cself->b; // bound by copy printf("hello, world!, number = %d, b = %d\n", number, (*b)); } int main(void) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int number = 10; static int b = 10; void (*test)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, number, &b)); ((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test); } return 0; }从代码中我们可以看出, 通过 & 操作符把 b 的地址传给 __main_block_impl_0 的构造函数, 同时 __main_block_impl_0 有一个 int *b 的成员, 同时在 __main_block_func_0 里进行解指针操作取值, 其实可以猜到一个行为, 如果在 block 调用之前修改 b, 最后取到的 b 是修改过的值, 因为它是通过 b 的指针进行取值.全局变量现在来看看全局变量的情况, 这种情况其实可以猜到, Block 直接使用全局变量, 不会在 struct 里添加成员. 现在来验证一下#import <stdio.h> int number_= 11; static int b_ = 11; int main(void) { @autoreleasepool { int number = 10; static int b = 10; void (^test)(void) = ^{ printf("hello, world!, number = %d, b = %d, number_ = %d, b_ = %d\n", number, b, number_, b_); }; test(); } return 0; }static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int number = __cself->number; // bound by copy int *b = __cself->b; // bound by copy printf("hello, world!, number = %d, b = %d, number_ = %d, b_ = %d\n", number, (*b), number_, b_); }跟我们刚才猜得行为是一致的.多参数 Block继续尝试修改代码后再 rewrite#import <stdio.h> int main(void) { @autoreleasepool { int number = 10; void (^test)(int a) = ^(int a) { printf("hello, world!, number = %d, a = %d\n", number, a); }; test(11); } return 0; }// ... static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a) { int number = __cself->number; // bound by copy printf("hello, world!, number = %d, a = %d\n", number, a); } // ... int main(void) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int number = 10; void (*test)(int a) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, number)); ((void (*)(__block_impl *, int))((__block_impl *)test)->FuncPtr)((__block_impl *)test, 11); } return 0; }__main_block_func_0 参数改变了, 增加了一个 int a 的参数, 当然相应的调用的代码也要改变下, 至于其他的地方, 倒没啥变化.现在来稍微总结一下, 等于讲 Clang 把 Block 转成 objc 的对象, 涉及捕获auto 变量时就给 struct 加个外部变量同名的成员, 涉及 static 变量, 就给 struct 加个同名的指针; 如果是访问全局变量, 则会直接在函数内部使用到; 涉及多参数的就给 __main_block_func_0 加更多形参.关于 _NSConcreteStackBlock我们再来看最初的 hello world#import <stdio.h> int main(void) { @autoreleasepool { void (^test)(void) = ^{ printf("hello, world!\n"); }; test(); } return 0; }struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };可以看到有个 isa 的指针, 给 isa 传得是 &_NSConcreteStackBlock, 由此可以看出 Block 是一个 objc 的对象, 同时它的 isa 可能是 _NSConcreteStackBlock.通过 rewrite-objc 看到 Block 的类型是 _NSConcreteStackBlock, 此外还有另外两个 _NSConcreteGlobalBlock, _NSConcreteMallocBlock, 分别对应以下类型类型class指定因素__NSGlobalBlock___NSConcreteGlobalBlock没有访问 auto 变量时__NSStackBlock___NSConcreteStackBlock访问了 auto 变量__NSMallocBlock___NSConcreteMallocBlock__NSStackBlock__ 使用 copy如果对 __NSGlobalBlock__ 使用 copy, 它还是 __NSGlobalBlock__, 并不会改变. Block 使用 copy 后的结果class源区域copy 结果_NSConcreteGlobalBlockdata无动作_NSConcreteStackBlockstackstack -> heap_NSConcreteMallocBlockheap引用计数增加既然 Block 是 objc 对象, 那意味着我们可以#import <Foundation/Foundation.h> int main(void) { @autoreleasepool { void (^test)(void) = ^{ printf("hello, world!\n"); }; NSLog(@"%@", [test class]); // __NSGlobalBlock__ int a = 10; NSLog(@"%@", [^{ NSLog(@"hello world!, a = %d\n", a); } class]); // __NSStackBlock__ NSLog(@"%@", [[^{ NSLog(@"hello world!, a = %d, b = %d\n", a); } copy] class]); // __NSMallocBlock__ } return 0; }然后对比 rewrite 后的代码就会发现, 第一条 NSLog 后出来的是 __NSGlobalBlock__, 说明其类型是 _NSConcreteGlobalBlock, 然而 rewrite-objc 出来的却是 _NSConcreteStackBlock, 第二第三条的 Block 也都是 _NSConcreteStackBlock, 很早之前的 Clang rewrite-objc 出来的内容不是这样的 (至少我 2014 年看到的不是这样的), 这里就不深究了, 以实际执行时的结果为准. 不过这也算是一个好事, 因为我们用 Rust 包装 Block 时只要处理 _NSConcreteStackBlock 就行啦!其他其实还有一些 MRC 跟 ARC 相关的, 以及使用 objc 对象时的情况.使用 Rust 包装了解到上面关于 Block 的一些基本原理, 现在来尝试用 Rust 包装一下 Block, 内容来源 rust-block 这个 crate. 首先创建一个 Rust 项目, 直接cargo new block --lib然后把 lib.rs 的内容删掉, 写上这玩意enum Class {} #[cfg_attr( any(target_os = "macos", target_os = "ios"), link(name = "System", kind = "dylib") )] #[cfg_attr( not(any(target_os = "macos", target_os = "ios")), link(name = "BlocksRuntime", kind = "dylib") )] extern "C" { static _NSConcreteStackBlock: Class; }这里主要是把 _NSConcreteStackBlock extern 出来, 至于 enum Class {} 是 Rust 的一个技巧, 这里是为了让编译通过, 不想用它可以直接用 (). 至于#[cfg_attr( any(target_os = "macos", target_os = "ios"), link(name = "System", kind = "dylib") )] #[cfg_attr( not(any(target_os = "macos", target_os = "ios")), link(name = "BlocksRuntime", kind = "dylib") )]是预处理一下 extern 块, 前面一段适用于一般的 macOS/iOS 环境, 后面一段适用于带 BlocksRuntime 的 Linux 环境.然后照着 rewrite 后的 Cpp 代码的样子写一下 Rust#[repr(C)] struct BlockBase<A, R> { isa: *const Class, flags: c_int, _reserved: c_int, invoke: unsafe extern "C" fn(*mut Block<A, R>, ...) -> R, }这里 repr(C) 表示的是使用 C 的内存布局, 这里 A 跟 R 泛型表示的是参数类型跟返回结果, 接着我们要描述 Block#[repr(C)] struct ConcreteBlock<A, R, F> { base: BlockBase<A, R>, descriptor: BlockDescriptor<ConcreteBlock<A, R, F>>, } #[repr(C)] struct BlockDescriptor<B> { _reserved: c_ulong, block_size: c_ulong, copy_helper: unsafe extern "C" fn(&mut B, &B), dispose_helper: unsafe extern "C" fn(&mut B), }copy 跟 dispose这里多了两个叫 copy, dispose 的东西, 前面讲到的 Block 全是跟基础类型(譬如 int) 相关的行为, 如果跟 objc 对象打交道, rewrite-cpp 后就会生成 copy 跟 dispose, 主要是为了管理 objc 对象的内存, 我们可以来验证一下#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface Person : NSObject { @public int _number; } @end NS_ASSUME_NONNULL_END @implementation Person @end int main(void) { @autoreleasepool { Person *person = [[Person alloc] init]; person->_number = 10; void (^test)(void) = ^{ NSLog(@"%d", person->_number); }; test(); } return 0; }然后做一下 rewrite 操作// ... static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);} static struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0}; // ...所以我们得在 Rust 这边加上这两个玩意, 由于这两个函数是 objc 管理的, 所以 Rust 这边主要是利用一下 drop 的行为unsafe extern "C" fn block_context_dispose<B>(block: &mut B) { std::ptr::read(block); } unsafe extern "C" fn block_context_copy<B>(_dst: &mut B, _src: &B) {}现在来定义一下 Block#[repr(C)] pub struct Block<A, R> { _base: PhantomData<BlockBase<A, R>>, }Block 内部是由 BlockBase 组成, 但其实并没有用到它, 所以直接用幽灵数据包裹一下, 接着写个 RcBlock 来包装一下 Block 结构体, 顺便把 _Block_copy _Block_release extern 出来, 在 RcBlock drop 时调用 _Block_release, 引用计数增加时调用 _Block_copyextern "C" { // ... fn _Block_copy(block: *const c_void) -> *mut c_void; fn _Block_release(block: *const c_void); } pub struct RcBlock<A, R> { ptr: *mut Block<A, R>, } impl<A, R> RcBlock<A, R> { pub unsafe fn new(ptr: *mut Block<A, R>) -> Self { RcBlock { ptr } } pub unsafe fn copy(ptr: *mut Block<A, R>) -> Self { let ptr = _Block_copy(ptr as *const c_void) as *mut Block<A, R>; RcBlock { ptr } } } impl<A, R> Clone for RcBlock<A, R> { fn clone(&self) -> Self { unsafe { RcBlock::copy(self.ptr) } } } impl<A, R> Deref for RcBlock<A, R> { type Target = Block<A, R>; fn deref(&self) -> &Self::Target { unsafe { &*self.ptr } } } impl<A, R> Drop for RcBlock<A, R> { fn drop(&mut self) { unsafe { _Block_release(self.ptr as *const c_void); } } }然后再来完善 ConcreteBlock, 主要是把 Rust 的闭包转换成 ConcreteBlock, 在此之前先弄个把参数抽象出来, 先弄个单个参数的, 比较好处理pub trait BlockArguments: Sized { unsafe fn call_block<R>(self, block: *mut Block<Self, R>) -> R; } impl<A> BlockArguments for A { unsafe fn call_block<R>(self, block: *mut Block<Self, R>) -> R { let invoke: unsafe extern "C" fn(*mut Block<Self, R>, A) -> R = { let base = block as *mut BlockBase<Self, R>; mem::transmute((*base).invoke) }; let a = self; invoke(block, a) } }然后可以考虑一下多个参数的怎么处理, 没有参数的又怎么处理. 只要把上面的 A 改成元组包装一下, 再用元组处理多个参数的情况impl<A> BlockArguments for (A,) { unsafe fn call_block<R>(self, block: *mut Block<Self, R>) -> R { let invoke: unsafe extern "C" fn(*mut Block<Self, R>, A) -> R = { let base = block as *mut BlockBase<Self, R>; mem::transmute((*base).invoke) }; let (a,) = self; invoke(block, a) } } impl<A, B> BlockArguments for (A, B) { unsafe fn call_block<R>(self, block: *mut Block<Self, R>) -> R { let invoke: unsafe extern "C" fn(*mut Block<Self, R>, A, B) -> R = { let base = block as *mut BlockBase<Self, R>; mem::transmute((*base).invoke) }; let (a, b) = self; invoke(block, a, b) } }不过这样太无脑了, 假如有 12 个参数就要写 12 遍, 写个宏先macro_rules! block_args_impl { ($($a:ident : $t:ident), *) => ( impl<$($t),*> BlockArguments for ($($t,)*) { unsafe fn call_block<R>(self, block: *mut Block<Self, R>) -> R { let invoke: unsafe extern "C" fn(*mut Block<Self, R> $(, $t)*) -> R = { let base = block as *mut BlockBase<Self, R>; mem::transmute((*base).invoke) }; let ($($a,)*) = self; invoke(block $(, $a)*) } } ); } block_args_impl!(); block_args_impl!(a: A); block_args_impl!(a: A, b: B); block_args_impl!(a: A, b: B, c: C); block_args_impl!(a: A, b: B, c: C, d: D); block_args_impl!(a: A, b: B, c: C, d: D, e: E); block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F); block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G); block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H); block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I); block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J); block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K); block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L);现在来定义个 IntoConcreteBlock 的 trait, 主要是把 Rust 闭包转化成 ConcreteBlock, 因为有多个参数的情况, 所以又要一对一式地实现对应个数的, 顺便先把解引用, 克隆之类的 trait 实现一下, copy 函数让 RcBlock 持有 blockpub trait IntoConcreteBlock<A>: Sized where A: BlockArguments, { type ReturnType; fn into_concrete_block(self) -> ConcreteBlock<A, Self::ReturnType, Self>; } impl<A, R, F> ConcreteBlock<A, R, F> where A: BlockArguments, F: IntoConcreteBlock<A, ReturnType = R>, { pub fn new(closure: F) -> Self { closure.into_concrete_block() } } impl<A, R, F> ConcreteBlock<A, R, F> { unsafe fn with_invoke(invoke: unsafe extern "C" fn(*mut Self, ...) -> R, closure: F) -> Self { ConcreteBlock { base: BlockBase { isa: &_NSConcreteStackBlock, flags: 1 << 25, _reserved: 0, invoke: mem::transmute(invoke), }, descriptor: Box::new(BlockDescriptor::new()), closure, } } } impl<A, R, F> ConcreteBlock<A, R, F> where F: 'static, { pub fn copy(self) -> RcBlock<A, R> { unsafe { let mut block = self; let copied = RcBlock::copy(&mut *block); mem::forget(block); copied } } } impl<A, R, F> Deref for ConcreteBlock<A, R, F> { type Target = Block<A, R>; fn deref(&self) -> &Self::Target { unsafe { &*(&self.base as *const _ as *const Block<A, R>) } } } impl<A, R, F> DerefMut for ConcreteBlock<A, R, F> { fn deref_mut(&mut self) -> &mut Block<A, R> { unsafe { &mut *(&mut self.base as *mut _ as *mut Block<A, R>) } } } impl<A, R, F> Clone for ConcreteBlock<A, R, F> where F: Clone, { fn clone(&self) -> Self { unsafe { ConcreteBlock::with_invoke(mem::transmute(self.base.invoke), self.closure.clone()) } } }参数相关的, 先把一个的情况写出来impl<A, R, X> IntoConcreteBlock<(A,)> for X where X: Fn(A) -> R, { type ReturnType = R; fn into_concrete_block(self) -> ConcreteBlock<(A,), R, X> { unsafe extern "C" fn concrete_block_invoke_args1<A, R, X>( block_ptr: *mut ConcreteBlock<A, R, X>, a: A, ) -> R where X: Fn(A) -> R, { let block = &*block_ptr; (block.closure)(a) } let f: unsafe extern "C" fn(*mut ConcreteBlock<A, R, X>, a: A) -> R = concrete_block_invoke_args1; unsafe { ConcreteBlock::with_invoke(mem::transmute(f), self) } } }继续用宏处理macro_rules! concrete_block_impl { ($f:ident) => ( concrete_block_impl!($f,); ); ($f:ident, $($a:ident : $t:ident),*) => ( impl<$($t,)* R, X> IntoConcreteBlock<($($t,)*)> for X where X: Fn($($t,)*) -> R { type ReturnType = R; fn into_concrete_block(self) -> ConcreteBlock<($($t,)*), R, X> { unsafe extern fn $f<$($t,)* R, X>( block_ptr: *mut ConcreteBlock<($($t,)*), R, X> $(, $a: $t)*) -> R where X: Fn($($t,)*) -> R { let block = &*block_ptr; (block.closure)($($a),*) } let f: unsafe extern fn(*mut ConcreteBlock<($($t,)*), R, X> $(, $a: $t)*) -> R = $f; unsafe { ConcreteBlock::with_invoke(mem::transmute(f), self) } } } ); } concrete_block_impl!(concrete_block_invoke_args0); concrete_block_impl!(concrete_block_invoke_args0, a: A); concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B); concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C); concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D); concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E); concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F); concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F, g: G); concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H); concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I); concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J); concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K); concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L);基本上已经用 Rust 把 Block 包装好了. 了解 objc Block 原理, 再配合上 Rust 的代码风格. 现在就是试试在 objc 端调用 Rust 的 Block 试试效果.在 objc 项目中试用先在 lib.rs 写上以下内容#[no_mangle] unsafe extern "C" fn sum(block: &Block<(i32, i32), i32>) -> i32 { block.call((1, 2)) + 1 }主要是调用 block 后加 1然后 Cargo.toml 加上[lib] name = "block" crate-type = ["staticlib", "cdylib"]后执行cargo build --release就能生成静态库, 为了简单起见, 直接写个 main.m 然后用 clang 编译同时链接静态库, 当然别忘了加上头文件, 内容如下// LLBlock.h #ifndef LLBlock_h #define LLBlock_h #import <Foundation/Foundation.h> int32_t sum(int32_t (^block)(int32_t, int32_t)); #endif /* LLBlock_h */// main.m #import "LLBlock.h" int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"%d", sum(^int32_t(int32_t a, int32_t b) { return a + b; })); } return 0; }然后用这个命令编译链接生成一个可执行文件cc ./main.m -framework Foundation ./libblock.a -o main && ./main只要是在 macOS 环境下, 应该能看到数字 4 的输出至此, 我们的任务完成了.
2022年04月25日
86 阅读
0 评论
0 点赞
2021-03-22
0b0010-抽象bit操作
提出问题这趟没屁话。接上文,我们发觉一桩事,修改状态寄存器的时候,我们不够抽象,譬如我想处理 Zero Flag,我需要通过位运算符去操作一个数,每次写二进制,很难受,现在来改得抽象一些解决问题我们发觉状态寄存器有 C、Z、I、D、B、U、V、N 这些状态,那就定义一个类型来描述它们。状态描述状态含义Carry Flag进位标志,操作后导致结果的第 7 位溢出或者 0 位下溢,就设置Zero Flag零标志,如果操作结果为零,就设置。Interrupt Disable中断禁用标志,设置后 CPU 将不处理设备的中断,除非执行清除中断禁用。Decimal Mode十进制模式,CPU 在进行加减时遵守二进制编码的十进制(BCD)算术规则。BBRK 相关U中断相关,这个要配合 B 标志使用Overflow Flag溢出标志,结果出现溢出时设置。Negative Flag负标志,操作结果的第 7 位设置为 1,就要设置。关于中断这里先不讲,直接讲概念没用,后面有实际场景来补充更好理解。struct Flag: OptionSet { static let C = Flag(rawValue: 0b0000_0001) // 1 << 0 static let Z = Flag(rawValue: 0b0000_0010) // 1 << 1 static let I = Flag(rawValue: 0b0000_0100) // 1 << 2 static let D = Flag(rawValue: 0b0000_1000) // 1 << 3 static let B = Flag(rawValue: 0b0001_0000) // 1 << 4 static let U = Flag(rawValue: 0b0010_0000) // 1 << 5 static let V = Flag(rawValue: 0b0100_0000) // 1 << 6 static let N = Flag(rawValue: 0b1000_0000) // 1 << 7 internal var rawValue: UInt8 = 0 init(rawValue flag: UInt8) { rawValue = flag } func bits() -> UInt8 { rawValue } }这样我就把这些状态抽象出来了,如果不想用 rawValue 这个命名,可以把 OptionSet 去掉,反正对应着现在的内容写就可以,使用是一样的效果,OptionSet 是一个协议,用来约束我们的结构体。然后我们要做得就是把行为抽象,根据我们目前已有的代码,我们需要两个函数,当然我也自作主张地添加了一个 set 方法用来处理分支的情况extension Flag { mutating func insert(other: Flag) { rawValue |= other.bits() } mutating func remove(other: Flag) { rawValue &= (~other.bits()) } mutating func set(other: Flag, condition: Bool) { if condition { insert(other) } else { remove(other) } } }修改我们得把 CPU 的 status 改成我们定义的 Flag 结构体class CPU { // ... var status: Flag = Flag(rawValue: 0b0010_0100) // ... } extension CPU { func reset() { // ... status = Flag(rawValue: 0b0010_0100) // ... } }我们的仿真代码也要改一下extension CPU { func interpret(program: [UInt8]) { pc = 0 while true { let code = program[Int(pc)] pc += 1 switch code { // ... case 0xa9: let param = program[Int(pc)] pc += 1 a = param // 处理 Zero Flag status.set(other: .Z, condition: a == 0) // 处理 Negative Flag status.set(other: .N, condition: a >> 7 == 1) default: break } } } }测试由于我们把实现修改嘞,所以我们还要把测试代码改一下,看看效果func testSth() { let cpu = CPU() cpu.interpret(program: [0xa9, 0x00, 0x00]) assert(cpu.status.bits() & 0b0000_0010 == 0b10) cpu.reset() cpu.interpret(program: [0xa9, 0x05, 0x00]) assert(cpu.a == 0x05) assert(cpu.status.bits() & 0b0000_0010 == 0) assert(cpu.status.bits() & 0b1000_0000 == 0) }Good Job,现在只要一两行代码就把之前的操作涵盖了,后面仿真其他指令的时候就更容易嘞,心智负担不会太重。
2021年03月22日
25 阅读
0 评论
0 点赞
1
2