201 lines
9.3 KiB
Markdown
201 lines
9.3 KiB
Markdown
# Generating C++ Binder Interfaces with `aidl-cpp`
|
||
|
||
## Background
|
||
|
||
“aidl” refers to several related but distinct concepts:
|
||
|
||
- the AIDL interface [definition language](http://developer.android.com/guide/components/aidl.html)
|
||
- .aidl files (which contain AIDL)
|
||
- the aidl generator which transforms AIDL into client/server IPC interfaces
|
||
|
||
The _aidl generator_ is a command line tool that generates client and server
|
||
stubs for Binder interfaces from a specification in a file with the .aidl
|
||
extension. For Java interfaces, the executable is called `aidl` while for C++
|
||
the binary is called `aidl-cpp`. In this document, we’ll use AIDL to describe
|
||
the language of .aidl files and _aidl generator_ to refer to the code generation
|
||
tool that takes an .aidl file, parses the AIDL, and outputs code.
|
||
|
||
Previously, the _aidl generator_ only generated Java interface/stub/proxy
|
||
objects. C++ Binder interfaces were handcrafted with various degrees of
|
||
compatibility with the Java equivalents. The Brillo project added support for
|
||
generating C++ with the _aidl generator_. This generated C++ is cross-language
|
||
compatible (e.g. Java clients are tested to interoperate with native services).
|
||
|
||
## Overview
|
||
|
||
This document describes how C++ generation works with attention to:
|
||
|
||
- build interface
|
||
- cross-language type mapping
|
||
- C++ parcelables
|
||
- cross-language error reporting
|
||
- cross-language null reference handling
|
||
- cross-language integer constants
|
||
|
||
## Detailed Design
|
||
|
||
### Build Interface
|
||
|
||
Write AIDL in .aidl files and add them to `LOCAL_SRC_FILES` in your Android.mk.
|
||
If your build target is a binary (e.g. you include `$(BUILD_SHARED_LIBRARY)`),
|
||
then the generated code will be C++, not Java.
|
||
|
||
AIDL definitions should be hosted from the same repository as the
|
||
implementation. Any system that needs the definition will also need the
|
||
implementation (for both parcelables and interface). If there are multiple
|
||
implementations (i.e. one in Java and one in C++), keep the definition with the
|
||
native implementation. Android
|
||
[now has systems](https://developers.google.com/brillo/?hl=en) that run the
|
||
native components of the system without the Java.
|
||
|
||
If you use an import statement in your AIDL, even from the same package, you
|
||
need to add a path to `LOCAL_AIDL_INCLUDES`. This path should be relative to
|
||
the root of the Android tree. For instance, a file IFoo.aidl defining
|
||
com.example.IFoo might sit in a folder hierarchy
|
||
something/something-else/com/example/IFoo.aidl. Then we would write:
|
||
|
||
```
|
||
LOCAL_AIDL_INCLUDES := something/something-else
|
||
```
|
||
|
||
Generated C++ ends up in nested namespaces corresponding to the interface’s
|
||
package. The generated header also corresponds to the interface package. So
|
||
com.example.IFoo becomes ::com::example::IFoo in header “com/example/IFoo.h”.
|
||
|
||
Similar to how Java works, the suffix of the path to a .aidl file must match
|
||
the package. So if IFoo.aidl declares itself to be in package com.example, the
|
||
folder structure (as given to `LOCAL_SRC_FILES`) must look like:
|
||
`some/prefix/com/example/IFoo.aidl`.
|
||
|
||
To generate code from .aidl files from another build target (e.g. another
|
||
binary or java), just add a relative path to the .aidl files to
|
||
`LOCAL_SRC_FILES`. Remember that importing AIDL works the same, even for code
|
||
in other directory hierarchies: add the include root path relative to the
|
||
checkout root to `LOCAL_AIDL_INCLUDES`.
|
||
|
||
### Type Mapping
|
||
|
||
The following table summarizes the equivalent C++ types for common Java types
|
||
and whether those types may be used as in/out/inout parameters in AIDL
|
||
interfaces.
|
||
|
||
| Java Type | C++ Type | inout | Notes |
|
||
|-----------------------|---------------------|-------|-------------------------------------------------------|
|
||
| boolean | bool | in | "These 8 types are all considered primitives. |
|
||
| byte | int8\_t | in | |
|
||
| char | char16\_t | in | |
|
||
| int | int32\_t | in | |
|
||
| long | int64\_t | in | |
|
||
| float | float | in | |
|
||
| double | double | in | |
|
||
| String | String16 | in | Supports null references. |
|
||
| android.os.Parcelable | android::Parcelable | inout | |
|
||
| T extends IBinder | sp<T> | in | |
|
||
| Arrays (T[]) | vector<T> | inout | May contain only primitives, Strings and parcelables. |
|
||
| List<String> | vector<String16> | inout | |
|
||
| PersistableBundle | PersistableBundle | inout | binder/PersistableBundle.h |
|
||
| List<IBinder> | vector<sp<IBinder>> | inout | |
|
||
| FileDescriptor | ScopedFd | inout | nativehelper/ScopedFd.h |
|
||
|
||
Note that java.util.Map and java.utils.List are not good candidates for cross
|
||
language communication because they may contain arbitrary types on the Java
|
||
side. For instance, Map is cast to Map<String,Object> and then the object
|
||
values dynamically inspected and serialized as type/value pairs. Support
|
||
exists for sending arbitrary Java serializables, Android Bundles, etc.
|
||
|
||
### C++ Parcelables
|
||
|
||
In Java, a parcelable should extend android.os.Parcelable and provide a static
|
||
final CREATOR field that acts as a factory for new instances/arrays of
|
||
instances of the parcelable. In addition, in order to be used as an out
|
||
parameter, a parcelable class must define a readFromParcel method.
|
||
|
||
In C++, parcelables must implement android::Parcelable from binder/Parcelable.h
|
||
in libbinder. Parcelables must define a constructor that takes no arguments.
|
||
In order to be used in arrays, a parcelable must implement a copy or move
|
||
constructor (called implicitly in vector).
|
||
|
||
The C++ generator needs to know what header defines the C++ parcelable. It
|
||
learns this from the `cpp_header` directive shown below. The generator takes
|
||
this string and uses it as the literal include statement in generated code.
|
||
The idea here is that you generate your code once, link it into a library along
|
||
with parcelable implementations, and export appropriate header paths. This
|
||
header include must make sense in the context of the Android.mk that compiles
|
||
this generated code.
|
||
|
||
```
|
||
// ExampleParcelable.aidl
|
||
package com.example.android;
|
||
|
||
// Native types must be aliased at their declaration in the appropriate .aidl
|
||
// file. This allows multiple interfaces to use a parcelable and its C++
|
||
// equivalent without duplicating the mapping between the C++ and Java types.
|
||
// Generator will assume bar/foo.h declares class
|
||
// com::example::android::ExampleParcelable
|
||
parcelable ExampleParcelable cpp_header "bar/foo.h";
|
||
```
|
||
|
||
### Null Reference Handling
|
||
|
||
The aidl generator for both C++ and Java languages has been expanded to
|
||
understand nullable annotations.
|
||
|
||
Given an interface definition like:
|
||
|
||
```
|
||
interface IExample {
|
||
void ReadStrings(String neverNull, in @nullable String maybeNull);
|
||
};
|
||
```
|
||
|
||
the generated C++ header code looks like:
|
||
|
||
```
|
||
class IExample {
|
||
android::binder::Status ReadStrings(
|
||
const android::String16& in_neverNull,
|
||
const std::unique_ptr<android::String16>& in_maybeNull);
|
||
};
|
||
```
|
||
|
||
Note that by default, the generated C++ passes a const reference to the value
|
||
of a parameter and rejects null references with a NullPointerException sent
|
||
back the caller. Parameters marked with @nullable are passed by pointer,
|
||
allowing native services to explicitly control whether they allow method
|
||
overloading via null parameters. Java stubs and proxies currently do nothing
|
||
with the @nullable annotation.
|
||
|
||
### Exception Reporting
|
||
|
||
C++ methods generated by the aidl generator return `android::binder::Status`
|
||
objects, rather than `android::status_t`. This Status object allows generated
|
||
C++ code to send and receive exceptions (an exception type and a String16 error
|
||
message) since we do not use real exceptions in C++. More background on Status
|
||
objects can be found here.
|
||
|
||
For legacy support and migration ease, the Status object includes a mechanism
|
||
to report a `android::status_t`. However, that return code is interpreted by a
|
||
different code path and does not include a helpful String message.
|
||
|
||
For situations where your native service needs to throw an error code specific
|
||
to the service, use `Status::fromServiceSpecificError()`. This kind of
|
||
exception comes with a helpful message and an integer error code. Make your
|
||
error codes consistent across services by using interface constants (see
|
||
below).
|
||
|
||
### Integer Constants
|
||
|
||
AIDL has been enhanced to support defining integer constants as part of an
|
||
interface:
|
||
|
||
```
|
||
interface IMyInterface {
|
||
const int CONST_A = 1;
|
||
const int CONST_B = 2;
|
||
const int CONST_C = 3;
|
||
...
|
||
}
|
||
```
|
||
|
||
These map to appropriate 32 bit integer class constants in Java and C++ (e.g.
|
||
`IMyInterface.CONST_A` and `IMyInterface::CONST_A` respectively).
|