pbni/
lib.rs

1//! # pbni-rs
2//! [![github](https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github)](https://github.com/gaoqiangz/pbni-rs) <br>
3//! [![crates.io](https://meritbadge.herokuapp.com/pbni-rs)](https://crates.io/crates/pbni-rs)
4//! [![docs.rs](https://docs.rs/pbni-rs/badge.svg)](https://docs.rs/pbni-rs)
5//! [![version](https://img.shields.io/badge/rustc-1.51+-ab6000.svg)](https://blog.rust-lang.org/2021/03/25/Rust-1.51.0.html)
6//! ![BSD-2-Clause licensed](https://img.shields.io/crates/l/pbni-rs.svg)
7//!
8//! pbni-rs是[`PBNI`]的Rust绑定,使开发者可以使用Rust语言进行PowerBuilder扩展开发.<br>
9//! **注意** pbni-rs只支持PowerBuilder 10及以上版本.
10//!
11//! # Feature flags
12//!
13//! | Flag              | Description                                                 | Default    |
14//! |-------------------|-------------------------------------------------------------|------------|
15//! | `global_function` | 全局函数导出                                                 | `enabled`  |
16//! | `nonvisualobject` | 不可视对象导出                                               | `enabled`  |
17//! | `visualobject`    | 可视对象导出                                                 | `enabled`  |
18//! | `decimal`         | `Decimal`类型处理,将引入[`rust_decimal`]库依赖                | `enabled`  |
19//! | `datetime`        | 日期类型处理,将引入[`chrono`]库依赖                           | `enabled`  |
20//! | `vm`              | 加载虚拟机以及创建[`Session`]等功能,将引入[`libloading`]库依赖 | `disabled`  |
21//!
22//! [`rust_decimal`]: https://crates.io/crates/rust_decimal
23//! [`chrono`]: https://crates.io/crates/chrono
24//! [`libloading`]: https://crates.io/crates/libloading
25//!
26//! # 什么是PBNI?
27//!
28//! [`PBNI`]是PowerBuilder虚拟机的C++扩展接口(PowerBuilder Native Interface).
29//!
30//! ![Figure]
31//!
32//! 通过[`PBNI`]接口我们可以使用底层语言与PBVM进行集成交互,极大的扩展了PowerBuilder的能力.
33//!
34//! 其他托管语言类似的技术有[`JNI`],[`C++/CLI`]等.
35//!
36//! [`PBNI`]: https://docs.appeon.com/pb2019/native_interface_programmers_guide_and_reference/ch01s01.html
37//! [`JNI`]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/intro.html
38//! [`C++/CLI`]: https://docs.microsoft.com/en-us/cpp/dotnet/native-and-dotnet-interoperability?view=msvc-160
39//! [Figure]: http://infocenter.sybase.com/help/topic/com.sybase.infocenter.dc37794.1250/html/pbnigref/pbni03.gif
40//!
41//! # 环境要求
42//!
43//! - rustc: 最低1.51 **(支持stable)**
44//! - toolchain: stable-x86_64-pc-windows-msvc
45//! - target: i686-pc-windows-msvc
46//!
47//! # 开始使用
48//!
49//! 1. 添加32位目标平台
50//!
51//! ```bash
52//! > rustup target add i686-pc-windows-msvc
53//! ```
54//!
55//! 2. 添加`pbni-rs`到`Cargo.toml`
56//!
57//! ```toml
58//! [lib]
59//! crate-type = ["cdylib"]
60//!
61//! [dependencies]
62//! pbni-rs = "0.1.0"
63//! ```
64//!
65//! > 注意[`crate-type`]需要为[`cdylib`]
66//!
67//! [`crate-type`]: https://doc.rust-lang.org/reference/linkage.html
68//! [`cdylib`]: https://rust-lang.github.io/rfcs/1510-cdylib.html
69//!
70//! 3. 编译
71//!
72//! ```bash
73//! > cargo build --target i686-pc-windows-msvc
74//! ```
75//!
76//! > 你也可以在工程目录下创建`.cargo/config`文件
77//! >
78//! > - 配置默认编译目标,免去输入`--target i686-pc-windows-msvc`参数
79//! >
80//! > ```toml
81//! > [build]
82//! > target = "i686-pc-windows-msvc"
83//! > ```
84//! >
85//! > - 配置静态链接CRT
86//! >
87//! > ```toml
88//! > [target.i686-pc-windows-msvc]
89//! > rustflags = ["-C", "target-feature=+crt-static"]
90//! > ```
91//!
92//! ### 错误排查
93//!
94//! - 编译出现`_PBX_GetVersion@0`此类链接错误 <br>
95//! 产生原因是因为你的项目代码没有引用pbni-rs,所以被编译器优化掉了pbni-rs库生成的导出符号,解决方法是项目中引入pbni-rs代码.
96//! ```rust
97//! //lib.rs
98//! use pbni::*;
99//! ```
100//! > 引入全部名称不是必须的,只要你的代码中使用了`pbni`即可
101//!
102//! # 数据类型映射
103//!
104//! |PowerBuilder|Rust|
105//! |---|---|
106//! |`int`|`pbint`,`i16`|
107//! |`uint`|`pbuint`,`u16`|
108//! |`long`|`pblong`,`i32`|
109//! |`ulong`|`pbulong`,`u32`|
110//! |`longlong`|`pblonglong`,`i64`|
111//! |`real`|`pbreal`,`f32`|
112//! |`double`|`pbdouble`,`f64`|
113//! |`decimal`|[`Decimal`] (需要开启`decimal`特性)|
114//! |`byte`|`pbbyte`,`u8`|
115//! |`boolean`|`bool`|
116//! |`char`|`PBChar`|
117//! |`string`|`&PBStr`,`PBString`,`String`|
118//! |`blob`|`&[u8]`,`Vec<u8>`|
119//! |`date`|[`NaiveDate`] (需要开启`datetime`特性)|
120//! |`time`|[`NaiveTime`] (需要开启`datetime`特性)|
121//! |`datetime`|[`NaiveDateTime`] (需要开启`datetime`特性)|
122//! |`any`|`Value`|
123//! |任意对象|`Object`|
124//! |任意数组|`Array`|
125//!
126//! > PowerBuilder的所有类型都是Nullable的,Rust里使用`Option<T>`表示.<br>
127//!
128//! # 字符串
129//!
130//! PowerBuilder字符编码是[UTF-16LE],而Rust字符串编码采用的是[UTF-8]编码,这使得字符串操作时可能会有一点的性能损失.如果对性能有较高要求,请使用`&PBStr`进行交互,避免发生内存拷贝和编码转换.
131//!
132//! pbni-rs提供了[`pbstr!`]宏在编译时生成`&'static PBStr`:
133//!
134//! ```rust
135//! let rstr: &'static str = "hell world!";
136//! let pstr: &'static PBStr = pbstr!("hell world!");
137//! ```
138//!
139//! > pbni-rs使用[`widestring`]进行UTF-16编码转换.
140//!
141//! [UTF-16LE]: https://en.wikipedia.org/wiki/UTF-16
142//! [UTF-8]: https://en.wikipedia.org/wiki/UTF-8
143//! [`widestring`]: https://crates.io/crates/widestring
144//!
145//! # 内存安全
146//!
147//! pbni-rs的Safe代码提供100%类型和内存安全保证,对于无法提供100%的内存安全保证的接口都使用了`unsafe`标记.最常见的就是获取引用,比如`&PBStr`.
148//!
149//! ```rust
150//! impl<'obj> Object<'obj> {
151//!     pub unsafe fn get_var_str(&self, fid: impl VarId) -> Option<&'obj PBStr> { ... }
152//!     pub fn get_var_string(&self, fid: impl VarId) -> Option<PBString> { ... }
153//!     pub fn set_var_str(&mut self, fid: impl VarId, value: impl AsPBStr) -> Result<()> { ... }
154//! }
155//! ```
156//!
157//! 可以看到`Object`的`get_var_str`是`unsafe`方法,而`get_var_string`则是Safe的,这是因为像`set_var_str`这样的方法可能会修改`get_var_str`返回引用的内存,导致垂悬引用([Dangling Reference]).<br>
158//! pbni-rs无法避免这种情况,因为对象的内部状态不完全由Rust维护,有很多途径会导致内存被修改,所以pbni-rs中所有返回引用的方法都将是[Unsafe]的,需要开发者自己保证对其正确使用.
159//!
160//! [Unsafe]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html
161//! [Dangling Reference]: https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#dangling-references
162//!
163//! # 线程安全
164//!
165//! `Session`及其所有分配的资源都不能跨线程访问(包括`Object`/`Array`),因此它们都不是`Send`和`Sync`的,跨线程访问建议结合消息队列实现.
166//!
167//! # 代码生成
168//!
169//! pbni-rs可以非常方便将Rust对象或函数与PowerBuilder建立映射,全部由pbni-rs生成代码,省去手写繁琐的样板代码的同时保证了类型安全.
170//!
171//! #### 映射PowerBuilder全局函数
172//!
173//! - PowerBuilder
174//!
175//! ```vbscript
176//! global type gf_bit_or from function_object native "pbrs.dll"
177//! end type
178//!
179//! forward prototypes
180//! global function long gf_bit_or (readonly long a,readonly long b)
181//! end prototypes
182//! ```
183//!
184//! - C++
185//!
186//! ```cpp
187//! #include <pbext.h>
188//!
189//! PBXRESULT bit_or(PBCallInfo *ci)
190//! {
191//!     pblong a = ci->pArgs->GetAt(0)->GetLong();
192//!     pblong b = ci->pArgs->GetAt(1)->GetLong();
193//!     return ci->returnValue->SetLong(a|b);
194//! }
195//! PBXRESULT PBXCALL PBX_InvokeGlobalFunction(
196//!     IPB_Session *pbsession,
197//!     LPCWSTR functionName,
198//!     PBCallInfo *ci)
199//! {
200//!     if(::wcscmp(functionName,L"gf_bit_or") == 0)
201//!         return bit_or(ci);
202//!     return PBX_E_NO_REGISTER_FUNCTION;
203//! }
204//! ```
205//!
206//! - Rust(pbni-rs)
207//!
208//! ```rust
209//! use pbni::*;
210//!
211//! #[global_function(name="gf_bit_or")]
212//! fn bit_or(a: pblong, b: pblong) -> pblong {
213//!     a | b
214//! }
215//! ```
216//!
217//! #### 映射PowerBuilder对象
218//!
219//! - PowerBuilder
220//!
221//! ```vbscript
222//! forward
223//! global type n_pbni from nonvisualobject
224//! end type
225//! end forward
226
227//! global type n_pbni from nonvisualobject native "pbrs.dll"
228//! public function string of_hello (string world)
229//! end type
230//! global n_pbni n_pbni
231//!
232//! on n_pbni.create
233//! call super::create
234//! TriggerEvent( this, "constructor" )
235//! end on
236//!
237//! on n_pbni.destroy
238//! TriggerEvent( this, "destructor" )
239//! call super::destroy
240//! end on
241//! ```
242//!
243//! - C++
244//!
245//! ```cpp
246//! #include <pbext.h>
247//!
248//! class CppObject: public IPBX_NonVisualObject
249//! {
250//!     IPB_Session *session;
251//!     pbobject ctx;
252//!
253//!     PBXRESULT handle_hello(PBCallInfo *ci)
254//!     {
255//!         LPCWSTR lpcsWorld = this->session->GetString(ci->pArgs->GetAt(0)->GetString());
256//!         std::wostringstream ss;
257//!         ss << L"hello " << lpcsWorld << L"!";
258//!         return ci->returnValue->SetString(ss.str().c_str());
259//!     }
260//!
261//! public:
262//!     CppObject(IPB_Session *pbsession,pbobject pbobj)
263//!     :session(pbsession),
264//!     ctx(pbobj)
265//!     {}
266//!     virtual ~CppObject() override {};
267//!
268//!     virtual void Destroy() override { delete this; }
269//!
270//!     virtual PBXRESULT Invoke(
271//!         IPB_Session *session,
272//!         pbobject obj,
273//!         pbmethodID mid,
274//!         PBCallInfo *ci) override
275//!    {
276//!         if(mid == 0)
277//!             return this->handle_hello(ci);
278//!         return PBX_E_NO_REGISTER_FUNCTION;
279//!    }
280//! };
281//!
282//! PBXRESULT PBXCALL PBX_CreateNonVisualObject(
283//!     IPB_Session *pbsession,
284//!     pbobject pbobj,
285//!     LPCWSTR className,
286//!     IPBX_NonVisualObject **obj)
287//! {
288//!     if(::wcscmp(className,L"n_pbni") == 0)
289//!     {
290//!         *obj = new CppObject(pbsession,pbobj);
291//!         return PBX_OK;
292//!     }
293//!     return PBX_E_NO_SUCH_CLASS;
294//! }
295//! ```
296//!
297//! - Rust(pbni-rs)
298//!
299//! ```rust
300//! use pbni::*;
301//!
302//! struct RustObject {
303//!     session: Session,
304//!     ctx: ContextObject
305//! }
306//!
307//! #[nonvisualobject(name = "n_pbni")]
308//! impl RustObject {
309//!     #[constructor]
310//!     fn new(session: Session, ctx: ContextObject) -> RustObject {
311//!         RustObject {
312//!             session,
313//!             ctx
314//!         }
315//!     }
316//!     #[method(name="of_Hello")]
317//!     fn hello(&self, world: String) -> String {
318//!         format!("hello {}!",world)
319//!     }
320//! }
321//! ```
322//! #### 参数提取
323//!
324//! pbni-rs代码生成宏会自动提取PB参数为Rust映射的[数据类型],参数的提取顺序与PB端定义的顺序保持一致.其中有几个特殊的参数: [`Session`]/[`CallInfoRef`]/[`ArgumentsRef`],这几个参数对位置没有要求并且数量任意.
325//!
326//! [数据类型]: #数据类型映射
327//!
328//! ```rust
329//! use pbni::*;
330//!
331//! #[global_function(name="gf_bit_or")]
332//! fn bit_or(session: Session,a: pblong, b: pblong) -> pblong {
333//!     a | b
334//! }
335//!
336//! //等同于
337//!
338//! #[global_function(name="gf_bit_or")]
339//! fn bit_or(session: Session,args: ArgumentsRef,a: pblong) -> pblong {
340//!     a | args.get(1).get_long().unwrap()
341//! }
342//! ```
343//!
344//! **注意** Rust端参数列表须与PB端定义的类型数量以及顺序一致,任何不匹配的情况都会在运行时触发异常. <br>
345//! - Rust端参数如果为非空类型(`Option`),而PB端提供的参数为NULL,那么框架自动返回NULL给PB调用端,兼容PB标准库的做法,也就是说任何参数传递为NULL那么返回值就为NULL,除非Rust端显式用`Option<T>`接收. <br>
346//! - 当参数列表通过`CallInfoRef`/`ArgumentsRef`接收后,将不再匹配参数数量,因为这两个参数已经隐式表示接收了所有的参数.`CallInfoRef`/`ArgumentsRef`一般用于处理引用传递参数以及变长参数列表.
347//!
348//! #### 可选参数列表匹配
349//!
350//! 以下示例为重载可选参数列表的匹配映射
351//!
352//! - PowerBuilder
353//!
354//! ```vbscript
355//! global type gf_test from function_object native "pbrs.dll"
356//! end type
357//!
358//! forward prototypes
359//! global function long gf_test (readonly long a,readonly long b)
360//! global function long gf_test (readonly long a,readonly long b,readonly long c)
361//! global function long gf_test (readonly long a,readonly long b,readonly long c,readonly long d)
362//! end prototypes
363//! ```
364//!
365//! - Rust(pbni-rs)
366//!
367//! ```rust
368//! use pbni::*;
369//!
370//! #[global_function(name="gf_test")]
371//! fn test(a: pblong, b: pblong, c: Option<long>, d: Option<long>) -> pblong {
372//!     a | b
373//! }
374//! ```
375
376#![allow(non_snake_case)]
377#![allow(non_camel_case_types)]
378#![allow(non_upper_case_globals)]
379#![allow(dead_code)]
380
381mod bindings;
382#[cfg(feature = "vm")]
383mod vm;
384mod session;
385mod callinfo;
386mod value;
387mod object;
388mod invoker;
389mod arguments;
390#[cfg(any(feature = "global_function", feature = "nonvisualobject", feature = "visualobject"))]
391mod export;
392#[doc(hidden)]
393mod codegen;
394
395pub use arguments::{Arguments, ArgumentsRef};
396pub use bindings::{
397    pbbyte, pbdouble, pbint, pblong, pblonglong, pbreal, pbuint, pbulong, AsPBStr, FieldId, MethodId, PBStr, PBString, ValueType, PBXRESULT
398};
399pub use callinfo::{CallInfo, CallInfoRef};
400pub use invoker::Invoker;
401pub use object::{ContextObject, Object, SharedObject};
402pub use session::{LocalFrame, OwnedSession, Session};
403pub use value::{Array, OwnedValue, Value};
404
405#[cfg(feature = "vm")]
406pub use vm::VM;
407
408#[cfg(any(feature = "nonvisualobject", feature = "visualobject"))]
409pub use object::UserObject;
410
411#[cfg(feature = "nonvisualobject")]
412pub use object::NonVisualObject;
413
414#[cfg(feature = "visualobject")]
415pub use object::VisualObject;
416
417#[cfg(feature = "datetime")]
418#[doc(no_inline)]
419pub use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
420
421#[cfg(feature = "decimal")]
422#[doc(no_inline)]
423pub use rust_decimal::prelude::*;
424
425#[cfg(any(feature = "global_function", feature = "nonvisualobject", feature = "visualobject"))]
426pub use pbni_codegen::*;
427
428#[doc(hidden)]
429pub mod __private {
430    pub use crate::codegen::__private as codegen;
431}
432
433pub type Result<T> = ::std::result::Result<T, PBXRESULT>;