sdbus-c++中文版使用说明(三)——便捷层API


  便捷 API 完全抽象了底层 D-Bus 消息和 D-Bus 数据类型,在编译时推断和解析了很多东西,与基本层相比,运行时性能成本接近于零。因此,使用便捷 API 编写的代码更具表现力、在更高的抽象层次上(更接近所要解决问题的抽象层次)、更短更简洁、几乎与使用基本 API 层编写的一样快。
  使用此层编写的代码以声明的方式表达了它做什么,而不是如何做。

1. 连接器的实现示例

1.1 同步Server端

#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>

int main(int argc, char *argv[])
{
    // Create D-Bus connection to the (either system or session) bus and requests a well-known name on it.
    sdbus::ServiceName serviceName{"org.sdbuscpp.concatenator"};
    auto connection = sdbus::createBusConnection(serviceName);

    // Create concatenator D-Bus object.
    sdbus::ObjectPath objectPath{"/org/sdbuscpp/concatenator"};
    auto concatenator = sdbus::createObject(*connection, std::move(objectPath));

    auto concatenate = [&concatenator](const std::vector<int> numbers, const std::string& separator)
    {
        // Return error if there are no numbers in the collection
        if (numbers.empty())
            throw sdbus::Error(sdbus::Error::Name{"org.sdbuscpp.Concatenator.Error"}, "No numbers provided");

        std::string result;
        for (auto number : numbers)
        {
            result += (result.empty() ? std::string() : separator) + std::to_string(number);
        }

        // Emit 'concatenated' signal
        concatenator->emitSignal("concatenated").onInterface("org.sdbuscpp.Concatenator").withArguments(result);

        return result;
    };

    // Register D-Bus methods and signals on the concatenator object, and exports the object.
    concatenator->addVTable(sdbus::registerMethod("concatenate").implementedAs(std::move(concatenate)),
                            sdbus::registerSignal("concatenated").withParameters<std::string>())
                           .forInterface("org.sdbuscpp.Concatenator");

    // Run the loop on the connection.
    connection->enterEventLoop();
}

提示:addVTable(...).forInterface()方法还有一个带有参数return_slot_t的重载,返回一个插槽对象。

1.2 同步Client端

#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>
#include <iostream>
#include <unistd.h>

void onConcatenated(const std::string& concatenatedString)
{
    std::cout << "Received signal with concatenated string " << concatenatedString << std::endl;
}

int main(int argc, char *argv[])
{
    // Create proxy object for the concatenator object on the server side
    sdbus::ServiceName destination{"org.sdbuscpp.concatenator"};
    sdbus::ObjectPath objectPath{"/org/sdbuscpp/concatenator"};
    auto concatenatorProxy = sdbus::createProxy(std::move(destination), std::move(objectPath));

    // Let's subscribe for the 'concatenated' signals
    sdbus::InterfaceName interfaceName{"org.sdbuscpp.Concatenator"};
    concatenatorProxy->uponSignal("concatenated").onInterface(interfaceName).call([](const std::string& str){ onConcatenated(str); });

    std::vector<int> numbers = {1, 2, 3};
    std::string separator = ":";

    // Invoke concatenate on given interface of the object
    {
        std::string concatenatedString;
        concatenatorProxy->callMethod("concatenate").onInterface(interfaceName).withArguments(numbers, separator).storeResultsTo(concatenatedString);
        assert(concatenatedString == "1:2:3");
    }

    // Invoke concatenate again, this time with no numbers and we shall get an error
    {
        try
        {
            concatenatorProxy->callMethod("concatenate").onInterface(interfaceName).withArguments(std::vector<int>(), separator);
            assert(false);
        }
        catch(const sdbus::Error& e)
        {
            std::cerr << "Got concatenate error " << e.getName() << " with message " << e.getMessage() << std::endl;
        }
    }

    // Give sufficient time to receive 'concatenated' signal from the first concatenate invocation
    sleep(1);

    return 0;
}

Client端代码解读:
  便捷层API的实现与前文基本层相比,在注册方法、调用方法或发出信号时,多行代码已缩减为简单的一行代码,回调函数类型、相关参数及其类型检查,以及 D-Bus 消息的序列化和反序列化完全由编译器生成。
提示:uponSignal(...).call()信号订阅函数还有个带参数return_slot_t的重载,返回一个插槽对象。

  根据官方建议,更希望用户使用在基本层API之上的便捷层API,当然最佳情况下应该使用还在便捷层之上的C++绑定层API,这样执行一个远程回调就会像在本地调用一个对象的函数一样简单,详见sdbus-c++中文版使用说明(四)——C++绑定层API

注意:如何调试信号处理函数的回调?
  默认情况下,若信号标签不匹配,信号处理的回调函数(示例中的onConcatenated)就不会被调用,即该信号会被默默丢弃。如果想要进一步确认信号匹配问题,可以在参数列表的首部添加一个std::optional<sdbus::Error>,这样sdbus-c++在调用到信号处理回调函数时,信号匹配正常则将这个参数设置为空,否则会产生一个Error对象,例如下面这个修改,将Client端的信号处理回调中参数类型改为int,这会因为实际收到了string类型的信号而造成匹配错误:

void onConcatenated(std::optional<sdbus::Error> e, int wrongParameter)
{
    assert(e.has_value());
    assert(e->getMessage() == "Failed to deserialize a int32 value");
}

  参数类型不匹配是客户端没有收到信号反馈的主要原因,我们可以使用dbus-monitor查看总线上的信号,使用上述基于std::optionalsdbus::Error的回调变体,可以轻松地检查确认具体原因。


提示:如何在便捷层API访问D-Bus消息?
  便捷 API 隐藏了 D-Bus 消息的级别,但这些消息会携带一些实现中可能需要的附加信息,例如发送者的名称,或有关凭据信息。在sdbud-c++中,我们可以在以下位置访问相应的 D-Bus 消息:

  • 方法实现回调处理程序(服务器端)
  • 属性集实现回调处理程序(服务器端)
  • 信号回调处理程序(客户端)

  IObject和都IProxy提供了getCurrentlyProcessedMessage()方法。该方法旨在从回调处理程序中调用,它返回指向导致调用处理程序的相应 D-Bus 消息的指针。只要执行流不离开回调处理程序,该指针就有效(可取消引用)。当从其他上下文/线程调用时,指针可能为零或非零,并且其取消引用是未定义的行为。
以下是1.1 同步Server端修改后的部分内容,用于打印出方法调用发送者的名称:

auto concatenate = [&concatenator](const std::vector<int> numbers, const std::string& separator)
{
    const auto* methodCallMsg = concatenator->getCurrentlyProcessedMessage();
    std::cout << "Sender of this method call: " << methodCallMsg.getSender() << std::endl;
     /*...*/
};

1.3 异步Server端

  异步方法回调函数的参数列表中,除了需要输入参数外,还需要接收一个结果对象参数,这个参数的要求如下:

  • 结果持有者的类型为Result<Types...>&&,其中Types...是方法输出参数类型的列表。
  • 结果对象必须是右值引用所采用的回调的第一个物理参数。Result类模板是仅移动的。
  • 回调本身在物理上是一个返回void 的函数。
  • 方法输入参数是按值而不是按常量引用来获取,因为我们通常希望将它们std::move传递给工作线程。move通常比copy节省得多,而且这是惯用的方式,对于不可移动类型,这将会退为copy。
      因此回调函数concatenate的函数类型将从std::string concatenate(const std::vector<int32_t>& numbers, const std::string& separator)更改为void concatenate(sdbus::Result[std::string](std::string)&& result, std::vector<int32_t> numbers, std::string separator)
void concatenate(sdbus::Result<std::string>&& result, std::vector<int32_t> numbers, std::string separator) override
{
    // Launch a thread for async execution...
    std::thread([this, methodResult = std::move(result), numbers = std::move(numbers), separator = std::move(separator)]()
    {
        // Return error if there are no numbers in the collection
        if (numbers.empty())
        {
            // Let's send the error reply message back to the client
            methodResult.returnError({"org.sdbuscpp.Concatenator.Error", "No numbers provided"});
            return;
        }

        std::string result;
        for (auto number : numbers)
        {
            result += (result.empty() ? std::string() : separator) + std::to_string(number);
        }

        // Let's send the reply message back to the client
        methodResult.returnResults(result);

        // Emit the 'concatenated' signal with the resulting string
        this->emitConcatenated(result);
    }).detach();
}

异步Server端的代码解读:
  Result是一个代表方法未来结果的便捷类,与基本层API实现异步Server端时类似,我们在便捷层API可以通过这个Result类向客户端返回该方法的执行结果:

  • returnResults():返回正常的执行结果
  • returnError():返回出现的Error消息

  与同步Server端的实现相比,异步Server端在对象上注册方法的方式(implementedAs()的使用)保持不变,其他内容也无需更改。

1.4 异步Client端

  在便捷 API 级别,调用语句是以callMethodAsync()开头,然后通过以下面两种语句作为结束,分别对应了实现客户端异步调用的两种方法:
1. 基于回调(callback-based):使用uponReplyInvoke()结束调用:

int main(int argc, char *argv[])
{
    /* ...  */

    auto callback = [](std::optional<sdbus::Error> error, const std::string& concatenatedString)
    {
        if (!error) // No error
            std::cout << "Got concatenate result: " << concatenatedString << std::endl;
        else // We got a D-Bus error...
            std::cerr << "Got concatenate error " << error->getName() << " with message " << error->getMessage() << std::endl;
    }

    // Invoke concatenate on given interface of the object
    {
        concatenatorProxy->callMethodAsync("concatenate").onInterface(interfaceName).withArguments(numbers, separator).uponReplyInvoke(callback);
        // When the reply comes, we shall get "Got concatenate result 1:2:3" on the standard output
    }

    // Invoke concatenate again, this time with no numbers and we shall get an error
    {
        concatenatorProxy->callMethodAsync("concatenate").onInterface(interfaceName).withArguments(std::vector<int>{}, separator).uponReplyInvoke(callback);
        // When the reply comes, we shall get concatenation error message on the standard error output
    }

    /* ... */

    return 0;
}

方法1-异步Client端的代码解读:
  该语句需要传入一个返回void的回调处理程序,它至少接受一个参数:std::optional<sdbus::Error>,后续参数对应了 D-Bus 方法的输出。回调函数的error用法与基础层API中5.4 异步Client端部分的方法2相同:

  • error参数为空:表示调用时没有发生 D-Bus 错误,后续参数是有效的 D-Bus 方法返回值。
  • error参数非空:表示调用期间发生了错误(后续参数只是默认构造的),底层Error实例会向我们提供错误名称和消息。

提示:该函数返回sdbus::PendingAsyncCall对象(非拥有、但可用于观察本次调用的句柄),它可用于查询调用是否仍在进行中,以及取消调用。

提示:uponReplyInvoke(callback, sdbus::return_slot);这是一个带return_slot参数的变体,它返回插槽对象(即持有本次异步调用的 RAII 句柄),这使客户端成为一个待处理异步调用的所有者,释放句柄意味着取消调用。

2. 基于std::future(Future-based):使用getResultAsFuture()结束调用:

...
auto future = concatenatorProxy->callMethodAsync("concatenate").onInterface(interfaceName).withArguments(numbers, separator).getResultAsFuture<std::string>();
try
{
    auto concatenatedString = future.get(); // This waits for the reply
    std::cout << "Got concatenate result: " << concatenatedString << std::endl;
}
catch (const sdbus::Error& e)
{
    std::cerr << "Got concatenate error " << e.getName() << " with message " << e.getMessage() << std::endl;
}
...

方法2-Client端的代码解读:
  这是一个模板函数,需要以 D-Bus 方法返回的类型作为列表(如果是返回值为void的方法,则为空列表),该函数返回一个std::future对象,当方法异步执行后,在没有异常发生时该对象将包含返回值对应的回复消息,在调用发生异常时返回Error(由std::future::get()抛出sdbus::Error)。
  对于调用没有发生异常的情况,该函数返回的std::future对象可以包含:

  • void:对应返回为 void 的 D-Bus 方法;
  • 单一类型:对应返回单一值的 D-Bus 方法;
  • std::tuple:该结构内对应保存具有多个返回值的 D-Bus 方法。

2. D-Bus属性(Property)的同步/异步读写

  sdbus-c++在便捷层API考虑了对D-Bus属性读写操作的方便,当然也包括了C++绑定层API使用说明(待发布)。下面假设远程 D-Bus 对象在接口org.sdbuscpp.Concatenator下提供了u类型(对应C++的uint32_t类型)的属性status

2.1 读取属性

1. IProxy::getProperty() :同步读取属性的方法。使用示例:

uint32_t status = proxy->getProperty("status").onInterface("org.sdbuscpp.Concatenator");

2. IProxy::getPropertyAsync():异步读取属性的方法。(无论基于回调,还是基于std::future的异步实现,均支持调用该方法异步读取D-Bus属性。使用示例:

// 基于回调
auto callback = [](std::optional<sdbus::Error> /*error*/, sdbus::Variant value)
{
    std::cout << "Got property value: " << value.get<uint32_t>() << std::endl;
};
uint32_t status = proxy->getPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").uponReplyInvoke(std::move(callback));

// 基于std::future
std::future<sdbus::Variant> statusFuture = object.getPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").getResultAsFuture();
...
std::cout << "Got property value: " << statusFuture.get().get<uint32_t>() << std::endl;

3. IProxy::getAllProperties():以同步方式获取全部D-Bus属性的方法。
4. getAllPropertiesAsync():以异步方式获取全部D-Bus属性的方法。

2.2 修改属性

1. IProxy::setProperty():同步修改D-Bus属性的方法。使用示例:

uint32_t status = ...;
proxy->setProperty("status").onInterface("org.sdbuscpp.Concatenator").toValue(status);

2. IProxy::setPropertyAsync():异步修改属性的方法。使用示例:

// 基于回调
auto callback = [](std::optional<sdbus::Error> error { /*... Error handling in case error contains a value...*/ };
uint32_t status = proxy->setPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").toValue(status).uponReplyInvoke(std::move(callback));

// 基于std::future
std::future<void> statusFuture = object.setPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").getResultAsFuture();

DBus专栏导航:
D-Bus理论基础
Linux系统DBus工具的使用
sdbus-c++中文版使用说明(一)——概括介绍与编译
sdbus-c++中文版使用说明(二)——基础层API
sdbus-c++中文版使用说明(三)——便捷层API
sdbus-c++中文版使用说明(四)——C++绑定层API

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值