LNS Binding

LNB:把 C 库声明成 LNS 可调用 API。

LNB 是 LNS 的声明库文件:用一份小而明确的接口描述,把 C 函数变成可授权、可审计、可生成绑定代码的 LNS API。

不是手写 glue

C 库作者写 `.lnb`,生成器负责参数检查、类型转换、错误码处理、C glue 和注册入口。

契约即权限边界

LNB 生成 `api { extern ... }` 契约。脚本只能 import 契约里公开的能力,宿主仍按 capability 授权。

自定义数据统一 handle

C 指针、设备对象、连接对象等统一映射成 LNS `handle`,不暴露裸 C 类型。

LNB 解决的问题

给 C 库一个脚本面

原始 C 函数通常命名长、类型复杂、错误返回不统一。LNB 把它们整理成稳定的 LNS API。

减少宿主接入成本

同一份 LNB 能生成契约、glue、init 注册函数,避免每个库都手写重复边界代码。

适合封闭系统开放能力

设备、游戏、网关、自动化工具可以只开放选定 capability,而不是把系统能力整体暴露给用户。

端到端工作流

LNB 不参与运行时执行。它是生成源:从同名 C 头读取符号,再按 LNB 规则生成 LNS 契约和 C 边界代码。

  • 输入:mylib.lnb + mylib.h
  • 输出:mylib.lns 契约
  • 输出:mylib_glue.c
  • 输出:mylib_init.c
mylib.lnb  ──自动匹配──►  mylib.h
        │
        ▼
  lnbind / lnbgen
        │
        ├─ trusted/lib/mylib.lns
        ├─ mylib_glue.c
        └─ mylib_init.c

同名头文件推导

`bindings/mylib.lnb` 默认查找 `bindings/mylib.h`。LNB 文件里不写头文件路径;多头依赖由 `mylib.h` 自己 include。

为什么不写 bind

绑定源文件名就是约定。这样减少配置项,也避免接口文件和头文件路径漂移。

mylib.h

普通 C 头文件,不依赖 LNS 语法。

int         mylib_starts(void);
double      mylib_add(double a, double b);
const char* mylib_name(void);
int         mylib_set_led(int pin, int on);

typedef struct Device Device;
Device* device_open(const char* id);
int     device_close(Device* device);

mylib.lnb

声明 C 函数如何映射成 LNS API。

option module = "mylib";
option output_contract = "trusted/lib/mylib.lns";
option output_glue = "mylib_glue.c";
option output_init = "mylib_init.c";
option init_name = "lnlib_init_mylib";

api "mylib" {
  starts  : int  = mylib_starts();
  add     : num  = mylib_add(num a, num b);
  name    : str  = mylib_name() @nonnull;
  set_led : int! = mylib_set_led(int pin, int on);
}

api "device" {
  open  : handle = device_open(str id) @handle("Device") @borrowed;
  close : int!   = device_close(handle device) @handle("Device");
}

LNB 语法结构

option

声明生成产物名称,例如模块名、契约路径、glue 文件、注册入口函数名。

api 块

每个 `api "cap"` 对应一个 LNS capability。块内左侧是脚本函数名,右侧是 C 符号。

属性

`@nonnull`、`@handle("Device")`、`@borrowed` 等属性补充转换策略,不污染 LNS 脚本语法。

api "mylib" {
  脚本函数名 : LNS返回类型 = C函数名(LNS参数类型 参数名) @属性;
  add       : num       = mylib_add(num a, num b);
  set_led   : int!      = mylib_set_led(int pin, int on);
}

类型转换是边界,不是脚本负担

数值

`int` 要求有限整数并检查 C 目标范围;`num` 转 double;`float/double` 按浮点规则转换。

字符串

`str` 可转 `const char*` 或 `const ln_str*`。C 返回 NULL 时按规则抛错或按属性映射。

错误码

`int!` 表示 C 返回 0 成功、非 0 失败。LNS 侧生成 `extern void`,失败进入异常/pcall 流程。

handle:自定义 C 数据的唯一通道

LNB 不暴露裸 C 指针类型。设备对象、连接对象、图像对象、文件句柄等都以 LNS `handle` 表示。脚本只能保存和回传 handle,不能访问 C 对象内部。

  • @handle("Device") 类型名
  • @borrowed 宿主管生命周期
  • @managed 预留给 VM 管生命周期
  • 调用前做类型校验
api "device" {
  open  : handle = device_open(str id)
                   @handle("Device") @borrowed;

  close : int!   = device_close(handle device)
                   @handle("Device");
}

生成后的 LNS 契约

api "mylib" {
  extern int starts();
  extern num add(num a, num b);
  extern str name();
  extern void set_led(int pin, int on);
}

api "device" {
  extern handle open(str id);
  extern void close(handle device);
}

脚本用户最终看到的样子

import mylib;
import device;

function main() {
  var total = mylib.add(1, 2);
  var d = device.open("motor-1");

  mylib.set_led(4, 1);
  device.close(d);

  return mylib.name() + ": " + total;
}