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 仍执行严格输出签名校验,包括缺失签名。