For Developers‎ > ‎Design Documents‎ > ‎Mojo‎ > ‎

Type Mapping in C++

Overview

Mojom serves as a convenient IDL for specifying message wire formats in a language-agnostic way. In a majority of cases it's probably sufficient to use the primitives baked into mojom along with the basic level of validation they provide.

In some cases however, it's convenient to map mojom types to more useful C++ types. Maybe your simple data type has a host of useful utility methods (e.g. gfx::Rect), or maybe you want custom validation for your data type. This is where typemaps come in.

Typemaps

A typemap is a mechanism by which we can direct the mojom bindings generator to substitute a custom type in place of some mojom struct (or union, enum) when generating bindings which reference that mojom type. This works in conjunction with some template magic in order to make the right thing happen efficiently at runtime.

This document will heavily reference the typemap we have in place to map between url::mojom::Url and GURL, allowing us to use GURL seamlessly across mojom interfaces while retaining its useful validation behavior as well as mojom's language-agnostic wire format. The mojom Url type is defined as follows:

module url.mojom;

struct Url {
  string url;
};

NOTE: although a URL's wire format is otherwise a simple string, wrapping it in a struct attaches additional type information at compile-time which we can take advantage for things like type mapping.

Now consider an interface which consumes this data type:

import "url/mojo/url.mojom";

interface Frobinator {
  Fetch(url.mojom.Url url);
};

Normally this would generate a C++ interface like the one below:

class Frobinator {
 public:
  virtual void Fetch(url::mojom::UrlPtr url) = 0;
};

You would then have to implement this interface and fumble around with converting between the url->url string and a GURL, remembering to validate the value in the process. Ain't nobody got time for that.

Fortunately we automatically apply a typemap to url.mojom.Url (as demonstrated throughout the rest of this document), resulting in the following much nicer interface being generated instead:

class Frobinator {
 public:
  virtual void Fetch(const GURL& url) = 0;
};

Even better is that implementations of this interface can safely assume that the value of url has already been validated by common validation code for any GURL used as a url.mojom.Url value. If an incoming message fails such validation, your implementation will never be called (see Message Validation & Error Handling.)

Please Tell Me How I Can Use This Wonderful Feature

If you insist! In the common case, using a typemap requires a number of steps which are outlined in this document:
  • Defining a mojo::StructTraits<DataViewType, T> specialization which maps the mojom struct whose data view type is DataViewType to type T. EnumTraits and UnionTraits are also supported to customize enums and unions. They are similar to StructTraits, please see the documentation in the corresponding C++ header files for more details.
  • Creating a typemap file describing the mapping and its effect on dependencies
  • Adding the typemap to a global build configuration for GN

Step 1: Defining StructTraits

A specialization StructTraits<DataViewType, T> are necessary to map between your custom type T and a mojom struct. DataViewType is the corresponding data view type of the mojom struct. For example, if the mojom struct is url.mojom.UrlDataViewType will be url::mojom::UrlDataView, which can also be referred to by url::mojom::Url::DataView (in chromium) and url::mojom::blink::Url::DataView (in blink). A specialization StructTraits<DataViewType, T> needs to implement a few things:
  •  One static getter for each field in the Mojom type. These should be of the form:
static <return type> <field name>(const T& input);

and should return a serializable form of the named field as extracted from input.

During serialization, getters for string/struct/array/map/union fields are called twice (one for size calculation and one for actual serialization). If you want to return a value (as opposed to a reference) from these getters, you have to be sure that constructing and copying the returned object is really cheap. Getters for fields of other types are called once.
  • A static Read() method to set the contents of a T instance from a DataViewType.
static bool Read(<MojomType>DataView data, T* output);

The generated DataViewType provides a convenient, inexpensive view of a serialized struct's field data. The caller guarantees that !data.is_null().
Returning false indicates invalid incoming data and causes the message pipe receiving it to be disconnected. Therefore, you can do custom validation for T in this method.
  • [Optional] A static IsNull() method indicating whether a given T instance is null. 
static bool IsNull(const T& input);

If this method returns true, it is guaranteed that none of the getters will be called for the same input. So you don't have to check whether input is null in those getters. If it is not defined, T instances are always considered non-null.

[Optional] A static SetToNull() method to set the contents of a given T instance to null.

static void SetToNull(T* output);

When a null serialized struct is received, the deserialization code calls this method instead of Read().
NOTE: It is to set the object pointed to by output to a null state, not to set the output pointer itself to null. "Null state" means whatever state you think it makes sense to map a null serialized struct to.

If it is not defined, null is not allowed to be converted to T. In that case, an incoming null value is considered invalid and causes the message pipe to be disconnected.
  • [Optional] As mentioned above, getters for string/struct/array/map/union fields are called multiple times (twice to be exact). If you need to do some expensive calculation/conversion, you probably want to cache the result across multiple calls. You can introduce an arbitrary context object by adding two optional methods:
static void* SetUpContext(const T& input);
static void TearDownContext(const T& input, void* context);

And then you append a second parameter, void* context, to getters:

static <return type> <field name>(const T& input, void* context);

If a T instance is not null, the serialization code will call SetUpContext() at the beginning, and pass the resulting context pointer to getters. After serialization is done, it calls TearDownContext() so that you can do any necessary cleanup.

In the description above, methods having an input parameter define it as const reference of T. Actually, it can be a non-const reference of T too. E.g., if T contains Mojo handles or interfaces whose ownership needs to be transferred. Correspondingly, it requires you to always give non-const T reference/value to the Mojo bindings for serialization:
  • if T is used in the type_mappings section of a typemap config file, you need to declare it as "move_only" or "copyable_pass_by_value":
type_mappings = [ "MojomType=T[move_only]" ]
  • if another type U's StructTraits/UnionTraits has a getter for T, it needs to return non-const reference/value.

See the StructTraits Reference below for more details on defining StructTraits specializations.

The complete StructTraits which map between GURL and url.mojom.Url are defined below:
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef URL_MOJO_URL_GURL_STRUCT_TRAITS_H_
#define URL_MOJO_URL_GURL_STRUCT_TRAITS_H_

#include "base/strings/string_piece.h"
#include "url/gurl.h"
#include "url/mojo/url.mojom.h"
#include "url/url_constants.h"

namespace mojo {

template <>
struct StructTraits<url::mojom::UrlDataView, GURL> {
  static base::StringPiece url(const GURL& r) {
    if (r.possibly_invalid_spec().length() > url::kMaxURLChars ||
        !r.is_valid()) {
      return base::StringPiece();
    }

    return base::StringPiece(r.possibly_invalid_spec().c_str(),
                             r.possibly_invalid_spec().length());
  }
  static bool Read(url::mojom::UrlDataView data, GURL* out) {
    base::StringPiece url_string;
    if (!data.ReadUrl(&url_string))
      return false;

    if (url_string.length() > url::kMaxURLChars)
      return false;

    *out = GURL(url_string);
    if (!url_string.empty() && !out->is_valid())
      return false;

    return true;
  }
};

}

#endif  // URL_MOJO_URL_GURL_STRUCT_TRAITS_H_

Step 2: Creating a typemap file

A typemap file tells the bindings generator about your type mapping and points it to the code (e.g. the StructTraits) necessary to make the mapping work properly. Typemap files are secretly just GNI files. Here's the typemap file for GURL (see ToT):

mojom = "//url/mojo/url.mojom"
public_headers = [ "//url/gurl.h" ] traits_headers = [ "//url/mojo/url_gurl_struct_traits.h" ] deps = [ "//url", ] type_mappings = [ "url.mojom.Url=GURL" ]

A reference for each of the variables above:
  • mojom: The corresponding mojom file.
  • public_headers:  These are additional headers required by any dependency on url.mojom.Url now that it's mapped to GURL. Any headers listed here will be #included by generated mojom headers which reference that type.
  • traits_headers:  Any headers which contain StructTraits definitions relevant to this typemap. These are included by generated bindings implementation code.
  • deps:  Compile-time dependencies introduced by this typemap.
  • type_mappings:  A list of actual type mappings to apply. These are strings in the format of "mojomType=customType" (sorry, GN doesn't have a dictionary data type!). You can also specify attributes after customType, in square brackets and separated by comma. Supported attributes are:
    • move_only: customType is move-only and should be passed by value. move_only is transitive. A container (array or map) containing customType will be considered move-only and passed by value, too.
    • copyable_pass_by_value: customType is copyable but should be passed by value. Unlike move_only, it is not transitive. A container containing customType will be passed by const reference. Enum custom types are treated as copyable_pass_by_value by default.
    • nullable_is_same_typeBy default, mojomType (non-nullable) will be mapped to customType; while mojomType? will be mapped to base::Optional<customType>. You could use attribute nullable_is_same_type if you want to map mojomType/mojomType? to the same type. In this case, the corresponding StructTraits<> must implement IsNull()/SetToNull() methods.
Other possible variables:
  • sources: Any .cc files which contain StructTraits implementation relevant to this typemap.
  • public_deps: Public dependencies (see GN documentation).

Step 3: Global build configuration

In order to avoid a mess of confusing ad hoc typemap configurations, we've opted for a set of centralized configurations (currently one for Chromium and one for Blink) which are implied when generating bindings. In order to enable a new typemap, it must be added to one or both of these configurations. For the sake of cleanliness this is done with an intermediate GNI file, such as the the one defined for GURL in //url/mojo/typemaps.gni:
typemaps = [ "//url/mojo/origin.typemap", "//url/mojo/gurl.typemap", ]

This GNI file is in turn added to the _typemap_imports variable in one of the centralized configurations.

StructTraits Reference

Field Return Types

Each static getter method for a struct field must return a type which can be used as a data source for that field during serialization. This is a quick reference which maps mojom field type to expected getter return type:

Mojom Type Return Type
 boolbool
 int8int8_t
 uint8uint8_t 
 int16int16_t 
 uint16uint16_t 
 int32int32_t 
 uint32uint32_t
 int64int64_t
 uint64uint64_t 
 floatfloat
 doubledouble
 handle        mojo::ScopedHandle
 handle<message_pipe>mojo::ScopedMessagePipeHandle
 handle<data_pipe_consumer>    mojo::ScopedDataPipeConsumerHandle
 handle<data_pipe_producer>mojo::ScopedDataPipeProducerHandle
 handle<shared_buffer>mojo::ScopedSharedBufferHandle
 FooInterfaceFooInterfacePtr
 FooInterface&FooInterfaceRequest
 associated FooInterfaceFooInterfaceAssociatedPtr
 associated FooInterface&FooInterfaceAssociatedRequest
  stringValue or reference of any type T that has a StringTraits defined.
Supported by default: base::StringPiece, std::string, WTF::String (in blink).
 array<T>Value or reference of any type that has an ArrayTraits defined.
Supported by default: std::vector, CArrayWTF::Vector (in blink).
  map<K, V>Value or reference of any type that has an MapTraits defined.
Supported by default: std::map, std::unordered_map, WTF::HashMap (in blink).
  FooEnumValue of any type that has an EnumTraits defined.
Supported by default: FooEnum.
  FooStructValue or reference of any type that has a StructTraits defined.
Supported by default: FooStructPtr.
  FooUnionValue or reference of any type that has a UnionTraits defined.
Supported by default: FooUnionPtr

For any nullable string/struct/array/map/union field you could also return value or reference of base::Optional<T>/WTF::Optional<T>, if T has the right *Traits defined.

DataView Interfaces

Every mojom struct has a corresponding DataView interface type (for example, url::mojom::Url has a url::mojom::UrlDataView). This is a lightweight wrapper around the serialized data of a struct's value within a received message, providing a view of the struct without necessarily copying data out of the message. The generated DataView has an appropriate accessor for every field in its mojom struct. StructTraits<DataViewType,T>::Read implementations use this interface to extract data from incoming structs.

Below is a reference which maps mojom field type to its accessor's signature in a generated DataView interface:

Field definition DataView accessor signature.
 bool foo;bool foo() const;
 int8 foo;int8_t foo() const;
 uint8 foo;uint8_t foo() const;
 int16 foo;int16_t foo() const;
 uint16 foo;uint16_t foo() const;
 int32 foo;int32_t foo() const;
 uint32 foo;uint32_t foo() const;
 int64 foo;int64_t foo() const;
 uint64 foo;uint64_t foo() const;
 float foo;float foo() const;
 double foo;double foo() const;
 handle foo;mojo::ScopedHandle TakeFoo();
  handle<message_pipe> foo;mojo::ScopedMessagePipeHandle TakeFoo();
  handle<data_pipe_consumer> foo;mojo::ScopedDataPipeConsumerHandle TakeFoo();
  handle<data_pipe_producer> foo;mojo::ScopedDataPipeProducerHandle TakeFoo();
  handle<shared_buffer> foo;mojo::ScopedSharedBufferHandle TakeFoo();
  FooInterface foo;template <typename T>
T TakeFoo();

T must be FooInterfacePtr from either the chromium or blink variant.
  FooInterface& foo;template <typename T>
T TakeFoo();

T must be FooInterfaceRequest from either the chromium or blink variant.
  associated FooInterface foo;template <typename T>
T TakeFoo();

T must be FooInterfaceAssociatedPtr from either the chromium or blink variant.
  associated FooInterface& foo;template <typename T>
T TakeFoo();

T must be FooInterfaceAssociatedRequest from either the chromium or blink variant.
 string foo;void GetFooDataView(StringDataView* output);

template <typename T>

bool ReadFoo(T* output);

You can use any T that has a StringTraits defined.
Supported by default: base::StringPiece, std::string, WTF::String (in blink).
 array<T> foo;void GetFooDataView(ArrayDataView<TDataView>* output);

template <typename T>

bool ReadFoo(T* output);

You can use any T that has an ArrayTraits defined.
Supported by default: std::vector, CArray, WTF::Vector (in blink).
  map<K, V> foo;void GetFooDataView(MapDataView<KDataView, VDataView>* output);

template <typename T>

bool ReadFoo(T* output);

You can use any that has an MapTraits defined.
Supported by default: std::mapstd::unordered_mapWTF::HashMap (in blink).
  FooEnum foo;FooEnum foo() const;

template <typename T>

bool ReadFoo(T* output);

With the second method, you can use any T that has an EnumTraits defined.
Supported by default: FooEnum.
 FooStruct foo;void GetFooDataView(FooStructDataView* output);

template <typename T>

bool ReadFoo(T* output);

You can use any T that has a StructTraits defined.
Supported by default: FooStructPtr.
  FooUnion foo;void GetFooDataView(FooUnionDataView* output);

template <typename T>

bool ReadFoo(T* output);

You can use any T that has a UnionTraits defined.
Supported by default: FooUnionPtr.

Using Existing IPC::ParamTraits

In some circumstances, there may be a C++ enum, struct or class that you want to use in a mojom. If the mojo interface will only called from C++, then instead of duplicating the enum/struct/class in a mojom and writing a typemap or converters you can use the (often existing) IPC::ParamTraits definition. To repeat, this should only be done if the interface will only be used by C++ and some factors prevent you from moving the definition to a mojom.


In order to do this, you must declare a placeholder enum/struct type in mojom somewhere, like so:

[Native]
enum Frobinator;

or

[Native]
struct Frobinator;

The rest of your mojom will use this type name when referring to the type in question. Note that you cannot provide a mojom wire format definition here, as the format is defined and owned entirely by your existing IPC::ParamTraits specialization.

You must go through all the steps for using typemaps as outlined above, except for Step 1 which should be omitted (you already have traits!). Your typemap's traits_headers must provide the declaration of your ParamTraits specialization. If that does not also include the definition of said ParamTraits, the definition must be provided by the typemap's deps target.

Finally, you must add a new static method to your ParamTraits:

static void GetSize(base::PickleSizer* sizer, const param_type& p) {
  // ...
}

base::PickleSizer has an interface analogous to base::Pickle, with the exception that instead of writing bytes somewhere it simply accumulates information about how many bytes would be written. There are several examples of this traits implementation in common IPC traits defined here. This new method is necessary because Mojo message buffers are preallocated before serialization and Mojo needs to know how big your data will be.

IPC_STRUCT_TRAITS

You might use IPC_STRUCT_TRAITS_(BEGIN|MEMBER|END) macros instead of using IPC::ParamTraits directly. It's also a use case of [Native].
Here is an example which has been used practically.

Here is an message header having IPC_STRUCT_TRAITS_*: resource_messages.h.
IPC_STRUCT_TRAITS_BEGIN(content::ResourceRequest)
  IPC_STRUCT_TRAITS_MEMBER(method)
  IPC_STRUCT_TRAITS_MEMBER(url)
...
IPC_STRUCT_TRAITS_END()

content::ResourceRequest is defined as a common struct here: resource_request.h.
namespace content { struct CONTENT_EXPORT ResourceRequest { ResourceRequest();
...
};

}  // namespace content

Now, we can map ResourceRequest to a mojo struct. Here, URLRequest is defined in url_loader.mojom as a representation of ResourceRequest in mojom files.
module content.mojom; [Native] struct URLRequest;


In a typemap file (url_request.typemap), it's mapped to content::ResourceRequest. public_headers contains a path to the definition of the struct, and traits_headers has a path to a file where IPC_STRUCT_TRAITS_... is defined.
mojom = "//content/common/url_loader.mojom"
public_headers = [ "//content/common/resource_request.h" ]
traits_headers = [ "//content/common/resource_messages.h" ]
...type_mappings = [ "content.mojom.URLRequest=content::ResourceRequest" ]


Comments