proxy — 对象代理
概述
mc/engine/proxy.h 提供访问本地或远程对象的统一代理机制:属性读取固定为“进程级 SHM 属性源 → 绑定
service_proto 的消息 RPC”两级。单属性、指定属性集合和全属性读取共用该调度层;proxy 只负责缓存状态和
对象消息,不拥有 D-Bus、旧消息队列或外部宿主。
调用形态(同步/异步)在工厂/类型层一次性确定:get_async_proxy 返回 mc::future,get_sync_proxy 阻塞等待并
直接返回值。get_proxy 是 get_sync_proxy 的别名。
主要特性
-
元数据支持有界、调用方限定生命周期的共享缓存;同一对象并发 Introspect 合并为一次请求,等待者独立取消。
-
resolve_call只返回调用所需签名,避免每次调用复制输出参数名和注解。 -
模式在工厂层确定:typed proxy 类模板化(单
ProxyMode参数),async_mode实例所有调用返回mc::future<R>,sync_mode实例直接返回R;.get()不再散落在调用点;默认形态为sync_mode -
属性读写显式命名:
value()读、set_value(v)写,与方法调用形式区分,阻塞/异步语义随ProxyMode实例化 -
形态互转:
as_sync_proxy()/as_async_proxy()同 seed 重绑(O(1)、无共享状态);dynamic 层object_proxy::as_sync()/sync_object_proxy::as_async() -
属性源抽象(
proxy_property_source):进程内唯一的 SHM 快路径读取源,按编译形态注入(new shm 直读 / old shm tree / 无 SHM 为空),proxy 不感知具体形态 -
指定属性集合读取:
get_properties在同一属性源快照中批量读取命中项,只对缺失项发一次带当前 mcengine context 的GetPropertiesByNames;旧服务不支持该方法时一次性回退标准GetAll -
批量源 ABI 兼容:
proxy_property_batch_source是与既有proxy_property_source分离的可选能力,旧二进制实现保持原 vtable,未实现批量能力的源由 proxy 兼容为逐项源读取 -
两级读取序:源
hit直接返回;nocache/cache_miss在对象 owner 已知时发送 Properties.Get 消息 -
transport 统一:所有消息都经构造 proxy 时绑定的 service;原生 mcapp 和嵌入旧应用使用各自已有的
service_proto -
GetAll 混合补齐:
complete模式下源命中对象时批量直读 + 仅对 miss/nocache 属性发消息补齐;对象整体 miss 退化为一次 GetAll 消息 -
单跳结果映射:typed 方法/属性的消息路径,以及
set_property/ GetAll 回退,把method_return直接映到目标类型;框架内部转换在 transport 完成线程当场执行,返回 Future 仍保留原 executor,用户 continuation 不会落到 transport 线程 -
本地/远程统一构造:
make_proxy_seed(本地 object_table)→make_remote_seed(远程 + default sender 契约)→make_object_proxy一站式回退 -
typed proxy 宏:
MC_INTERFACE_PROXY/MC_PROXY_PROP/MC_PROXY_METHOD生成 IDL 风格客户端类,返回类型随ProxyMode实例化 -
远端错误保真:远端 error 响应以
mc::error_exception抛出,保留错误名 -
透明成员解析(
proxy_member_table):按客户端声明的接口集合解析成员种类(属性/方法/signal)+ 所属接口名 + 接口内原始成员名,供透明代理区分p.prop与p:method();A 路从runtime_interface_metadata登记声明接口、B 路解析 Introspect XML,二者统一由resolve消费,不依赖远端对象实例
基本用法
typed proxy(宏定义客户端类,模板参数名约定为 ProxyMode)
#include <mc/engine/proxy.h>
template <typename ProxyMode>
class chip_proxy : public mc::engine::interface_proxy<chip_proxy, ProxyMode> {
public:
MC_INTERFACE_PROXY("bmc.dev.Chip");
MC_PROXY_PROP(uint32_t, Address);
MC_PROXY_METHOD(std::vector<uint8_t>, BitIORead,
((uint32_t, offset))((uint8_t, length, "y"))((uint32_t, mask)));
MC_PROXY_METHOD(int32_t, GetVersion); // 无参方法
};
void use(mc::engine::service& svc)
{
// 同步形态(默认):调用点无 .get(),所有调用阻塞等待
// get_proxy 即 get_sync_proxy 的别名
auto proxy = svc.get_proxy<chip_proxy>("/bmc/dev/chip0", "bmc.kepler.devmon");
if (proxy == nullptr) {
return;
}
uint32_t addr = proxy->Address.value(); // 直接返回值
proxy->Address.set_value(0x2000U); // 阻塞到写完成
auto bytes = proxy->BitIORead(0U, uint8_t{4}, 0xffU); // 直接返回值
// 异步形态:所有调用返回 mc::future
auto async_proxy = svc.get_async_proxy<chip_proxy>("/bmc/dev/chip0", "bmc.kepler.devmon");
async_proxy->GetVersion().then([](int32_t version) {
ilog("version=${v}", ("v", version));
});
// 互转:同 seed 重绑
auto sync_again = async_proxy->as_sync_proxy();
auto version = sync_again->GetVersion();
}
对象级聚合 proxy(多接口对象)
template <typename ProxyMode>
class chip_obj_proxy : public mc::engine::proxy_object<chip_obj_proxy, ProxyMode> {
public:
chip_proxy<ProxyMode> chip;
void bind_interfaces(const mc::engine::object_proxy_seed& seed)
{
chip.bind_proxy(seed);
}
};
void use_object(mc::engine::service& svc)
{
auto proxy = svc.get_proxy<chip_obj_proxy>("/bmc/dev/chip0"); // 默认同步形态
auto addr = proxy->chip.Address.value();
auto back = proxy->as_async_proxy(); // 对象级互转
}
dynamic proxy(运行期接口名)
#include <mc/engine/proxy.h>
void dynamic_use(mc::engine::service& svc)
{
mc::engine::object_ref ref;
ref.service = "org.example.Svc";
ref.path = "/org/example/obj0";
// 异步形态(默认)
mc::engine::object_proxy proxy(&svc, ref);
proxy.invoke("org.example.Iface", "Add", mc::variants{int32_t{1}, int32_t{2}}, "ii")
.then([](const mc::variant& result) {
ilog("sum=${s}", ("s", result));
});
// 同步形态视图:调用点无 .get()
auto sync = proxy.as_sync();
auto value = sync.get_property("org.example.Iface", "Counter");
sync.set_property("org.example.Iface", "Counter", mc::variant(int32_t{10}));
// 指定属性集合:SHM 命中项批量直读,缺失项一次消息补齐
auto selected = sync.get_properties("org.example.Iface", {"Counter", "Enabled"});
// complete 模式:源批量直读 + miss/nocache 属性经消息补齐
auto all = sync.get_all_properties("org.example.Iface");
}
seed 工厂与远程回退
#include <mc/engine/proxy.h>
void seeds(mc::engine::service& svc)
{
// 本地命中 → 本地 seed;miss 且有 service_hint → 远程 seed(sender 缺省为
// engine 的 default proxy sender,即本进程第一个注册的 service)
auto proxy = mc::engine::make_object_proxy("/org/example/obj0", "org.example.Svc");
if (proxy.has_value()) {
auto v = proxy->get_property("org.example.Iface", "Counter").get();
}
// 显式指定 sender 的远程 seed
auto seed = mc::engine::make_remote_seed("/remote/path", "remote.Svc", {}, &svc);
}
自定义属性源
SHM 树形态(use_old_shm=true 构建)无需手写:mcapp 的 service_backend::install 以及嵌入宿主的 mc::app::install_shm_data_sources() 会自动以 mc::dbus::shm_tree::try_read_property/try_read_properties 为底座注册旧系统宿主的进程级属性源。数据源可先于进程附着 SHM 安装,首次读取时延迟附着;宿主安装动作会替换此前注册的其他形态属性源。以下示例展示如何接入其他自定义形态:
#include <mc/engine/engine.h>
#include <mc/engine/proxy.h>
class my_source : public mc::engine::proxy_property_source {
public:
mc::engine::property_read_result read(const mc::engine::object_ref& ref, mc::string_view iface,
mc::string_view prop, const mc::engine::proxy_policy& policy) override
{
// 命中返回 {hit, value};不可缓存返回 {nocache, {}};找不到返回 {miss, {}}
return {};
}
mc::engine::property_read_all_result read_all(const mc::engine::object_ref& ref, mc::string_view iface,
const mc::engine::proxy_policy& policy) override
{
return {};
}
};
// 可选:需要在一个快照/锁周期内读取指定集合时,同时实现该独立接口。
class my_batch_source : public my_source, public mc::engine::proxy_property_batch_source {
public:
mc::engine::property_read_many_result
read_many(const mc::engine::object_ref& ref, mc::string_view iface,
const std::vector<mc::string>& properties, const mc::engine::proxy_policy& policy) override
{
return {};
}
};
void install()
{
mc::engine::engine::set_proxy_property_source(mc::make_shared<my_source>());
}
API 详细说明
枚举与配置
| 类型 | 取值 | 说明 |
|---|---|---|
proxy_route |
auto_ / no_cache |
auto_ 尊重属性标记走源快路径;no_cache 全部走消息 |
proxy_message_route |
auto_ / mq / dbus |
消息通道选择,写入 mcapp.source 供 app_proto 路由 |
proxy_context_mode |
disabled / enabled |
是否允许代理为明确支持内部 context 协议的下游方法追加 wire context,默认关闭 |
proxy_get_all_mode |
complete / fast_available_only |
complete 源直读 + 消息补齐;fast_available_only 仅返回源中立即可读属性 |
property_read_status |
cache_miss / nocache / hit |
属性缓存读取状态;miss 仅为 cache_miss 的兼容别名 |
struct object_ref
| 字段 | 类型 | 说明 |
|---|---|---|
service |
mc::string |
目标 service 名 |
path |
mc::string |
对象路径 |
endpoint_id / instance_id |
std::optional<uint16_t/uint32_t> |
MQ 定向路由目标(成对存在时写入消息 context) |
object_id |
std::optional<uint64_t> |
对象 id,源解析时精确匹配 |
service_epoch |
std::optional<uint64_t> |
owner service 纪元,源读取时校验防止读到重启前的旧数据 |
struct proxy_policy
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
route |
proxy_route |
auto_ |
属性读取路由 |
message_route |
proxy_message_route |
auto_ |
消息通道 |
timeout |
mc::milliseconds |
5000 | RPC 超时 |
allow_stale_read |
bool |
false | true 时跳过 service_epoch 校验 |
class proxy_property_source
| 方法 | 参数 | 返回值 | 异常 | 线程安全 | 说明 |
|---|---|---|---|---|---|
read |
ref, interface_name, property_name, policy |
property_read_result |
实现自定(默认源不抛) | 实现自定(默认源安全) | 读单个属性,返回 hit/nocache/cache_miss |
read_all |
ref, interface_name, policy |
property_read_all_result(object_found + values + missing) |
实现自定(默认源不抛) | 实现自定(默认源安全) | 批量读,missing 为需消息补齐的属性名 |
// 默认源:new shm 形态直读 mcengine 自有 SHM 属性槽;无 SHM 形态返回空
MC_API proxy_property_source_ptr make_default_proxy_property_source();
engine 级注册(见 mc/engine/engine.h):
| 方法 | 参数 | 返回值 | 异常 | 线程安全 | 说明 |
|---|---|---|---|---|---|
engine::get_proxy_property_source |
无 | mc::shared_ptr<proxy_property_source> |
不抛 | 安全 | 未显式设置时按编译形态惰性安装默认源 |
engine::set_proxy_property_source |
source |
void |
不抛 | 安全 | 替换进程级属性源(可设为空禁用快路径) |
engine::get_default_proxy_sender |
无 | const service* |
不抛 | 安全 | 默认为本进程第一个注册的 service |
engine::set_default_proxy_sender |
svc |
void |
不抛 | 安全 | 覆盖默认 sender |
默认 SHM 属性源使用对象接口元数据恢复整数槽的 D-Bus 标量类型(包括 bool 和各宽度整数),并通过
engine message 的类型保真编码保存 array/dict。空槽、类型不匹配、无法稳定编码的扩展类型和 nocache
属性均视为未完整命中并回退消息路径,使源命中与消息读取保持相同的 mc::variant 类型契约。
模式标签
| 类型 | result_t<T> |
说明 |
|---|---|---|
async_mode |
mc::future<T> |
异步形态,调用立即返回 future |
sync_mode |
T |
同步形态(默认模板参数);远端方法直接进入 transport 同步扩展点,进程内方法保留 Future 自驱动等待 |
class object_proxy
出站 RPC 预算传播
proxy_policy 保持既有 ABI,不包含上下文传播开关。C++ 调用方需要明确协议支持时,使用
object_proxy::invoke_with_context() 或 detail::build_method_call(..., proxy_context_mode::enabled);
普通接口仍使用原有调用。Lua 代理的 { context_mode = "enabled" } 只保存在 Lua 代理对象中,
不会改变 C++ proxy_policy 的布局。
这些显式传播接口接收纯业务参数;调用方已经构造 wire context 首参时应继续使用普通调用接口, 避免同一请求出现两个上下文首参。
invoke_resolved_with_context() 首次调用需要异步获取 Introspect 时,框架会为这一步显式绑定调用时的
上下文快照与准入身份。后续业务请求沿用原有绝对 deadline,等待 Introspect 的时间计入预算;
成员表已缓存时遵循相同规则。业务自身创建的异步回调仍需按需使用 bind_context()。
普通 proxy 调用默认不携带上下文首参,与改动前一致。只有显式选择上下文传播的
invoke_with_context() 或 build_method_call(..., proxy_context_mode::enabled),并且当前执行上下文处在一条
已声明预算的 RPC 链路上(context::remaining_rpc_budget() 有值)时,才会在业务参数前追加一个上下文 dict,
并把线型签名扩成 a{sv} + 原业务签名;该 dict 中的 TimeoutMs 是剩余预算(毫秒;Ms 后缀即单位,
只此一种形式、不存在秒版本),因此下游拿到的是上游还剩多少,实现逐跳递减。
三条边界(都有回归护栏):没有当前上下文、当前上下文没有预算(后台任务、timer 回调)时
不追加;预算已耗尽时不追加,下游回到各自默认策略;接收侧对显式声明 context 与
隐式可选 context 的方法都能识别该首参。自带上下文首参的路径(属性批量补读、
get_all_properties_with_context)经 detail::forwarded_context_arg() 构造首参,预算耗尽时
抹掉 TimeoutMs 下传——与「不追加」同一语义:下游不继承任何预算,尤其不能继承客户端
原始声明值(下游按它重新起算,已耗尽的预算会被复活并放大)。
还有两条「不追加」的护栏,避免把上下文首参加到接收侧无法解释的调用上:
- 调用方已自带上下文首参(
a{sv}:属性批量补读get_properties/get_all_properties_with_context;a{ss}:MDB 查询mdb_resolver与 discovery 对象组 拉取):再追加会变成双份首参,接收侧的参数个数校验判invalid_args,或按位置解析时 直接错读。a{sv}路径的首参同样携带剩余预算,因此逐跳递减对补读一样成立;a{ss}路径的上下文由调用方构造,预算不传播。 - 目标是
org.freedesktop.DBus.*标准接口:接收侧把实参按位置原样交给反射层,不识别 上下文首参,追加会让第一个形参类型不匹配。标准接口 handler 是框架内部实现,不需要下传预算。
| 方法 | 参数 | 返回值 | 异常 | 线程安全 | 说明 |
|---|---|---|---|---|---|
| 构造 | svc, ref, policy = {} |
— | 不抛 | — | 不持有 svc 生命周期,调用方保证 svc 存活 |
ref / policy |
无 | const object_ref& / const proxy_policy& |
不抛 | 安全 | 访问器 |
get_property |
interface_name, property_name |
mc::future<mc::variant> |
future 携带 mc::error_exception(远端错误)/ mc::method_call_exception(协议错误) |
安全 | 源命中直接 resolved future,否则消息 |
get_all_properties_with_context |
interface_name |
mc::future<proxy_properties_result> |
源读取失败、全部 RPC 失败 | 安全(依赖属性源契约) | 一次源快照与上下文补读,保留部分错误 |
get_properties |
interface_name, property_names, mode = complete |
mc::future<proxy_properties_result> |
同上 | 安全 | 指定集合批量源读取;缺失项一次带当前 context 的 RPC 补齐,返回 values/errors |
get_all_properties |
interface_name, mode = complete |
mc::future<mc::dict> |
同上 | 安全 | complete 混合补齐;fast 仅源子集 |
set_property |
interface_name, property_name, value |
mc::future<void> |
同上 | 安全 | Properties.Set 消息 |
invoke |
interface_name, method_name, args, signature, result_signature |
mc::future<mc::variant> |
同上;结果签名不匹配 mc::method_call_exception |
安全 | 通用方法调用 |
invoke_with_context |
interface_name, method_name, args, signature, result_signature |
mc::future<mc::variant> |
同上 | 安全 | 明确允许下游方法接收框架 wire context 后调用 |
invoke_resolved |
interface_name, method_name, args, members |
mc::future<mc::variant> |
同上;Introspect 未返回目标方法时 mc::method_call_exception |
安全(成员表内部同步) | 先查本地接口元数据;未命中时通过当前消息通道执行 Introspect,将输入/输出签名写入共享成员表,并以精确输入签名调用;输出签名保留给多返回值展开,不要求已解码的外部 transport 回填线级签名 |
invoke_resolved_with_context |
interface_name, method_name, args, members |
mc::future<mc::variant> |
同上 | 安全 | 元数据解析后调用,并明确允许下游接收框架 wire context |
as_sync |
无 | sync_object_proxy |
不抛 | 安全 | 同 seed 的同步形态视图 |
as<ProxyType> |
模板 | mc::shared_ptr<ProxyType> |
mc::method_call_exception(seed 无效) |
安全 | 绑定 typed proxy(完整实例化类型) |
异步形态下同步语义 = .get(),禁止在 service strand / io 线程上 .get()(同样适用于 sync 形态的全部调用)。
class sync_object_proxy
| 方法 | 参数 | 返回值 | 异常 | 线程安全 | 说明 |
|---|---|---|---|---|---|
| 构造 | object_proxy 或 svc, ref, policy = {} |
— | 不抛 | — | 包装同 seed 的 object_proxy |
get_property |
interface_name, property_name |
mc::variant |
mc::error_exception / mc::method_call_exception 直接抛出 |
安全 | 阻塞读 |
get_properties |
interface_name, property_names, mode = complete |
proxy_properties_result |
同上 | 安全 | 阻塞读取指定属性集合 |
get_all_properties |
interface_name, mode = complete |
mc::dict |
同上 | 安全 | 阻塞批量读 |
set_property |
interface_name, property_name, value |
void |
同上 | 安全 | 阻塞写 |
invoke |
interface_name, method_name, args, signature, result_signature |
mc::variant |
同上 | 安全 | 阻塞调用;远端目标不创建中间 reply Future,进程内目标保留线程池自驱动语义 |
as_async |
无 | const object_proxy& |
不抛 | 安全 | 取回异步形态 |
class interface_proxy_context
| 方法 | 参数 | 返回值 | 异常 | 线程安全 | 说明 |
|---|---|---|---|---|---|
bind |
seed, interface_name |
void |
mc::method_call_exception(seed 无效) |
否(绑定期单线程) | 固定 interface 视图 |
is_bound / ref / policy / interface_name |
无 | 状态访问器 | 不抛 | 安全 | 未绑定时 ref/policy 返回空对象 |
get_property / get_properties / set_property / get_all_properties / invoke |
同 object_proxy 去掉 interface 参数 |
mc::future<...> |
未绑定时同步抛 mc::method_call_exception;其余同 object_proxy |
安全 | 委托内部 object_proxy |
class property_proxy<T, ProxyMode>
| 方法 | 参数 | 返回值 | 异常 | 线程安全 | 说明 |
|---|---|---|---|---|---|
value |
无 | ProxyMode::result_t<T>(async:mc::future<T>;sync:T) |
mc::bad_cast_exception(类型不符)等,async 经 future 携带,sync 直接抛 |
安全 | 读取并转换 |
set_value |
value |
ProxyMode::result_t<void> |
同 set_property |
安全 | 写入 |
name |
无 | mc::string_view |
不抛 | 安全 | 属性名 |
无隐式转换/调用语法(operator T / operator= / operator() 均不提供):读写显式拼写 value() / set_value(),与方法调用区分,形态由 Mode 编译期确定。
class interface_proxy<DerivedT, ProxyMode> / proxy_object<DerivedT, ProxyMode>
DerivedT 为类模板本身(模板模板参数),如 interface_proxy<chip_proxy, ProxyMode>。
| 方法 | 参数 | 返回值 | 异常 | 线程安全 | 说明 |
|---|---|---|---|---|---|
bind_proxy |
seed |
void |
mc::method_call_exception(seed 无效) |
否(绑定期单线程) | interface_proxy 绑定 context;proxy_object 转发派生类 bind_interfaces |
is_bound / seed |
无 | bool / const object_proxy_seed& |
不抛 | 安全 | 状态访问器 |
as_sync_proxy |
无 | mc::shared_ptr<DerivedT<sync_mode>> |
不抛 | 安全 | 同 seed 重绑同步形态 |
as_async_proxy |
无 | mc::shared_ptr<DerivedT<async_mode>> |
不抛 | 安全 | 同 seed 重绑异步形态 |
interface_proxy / proxy_object 均继承 mc::shared_base(侵入式引用计数),工厂统一返回 mc::shared_ptr:异步形态下 proxy 必须活到回复到达,将其捕获进续延即可延长生命周期;栈上手动构造仍然允许(未被 shared_ptr 管理的对象不参与计数)。
seed 工厂函数
MC_API object_proxy_seed make_proxy_seed(mc::string_view path, proxy_policy policy = {});
MC_API object_proxy_seed make_proxy_seed(mc::string_view path, mc::string_view service_hint, proxy_policy policy = {});
MC_API object_proxy_seed make_remote_seed(mc::string_view path, mc::string_view service_hint, proxy_policy policy = {},
const service* sender = nullptr);
MC_API std::optional<object_proxy> make_object_proxy(mc::string_view path, mc::string_view service_hint = {},
proxy_policy policy = {});
| 函数 | 参数说明 | 返回值 | 异常 |
|---|---|---|---|
make_proxy_seed(path) |
本地 object_table 按路径查找 | 命中返回有效 seed,miss 返回 invalid | 不抛 |
make_proxy_seed(path, hint) |
本地查找且 service 名匹配 hint | 同上 | 不抛 |
make_remote_seed |
sender 为空时取 engine default sender;hint 为空或无 sender 返回 invalid |
远程 seed | 不抛 |
make_object_proxy |
本地命中→本地 seed;miss 且有 hint→远程 seed;均失败→std::nullopt |
std::optional<object_proxy> |
不抛 |
service 工厂(typed 封装,本地查找失败时以自身为 sender 回退远程 seed)
| 方法 | 模板参数 | 返回值 | 说明 |
|---|---|---|---|
get_async_proxy<ProxyT>(path[, hint]) |
proxy 类模板 | mc::shared_ptr<ProxyT<async_mode>> |
异步形态,所有调用返回 future |
get_sync_proxy<ProxyT>(path[, hint]) |
proxy 类模板 | mc::shared_ptr<ProxyT<sync_mode>> |
同步形态,调用点无 .get() |
get_proxy<ProxyT>(path[, hint]) |
proxy 类模板 | mc::shared_ptr<ProxyT<sync_mode>> |
get_sync_proxy 的别名(同步默认) |
get_proxy<T>(path[, hint]) |
完整实例化类型 | mc::shared_ptr<T> |
显式传 chip_proxy<sync_mode> 等完整类型 |
自由函数 make_async_proxy<ProxyT> / make_sync_proxy<ProxyT> / make_proxy<T> 语义相同(使用 engine default sender)。
宏
| 宏 | 形式 | 说明 |
|---|---|---|
MC_INTERFACE_PROXY(NAME) |
MC_INTERFACE_PROXY("bmc.dev.Chip") |
声明 interface 名 |
MC_PROXY_PROP(TYPE, NAME) |
MC_PROXY_PROP(uint32_t, Address) |
生成 property_proxy<TYPE, ProxyMode> 成员 |
MC_PROXY_METHOD(R, NAME[, SIG][, ARGS]) |
见基本用法 | 生成返回 ProxyMode::result_t<R> 的方法;R 可写 (类型, "签名") 指定返回签名;参数三元组 ((类型, 名, "签名")) 第三项可省略 |
宏内部引用 ProxyMode 标识符,typed proxy 类的模板参数名必须为 ProxyMode。
透明成员解析:enum proxy_member_kind / struct proxy_member_info / class proxy_member_table
供透明代理(如 lmc 的 p.prop / p:method())在访问点区分属性读 / 方法调用 / signal 并定向到具体接口名和接口内原始成员名。解析只依赖客户端声明的接口集合,不查询远端对象实例,因此跨进程远程对象同样适用。
enum class proxy_member_kind { none, property, method, signal };
struct proxy_member_info {
proxy_member_kind kind{proxy_member_kind::none};
mc::string interface_name; // 命中成员所属的接口名
mc::string member_name; // 接口内原始成员名(无 alias 时等于查询名)
};
proxy_member_table 把两条数据来源写入同一张内部映射、由 resolve 和 method_result_arity 统一消费
(消费侧不感知来源):
| 方法 | 参数 | 返回值 | 异常 | 线程安全 | 说明 |
|---|---|---|---|---|---|
add_interface |
mc::string_view interface_name |
void |
无 | 是 | A 路:按已注册的 runtime_interface_metadata 登记该接口全部成员;接口未注册则忽略,幂等 |
load_from_introspect_xml |
mc::string_view xml |
void |
无 | 是 | B 路:解析框架装配的 DBus Introspect XML(格式稳定,字符串匹配)登记其全部接口成员,用于无本地声明的智能代理 |
resolve |
mc::string_view member |
proxy_member_info |
std::invalid_argument(同名成员见于多个声明接口,需显式接口消歧) |
是(const,仅读) | 唯一命中→{kind, iface, member};未登记→{none, "", ""} |
method_result_arity |
interface_name, method |
std::optional<std::size_t> |
仅可能抛内存分配异常 | 是(const,仅读) | 返回方法 D-Bus 顶层返回参数个数;未知接口、未知成员或非方法返回 nullopt,零返回方法返回 0 |
empty |
无 | bool |
无 | 是 | 是否未登记任何成员 |
introspection_loaded |
无 | bool |
无 | 是 | 是否已装载过远端 Introspect XML;用于避免未知成员被反复探测 |
clear |
无 | void |
无 | 是 | 清空已登记成员 |
取代旧的
resolve_object_member(按 path 在本地object_table查询对象实例 metadata,对未在本进程注册的远程对象失效)。A 路为主路(声明式,便于代码生成预登记接口);B 路为兑底(lmc 透明代理在 A 路 miss 时await远端Introspect()取 XML 后调load_from_introspect_xml填表,仅 sync 视图,结果缓存)。introspect 是正式的远端元数据获取途径(非GetManagedObjects)。
最佳实践
✅ 推荐
// 1. 同步语义场景用 get_proxy(默认同步),调用点干净、阻塞语义由类型表达
auto proxy = svc.get_proxy<chip_proxy>("/bmc/dev/chip0");
auto addr = proxy->Address.value();
// 2. 异步链式调用用 get_async_proxy,避免阻塞 strand
auto ap = svc.get_async_proxy<chip_proxy>("/bmc/dev/chip0");
ap->GetVersion().then([](int32_t version) {
ilog("version=${v}", ("v", version));
});
// 3. 批量读属性用 complete 模式(源直读 + 自动补齐),而不是逐个 get_property
auto all = sync.get_all_properties("org.example.Iface");
// 4. 远端错误按错误名分流(异步形态)
proxy.invoke("org.example.Iface", "Apply").catch_error([](const mc::exception& e) {
elog("apply failed: ${what}", ("what", e.what()));
return mc::variant{};
});
❌ 避免
// 1. 不要在 service strand / io 线程上使用 sync 形态或对 future .get()(会死锁派发线程)
svc.strand().post([proxy] {
auto v = proxy->Address.value(); // 错误:sync 形态阻塞派发线程,应改用 async 形态 + .then(...)
});
// 2. 不要在异步调用点零散 .get() 凑同步语义;需要同步就在工厂层用 get_proxy / get_sync_proxy
auto ap = svc.get_async_proxy<chip_proxy>("/bmc/dev/chip0");
auto v = ap->Address.value().get(); // 不推荐:模式应在工厂层确定,而非调用点
// 3. 不要为单个 proxy 自建属性缓存层;属性源是进程级唯一的,
// 重复缓存会引入一致性问题(SHM 已是权威 cache)
class my_proxy_with_cache { mc::dict m_cache; /* 错误设计 */ };
// 4. 不要绕过 seed 工厂手工拼 seed 又遗漏 svc(is_valid() 为 false,bind 抛异常)
mc::engine::object_proxy_seed seed;
seed.ref.path = "/org/example/obj0"; // 错误:缺 svc
proxy->bind_proxy(seed); // 抛 mc::method_call_exception
属性注解
proxy_result_metadata 增加 mc::dict annotations,保留 D-Bus 注解的名称与字符串值。
proxy_member_table::property_metadata(interface, property) 从已登记的接口/模型选项或 Introspect XML
返回同样的属性元数据;未声明属性返回 std::nullopt。XML 注解仅归属所在属性,不从相邻属性或其他接口继承。
已有 name、signature、annotation 字段语义不变。代理不以注解改写原生返回值。
mc::engine::proxy_member_table members;
members.add_interface("demo.ICalc");
if (auto property = members.property_metadata("demo.ICalc", "Total")) {
const auto it = property->annotations.find("org.freedesktop.DBus.Property.Volatile");
const bool is_volatile = it != property->annotations.end() && it->value.as_string() == "true";
(void)is_volatile;
}
get_properties 在已有部分属性值而补读 RPC 失败时,返回已读值,并在 errors 中为未完成属性保留
错误 name/format;完全未读到值时仍拒绝 future。该规则也适用于批量接口不支持而回退 GetAll 后的失败。
正常批量服务对不存在或 explicit 属性的跳过语义不变,客户端不将这些省略项自动变成错误。
元数据缓存与并发加载
| 方法 | 参数 | 返回值 | 异常 | 线程安全 | 说明 |
|---|---|---|---|---|---|---|
| proxy_metadata_cache(size_t capacity = 256, size_t interface_capacity = 1024) | 对象条目、接口定义弱索引容量 | 缓存实例 | 任一容量为零抛 invalid_argument_exception;分配失败 | 构造期间独占 | 对象表使用 LRU;完整内容相同的接口定义通过有界弱索引共享,不保存 XML 或属性值 |
| proxy_metadata_cache::get(service_name, path) | 服务、对象路径 | shared_ptr<proxy_member_table> | 分配失败 | 是 | 同键复用;仅创建代理时查找,后续直接持有成员表 |
| proxy_metadata_cache::invalidate(service_name, path) | 服务、对象路径 | void | 分配失败 | 是 | 移除缓存入口;已持有该表的代理继续使用原快照,新代理得到新表 |
| proxy_metadata_cache::clear() | 无 | void | 分配失败 | 是 | 移除对象入口并创建新的定义池代际;旧代理保留旧表,不取消进行中的请求 |
| proxy_metadata_cache::get_stats() | 无 | stats | 无 | 是 | 当前代际的 objects、indexed_definitions、live_definitions、shared_hits |
| object_proxy::introspect(members) | 共享成员表 | future<bool> | 空表参数抛 invalid_argument_exception;传输和解析错误由 future 返回 | 是 | 同表仅一个加载;成功返回 true,失败下次可重试;一个等待者取消不影响其他等待者 |
| proxy_member_table::resolve_call(member, interface_name = {}) | 成员名、可选接口名 | proxy_member_info | 歧义抛 invalid_argument_exception;分配失败 | 是 | 不填充 result_names/result_metadata;未找到返回 kind=none |
| proxy_member_table::property_is_volatile(interface_name, property) | 接口、属性名 | optional<bool> | 分配失败 | 是 | 未知属性为 nullopt;无 volatile 注解为 false,不复制注解 |
| proxy_member_table::clear() | 无 | void | 加载未完成抛 invalid_op_exception | 是 | 清除成员和接口登记状态;进行中需用缓存 invalidate 隔离新代际 |
#include <mc/engine/proxy.h>
#include <mc/engine/service.h>
int main()
{
mc::engine::service sender("example.client");
mc::engine::proxy_metadata_cache cache(256);
mc::engine::object_ref ref;
ref.service = "example.owner";
ref.path = "/example/object";
mc::engine::object_proxy proxy(&sender, ref);
auto members = cache.get(ref.service, ref.path);
// sender 必须先由宿主绑定 transport;多个 proxy 可复用 members。
auto loaded = proxy.introspect(members);
return 0;
}
✅ 同一请求/对象代际复用成员表,用 resolve_call 执行业务调用,展示时才读取完整 metadata。
✅ 接口定义公开且与调用者无关时,不同调用方 service 可共享同一缓存。传输命名空间保持一致;目标 owner 或接口变化时 invalidate 并重建代理。
❌ 不要跨不同 transport、不同元数据可见权限的用户或服务重启代际直接复用缓存。
❌ 不要将缓存当作属性值快照;属性仍经过 property source/RPC,不能因 metadata 命中而忽略 volatile。
接口登记成功后在成员表内只构建一次。应用应在发布代理前完成接口/模型元数据装配;变更时创建新的成员表。
接口定义共享与内存边界
对象成员表保存接口名到定义的引用。同一个缓存实例内,不同目标 service/path 的接口内容相等时共享定义; 哈希只负责候选查找,最终比较签名、返回参数名和注解,哈希碰撞不会合并不同定义。 本地注册接口与远端 Introspect XML 均可装配定义;调用方 service 不参与共享身份。 已发布定义采用写时复制,补充某个对象的成员不会修改其他对象;限定接口查询直接定位对应定义。 跨进程的缓存实例不会共享物理内存。
stats.objects 是 LRU 中的对象数;indexed_definitions 是弱索引条目数(含尚未清扫的过期条目);
live_definitions 是这些索引中仍存活的定义数;shared_hits 是装配时复用已有定义的次数。
这些计数不是字节数,也不包含已被弱索引淘汰或旧代际仍被代理持有的定义。
弱索引默认 1024 条,不强持有定义;索引淘汰只影响后续去重效率。对象容量默认 256,均为每实例限制。
已有代理仍可持有被淘汰的表,故容量不是进程总内存硬上限。测试清空后应释放旧代理,才能模拟完整冷启动。
带上下文的完整属性读取
object_proxy::get_all_properties_with_context(interface_name) 返回
mc::future<proxy_properties_result>,保留 values 和逐属性 errors。命中属性源时复用一次 read_all
快照,仅将缺失名称交给批量上下文 RPC;完全未命中时调用 GetAllWithContext。
部分补读失败保留已读值和错误;完全失败拒绝 Future。它与 get_properties 共用批量补读和旧服务回退路径。
已有 get_all_properties 的标准 D-Bus Properties 语义保持不变。
动态 invoke_resolved 将内省签名用于编码输入和提供输出元数据,不要求已解码的外部响应补充线级签名。 显式 invoke 的 result_signature 参数以及 typed proxy 仍执行严格输出签名校验,包括缺失签名。