1# OpenHarmony IDL工具规格及使用说明书 2 3## IDL接口描述语言简介 4在OpenHarmony中,当客户端和服务器进行IPC通信时,需要定义双方都认可的接口,以保障双方可以成功通信,OpenHarmony IDL(OpenHarmony Interface Definition Language)则是一种定义此类接口的工具。OpenHarmony IDL先把需要传递的对象分解成操作系统能够理解的基本类型,并根据开发者的需要封装跨边界的对象。 5 6 **图1** IDL接口描述 7 8![IDL-interface-description](./figures/IDL-interface-description.png) 9 10 **OpenHarmony IDL接口描述语言主要用于:** 11 12- 声明系统服务对外提供的服务接口,根据接口声明在编译时生成跨进程调用(IPC)或跨设备调用(RPC)的代理(Proxy)和桩(Stub)的C/C++代码或JS/TS代码。 13 14- 声明Ability对外提供的服务接口,根据接口声明在编译时生成跨进程调用(IPC)或跨设备调用(RPC)的代理(Proxy)和桩(Stub)的C/C++代码或JS/TS代码。 15 16**图2** IPC/RPC通信模型 17 18![IPC-RPC-communication-model](./figures/IPC-RPC-communication-model.png) 19 20 **使用OpenHarmony IDL接口描述语言声明接口具有以下优点:** 21 22- OpenHarmony IDL中是以接口的形式定义服务,可以专注于定义而隐藏实现细节。 23 24- OpenHarmony IDL中定义的接口可以支持跨进程调用或跨设备调用。根据OpenHarmony IDL中的定义生成的信息或代码可以简化跨进程或跨设备调用接口的实现。 25 26## IDL接口描述语言构成 27 28### 数据类型 29 30#### 基础数据类型 31| IDL基本数据类型 | C++基本数据类型 | TS基本数据类型 | 32| -------- | -------- | -------- | 33|void | void | void | 34|boolean | bool | boolean | 35|byte | int8_t | number | 36|short | int16_t | number | 37|int | int32_t | number | 38|long | int64_t | number | 39|float | float | number | 40|double | double | number | 41|String | std::string | string | 42 43IDL支持的基本数据类型及其映射到C++、TS上的数据类型的对应关系如上表所示。 44 45#### sequenceable数据类型 46sequenceable数据类型是指使用“sequenceable”关键字声明的数据,表面该数据类型可以被序列化进行跨进程或跨设备传递。sequenceable在C++与TS中声明方式存在一定差异。 47 48在C++中sequenceable数据类型的声明放在文件的头部,以“sequenceable includedir..namespace.typename”的形式声明。具体而言。声明可以有如下三个形式: 49 50```cpp 51sequenceable includedir..namespace.typename 52sequenceable includedir...typename 53sequenceable namespace.typename 54``` 55 56其中,includedir表示该数据类型头文件所在目录,includedir中以“.”作为分隔符。namespace表示该数据类型所在命名空间,namespace中同样以“.”作为分隔符。typename表示数据类型,数据类型中不能包含非英文字符类型的其他符号。includedir与namespace之间通过“..”分割,如果类型声明的表达式中不包含“..”,除去最后一个typename之外的字符都会被解析为命名空间。例如: 57 58```cpp 59sequenceable a.b..C.D 60``` 61 62 上述声明在生成的的C++头文件中将被解析为如下代码: 63 64```cpp 65#include “a/b/d.h” 66using C::D; 67``` 68 69TS声明放在文件的头部,以 “sequenceable namespace.typename;”的形式声明。具体而言,声明可以有如下形式: 70 71```ts 72sequenceable idl.MySequenceable 73``` 74 75其中,namespace是该类型所属的命名空间,typename是类型名。MySequenceable类型表示可以通过Parcel进行跨进程传递。sequenceable数据类型并不在OpenHarmony IDL文件中定义,而是定义在.ts文件中。因此,OpenHarmony IDL工具将根据声明在生成的.ts代码文件中加入如下语句: 76 77```ts 78import MySequenceable from "./my_sequenceable" 79``` 80 81需要注意的是,IDL并不负责该类型的代码实现,仅仅按照指定的形式引入该头文件或import指定模块,并使用该类型,因此开发者需要自行保证引入目录、命名空间及类型的正确性。 82 83#### 接口类型 84 接口类型是指OpenHarmony IDL文件中定义的接口。对于当前IDL文件中定义的接口,可以直接使用它作为方法参数类型或返回值类型。而在其它OpenHarmony IDL文件中定义的接口,则需要在文件的头部进行前置声明。 85 86 C++中声明的形式与sequenceable类型相似,具体而言可以有如下形式: 87 88```cpp 89interface includedir..namespace.typename 90``` 91 92 TS中声明的形式,具体而言可以有如下形式: 93 94```ts 95interface namespace.interfacename 96``` 97 98其中,namespace是该接口所属的命名空间,interfacename是接口名。例如:“interface OHOS.IIdlTestObserver;”声明了在其他OpenHarmony IDL文件定义的IIdlTestObserver接口,该接口可以作为当前定义中方法的参数类型或返回值类型使用。OpenHarmony IDL工具将根据该声明在生成的TS代码文件中加入如下语句: 99 100```ts 101import IIdlTestObserver from "./i_idl_test_observer" 102``` 103 104#### 数组类型 105数组类型使用“T[]”表示,其中T可以是基本数据类型、sequenceable数据类型、interface类型和数组类型。该类型在C++生成代码中将被生成为std::vector<T>类型。 106OpenHarmony IDL数组数据类型与TS数据类型、C++数据类型的对应关系如下表所示: 107 108|OpenHarmony IDL数据类型 | C++数据类型 | TS数据类型 | 109| ------- | -------- | -------- | 110|T[] | std::vector<T> | T[] | 111 112#### 容器类型 113IDL支持两种容器类型,即List和Map。其中List类型容器的用法为List<T>;Map容器的用法为Map<KT,VT>,其中T、KT、VT为基本数据类型、sequenceable类型、interface类型、数组类型或容器类型。 114 115List类型在C++代码中被映射为std::list,Map容器被映射为std::map。 116 117List类型在TS代码中不支持,Map容器被映射为Map。 118 119OpenHarmony IDL容器数据类型与Ts数据类型、C++数据类型的对应关系如下表所示: 120 121|OpenHarmony IDL数据类型 | C++数据类型 | TS数据类型 | 122| -------- | -------- | ------- | 123|List<T> | std::list | 不支持 | 124|Map<KT,VT> | std::map | Map | 125 126 127### IDL文件编写规范 128一个idl文件只能定义一个interface类型,且该interface名称必须和文件名相同。idl文件的接口定义使用BNF范式描述,其基本定义的形式如下: 129 130``` 131[<*interface_attr_declaration*>]interface<*interface_name_with_namespace*>{<*method_declaration*>} 132``` 133 134其中,<*interface_attr_declaration*>表示接口属性声明。当前仅支持“oneway”属性,表示该接口中的接口都是单向方法,即调用方法后不用等待该方法执行即可返回。这个属性为可选项,如果未声明该属性,则默认为同步调用方法。接口名需要包含完整的接口头文件目录及命名空间,且必须包含方法声明,不允许出现空接口。 135接口内的方法声明形式为: 136 137``` 138[<*method_attr_declaration*>]<*result_type*><*method_declaration*> 139``` 140 141其中,<*method_attr_declaration*>表示接口属性说明。当前仅支持“oneway”属性,表示该方法为单向方法,即调用方法后不用等待该方法执行即可返回。这个属性为可选项,如果未声明该属性,则默认为同步调用方法。<*result_type*>为返回值类型,<*method_declaration*>是方法名和各个参数声明。 142参数声明的形式为: 143 144``` 145[<*formal_param_attr*>]<*type*><*identifier*> 146``` 147 148其中<*formal_param_attr*>的值为“in”,“out”,“inout”,分别表示该参数是输入参数,输出参数或输入输出参数。需要注意的是,如果一个方法被声明为oneway,则该方法不允许有输出类型的参数(及输入输出类型)和返回值。 149 150## 开发步骤 151 152### IDL工具的获取 153首先,打开DevEco Studio—>Tools—>SDK Manager,查看OpenHarmony SDK的本地安装路径,此处以DevEco Studio 3.0.0.993版本为例,查看方式如下图所示。 154![SDKpath](./figures/SDKpath.png) 155![SDKpath](./figures/SDKpath2.png) 156 157进入对应路径后,查看toolchains->3.x.x.x(对应版本号命名文件夹)下是否存在idl工具的可执行文件。 158 159> **注意**:请保证使用最新版的SDK,版本老旧可能导致部分语句报错。 160 161若不存在,可对应版本前往[docs仓版本目录](../../release-notes)下载SDK包,以[3.2Beta5版本](../../release-notes/OpenHarmony-v3.2-beta5.md#从镜像站点获取)为例,可通过镜像站点获取。 162 163关于如何替换DevEco Studio的SDK包具体操作,参考[full-SDK替换指南](../quick-start/full-sdk-switch-guide.md)中的替换方法。 164 165得到idl工具的可执行文件后,根据具体场景进行后续开发步骤。 166 167### TS开发步骤 168 169#### 创建.idl文件 170 171 开发者可以使用TS编程语言构建.idl文件。 172 173 例如,此处构建一个名为IIdlTestService.idl的文件,文件内具体内容如下: 174 175```cpp 176 interface OHOS.IIdlTestService { 177 int TestIntTransaction([in] int data); 178 void TestStringTransaction([in] String data); 179 void TestMapTransaction([in] Map<int, int> data); 180 int TestArrayTransaction([in] String[] data); 181 } 182``` 183 184在idl的可执行文件所在文件夹下执行命令 `idl -gen-ts -d dir -c dir/IIdlTestService.idl`。 185 186-d后的dir为目标输出目录,以输出文件夹名为IIdlTestServiceTs为例,在idl可执行文件所在目录下执行`idl -gen-ts -d IIdlTestServiceTs -c IIdlTestServiceTs/IIdlTestService.idl`,将会在执行环境的dir目录(即IIdlTestServiceTs目录)中生成接口文件、Stub文件、Proxy文件。 187 188> **注意**:生成的接口类文件名称和.idl文件名称保持一致,否则会生成代码时会出现错误。 189 190以名为`IIdlTestService.idl`的.idl文件、目标输出文件夹为IIdlTestServiceTs为例,其目录结构应类似于: 191 192``` 193├── IIdlTestServiceTs # idl代码输出文件夹 194│ ├── i_idl_test_service.ts # 生成文件 195│ ├── idl_test_service_proxy.ts # 生成文件 196│ ├── idl_test_service_stub.ts # 生成文件 197│ └── IIdlTestService.idl # 构造的.idl文件 198└── idl.exe # idl的可执行文件 199``` 200 201#### 服务端公开接口 202 203OpenHarmony IDL工具生成的Stub类是接口类的抽象实现,并且会声明.idl文件中的所有方法。 204 205```ts 206import {testIntTransactionCallback} from "./i_idl_test_service"; 207import {testStringTransactionCallback} from "./i_idl_test_service"; 208import {testMapTransactionCallback} from "./i_idl_test_service"; 209import {testArrayTransactionCallback} from "./i_idl_test_service"; 210import IIdlTestService from "./i_idl_test_service"; 211import rpc from "@ohos.rpc"; 212 213export default class IdlTestServiceStub extends rpc.RemoteObject implements IIdlTestService { 214 constructor(des: string) { 215 super(des); 216 } 217 218 async onRemoteMessageRequest(code: number, data, reply, option): Promise<boolean> { 219 console.log("onRemoteMessageRequest called, code = " + code); 220 switch(code) { 221 case IdlTestServiceStub.COMMAND_TEST_INT_TRANSACTION: { 222 let _data = data.readInt(); 223 this.testIntTransaction(_data, (errCode, returnValue) => { 224 reply.writeInt(errCode); 225 if (errCode == 0) { 226 reply.writeInt(returnValue); 227 } 228 }); 229 return true; 230 } 231 case IdlTestServiceStub.COMMAND_TEST_STRING_TRANSACTION: { 232 let _data = data.readString(); 233 this.testStringTransaction(_data, (errCode) => { 234 reply.writeInt(errCode); 235 }); 236 return true; 237 } 238 case IdlTestServiceStub.COMMAND_TEST_MAP_TRANSACTION: { 239 let _data = new Map(); 240 let _dataSize = data.readInt(); 241 for (let i = 0; i < _dataSize; ++i) { 242 let key = data.readInt(); 243 let value = data.readInt(); 244 _data.set(key, value); 245 } 246 this.testMapTransaction(_data, (errCode) => { 247 reply.writeInt(errCode); 248 }); 249 return true; 250 } 251 case IdlTestServiceStub.COMMAND_TEST_ARRAY_TRANSACTION: { 252 let _data = data.readStringArray(); 253 this.testArrayTransaction(_data, (errCode, returnValue) => { 254 reply.writeInt(errCode); 255 if (errCode == 0) { 256 reply.writeInt(returnValue); 257 } 258 }); 259 return true; 260 } 261 default: { 262 console.log("invalid request code" + code); 263 break; 264 } 265 } 266 return false; 267 } 268 269 testIntTransaction(data: number, callback: testIntTransactionCallback): void{} 270 testStringTransaction(data: string, callback: testStringTransactionCallback): void{} 271 testMapTransaction(data: Map<number, number>, callback: testMapTransactionCallback): void{} 272 testArrayTransaction(data: string[], callback: testArrayTransactionCallback): void{} 273 274 static readonly COMMAND_TEST_INT_TRANSACTION = 1; 275 static readonly COMMAND_TEST_STRING_TRANSACTION = 2; 276 static readonly COMMAND_TEST_MAP_TRANSACTION = 3; 277 static readonly COMMAND_TEST_ARRAY_TRANSACTION = 4; 278} 279``` 280 281开发者需要继承.idl文件中定义的接口类并实现其中的方法。在本示例中,我们继承了IdlTestServiceStub接口类并实现了其中的testIntTransaction、testStringTransaction、testMapTransaction和testArrayTransaction方法。具体的示例代码如下: 282 283```ts 284import {testIntTransactionCallback} from "./i_idl_test_service" 285import {testStringTransactionCallback} from "./i_idl_test_service" 286import {testMapTransactionCallback} from "./i_idl_test_service"; 287import {testArrayTransactionCallback} from "./i_idl_test_service"; 288import IdlTestServiceStub from "./idl_test_service_stub" 289 290 291class IdlTestImp extends IdlTestServiceStub { 292 293 testIntTransaction(data: number, callback: testIntTransactionCallback): void 294 { 295 callback(0, data + 1); 296 } 297 testStringTransaction(data: string, callback: testStringTransactionCallback): void 298 { 299 callback(0); 300 } 301 testMapTransaction(data: Map<number, number>, callback: testMapTransactionCallback): void 302 { 303 callback(0); 304 } 305 testArrayTransaction(data: string[], callback: testArrayTransactionCallback): void 306 { 307 callback(0, 1); 308 } 309} 310``` 311 312在服务实现接口后,需要向客户端公开该接口,以便客户端进程绑定。如果开发者的服务要公开该接口,请扩展Ability并实现onConnect()从而返回IRemoteObject,以便客户端能与服务进程交互。服务端向客户端公开IRemoteAbility接口的代码示例如下: 313 314```ts 315export default { 316 onStart() { 317 console.info('ServiceAbility onStart'); 318 }, 319 onStop() { 320 console.info('ServiceAbility onStop'); 321 }, 322 onCommand(want, startId) { 323 console.info('ServiceAbility onCommand'); 324 }, 325 onConnect(want) { 326 console.info('ServiceAbility onConnect'); 327 try { 328 console.log('ServiceAbility want:' + typeof(want)); 329 console.log('ServiceAbility want:' + JSON.stringify(want)); 330 console.log('ServiceAbility want name:' + want.bundleName) 331 } catch(err) { 332 console.log('ServiceAbility error:' + err) 333 } 334 console.info('ServiceAbility onConnect end'); 335 return new IdlTestImp('connect'); 336 }, 337 onDisconnect(want) { 338 console.info('ServiceAbility onDisconnect'); 339 console.info('ServiceAbility want:' + JSON.stringify(want)); 340 } 341}; 342``` 343 344#### 客户端调用IPC方法 345 346客户端调用connectAbility()以连接服务时,客户端的onAbilityConnectDone中的onConnect回调会接收服务的onConnect()方法返回的IRemoteObject实例。由于客户端和服务在不同应用内,所以客户端应用的目录内必须包含.idl文件(SDK工具会自动生成Proxy代理类)的副本。客户端的onAbilityConnectDone中的onConnect回调会接收服务的onConnect()方法返回的IRemoteObject实例,使用IRemoteObject创建IdlTestServiceProxy类的实例对象testProxy,然后调用相关IPC方法。示例代码如下: 347 348```ts 349import IdlTestServiceProxy from './idl_test_service_proxy' 350import featureAbility from '@ohos.ability.featureAbility'; 351 352function callbackTestIntTransaction(result: number, ret: number): void { 353 if (result == 0 && ret == 124) { 354 console.log('case 1 success'); 355 } 356} 357 358function callbackTestStringTransaction(result: number): void { 359 if (result == 0) { 360 console.log('case 2 success'); 361 } 362} 363 364function callbackTestMapTransaction(result: number): void { 365 if (result == 0) { 366 console.log('case 3 success'); 367 } 368} 369 370function callbackTestArrayTransaction(result: number, ret: number): void { 371 if (result == 0 && ret == 124) { 372 console.log('case 4 success'); 373 } 374} 375 376var onAbilityConnectDone = { 377 onConnect:function (elementName, proxy) { 378 let testProxy = new IdlTestServiceProxy(proxy); 379 let testMap = new Map(); 380 testMap.set(1, 1); 381 testMap.set(1, 2); 382 testProxy.testIntTransaction(123, callbackTestIntTransaction); 383 testProxy.testStringTransaction('hello', callbackTestStringTransaction); 384 testProxy.testMapTransaction(testMap, callbackTestMapTransaction); 385 testProxy.testArrayTransaction(['1','2'], callbackTestMapTransaction); 386 }, 387 onDisconnect:function (elementName) { 388 console.log('onDisconnectService onDisconnect'); 389 }, 390 onFailed:function (code) { 391 console.log('onDisconnectService onFailed'); 392 } 393}; 394 395function connectAbility: void { 396 let want = { 397 bundleName: 'com.example.myapplicationidl', 398 abilityName: 'com.example.myapplicationidl.ServiceAbility' 399 }; 400 let connectionId = -1; 401 connectionId = featureAbility.connectAbility(want, onAbilityConnectDone); 402} 403 404 405``` 406 407#### IPC传递sequenceable对象 408 409开发者可以通过 IPC 接口,将某个类从一个进程发送至另一个进程。但是,必须确保 IPC 通道的另一端可使用该类的代码,并且该类必须支持marshalling和unmarshalling方法。OpenHarmony 需要通过该marshalling和unmarshalling方法将对象序列化和反序列化成各进程能识别的对象。 410 411 **如需创建支持sequenceable 类型数据,开发者必须执行以下操作:** 412 4131. 实现marshalling方法,它会获取对象的当前状态并将其序列化后写入Parcel。 4142. 实现unmarshalling方法,它会从Parcel中反序列化出对象。 415 416MySequenceable类的代码示例如下: 417 418```ts 419import rpc from '@ohos.rpc'; 420export default class MySequenceable { 421 constructor(num: number, str: string) { 422 this.num = num; 423 this.str = str; 424 } 425 getNum() : number { 426 return this.num; 427 } 428 getString() : string { 429 return this.str; 430 } 431 marshalling(messageParcel) { 432 messageParcel.writeInt(this.num); 433 messageParcel.writeString(this.str); 434 return true; 435 } 436 unmarshalling(messageParcel) { 437 this.num = messageParcel.readInt(); 438 this.str = messageParcel.readString(); 439 return true; 440 } 441 private num; 442 private str; 443} 444``` 445