OpenHarmony 的 Binder C++ 框架
开发步骤
-
添加依赖
SDK 依赖:
#ipc场景 external_deps = [ "ipc:ipc_single", ] #rpc场景 external_deps = [ "ipc:ipc_core", ]
此外,
IPC/RPC
依赖的refbase
实现在公共基础库下,请增加对utils
的依赖:external_deps = [ "c_utils:utils", ]
-
定义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; // 定义业务函数 };
-
定义和实现服务端
TestAbilityStub
该类是和
IPC
框架相关的实现,需要继承IRemoteStub
。Stub
端作为接收请求的一端,需重写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); } }
-
定义服务端业务函数具体实现类
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; }
-
定义和实现客户端 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; }
-
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);
-
SA获取与调用
通过
SystemAbilityManager
的GetSystemAbility
方法可获取到对应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 = (