OpenHarmony 的 Binder C++ 框架

OpenHarmony 的 Binder C++ 框架

开发步骤

  1. 添加依赖

    SDK 依赖:

    #ipc场景
    external_deps = [
      "ipc:ipc_single",
    ]
    
    #rpc场景
    external_deps = [
      "ipc:ipc_core",
    ]
    

    此外, IPC/RPC 依赖的 refbase 实现在公共基础库下,请增加对 utils 的依赖:

    external_deps = [
     "c_utils:utils",
    ]
    
  2. 定义IPC接口ITestAbility

    SA 接口继承 IPC 基类接口 IRemoteBroker,接口里定义描述符、业务函数和消息码,其中业务函数在 Proxy 端和 Stub 端都需要实现。

    #include "iremote_broker.h"
    
    //定义消息码
    const int TRANS_ID_PING_ABILITY = 5
    
    const std::string DESCRIPTOR = "test.ITestAbility";
    
    class ITestAbility : public IRemoteBroker {
         
    public:
        // DECLARE_INTERFACE_DESCRIPTOR 是必需的,入参需使用 std::u16string。在 IRemoteStub 构造函数中会调用此元接口,初始化父类
        DECLARE_INTERFACE_DESCRIPTOR(to_utf16(DESCRIPTOR));
        virtual int TestPingAbility(const std::u16string &dummy) = 0; // 定义业务函数
    };
    
  3. 定义和实现服务端 TestAbilityStub

    该类是和 IPC 框架相关的实现,需要继承 IRemoteStubStub 端作为接收请求的一端,需重写 OnRemoteRequest 方法用于接收客户端调用。

    #include "iability_test.h"
    #include "iremote_stub.h"
    
    class TestAbilityStub : public IRemoteStub<ITestAbility> {
         
    public:
        virtual int OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) override;
        int TestPingAbility(const std::u16string &dummy) override;
     };
    
    int TestAbilityStub::OnRemoteRequest(uint32_t code,
        MessageParcel &data, MessageParcel &reply, MessageOption &option)
    {
         
        switch (code) {
         
            case TRANS_ID_PING_ABILITY: {
         
                std::u16string dummy = data.ReadString16();
                int result = TestPingAbility(dummy);
                reply.WriteInt32(result);
                return 0;
            }
            default:
                return IPCObjectStub::OnRemoteRequest(code, data, reply, option);
        }
    }
    
  4. 定义服务端业务函数具体实现类 TestAbility

    #include "iability_server_test.h"
    
    class TestAbility : public TestAbilityStub {
         
    public:
        int TestPingAbility(const std::u16string &dummy);
    }
    
    int TestAbility::TestPingAbility(const std::u16string &dummy) {
         
        return 0;
    }
    
  5. 定义和实现客户端 TestAbilityProxy

    该类是 Proxy 端实现,继承 IRemoteProxy,调用 SendRequest 接口向 Stub 端发送请求,对外暴露服务端提供的能力。

    #include "iability_test.h"
    #include "iremote_proxy.h"
    #include "iremote_object.h"
    
    class TestAbilityProxy : public IRemoteProxy<ITestAbility> {
         
    public:
        explicit TestAbilityProxy(const sptr<IRemoteObject> &impl);
        int TestPingAbility(const std::u16string &dummy) override;
    private:
        static inline BrokerDelegator<TestAbilityProxy> delegator_; // 方便后续使用iface_cast宏
    }
    
    TestAbilityProxy::TestAbilityProxy(const sptr<IRemoteObject> &impl)
        : IRemoteProxy<ITestAbility>(impl)
    {
         
    }
    
    int TestAbilityProxy::TestPingAbility(const std::u16string &dummy){
         
        MessageOption option;
        MessageParcel dataParcel, replyParcel;
        dataParcel.WriteString16(dummy);
        int error = Remote()->SendRequest(TRANS_ID_PING_ABILITY, dataParcel, replyParcel, option);
        int result = (error == ERR_NONE) ? replyParcel.ReadInt32() : -1;
        return result;
    }
    
  6. SA 注册与启动

    SA 需要将自己的 TestAbilityStub 实例通过 AddSystemAbility 接口注册到 SystemAbilityManager,设备内与分布式的注册参数不同。

    // 注册到本设备内
    auto samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
    samgr->AddSystemAbility(saId, new TestAbility());
    
    // 在组网场景下,会被同步到其他设备上
    auto samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
    ISystemAbilityManager::SAExtraProp saExtra;
    saExtra.isDistributed = true; // 设置为分布式SA
    int result = samgr->AddSystemAbility(saId, new TestAbility(), saExtra);
    
  7. SA获取与调用

    通过 SystemAbilityManagerGetSystemAbility 方法可获取到对应 SA 的代理 IRemoteObject,然后构造 TestAbilityProxy 即可。

    // 获取本设备内注册的SA的proxy
    sptr<ISystemAbilityManager> samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
    sptr<IRemoteObject> remoteObject = samgr->GetSystemAbility(saId);
    sptr<ITestAbility> testAbility = iface_cast<ITestAbility>(remoteObject); // 使用iface_cast宏转换成具体类型
    
    // 获取其他设备注册的SA的proxy
    sptr<ISystemAbilityManager> samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
    
    // networkId是组网场景下对应设备的标识符,可以通过GetLocalNodeDeviceInfo获取
    sptr<IRemoteObject> remoteObject = samgr->GetSystemAbility(saId, networkId);
    sptr<TestAbilityProxy> proxy(new TestAbilityProxy(remoteObject)); // 直接构造具体Proxy
    

原理分析

相关类的继承关系:

在这里插入图片描述

类名 含义
RefBase 用于智能指针引用计数。
Parcelable 表示可以被序列化,子对象可以用于 Binder 跨进程传输。与之对应的 Parcel 类,用于保存数据本身。
IRemoteObject IPC 对象的父类,定义了SendRequest 接口,不会被实例化。
IPCObjectProxy 代理类,由 Binder 框架实例化。
IRemoteProxy 持有代理类实例。作为业务类的父类使用。
IPCObjectStub 桩类,通过子类来创建对象。
IRemoteStub IPCObjectStub 的子类,作为业务类的父类使用。
IPCThreadSkeleton 线程单例,主要用来获取 IRemoteInvoker 对象。
InvokerFactory 工厂类,用户创建 IRemoteInvoker 对象。
IRemoteInvoker 远程调用者抽象类,根据不同协议有两种实现。
BinderInvoker 解析 Binder 通信。
BinderConnector 单例,负责与 Binder 驱动交互。
IPCWorkThread 负责启动 Binder 工作线程。
IPCWorkThreadPool 线程池,管理所有的 Binder 工作线程。
IPCProcessSkeleton 单例,负责派生线程。
IRemoteBroker 业务类的父类,辅助进行类型转换,将 IRemoteProxy 对象转换成业务类对象。
BrokerDelegator 在代理端业务类中用 static 方式定义。用于将 IRemoteProxy 对象,创建业务类对象。

发送消息

调用时序图:

在这里插入图片描述

代理端发送消息会调用的 BinderInvoker 的 SendRequest 函数:

int BinderInvoker::SendRequest(int handle, uint32_t code, MessageParcel &data, MessageParcel &reply,
    MessageOption &option)
{
   
    ...
    if (!WriteTransaction(BC_TRANSACTION, flags, handle, code, data, nullptr)) {
   
        ...
        return IPC_INVOKER_WRITE_TRANS_ERR;
    }

    if ((flags & TF_ONE_WAY) != 0) {
   
        error = WaitForCompletion(nullptr);
    } else {
   
        error = WaitForCompletion(&reply);
    }
    ...
    return error;
}

Binder 事务使用 BINDER_WRITE_READ 这个 ioctl 命令写到驱动,此命令的参数使用 binder_write_read 结构体表示,包含了读写 buffer 的指针。

           |   binder_write_read   |
               /               \
|     write_buffer     |     read_buffer     |
| cmd |      data      | cmd |     data      |
              /                      \
         | user data|           | user data|
  • cmd:4 字节表示命令,发送端用 BC_* 打头的宏表示,接收端用 BR_* 打头的宏表示。
  • data:当 cmd 是 BC_TRANSACTION/BR_TRANSACTION/BC_REPLY/BR_REPLY 时,数据部分由 binder_transaction_data 描述,真正用于跨进程传输的数据则通过指针指向另一篇内存。(由于传输的指针,因此不会发生内存拷贝)
  • user data:对应函数的参数,也就是跨进程传输的有效载荷。

扁平化后的用户数据在内存中的组织形式如下:

offs0          data0
+--------------+----------------------------------------------------------+
|i|j|..........|.....| flat_binder_object |......| flat_binder_object |...|
+--------------+----------------------------------------------------------+
|<-offs_avail->|<-i->|                           |                        |
               |<----------------j-------------->|                        |
               |<-----------------------data_avail----------------------->|
  • offs0:偏移地址的记录区,每一条记录占用 sizeof(binder_size_t) 个字节。
  • data0:有效数据区。包括普通数据和 flat_binder_object 对象。
  • i:表示 data0+i 地址存放了一个 flat_binder_object 对象。
  • offs_avail:表示偏移地址区的存放的条目数。
  • data_avail:表示数据区字节数。

为什么要个单独保存 flat_binder_object 的偏移地址?原因是驱动需要处理 flat_binder_object,驱动不能分辨哪些数据是普通数据,哪些数据是 flat_binder_object,因此需要保存其在数据区的偏移。而其它数据只需在用户空间处理,发送端和接收按约定的顺序取数据即可。

将扁平化后的数据写入发送缓存:

bool BinderInvoker::WriteTransaction(int cmd, uint32_t flags, int32_t handle, uint32_t code, const MessageParcel &data,
    const int32_t *status)
{
   
    binder_transaction_data tr {
   };
    tr.target.handle = (uint32_t)handle;
    tr.code = code;
    tr.flags = flags;
    tr.flags |= TF_ACCEPT_FDS;
    if (data.GetDataSize() > 0) {
   
        // Send this parcel's data through the binder.
        tr.data_size = data.GetDataSize();
        tr.data.ptr.buffer = (binder_uintptr_t)data.GetData();
        tr.offsets_size = data.GetOffsetsSize() * sizeof(binder_size_t);
        tr.data.ptr.offsets = data.GetObjectOffsets();
    } else if (status != nullptr) {
   
        // Send this parcel's status through the binder.
        tr.flags |= TF_STATUS_CODE;
        tr.data_size = sizeof(int32_t);
        tr.data.ptr.buffer = reinterpret_cast<uintptr_t>(status);
        tr.offsets_size = 0;
        tr.data.ptr.offsets = 0;
    }

    if (!output_.WriteInt32(cmd)) {
   
        ZLOGE(LABEL, "WriteTransaction Command failure");
        return false;
    }
    return output_.WriteBuffer(&tr, sizeof(binder_transaction_data));
}

使用 BINDER_WRITE_READ 命令发送数据:

int BinderInvoker::TransactWithDriver(bool doRead)
{
   
    if ((binderConnector_ == nullptr) || (!binderConnector_->IsDriverAlive())) {
   
        ZLOGE(LABEL, "%{public}s: Binder Driver died", __func__);
        return IPC_INVOKER_CONNECT_ERR;
    }

    binder_write_read bwr;
    const bool readAvail = input_.GetReadableBytes() == 0;
    const size_t outAvail = (
binder c是指在Android系统中使用C语言编写的binder代码实现。它可以通过添加内存共享、读写通知来实现binder通信。在Android 9上经过测试并没有问题。[1] 在Android系统中,binder的作用是配接函数对象,形成新的函数对象,调用全局函数针对对象、对象指针、智能对象指针调用成员函数。它可以用于使用C标准库预定义的函数对象时指定参数。 对于Android系统而言,binder机制是常见的,也是初学者最难搞明白的部分,很多Service都是通过binder机制与客户端进行通讯交互的。理解binder的机制可以帮助我们更好地理解程序运行的流程。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Android binder C++ service/client 实现. 共享内存](https://download.csdn.net/download/jounehou/12915968)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [C++函数适配器Binder](https://blog.csdn.net/weixin_44048823/article/details/93380331)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [C++使用binder实例](https://blog.csdn.net/weixin_30293079/article/details/95537179)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

翻滚吧香香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值