pbni-rs
pbni-rs是PBNI
的Rust绑定,使开发者可以使用Rust语言进行PowerBuilder扩展开发.
注意 pbni-rs只支持PowerBuilder 10及以上版本.
Feature flags
Flag | Description | Default |
---|---|---|
global_function |
全局函数导出 | enabled |
nonvisualobject |
不可视对象导出 | enabled |
visualobject |
可视对象导出 | enabled |
decimal |
Decimal 类型处理,将引入rust_decimal 库 |
enabled |
datetime |
日期类型处理,将引入chrono 库 |
enabled |
vm |
加载虚拟机以及创建Session 等功能,将引入libloading 库 |
disabled |
什么是PBNI?
PBNI
是PowerBuilder虚拟机的C++扩展接口(PowerBuilder Native Interface).
通过PBNI
接口我们可以使用底层语言与PBVM进行集成交互,极大的扩展了PowerBuilder的能力.
环境要求
- rustc: 最低1.51 (支持stable)
- toolchain: stable-x86_64-pc-windows-msvc
- target: i686-pc-windows-msvc
开始使用
- 添加32位目标平台
> rustup
- 添加
pbni-rs
到Cargo.toml
[]
= ["cdylib"]
[]
= "0.1.0"
注意
crate-type
需要为cdylib
- 编译
> cargo
你也可以在工程目录下创建
.cargo/config
文件
- 配置默认编译目标,免去输入
--target i686-pc-windows-msvc
参数[] = "i686-pc-windows-msvc"
- 配置静态链接CRT
[] = ["-C", "target-feature=+crt-static"]
错误排查
- 编译出现
_PBX_GetVersion@0
此类链接错误 产生原因是因为你的项目代码没有引用pbni-rs,所以被编译器优化掉了pbni-rs库生成的导出符号,解决方法是项目中引入pbni-rs代码.
//lib.rs
use *;
引入全部名称不是必须的,只要你的代码中使用了
pbni
即可
数据类型映射
PowerBuilder | Rust |
---|---|
int |
pbint ,i16 |
uint |
pbuint ,u16 |
long |
pblong ,i32 |
ulong |
pbulong ,u32 |
longlong |
pblonglong ,i64 |
real |
pbreal ,f32 |
double |
pbdouble ,f64 |
decimal |
Decimal (需要开启decimal 特性) |
byte |
pbbyte ,u8 |
boolean |
bool |
char |
PBChar |
string |
&PBStr ,PBString ,String |
blob |
&[u8] ,Vec<u8> |
date |
NaiveDate (需要开启datetime 特性) |
time |
NaiveTime (需要开启datetime 特性) |
datetime |
NaiveDateTime (需要开启datetime 特性) |
any |
Value |
任意对象 | Object |
任意数组 | Array |
PowerBuilder的所有类型都是Nullable的,Rust里使用
Option<T>
表示.
字符串
PowerBuilder字符编码是UTF-16LE,而Rust字符串编码采用的是UTF-8编码,这使得字符串操作时可能会有一点的性能损失.如果对性能有较高要求,请使用&PBStr
进行交互,避免发生内存拷贝和编码转换.
pbni-rs提供了pbstr!
宏在编译时生成&'static PBStr
:
let rstr: &'static str = "hell world!";
let pstr: &'static PBStr = pbstr!;
pbni-rs使用
widestring
进行UTF-16编码转换.
内存安全
pbni-rs的Safe代码提供100%类型和内存安全保证,对于无法提供100%的内存安全保证的接口都使用了unsafe
标记.最常见的就是获取引用,比如&PBStr
.
可以看到Object
的get_var_str
是unsafe
方法,而get_var_string
则是Safe的,这是因为像set_var_str
这样的方法可能会修改get_var_str
返回引用的内存,导致垂悬引用(Dangling Reference).
pbni-rs无法避免这种情况,因为对象的内部状态不完全由Rust维护,有很多途径会导致内存被修改,所以pbni-rs中所有返回引用的方法都将是Unsafe的,需要开发者自己保证对其正确使用.
线程安全
Session
及其所有分配的资源都不能跨线程访问(包括Object
/Array
),因此它们都不是Send
和Sync
的,跨线程访问建议结合消息队列实现.
代码生成
pbni-rs可以非常方便将Rust对象或函数与PowerBuilder建立映射,全部由pbni-rs生成代码,省去手写繁琐的样板代码的同时保证了类型安全.
映射PowerBuilder全局函数
- PowerBuilder
global type gf_bit_or from function_object native "pbrs.dll"
end type
forward prototypes
global function long gf_bit_or (readonly long a,readonly long b)
end prototypes
- C++
PBXRESULT
PBXRESULT PBXCALL
- Rust(pbni-rs)
use *;
映射PowerBuilder对象
- PowerBuilder
forward
global type n_pbni from nonvisualobject
end type
end forward
global type n_pbni from nonvisualobject native "pbrs.dll"
public function string of_hello (string world)
end type
global n_pbni n_pbni
on n_pbni.create
call super::create
TriggerEvent( this, "constructor" )
end on
on n_pbni.destroy
TriggerEvent( this, "destructor" )
call super::destroy
end on
- C++
;
PBXRESULT PBXCALL
- Rust(pbni-rs)
use *;
参数提取
pbni-rs代码生成宏会自动提取PB参数为Rust映射的数据类型,参数的提取顺序与PB端定义的顺序保持一致.其中有几个特殊的参数: Session
/CallInfoRef
/ArgumentsRef
,这几个参数对位置没有要求并且数量任意.
use *;
//等同于
注意 Rust端参数列表须与PB端定义的类型数量以及顺序一致,任何不匹配的情况都会在运行时触发异常.
- Rust端参数如果为非空类型(
Option
),而PB端提供的参数为NULL,那么框架自动返回NULL给PB调用端,兼容PB标准库的做法,也就是说任何参数传递为NULL那么返回值就为NULL,除非Rust端显式用Option<T>
接收. - 当参数列表通过
CallInfoRef
/ArgumentsRef
接收后,将不再匹配参数数量,因为这两个参数已经隐式表示接收了所有的参数.CallInfoRef
/ArgumentsRef
一般用于处理引用传递参数以及变长参数列表.
可选参数列表匹配
以下示例为重载可选参数列表的匹配映射
- PowerBuilder
global type gf_test from function_object native "pbrs.dll"
end type
forward prototypes
global function long gf_test (readonly long a,readonly long b)
global function long gf_test (readonly long a,readonly long b,readonly long c)
global function long gf_test (readonly long a,readonly long b,readonly long c,readonly long d)
end prototypes
- Rust(pbni-rs)
use *;