← SDK
Rust SDK

majorsilence-reporting

从 Rust 生成 PDF、Excel、CSV 等格式的 RDL 报表——通过 libloading 实现进程内原生 FFI。无需 .NET 运行时,无需子进程。线程安全、基于 Arc 的库句柄。仅有一个依赖项。


安装

该 crate 尚未发布到 crates.io。请将其作为路径依赖添加:

# Cargo.toml
[dependencies]
majorsilence-reporting = { path = "/path/to/reporting-rust" }

# 发布到 crates.io 后:
# majorsilence-reporting = "1.0"

唯一的运行时依赖是 libloading = "0.8"。Rust 2021 版,最低支持 Rust 1.65。


快速开始

use majorsilence_reporting::RdlLibrary;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 每个进程只加载一次原生库。
    let lib = RdlLibrary::load("/path/to/rdlnative/librdlnative.so")?;

    // 构建报表配置。
    let mut rpt = lib.report("/path/to/report.rdl");
    rpt.set_connection_string("Data Source=/path/to/northwindEF.db")
       .set_parameter("Country", "Germany");

    // 导出到文件。
    rpt.export("pdf", "/tmp/output.pdf")?;

    // 或者导出到内存——不写入任何临时文件。
    let pdf_bytes: Vec<u8> = rpt.export_to_memory("pdf")?;
    println!("获得 {} 字节", pdf_bytes.len());

    Ok(())
}

加载库

RdlLibrary::load 会完成所有必要的准备工作:设置 RDLNATIVE_LIB_DIR,以 RTLD_GLOBAL 方式预加载相关的 .so / .dylib 文件,以便 .NET 的 P/Invoke 解析器能找到它们,解析所有 FFI 符号,并调用 rdl_init()

use majorsilence_reporting::RdlLibrary;

// 在进程启动时调用一次。RdlLibrary 基于 Arc——克隆成本低,且满足 Send + Sync。
let lib = RdlLibrary::load("/path/to/librdlnative.so")?;

// 跨线程克隆(例如放入 Axum 的 state 或 Actix 的 Data 中)
let lib2 = lib.clone();
std::thread::spawn(move || {
    let mut rpt = lib2.report("/path/to/report.rdl");
    rpt.export("pdf", "/tmp/thread.pdf").unwrap();
});
在进程启动前,LD_LIBRARY_PATH(Linux)或 DYLD_LIBRARY_PATH(macOS)必须包含原生库所在目录。RdlLibrary::load 会处理 RDLNATIVE_LIB_DIR 及相关库的预加载,但无法为正在运行的进程追溯性地修改动态链接器的搜索路径。

核心 API

构建器方法返回 &mut Self 以支持链式调用。每次调用 exportexport_to_memory 都会打开并关闭一个新的原生报表句柄,因此一个 Report 值可以在多次渲染中重复使用。

方法说明
RdlLibrary::load(path)加载原生库并初始化 FFI 符号表。返回 Result<RdlLibrary, Error>。每个进程调用一次。
lib.report(rdl_path)创建一个绑定到 .rdl 文件的 Report 值。成本很低——此时尚未打开任何原生句柄。
rpt.set_connection_string(cs)设置数据库连接字符串。返回 &mut Self
rpt.set_parameter(name, value)设置一个具名报表参数。返回 &mut Self。可根据需要多次调用。
rpt.add_data(name, rows)提供一个内存数据集。rows 类型为 Vec<HashMap<String, String>>。返回 &mut Self。参见内存数据集
rpt.export(format, path)渲染并写入文件路径。返回 Result<(), Error>
rpt.export_to_memory(format)渲染并返回字节。返回 Result<Vec<u8>, Error>。不写入任何临时文件。

内存数据集

无需数据库即可将结构化数据直接传递给报表。每一行是一个 HashMap<String, String>,键为列名,值为以字符串编码的字段值。

use std::collections::HashMap;
use majorsilence_reporting::RdlLibrary;

let lib = RdlLibrary::load("/path/to/librdlnative.so")?;
let mut rpt = lib.report("/path/to/report.rdl");

let rows: Vec<HashMap<String, String>> = vec![
    [("Product", "PDF Library Pro"), ("Revenue", "1200.00"), ("Units", "3")]
        .iter().map(|(k, v)| (k.to_string(), v.to_string())).collect(),
    [("Product", "Report Designer"),  ("Revenue", "250.00"),  ("Units", "1")]
        .iter().map(|(k, v)| (k.to_string(), v.to_string())).collect(),
];

rpt.add_data("SalesData", rows);
let pdf = rpt.export_to_memory("pdf")?;
数据集名称必须与 .rdl 文件中定义的数据集匹配。所有字段值都必须是字符串——在插入映射前请先转换数字、日期和布尔值。可以通过多次调用 add_data 提供多个数据集。

导出到内存

export_to_memory 调用原生 FFI 函数 rdl_report_render_buffer,该函数填充一个原生分配的缓冲区,并返回指针和长度。Rust 封装层会将字节复制到 Vec<u8> 中,然后调用 rdl_free。整个过程都不会写入临时文件——非常适合用于 HTTP 响应体或流水线集成。

// Axum 处理函数示例
async fn report_handler(State(lib): State<RdlLibrary>) -> impl IntoResponse {
    let mut rpt = lib.report("/reports/sales.rdl");
    rpt.set_connection_string("Data Source=/data/northwindEF.db");

    match rpt.export_to_memory("pdf") {
        Ok(bytes) => (
            StatusCode::OK,
            [("content-type", "application/pdf")],
            bytes,
        ).into_response(),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    }
}

错误处理

所有可能失败的操作都返回 Result<T, majorsilence_reporting::Error>Error 实现了 std::error::Error,并封装了原生函数 rdl_last_error() 返回的消息。

match rpt.export_to_memory("pdf") {
    Ok(bytes) => { /* 使用 bytes */ }
    Err(e)    => eprintln!("渲染失败:{e}"),
}

// 或使用 ? 进行传播
let bytes = rpt.export_to_memory("pdf")?;
无法识别的格式字符串不会返回错误——会静默回退为 "pdf"。如需在编译期枚举所有有效格式,请使用公共常量 majorsilence_reporting::VALID_FORMATS: &[&str]

导出格式

支持全部十种格式。与 Python/PHP/Ruby 的子进程模式不同,Rust 封装层始终使用原生 FFI 路径,因此所有格式均可用。

格式字符串输出
pdfPDF(同时也是任何无法识别字符串的回退格式)
csv逗号分隔值
xlsxExcel 工作簿
xlsx_tableExcel 工作簿(表格样式)
xmlXML 数据
rtfRich Text Format
tifTIFF 图像(彩色)
tifbTIFF 图像(黑白)
htmlHTML
mhtMHTML 网页存档