仓颉语言 -- 跨语言互操作

使用新版本 (2024-07-19 16:10发布的)

1、仓颉-C 互操作

为了兼容已有的生态,仓颉支持调用 C 语言的函数,也支持 C 语言调用仓颉的函数

1.1 仓颉调用 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)
    }
}

需要注意的是:

  1. foreign 修饰函数声明,代表该函数为外部函数。被 foreign 修饰的函数只能有函数声明,不能有函数实现
  2. foreign 声明的函数,参数和返回类型必须符合 C 和仓颉数据类型之间的映射关系(详见下节:类型映射)。
  3. 由于 C 侧函数很可能产生不安全操作,所以调用 foreign 修饰的函数需要被 unsafe 块包裹,否则会发生编译错误。
  4. @C 修饰的 foreign 关键字只能用来修饰函数声明,不可用来修饰其他声明,否则会发生编译错误。
  5. @C 只支持修饰 foreign 函数top-level 作用域中的非泛型函数和 struct 类型
  6. foreign 函数不支持命名参数参数默认值。foreign 函数允许变长参数,使用 ...表达,只能用于参数列表的最后。变长参数均需要满足 CType 约束,但不必是同一类型。
  7. 仓颉(CJNative 后端)虽然提供了栈扩容能力,但是由于 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

1.2 CFunc

仓颉中的 CFunc 指可以被 C 语言代码调用的函数,共有以下三种形式:

  1. @C 修饰的 foreign 函数
  2. @C 修饰的仓颉函数
  3. 类型为 CFunc 的 lambda 表达式,与普通的 lambda 表达式不同,CFunc 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>): Int32

与 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 并进行函数调用是危险行为,需要用户保证指针指向的是一个切实可用的函数地址,否则将发生运行时错误。

1.3 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
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值