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定义。
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服务端和客户端交互示例:
- 首先创建
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"
- 创建接口定义文件
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>
- 创建构建脚本
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();
}
- 服务端实现
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))?;
}
}
- 客户端实现
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(())
}
- 运行示例:
首先在一个终端运行服务端:
cargo run --bin server
然后在另一个终端运行客户端:
cargo run --bin client
注意事项
- 生成的代码依赖于
dbus
crate,确保你的Cargo.toml
中包含它 - 对于复杂的接口,生成的代码可能会很大,考虑将其放在单独的模块中
- 如果接口定义发生变化,需要重新运行构建脚本以更新生成的代码
dbus-codegen
极大地简化了在Rust中使用D-Bus的过程,通过自动生成绑定代码,让你可以专注于业务逻辑的实现。