最近在研究 RUST FFI(Foreign Function Interface),刚好又比较熟悉 iOS 开发。
为什么要用 rust:
使用 rust lib 开发 solana 移动客户端,主要分为两部分:
代码已上传到 github: https://github.com/kouliang/solana-ios.git
创建 rust lib 项目
cargo 命令: cargo new solana-core --lib
添加依赖库
[lib]
name = "solana_core"
crate-type = ["staticlib"]
[dependencies]
libc = "0.2.169"
solana-client = "2.1.7"
solana-sdk = "2.1.7"
rust-client = { git = "https://github.com/kouliang/solana-client.git" }
3.方法实现
要遵循 rust 中 FFI 标准: https://doc.rust-lang.org/nomicon/ffi.html#rust-side
test_key_pair 函数的功能是:接收外部传递过来的字符串,并尝试转换为 keypair 类型。如果转换成功则返回此 keypair 对应的账户地址,如果失败返回错误信息。
#[no_mangle] 是必要的,告诉 Rust 编译器:不要乱改函数的名称。
#[no_mangle]
pub extern "C" fn test_key_pair(content: *const libc::c_char) -> *const libc::c_char {
let c_str = unsafe { std::ffi::CStr::from_ptr(content) };
let content = c_str.to_str().unwrap();
let mut reader = Cursor::new(content.as_bytes());
let keypair = keypair::read_keypair(&mut reader);
match keypair {
Ok(keypair) => {
let pubkey = keypair.pubkey();
let pubkey = pubkey.to_string();
return std::ffi::CString::new(format!("Address: {:?}", pubkey)).unwrap().into_raw();
},
Err(e) => {
return std::ffi::CString::new(format!("Error: {:?}", e)).unwrap().into_raw();
}
}
}
#[no_mangle]
pub extern "C" fn test_rpc(content: *const libc::c_char) -> *const libc::c_char {
let c_str = unsafe { std::ffi::CStr::from_ptr(content) };
let content = c_str.to_str().unwrap();
let lowercase = content.to_lowercase();
let url = match lowercase.as_str() {
"localhost" => "http://localhost:8899".to_string(),
"testnet" => "https://api.testnet.solana.com".to_string(),
"devnet" => "https://api.devnet.solana.com".to_string(),
"mainnet" => "https://api.mainnet-beta.solana.com".to_string(),
other => other.to_string()
};
let client = RpcClient::new_with_commitment(url, CommitmentConfig::confirmed());
let block_height = client.get_block_height();
match block_height {
Ok(block_height) => {
return std::ffi::CString::new(format!("block_height: {:?}", block_height)).unwrap().into_raw();
},
Err(e) => {
return std::ffi::CString::new(format!("Error: {:?}", e)).unwrap().into_raw();
}
}
}
4.交叉编译
rustup target list
rustup target add <your_target>
cargo build --target=<your_target> --release
target triple 格式是 {arch}-{vendor}-{sys}-{abi} 分别代表:编译程序的主机系统、供应商、操作系统、ABI 接口,最后 abi 有时候可以省略
一定要根据开发环境选择对应的 target 才能编译出可用的库。我本地是 Appel M2 要在 模拟器上执行,所以选择的是 aarch64-apple-ios-sim
最终的编译产物是 target/aarch64-apple-ios-sim/release/libsolana_core.a
将 libsolana_core.a 添加到 iOS 项目中。具体操作不赘述,可以搜索 iOS 添加静态库。
创建桥接头文件
#ifndef Bridging_Header_h
#define Bridging_Header_h
#include <stdint.h>
const char* test_rpc(const char* content);
const char* test_key_pair(const char* content);
const char* save_config(const char* rpc, const char* keypair);
const char* balance(const char* content);
const char* transfer_to(const char* address, const char* amount);
#endif /* Bridging_Header_h */
3.swift 中直接使用 libsolana_core 中提供的方法。
@IBAction func balanceRequest(_ sender: Any) {
let addr = balanceTextField.text ?? ""
let result = balance(addr)!
let resultstr = String(cString: result)
print(resultstr)
balanceLable.text = resultstr
}
@IBAction func transferRequest(_ sender: Any) {
let addr = address.text ?? ""
let am = amount.text ?? ""
let result = transfer_to(addr, am)!
let resultstr = String(cString: result)
print(resultstr)
transferLabel.text = resultstr
}
4.solana-ios App 使用说明