idl-guidelines.md 20.5 KB
Newer Older
G
Gloria 已提交
1
# IDL Specifications and User Guide
W
wusongqing 已提交
2 3 4 5

## IDL Overview
To ensure successful communications between the client and server, interfaces recognized by both parties must be defined. The OpenHarmony Interface Definition Language (IDL) is a tool for defining such interfaces. OpenHarmony IDL decomposes objects to be transferred into primitives that can be understood by the operating system and encapsulates cross-boundary objects based on developers' requirements.

G
Gloria 已提交
6
**Figure 1** IDL interface description
W
wusongqing 已提交
7 8 9

![IDL-interface-description](./figures/IDL-interface-description.png)

G
Gloria 已提交
10
**IDL provides the following functions:**
W
wusongqing 已提交
11 12 13 14 15 16 17 18 19

- Declares interfaces provided by system services for external systems, and based on the interface declaration, generates C, C++, JS, or TS code for inter-process communication (IPC) or remote procedure call (RPC) proxies and stubs during compilation.

- Declares interfaces provided by abilities for external systems, and based on the interface declaration, generates C, C++, JS, or TS code for IPC or RPC proxies and stubs during compilation.

**Figure 2** IPC/RPC communication model

![IPC-RPC-communication-model](./figures/IPC-RPC-communication-model.png)

G
Gloria 已提交
20
**IDL has the following advantages:**
W
wusongqing 已提交
21 22 23 24 25 26 27 28 29 30 31

- Services are defined in the form of interfaces in IDL. Therefore, you do not need to focus on implementation details.

- Interfaces defined by IDL can be used in IPC or RPC scenarios. The information or code generated based on the definitions in IDL simplifies IPC or RPC implementation.

## IDL File Structure

### Data Types

#### Primitive Type
| IDL Primitive Type| C++ Primitive Type| TS Primitive Type|
W
wusongqing 已提交
32 33 34 35 36 37 38 39 40 41
|   --------    |  --------     | --------     |
|void           | void          | void         |
|boolean        | bool          | boolean      |
|byte           | int8_t        | number       |
|short          | int16_t       | number       |
|int            | int32_t       | number       |
|long           | int64_t       | number       |
|float          | float         | number       |
|double         | double        | number       |
|String         | std::string   | string       |
W
wusongqing 已提交
42 43 44 45 46 47 48 49

The preceding table lists the primitive types supported by IDL and the mappings to the C++ and TS primitive types.

#### sequenceable Type
The sequenceable type is declared using the keyword **sequenceable**. This type can be passed during IPC or RPC through **Parcel** objects. The declaration mode of the sequenceable type in C++ is different from that in TS.

In C++, the declaration is placed in the file header in the format of **sequenceable includedir..namespace.typename**.  It can be in any of the following forms:

W
wusongqing 已提交
50
```cpp
W
wusongqing 已提交
51 52 53 54
sequenceable includedir..namespace.typename
sequenceable includedir...typename
sequenceable namespace.typename
```
W
wusongqing 已提交
55

W
wusongqing 已提交
56
In the preceding information, **includedir** indicates the directory where the header file of the type is located, and the dot (.) is used as the separator. **namespace** indicates the namespace where the type is located, and the dot (.) is used as the separator. **typename** indicates the data type, which can contain only English characters. **includedir** and **namespace** are separated by two dots (..). If the declaration statement does not contain two dots, all characters except the last typename will be parsed as a namespace. Example:
W
wusongqing 已提交
57 58

```cpp
W
wusongqing 已提交
59 60
sequenceable a.b..C.D
```
W
wusongqing 已提交
61

W
wusongqing 已提交
62
The preceding statement is parsed into the following code in the C++ header file:
W
wusongqing 已提交
63 64

```cpp
W
wusongqing 已提交
65 66 67
#include  "a/b/d.h"
using C::D;
```
W
wusongqing 已提交
68

W
wusongqing 已提交
69 70
In TS, the declaration is placed in the file header in the format of **sequenceable namespace.typename;**. It can be in the following form:

W
wusongqing 已提交
71
```ts
W
wusongqing 已提交
72 73 74 75 76
sequenceable idl.MySequenceable
```

In the preceding information, **namespace** indicates the namespace to which the data type belongs, **typename** indicates the data type name, and **MySequenceable** indicates that data can be passed during IPC using **Parcel** objects. The sequenceable type is not defined in the IDL file, but in the .ts file. Therefore, IDL adds the following statement to the generated .ts file based on the declaration:

W
wusongqing 已提交
77
```ts
W
wusongqing 已提交
78 79 80 81 82 83 84 85 86 87
import MySequenceable from "./my_sequenceable"
```

Note that IDL does not implement code for this type. It only imports the header file in the specified format or imports the specified module and uses the type. Therefore, you must ensure that the imported directory, namespace, and type are correct.

#### Interface Type
The interface type refers to interfaces defined in IDL files. The interfaces defined in an IDL file can be directly used as the parameter type or return value type of a method declared in the file. If an IDL file attempts to use interfaces defined in other IDL files, forward declaration must be contained in the header of that IDL file.

The declaration form in C++ is similar to that of the sequenceable type. The declaration form is as follows:

W
wusongqing 已提交
88
```cpp
W
wusongqing 已提交
89 90 91 92 93
interface includedir..namespace.typename
```

In TS, the declaration form is as follows:

W
wusongqing 已提交
94
```ts
W
wusongqing 已提交
95 96 97 98 99
interface namespace.interfacename
```

In the preceding information, **namespace** indicates the namespace to which the interface belongs, and **interfacename** indicates the name of the interface. For example, **interface OHOS.IIdlTestObserver;** declares the **IIdlTestObserver** interface defined in another IDL file. This interface can be used as the parameter type or return value type of a method in the current file. IDL adds the following statement to the generated .ts file based on the statement:

W
wusongqing 已提交
100
```ts
W
wusongqing 已提交
101 102 103 104 105 106 107
import IIdlTestObserver from "./i_idl_test_observer"
```

#### Array Type
The array type is represented by T[], where **T** can be the primitive, sequenceable, interface, or array type. In C++, this type is generated as **std::vector<T>**.
The table below lists the mappings between the IDL array type and TS and C++ data types.

W
wusongqing 已提交
108 109 110
|IDL Data Type | C++ Data Type          | TS Data Type    |
|   -------              |  --------            |  --------    |
|T[]                     | std::vector<T> | T[]          |
W
wusongqing 已提交
111 112 113 114 115 116 117 118 119 120 121

#### Container Type
IDL supports two container types: List and Map. The List container is represented in the format of **List&lt;T&gt;**. The Map container is represented in the format of **Map<KT,VT>**, where **T**, **KT**, and **VT** can be of the primitive, sequenceable, interface, array, or container type.

In C++, the List container type is generated as **std::list**, and the Map container type is generated as **std::map**.

In TS, the List container type is not supported, and the Map container type is generated as **Map**.

The table below lists the mappings between the IDL container type and TS and C++ data types.

|IDL Data Type | C++ Data Type      | TS Data Type    |
W
wusongqing 已提交
122 123 124
|   --------             |  --------        |  -------     |
|List&lt;T&gt;           | std::list        | Not supported       |
|Map<KT,VT>              | std::map         | Map          |
W
wusongqing 已提交
125 126 127 128


### Specifications for Compiling IDL Files
Only one interface type can be defined in an IDL file, and the interface name must be the same as the file name. The interface definition of the IDL file is described in Backus-Naur form (BNF). The basic definition format is as follows:
W
wusongqing 已提交
129

W
wusongqing 已提交
130 131 132
```
[<*interface_attr_declaration*>]interface<*interface_name_with_namespace*>{<*method_declaration*>}
```
W
wusongqing 已提交
133

W
wusongqing 已提交
134 135
In the preceding information, <*interface_attr_declaration*> declares interface attributes. Currently, only the **oneway** attribute is supported, indicating that all methods in the interface are unidirectional. Such a method returns value without waiting for the execution to complete. This attribute is optional. If this attribute is not set, synchronous call is used. The interface name must contain the complete interface header file directory, namespace, and method declaration. Empty interfaces are not allowed.
The method declaration format in the interface is as follows:
W
wusongqing 已提交
136

W
wusongqing 已提交
137 138 139
```
[<*method_attr_declaration*>]<*result_type*><*method_declaration*>
```
W
wusongqing 已提交
140

W
wusongqing 已提交
141 142
In the preceding information, <*method_attr_declaration*> describes the interface attributes. Currently, only the **oneway** attribute is supported, indicating that the method is unidirectional. Such a method returns value without waiting for the execution to complete. This attribute is optional. If this attribute is not set, synchronous call is used. <*result_type*> indicates the type of the return value, and <*method_declaration*> indicates the method name and parameter declaration.
The parameter declaration format is as follows:
W
wusongqing 已提交
143

W
wusongqing 已提交
144 145 146
```
[<*formal_param_attr*>]<*type*><*identifier*>
```
W
wusongqing 已提交
147

W
wusongqing 已提交
148 149 150 151
The value of <*formal_param_attr*> can be **in**, **out**, or **inout**, indicating that the parameter is an input parameter, an output parameter, or both an input and an output parameter, respectively. A **oneway** method does not allow **output** or **inout** parameters or return values.

## How to Develop

G
Gloria 已提交
152 153 154 155
### Obtaining IDL
On DevEco Studio, choose **Tools > SDK Manager** to view the local installation path of the OpenHarmony SDK. The following figure uses DevEco Studio 3.0.0.993 as an example.
![SDKpath](./figures/SDKpath.png)
![SDKpath](./figures/SDKpath2.png)
W
wusongqing 已提交
156

G
Gloria 已提交
157
Go to the local installation path, choose **toolchains > 3.x.x.x** (the folder named after the version number), and check whether the executable file of IDL exists.
W
wusongqing 已提交
158

G
Gloria 已提交
159 160 161
> **NOTE**
>
> Use the SDK of the latest version. The use of an earlier version may cause errors in some statements.
W
wusongqing 已提交
162

G
Gloria 已提交
163
If the executable file does not exist, download the SDK package from the mirror as instructed in the [Release Notes](../../release-notes). The following uses [3.2 Beta3](../../release-notes/OpenHarmony-v3.2-beta3.md) as an example.
W
wusongqing 已提交
164

G
Gloria 已提交
165
For details about how to replace the SDK package, see [Full SDK Compilation Guide](../faqs/full-sdk-compile-guide.md).
W
wusongqing 已提交
166

G
Gloria 已提交
167
After obtaining the executable file, perform subsequent development steps based on your scenario.
W
wusongqing 已提交
168

G
Gloria 已提交
169
### Development Using TS
W
wusongqing 已提交
170

G
Gloria 已提交
171
#### Creating an IDL File
W
wusongqing 已提交
172

G
Gloria 已提交
173
You can use TS to create IDL files.
W
wusongqing 已提交
174

G
Gloria 已提交
175
 For example, create a file named **IIdlTestService.idl** with the following content: 
W
wusongqing 已提交
176

W
wusongqing 已提交
177
```cpp
G
Gloria 已提交
178 179 180
  interface OHOS.IIdlTestService {
      int TestIntTransaction([in] int data);
      void TestStringTransaction([in] String data);
G
Gloria 已提交
181 182
      void TestMapTransaction([in] Map<int, int> data);
      int TestArrayTransaction([in] String[] data);
G
Gloria 已提交
183
  }
W
wusongqing 已提交
184 185
```

G
Gloria 已提交
186
Run the **idl -gen-ts -d *dir* -c dir/IIdlTestService.idl** command in the folder where the executable file is located.
W
wusongqing 已提交
187

G
Gloria 已提交
188
-*dir* next to **d** is the target output folder. For example, if the target output folder is **IIdlTestServiceTs**, run the **idl -gen-ts -d IIdlTestServiceTs -c IIdlTestServiceTs/IIdlTestService.idl** command in the folder where the executable file is located. The interface file, stub file, and proxy file are generated in the *dir* directory (**IIdlTestServiceTs** directory in this example) in the execution environment.
W
wusongqing 已提交
189

G
Gloria 已提交
190 191 192
> **NOTE**
>
> The generated interface class file name must be the same as that of the .idl file. Otherwise, an error occurs during code generation.
W
wusongqing 已提交
193

G
Gloria 已提交
194
For example, for an .idl file named **IIdlTestService.idl** and target output directory named **IIdlTestServiceTs**, the directory structure is similar to the following:
W
wusongqing 已提交
195 196

```
G
Gloria 已提交
197 198 199 200 201 202
├── IIdlTestServiceTs  # IDL code output folder
│   ├── i_idl_test_service.ts  # File generated
│   ├── idl_test_service_proxy.ts  # File generated
│   ├── idl_test_service_stub.ts  # File generated
│   └── IIdlTestService.idl  # Constructed .idl file
└── idl.exe  # Executable file of IDL
W
wusongqing 已提交
203 204 205 206 207 208
```

#### Exposing Interfaces on the Server

The stub class generated by IDL is an abstract implementation of the interface class and declares all methods in the IDL file.

W
wusongqing 已提交
209
```ts
W
wusongqing 已提交
210 211
import {testIntTransactionCallback} from "./i_idl_test_service";
import {testStringTransactionCallback} from "./i_idl_test_service";
G
Gloria 已提交
212 213
import {testMapTransactionCallback} from "./i_idl_test_service";
import {testArrayTransactionCallback} from "./i_idl_test_service";
W
wusongqing 已提交
214 215 216 217 218 219 220 221
import IIdlTestService from "./i_idl_test_service";
import rpc from "@ohos.rpc";

export default class IdlTestServiceStub extends rpc.RemoteObject implements IIdlTestService {
    constructor(des: string) {
        super(des);
    }
    
G
Gloria 已提交
222 223
    async onRemoteMessageRequest(code: number, data, reply, option): Promise<boolean> {
        console.log("onRemoteMessageRequest called, code = " + code);
W
wusongqing 已提交
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
        switch(code) {
            case IdlTestServiceStub.COMMAND_TEST_INT_TRANSACTION: {
                let _data = data.readInt();
                this.testIntTransaction(_data, (errCode, returnValue) => {
                    reply.writeInt(errCode);
                    if (errCode == 0) {
                        reply.writeInt(returnValue);
                    }
                });
                return true;
            }
            case IdlTestServiceStub.COMMAND_TEST_STRING_TRANSACTION: {
                let _data = data.readString();
                this.testStringTransaction(_data, (errCode) => {
                    reply.writeInt(errCode);
                });
                return true;
            }
G
Gloria 已提交
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
            case IdlTestServiceStub.COMMAND_TEST_MAP_TRANSACTION: {
                let _data = new Map();
                let _dataSize = data.readInt();
                for (let i = 0; i < _dataSize; ++i) {
                    let key = data.readInt();
                    let value = data.readInt();
                    _data.set(key, value);
                }
                this.testMapTransaction(_data, (errCode) => {
                    reply.writeInt(errCode);
                });
                return true;
            }
            case IdlTestServiceStub.COMMAND_TEST_ARRAY_TRANSACTION: {
                let _data = data.readStringArray();
                this.testArrayTransaction(_data, (errCode, returnValue) => {
                    reply.writeInt(errCode);
                    if (errCode == 0) {
                        reply.writeInt(returnValue);
                    }
                });
                return true;
            }
W
wusongqing 已提交
265 266 267 268 269 270 271 272 273 274
            default: {
                console.log("invalid request code" + code);
                break;
            }
        }
        return false;
    }
    
    testIntTransaction(data: number, callback: testIntTransactionCallback): void{}
    testStringTransaction(data: string, callback: testStringTransactionCallback): void{}
G
Gloria 已提交
275 276
    testMapTransaction(data: Map<number, number>, callback: testMapTransactionCallback): void{}
    testArrayTransaction(data: string[], callback: testArrayTransactionCallback): void{}
W
wusongqing 已提交
277 278 279

    static readonly COMMAND_TEST_INT_TRANSACTION = 1;
    static readonly COMMAND_TEST_STRING_TRANSACTION = 2;
G
Gloria 已提交
280 281
    static readonly COMMAND_TEST_MAP_TRANSACTION = 3;
    static readonly COMMAND_TEST_ARRAY_TRANSACTION = 4;
W
wusongqing 已提交
282 283 284
}
```

G
Gloria 已提交
285
You need to inherit the interface class defined in the IDL file and implement the methods in the class. The following code snippet shows how to inherit the **IdlTestServiceStub** interface class and implement the **testIntTransaction**, **testStringTransaction**, **testMapTransaction**, and **testArrayTransaction** methods.  
W
wusongqing 已提交
286

W
wusongqing 已提交
287
```ts
W
wusongqing 已提交
288 289
import {testIntTransactionCallback} from "./i_idl_test_service"
import {testStringTransactionCallback} from "./i_idl_test_service"
G
Gloria 已提交
290 291
import {testMapTransactionCallback} from "./i_idl_test_service";
import {testArrayTransactionCallback} from "./i_idl_test_service";
W
wusongqing 已提交
292 293 294 295 296 297 298 299 300 301 302 303 304
import IdlTestServiceStub from "./idl_test_service_stub"


class IdlTestImp extends IdlTestServiceStub {

    testIntTransaction(data: number, callback: testIntTransactionCallback): void
    {
        callback(0, data + 1);
    }
    testStringTransaction(data: string, callback: testStringTransactionCallback): void
    {
        callback(0);
    }
G
Gloria 已提交
305 306 307 308 309 310 311 312
    testMapTransaction(data: Map<number, number>, callback: testMapTransactionCallback): void
    {
        callback(0);
    }
    testArrayTransaction(data: string[], callback: testArrayTransactionCallback): void
    {
        callback(0, 1);
    }
W
wusongqing 已提交
313 314 315 316 317
}
```

After the service implements the interface, the interface needs to be exposed to the client for connection. If your service needs to expose this interface, extend **Ability** and implement **onConnect()** to return **IRemoteObject** so that the client can interact with the service process. The following code snippet shows how to expose the **IRemoteAbility** interface to the client:

W
wusongqing 已提交
318
```ts
W
wusongqing 已提交
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
export default {
    onStart() {
        console.info('ServiceAbility onStart');
    },
    onStop() {
        console.info('ServiceAbility onStop');
    },
    onCommand(want, startId) {
        console.info('ServiceAbility onCommand');
    },
    onConnect(want) {
        console.info('ServiceAbility onConnect');
        try {
            console.log('ServiceAbility want:' + typeof(want));
            console.log('ServiceAbility want:' + JSON.stringify(want));
            console.log('ServiceAbility want name:' + want.bundleName)
        } catch(err) {
G
Gloria 已提交
336
            console.log('ServiceAbility error:' + err)
W
wusongqing 已提交
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
        }
        console.info('ServiceAbility onConnect end');
        return new IdlTestImp('connect');
    },
    onDisconnect(want) {
        console.info('ServiceAbility onDisconnect');
        console.info('ServiceAbility want:' + JSON.stringify(want));
    }
};
```

#### Calling Methods from the Client for IPC

When the client calls **connectAbility()** to connect to a Service ability, the **onConnect** callback in **onAbilityConnectDone** of the client receives the **IRemoteObject** instance returned by the **onConnect()** method of the Service ability. The client and Service ability are in different applications. Therefore, the directory of the client application must contain a copy of the .idl file (the SDK automatically generates the proxy class). The **onConnect** callback then uses the **IRemoteObject** instance to create the **testProxy** instance of the **IdlTestServiceProxy** class and calls the related IPC method. The sample code is as follows:

W
wusongqing 已提交
352
```ts
W
wusongqing 已提交
353 354 355 356 357
import IdlTestServiceProxy from './idl_test_service_proxy'
import featureAbility from '@ohos.ability.featureAbility';

function callbackTestIntTransaction(result: number, ret: number): void {
  if (result == 0 && ret == 124) {
G
Gloria 已提交
358
    console.log('case 1 success');
W
wusongqing 已提交
359 360 361 362 363
  }
}

function callbackTestStringTransaction(result: number): void {
  if (result == 0) {
G
Gloria 已提交
364
    console.log('case 2 success');
W
wusongqing 已提交
365 366 367
  }
}

G
Gloria 已提交
368 369 370 371 372 373 374 375 376 377 378 379
function callbackTestMapTransaction(result: number): void {
  if (result == 0) {
    console.log('case 3 success');
  }
}

function callbackTestArrayTransaction(result: number, ret: number): void {
  if (result == 0 && ret == 124) {
    console.log('case 4 success');
  }
}

W
wusongqing 已提交
380 381 382
var onAbilityConnectDone = {
  onConnect:function (elementName, proxy) {
    let testProxy = new IdlTestServiceProxy(proxy);
G
Gloria 已提交
383 384 385
    let testMap = new Map();
    testMap.set(1, 1);
    testMap.set(1, 2);
W
wusongqing 已提交
386 387
    testProxy.testIntTransaction(123, callbackTestIntTransaction);
    testProxy.testStringTransaction('hello', callbackTestStringTransaction);
G
Gloria 已提交
388 389
    testProxy.testMapTransaction(testMap, callbackTestMapTransaction);
    testProxy.testArrayTransaction(['1','2'], callbackTestMapTransaction);
W
wusongqing 已提交
390 391
  },
  onDisconnect:function (elementName) {
G
Gloria 已提交
392
    console.log('onDisconnectService onDisconnect');
W
wusongqing 已提交
393 394
  },
  onFailed:function (code) {
G
Gloria 已提交
395
    console.log('onDisconnectService onFailed');
W
wusongqing 已提交
396 397 398 399 400
  }
};

function connectAbility: void {
    let want = {
G
Gloria 已提交
401 402
        bundleName: 'com.example.myapplicationidl',
        abilityName: 'com.example.myapplicationidl.ServiceAbility'
W
wusongqing 已提交
403 404 405 406 407 408 409 410 411 412 413 414
    };
    let connectionId = -1;
    connectionId = featureAbility.connectAbility(want, onAbilityConnectDone);
}


```

#### Transferring a sequenceable Object During IPC

You can send a class from one process to another through IPC interfaces. However, you must ensure that the peer can use the code of this class and this class supports the **marshalling** and **unmarshalling** methods. OpenHarmony uses **marshalling** and **unmarshalling** to serialize and deserialize objects into objects that can be identified by each process.

G
Gloria 已提交
415
**To create a class that supports the sequenceable type, perform the following operations:**
W
wusongqing 已提交
416 417 418 419 420 421

1. Implement the **marshalling** method, which obtains the current state of the object and serializes the object into a **Parcel** object.
2. Implement the **unmarshalling** method, which deserializes the object from a **Parcel** object.

The following is an example of the **MySequenceable** class code:

W
wusongqing 已提交
422
```ts
W
wusongqing 已提交
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
import rpc from '@ohos.rpc';
export default class MySequenceable {
    constructor(num: number, str: string) {
        this.num = num;
        this.str = str;
    }
    getNum() : number {
        return this.num;
    }
    getString() : string {
        return this.str;
    }
    marshalling(messageParcel) {
        messageParcel.writeInt(this.num);
        messageParcel.writeString(this.str);
        return true;
    }
    unmarshalling(messageParcel) {
        this.num = messageParcel.readInt();
        this.str = messageParcel.readString();
        return true;
    }
    private num;
    private str;
}
```