跳到主要内容

C SDK 概述

C ABI 内核(darra_ai.h + DarraAI.Core)。全部推理唯一入口:darra_session_open / darra_session_open_plaindarra_session_infer_imagedarra_session_close。GUI、PLC、客户应用同一路径,无第二套。

当前版本

本文档对应 C SDK v1.0.0(与 darra_ai.hDARRA_VERSION_MAJOR/MINOR/PATCH 同一组数字)。加载后第一件事核对 ABI:DARRA_VERSION_DECODE_MAJOR(darra_version()) == DARRA_VERSION_MAJOR,不等 = 装错了库,禁止继续。运行时字符串用 darra_version_string(),客户机必须看到 1.0.0+runtime

公开 runtime 构建只含解密、验票、推理分发、诊断、运行时包管理;物理上没有加密 / 签发符号。权威契约 = darra_ai.h,语义冲突以头文件为准。

安装

下载页面 获取内核包或 darra-ai-c-sdk.zip(头文件 + import lib + 示例)。

包:darra-ai-core-windows-x64.zipDarraAI.Core.dlldarra_ai.h、import lib、darra-selftest)。

cl /utf-8 /Fe:my_app.exe main.c DarraAI.Core.lib /I include

运行目录必须带 DarraAI.Core.dll。MSVC 消费方加 /utf-8(头文件 UTF-8 无 BOM,注释全中文)。

#include "darra_ai.h"

只交付 x64 / ARM64,两平台均单调用约定,无 __stdcall 变体。

环境要求

项目要求
操作系统Windows x64 / Linux x86_64 / Linux ARM64(RK3588)。macOS 不在 v1
编译器MSVC(/utf-8)/ GCC / Clang,C11 或更高
头文件darra_ai.h
运行库Windows:DarraAI.Core.dll;Linux:libdarraai_core.so
权限装运行时包到系统目录时可能需要管理员;SDK 不代装 NVIDIA / RK3588 NPU 等系统驱动
重依赖ORT / CUDA / TensorRT / OpenVINO / RKNN 走 Provider;首次开会话时 darra_runtime_ensure 按硬件探测下载校验
诚实边界
  1. 不承诺 dump 免疫。 授权机 + 管理员级调试器在会话存活期暂停 dump,挡不住。本 SDK 防的是静态拷走、转发、无授权运行、常规 dump 工具、逆向授权算法。
  2. 加密单向。 官方不提供解回明文的通道。永久密码只用于无限次推理。
  3. 驱动不代装。 进程内能解决的全部自动;要动系统的只诊断不代劳(darra_diag_collect 给出原因)。
  4. macOS 不在 v1。 不是「即将支持」。
  5. 不承诺硬实时。 推理是阻塞调用。PLC 侧必须在扫描周期外进程 / 线程调用。
  6. 诊断 JSON 不含密钥 / 密码 / token / 机器指纹字节。

快速开始

生产路径:加密容器

客户现场加载 .darmodel(DARM1)+ .darmkey(或永久密码)。导出格式即加载格式,用户零配置。

#include <stdio.h>
#include "darra_ai.h"

int main(void) {
if (DARRA_VERSION_DECODE_MAJOR(darra_version()) != DARRA_VERSION_MAJOR) {
fprintf(stderr, "ABI 不匹配: %s\n", darra_version_string());
return 1;
}

darra_session* s = NULL;
int32_t rc = darra_session_open("model.darmodel", "model.darmkey", NULL, NULL, &s);
if (rc != DARRA_OK) {
darra_error_t err; err.size = sizeof(err);
darra_last_error(&err);
fprintf(stderr, "[%d] %s\n建议: %s\n", err.code, err.message, err.hint);
return (int)rc;
}

/* 读图、填 darra_image_desc、darra_session_infer_image … */

darra_session_close(s);
return 0;
}

对应包未装时,open 内部会调一次 darra_runtime_ensure(无进度回调,可能阻塞下载)。想看进度请先自己调 ensure(幂等)。

备选方式:明文模型(试跑 / 自有无需加密模型)

与加密入口同一推理路径,只跳过容器解码与验票。

darra_session* s = NULL;
int32_t rc = darra_session_open_plain("model.onnx", NULL, &s);

open_plain 的路径可以是 .onnx.pdmodel(同目录须有 .pdiparams)、或含二者的目录。.pt / .pth 仍拒,返回 DARRA_UNSUPPORTED_PAYLOAD

何时用哪种
  • .darmodel + 授权 — 生产环境,推荐。
  • open_plain — Studio 试跑 / 客户自有明文模型。

约定

返回值

除注明 void 的函数外,一律返回 int32_t 错误码:0DARRA_OK)= 成功;非 0 = darra_error_code 之一,返回值 == 本线程 darra_last_error 的 code。细节用 darra_last_error 取。

int32_t rc = darra_xxx(...);
if (rc != DARRA_OK) {
darra_error_t err; err.size = sizeof(err);
darra_last_error(&err); /* err.message 是什么错,err.hint 怎么修 */
}

错误槽是线程局部的:每个 API 进入时清空本线程槽,失败时填满。失败后立刻在同一线程取,不要被同线程下一次 API 冲掉。逐码文案见 错误码

参数契约违反(NULL 句柄 / NULL 出参 / size 未填 / 裸缓冲缺宽高)属调用方 bug:返回 DARRA_INTERNAL,message 指明哪个参数。SDK 在任何非法入参下都不崩溃。

字符串所有权

来源释放
char** 出参(diag / meta / infer 结果)必须 darra_string_free,禁止 free() / 跨 CRT 堆释放
darra_version_string()静态字符串,禁止释放
编码UTF-8,SDK 保证 NUL 结尾

结构体 size

所有结构体首字段 uint32_t size调用方填 sizeof(结构体)。SDK 只读写它认识的字段;旧头配新 dll/so 照常工作。

darra_image_desc desc;
memset(&desc, 0, sizeof(desc));
desc.size = sizeof(darra_image_desc);

线程安全

函数线程安全备注
darra_version / darra_version_string纯查询,不改错误槽
darra_last_error / darra_clear_error只碰本线程槽
darra_string_freeNULL 安全;重复释放 = 未定义行为
darra_diag_collect耗时可到数百毫秒,禁止在 PLC 扫描周期调
darra_runtime_ensure进程内互斥串行,重复 / 并发安全
darra_runtime_ensure_cancel任意线程可调;进度回调内禁止
darra_session_open / open_plain / open_ex / open_plain_ex各开各的会话可并发;缺包时内部 ensure 走同一把进程锁
darra_session_meta_json只读会话内不可变数据
darra_session_infer_image同会话可并发吞吐扩展推荐每线程一个会话;v1 无视频流
darra_session_close仅在不与其它调用并发时close 后句柄失效,再用 = 未定义行为

API 总览

类别属性类型访问说明
版本darra_versionuint32_t查询打包版本,用 DARRA_VERSION_DECODE_* 宏拆 major/minor/patch
darra_version_stringconst char*查询静态字符串,形如 1.0.0+runtime;禁止释放
错误darra_last_errorint32_t查询取本线程最近一次失败细节;out 可为 NULL
darra_clear_errorvoid过程显式清空本线程错误槽
字符串darra_string_freevoid释放释放 SDK 堆字符串;NULL 安全
诊断darra_diag_collectint32_t查询本机环境诊断 JSON;用完 darra_string_free
运行时包darra_runtime_ensureint32_t过程确保 Provider / profile 已装;幂等、事务性
darra_runtime_ensure_cancelvoid过程取消在飞的 ensure;无在飞则为 no-op
会话darra_session_openint32_t过程打开 .darmodel 建会话(options = NULL)
darra_session_open_plainint32_t过程打开明文模型建会话(options = NULL)
darra_session_open_exint32_t过程打开 .darmodel,带 darra_session_options
darra_session_open_plain_exint32_t过程打开明文模型,带 darra_session_options
darra_session_meta_jsonint32_t查询会话元信息 JSON;用完 darra_string_free
darra_session_infer_imageint32_t过程单张图像推理,结果 JSON;v1 无视频流
darra_session_closevoid释放关闭会话;NULL 安全;禁止与在飞调用并发

一模型一实例:一个 darra_session* = 一份已加载模型 + 一个 Provider。多模型或多份同模型 = 多会话,API 完全相同。指名 Provider 不可用 = fail-closed,绝不静默降级。

版本

darra_version()

uint32_t darra_version(void);

返回打包版本 (major << 16) | (minor << 8) | patch,用 DARRA_VERSION_DECODE_MAJOR / MINOR / PATCH 宏拆。不会失败,不改错误槽。

头文件常量:DARRA_VERSION_MAJOR 1DARRA_VERSION_MINOR 0DARRA_VERSION_PATCH 0DARRA_HEADER_VERSION 为本头打包值。绑定侧核对:运行时 major 必须等于 DARRA_VERSION_MAJOR,否则 ABI 已破坏。

darra_version_string()

const char* darra_version_string(void);

返回形如 "1.0.0+runtime" / "1.0.0+authoring" 的静态字符串。生命周期 = 进程,禁止释放。客户机上必须看到 +runtime

if (DARRA_VERSION_DECODE_MAJOR(darra_version()) != DARRA_VERSION_MAJOR) {
fprintf(stderr, "ABI 不匹配: 头=%d 库=%s\n", DARRA_VERSION_MAJOR, darra_version_string());
return 1;
}

错误

darra_error_codedarra_error_t

类别属性类型访问说明
错误码DARRA_OK0只读成功
错误码DARRA_IO_NOT_FOUND1只读文件不存在(容器 / 授权 / 图片 / 离线包路径错)
错误码DARRA_IO_DENIED2只读文件存在但读 / 写被拒
错误码DARRA_BAD_CONTAINER3只读.darmodel 损坏 / 非 DARM1 / 被截断篡改
错误码DARRA_BAD_LICENSE4只读.darmkey 损坏或与容器不配对
错误码DARRA_PASSWORD_REQUIRED5只读该容器需要密码但调用方没给
错误码DARRA_PASSWORD_WRONG6只读密码错误
错误码DARRA_MACHINE_MISMATCH7只读绑机授权在非目标机器上使用
错误码DARRA_TRIAL_EXHAUSTED8只读试用计次已用完
错误码DARRA_UNSUPPORTED_PAYLOAD9只读载荷类型在当前平台 / Provider 下不支持
错误码DARRA_UNSUPPORTED_PLATFORM10只读整个 OS / 架构不在支持矩阵(如 macOS、x86)
错误码DARRA_PROVIDER_MISSING11只读需要的运行时 Provider 包未安装
错误码DARRA_PROVIDER_ABI_MISMATCH12只读Provider 包与 Core 的 ABI 版本不匹配
错误码DARRA_HARDWARE_MISSING13只读未探测到所需硬件(N 卡 / Intel / RK3588 NPU)
错误码DARRA_DRIVER_TOO_OLD14只读驱动版本低于 Provider 要求(SDK 不代装)
错误码DARRA_NETWORK_FAILED15只读清单 / 运行时包下载失败
错误码DARRA_CHECKSUM_MISMATCH16只读下载包或离线包 sha256 校验不过
错误码DARRA_CANCELLED17只读操作被取消(见 darra_runtime_ensure_cancel
错误码DARRA_INTERNAL18只读SDK 内部错误;也用于调用方参数契约违反
结构darra_error_t.sizeuint32_t调用方填sizeof(darra_error_t)
结构darra_error_t.codeint32_t只读darra_error_code
结构darra_error_t.messagechar[256]只读UTF-8,保证 NUL 结尾,无需释放
结构darra_error_t.hintchar[512]只读修复建议,可为空串;文案见 错误码

darra_last_error()

int32_t darra_last_error(darra_error_t* out);

取本线程最近一次失败的细节。out 可为 NULL(只查码);非 NULL 时调用方先填 out->size = sizeof(*out)。无错误(或上次调用成功)返回 DARRA_OK

darra_clear_error()

void darra_clear_error(void);

显式清空本线程错误槽。一般不需要手动调(每次 API 进入自动清),供绑定层在特殊时序下使用。

字符串

darra_string_free()

void darra_string_free(char* s);

释放 SDK 返回的堆字符串。NULL 安全;重复释放 = 未定义行为。Windows 上 SDK 与调用方可能不是同一 CRT 堆,禁止 free() / delete[]

环境自检

darra_diag_collect()

int32_t darra_diag_collect(char** out_json);

采集本机环境诊断,输出统一 JSON(schema 见 附录 A)。覆盖:os/arch、CPU、GPU 型号 + 驱动版本 + CUDA 上限、已装 Provider 及版本、授权状态(去敏:绝不含密钥 / 密码 / token / 机器指纹字节)、最近错误环形缓冲(去敏)。

  • 尽力而为:单个字段采不到时 JSON 中该字段为 null 或缺席,整体不失败;只有彻底无法采集才返回 DARRA_INTERNAL
  • 注意:可能触发 WMI / NVML / sysfs 查询,耗时可到数百毫秒——禁止在 PLC 扫描周期等实时路径调用
  • out_json:SDK 分配的 UTF-8 JSON,用完 darra_string_free
char* diag = NULL;
if (darra_diag_collect(&diag) == DARRA_OK) {
puts(diag);
darra_string_free(diag);
}

运行时包

合法 ID(头文件第 5 节,不得另造):

类别属性类型访问说明
细包onnx-cpuProvider入参ORT CPU
细包onnx-cudaProvider入参ORT CUDA EP
细包onnx-openvinoProvider入参ORT OpenVINO EP
细包onnx-trt-epProvider入参ORT TensorRT EP
细包trt-nativeProvider入参裸 TensorRT 引擎
细包edge-rknnProvider入参仅 linux-arm64;Windows 上指名返回 DARRA_UNSUPPORTED_PLATFORM
场景包cpu-onlyprofile入参细包清单,不产生新二进制
场景包nvidia-gpuprofile入参同上
场景包intel-iapprofile入参同上
场景包amd-dmlprofile入参同上
场景包board-rk3588profile入参同上
自动NULL入参按硬件探测自动选细包(默认推荐)

进度回调:

typedef void (*darra_progress_cb)(float percent, const char* stage, void* user);

percent 0..100,每个 stage 内单调不降。stageprobe / manifest / download / verify / install / done。回调在 SDK 内部工作线程触发:回调里禁止调用任何 darra_* 函数,只准记录或转发到调用方自己的 UI 队列。

darra_runtime_ensure()

int32_t darra_runtime_ensure(const char* profile_or_provider_id,
const char* offline_zip_or_null,
darra_progress_cb cb,
void* user);

确保指定 Provider / profile 已装好;没装就探测 → 选包 → 下载 → sha256 校验 → 解压登记。

  • profile_or_provider_id:上表细包 / 场景包;NULL = 按硬件探测自动选
  • offline_zip_or_null:离线整合包 zip 路径;给定时跳过 manifest/download,仍过内嵌 sha256 清单校验。NULL = 在线下载。
  • cb / user:进度回调与透传指针;cb 可为 NULL。
  • 幂等:已装且清单匹配 → 直接成功(回调收到一次 "done", 100)。
  • 进程内串行:并发调用被内部互斥串行化。
  • 事务性:全部步骤成功才登记;取消或任何失败不留半成品。
  • SDK 自动安装 / 升级系统驱动。

典型错误:DARRA_IO_NOT_FOUND(离线包路径错)/ DARRA_NETWORK_FAILED / DARRA_CHECKSUM_MISMATCH / DARRA_IO_DENIED(目标目录需管理员)/ DARRA_UNSUPPORTED_PLATFORM / DARRA_PROVIDER_ABI_MISMATCH / DARRA_CANCELLED

static void on_progress(float percent, const char* stage, void* user) {
(void)user;
printf("\r[%-8s] %5.1f%%", stage, percent); fflush(stdout);
}

int32_t rc = darra_runtime_ensure("cpu-only", NULL, on_progress, NULL);

darra_runtime_ensure_cancel()

void darra_runtime_ensure_cancel(void);

请求取消当前在飞的 darra_runtime_ensure(可从任意线程调,含进度回调外的 UI 线程)。没有在飞的 ensure 时为 no-op。被取消的 ensure 返回 DARRA_CANCELLED进度回调内部禁止调用。

推理会话

打开时按 PayloadKind × 硬件 × prefer_provider 选 Provider;对应包未装则内部调一次 darra_runtime_ensurecb=NULL,可能阻塞下载)。指名但不可用(无硬件 / 驱动过旧 / 平台不支持)= fail-closed。

Windows 收到 rknn 载荷返回 DARRA_UNSUPPORTED_PAYLOAD。禁止改 Windows ReservedCpuSets,禁止动 PLC 隔离核——darra_session_options 只约束本会话推理线程池。

Paddle 是运行时载荷(PayloadKind=paddle):明文 open_plain / open_plain_ex 可接 .pdmodel(同目录须有 .pdiparams)或含二者的目录。.pt / .pth 仍拒。

不透明句柄 darra_session:绑定侧永远只持有指针。

darra_session_open()

int32_t darra_session_open(const char* container_path,
const char* key_path_or_null,
const char* password_or_null,
const char* prefer_provider_or_null,
darra_session** out);

打开加密容器(.darmodel,DARM1)建会话。本函数 ≡ darra_session_open_ex(..., options=NULL)

类别属性类型访问说明
入参container_pathconst char*必填.darmodel 路径
入参key_path_or_nullconst char*可选.darmkey;NULL = 走密码 / 试用通道
入参password_or_nullconst char*可选永久密码;NULL = 走授权文件 / 试用通道。两个通道至少给一个;都给则 key 文件优先验票
入参prefer_provider_or_nullconst char*可选指名 Provider;NULL = 自动选。指名但不可用 = fail-closed,绝不静默降级
出参outdarra_session**写出成功时 *out 拿到会话;失败时 *out == NULL

典型错误:DARRA_IO_NOT_FOUND / DARRA_IO_DENIED / DARRA_BAD_CONTAINER / DARRA_BAD_LICENSE / DARRA_PASSWORD_REQUIRED / DARRA_PASSWORD_WRONG / DARRA_MACHINE_MISMATCH / DARRA_TRIAL_EXHAUSTED / DARRA_UNSUPPORTED_PAYLOAD / DARRA_PROVIDER_MISSING / DARRA_PROVIDER_ABI_MISMATCH / DARRA_HARDWARE_MISSING / DARRA_DRIVER_TOO_OLD / DARRA_NETWORK_FAILED / DARRA_CHECKSUM_MISMATCH / DARRA_CANCELLED(后三码来自内部 ensure)。

darra_session* s = NULL;
int32_t rc = darra_session_open("model.darmodel", "model.darmkey", NULL, NULL, &s);
if (rc != DARRA_OK) { /* darra_last_error 取细节 */ return rc; }

darra_session_open_plain()

int32_t darra_session_open_plain(const char* onnx_path,
const char* prefer_provider_or_null,
darra_session** out);

打开明文模型建会话。本函数 ≡ darra_session_open_plain_ex(..., options=NULL)。与 darra_session_open 同一推理路径,只跳过容器解码与验票。

onnx_path(历史参数名,语义为明文模型路径):.onnx 文件;.pdmodel 文件(同目录须有对应 .pdiparams);含 *.pdmodel + *.pdiparams 的目录。.pt / .pth 仍拒 → DARRA_UNSUPPORTED_PAYLOAD

其余(含内部 ensure、fail-closed)同 darra_session_open

darra_session_options

typedef struct darra_session_options {
uint32_t size;
int32_t intra_op_threads;
int32_t inter_op_threads;
uint64_t cpu_affinity_mask;
} darra_session_options;
类别属性类型访问说明
选项sizeuint32_t调用方填sizeof(darra_session_options)
选项intra_op_threadsint32_t读写0 = 自动,自动上限 min(硬件逻辑核, 4)>0 照填给本会话线程池;<0 = DARRA_INTERNAL
选项inter_op_threadsint32_t读写同上
选项cpu_affinity_maskuint64_t读写0 = 不设;非 0 = 本会话线程池 CPU 位图(bit0 = 逻辑核 0)。禁止ReservedCpuSets / PLC 隔离核

NULL*_ex = 与旧 open / open_plain 完全相同(自动线程、不设亲和)。多会话各用各的线程池,互不抢同一把全局锁(darra_runtime_ensure 的进程内互斥除外)。

darra_session_open_ex()

int32_t darra_session_open_ex(const char* container_path,
const char* key_path_or_null,
const char* password_or_null,
const char* prefer_provider_or_null,
const darra_session_options* options_or_null,
darra_session** out);

打开加密容器建会话(带选项)。options_or_null == NULL 时与 darra_session_open 逐字相同。非 NULL 时须先填 options->size;size 未填 / 线程数为负 = DARRA_INTERNAL。其余同 darra_session_open

darra_session_options opt;
memset(&opt, 0, sizeof(opt));
opt.size = sizeof(opt);
opt.intra_op_threads = 2;
opt.inter_op_threads = 1;

darra_session* s = NULL;
int32_t rc = darra_session_open_ex("model.darmodel", "model.darmkey", NULL, NULL, &opt, &s);

darra_session_open_plain_ex()

int32_t darra_session_open_plain_ex(const char* model_path,
const char* prefer_provider_or_null,
const darra_session_options* options_or_null,
darra_session** out);

打开明文模型建会话(带选项)。model_path.onnx / .pdmodel(伴生 .pdiparams)/ 含二者的目录。.pt / .pth 仍拒。options_or_null == NULL 时与 darra_session_open_plain 逐字相同。

darra_session_meta_json()

int32_t darra_session_meta_json(darra_session* session, char** out_json);

读会话元信息(schema 见 附录 B):task / labels / 输入宽高与通道 / Layout / Mean / Std / PayloadKind / 实际选中的 Provider / 是否加密 / 授权摘要(去敏,含试用剩余次数)。全部来自容器头或明文探测,用户零配置。 头里缺的字段派生 JSON 里为 null / 缺席,不编默认 640/80/17。

调用代价:读会话缓存的头信息,微秒级。out_json 用完 darra_string_free

darra_session_infer_image()

int32_t darra_session_infer_image(darra_session* session,
const uint8_t* image_bytes,
size_t len,
const darra_image_desc* desc,
char** out_results_json);

对一张图片跑推理,结果输出 JSON(envelope 见 附录 C)。v1 只接受单张图像,不做视频流(连续帧请循环调用本函数)。阻塞调用,耗时随 Provider/硬件而定。不承诺硬实时。

图像格式与描述:

类别属性类型访问说明
格式DARRA_IMAGE_AUTO0入参按字节魔数自嗅探 JPG/PNG/BMP。裸缓冲禁止用 AUTO
格式DARRA_IMAGE_ENCODED1入参显式已编码字节流;v1 行为同 AUTO
格式DARRA_IMAGE_RGB8882入参裸缓冲 RGB 8bit,须给宽高
格式DARRA_IMAGE_BGR8883入参裸缓冲 BGR 8bit,须给宽高
格式DARRA_IMAGE_GRAY84入参裸缓冲灰度 8bit,须给宽高
格式DARRA_IMAGE_RGBA88885入参裸缓冲 RGBA 8bit,须给宽高
描述darra_image_desc.sizeuint32_t调用方填sizeof(darra_image_desc)
描述darra_image_desc.formatdarra_image_format读写见上
描述darra_image_desc.widthuint32_t读写AUTO/ENCODED 忽略填 0;裸缓冲必填 >0
描述darra_image_desc.heightuint32_t读写同上
描述darra_image_desc.strideuint32_t读写每行字节数;0 = 紧凑:RGB/BGR=width*3,GRAY8=width,RGBA=width*4

image_bytes / len:AUTO/ENCODED = 编码文件字节流;裸缓冲 = 像素内存,len 必须 ≥ stride*height(stride 为 0 时按 format 紧凑宽度计)。裸缓冲缺宽高 / len 不足 / desc->size 未填 = DARRA_INTERNALDARRA_IO_DENIED = 裸缓冲指针不可读。

darra_image_desc desc;
memset(&desc, 0, sizeof(desc));
desc.size = sizeof(desc);
desc.format = DARRA_IMAGE_BGR888; /* 相机原生 BGR */
desc.width = 1920;
desc.height = 1080;
desc.stride = 0;

char* results = NULL;
int32_t rc = darra_session_infer_image(s, frame, (size_t)1920 * 1080 * 3, &desc, &results);
if (rc == DARRA_OK) { puts(results); darra_string_free(results); }

同一会话可并发调用(底层运行时会话线程安全;解密明文窗口由 SDK 内部串行管理);吞吐扩展推荐每线程一个会话。

darra_session_close()

void darra_session_close(darra_session* session);

关闭会话并释放全部资源(含解密明文窗口清零)。NULL 安全。close 后句柄失效,再用 = 未定义行为;禁止与任何在飞调用并发。

完整例子

场景:一台刚装完系统的客户机,没有任何 AI 运行时。目标:装齐环境 → 打开加密模型 → 读元信息 → 推理一张图片 → 干净退出。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "darra_ai.h"

static int fail(const char* where, int32_t rc) {
darra_error_t err; err.size = sizeof(err);
if (darra_last_error(&err) == DARRA_OK) {
fprintf(stderr, "%s 失败 rc=%d(无错误细节)\n", where, rc);
} else {
fprintf(stderr, "%s 失败 [%d] %s\n建议: %s\n", where, err.code, err.message, err.hint);
}
return (int)rc;
}

static void on_progress(float percent, const char* stage, void* user) {
(void)user;
printf("\r环境准备 [%-8s] %5.1f%% ", stage, percent);
fflush(stdout);
}

static uint8_t* read_file(const char* path, size_t* out_len) {
FILE* f = fopen(path, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END); long n = ftell(f); fseek(f, 0, SEEK_SET);
if (n <= 0) { fclose(f); return NULL; }
uint8_t* buf = (uint8_t*)malloc((size_t)n);
if (buf && fread(buf, 1, (size_t)n, f) != (size_t)n) { free(buf); buf = NULL; }
fclose(f);
if (buf) *out_len = (size_t)n;
return buf;
}

int main(void) {
int32_t rc;
darra_session* session = NULL;
char* meta = NULL;
char* results = NULL;
uint8_t* image = NULL;
int exit_code = 1;

if (DARRA_VERSION_DECODE_MAJOR(darra_version()) != DARRA_VERSION_MAJOR) {
fprintf(stderr, "ABI 不匹配,库版本 = %s\n", darra_version_string());
return 1;
}

{
char* diag = NULL;
if (darra_diag_collect(&diag) == DARRA_OK) {
fprintf(stderr, "[diag] %s\n", diag);
darra_string_free(diag);
}
}

/* NULL = 按硬件自动选细包;断网现场换成
* darra_runtime_ensure("cpu-only", "darra-ai-runtime-cpu-only-offline.zip", ...) */
rc = darra_runtime_ensure(NULL, NULL, on_progress, NULL);
printf("\n");
if (rc != DARRA_OK) return fail("darra_runtime_ensure", rc);

rc = darra_session_open("model.darmodel", "model.darmkey", NULL, NULL, &session);
if (rc != DARRA_OK) return fail("darra_session_open", rc);

rc = darra_session_meta_json(session, &meta);
if (rc != DARRA_OK) { fail("darra_session_meta_json", rc); goto cleanup; }
printf("[meta] %s\n", meta);

{
size_t image_len = 0;
image = read_file("photo.jpg", &image_len);
if (!image) { fprintf(stderr, "读图片失败\n"); goto cleanup; }

darra_image_desc desc;
memset(&desc, 0, sizeof(desc));
desc.size = sizeof(desc);
desc.format = DARRA_IMAGE_AUTO;

rc = darra_session_infer_image(session, image, image_len, &desc, &results);
if (rc != DARRA_OK) { fail("darra_session_infer_image", rc); goto cleanup; }
printf("[results] %s\n", results);
}

exit_code = 0;

cleanup:
free(image);
if (results) darra_string_free(results);
if (meta) darra_string_free(meta);
darra_session_close(session);
return exit_code;
}

要点:

  1. ABI 核对先行,装错库不往下走;
  2. runtime_ensure 幂等——已装好就是秒过,可每次启动都调;
  3. 所有 char* 出参一律 darra_string_free,调用方自己 malloc 的自己 free
  4. 任何一步失败,darra_last_errorhint 就是给最终用户的修复建议(见 错误码)。

附录 A:诊断 JSON

darra_diag_collect 输出:

{
"schema": 1,
"sdk": { "version": "1.0.0+runtime", "abi": 65536 },
"os": { "name": "windows", "version": "10.0.26100", "arch": "x64" },
"cpu": { "model": "AMD Ryzen 7 9800X3D", "cores": 16 },
"gpus": [ { "model": "NVIDIA RTX 4070", "driver": "560.94", "cudaMax": "12.6" } ],
"providers": [ { "id": "onnx-cpu", "version": "1.0.0", "path": "C:/.../Env/onnx-cpu/1.0.0" } ],
"license": { "present": true, "mode": "trial", "trialRemaining": 12, "machineBound": false },
"recentErrors": [ { "code": 11, "message": "...", "when": "2026-09-01T10:00:00+08:00" } ]
}
  • schema 恒存在;其余字段采不到时为 null 或缺席,消费方必须容忍。
  • 去敏:永不出现密钥 / 密码 / token / 机器指纹原始字节;license 只有状态摘要。
  • gpus 无独显时为 []providers 未装任何包时为 []

附录 B:会话元信息 JSON

darra_session_meta_json 输出:

{
"schema": 1,
"task": "detect",
"labels": ["scratch", "dent"],
"input": { "width": 640, "height": 640, "channels": 3 },
"payloadKind": "onnx",
"provider": "onnx-cuda",
"encrypted": true,
"containerFormat": "DARM1",
"license": { "mode": "trial", "trialRemaining": 12, "machineBound": true }
}
  • task 派生短名随容器头;推理结果 results 元素结构以此为准。缺席不编 detect。头文件对结果形态的承诺:detect→box,classify→top-k,segment→掩码。
  • payloadKindonnx / trt-engine / openvino / rknn / paddle
  • input.width / height / channels 来自容器头。缺则 null,不编 640/80/17。
  • provider实际选中的 Provider(不是 prefer 入参)。
  • encrypted=false 时无 containerFormat / license(明文会话)。
  • 标准字段表见 元数据

附录 C:推理结果 JSON

darra_session_infer_image 输出:

{
"schema": 1,
"task": "detect",
"provider": "onnx-cuda",
"elapsedMs": 3.2,
"image": { "width": 1920, "height": 1080 },
"results": [ { "label": "scratch", "score": 0.93, "box": [120, 40, 36, 18] } ]
}
  • envelope 字段(schema / task / provider / elapsedMs / image / results)恒定;results 元素随 task 而变:
    • detect{label, score, box:[x,y,w,h]}
    • classify{label, score} top-k 按分降序;
    • segment{label, score, maskRle}
    • 其余 task 的逐元素 schema 随对应 Provider 落地,本契约只承诺 envelope。
  • 结果坐标基于 image 字段(解码后、送入模型前的尺寸);裸缓冲输入时等于 desc 的宽高。
  • 永不包含模型明文、密钥等任何敏感字节。