跨语言互操作
Foreign Function Interfaces (FFI) 是一种机制,通过该机制,一种编程语言写的程序可以调用另外一种编程语言编写的函数。
与 C 语言互操作
为了兼容已有的生态,仓颉支持调用 C 语言的函数,也支持 C 语言调用仓颉的函数。
仓颉调用 C 的函数
在仓颉中要调用 C 的函数,需要在仓颉语言中用 @C
和 foreign
关键字声明这个函数,但 @C
在修饰 foreign
声明的时候,可以省略。
举个例子,假设我们要调用 C 的 rand
和 printf
函数,它的函数签名是这样的:
// stdlib.h
int rand();
// stdio.h
int printf (const char *fmt, ...);
那么在仓颉中调用这两个函数的方式如下:
// declare the function by `foreign` keyword, and omit `@C`
foreign func rand(): Int32
foreign func printf(fmt: CString, ...): Int32
main() {
// call this function by `unsafe` block
let r = unsafe { rand() }
println("random number ${r}")
unsafe {
var fmt = LibC.mallocCString("Hello, No.%d\n")
printf(fmt, 1)
LibC.free(fmt)
}
}
需要注意的是:
foreign
修饰函数声明,代表该函数为外部函数。被foreign
修饰的函数只能有函数声明,不能有函数实现。foreign
声明的函数,参数和返回类型必须符合 C 和仓颉数据类型之间的映射关系(详见下节:类型映射)。- 由于 C 侧函数很可能产生不安全操作,所以调用
foreign
修饰的函数需要被unsafe
块包裹,否则会发生编译错误。 @C
修饰的foreign
关键字只能用来修饰函数声明,不可用来修饰其他声明,否则会发生编译错误。@C
只支持修饰foreign
函数、top-level
作用域中的非泛型函数和struct
类型。foreign
函数不支持命名参数和参数默认值。foreign
函数允许变长参数,使用...
表达,只能用于参数列表的最后。变长参数均需要满足CType
约束,但不必是同一类型。- 仓颉虽然提供了栈扩容能力,但是由于 C 侧函数实际使用栈大小仓颉无法感知,所以 ffi 调用进入 C 函数后,仍然存在栈溢出的风险,需要开发者根据实际情况,修改
cjStackSize
的配置。
一些不合法的 foreign
声明的示例代码如下:
foreign func rand(): Int32 { // compiler error
return 0
}
@C
foreign var a: Int32 = 0 // compiler error
@C
foreign class A{} // compiler error
@C
foreign interface B{} // compiler error
CFunc
仓颉中的 CFunc
指可以被 C 语言代码调用的函数,共有以下三种形式:
@C
修饰的foreign
函数@C
修饰的仓颉函数- 类型为
CFunc
的lambda
表达式- 与普通的 lambda 表达式不同,
CFunc lambda
不能捕获变量。
- 与普通的 lambda 表达式不同,
// Case 1
foreign func free(ptr: CPointer<Int8>): Unit
// Case 2
@C
func callableInC(ptr: CPointer<Int8>) {
print("This function is defined in Cangjie.")
}
// Case 3
let f1: CFunc<(CPointer<Int8>) -> Unit> = { ptr =>
print("This function is defined with CFunc lambda.")
}
以上三种形式声明/定义的函数的类型均为 CFunc<(CPointer<Int8>) -> Unit>
。CFunc
对应 C 语言的函数指针类型。这个类型为泛型类型,其泛型参数表示该 CFunc
入参和返回值类型,使用方式如下:
foreign func atexit(cb: CFunc<() -> Unit>)
与 foreign
函数一样,其他形式的 CFunc
的参数和返回类型必须满足 CType
约束,且不支持命名参数和参数默认值。
CFunc
在仓颉代码中被调用时,需要处在 unsafe
上下文中。
仓颉语言支持将一个 CPointer<T>
类型的变量类型转换为一个具体的 CFunc
,其中 CPointer
的泛型参数 T
可以是满足 CType
约束的任意类型,使用方式如下:
main() {
var ptr = CPointer<Int8>()
var f = CFunc<() -> Unit>(ptr)
unsafe { f() } // core dumped when running, because the pointer is nullptr.
}
注意:将一个指针强制类型转换为 CFunc
并进行函数调用是危险行为,需要用户保证指针指向的是一个切实可用的函数地址,否则将发生运行时错误。
inout 参数
在仓颉中调用 CFunc
时,其实参可以使用 inout
关键字修饰,组成引用传值表达式,此时,该参数按引用传递。引用传值表达式的类型为 CPointer<T>
,其中 T
为 inout
修饰的表达式的类型。
引用传值表达式具有以下约束:
- 仅可用于对
CFunc
的调用处; - 其修饰对象的类型必须满足
CType
约束,但不可以是CString
; - 其修饰对象不可以是用
let
定义的,不可以是字面量、入参、其他表达式的值等临时变量; - 通过仓颉侧引用传值表达式传递到 C 侧的指针,仅保证在函数调用期间有效,即此种场景下 C 侧不应该保存指针以留作后用。
inout
修饰的变量,可以是定义在 top-level
作用域中的变量、局部变量、struct
中的成员变量,但不能直接或间接来源于 class
的实例成员变量。
下面是一个例子:
foreign func foo1(ptr: CPointer<Int32>): Unit
@C
func foo2(ptr: CPointer<Int32>): Unit {
let n = unsafe { ptr.read() }
println("*ptr = ${n}")
}
let foo3: CFunc<(CPointer<Int32>) -> Unit> = { ptr =>
let n = unsafe { ptr.read() }
println("*ptr = ${n}")
}
struct Data {
var n: Int32 = 0
}
class A {
var data = Data()
}
main() {
var n: Int32 = 0
unsafe {
foo1(inout n) // OK
foo2(inout n) // OK
foo3(inout n) // OK
}
var data = Data()
var a = A()
unsafe {
foo1(inout data.n) // OK
foo1(inout a.data.n) // Error, n is derived indirectly from instance member variables of class A
}
}
**注意:**使用宏扩展特性时,在宏的定义中,暂时不能使用 inout
参数特性。
unsafe
在引入与 C 语言的互操作过程中,同时也引入了 C 的许多不安全因素,因此在仓颉中使用 unsafe
关键字,用于对跨 C 调用的不安全行为进行标识。
关于 unsafe 关键字,有以下几点说明:
unsafe
可以修饰函数、表达式,也可以修饰一段作用域。- 被
@C
修饰的函数,被调用处需要在unsafe
上下文中。 - 在调用
CFunc
时,使用处需要在unsafe
上下文中。 foreign
函数在仓颉中进行调用,被调用处需要在unsafe
上下文中。- 当被调用函数被
unsafe
修饰时,被调用处需要在unsafe
上下文中。
使用方式如下:
foreign func rand(): Int32
@C
func foo(): Unit {
println("foo")
}
var foo1: CFunc<() -> Unit> = { =>
println("foo1")
}
main(): Int64 {
unsafe {
rand() // Call foreign func.
foo() // Call @C func.
foo1() // Call CFunc var.
}
0
}
注意: 普通 lambda
无法传递 unsafe
属性,当 unsafe
的 lambda
逃逸后,可以不在 unsafe
上下文中直接调用而未产生任何编译错误。当需要在 lambda
中调用 unsafe
函数时,建议在 unsafe
块中进行调用,参考如下用例:
unsafe func A(){}
unsafe func B(){
var f = { =>
unsafe { A() } // Avoid calling A() directly without unsafe in a normal lambda.
}
return f
}
main() {
var f = unsafe{ B() }
f()
println("Hello World")
}
调用约定
函数调用约定描述调用者和被调用者双方如何进行函数调用(如参数如何传递、栈由谁清理等),函数调用和被调用双方必须使用相同的调用约定才能正常运行。仓颉编程语言通过 @CallingConv
来表示各种调用约定,支持的调用约定如下:
- CDECL,
CDECL
表示 clang 的 C 编译器在不同平台上默认使用的调用约定。 - STDCALL,
STDCALL
表示 Win32 API 使用的调用约定。
通过 C 语言互操作机制调用的 C 函数,未指定调用约定时将采用默认的 CDECL
调用约定。如下调用 C 标准库函数 rand
示例:
@CallingConv[CDECL] // Can be omitted in default.
foreign func rand(): Int32
main() {
println(rand())
}
@CallingConv
只能用于修饰 foreign
块、单个 foreign
函数和 top-level
作用域中的 CFunc
函数。当 @CallingConv
修饰 foreign
块时,会为 foreign
块中的每个函数分别加上相同的 @CallingConv
修饰。
类型映射
基础类型
仓颉与 C 语言支持基本数据类型的映射,总体原则是:
- 仓颉的类型不包含指向托管内存的引用类型;
- 仓颉的类型和 C 的类型具有同样的内存布局。
比如说,一些基本的类型映射关系如下:
Cangjie Type | C Type | Size (byte) |
---|---|---|
Unit | void | 0 |
Bool | bool | 1 |
UInt8 | char | 1 |
Int8 | int8_t | 1 |
UInt8 | uint8_t | 1 |
Int16 | int16_t | 2 |
UInt16 | uint16_t | 2 |
Int32 | int32_t | 4 |
UInt32 | uint32_t | 4 |
Int64 | int64_t | 8 |
UInt64 | uint64_t | 8 |
IntNative | ssize_t | platform dependent |
UIntNative | size_t | platform dependent |
Float32 | float | 4 |
Float64 | double | 8 |
注:int
类型、long
类型等由于其在不同平台上的不确定性,需要程序员自行指定对应仓颉编程语言类型。在 C 互操作场景中,与 C 语言类似,Unit
类型仅可作为 CFunc
中的返回类型和 CPointer
的泛型参数。
仓颉也支持与 C 语言的结构体和指针类型的映射。
结构体
对于结构体类型,仓颉用 @C
修饰的 struct
来对应。比如说 C 语言里面有这样的一个结构体:
typedef struct {
long long x;
long long y;
long long z;
} Point3D;
那么它对应的仓颉类型可以这么定义:
@C
struct Point3D {
var x: Int64 = 0
var y: Int64 = 0
var z: Int64 = 0
}
如果 C 语言里有这样的一个函数:
Point3D addPoint(Point3D p1, Point3D p2);
那么对应的,在仓颉里面可以这样声明这个函数:
foreign func addPoint(p1: Point3D, p2: Point3D): Point3D
用 @C
修饰的 struct
必须满足以下限制:
- 成员变量的类型必须满足
CType
约束 - 不能实现或者扩展
interfaces
- 不能作为
enum
的关联值类型 - 不允许被闭包捕获
- 不能具有泛型参数
用 @C
修饰的 struct
自动满足 CType
约束。
指针
对于指针类型,仓颉提供 CPointer<T>
类型来对应 C 侧的指针类型,其泛型参数 T
需要满足 CType
约束。
比如对于 malloc 函数,在 C 里面的签名为:
void* malloc(size_t size);
那么在仓颉中,它可以声明为:
foreign func malloc(size: UIntNative): CPointer<Unit>
CPointer
可以进行读写、偏移计算、判空以及转为指针的整型形式等,详细 API 可以参考 core 包标准库文档。其中读写和偏移计算为不安全行为,当不合法的指针调用这些函数时,可能发生未定义行为,这些 unsafe 函数需要在 unsafe 块中调用。
CPointer
的使用示例如下:
foreign func malloc(size: UIntNative): CPointer<Unit>
foreign func free(ptr: CPointer<Unit>): Unit
@C
struct Point3D {
var x: Int64
var y: Int64
var z: Int64
init(x: Int64, y: Int64, z: Int64) {
this.x = x
this.y = y
this.z = z
}
}
main() {
let p1 = CPointer<Point3D>() // create a CPointer with null value
if (p1.isNull()) { // check if the pointer is null
print("p1 is a null pointer")
}
let sizeofPoint3D: UIntNative = 24
var p2 = unsafe { malloc(sizeofPoint3D) } // malloc a Point3D in heap
var p3 = unsafe { CPointer<Point3D>(p2) } // pointer type cast
unsafe { p3.write(Point3D(1, 2, 3)) } // write data through pointer
let p4: Point3D = unsafe { p3.read() } // read data through pointer
let p5: CPointer<Point3D> = unsafe { p3 + 1 } // offset of pointer
unsafe { free(p2) }
}
仓颉语言支持 CPointer
之间的强制类型转换,转换前后的 CPointer
的泛型参数 T
均需要满足 CType
的约束,使用方式如下:
main() {
var pInt8 = CPointer<Int8>()
var pUInt8 = CPointer<UInt8>(pInt8) // CPointer<Int8> convert to CPointer<UInt8>
0
}
仓颉语言支持将一个 CFunc
类型的变量类型转换为一个具体的 CPointer
,其中 CPointer
的泛型参数 T
可以是满足 CType
约束的任意类型,使用方式如下:
foreign func rand(): Int32
main() {
var ptr = CPointer<Int8>(rand)
0
}
注意:将一个 CFunc
强制类型转换为指针通常是安全的,但是不应该对转换后的指针执行任何的 read
,write
操作,可能会导致运行时错误。
数组
仓颉使用 VArray
类型与 C 的数组类型映射,VArray
可以用户作为函数参数和 @C struct
成员。当 VArray<T, $N>
中的元素类型 T
满足 CType
约束时, VArray<T, $N>
类型也满足 CType
约束。
作为函数参数类型:
当 VArray
作为 CFunc
的参数时, CFunc
的函数签名仅可以是 CPointer<T>
类型或 VArray<T, $N>
类型。当函数签名中的参数类型为 VArray<T, $N>
时,传递的参数仍以 CPointer<T>
形式传递。
VArray
作为参数的使用示例如下:
foreign func cfoo1(a: CPointer<Int32>):Unit
foreign func cfoo2(a: VArray<Int32, $3): Unit
对应的 C 侧函数定义可以是:
void cfoo1(int *a) { ... }
void cfoo2(int a[3]) { ... }
调用 CFunc
时,需要通过 inout
修饰 VArray
类型变量:
var a: VArray<Int32, $3> = [1, 2, 3]
unsafe {
cfoo1(inout a)
cfoo2(inout a)
}
VArray
不允许作为 CFunc
的返回值类型。
作为 @C struct 成员:
当 VArray
作为 @C struct
成员时,它的内存布局与 C 侧的结构体排布一致,需要保证仓颉侧声明长度与类型也与 C 完全一致:
struct S {
int a[2];
int b[0];
}
在仓颉中,可以声明为如下结构体与 C 代码对应:
@C
struct S {
var a = VArray<Int32, $2>(item: 0)
var b = VArray<Int32, $0>(item: 0)
}
注意:C 语言中允许结构体的最后一个字段为未指明长度的数组类型,该数组被称为柔性数组(flexible array),仓颉不支持包含柔性数组的结构体的映射。
字符串
特别地,对于 C 语言中的字符串类型,仓颉中设计了一个 CString
类型来对应。为简化为 C 语言字符串的操作,CString
提供了以下成员函数:
init(p: CPointer<UInt8>)
通过 CPointer 构造一个 CStringfunc getChars()
获取字符串的地址,类型为CPointer<UInt8>
func size(): Int64
计算该字符串的长度func isEmpty(): Bool
判断该字符串的长度是否为 0,如果字符串的指针为空返回 truefunc isNotEmpty(): Bool
判断该字符串的长度是否不为 0,如果字符串的指针为空返回 falsefunc isNull(): Bool
判断该字符串的指针是否为 nullfunc startsWith(str: CString): Bool
判断该字符串是否以 str 开头func endsWith(str: CString): Bool
判断该字符串是否以 str 结尾func equals(rhs: CString): Bool
判断该字符串是否与 rhs 相等func equalsLower(rhs: CString): Bool
判断该字符串是否与 rhs 相等,忽略大小写func subCString(start: UInt64): CString
从 start 开始截取子串,返回的子串存储在新分配的空间中func subCString(start: UInt64, len: UInt64): CString
从 start 开始截取长度为 len 的子串,返回的子串存储在新分配的空间中func compare(str: CString): Int32
该字符串与 str 比较,返回结果与 C 语言的strcmp(this, str)
一样func toString(): String
用该字符串构造一个新的 String 对象func asResource(): CStringResource
获取 CString 的 Resource 类型
另外,将 String
类型转换为 CString
类型,可以通过调用 LibC 中的 mallocCString
接口,使用完成后需要对 CString
进行释放。
CString
的使用示例如下:
foreign func strlen(s: CString): UIntNative
main() {
var s1 = unsafe { LibC.mallocCString("hello") }
var s2 = unsafe { LibC.mallocCString("world") }
let t1: Int64 = s1.size()
let t2: Bool = s2.isEmpty()
let t3: Bool = s1.equals(s2)
let t4: Bool = s1.startsWith(s2)
let t5: Int32 = s1.compare(s2)
let length = unsafe { strlen(s1) }
unsafe {
LibC.free(s1)
LibC.free(s2)
}
}
sizeOf/alignOf
在仓颉标准库中,提供了 sizeOf
和 alignOf
两个函数,用于获取上述 C 互操作类型的内存占用和内存对齐数值(单位:字节),函数声明如下:
public func sizeOf<T>(): UIntNative where T <: CType
public func alignOf<T>(): UIntNative where T <: CType
使用示例:
@C
struct Data {
var a: Int64 = 0
var b: Float32 = 0.0
}
main() {
println(sizeOf<Data>())
println(alignOf<Data>())
}
在 64 位机器上运行,将输出:
16
8
CType
除类型映射一节提供的与 C 侧类型进行映射的类型外,仓颉还提供了一个 CType
接口,接口本身不包含任何方法,它可以作为所有 C 互操作支持的类型的父类型,便于在泛型约束中使用。
需要注意的是:
CType
接口是仓颉中的一个接口类型,它本身不满足CType
约束;CType
接口不允许被继承、扩展;CType
接口不会突破子类型的使用限制。
CType
的使用示例如下:
func foo<T>(x: T): Unit where T <: CType {
match (x) {
case i32: Int32 => println(i32)
case ptr: CPointer<Int8> => println(ptr.isNull())
case f: CFunc<() -> Unit> => unsafe { f() }
case _ => println("match failed")
}
}
main() {
var i32: Int32 = 1
var ptr = CPointer<Int8>()
var f: CFunc<() -> Unit> = { => println("Hello") }
var f64 = 1.0
foo(i32)
foo(ptr)
foo(f)
foo(f64)
}
执行结果如下:
1
true
Hello
match failed
C 调用仓颉的函数
仓颉提供 CFunc
类型来对应 C 侧的函数指针类型。C 侧的函数指针可以传递到仓颉,仓颉也可以构造出对应 C 的函数指针的变量传递到 C 侧。
假设一个 C 的库 API 如下:
typedef void (*callback)(int);
void set_callback(callback cb);
对应的,在仓颉里面这个函数可以声明为:
foreign func set_callback(cb: CFunc<(Int32) -> Unit>): Unit
CFunc 类型的变量可以从 C 侧传递过来,也可以在仓颉侧构造出来。在仓颉侧构造 CFunc 类型有两种办法,一个是用 @C
修饰的函数,另外一个是标记为 CFunc 类型的闭包。
@C
修饰的函数,表明它的函数签名是满足 C 的调用规则的,定义还是写在仓颉这边。foreign
修饰的函数定义是在 C 侧的。
注意:foreign
修饰的函数与 @C
修饰的函数,这两种 CFunc
的命名不建议使用 CJ_
(不区分大小写)作为前缀,否则可能与标准库及运行时等编译器内部符号出现冲突,导致未定义行为。
示例如下:
@C
func myCallback(s: Int32): Unit {
println("handle ${s} in callback")
}
main() {
// the argument is a function qualified by `@C`
unsafe { set_callback(myCallback) }
// the argument is a lambda with `CFunc` type
let f: CFunc<(Int32) -> Unit> = { i => "handle ${i} in callback" }
unsafe { set_callback(f) }
}
假设 C 函数编译出来的库是 "libmyfunc.so",那么需要使用 cjc -L. -lmyfunc test.cj -o test.out
编译命令,使仓颉编译器去链接这个库。最终就能生成想要的可执行程序。
另外,在编译 C 代码时,请打开 -fstack-protector-all/-fstack-protector-strong
栈保护选项,仓颉侧代码默认拥有溢出检查与栈保护功能。在引入 C 代码后,需要同步保证 unsafe 块中的溢出的安全性。
编译选项
使用 C 互操作通常需要手动链接 C 的库,仓颉编译器提供了相应的编译选项。
-
--library-path <value>
,-L <value>
,-L<value>
:指定要链接的库文件所在的目录。--library-path <value>
指定的路径会被加入链接器的库文件搜索路径。另外环境变量LIBRARY_PATH
中指定的路径也会被加入链接器的库文件搜索路径中,通过--library-path
指定的路径会比LIBRARY_PATH
中的路径拥有更高的优先级。 -
--library <value>
,-l <value>
,-l<value>
:指定要链接的库文件。给定的库文件会被直接传给链接器,库文件名的格式应为
lib[arg].[extension]
。
关于仓颉编译器支持的所有编译选项,详见仓颉编译器手册一章。
示例
假设我们有一个 C 库 libpaint.so
,其头文件如下:
#include <stdint.h>
typedef struct {
int64_t x;
int64_t y;
} Point;
typedef struct {
int64_t x;
int64_t y;
int64_t r;
} Circle;
int32_t DrawPoint(const Point* point);
int32_t DrawCircle(const Circle* circle);
在仓颉代码中使用该 C 库的示例代码如下:
// main.cj
foreign {
func DrawPoint(point: CPointer<Point>): Int32
func DrawCircle(circle: CPointer<Circle>): Int32
func malloc(size: UIntNative): CPointer<Int8>
func free(ptr: CPointer<Int8>): Unit
}
@C
struct Point {
var x: Int64 = 0
var y: Int64 = 0
}
@C
struct Circle {
var x: Int64 = 0
var y: Int64 = 0
var r: Int64 = 0
}
main() {
let SIZE_OF_POINT: UIntNative = 16
let SIZE_OF_CIRCLE: UIntNative = 24
let ptr1 = unsafe { malloc(SIZE_OF_POINT) }
let ptr2 = unsafe { malloc(SIZE_OF_CIRCLE) }
let pPoint = CPointer<Point>(ptr1)
let pCircle = CPointer<Circle>(ptr2)
var point = Point()
point.x = 10
point.y = 20
unsafe { pPoint.write(point) }
var circle = Circle()
circle.r = 1
unsafe { pCircle.write(circle) }
unsafe {
DrawPoint(pPoint)
DrawCircle(pCircle)
free(ptr1)
free(ptr2)
}
}
编译仓颉代码的命令如下:
cjc -L . -l paint ./main.cj
其中编译命令中 -L .
表示链接库时从当前目录查找(假设 libpaint.so
存在于当前目录),-l paint
表示链接的库的名字,编译成功后默认生成二进制文件 main
,执行二进制文件的命令如下:
LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./main
与 Python 语言互操作
为了兼容强大的计算和 AI 生态,仓颉支持与 Python 语言的互操作调用。Python 的互操作通过 std 模块中的 ffi.python 库为用户提供能力。
目前 Python 互操作仅支持在 Linux 平台使用,并且仅支持仓颉编译器的 llvmgc 后端。
Python 的全局资源及使用
提供内建函数类以及全局资源
代码原型:
public class PythonBuiltins {
...
}
public let Python = PythonBuiltins()
Python 库提供的接口不能保证并发安全,当对 Python 进行异步调用时(系统线程 ID
不一致)会抛出 PythonException
异常。
在 Python 初始化时,GIL 全局解释器锁基于当前所在 OS 线程被锁定,如果执行的代码所在的 Cangjie 线程(包括
main
所在 Cangjie 线程)在 OS 线程上发生调度(OS 线程ID
发生变化),Python 内部再次尝试检查 GIL 时会对线程状态进行校验,发现 GIL 状态中保存的 OS 线程ID
与当前执行的 OS 线程ID
不一致,此时会触发内部错误,导致程序崩溃。
**注意:**由于 Python 互操作使用到大量 Python 库的 native 代码,这部分代码在仓颉侧无法对其进行相应的栈保护。仓颉栈保护默认大小为 64KB,在对 Python C API 进行调用过程中,容易造成 native 代码超出默认栈大小,发生溢出,会触发不可预期的结果。建议用户在执行 Python 互操作相关代码前,配置仓颉默认栈大小至少为 1MB:export cjStackSize=1MB
。
使用示例:
from std import ffi.python.*
main(): Int64 {
Python.load()
Python.unload()
return 0
}
提供 Python 库日志类 PythonLogger
代码原型:
public class PythonLogger <: Logger {
mut prop level: LogLevel {...}
public func setOutput(output: io.File): Unit {} // do nothing
public func trace(msg: String): Unit {...}
public func debug(msg: String): Unit {...}
public func info(msg: String): Unit {...}
public func warn(msg: String): Unit {...}
public func error(msg: String): Unit {...}
public func log(level: LogLevel, msg: String): Unit {...}
}
public let PYLOG = PythonLogger()
Logger 类的几点声明:
PythonLogger
实现Logger
接口仅做打印输出以及打印等级控制,不做日志转储到 log 文件;setOutput
为空实现,不支持 log 转储文件;info/warn/error
等接口输出打印以对应前缀开头,其他不做区分;PythonLogger
默认打印等级为LogLevel.WARN
;PYLOG.error(msg)
和log(LogLevel.ERROR, msg)
接口会抛出PythonException
异常。
使用示例:
from std import ffi.python.*
from std import log.*
main(): Int64 {
PYLOG.level = LogLevel.WARN // Only logs of the warn level and above are printed.
PYLOG.info("log info")
PYLOG.warn("log warn")
try {
PYLOG.error("log error")
} catch(e: PythonException) {}
PYLOG.log(LogLevel.INFO, "loglevel info")
PYLOG.log(LogLevel.WARN, "loglevel warn")
try {
PYLOG.log(LogLevel.ERROR, "loglevel error")
} catch(e: PythonException) {}
return 0
}
执行结果:
WARN: log warn
ERROR: log error
WARN: loglevel warn
ERROR: loglevel error
提供 Python 库异常类 PythonException
代码原型:
public class PythonException <: Exception {
public init() {...}
public init(message: String) {...}
}
PythonException
有以下说明:
PythonException
与被继承的Exception
除了异常前缀存在差异,其他使用无差异;- 当 Python 内部出现异常时,外部可以通过
try-catch
进行捕获,如果不进行捕获会打印异常堆栈并退出程序,返回值为 1。
使用示例:
from std import ffi.python.*
from std import log.*
main(): Int64 {
try {
Python.load("/usr/lib/", loglevel: LogLevel.INFO)
} catch(e: PythonException) {
print("${e}") // PythonException: "/usr/lib/" does not exist or the file path is invalid.
}
return 0
}
提供 Python 库的版本信息类 Version
代码原型:
public struct Version <: ToString {
public init(major: Int64, minor: Int64, micro: Int64)
public func getMajor(): Int64
public func getMinor(): Int64
public func getMicro(): Int64
public func getVersion(): (Int64, Int64, Int64)
public func toString(): String
}
关于 Version
类的几点声明:
Version
版本信息包含三个部分:major version
,minor version
,micro version
。Version
版本仅通过构造函数进行初始化,一旦定义,后续无法修改。- 提供
toString
接口,可以直接进行打印。 - 提供
getVersion
接口,可以获取版本的 tuple 形式。
使用示例:
from std import ffi.python.*
main(): Int64 {
Python.load()
var version = Python.getVersion()
print("${version}")
var tuple_version = version.getVersion()
Python.unload()
return 0
}
PythonBuiltins
内建函数类
Python 库的导入和加载
代码原型:
public class PythonBuiltins {
public func load(loglevel!: LogLevel = LogLevel.WARN): Unit
public func load(path: String, loglevel!: LogLevel = LogLevel.WARN): Unit
public func isLoad(): Bool
public func unload(): Unit
}
public let Python = PythonBuiltins()
关于加载与卸载有以下几点声明:
load
函数使用重载的方式实现,同时支持无参加载和指定动态库路径加载,提供可选参数配置PythonLogger
的打印等级,如果不配置,会将PYLOG
重置为warn
打印等级;load()
函数进行了 Python 相关的准备工作,在进行 Python 互操作前必须调用,其中动态库查询方式请见:动态库的加载策略;load(path: String)
函数需要用户配置动态库路径path
,path
指定到动态库文件(如:/usr/lib/libpython3.9.so
),不可以配置为目录或者非动态库文件;load
函数失败时会抛出PythonException
异常,如果程序仍然需要继续执行,请注意try-catch
;unload
函数在进行完 Python 互操作时调用,否则会造成相关资源泄露;- 加载和卸载操作仅需要调用一次,并且一一对应,多次调用仅第一次生效;
isload()
函数用于判断 Python 库是否被加载。
使用示例:
load
与 unload
:
from std import ffi.python.*
main(): Int64 {
Python.load()
Python.unload()
Python.load("/usr/lib/libpython3.9.so")
Python.unload()
return 0
}
isLoad
函数:
from std import ffi.python.*
main(): Int64 {
print("${Python.isLoad()}\n") // false
Python.load()
print("${Python.isLoad()}\n") // true
Python.unload()
return 0
}
动态库的加载策略
Python 库需要依赖 Python 的官方动态链接库: libpython3.x.so
,推荐版本:3.9.2,支持读取 Python3.0 以上版本。
从 Python 源码编译获取动态库:
# 在Python源码路径下:
$ ./configure --enable-shared --with-system-ffi --prefix=/usr
$ make
$ make install
Python 的动态库按照以下方式进行自动查找:
1、使用指定的环境变量:
$ export PYTHON_DYNLIB=".../libpython3.9.so"
2、如果环境变量未指定,从可执行文件的依赖中查找:
- 需要保证可执行文件
python3
可正常执行(所在路径已添加值 PATH 环境变量中),通过对 python3 可执行文件的动态库依赖进行查询。 - 非动态库依赖的 Python 可执行文件无法使用(源码编译未使用
--enable-shared
编译的 Python 可执行文件,不会对动态库依赖)。
$ ldd $(which python3)
...
libpython3.9d.so.1.0 => /usr/local/lib/libpython3.9d.so.1.0 (0x00007f499102f000)
...
3、如果无法找到可执行文件依赖,尝试从系统默认动态库查询路径中查找:
["/lib", "/usr/lib", "/usr/local/lib"]
- 所在路径下查询的动态库名称必须满足
libpythonX.Y.so
的命名方式,其中X
Y
分别为主版本号以及次版本号,并且支持的后缀有:d.so
,m.so
,dm.so
,.so
,支持的版本高于 python3.0,低于或等于 python3.10。如:
libpython3.9.so
libpython3.9d.so
libpython3.9m.so
libpython3.9dm.so
使用示例:
from std import ffi.python.*
from std import log.*
main(): Int64 {
Python.load(loglevel: LogLevel.INFO)
print("${Python.getVersion()}\n")
Python.unload()
return 0
}
可以开启 Python 的 INFO
级打印,查看 Python 库路径的搜索过程:
# Specifying .so by Using Environment Variables
$ export PYTHON_DYNLIB=/root/code/python_source_code/Python-3.9.2/libpython3.9d.so
$ cjc ./main.cj -o ./main && ./main
INFO: Try to get libpython path.
INFO: Found PYTHON_DYNLIB value: /root/code/python_source_code/Python-3.9.2/libpython3.9d.so
...
# Find dynamic libraries by executable file dependency.
INFO: Try to get libpython path.
INFO: Can't get path from environment PYTHON_DYNLIB, try to find it from executable file path.
INFO: Exec cmd: "ldd $(which python3)":
INFO: ...
libpython3.9d.so.1.0 => /usr/local/lib/libpython3.9d.so.1.0 (0x00007fbbb5014000)
...
INFO: Found lib: /usr/local/lib/libpython3.9d.so.1.0.
INFO: Found exec dependency: /usr/local/lib/libpython3.9d.so.1.0
...
# Search for the dynamic library in the system path.
$ unset PYTHON_DYNLIB
$ cjc ./main.cj -o ./main && ./main
INFO: Can't get path from environment PYTHON_DYNLIB, try to find it from executable file path.
INFO: Can't get path from executable file path, try to find it from system lib path.
INFO: Find in /lib.
INFO: Found lib: /lib/libpython3.9.so.
...
# Failed to find the dynamic library.
$ cjc ./main.cj -o ./main && ./main
INFO: Can't get path from environment PYTHON_DYNLIB, try to find it from executable file path.
INFO: Can't get path from executable file path, try to find it from system lib path.
INFO: Find in /lib.
INFO: Can't find lib in /lib.
INFO: Find in /usr/lib.
INFO: Can't find lib in /usr/lib.
INFO: Find in /usr/local/lib.
INFO: Can't find lib in /usr/local/lib.
An exception has occurred:
PythonException: Can't get path from system lib path, load exit.
at std/ffi/python.std/ffi/python::(PythonException::)init(std/core::String)(stdlib/std/ffi/python/Python.cj:82)
at std/ffi/python.std/ffi/python::(PythonBuiltins::)load(std/log::LogLevel)(stdlib/std/ffi/python/Python.cj:127)
at default.default::main()(/root/code/debug/src/main.cj:5)
getVersion()
函数
函数原型:
public func getVersion(): Version
接口描述:
getVersion()
函数用于获取当前使用的 Python 版本;
入参返回值:
getVersion()
函数无参数,返回Version
类对象;
异常情况:
getVersion()
函数需要保证load
函数已被调用,否则返回的版本信息号为0.0.0
。
使用示例:
from std import ffi.python.*
main(): Int64 {
Python.load()
var version = Python.getVersion()
print("${version}")
var tuple_version = version.getVersion()
Python.unload()
return 0
}
Import()
函数
函数原型:
public func Import(module: String): PyModule
入参返回值:
Import
函数接受一个String
类型入参,即模块名,并且返回一个PyModule
类型的对象;
异常情况:
Import
函数需要保证 load 函数已被调用,否则返回的PyModule
类型对象不可用(isAvaliable()
为false
);- 如果找不到对应的模块,仅会报错,且返回的
PyModule
类型对象不可用(isAvaliable()
为false
)。
使用示例:
from std import ffi.python.*
main(): Int64 {
Python.load()
var sys = Python.Import("sys")
if (sys.isAvailable()) {
print("Import sys success\n")
}
// Import the test.py file in the current folder.
var test = Python.Import("test")
if (test.isAvailable()) {
print("Import test success\n")
}
var xxxx = Python.Import("xxxx")
if (!xxxx.isAvailable()) {
print("Import test failed\n")
}
Python.unload()
return 0
}
执行结果:
Import sys success
Import test success
Import test failed
Eval()
函数
函数原型:
public func Eval(cmd: String, module!: String = "__main__"): PyObj
接口描述:
Eval()
函数用于创建一个 Python 数据类型;
入参返回值:
Eval()
接受一个String
类型的命令cmd
,并返回该指令的结果的PyObj
形式;Eval()
接受一个String
类型的指定域,默认域为"__main__"
;
异常情况:
Eval()
接口需要保证load
函数已被调用,否则返回的PyObj
类型对象不可用(isAvaliable()
为false
);Eval()
如果接收的命令执行失败,Python 侧会进行报错,并且返回的PyObj
类型对象不可用(isAvaliable()
为false
)。
使用示例:
from std import ffi.python.*
main(): Int64 {
Python.load()
var a = Python.Eval("123")
if (a.isAvailable()) {
Python["print"]([a])
}
var b = Python.Eval("x = 123") // The expression in `Eval` needs have a return value.
if (!b.isAvailable()) {
print("b is unavailable.\n")
}
Python.unload()
return 0
}
执行结果:
123
b is unavailable.
index []
运算符重载
接口描述:
[]
函数提供了其他 Python 的内置函数调用能力;
入参返回值:
[]
函数入参接受String
类型的内建函数名,返回类型为PyObj
;
异常处理:
[]
函数需要保证load
函数已被调用,否则返回的PyObj
类型对象不可用(isAvaliable()
为false
);- 如果指定的函数名未找到,则会报错,且返回的
PyObj
类型对象不可用(isAvaliable()
为false
)。
使用示例:
from std import ffi.python.*
main(): Int64 {
Python.load()
if (Python["type"].isAvailable()) {
print("find type\n")
}
if (!Python["type1"].isAvailable()) {
print("cant find type1\n")
}
Python.unload()
return 0
}
执行结果:
find type
WARN: Dict key "type1" not found!
cant find type1
类型映射
由于 Python 与仓颉互操作基于 C API 开发,Python 与 C 的数据类型映射统一通过 PyObject
结构体指针完成,并且具有针对不同数据类型的一系列接口。对比 C 语言,仓颉具有面向对象的编程优势,因此将 PyObject
结构体指针统一封装为父类,并且被不同的数据类型进行继承。
类型映射表
仓颉类型到 Python 类型映射
Cangjie Type | Python Type |
---|---|
Bool | PyBool |
UInt8/Int8/Int16/UInt16/Int32/UInt32/Int64/UInt64 | PyLong |
Float32/Float64 | PyFloat |
Char/String | PyString |
Array< PyObj > | PyTuple |
Array | PyList |
HashMap | PyDict |
HashSet | PySet |
Python 类型到仓颉类型映射
Python Type | Cangjie Type |
---|---|
PyBool | Bool |
PyLong | Int64/UInt64 |
PyFloat | Float64 |
PyString | String |
PyTuple | - |
PyList | Array |
PyDict | HashMap |
PySet | HashSet |
Python FFI 库泛型约束的接口 PyFFIType
public interface PyFFIType { }
- 由于部分类引入了泛型,为了对用户在泛型使用过程中进行约束,引入了抽象接口
PyFFIType
; - 该接口无抽象成员函数,其仅被
PyObj
和CjObj
实现或继承,该接口不允许在包外进行实现,如果用户自定义类并实现改接口,可能发生未定义行为。
PyObj
类
与 Python 库中的结构体 PyObject
对应,对外提供细分数据类型通用的接口,如成员变量访问、函数访问、到仓颉字符串转换等。
类原型:
public open class PyObj <: ToString & PyFFIType {
public func isAvailable(): Bool { ... }
public open operator func [](key: String): PyObj { ... }
public open operator func [](key: String, value!: PyObj): Unit { ... }
public operator func ()(): PyObj { ... }
public operator func ()(kargs: HashMap<String, PyObj>): PyObj { ... }
public operator func ()(args: Array<PyObj>): PyObj { ... }
public operator func ()(args: Array<PyObj>, kargs: HashMap<String, PyObj>): PyObj { ... }
public operator func ()(args: Array<CjObj>): PyObj { ... }
public operator func ()(args: Array<CjObj>, kargs: HashMap<String, PyObj>): PyObj { ... }
public operator func +(b: PyObj): PyObj { ... }
public operator func -(b: PyObj): PyObj { ... }
public operator func *(b: PyObj): PyObj { ... }
public operator func /(b: PyObj): PyObj { ... }
public operator func **(b: PyObj): PyObj { ... }
public operator func %(b: PyObj): PyObj { ... }
public open func toString(): String { ... }
public func hashCode(): Int64 { ... }
public operator func ==(right: PyObj): Bool { ... }
public operator func !=(right: PyObj): Bool { ... }
}
关于 PyObj
类的几点说明
-
PyObj
不对外提供创建的构造函数,该类不能在包外进行继承,如果用户自定义类并实现改接口,可能发生未定义行为; -
public func isAvailable(): Bool { ... }
:isAvailable
接口用于判断该PyObj
是否可用(即封装的 C 指针是否为NULL
)。
-
public open operator func [](key: String): PyObj { ... }
:[](key)
用于访问 Python 类的成员或者模块中的成员等;- 如果
PyObj
本身不可用(isAvaliable()
为false
),将抛出异常; - 如果
PyObj
中不存在对应的key
,此时由 Python 侧打印对应的错误,并返回不可用的PyObj
类对象(isAvaliable()
为false
)。
-
public open operator func [](key: String, value!: PyObj): Unit { ... }
:[](key, value)
设置 Python 类、模块的成员变量值为value
;- 如果
PyObj
本身不可用(isAvaliable()
为false
),将抛出异常; - 如果
PyObj
中不存在对应的key
,此时由 Python 侧打印对应的错误; - 如果
value
值为一个不可用的对象(isAvaliable()
为false
),此时会将对应的key
从模块或类中删除。
-
()
括号运算符重载,可调用对象的函数调用:- 如果
PyObj
本身不可用(isAvaliable()
为false
),将抛出异常; - 如果
PyObj
本身为不可调用对象,将由 Python 侧报错,且返回不可用的PyObj
类对象(isAvaliable()
为false
); ()
接受无参的函数调用;([...])
接受大于等于 1 个参数传递,参数类型支持仓颉类型CjObj
和 Python 数据类型PyObj
,需要注意的是,多个参数传递时,CjObj
和PyObj
不可混用;- 如果参数中包含不可用对象(
isAvaliable()
为false
),此时将会抛出异常,避免发生在 Python 侧出现不可预测的程序崩溃; ()
运算符支持kargs
,即对应 Python 的可变命名参数设计,其通过一个HashMap
进行传递,其key
类型String
配置为变量名,value
类型为 PyObj 配置为参数值。
- 如果
-
二元运算符重载:
-
+
两变量相加:- 基础数据类型:
PyString
与PyBool/PyLong/PyFloat
不支持相加,其他类型均可相互相加; - 高级数据类型:
PyDict/PySet
与所有类型均不支持相加,PyTuple/PyList
仅能与自身相加。
- 基础数据类型:
-
-
两变量相减:- 基础数据类型:
PyString
与PyBool/PyLong/PyFloat/PyString
不支持相减,其他类型均可相互相减; - 高级数据类型:
PyDict/PySet/PyTuple/PyList
与所有类型均不支持相减。
- 基础数据类型:
-
*
两变量相乘:- 基础数据类型:
PyString
与PyFloat/PyString
不支持相乘,其他类型均可相乘; - 高级数据类型:
PyDict/PySet
与所有类型均不支持相乘,PyTuple/PyList
仅能与PyLong/PyBool
相乘。
- 基础数据类型:
-
/
两变量相除:- 基础数据类型:
PyString
与PyBool/PyLong/PyFloat/PyString
不支持相除,其他类型均可相互相除;如果除数为 0(False
在 Python 侧解释为 0,不可作为除数),会在 Python 侧进行错误打印; - 高级数据类型:
PyDict/PySet/PyTuple/PyList
与所有类型均不支持相除。
- 基础数据类型:
-
**
指数运算:- 基础数据类型:
PyString
与PyBool/PyLong/PyFloat/PyString
不支持指数运算,其他类型均可进行指数运算; - 高级数据类型:
PyDict/PySet/PyTuple/PyList
与所有类型均不支持指数运算。
- 基础数据类型:
-
%
取余:- 基础数据类型:
PyString
与PyBool/PyLong/PyFloat/PyString
不支持取余运算,其他类型均可进行取余运算;如果除数为 0(False
在 Python 侧解释为 0,不可作为除数),会在 Python 侧进行错误打印; - 高级数据类型:
PyDict/PySet/PyTuple/PyList
与所有类型均不支持取余运算。
- 基础数据类型:
-
以上所有错误情况均会进行 warn 级别打印,并且返回的
PyObj
不可用(isAvaliable()
为false
)。
-
-
public open func toString(): String { ... }
:toString
函数可以将 Python 数据类型以字符串形式返回,基础数据类型将以 Python 风格返回;- 如果
PyObj
本身不可用(isAvaliable()
为false
),将抛出异常。
-
hashCode
函数为封装的 Pythonhash
算法,其返回一个 Int64 的哈希值; -
==
操作符用于判定两个PyObj
对象是否相同,!=
与之相反,如果接口比较失败,==
返回为false
并捕获 Python 侧报错,如果被比较的两个对象存在不可用,会抛出异常。
使用示例:
test01.py 文件:
a = 10
def function():
print("a is", a)
def function02(b, c = 1):
print("function02 call.")
print("b is", b)
print("c is", c)
同级目录下的仓颉文件 main.cj:
from std import ffi.python.*
from std import collection.*
main(): Int64 {
Python.load()
// Create an unavailable value.
var a = Python.Eval("a = 10") // SyntaxError: invalid syntax
print("${a.isAvailable()}\n") // false
// Uncallable value `b` be invoked
var b = Python.Eval("10")
b() // TypeError: 'int' object is not callable
// Import .py file.
var test = Python.Import("test01")
// `get []` get value of `a`.
var p_a = test["a"]
print("${p_a}\n") // 10
// `set []` set the value of a to 20.
test["a"] = Python.Eval("20")
test["function"]() // a is 20
// Call function02 with a named argument.
test["function02"]([1], HashMap<String, PyObj>([("c", 2.toPyObj())]))
// Set `a` in test01 to an unavailable value, and `a` will be deleted.
test["a"] = a
test["function"]() // NameError: name 'a' is not defined
Python.unload()
0
}
CjObj
接口
接口原型及类型扩展:
public interface CjObj <: PyFFIType {
func toPyObj(): PyObj
}
extend Bool <: CjObj {
public func toPyObj(): PyBool { ... }
}
extend Char <: CjObj {
public func toPyObj(): PyString { ... }
}
extend Int8 <: CjObj {
public func toPyObj(): PyLong { ... }
}
extend UInt8 <: CjObj {
public func toPyObj(): PyLong { ... }
}
extend Int16 <: CjObj {
public func toPyObj(): PyLong { ... }
}
extend UInt16 <: CjObj {
public func toPyObj(): PyLong { ... }
}
extend Int32 <: CjObj {
public func toPyObj(): PyLong { ... }
}
extend UInt32 <: CjObj {
public func toPyObj(): PyLong { ... }
}
extend Int64 <: CjObj {
public func toPyObj(): PyLong { ... }
}
extend UInt64 <: CjObj {
public func toPyObj(): PyLong { ... }
}
extend Float32 <: CjObj {
public func toPyObj(): PyFloat { ... }
}
extend Float64 <: CjObj {
public func toPyObj(): PyFloat { ... }
}
extend String <: CjObj {
public func toPyObj(): PyString { ... }
}
extend Array<T> <: CjObj where T <: PyFFIType {
public func toPyObj(): PyList<T> { ... }
}
extend HashMap<K, V> <: CjObj where K <: Hashable & Equatable<K> & PyFFIType {
public func toPyObj(): PyDict<K, V> { ... }
}
extend HashSet<T> <: CjObj where T <: Hashable, T <: Equatable<T> & PyFFIType {
public func toPyObj(): PySet<T> { ... }
}
关于 CjObj
类的几点说明
CjObj
接口被所有基础数据类型实现并完成toPyObj
扩展,分别支持转换为与之对应的 Python 数据类型。
PyBool
与 Bool
的映射
类原型:
public class PyBool <: PyObj {
public init(bool: Bool) { ... }
public func toCjObj(): Bool { ... }
}
关于 PyBool
类的几点说明
PyBool
类继承自PyObj
类,PyBool
具有所有父类拥有的接口;PyBool
仅允许用户使用仓颉的Bool
类型进行构造;toCjObj
接口将PyBool
转换为仓颉数据类型Bool
。
使用示例:
from std import ffi.python.*
main(): Int64 {
Python.load()
// Creation of `PyBool`.
var a = PyBool(true) // The type of `a` is `PyBool`.
var b = Python.Eval("True") // The type of `b` is `PyObj` and needs to be matched to `PyBool`.
var c = true.toPyObj() // The type of `c` is `PyBool`, which is the same as `a`.
print("${a}\n")
if (a.toCjObj()) {
print("success\n")
}
if (b is PyBool) {
print("b is PyBool\n")
}
Python.unload()
0
}
执行效果:
True
success
b is PyBool
PyLong
与整型的映射
类原型:
public class PyLong <: PyObj {
public init(value: Int64) { ... }
public init(value: UInt64) { ... }
public init(value: Int32) { ... }
public init(value: UInt32) { ... }
public init(value: Int16) { ... }
public init(value: UInt16) { ... }
public init(value: Int8) { ... }
public init(value: UInt8) { ... }
public func toCjObj(): Int64 { ... }
public func toInt64(): Int64 { ... }
public func toUInt64(): UInt64 { ... }
}
关于 PyLong
类的几点说明
-
PyLong
类继承自PyObj
类,PyLong
具有所有父类拥有的接口; -
PyLong
支持来自所有仓颉整数类型的入参构造; -
toCjObj
与toInt64
接口将PyLong
转换为Int64
类型; -
toUInt64
接口将PyLong
转换为UInt64
类型; -
PyLong
类型向仓颉类型转换统一转换为 8 字节类型,不支持转换为更低字节类型; -
溢出问题:
toInt64
原数值(以UInt64
赋值,赋值不报错)超出Int64
范围判定为溢出;toUInt64
原数值(以Int64
赋值,赋值不报错)超出UInt64
范围判定为溢出;
-
PyLong
暂不支持大数处理。
使用示例:
from std import ffi.python.*
main(): Int64 {
Python.load()
// Creation of `PyLong`.
var a = PyLong(10) // The type of `a` is `PyLong`.
var b = Python.Eval("10") // The type of `b` is `PyObj` and needs to be matched to `PyLong`.
var c = 10.toPyObj() // The type of `c` is `PyLong`, which is the same as `a`.
print("${a}\n")
if (a.toCjObj() == 10 && a.toUInt64() == 10) {
print("success\n")
}
if (b is PyLong) {
print("b is PyLong\n")
}
Python.unload()
0
}
执行效果:
10
success
b is PyLong
PyFloat
与浮点的映射
类原型:
public class PyFloat <: PyObj {
public init(value: Float32) { ... }
public init(value: Float64) { ... }
public func toCjObj(): Float64 { ... }
}
关于 PyFloat
类的几点说明
PyFloat
类继承自PyObj
类,PyFloat
具有所有父类拥有的接口;PyBool
支持使用仓颉Float32
/Float64
类型的数据进行构造;toCjObj
接口为了保证精度,将PyFloat
转换为仓颉数据类型Float64
。
使用示例:
from std import ffi.python.*
main(): Int64 {
Python.load()
// Creation of `PyLong`.
var a = PyFloat(3.14) // The type of `a` is `PyFloat`.
var b = Python.Eval("3.14") // The type of `b` is `PyObj` and needs to be matched to `PyFloat`.
var c = 3.14.toPyObj() // The type of `c` is `PyFloat`, which is the same as `a`.
print("${a}\n")
if (a.toCjObj() == 3.14) {
print("success\n")
}
if (b is PyFloat) {
print("b is PyFloat\n")
}
Python.unload()
0
}
执行效果:
3.14
success
b is PyFloat
PyString
与字符、字符串的映射
类原型:
public class PyString <: PyObj {
public init(value: String) { ... }
public init(value: Char) { ... }
public func toCjObj(): String { ... }
public override func toString(): String { ... }
}
关于 PyString
类的几点说明
PyString
类继承自PyObj
类,PyString
具有所有父类拥有的接口;PyString
支持使用仓颉Char
/String
类型的数据进行构造;toCjObj
/toString
接口为将PyString
转换为仓颉数据类型String
。
使用示例:
from std import ffi.python.*
main(): Int64 {
Python.load()
// Creation of `PyString`.
var a = PyString("hello python") // The type of `a` is `PyString`.
var b = Python.Eval("\"hello python\"") // The type of `b` is `PyObj` and needs to be matched to `PyString`.
var c = "hello python".toPyObj() // The type of `c` is `PyString`, which is the same as `a`.
print("${a}\n")
if (a.toCjObj() == "hello python") {
print("success\n")
}
if (b is PyString) {
print("b is PyString\n")
}
Python.unload()
0
}
执行结果:
hello python
success
b is PyString
PyTuple
类型
类原型:
public class PyTuple <: PyObj {
public init(args: Array<PyObj>) { ... }
public operator func [](key: Int64): PyObj { ... }
public func size(): Int64 { ... }
public func slice(begin: Int64, end: Int64): PyTuple { ... }
}
关于 PyTuple
类的几点说明
-
PyTuple
与 Python 中的元组类型一致,即 Python 代码中使用(...)
的变量; -
PyTuple
类继承自PyObj
类,PyTuple
具有所有父类拥有的接口; -
PyTuple
支持使用仓颉Array
来进行构造,Array
的元素类型必须为PyObj
(Python 不同数据类型均可以使用PyObj
传递,即兼容Tuple
中不同元素的不同数据类型),当成员中包含不可用对象时,会抛出异常; -
[]
操作符重载:- 父类
PyObj
中[]
入参类型为String
类型,该类对象调用时能够访问或设置Python
元组类型内部成员变量或者函数; - 子类
PyTuple
支持使用[]
对元素进行访问,如果角标key
超出 [0, size()) 区间,会进行报错,并且返回不可用的PyObj
对象; - 由于 Python 的元组为不可变对象,未进行
set []
操作符重载。
- 父类
-
size
函数用于获取PyTuple
的长度; -
slice
函数用于对源PyTuple
进行剪裁,并返回一个新的PyTuple
, 如果slice
的入参begin
和end
不在 [0, size()) 区间内,仍会正常裁切。
使用示例:
from std import ffi.python.*
main(): Int64 {
Python.load()
// Creation of `PyTuple`.
var a = PyTuple(["Array".toPyObj(), 'a'.toPyObj(), 1.toPyObj(), 1.1.toPyObj()])
var b = match (Python.Eval("('Array', 'a', 1, 1.1)")) {
case val: PyTuple => val
case _ => throw PythonException()
}
// Usage of size
println(a.size()) // 4
// Usage of slice
println(a.slice(1, 2)) // ('a',). This print is same as Python code `a[1: 2]`.
println(a.slice(-1, 20)) // ('Array', 'a', 'set index 3 to String', 1.1)
Python.unload()
return 0
}
执行结果:
4
('a',)
('Array', 'a', 1, 1.1)
PyList
与 Array
的映射
类原型:
public class PyList<T> <: PyObj where T <: PyFFIType {
public init(args: Array<T>) { ... }
public operator func [](key: Int64): PyObj { ... }
public operator func [](key: Int64, value!: T): Unit { ... }
public func toCjObj(): Array<PyObj> { ... }
public func size(): Int64 { ... }
public func insert(index: Int64, value: T): Unit { ... }
public func append(item: T): Unit { ... }
public func slice(begin: Int64, end: Int64): PyList<T> { ... }
}
关于 PyList
类的几点说明
-
PyList
类与 Python 中的列表类型一致,即 Python 代码中使用[...]
的变量; -
PyList
类继承自PyObj
类,PyList
具有所有父类拥有的接口,该类由于对仓颉的 Array 进行映射,因此该类引入了泛型T
,T
类型约束为PyFFIType
接口的子类; -
PyList
类可以通过仓颉的Array
类型进行构造,Array
的成员类型同样约束为PyFFIType
接口的子类; -
[]
操作符重载:- 父类
PyObj
中[]
入参类型为String
类型,该类对象调用时仅能访问或设置Python
内部成员变量或者函数; - 该类中的
[]
入参类型为Int64
,即对应Array
的角标值,其范围为 [0, size()),如果入参不在范围内,将进行报错,并且返回的对象为不可用; []
同样支持get
以及set
,并且set
时,value
类型为T
,如果value
其中包含不可用的 Python 对象时,会抛出异常。
- 父类
-
toCjObj
函数支持将PyList
转换为仓颉的Array<PyObj>
,请注意,此时并不会转换为Array<T>
; -
size
函数返回PyList
的长度; -
insert
函数将在index
位置插入value
,其后元素往后移,index 不在 [0, size()) 可以正常插入,如果value
为不可用对象,将会抛出异常; -
append
函数将item
追加在PyList
最后,如果value
为不可用对象,将会抛出异常; -
slice
函数用于截取 [begin, end) 区间内的数据并且返回一个新的PyList
,begin
和end
不在 [0, size()) 也可以正常截取。
使用示例:
from std import ffi.python.*
main(): Int64 {
Python.load()
// Creation of `PyList`.
var a = PyList<Int64>([1, 2, 3])
var b = match (Python.Eval("[1, 2, 3]")) {
case val: PyList<PyObj> => val
case _ => throw PythonException()
}
var c = [1, 2, 3].toPyObj()
// Usage of `[]`
println(a["__add__"]([b])) // [1, 2, 3, 1, 2, 3]
a[1]
b[1]
a[2] = 13
b[2] = 15.toPyObj()
// Usage of `toCjObj`
var cjArr = a.toCjObj()
for (v in cjArr) {
print("${v} ") // 1 2 13
}
print("\n")
// Usage of `size`
println(a.size()) // 3
// Usage of `insert`
a.insert(1, 4) // [1, 4, 2, 13]
a.insert(-100, 5) // [5, 1, 4, 2, 13]
a.insert(100, 6) // [5, 1, 4, 2, 13, 6]
b.insert(1, 4.toPyObj()) // [1, 4, 2, 15]
// Usage of `append`
a.append(7) // [5, 1, 4, 2, 13, 6, 7]
b.append(5.toPyObj()) // [1, 4, 2, 15, 5]
// Usage of `slice`
a.slice(1, 2) // [1]
a.slice(-100, 100) // [5, 1, 4, 2, 13, 6, 7]
b.slice(-100, 100) // [1, 4, 2, 15, 5]
return 0
}
执行结果:
[1, 2, 3, 1, 2, 3]
1 2 13
3
PyDict
与 HashMap
的映射
类原型:
public class PyDict<K, V> <: PyObj where K <: Hashable & Equatable<K> & PyFFIType {
public init(args: HashMap<K, V>) { ... }
public func getItem(key: K): PyObj { ... }
public func setItem(key: K, value: V): Unit { ... }
public func toCjObj(): HashMap<PyObj, PyObj> { ... }
public func contains(key: K): Bool { ... }
public func copy(): PyDict<K, V> { ... }
public func del(key: K): Unit { ... }
public func size(): Int64 { ... }
public func empty(): Unit { ... }
public func items(): PyList<PyObj> { ... }
public func values(): PyList<PyObj> { ... }
public func keys(): PyList<PyObj> { ... }
}
关于 PyDict
类的几点说明
-
PyDict
与 Python 的字典类型一致,即 Python 代码中使用{ a: b }
的变量; -
PyDict
类继承自PyObj
类,PyDict
具有所有父类拥有的接口,该类由于对仓颉的 HashMap 进行映射,因此该类引入了泛型<K, V>
,其中K
类型约束为PyFFIType
接口的子类,且可被Hash
计算以及重载了==
与!=
运算符; -
PyDict
接受来自仓颉类型HashMap
的数据进行构造:K
仅接受CjObj
或PyObj
类型或其子类;- 相同的 Python 数据其值也相同,例如
Python.Eval("1")
与1.toPyObj()
为==
关系。
-
getItem
函数用于获取PyDict
对应键值的value
,如果键值无法找到,会进行报错并返回不可用的PyObj
,如果配置的值key
或为value
为PyObj
类型且不可用,此时抛出异常;; -
setItem
函数用于配置PyDict
对应键值的value
,如果对应键值无法找到,会进行插入,如果配置的值key
或为value
为PyObj
类型且不可用,此时抛出异常; -
toCjObj
函数用于将PyDict
转换为HashMap<PyObj, PyObj>
类型; -
contains
函数用于判断key
值是否包含在当前字典中,返回类型为 Bool 型,如果接口失败,进行报错,并且返回 false; -
copy
函数用于拷贝当前字典,并返回一个新的PyDict<T>
类型,如果拷贝失败,返回的 PyDict 不可用; -
del
函数用于删除对应key
的值,如果 key 值为 PyObj 类型且不可用,会抛出异常; -
size
函数用于返回当前字典的长度; -
empty
函数用于清空当前字典内容; -
items
函数用于获取一个 Pythonlist
类型的键值对列表,可以被迭代访问; -
values
函数用于获取一个 Pythonlist
类型的值列表,可以被迭代访问; -
keys
函数用于获取一个 Pythonlist
类型的键列表,可以被迭代访问。
使用示例:
from std import ffi.python.*
from std import collection.*
main() {
Python.load()
// Creation of `PyDict`
var a = PyDict(HashMap<Int64, Int64>([(1, 1), (2, 2)])) // The key type is `CjObj`.
var b = PyDict(HashMap<PyObj, Int64>([(Python.Eval("1"), 1), (Python.Eval("2"), 2)])) // The key type is `PyObj`.
var c = match (Python.Eval("{'pydict': 1, 'hashmap': 2, 3: 3, 3.1: 4}")) {
case val: PyDict<PyObj, PyObj> => val // Python side return `PyDict<PyObj, PyObj>`
case _ => throw PythonException()
}
var d = HashMap<Int64, Int64>([(1, 1), (2, 2)]).toPyObj()
// Usage of `getItem`
println(a.getItem(1)) // 1
println(b.getItem(1.toPyObj())) // 1
// Usage of `setItem`
a.setItem(1, 10)
b.setItem(1.toPyObj(), 10)
println(a.getItem(1)) // 10
println(b.getItem(1.toPyObj())) // 10
// Usage of `toCjObj`
var hashA = a.toCjObj()
for ((k, v) in hashA) {
print("${k}: ${v}, ") // 1: 10, 2: 2,
}
print("\n")
var hashB = b.toCjObj()
for ((k, v) in hashB) {
print("${k}: ${v}, ") // 1: 10, 2: 2,
}
print("\n")
// Usage of `contains`
println(a.contains(1)) // true
println(a.contains(3)) // false
println(b.contains(1.toPyObj())) // true
// Usage of `copy`
println(a.copy()) // {1: 10, 2: 2}
// Usage of `del`
a.del(1) // Delete the key-value pair (1: 1).
// Usage of `size`
println(a.size()) // 1
// Usage of `empty`
a.empty() // Clear all elements in dict.
// Usage of `items`
for (i in b.items()) {
print("${i} ") // (1, 10) (2, 2)
}
print("\n")
// Usage of `values`
for (i in b.values()) {
print("${i} ") // 10 2
}
print("\n")
// Usage of `keys`
for (i in b.keys()) {
print("${i} ") // 1, 2
}
print("\n")
Python.unload()
}
PySet
与 HashSet
的映射
类原型:
public class PySet<T> <: PyObj where T <: Hashable, T <: Equatable<T> & PyFFIType {
public init(args: HashSet<T>) { ... }
public func toCjObj(): HashSet<PyObj> { ... }
public func contains(key: T): Bool { ... }
public func add(key: T): Unit { ... }
public func pop(): PyObj { ... }
public func del(key: T): Unit { ... }
public func size(): Int64 { ... }
public func empty(): Unit { ... }
}
关于 PySet
类的几点说明
-
PySet
对应的是 Python 中的集合的数据类型,当元素插入时会使用 Python 内部的 hash 算法对集合元素进行排序(并不一定按照严格升序,一些方法可能因此每次运行结果不一致)。 -
PySet
类继承自PyObj
类,PySet
具有所有父类拥有的接口,该类由于对仓颉的HashSet
进行映射,因此该类引入了泛型T
,T
类型约束为PyFFIType
接口的子类,且可被Hash
计算以及重载了==
与!=
运算符; -
PySet
接受来自仓颉类型HashMap
的数据进行构造:K
仅接受CjObj
或PyObj
类型或其子类;- 相同的 Python 数据其值也相同,例如
Python.Eval("1")
与1.toPyObj()
为==
关系。
-
toCjObj
函数用于将PySet<T>
转为HashSet<PyObj>
需要注意的是此处只能转为元素类型为PyObj
类型; -
contains
函数用于判断key
是否在当前字典中存在,key
类型为T
; -
add
函数可以进行值插入,当PySet
中已存在键值,则插入不生效,如果key
为PyObj
且不可用,则会抛出异常; -
pop
函数将PySet
中的第一个元素取出; -
del
删除对应的键值,如果key
不在PySet
中,则会报错并正常退出,如果key
为PyObj
且不可用,则会抛出异常; -
size
用于返回PySet
的长度; -
empty
用于清空当前PySet
。
注意:调用
toCjObj
完后,所有元素将被pop
出来,此时原PySet
将会为空(size
为 0,原PySet
仍然可用);
使用示例:
from std import ffi.python.*
from std import collection.*
main() {
Python.load()
// Creation of `PySet`
var a = PySet<Int64>(HashSet<Int64>([1, 2, 3]))
var b = match (Python.Eval("{'PySet', 'HashSet', 1, 1.1, True}")) {
case val: PySet<PyObj> => val
case _ => throw PythonException()
}
var c = HashSet<Int64>([1, 2, 3]).toPyObj()
// Usage of `toCjObj`
var cja = a.toCjObj()
println(a.size()) // 0
// Usage of `contains`
println(b.contains("PySet".toPyObj())) // true
// Usage of `add`
a.add(2)
println(a.size()) // 1
a.add(2) // Insert same value, do nothing.
println(a.size()) // 1
a.add(1) // Insert `1`.
// Usage of `pop`
println(a.pop()) // 1. Pop the first element.
println(a.size()) // 1
// Usage of `del`
c.del(2)
println(c.contains(2)) // false
// Usage of `empty`
println(c.size()) // 2
c.empty()
println(c.size()) // 0
Python.unload()
}
PySlice
类型
PySlice
类型与 Python 内建函数 slice()
的返回值用法一致,可以被用来标识一段区间及步长,可以用来作为可被切片的类型下标值来剪裁获取子串。为了方便从仓颉侧构造, PySlice
类可以与仓颉的 Range
区间类型进行互相转换,详细描述见以下。
类原型:
public class PySlice<T> <: PyObj where T <: Countable<T> & Comparable<T> & Equatable<T> & CjObj {
public init(args: Range<T>) { ... }
public func toCjObj(): Range<Int64> { ... }
}
关于 PySlice
的几点说明:
PySlice
可以使用仓颉的Range
类型来进行构造,并且支持Range
的语法糖,其中泛型T
在原有Range
约束的同时,加上约束在来自CjObj
的实现,不支持PyObj
类型;toCjObj
函数支持将PySlice
转为仓颉Range
的接口,应注意此时Range
的泛型类型为Int64
类型的整型;- 如果希望把
PySlice
类型传递给PyString/PyList/PyTuple
或者是其他可被slice
的PyObj
类型,可以通过其成员函数__getitem__
进行传递,详情见示例。
使用示例:
from std import ffi.python.*
main() {
Python.load()
var range = 1..6:2
// Create a PySlice.
var slice1 = PySlice(range)
var slice2 = match (Python["slice"]([0, 6, 2])) {
case val: PySlice<Int64> => val
case _ => throw PythonException()
}
var slice3 = range.toPyObj()
// Use PySlice in PyString.
var str = PyString("1234567")
println(str["__getitem__"]([range])) // 246
println(str["__getitem__"]([slice1])) // 246
// Use PySlice in PyList.
var list = PyList(["a", "b", "c", "d", "e", "f", "g", "h"])
println(list["__getitem__"]([range])) // ['b', 'd', 'f']
println(list["__getitem__"]([slice1])) // ['b', 'd', 'f']
// Use PySlice in PyTuple.
var tup = PyTuple(list.toCjObj())
println(tup["__getitem__"]([range])) // ('b', 'd', 'f')
println(tup["__getitem__"]([slice1])) // ('b', 'd', 'f')
Python.unload()
0
}
执行结果:
246
246
['b', 'd', 'f']
['b', 'd', 'f']
('b', 'd', 'f')
('b', 'd', 'f')
PyObj
的迭代器类型 PyObjIterator
代码原型:
PyObj
的扩展:
extend PyObj <: Iterable<PyObj> {
public func iterator(): Iterator<PyObj> { ... }
}
PyObjIterator
类型:
public class PyObjIterator <: Iterator<PyObj> {
public init(obj: PyObj) { ... }
public func next(): Option<PyObj> { ... }
public func iterator(): Iterator<PyObj> { ... }
}
关于 PyObjIterator
的几点说明:
-
获取
PyObjIterator
可以通过PyObj
的 iterator 方法获取; -
PyObjIterator
允许被外部构造,如果提供的PyObj
不可以被迭代或提供的PyObj
不可用,则会直接抛出异常;- 可以被迭代的对象有:
PyString/PyTuple/PyList/PySet/PyDict
; - 注意,直接对
PyDict
进行迭代时,迭代的为其键key
的值。
- 可以被迭代的对象有:
-
next
函数用于对该迭代器进行迭代; -
iterator
方法用于返回本身。
使用示例:
from std import ffi.python.*
from std import collection.*
main() {
Python.load()
// iter of PyString
var S = PyString("Str")
for (s in S) {
print("${s} ") // S t r
}
print("\n")
// iter of PyTuple
var T = PyTuple(["T".toPyObj(), "u".toPyObj(), "p".toPyObj()])
for (t in T) {
print("${t} ") // T u p
}
print("\n")
// iter of PyList
var L = PyList(["L", "i", "s", "t"])
for (l in L) {
print("${l} ") // L i s t
}
print("\n")
// iter of PyDict
var D = PyDict(HashMap<Int64, String>([(1, "D"), (2, "i"), (3, "c"), (4, "t")]))
for (d in D) {
print("${d} ") // 1 2 3 4, dict print keys.
}
print("\n")
// iter of PySet
var Se = PySet(HashSet<Int64>([1, 2, 3]))
for (s in Se) {
print("${s} ") // 1 2 3
}
print("\n")
0
}
执行结果:
S t r
T u p
L i s t
1 2 3 4
1 2 3
仓颉与 Python 的注册回调
Python 互操作库支持简单的函数注册及 Python 对仓颉函数调用。
Python 回调仓颉代码通过需要通过 C 作为介质进行调用,并且使用到了 Python 的三方库: ctypes
以及 _ctypes
。
类型映射
基础数据对照如下表:
Cangjie Type | CType | Python Type |
---|---|---|
Bool | PyCBool | PyBool |
Char | PyCWchar | PyString |
Int8 | PyCByte | PyLong |
UInt8 | PyCUbyte/PyCChar | PyLong |
Int16 | PyCShort | PyLong |
UInt16 | PyCUshort | PyLong |
Int32 | PyCInt | PyLong |
UInt32 | PyCUint | PyLong |
Int64 | PyCLonglong | PyLong |
UInt64 | PyCUlonglong | PyLong |
Float32 | PyCFloat | PyFloat |
Float64 | PyCDouble | PyFloat |
[unsupport CPointer as param] CPointer<T> | PyCPointer | ctypes.pointer |
[unsupport CString as param] CString | PyCCpointer | ctypes.c_char_p |
[unsupport CString as param] CString | PyCWcpointer | ctypes.c_wchar_p |
Unit | PyCVoid | - |
Cangjie Type
是在仓颉侧修饰的变量类型,无特殊说明则支持传递该类型参数给 Python 代码,并且支持从 Python 传递给仓颉;PyCType
为仓颉侧对应的PyCFunc
接口配置类型,详细见类原型以及示例展示;Python Type
是在仓颉侧的类型映射,无指针类型映射,不支持从仓颉侧调用 Python 带有指针的函数;PyCCpointer
与PyCWcpointer
同样都是映射到CString
,两者区别为PyCCpointer
为 C 中的字符串,PyCWcpointer
仅为字符指针,即使传递多个字符,也只取第一个字符;- 类型不匹配将会导致不可预测的结果。
PyCFunc
类原型
PyCFunc
是基于 Python 互操作库和 Python 三方库 ctype/_ctype
的一个 PyObj 子类型,该类型可以直接传递给 Python 侧使用。 PyCFunc
为用户提供了注册仓颉的 CFunc
函数给 Python 侧,并且支持由 Python 回调 CFunc
函数的能力。
代码原型:
public enum PyCType {
PyCBool |
PyCChar |
PyCWchar |
PyCByte |
PyCUbyte |
PyCShort |
PyCUshort |
PyCInt |
PyCUint |
PyCLonglong |
PyCUlonglong |
PyCFloat |
PyCDouble |
PyCPointer |
PyCCpointer |
PyCWcpointer |
PyCVoid
}
public class PyCFunc <: PyObj {
public init(f: CPointer<Unit>, argsTy!: Array<PyCType> = [], retTy!: PyCType = PyCType.PyCVoid) { ... }
public func setArgTypes(args: Array<PyCType>): PyCFunc { ... }
public func setRetTypes(ret: PyCType): PyCFunc { ... }
}
关于类的几点说明:
-
PyCFunc
继承自PyObj
,可以使用父类的部分接口(如果不支持的接口会相应报错); -
init
允许外部用户构造,必须提供函数指针作为第一个参数(仓颉侧需要将CFunc
类型转换为CPointer<Unit>
类型),后面两个可选参数分别为入参类型的数组、返回值类型;这里特别声明,如果传入的指针并非函数指针会导致函数调用时程序崩溃(库层面无法进行拦截);
-
setArgTypes/setRetTypes
函数用于配置参数和返回值类型,支持的参数见PyCType
枚举; -
父类中的
()
操作符,支持在仓颉侧调用该注册的CFunc
函数; -
该类可以直接传递给 Python 侧使用,也可以在仓颉侧直接调用(如果该类构造时使用非函数指针,这里调用将会崩溃);
-
该类支持类似 Js 的链式调用。
示例
1、准备仓颉的 CFunc
函数:
@C
func cfoo(a: Bool, b: Int32, c: Int64): CPointer<Unit> {
print("cfoo called.\n")
print("${a}, ${b}, ${c}\n")
return CPointer<Unit>()
}
2、构造 PyCFunc 类对象:
from std import ffi.python.*
// Define the @C function.
@C
func cfoo(a: Bool, b: Int32, c: Int64): CPointer<Unit> {
print("cfoo called.\n")
print("${a}, ${b}, ${c}\n")
return CPointer<Unit>()
}
main() {
Python.load()
/*
Construct PyCFunc class.
Set args type: Bool -> PyCBool
Int32 -> PyCInt
Int64 -> PyCLonglong
CPointer<Unit> -> PyCPointer
*/
var f1 = PyCFunc(unsafe {CPointer<Unit>(cfoo)},
argsTy: [PyCBool, PyCInt, PyCLonglong],
retTy: PyCPointer)
// You also can use it by chain-call.
var f2 = PyCFunc(unsafe {CPointer<Unit>(cfoo)})
.setArgTypes([PyCBool, PyCInt, PyCLonglong])
.setRetTypes(PyCPointer)([true, 1, 2])
// Call f1
f1([true, 1, 2])
f1([PyBool(true), PyLong(1), PyLong(2)])
Python.unload()
0
}
编译仓颉文件并执行:
$ cjc ./main.cj -o ./main && ./main
cfoo called.
true, 1, 2
cfoo called.
true, 1, 2
cfoo called.
true, 1, 2
3、将函数注册给 Python 并且由 Python 进行调用:
Python 代码如下:
# File test.py
# `foo` get a function pointer and call it.
def foo(func):
func(True, 10, 40)
对上面仓颉 main
进行修改:
main() {
Python.load()
var f1 = PyCFunc(unsafe {CPointer<Unit>(cfoo)},
argsTy: [PyCBool, PyCInt, PyCLonglong],
retTy: PyCPointer)
// Import test.py
var cfunc01 = Python.Import("test")
// Call `foo` and transfer `f1`
cfunc01["foo"]([f1])
Python.unload()
0
}
4、Python 侧传递指针到仓颉侧:
为 Python 文件增加函数:
# File test.py
# If you want transfer pointer type to Cangjie CFunc, you need import ctypes.
from ctypes import *
# `foo` get a function pointer and call it.
def foo(func):
func(True, 10, 40)
# `fooptr` get a function pointer and call it with pointer type args.
def fooptr(func):
a = c_int(10)
# c_char_p will get whole symbols, but c_wchar_p only get first one symbol 'd'.
func(pointer(a), c_char_p(b'abc'), c_wchar_p('def'))
修改仓颉代码:
from std import ffi.python.*
var x = Python.load()
// Modify the `foo` param type to pointer.
@C
func foo(a: CPointer<Int64>, b: CString, c: CString): Unit {
print("${unsafe {a.read(0)}}, ${b.toString()}, ${c.toString()}\n")
}
main(): Int64 {
var f1 = PyCFunc(unsafe {CPointer<Unit>(foo)},
argsTy: [PyCPointer, PyCCpointer, PyCWcpointer],
retTy: PyCVoid)
// Import test.py
var test = Python.Import("test")
// Call `fooptr` and transfer `f1`
test["fooptr"]([f1])
return 0
}
- 由于仓颉侧调用函数不能将指针类型传递给 Python 库,所以该处仅支持在 Python 侧进行调用。
对其编译并执行:
$ cjc ./main.cj -o ./main && ./main
10, abc, d