应用软件是指|使用 Rust 开发一个小程序沙盒 SDK 原生扩展

应用软件是指|使用 Rust 开发一个小程序沙盒 SDK 原生扩展

2022中国现状调查全面启动! >>>

不懂移动开发,也可以贡献移动代码

无限提升小程序安全运行沙盒

小程序安全运行的沙盒,可以由App开发者以SDK的形式嵌入,让他们的App成为秒级运行小程序的超级App。在这里,“App”不仅仅指 iOS 或应用程序。主机可以像具有多核 CPU 和更多内存的 PC 一样强大,也可以是计算能力有限的嵌入式设备 ( ),例如带有触摸屏的 PI。

主机硬件环境和软件环境不同,需要暴露给小程序使用的功能也不同;此外,在不同的业务环境中,应用集成的原生能力(如地图、支付、加密等)、音视频,甚至AR/XR等)也不同。支持开发者自定义各种API接口,注入SDK,供小程序开发者通过这种方式调用。

这样,任何小程序理论上都可以利用其宿主环境的任何技术能力,而不仅限于SDK提供的标准接口。

官方支持的自定义接口扩展方法

目前官方支持的自定义接口需要使用宿主环境的原生技术实现,e.g.在 iOS 上需要用 -C 或 Swift 开发,在 Java/etc 上。这种方式需要工程师至少在两个平台上分别开发——当然,这在过去不是问题,任何移动应用程序都必须维护两个团队来开发两个版本。但是当你要支持更多类型的终端时,就变得很麻烦了。

在“小程序+Rust”系列中提到,对于音视频编码处理、加解密等纯逻辑和算法功能,完全没有人机交互部分。使用跨平台的通用语言来实现更方便。但是我们不想乱用 C 代码,Rust 是一个不错的选择。作为一种新兴的、内存安全的、线程安全的语言,Rust 可以跨平台编译,性能高,体积小。特别适合设备端编程,包括在低算力、低功耗、低内存的物联网设备上开发。代码;而且,Rust 已经是一种官方支持的系统语言。

那么,是否可以支持使用 Rust 的开发人员为其安全运行的沙箱提供自定义扩展?

我们来看看环境中的分工与协作

在回答上述问题之前,我们在这里想象一下,如果在一个端到端的应用软件生产环节,技术是每个环节的“粘合剂”。新型技术团队的角色(技能)构成是怎样的:

这是一条基本的“流水线”,每个岗位使用不同的语言技能,分工越清晰越好。即使这些事情是同一个人“遍天下”,你也需要明确自己会在不同的角色下做不同的事情,这有助于你理出一个合理的结构。在端到端的技术链接上,定义每个链接的功能范围。

胶水代码与生态

这项技术,纯粹从软件工程的角度来看,起到胶水代码(glue)的作用,将多种技术的软件系统粘合在一起。胶水代码往往是最繁琐、最容易出错的地方。解决这个级别的技术可以极大的释放开发者的生产力和创造力。相当于建造了流水线,每个环节都可以独立润滑、丰富和加强。并且每个环节都变得更加专业,甚至形成了自己的零部件“供应链”。

例如通过将HTML/部分技术与设备端原生技术对接,引入大量可以专注于小程序开发的工程师,提供丰富的应用场景;将设备的通用逻辑解耦成一个可插拔的“插件”,可以进一步推动只专注于这部分工作的人,生产出丰富优质的插件。只要有标准化,就有机会形成生态。

提供端到端的应用解决方案,已经成为一个“集成”,在技术环节的每个环节的“供应链”中,选择你需要的部件来组装你自己的应用。

插件开发者:使用Rust实现SDK的“插件”

就像小程序开发者不需要了解任何iOS/ObjC/Swift、/Java/技能一样,只要掌握HTML/就可以开发出有用的应用程序,SDK的开发者可以自己开发无需过多了解操作系统平台的编程知识,甚至不会处理 ObjC 和 Java。

我们可以在技术上做到这一点。请按照以下步骤操作 – 请注意,以下所有操作都发生在 Rust 端,并且需要在 ObjC/Java 空间中进行零代码开发。

准备构建静态库所需的环境

创建一个带有cargo的lib-type项目,例如

cargo new --lib myplugin

然后修改生成的Cargo.toml:

[package]
name = "myplugin"
version = "0.1.0"
edition = "2021"
[lib]
name = "myplugin"
# this is needed to build for iOS and Android.
crate-type = ["staticlib", "lib"]
[dependencies]
serde_json = "1.0.81"  # 建议使用这个crate实现json对象序列化
# 其他你准备封装或者依赖的crate

定义要注入的 API

在 src/lib.rs 中,开始定义和封装您计划提供给本地主机应用程序开发人员以注入其 SDK 的函数。

首先,定义一个新类型:

type FinClipCall = fn(&String) -> String;

请将此类型命名为”,该类型所代表的函数的签名必须是:

fn(&String) -> String

其实是一个函数指针,可以指向这样一个函数,例如:

fn invoke(param: &String) -> String {...}

注意这种类型的函数,期望的输入参数是一个有效的JSON字符串,返回的也必须是一个有效的JSON字符串。由于自定义的API,统一使用JSON作为输入输出参数,方便小程序端代码的处理。因此,建议在上面引入这个 crate 来帮助做一些 JSON 相关的数据转换。

其次,开始实现你的函数实现,例如:

fn api_drinker(input: &String) -> String {
    // 先处理一下入参,把进来的字符串检测为合法JSON对象,
    // 再用serde_json把它转化为某个类型的参数对象,供后续使用
    println!("invoked with parameter {}", input);
    
    //中间的逻辑算法从略,这里应该是你自己的算法,产生的结果对象,可以
    //用serde_json进行Json serialization
    let john = json!({
        "name": "john doe",
        "phones": "1234567"
    });
    john.to_string()
}
fn api_whisky(input: &String) -> String {
    // 先处理一下入参,把进来的字符串检测为合法JSON对象,
    // 再用serde_json把它转化为某个类型的参数对象,供后续使用
    println!("invoked with parameter {}", input);
    
    //中间的逻辑算法从略,这里应该是你自己的算法,产生的结果对象,可以
    //用serde_json进行Json serialization
    let brands = json!({
        "whisky": {
            "jack": "daniel",
            "johny": "walker",
            "henry": "Mckenna",
            "suntory": "toki"
        }
    });
    brands.to_string()
}

等等,用类似的签名 API 实现你的函数。

以文本命名您的 API 并注册“花名”

在小程序方面,调用自定义 API 的方式是通过 API 的名称。比如你把一个API接口命名为’abc’,那么这个接口注入SDK后,它在侧边的调用就是’ft.abc(…)’。

这里,你需要给每个自定义函数一个文本名称并映射它们的关系,这确实有点像代码编译器中的表格。在我们开发的这个项目中,还有一点需要注意:

pub unsafe extern "C" fn myplugin_register_apis() -> *mut HashMap<String, FinClipCall> {
    let mut map: HashMap<String, FinClipCall> = HashMap::new();
    map.insert("get_drinker".to_string(), api_drinker);
    map.insert("get_whisky".to_string(), api_whisky);
    Box::into_raw(Box::new(map))
}

这个函数有什么作用?虽然只有几行代码,但还是需要说明一下:

至此,我们的工作基本完成了90%,是不是很简单?

向其他语言公开提供“名册”的函数

到目前为止,我们一直在 Rust 世界中辗转反侧。但最终这些结果必须被外界发现和使用。最后,异步是将“名册”发送到异构语言的世界。我们需要使用 Rust FFI( ) 来让 Rust 编译器在编译上述代码时生成 C 风格的代码库,所以我们需要对上述函数做一点补充:

#[no_mangle]
pub unsafe extern "C" fn myext_register_apis() -> *mut HashMap<String, FinClipCall> {
    let mut map: HashMap<String, FinClipCall> = HashMap::new();
    map.insert("api_drinker".to_string(), api_drinker);
    map.insert("api_whisky".to_string(), api_whisky);
    Box::into_raw(Box::new(map))
}

”告诉Rust编译器在编译的时候不要混淆或者改变’i()’函数的名字,否则ObjC或者Java/JNI端没有办法知道调用什么名字。

注意,在上述函数的声明中,有”和'”C”‘的符号。很容易理解,就是以C函数调用的形式标明这个函数被异构语言使用,而”涉及到Rust关于内存安全和线程安全的规则或思路是什么,读者可以理解细节。这里我们主要使用’Box::’函数,也就是我们取一个只有Rust才能解析的数据结构,通过一个原始指针丢给C端,相当于这块内存被异构语言下的代码被“持有”,其内存安全不再被Rust监控和保证,因此并不安全。

这里有一个问题,那就是:既然Rust端不能被C端解析,那扔这个东西的raw指针有什么用呢?很有用,因为它实际上相当于一个不透明的指针,由主机应用程序端“持有”。当宿主需要调用一个 Rust 函数时,它只是将其传递回去。

有去有去,记得防止内存泄漏

在 Rust FFI 中,生成的每个 ” 操作最终都必须有一个对应的 ” 操作。前者将一块Rust管理的内存的控制权转移,后者是外部异构语言中的代码必须将内存的控制权交还给Rust,否则会发生内存泄漏。所以,最后,我们需要为异构语言添加一个函数应用软件是指,使用上面的东西后记得通知Rust回收:

#[no_mangle]
pub unsafe extern "C" fn myext_release(ptr: *mut HashMap<String, FinClipCall>) {
    if !ptr.is_null() {
       drop(Box::from_raw(ptr));
    }
}

注意,调用这个函数是宿主应用程序开发者的责任,所以必须在你的使用文档中强调它们。这很丑但是好像没有什么好办法,而且跨语言调用总有一些小不便。

回顾:使用 Rust 开发 SDK 扩展的步骤

其实很简单自由,没有专门的库或协议来继承和实现,就是“徒手”写一个Rust lib,只要包含以下元素即可:

交付物是一个静态库

正如《小程序+Rust》系列中介绍的,Rust代码需要跨平台编译构建相关目标架构下的-apple-ios、-apple-ios和二进制库。例如,构建适合在 ios 和 ios 设备上运行的东西:

cbindgen src/lib.rs -l c > myplugin.h
cargo lipo --release

您最终交付给宿主应用开发者的内容应包括:

应用软件是指|使用 Rust 开发一个小程序沙盒 SDK 原生扩展

至此,作为 Rust 开发者提供 SDK 的使命就完成了。再次明确,”的头文件名、创建名册数据结构和释放名册内存的函数名都是由开发者决定的。

应用开发者:如何使用 Rust 插件

接下来,由宿主应用程序的开发人员来使用它。宿主应用是运行在iOS或其他设备上的应用软件,它嵌入SDK,获得运行小程序的能力。

写代码只需要加三行

集成SDK详情见官网,“小程序+Rust”也有介绍,这里不再赘述。下面以iOS App的集成为例,在.m中添加三行代码(下面有注释的三行):

#import "AppDelegate.h"
#import 
#import "FinClipExt.h"  //引入一个特殊的库支持Rust扩展
#import "myplugin.h"    // 引入要安装供FinClip小程序开发者使用的SDK extension
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    NSString *appKey = @"22LyZEib0gLTQdU3MUauARgvo5OK1UkzIY2eR+LFy28NAKxKlxHnzqdyifD+rGyG";
    FATConfig *config = [FATConfig configWithAppSecret:@"8fe39ccd4c9862ae" appKey:appKey];
    config.apiServer = @"http://127.0.0.1:8000";
    [[FATClient sharedClient] initWithConfig:config error:nil];
    [[FATClient sharedClient] setEnableLog:NO];
    // 安装 myplugin 到在这里初始化的FinClip SDK中
    [[FinClipExt singleton] installFor:[FATClient sharedClient] withExt :myplugin_register_apis()];
    
    return YES;
}
#pragma mark - UISceneSession lifecycle
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
    // Called when a new scene session is being created.
    // Use this method to select a configuration to create the new scene with.
    return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
}
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {
    // Called when the user discards a scene session.
    // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
    // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
@end

以上代码,. m,在使用 xcode 创建 ObjC 项目时自动生成。我们这里初始化SDK(详见官网,或者《小程序+Rust》系列。在此基础上再安装,代码很简单:

这里的 ” 负责将 Rust API 转换为 ObjC 方法并注入到 SDK 中。

编译构建需要链接静态库

这个库,我们从上面已经可以看出,目前还没有专门的库直接相关。唯一的限制是两个规范:

就是这样。那么这个库是如何注入到SDK中并被小程序调用的呢?神奇的是宿主App的开发者在他的项目中引入了一个名为.a的静态库(这个库还不是官方支持的标准工具,只是我的个人项目,目前只有iOS版本。有兴趣的朋友可以优化,欢迎提供版本,源码在上面,由ObjC和Rust代码组成)。作为用户,你不需要关注实现,只需下载这个静态库,在编译和构建应用程序时指定依赖并链接它。是的。

最后,当然要安装的静态库,.a,必须导入到项目中,一起构建。

作为宿主应用开发者,必须引入一个叫做SDK的SDK,小程序开发者调用的任务也已经完成。

小程序开发者:如何调用 Rust 接口

在上面的“花表”中,有两个分别暴露给了”和”的 API。这两个用 Rust 编写的函数,以 JSON 字符串作为输入和输出参数,经过 .a 的一些“神奇”操作应用软件是指,转化为 ObjC,并动态注入 SDK。要使用这些API,小程序开发者需要在自己的小程序项目的根目录下写一个.js:

module.exports = {
  extApi:[
    {
      name: 'get_drinker',
      sync: true, //同步api
      params: { //扩展api 的参数格式,可以只列必须的属性
      }
    },
    {
      name: 'get_whisky',
      sync: true,
      params: {
      }
    }
 ]
}

此后,在’ft’对象中调用这些API只需通过’ft’对象,如’ft’。

总结

在现代软件项目中,多语言混合编程在所难免——不同的语言适合解决端到端技术环节中不同环节的问题,但也不可避免地带来了集成和集成的麻烦。集成,往往影响开发效率,带来很多麻烦。例如,跨语言传输涉及 API 接口的生成和异构语言中数据结构的重复“翻译”。编写胶水代码非常麻烦。更顺畅地解决了前端异构技术对接的问题。本文进一步介绍了一种更“透明”的方法,让完全不熟悉、不懂ObjC、不懂终端开发的工程师可以使用Rust这个强大的语言,开发逻辑通用的SDK扩展,最终可以被小程序开发者使用。

本文的示例代码在这里。

{{o.name}}

{{m.name}}

相关推荐

本科专业分析专业介绍\\u0026就业方向\\u0026最低投档线\\u0026自荐学科(七)

首先祝各位宝贝们元宵节快乐!新的一期专业分析来啦~快来看看有没有你的专业吧!一起来看...

暨南大学小自考专业有哪些,小自考什么专业简单一点

哈喽大家好!很多小伙伴说什么是自考,小自考又是什么鬼呢?那么接下来的一段时间里,我为...

应用软件是指|laravel 路由是什么意思

在中,路由是外界访问应用程序的通路,或者说路由定义了的应用程序向外界提供服务的具体方...

武汉成人高考院校,湖北成人教育函授站

湖北武汉2022年成人高考官方报名网站/函授站是什么?-最新发布-成人高等学校招生全...

教师招聘面试看学历吗,教师编制看不看学历

今年以来,各地纷纷发布2022年上半年中小学教师资格认定公告。如杭州市教育局所属杭州...