Rust D-Bus代码生成库dbus-codegen的使用,自动生成Rust绑定与接口代码实现D-Bus通信

Rust D-Bus代码生成库dbus-codegen的使用,自动生成Rust绑定与接口代码实现D-Bus通信

简介

dbus-codegen-rust是一个能够接收D-Bus XML Introspection数据并生成Rust代码的工具,用于调用和实现内省数据中的接口。

示例

从一个D-Bus接口定义开始:

<node>
    <interface name="org.example.test">
        <method name="Foo">
            <arg type="i" name="bar" direction="in"/>
            <arg type="s" name="baz" direction="out"/>
        </method>
        <signal name="Laundry">
            <arg type="b" name="eaten"/>
        </signal>
    </interface>
</node>

可以生成三种代码:

  • 客户端代码(调用接口)
  • 使用dbus-crossroads的服务端代码(实现接口)
  • 使用dbus-tree的服务端代码(实现接口)

客户端和服务端通用部分

  • 为接口方法生成trait

客户端版本:

pub trait OrgExampleTest {
    fn foo(&self, bar: i32) -> Result<String, dbus::Error>;
}

服务端版本:

pub trait OrgExampleTest {
    fn foo(&self, bar: i32) -> Result<String, dbus::MethodErr>;
}
  • 为每个信号生成结构体:
#[derive(Debug, Default)]
pub struct OrgExampleTestLaundry {
    pub eaten: bool,
}

impl dbus::SignalArgs for OrgExampleTestLaundry { /* 实现代码 */ }

客户端代码

  • 调用方法:
use OrgExampleTest;
let myString = myProxy.foo(myInteger)?;
  • 捕获信号:
use dbus::SignalArgs;
myConnection.add_match(OrgExampleTestLaundry::match_rule(None, None).into_static(), |laundrySignal| {
  println!("Laundry was eaten: {:?}", laundrySignal.eaten);
})

服务端代码 - dbus-crossroads

注册接口:

let token = register_org_example_test(&mut myCrossroads);
myCrossroads.insert("/", &[token], myData);

服务端代码 - dbus-tree

获取接口:

myInterface = orgexampletest_server(&myFactory, ());

实现方法:

impl OrgExampleTest for MyStruct {
    type Err = tree::MethodErr;
    fn foo(&self, bar: i32) -> Result<String, Self::Err> {
        /* 实现代码 */
    }
}

完整示例

1. 准备D-Bus接口定义

example.xml文件内容:

<node>
    <interface name="org.example.test">
        <method name="Foo">
            <arg type="i" name="bar" direction="in"/>
            <arg type="s" name="baz" direction="out"/>
        </method>
        <signal name="Laundry">
            <arg type="b" name="eaten"/>
        </signal>
    </interface>
</node>

2. 生成客户端代码

执行命令:

dbus-codegen-rust < example.xml > client.rs

生成代码client.rs

#[allow(non_upper_case_globals)]
pub const OrgExampleTest_NAME: &str = "org.example.test";
#[allow(non_upper_case_globals)]
pub const OrgExampleTest_PATH: &str = "/org/example/test";

pub trait OrgExampleTest {
    fn foo(&self, bar: i32) -> Result<String, dbus::Error>;
}

#[derive(Debug, Default)]
pub struct OrgExampleTestLaundry {
    pub eaten: bool,
}

impl dbus::SignalArgs for OrgExampleTestLaundry {
    const NAME: &'static str = "Laundry";
    const INTERFACE: &'static str = "org.example.test";
    fn matches(&self, h: &dbus::MessageHeader) -> bool {
        h.path().map(|p| p.as_cstr() == OrgExampleTest_PATH).unwrap_or(false)
            && h.interface().map(|i| i.as_cstr() == OrgExampleTest_NAME).unwrap_or(false)
            && h.member().map(|m| m.as_cstr() == "Laundry").unwrap_or(false)
    }
}

impl<T: dbus::blocking::Connection> OrgExampleTest for dbus::blocking::Proxy<'_, T> {
    fn foo(&self, bar: i32) -> Result<String, dbus::Error> {
        self.method_call("org.example.test", "Foo", (bar,))
            .and_then(|r: (String,)| Ok(r.0))
    }
}

3. 使用客户端代码

use dbus::blocking::Connection;
use std::time::Duration;

mod client;
use client::OrgExampleTest;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 建立连接
    let conn = Connection::new_session()?;
    
    // 创建代理
    let proxy = conn.with_proxy("org.example.test", "/org/example/test", Duration::from_millis(5000));
    
    // 调用方法
    let result = proxy.foo(42)?;
    println!("结果: {}", result);
    
    Ok(())
}

4. 生成服务端代码(dbus-crossroads)

执行命令:

dbus-codegen-rust --crossroads < example.xml > server.rs

生成代码server.rs

use dbus::crossroads::Crossroads;

pub trait OrgExampleTest {
    fn foo(&self, bar: i32) -> Result<String, dbus::MethodErr>;
}

#[derive(Debug, Default)]
pub struct OrgExampleTestLaundry {
    pub eaten: bool,
}

impl dbus::SignalArgs for OrgExampleTestLaundry {
    const NAME: &'static str = "Laundry";
    const INTERFACE: &'static str = "org.example.test";
    fn matches(&self, h: &dbus::MessageHeader) -> bool {
        h.path().map(|p| p.as_cstr() == "/org/example/test").unwrap_or(false)
            && h.interface().map(|i| i.as_cstr() == "org.example.test").unwrap_or(false)
            && h.member().map(|m| m.as_cstr() == "Laundry").unwrap_or(false)
    }
}

pub fn register_org_example_test<T>(cr: &mut Crossroads<T>) -> dbus::crossroads::IfaceToken<T>
where
    T: OrgExampleTest + Send + 'static,
{
    cr.register("org.example.test", |b| {
        b.method("Foo", ("bar",), ("baz",), |_, t, (bar,)| {
            t.foo(bar).map(|baz| (baz,))
        });
    })
}

5. 实现服务端

use dbus::crossroads::Crossroads;
use std::sync::Arc;

mod server;
use server::{OrgExampleTest, register_org_example_test};

struct MyHandler;

impl OrgExampleTest for MyHandler {
    fn foo(&self, bar: i32) -> Result<String, dbus::MethodErr> {
        Ok(format!("收到: {}", bar))
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 创建连接
    let conn = dbus::blocking::Connection::new_session()?;
    
    // 创建Crossroads实例
    let mut cr = Crossroads::new();
    
    // 注册接口
    let token = register_org_example_test(&mut cr);
    
    // 创建handler
    let handler = Arc::new(MyHandler);
    
    // 插入路径和handler
    cr.insert("/org/example/test", &[token], handler);
    
    // 开始处理请求
    conn.request_name("org.example.test", false, true, false)?;
    cr.serve(&conn)?;
    
    Ok(())
}

使用方法

安装工具

cargo install dbus-codegen

生成代码

dbus-codegen-rust < mydefinition.xml > mod.rs

从运行中服务获取定义

dbus-codegen-rust -s -d org.freedesktop.PolicyKit1 -p "/org/freedesktop/PolicyKit1/Authority" > policykit.rs

库使用方式

let opts = Default::default();
let code = dbus_codegen::generate(xml_str, &opts)?;

特性

默认启用dbus特性。使用--no-default-features参数可关闭它,这样就不需要安装D-Bus C开发头文件,但也无法从运行中程序获取xml定义。


1 回复

Rust D-Bus代码生成库dbus-codegen的使用指南

概述

dbus-codegen是一个用于自动生成Rust绑定与接口代码的工具,可以简化D-Bus通信的实现过程。它能够从D-Bus接口定义(XML文件)生成Rust代码,省去了手动编写大量样板代码的工作。

安装

首先需要将dbus-codegen作为开发依赖添加到项目中:

[build-dependencies]
dbus-codegen = "0.9"

基本使用方法

1. 准备D-Bus接口定义文件

创建一个XML文件(例如com.example.MyInterface.xml)定义你的D-Bus接口:

<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="com.example.MyInterface">
    <method name="SayHello">
      <arg name="name" type="s" direction="in"/>
      <arg name="greeting" type="s" direction="out"/>
    </method>
    <signal name="SomethingHappened">
      <arg name="message" type="s"/>
    </signal>
    <property name="Version" type="s" access="read"/>
    </interface>
</node>

2. 生成Rust代码

创建一个构建脚本(build.rs)来生成代码:

use std::path::Path;

fn main() {
    let iface_file = Path::new("src/com.example.MyInterface.xml");
    let out_file = Path::new(&std::env::var("OUT_DIR").unwrap()).join("dbus_codegen.rs");
    
    let config = dbus_codegen::GenOpts {
        methodtype: None,
        skip: None,
        connectiontype: dbus_codegen::ConnectionType::Blocking,
        ..Default::default()
    };
    
    dbus_codegen::generate(iface_file, &config)
        .unwrap()
        .write_to_file(&out_file)
        .unwrap();
}

3. 在项目中使用生成的代码

在你的Rust代码中引入生成的模块:

include!(concat!(env!("OUT_DIR"), "/dbus_codegen.rs"));

fn main() {
    // 使用生成的代码
    let conn = dbus::blocking::Connection::new_session().unwrap();
    let proxy = conn.with_proxy("com.example.MyService", "/", std::time::Duration::from_millis(5000));
    
    // 调用方法
    let (greeting,): (String,) = proxy.method_call("com.example.MyInterface", "SayHello", ("Rust",)).unwrap();
    println!("{}", greeting);
    
    // 监听信号
    proxy.match_signal(|signal: SomethingHappened| {
        println!("Something happened: {}", signal.message);
        true
    });
}

高级用法

使用异步连接

如果你需要使用异步D-Bus连接,可以在生成配置中指定:

let config = dbus_codegen::GenOpts {
    connectiontype: dbus_codegen::ConnectionType::Nonblocking,
    ..Default::default()
};

自定义方法类型

你可以自定义生成的方法类型:

let config = dbus_codegen::GenOpts {
    methodtype: Some("MyMethodType".to_string()),
    ..Default::default()
};

生成特质(Trait)

dbus-codegen还可以生成特质,方便实现服务端:

let config = dbus_codegen::GenOpts {
    gen_trait: true,
    ..Default::default()
};

然后你可以实现这个特质:

struct MyInterfaceImpl;

impl generated::OrgExampleMyInterface for MyInterfaceImpl {
    fn say_hello(&self, name: &str) -> Result<String, dbus::MethodErr> {
        Ok(format!("Hello, {}!", name))
    }
}

实际示例

服务端实现

use dbus::tree;

include!(concat!(env!("OUT_DIR"), "/dbus_codegen.rs"));

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let conn = dbus::blocking::Connection::new_session()?;
    let factory = tree::Factory::new_fn::<()>();
    
    let iface = generated::org_example_my_interface_server(&factory, ());
    
    let tree = factory.tree(()).add(
        factory.object_path("/", ()).add(iface)
    );
    
    tree.start_receive(&conn);
    
    loop {
        conn.process(std::time::Duration::from_millis(1000))?;
    }
}

客户端调用

include!(concat!(env!("OUT_DIR"), "/dbus_codegen.rs"));

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let conn = dbus::blocking::Connection::new_session()?;
    let proxy = conn.with_proxy("com.example.MyService", "/", std::time::Duration::from_millis(5000));
    
    // 调用方法
    let greeting = proxy.say_hello("World")?;
    println!("{}", greeting);
    
    // 读取属性
    let version = proxy.version()?;
    println!("Service version: {}", version);
    
    Ok(())
}

完整示例demo

下面是一个完整的D-Bus服务端和客户端交互示例:

  1. 首先创建Cargo.toml文件:
[package]
name = "dbus-example"
version = "0.1.0"
edition = "2021"

[dependencies]
dbus = { version = "0.9", features = ["tokio"] }

[build-dependencies]
dbus-codegen = "0.9"
  1. 创建接口定义文件src/com.example.Greeter.xml:
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="com.example.Greeter">
    <method name="Greet">
      <arg name="name" type="s" direction="in"/>
      <arg name="response" type="s" direction="out"/>
    </method>
    <signal name="Greeted">
      <arg name="name" type="s"/>
    </signal>
  </interface>
</node>
  1. 创建构建脚本build.rs:
use std::path::Path;

fn main() {
    let iface_file = Path::new("src/com.example.Greeter.xml");
    let out_file = Path::new(&std::env::var("OUT_DIR").unwrap()).join("dbus_codegen.rs");
    
    let config = dbus_codegen::GenOpts {
        gen_trait: true,
        connectiontype: dbus_codegen::ConnectionType::Blocking,
        ..Default::default()
    };
    
    dbus_codegen::generate(iface_file, &config)
        .unwrap()
        .write_to_file(&out_file)
        .unwrap();
}
  1. 服务端实现src/server.rs:
include!(concat!(env!("OUT_DIR"), "/dbus_codegen.rs"));

use dbus::tree;

struct GreeterImpl;

impl generated::OrgExampleGreeter for GreeterImpl {
    fn greet(&self, name: &str) -> Result<String, dbus::MethodErr> {
        println!("Received greeting request for: {}", name);
        Ok(format!("Hello, {}!", name))
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let conn = dbus::blocking::Connection::new_session()?;
    let factory = tree::Factory::new_fn::<()>();
    
    let iface = generated::org_example_greeter_server(&factory, GreeterImpl);
    
    let tree = factory.tree(()).add(
        factory.object_path("/com/example/Greeter", ()).add(iface)
    );
    
    tree.start_receive(&conn);
    
    println!("Greeter service started");
    
    loop {
        conn.process(std::time::Duration::from_millis(1000))?;
    }
}
  1. 客户端实现src/client.rs:
include!(concat!(env!("OUT_DIR"), "/dbus_codegen.rs"));

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let conn = dbus::blocking::Connection::new_session()?;
    let proxy = conn.with_proxy(
        "com.example.Greeter", 
        "/com/example/Greeter", 
        std::time::Duration::from_millis(5000)
    );
    
    // 调用greet方法
    let response = proxy.greet("Rust D-Bus")?;
    println!("Server responded: {}", response);
    
    // 监听Greeted信号
    proxy.match_signal(|signal: generated::Greeted| {
        println!("Received Greeted signal for: {}", signal.name);
        true
    });
    
    Ok(())
}
  1. 运行示例:

首先在一个终端运行服务端:

cargo run --bin server

然后在另一个终端运行客户端:

cargo run --bin client

注意事项

  1. 生成的代码依赖于dbus crate,确保你的Cargo.toml中包含它
  2. 对于复杂的接口,生成的代码可能会很大,考虑将其放在单独的模块中
  3. 如果接口定义发生变化,需要重新运行构建脚本以更新生成的代码

dbus-codegen极大地简化了在Rust中使用D-Bus的过程,通过自动生成绑定代码,让你可以专注于业务逻辑的实现。

回到顶部