[pigeon] Added basic C++ support (#476)

diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index 6785e20..09de8d6 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 3.1.0
+
+* [c++] Adds C++ code generator.
+
 ## 3.0.4
 
 * [objc] Simplified some code output, including avoiding Xcode warnings about
@@ -35,8 +39,8 @@
 ## 2.0.2
 
 * Fixes Java crash for nullable nested type.
-
-* ## 2.0.1
+  
+## 2.0.1
 
 * Adds support for TaskQueues for serial background execution.
 
diff --git a/packages/pigeon/CONTRIBUTING.md b/packages/pigeon/CONTRIBUTING.md
index 39266c0..2dc8e70 100644
--- a/packages/pigeon/CONTRIBUTING.md
+++ b/packages/pigeon/CONTRIBUTING.md
@@ -23,6 +23,7 @@
 * [java_generator.dart](./lib/java_generator.dart) - The Java code generator.
 * [objc_generator.dart](./lib/objc_generator.dart) - The Objective-C code
   generator (header and source files).
+* [cpp_generator.dart](./lib/cpp_generator.dart) - The C++ code generator.
 * [generator_tools.dart](./lib/generator_tools.dart) - Shared code between generators.
 * [pigeon_cl.dart](./lib/pigeon_cl.dart) - The top-level function executed by
   the command line tool in [bin/][./bin].
diff --git a/packages/pigeon/README.md b/packages/pigeon/README.md
index 149e2c1..1be5647 100644
--- a/packages/pigeon/README.md
+++ b/packages/pigeon/README.md
@@ -5,8 +5,9 @@
 
 ## Supported Platforms
 
-Currently Pigeon only supports generating Objective-C code for usage on iOS and
-Java code for Android.  The Objective-C code is
+Currently Pigeon supports generating Objective-C code for usage on iOS, Java
+code for Android, and has experimental support for C++ for Windows. The
+Objective-C code is
 [accessible to Swift](https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/importing_objective-c_into_swift)
 and the Java code is accessible to Kotlin.
 
@@ -18,32 +19,39 @@
 
 ## Usage
 
-### Flutter calling into iOS Steps
+1) Add Pigeon as a `dev_dependency`.
+1) Make a ".dart" file outside of your "lib" directory for defining the
+   communication interface.
+1) Run pigeon on your ".dart" file to generate the required Dart and 
+   host-language code: `flutter pub get` then `flutter pub run pigeon` 
+   with suitable arguments (see [example](./example)).
+1) Add the generated Dart code to `./lib` for compilation.
+1) Implement the host-language code and add it to your build (see below).
+1) Call the generated Dart methods.
 
-1) Add Pigeon as a dev_dependency.
-1) Make a ".dart" file outside of your "lib" directory for defining the communication interface.
-1) Run pigeon on your ".dart" file to generate the required Dart and Objective-C
-   code: `flutter pub get` then `flutter pub run pigeon` with suitable arguments
-   (see [example](./example)).
-1) Add the generated Dart code to `lib` for compilation.
+### Flutter calling into iOS steps
+
 1) Add the generated Objective-C code to your Xcode project for compilation
    (e.g. `ios/Runner.xcworkspace` or `.podspec`).
 1) Implement the generated iOS protocol for handling the calls on iOS, set it up
    as the handler for the messages.
-1) Call the generated Dart methods.
 
 ### Flutter calling into Android Steps
 
-1) Add Pigeon as a dev_dependency.
-1) Make a ".dart" file outside of your "lib" directory for defining the communication interface.
-1) Run pigeon on your ".dart" file to generate the required Dart and Java code.
-   `flutter pub get` then `flutter pub run pigeon` with suitable arguments
-   (see [example](./example)).
-1) Add the generated Dart code to `./lib` for compilation.
-1) Add the generated Java code to your `./android/app/src/main/java` directory for compilation.
-1) Implement the generated Java interface for handling the calls on Android, set it up
-   as the handler for the messages.
-1) Call the generated Dart methods.
+1) Add the generated Java code to your `./android/app/src/main/java` directory
+   for compilation.
+1) Implement the generated Java interface for handling the calls on Android, set
+   it up as the handler for the messages.
+
+### Flutter calling into Windows Steps
+
+1) Add the generated C++ code to your `./windows` directory for compilation, and
+   to your `windows/CMakeLists.txt` file.
+1) Implement the generated C++ abstract class for handling the calls on Windows,
+   set it up as the handler for the messages.
+
+**Note:** Windows C++ is experimental while we get more usage and add more
+testing.  Not all features may be supported.
 
 ### Calling into Flutter from the host platform
 
@@ -116,6 +124,16 @@
 }
 ```
 
+```c++
+// C++
+
+/** Generated class from Pigeon that represents a handler of messages from Flutter.*/
+class Api2Host {
+public:
+    virtual void calculate(Value value, flutter::MessageReply<Value> result) = 0;
+}
+```
+
 ### Null Safety (NNBD)
 
 Pigeon supports generating null-safe code, but it doesn't yet support:
diff --git a/packages/pigeon/lib/cpp_generator.dart b/packages/pigeon/lib/cpp_generator.dart
new file mode 100644
index 0000000..2e59fe9
--- /dev/null
+++ b/packages/pigeon/lib/cpp_generator.dart
@@ -0,0 +1,862 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:pigeon/functional.dart';
+
+import 'ast.dart';
+import 'generator_tools.dart';
+import 'pigeon_lib.dart' show Error;
+
+/// Options that control how C++ code will be generated.
+class CppOptions {
+  /// Creates a [CppOptions] object
+  const CppOptions({
+    this.header,
+    this.namespace,
+    this.copyrightHeader,
+  });
+
+  /// The path to the header that will get placed in the source filed (example:
+  /// "foo.h").
+  final String? header;
+
+  /// The namespace where the generated class will live.
+  final String? namespace;
+
+  /// A copyright header that will get prepended to generated code.
+  final Iterable<String>? copyrightHeader;
+
+  /// Creates a [CppOptions] from a Map representation where:
+  /// `x = CppOptions.fromMap(x.toMap())`.
+  static CppOptions fromMap(Map<String, Object> map) {
+    return CppOptions(
+      header: map['header'] as String?,
+      namespace: map['namespace'] as String?,
+      copyrightHeader: map['copyrightHeader'] as Iterable<String>?,
+    );
+  }
+
+  /// Converts a [CppOptions] to a Map representation where:
+  /// `x = CppOptions.fromMap(x.toMap())`.
+  Map<String, Object> toMap() {
+    final Map<String, Object> result = <String, Object>{
+      if (header != null) 'header': header!,
+      if (namespace != null) 'namespace': namespace!,
+      if (copyrightHeader != null) 'copyrightHeader': copyrightHeader!,
+    };
+    return result;
+  }
+
+  /// Overrides any non-null parameters from [options] into this to make a new
+  /// [CppOptions].
+  CppOptions merge(CppOptions options) {
+    return CppOptions.fromMap(mergeMaps(toMap(), options.toMap()));
+  }
+}
+
+String _getCodecName(Api api) => '${api.name}CodecSerializer';
+
+String _pointerPrefix = 'pointer';
+String _encodablePrefix = 'encodable';
+
+void _writeCodecHeader(Indent indent, Api api, Root root) {
+  final String codecName = _getCodecName(api);
+  indent.write('class $codecName : public flutter::StandardCodecSerializer ');
+  indent.scoped('{', '};', () {
+    indent.scoped(' public:', '', () {
+      indent.writeln('');
+      indent.format('''
+inline static $codecName& GetInstance() {
+\tstatic $codecName sInstance;
+\treturn sInstance;
+}
+''');
+      indent.writeln('$codecName();');
+    });
+    if (getCodecClasses(api, root).isNotEmpty) {
+      indent.writeScoped(' public:', '', () {
+        indent.writeln(
+            'void WriteValue(const flutter::EncodableValue& value, flutter::ByteStreamWriter* stream) const override;');
+      });
+      indent.writeScoped(' protected:', '', () {
+        indent.writeln(
+            'flutter::EncodableValue ReadValueOfType(uint8_t type, flutter::ByteStreamReader* stream) const override;');
+      });
+    }
+  }, nestCount: 0);
+}
+
+void _writeCodecSource(Indent indent, Api api, Root root) {
+  final String codecName = _getCodecName(api);
+  indent.writeln('$codecName::$codecName() {}');
+  if (getCodecClasses(api, root).isNotEmpty) {
+    indent.write(
+        'flutter::EncodableValue $codecName::ReadValueOfType(uint8_t type, flutter::ByteStreamReader* stream) const');
+    indent.scoped('{', '}', () {
+      indent.write('switch (type) ');
+      indent.scoped('{', '}', () {
+        for (final EnumeratedClass customClass in getCodecClasses(api, root)) {
+          indent.write('case ${customClass.enumeration}: ');
+          indent.writeScoped('', '', () {
+            indent.writeln(
+                'return flutter::CustomEncodableValue(${customClass.name}(std::get<flutter::EncodableMap>(ReadValue(stream))));');
+          });
+        }
+        indent.write('default:');
+        indent.writeScoped('', '', () {
+          indent.writeln(
+              'return flutter::StandardCodecSerializer::ReadValueOfType(type, stream);');
+        });
+      });
+    });
+    indent.writeln('');
+    indent.write(
+        'void $codecName::WriteValue(const flutter::EncodableValue& value, flutter::ByteStreamWriter* stream) const');
+    indent.writeScoped('{', '}', () {
+      indent.write(
+          'if (const flutter::CustomEncodableValue* custom_value = std::get_if<flutter::CustomEncodableValue>(&value))');
+      indent.scoped('{', '}', () {
+        for (final EnumeratedClass customClass in getCodecClasses(api, root)) {
+          indent.write(
+              'if (custom_value->type() == typeid(${customClass.name}))');
+          indent.scoped('{', '}', () {
+            indent.writeln('stream->WriteByte(${customClass.enumeration});');
+            indent.writeln(
+                'WriteValue(std::any_cast<${customClass.name}>(*custom_value).ToEncodableMap(), stream);');
+            indent.writeln('return;');
+          });
+        }
+      });
+      indent.writeln(
+          'flutter::StandardCodecSerializer::WriteValue(value, stream);');
+    });
+  }
+}
+
+void _writeErrorOr(Indent indent) {
+  indent.format('''
+class FlutterError {
+ public:
+\tFlutterError();
+\tFlutterError(const std::string& arg_code)
+\t\t: code(arg_code) {};
+\tFlutterError(const std::string& arg_code, const std::string& arg_message)
+\t\t: code(arg_code), message(arg_message) {};
+\tFlutterError(const std::string& arg_code, const std::string& arg_message, const flutter::EncodableValue& arg_details)
+\t\t: code(arg_code), message(arg_message), details(arg_details) {};
+\tstd::string code;
+\tstd::string message;
+\tflutter::EncodableValue details;
+};
+template<class T> class ErrorOr {
+\tstd::variant<std::unique_ptr<T>, T, FlutterError> v;
+ public:
+\tErrorOr(const T& rhs) { new(&v) T(rhs); }
+\tErrorOr(const FlutterError& rhs) {
+\t\tnew(&v) FlutterError(rhs);
+\t}
+\tstatic ErrorOr<std::unique_ptr<T>> MakeWithUniquePtr(std::unique_ptr<T> rhs) {
+\t\tErrorOr<std::unique_ptr<T>> ret = ErrorOr<std::unique_ptr<T>>();
+\t\tret.v = std::move(rhs);
+\t\treturn ret;
+\t}
+\tbool hasError() const { return std::holds_alternative<FlutterError>(v); }
+\tconst T& value() const { return std::get<T>(v); };
+\tconst FlutterError& error() const { return std::get<FlutterError>(v); };
+ private:
+\tErrorOr() = default;
+\tfriend class ErrorOr;
+};
+''');
+}
+
+void _writeHostApiHeader(Indent indent, Api api) {
+  assert(api.location == ApiLocation.host);
+
+  indent.writeln(
+      '/* Generated class from Pigeon that represents a handler of messages from Flutter. */');
+  indent.write('class ${api.name} ');
+  indent.scoped('{', '};', () {
+    indent.scoped(' public:', '', () {
+      indent.writeln('${api.name}(const ${api.name}&) = delete;');
+      indent.writeln('${api.name}& operator=(const ${api.name}&) = delete;');
+      indent.writeln('virtual ~${api.name}() { };');
+      for (final Method method in api.methods) {
+        final String returnTypeName = method.returnType.isVoid
+            ? 'std::optional<FlutterError>'
+            : 'ErrorOr<${_nullSafeCppTypeForDartType(method.returnType, considerReference: false)}>';
+
+        final List<String> argSignature = <String>[];
+        if (method.arguments.isNotEmpty) {
+          final Iterable<String> argTypes = method.arguments
+              .map((NamedType e) => _nullSafeCppTypeForDartType(e.type));
+          final Iterable<String> argNames =
+              method.arguments.map((NamedType e) => e.name);
+          argSignature.addAll(
+              map2(argTypes, argNames, (String argType, String argName) {
+            return '$argType $argName';
+          }));
+        }
+        if (method.isAsynchronous) {
+          argSignature.add('std::function<void($returnTypeName reply)> result');
+          indent.writeln(
+              'virtual void ${method.name}(${argSignature.join(', ')}) = 0;');
+        } else {
+          indent.writeln(
+              'virtual $returnTypeName ${method.name}(${argSignature.join(', ')}) = 0;');
+        }
+      }
+      indent.addln('');
+      indent.writeln('/** The codec used by ${api.name}. */');
+      indent.writeln('static const flutter::StandardMessageCodec& GetCodec();');
+      indent.writeln(
+          '/** Sets up an instance of `${api.name}` to handle messages through the `binary_messenger`. */');
+      indent.writeln(
+          'static void SetUp(flutter::BinaryMessenger* binary_messenger, ${api.name}* api);');
+      indent.writeln(
+          'static flutter::EncodableMap WrapError(std::string_view errorMessage);');
+      indent.writeln(
+          'static flutter::EncodableMap WrapError(const FlutterError& error);');
+    });
+    indent.scoped(' protected:', '', () {
+      indent.writeln('${api.name}() = default;');
+    });
+  }, nestCount: 0);
+}
+
+void _writeHostApiSource(Indent indent, Api api) {
+  assert(api.location == ApiLocation.host);
+
+  final String codecName = _getCodecName(api);
+  indent.format('''
+/** The codec used by ${api.name}. */
+const flutter::StandardMessageCodec& ${api.name}::GetCodec() {
+\treturn flutter::StandardMessageCodec::GetInstance(&$codecName::GetInstance());
+}
+''');
+  indent.writeln(
+      '/** Sets up an instance of `${api.name}` to handle messages through the `binary_messenger`. */');
+  indent.write(
+      'void ${api.name}::SetUp(flutter::BinaryMessenger* binary_messenger, ${api.name}* api) ');
+  indent.scoped('{', '}', () {
+    for (final Method method in api.methods) {
+      final String channelName = makeChannelName(api, method);
+      indent.write('');
+      indent.scoped('{', '}', () {
+        indent.writeln(
+            'auto channel = std::make_unique<flutter::BasicMessageChannel<flutter::EncodableValue>>(');
+        indent.inc();
+        indent.inc();
+        indent.writeln('binary_messenger, "$channelName", &GetCodec());');
+        indent.dec();
+        indent.dec();
+        indent.write('if (api != nullptr) ');
+        indent.scoped('{', '} else {', () {
+          indent.write(
+              'channel->SetMessageHandler([api](const flutter::EncodableValue& message, const flutter::MessageReply<flutter::EncodableValue>& reply)');
+          indent.scoped('{', '});', () {
+            final String returnTypeName = method.returnType.isVoid
+                ? 'std::optional<FlutterError>'
+                : 'ErrorOr<${_nullSafeCppTypeForDartType(method.returnType, considerReference: false)}>';
+            indent.writeln('flutter::EncodableMap wrapped;');
+            indent.write('try ');
+            indent.scoped('{', '}', () {
+              final List<String> methodArgument = <String>[];
+              if (method.arguments.isNotEmpty) {
+                indent.writeln(
+                    'auto args = std::get<flutter::EncodableList>(message);');
+                enumerate(method.arguments, (int index, NamedType arg) {
+                  final String argType = _nullSafeCppTypeForDartType(arg.type);
+                  final String argName = _getSafeArgumentName(index, arg);
+
+                  final String encodableArgName =
+                      '${_encodablePrefix}_$argName';
+                  indent.writeln('auto $encodableArgName = args.at($index);');
+                  if (!arg.type.isNullable) {
+                    indent.write('if ($encodableArgName.IsNull()) ');
+                    indent.scoped('{', '}', () {
+                      indent.writeln(
+                          'wrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.error}"), WrapError("$argName unexpectedly null.")));');
+                      indent.writeln('reply(wrapped);');
+                      indent.writeln('return;');
+                    });
+                  }
+                  indent.writeln(
+                      '$argType $argName = std::any_cast<$argType>(std::get<flutter::CustomEncodableValue>($encodableArgName));');
+                  methodArgument.add(argName);
+                });
+              }
+
+              String _wrapResponse(String reply, TypeDeclaration returnType) {
+                final bool isReferenceReturnType =
+                    _isReferenceType(_cppTypeForDartType(method.returnType));
+                String elseBody = '';
+                final String ifCondition;
+                final String errorGetter;
+                final String prefix = (reply != '') ? '\t' : '';
+                if (returnType.isVoid) {
+                  elseBody =
+                      '$prefix\twrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.result}"), flutter::EncodableValue()));${indent.newline}';
+                  ifCondition = 'output.has_value()';
+                  errorGetter = 'value';
+                } else {
+                  if (isReferenceReturnType && !returnType.isNullable) {
+                    elseBody = '''
+$prefix\tif (!output.value()) {
+$prefix\t\twrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.error}"), WrapError("output is unexpectedly null.")));
+$prefix\t} else {
+$prefix\t\twrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.result}"), flutter::CustomEncodableValue(*output.value().get())));
+$prefix\t}${indent.newline}''';
+                  } else {
+                    elseBody =
+                        '$prefix\twrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.result}"), flutter::CustomEncodableValue(output.value())));${indent.newline}';
+                  }
+                  ifCondition = 'output.hasError()';
+                  errorGetter = 'error';
+                }
+                return '${prefix}if ($ifCondition) {${indent.newline}'
+                    '$prefix\twrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.error}"), WrapError(output.$errorGetter())));${indent.newline}'
+                    '$prefix$reply'
+                    '$prefix} else {${indent.newline}'
+                    '$elseBody'
+                    '$prefix$reply'
+                    '$prefix}';
+              }
+
+              if (method.isAsynchronous) {
+                methodArgument.add(
+                  '[&wrapped, &reply]($returnTypeName output) {${indent.newline}'
+                  '${_wrapResponse('\treply(wrapped);${indent.newline}', method.returnType)}'
+                  '}',
+                );
+              }
+              final String call =
+                  'api->${method.name}(${methodArgument.join(', ')})';
+              if (method.isAsynchronous) {
+                indent.format('$call;');
+              } else {
+                indent.writeln('$returnTypeName output = $call;');
+                indent.format(_wrapResponse('', method.returnType));
+              }
+            });
+            indent.write('catch (const std::exception& exception) ');
+            indent.scoped('{', '}', () {
+              indent.writeln(
+                  'wrapped.insert(std::make_pair(flutter::EncodableValue("${Keys.error}"), WrapError(exception.what())));');
+              if (method.isAsynchronous) {
+                indent.writeln('reply(wrapped);');
+              }
+            });
+            if (!method.isAsynchronous) {
+              indent.writeln('reply(wrapped);');
+            }
+          });
+        });
+        indent.scoped(null, '}', () {
+          indent.writeln('channel->SetMessageHandler(nullptr);');
+        });
+      });
+    }
+  });
+}
+
+String _getArgumentName(int count, NamedType argument) =>
+    argument.name.isEmpty ? 'arg$count' : argument.name;
+
+/// Returns an argument name that can be used in a context where it is possible to collide.
+String _getSafeArgumentName(int count, NamedType argument) =>
+    _getArgumentName(count, argument) + '_arg';
+
+void _writeFlutterApiHeader(Indent indent, Api api) {
+  assert(api.location == ApiLocation.flutter);
+  indent.writeln(
+      '/* Generated class from Pigeon that represents Flutter messages that can be called from C++. */');
+  indent.write('class ${api.name} ');
+  indent.scoped('{', '};', () {
+    indent.scoped(' private:', '', () {
+      indent.writeln('flutter::BinaryMessenger* binary_messenger_;');
+    });
+    indent.scoped(' public:', '', () {
+      indent.write('${api.name}(flutter::BinaryMessenger* binary_messenger);');
+      indent.writeln('');
+      indent.writeln('static const flutter::StandardMessageCodec& GetCodec();');
+      for (final Method func in api.methods) {
+        final String returnType = func.returnType.isVoid
+            ? 'void'
+            : _nullSafeCppTypeForDartType(func.returnType);
+        final String callback = 'std::function<void($returnType)>&& callback';
+        if (func.arguments.isEmpty) {
+          indent.writeln('void ${func.name}($callback);');
+        } else {
+          final Iterable<String> argTypes = func.arguments
+              .map((NamedType e) => _nullSafeCppTypeForDartType(e.type));
+          final Iterable<String> argNames =
+              indexMap(func.arguments, _getSafeArgumentName);
+          final String argsSignature =
+              map2(argTypes, argNames, (String x, String y) => '$x $y')
+                  .join(', ');
+          indent.writeln('void ${func.name}($argsSignature, $callback);');
+        }
+      }
+    });
+  }, nestCount: 0);
+}
+
+void _writeFlutterApiSource(Indent indent, Api api) {
+  assert(api.location == ApiLocation.flutter);
+  indent.writeln(
+      '/* Generated class from Pigeon that represents Flutter messages that can be called from C++. */');
+  indent.write(
+      '${api.name}::${api.name}(flutter::BinaryMessenger* binary_messenger)');
+  indent.scoped('{', '}', () {
+    indent.writeln('this->binary_messenger_ = binary_messenger;');
+  });
+  indent.writeln('');
+  final String codecName = _getCodecName(api);
+  indent.format('''
+const flutter::StandardMessageCodec& ${api.name}::GetCodec() {
+\treturn flutter::StandardMessageCodec::GetInstance(&$codecName::GetInstance());
+}
+''');
+  for (final Method func in api.methods) {
+    final String channelName = makeChannelName(api, func);
+    final String returnType = func.returnType.isVoid
+        ? 'void'
+        : _nullSafeCppTypeForDartType(func.returnType);
+    String sendArgument;
+    final String callback = 'std::function<void($returnType)>&& callback';
+    if (func.arguments.isEmpty) {
+      indent.write('void ${api.name}::${func.name}($callback) ');
+      sendArgument = 'flutter::EncodableValue()';
+    } else {
+      final Iterable<String> argTypes = func.arguments
+          .map((NamedType e) => _nullSafeCppTypeForDartType(e.type));
+      final Iterable<String> argNames =
+          indexMap(func.arguments, _getSafeArgumentName);
+      sendArgument =
+          'flutter::EncodableList { ${(argNames.map((String arg) => 'flutter::CustomEncodableValue($arg)')).join(', ')} }';
+      final String argsSignature =
+          map2(argTypes, argNames, (String x, String y) => '$x $y').join(', ');
+      indent
+          .write('void ${api.name}::${func.name}($argsSignature, $callback) ');
+    }
+    indent.scoped('{', '}', () {
+      const String channel = 'channel';
+      indent.writeln(
+          'auto channel = std::make_unique<flutter::BasicMessageChannel<flutter::EncodableValue>>(');
+      indent.inc();
+      indent.inc();
+      indent.writeln('binary_messenger_, "$channelName", &GetCodec());');
+      indent.dec();
+      indent.dec();
+      indent.write(
+          '$channel->Send($sendArgument, [callback](const uint8_t* reply, size_t reply_size)');
+      indent.scoped('{', '});', () {
+        if (func.returnType.isVoid) {
+          indent.writeln('callback();');
+        } else {
+          indent.writeln(
+              'std::unique_ptr<flutter::EncodableValue> decoded_reply = GetCodec().DecodeMessage(reply, reply_size);');
+          indent.writeln(
+              'flutter::EncodableValue args = *(flutter::EncodableValue*)(decoded_reply.release());');
+          const String output = 'output';
+
+          final bool isBuiltin =
+              _cppTypeForBuiltinDartType(func.returnType) != null;
+          final String returnTypeName = _cppTypeForDartType(func.returnType);
+          if (func.returnType.isNullable) {
+            indent.writeln('$returnType $output{};');
+          } else {
+            indent.writeln('$returnTypeName $output{};');
+          }
+          final String pointerVariable = '${_pointerPrefix}_$output';
+          if (func.returnType.baseName == 'int') {
+            indent.format('''
+if (const int32_t* $pointerVariable = std::get_if<int32_t>(&args))
+\t$output = *$pointerVariable;
+else if (const int64_t* ${pointerVariable}_64 = std::get_if<int64_t>(&args))
+\t$output = *${pointerVariable}_64;''');
+          } else if (!isBuiltin) {
+            indent.write(
+                'if (const flutter::EncodableMap* $pointerVariable = std::get_if<flutter::EncodableMap>(&args))');
+            indent.scoped('{', '}', () {
+              indent.writeln('$output = $returnTypeName(*$pointerVariable);');
+            });
+          } else {
+            indent.write(
+                'if (const $returnTypeName* $pointerVariable = std::get_if<$returnTypeName>(&args))');
+            indent.scoped('{', '}', () {
+              indent.writeln('$output = *$pointerVariable;');
+            });
+          }
+
+          indent.writeln('callback($output);');
+        }
+      });
+    });
+  }
+}
+
+String _makeGetterName(NamedType field) {
+  final String uppercased =
+      field.name.substring(0, 1).toUpperCase() + field.name.substring(1);
+  return 'get$uppercased';
+}
+
+String _makeSetterName(NamedType field) {
+  final String uppercased =
+      field.name.substring(0, 1).toUpperCase() + field.name.substring(1);
+  return 'set$uppercased';
+}
+
+bool _isReferenceType(String dataType) {
+  switch (dataType) {
+    case 'bool':
+    case 'int64_t':
+    case 'double':
+      return false;
+    default:
+      return true;
+  }
+}
+
+String? _cppTypeForBuiltinDartType(TypeDeclaration type) {
+  const Map<String, String> cppTypeForDartTypeMap = <String, String>{
+    'bool': 'bool',
+    'int': 'int64_t',
+    'String': 'std::string',
+    'double': 'double',
+    'Uint8List': 'std::vector<uint8_t>',
+    'Int32List': 'std::vector<int32_t>',
+    'Int64List': 'std::vector<int64_t>',
+    'Float64List': 'std::vector<double>',
+    'Map': 'flutter::EncodableMap',
+    'List': 'flutter::EncodableList',
+  };
+  if (cppTypeForDartTypeMap.containsKey(type.baseName)) {
+    return cppTypeForDartTypeMap[type.baseName];
+  } else {
+    return null;
+  }
+}
+
+String _cppTypeForDartType(TypeDeclaration type) {
+  return _cppTypeForBuiltinDartType(type) ?? type.baseName;
+}
+
+String _nullSafeCppTypeForDartType(TypeDeclaration type,
+    {bool considerReference = true}) {
+  if (type.isNullable) {
+    return 'std::optional<${_cppTypeForDartType(type)}>';
+  } else {
+    String typeName = _cppTypeForDartType(type);
+    if (_isReferenceType(typeName)) {
+      if (considerReference)
+        typeName = 'const $typeName&';
+      else
+        typeName = 'std::unique_ptr<$typeName>';
+    }
+    return typeName;
+  }
+}
+
+String _getGuardName(String? headerFileName, String? namespace) {
+  String guardName = 'PIGEON_';
+  if (headerFileName != null) {
+    guardName += '${headerFileName.replaceAll('.', '_').toUpperCase()}_';
+  }
+  if (namespace != null) {
+    guardName += '${namespace.toUpperCase()}_';
+  }
+  return guardName + 'H_';
+}
+
+/// Generates the ".h" file for the AST represented by [root] to [sink] with the
+/// provided [options] and [headerFileName].
+void generateCppHeader(
+    String? headerFileName, CppOptions options, Root root, StringSink sink) {
+  final Indent indent = Indent(sink);
+  if (options.copyrightHeader != null) {
+    addLines(indent, options.copyrightHeader!, linePrefix: '// ');
+  }
+  indent.writeln('// $generatedCodeWarning');
+  indent.writeln('// $seeAlsoWarning');
+  indent.addln('');
+  final String guardName = _getGuardName(headerFileName, options.namespace);
+  indent.writeln('#ifndef $guardName');
+  indent.writeln('#define $guardName');
+  indent.writeln('#include <flutter/encodable_value.h>');
+  indent.writeln('#include <flutter/basic_message_channel.h>');
+  indent.writeln('#include <flutter/binary_messenger.h>');
+  indent.writeln('#include <flutter/standard_message_codec.h>');
+  indent.addln('');
+  indent.writeln('#include <map>');
+  indent.writeln('#include <string>');
+  indent.writeln('#include <optional>');
+
+  indent.addln('');
+
+  if (options.namespace != null) {
+    indent.writeln('namespace ${options.namespace} {');
+  }
+
+  indent.addln('');
+  indent.writeln('/* Generated class from Pigeon. */');
+
+  for (final Enum anEnum in root.enums) {
+    indent.writeln('');
+    indent.write('enum class ${anEnum.name} ');
+    indent.scoped('{', '};', () {
+      int index = 0;
+      for (final String member in anEnum.members) {
+        indent.writeln(
+            '$member = $index${index == anEnum.members.length - 1 ? '' : ','}');
+        index++;
+      }
+    });
+  }
+
+  indent.addln('');
+
+  _writeErrorOr(indent);
+
+  for (final Class klass in root.classes) {
+    indent.addln('');
+    indent.writeln(
+        '/* Generated class from Pigeon that represents data sent in messages. */');
+    indent.write('class ${klass.name} ');
+    indent.scoped('{', '};', () {
+      indent.scoped(' public:', '', () {
+        indent.writeln('${klass.name}();');
+        for (final NamedType field in klass.fields) {
+          final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
+              root.enums, (NamedType x) => _cppTypeForBuiltinDartType(x.type));
+          if (_isReferenceType(hostDatatype.datatype)) {
+            indent.writeln(
+                'const ${hostDatatype.datatype}& ${_makeGetterName(field)}() const;');
+            indent.writeln(
+                'void ${_makeSetterName(field)}(const ${hostDatatype.datatype}& setterArg);');
+          } else {
+            indent.writeln(
+                '${hostDatatype.datatype} ${_makeGetterName(field)}() const;');
+            indent.writeln(
+                'void ${_makeSetterName(field)}(const ${hostDatatype.datatype} setterArg);');
+          }
+          indent.addln('');
+        }
+      });
+
+      indent.scoped(' private:', '', () {
+        indent.writeln('${klass.name}(flutter::EncodableMap map);');
+        indent.writeln('flutter::EncodableMap ToEncodableMap();');
+        for (final Class friend in root.classes) {
+          if (friend != klass &&
+              friend.fields.any(
+                  (NamedType element) => element.type.baseName == klass.name)) {
+            indent.writeln('friend class ${friend.name};');
+          }
+        }
+        for (final Api api in root.apis) {
+          // TODO(gaaclarke): Find a way to be more precise with our
+          // friendships.
+          indent.writeln('friend class ${api.name};');
+          indent.writeln('friend class ${_getCodecName(api)};');
+        }
+
+        for (final NamedType field in klass.fields) {
+          final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
+              root.enums, (NamedType x) => _cppTypeForBuiltinDartType(x.type));
+          indent.writeln('${hostDatatype.datatype} ${field.name}_;');
+        }
+      });
+    }, nestCount: 0);
+    indent.writeln('');
+  }
+
+  for (final Api api in root.apis) {
+    _writeCodecHeader(indent, api, root);
+    indent.addln('');
+    if (api.location == ApiLocation.host) {
+      _writeHostApiHeader(indent, api);
+    } else if (api.location == ApiLocation.flutter) {
+      _writeFlutterApiHeader(indent, api);
+    }
+  }
+
+  if (options.namespace != null) {
+    indent.writeln('} // namespace');
+  }
+
+  indent.writeln('#endif  // $guardName');
+}
+
+/// Generates the ".cpp" file for the AST represented by [root] to [sink] with the
+/// provided [options].
+void generateCppSource(CppOptions options, Root root, StringSink sink) {
+  final Set<String> rootClassNameSet =
+      root.classes.map((Class x) => x.name).toSet();
+  final Set<String> rootEnumNameSet =
+      root.enums.map((Enum x) => x.name).toSet();
+  final Indent indent = Indent(sink);
+  if (options.copyrightHeader != null) {
+    addLines(indent, options.copyrightHeader!, linePrefix: '// ');
+  }
+  indent.writeln('// $generatedCodeWarning');
+  indent.writeln('// $seeAlsoWarning');
+  indent.addln('');
+  indent.addln('#undef _HAS_EXCEPTIONS');
+  indent.addln('');
+  indent.writeln('#include <flutter/basic_message_channel.h>');
+  indent.writeln('#include <flutter/binary_messenger.h>');
+  indent.writeln('#include <flutter/standard_message_codec.h>');
+  indent.writeln('#include <map>');
+  indent.writeln('#include <string>');
+  indent.writeln('#include <optional>');
+
+  indent.writeln('#include "${options.header}"');
+
+  indent.addln('');
+
+  indent.addln('');
+
+  if (options.namespace != null) {
+    indent.writeln('namespace ${options.namespace} {');
+  }
+
+  indent.addln('');
+  indent.writeln('/* Generated class from Pigeon. */');
+
+  for (final Class klass in root.classes) {
+    indent.addln('');
+    indent.writeln('/* ${klass.name} */');
+    indent.addln('');
+    for (final NamedType field in klass.fields) {
+      final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
+          root.enums, (NamedType x) => _cppTypeForBuiltinDartType(x.type));
+      if (_isReferenceType(hostDatatype.datatype)) {
+        indent.writeln(
+            'const ${hostDatatype.datatype}& ${klass.name}::${_makeGetterName(field)}() const { return ${field.name}_; }');
+        indent.writeln(
+            'void ${klass.name}::${_makeSetterName(field)}(const ${hostDatatype.datatype}& setterArg) { this->${field.name}_ = setterArg; }');
+      } else {
+        indent.writeln(
+            '${hostDatatype.datatype} ${klass.name}::${_makeGetterName(field)}() const { return ${field.name}_; }');
+        indent.writeln(
+            'void ${klass.name}::${_makeSetterName(field)}(const ${hostDatatype.datatype} setterArg) { this->${field.name}_ = setterArg; }');
+      }
+
+      indent.addln('');
+    }
+    indent.write('flutter::EncodableMap ${klass.name}::ToEncodableMap() ');
+    indent.scoped('{', '}', () {
+      indent.writeln('flutter::EncodableMap toMapResult;');
+      for (final NamedType field in klass.fields) {
+        final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
+            root.enums, (NamedType x) => _cppTypeForBuiltinDartType(x.type));
+        String toWriteValue = '';
+        if (!hostDatatype.isBuiltin &&
+            rootClassNameSet.contains(field.type.baseName)) {
+          toWriteValue = '${field.name}_.ToEncodableMap()';
+        } else if (!hostDatatype.isBuiltin &&
+            rootEnumNameSet.contains(field.type.baseName)) {
+          toWriteValue = 'flutter::EncodableValue((int)${field.name}_)';
+        } else {
+          toWriteValue = 'flutter::EncodableValue(${field.name}_)';
+        }
+        indent.writeln(
+            'toMapResult.insert(std::make_pair(flutter::EncodableValue("${field.name}"), $toWriteValue));');
+      }
+      indent.writeln('return toMapResult;');
+    });
+    indent.writeln('${klass.name}::${klass.name}() {}');
+    indent.write('${klass.name}::${klass.name}(flutter::EncodableMap map) ');
+    indent.scoped('{', '}', () {
+      for (final NamedType field in klass.fields) {
+        final String pointerFieldName = '${_pointerPrefix}_${field.name}';
+        final String encodableFieldName = '${_encodablePrefix}_${field.name}';
+        indent.writeln(
+            'auto $encodableFieldName = map.at(flutter::EncodableValue("${field.name}"));');
+        if (rootEnumNameSet.contains(field.type.baseName)) {
+          indent.writeln(
+              'if (const int32_t* $pointerFieldName = std::get_if<int32_t>(&$encodableFieldName))\t${field.name}_ = (${field.type.baseName})*$pointerFieldName;');
+        } else {
+          final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
+              root.enums, (NamedType x) => _cppTypeForBuiltinDartType(x.type));
+          if (field.type.baseName == 'int') {
+            indent.format('''
+if (const int32_t* $pointerFieldName = std::get_if<int32_t>(&$encodableFieldName))
+\t${field.name}_ = *$pointerFieldName;
+else if (const int64_t* ${pointerFieldName}_64 = std::get_if<int64_t>(&$encodableFieldName))
+\t${field.name}_ = *${pointerFieldName}_64;''');
+          } else if (!hostDatatype.isBuiltin &&
+              root.classes
+                  .map((Class x) => x.name)
+                  .contains(field.type.baseName)) {
+            indent.write(
+                'if (const flutter::EncodableMap* $pointerFieldName = std::get_if<flutter::EncodableMap>(&$encodableFieldName))');
+            indent.scoped('{', '}', () {
+              indent.writeln(
+                  '${field.name}_ = ${hostDatatype.datatype}(*$pointerFieldName);');
+            });
+          } else {
+            indent.write(
+                'if (const ${hostDatatype.datatype}* $pointerFieldName = std::get_if<${hostDatatype.datatype}>(&$encodableFieldName))');
+            indent.scoped('{', '}', () {
+              indent.writeln('${field.name}_ = *$pointerFieldName;');
+            });
+          }
+        }
+      }
+    });
+    indent.addln('');
+  }
+
+  for (final Api api in root.apis) {
+    _writeCodecSource(indent, api, root);
+    indent.addln('');
+    if (api.location == ApiLocation.host) {
+      _writeHostApiSource(indent, api);
+
+      indent.addln('');
+      indent.format('''
+flutter::EncodableMap ${api.name}::WrapError(std::string_view errorMessage) {
+\treturn flutter::EncodableMap({
+\t\t{flutter::EncodableValue("${Keys.errorMessage}"), flutter::EncodableValue(std::string(errorMessage).data())},
+\t\t{flutter::EncodableValue("${Keys.errorCode}"), flutter::EncodableValue("Error")},
+\t\t{flutter::EncodableValue("${Keys.errorDetails}"), flutter::EncodableValue()}
+\t});
+}
+flutter::EncodableMap ${api.name}::WrapError(const FlutterError& error) {
+\treturn flutter::EncodableMap({
+\t\t{flutter::EncodableValue("${Keys.errorMessage}"), flutter::EncodableValue(error.message)},
+\t\t{flutter::EncodableValue("${Keys.errorCode}"), flutter::EncodableValue(error.code)},
+\t\t{flutter::EncodableValue("${Keys.errorDetails}"), error.details}
+\t});
+}''');
+      indent.addln('');
+    } else if (api.location == ApiLocation.flutter) {
+      _writeFlutterApiSource(indent, api);
+    }
+  }
+
+  if (options.namespace != null) {
+    indent.writeln('} // namespace');
+  }
+}
+
+/// Validates an AST to make sure the cpp generator supports everything.
+List<Error> validateCpp(CppOptions options, Root root) {
+  final List<Error> result = <Error>[];
+  for (final Class aClass in root.classes) {
+    for (final NamedType field in aClass.fields) {
+      if (!field.type.isNullable) {
+        // TODO(gaaclarke): Add line number and filename.
+        result.add(Error(
+            message:
+                'unsupported nonnull field "${field.name}" in "${aClass.name}"'));
+      }
+    }
+  }
+  return result;
+}
diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart
index 4d92c38..97f62e0 100644
--- a/packages/pigeon/lib/generator_tools.dart
+++ b/packages/pigeon/lib/generator_tools.dart
@@ -8,7 +8,7 @@
 import 'ast.dart';
 
 /// The current version of pigeon. This must match the version in pubspec.yaml.
-const String pigeonVersion = '3.0.4';
+const String pigeonVersion = '3.1.0';
 
 /// Read all the content from [stdin] to a String.
 String readStdin() {
@@ -89,11 +89,12 @@
     String? end,
     Function func, {
     bool addTrailingNewline = true,
+    int nestCount = 1,
   }) {
     if (begin != null) {
       _sink.write(begin + newline);
     }
-    nest(1, func);
+    nest(nestCount, func);
     if (end != null) {
       _sink.write(str() + end);
       if (addTrailingNewline) {
diff --git a/packages/pigeon/lib/pigeon.dart b/packages/pigeon/lib/pigeon.dart
index d056115..7a26cf3 100644
--- a/packages/pigeon/lib/pigeon.dart
+++ b/packages/pigeon/lib/pigeon.dart
@@ -4,6 +4,7 @@
 
 export 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List;
 
+export 'cpp_generator.dart' show CppOptions;
 export 'dart_generator.dart' show DartOptions;
 export 'java_generator.dart' show JavaOptions;
 export 'objc_generator.dart' show ObjcOptions;
diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart
index aea6ef7..2fd9583 100644
--- a/packages/pigeon/lib/pigeon_lib.dart
+++ b/packages/pigeon/lib/pigeon_lib.dart
@@ -19,6 +19,7 @@
 import 'package:analyzer/error/error.dart' show AnalysisError;
 import 'package:args/args.dart';
 import 'package:path/path.dart' as path;
+import 'package:pigeon/cpp_generator.dart';
 import 'package:pigeon/generator_tools.dart';
 import 'package:pigeon/java_generator.dart';
 
@@ -157,6 +158,9 @@
       this.objcOptions,
       this.javaOut,
       this.javaOptions,
+      this.cppHeaderOut,
+      this.cppSourceOut,
+      this.cppOptions,
       this.dartOptions,
       this.copyrightHeader,
       this.oneLanguage,
@@ -187,6 +191,15 @@
   /// Options that control how Java will be generated.
   final JavaOptions? javaOptions;
 
+  /// Path to the ".h" C++ file that will be generated.
+  final String? cppHeaderOut;
+
+  /// Path to the ".cpp" C++ file that will be generated.
+  final String? cppSourceOut;
+
+  /// Options that control how C++ will be generated.
+  final CppOptions? cppOptions;
+
   /// Options that control how Dart will be generated.
   final DartOptions? dartOptions;
 
@@ -218,6 +231,12 @@
       javaOptions: map.containsKey('javaOptions')
           ? JavaOptions.fromMap((map['javaOptions'] as Map<String, Object>?)!)
           : null,
+      cppHeaderOut: map['experimental_cppHeaderOut'] as String?,
+      cppSourceOut: map['experimental_cppSourceOut'] as String?,
+      cppOptions: map.containsKey('experimental_cppOptions')
+          ? CppOptions.fromMap(
+              (map['experimental_cppOptions'] as Map<String, Object>?)!)
+          : null,
       dartOptions: map.containsKey('dartOptions')
           ? DartOptions.fromMap((map['dartOptions'] as Map<String, Object>?)!)
           : null,
@@ -240,6 +259,9 @@
       if (objcOptions != null) 'objcOptions': objcOptions!.toMap(),
       if (javaOut != null) 'javaOut': javaOut!,
       if (javaOptions != null) 'javaOptions': javaOptions!.toMap(),
+      if (cppHeaderOut != null) 'experimental_cppHeaderOut': cppHeaderOut!,
+      if (cppSourceOut != null) 'experimental_cppSourceOut': cppSourceOut!,
+      if (cppOptions != null) 'experimental_cppOptions': cppOptions!.toMap(),
       if (dartOptions != null) 'dartOptions': dartOptions!.toMap(),
       if (copyrightHeader != null) 'copyrightHeader': copyrightHeader!,
       if (astOut != null) 'astOut': astOut!,
@@ -468,6 +490,54 @@
   List<Error> validate(PigeonOptions options, Root root) => <Error>[];
 }
 
+/// A [Generator] that generates C++ header code.
+class CppHeaderGenerator implements Generator {
+  /// Constructor for [CppHeaderGenerator].
+  const CppHeaderGenerator();
+
+  @override
+  void generate(StringSink sink, PigeonOptions options, Root root) {
+    final CppOptions cppOptions = options.cppOptions ?? const CppOptions();
+    final CppOptions cppOptionsWithHeader = cppOptions.merge(CppOptions(
+        copyrightHeader: options.copyrightHeader != null
+            ? _lineReader(options.copyrightHeader!)
+            : null));
+    generateCppHeader(path.basenameWithoutExtension(options.cppHeaderOut!),
+        cppOptionsWithHeader, root, sink);
+  }
+
+  @override
+  IOSink? shouldGenerate(PigeonOptions options) =>
+      _openSink(options.cppHeaderOut);
+
+  @override
+  List<Error> validate(PigeonOptions options, Root root) =>
+      validateCpp(options.cppOptions!, root);
+}
+
+/// A [Generator] that generates C++ source code.
+class CppSourceGenerator implements Generator {
+  /// Constructor for [CppSourceGenerator].
+  const CppSourceGenerator();
+
+  @override
+  void generate(StringSink sink, PigeonOptions options, Root root) {
+    final CppOptions cppOptions = options.cppOptions ?? const CppOptions();
+    final CppOptions cppOptionsWithHeader = cppOptions.merge(CppOptions(
+        copyrightHeader: options.copyrightHeader != null
+            ? _lineReader(options.copyrightHeader!)
+            : null));
+    generateCppSource(cppOptionsWithHeader, root, sink);
+  }
+
+  @override
+  IOSink? shouldGenerate(PigeonOptions options) =>
+      _openSink(options.cppSourceOut);
+
+  @override
+  List<Error> validate(PigeonOptions options, Root root) => <Error>[];
+}
+
 dart_ast.Annotation? _findMetadata(
     dart_ast.NodeList<dart_ast.Annotation> metadata, String query) {
   final Iterable<dart_ast.Annotation> annotations = metadata
@@ -1063,6 +1133,12 @@
     ..addOption('java_out', help: 'Path to generated Java file (.java).')
     ..addOption('java_package',
         help: 'The package that generated Java code will be in.')
+    ..addOption('experimental_cpp_header_out',
+        help: 'Path to generated C++ header file (.h). (experimental)')
+    ..addOption('experimental_cpp_source_out',
+        help: 'Path to generated C++ classes file (.cpp). (experimental)')
+    ..addOption('cpp_namespace',
+        help: 'The namespace that generated C++ code will be in.')
     ..addOption('objc_header_out',
         help: 'Path to generated Objective-C header file (.h).')
     ..addOption('objc_prefix',
@@ -1101,6 +1177,11 @@
       javaOptions: JavaOptions(
         package: results['java_package'],
       ),
+      cppHeaderOut: results['experimental_cpp_header_out'],
+      cppSourceOut: results['experimental_cpp_source_out'],
+      cppOptions: CppOptions(
+        namespace: results['cpp_namespace'],
+      ),
       copyrightHeader: results['copyright_header'],
       oneLanguage: results['one_language'],
       astOut: results['ast_out'],
@@ -1143,6 +1224,8 @@
         <Generator>[
           const DartGenerator(),
           const JavaGenerator(),
+          const CppHeaderGenerator(),
+          const CppSourceGenerator(),
           const DartTestGenerator(),
           const ObjcHeaderGenerator(),
           const ObjcSourceGenerator(),
@@ -1196,6 +1279,12 @@
               ObjcOptions(header: path.basename(options.objcHeaderOut!)))));
     }
 
+    if (options.cppHeaderOut != null) {
+      options = options.merge(PigeonOptions(
+          cppOptions: options.cppOptions!.merge(
+              CppOptions(header: path.basename(options.cppHeaderOut!)))));
+    }
+
     for (final Generator generator in safeGenerators) {
       final IOSink? sink = generator.shouldGenerate(options);
       if (sink != null) {
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart
index 4a1e2a0..bf19653 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //
-// Autogenerated from Pigeon (v2.0.4), do not edit directly.
+// Autogenerated from Pigeon (v2.1.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
 // @dart = 2.12
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart
index 7f7aa0d..9f0ce12 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //
-// Autogenerated from Pigeon (v2.0.4), do not edit directly.
+// Autogenerated from Pigeon (v2.1.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
 // @dart = 2.12
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
index 0b7e513..9791455 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //
-// Autogenerated from Pigeon (v3.0.2), do not edit directly.
+// Autogenerated from Pigeon (v3.1.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
 // @dart = 2.12
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart
index 4ac7d91..dd7c7c9 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //
-// Autogenerated from Pigeon (v2.0.4), do not edit directly.
+// Autogenerated from Pigeon (v2.1.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
 // @dart = 2.12
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
index 2ed1294..a1f8030 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //
-// Autogenerated from Pigeon (v2.0.4), do not edit directly.
+// Autogenerated from Pigeon (v2.1.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
 // @dart = 2.12
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart
index 52cf1e5..757d4a9 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //
-// Autogenerated from Pigeon (v3.0.2), do not edit directly.
+// Autogenerated from Pigeon (v3.1.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
 // @dart = 2.12
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart
index c398ef2..b5da7e5 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //
-// Autogenerated from Pigeon (v2.0.4), do not edit directly.
+// Autogenerated from Pigeon (v2.1.0), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
 // @dart = 2.12
diff --git a/packages/pigeon/platform_tests/windows_unit_tests/windows/CMakeLists.txt b/packages/pigeon/platform_tests/windows_unit_tests/windows/CMakeLists.txt
index 9d456d4..0fe0a39 100644
--- a/packages/pigeon/platform_tests/windows_unit_tests/windows/CMakeLists.txt
+++ b/packages/pigeon/platform_tests/windows_unit_tests/windows/CMakeLists.txt
@@ -53,8 +53,38 @@
 # directly into the test binary rather than using the DLL.
 add_executable(${TEST_RUNNER}
   test/pigeon_test.cpp
-  # TODO(gaaclarke): Add test/message.g.cpp.
-  # TODO(gaaclarke): Add test/message.g.h.
+  test/all_datatypes.g.cpp
+  test/all_datatypes.g.h
+  test/all_void.g.cpp
+  test/all_void.g.h
+  test/async_handlers.g.cpp
+  test/async_handlers.g.h
+  test/message.g.cpp
+  test/message.g.h
+  test/enum.g.cpp
+  test/enum.g.h
+  test/host2flutter.g.cpp
+  test/host2flutter.g.h
+  test/list.g.cpp
+  test/list.g.h
+  test/multiple_arity.g.cpp
+  test/multiple_arity.g.h
+  # Removed until supported by C++ generator.
+  # test/non_null_fields.g.cpp
+  # test/non_null_fields.g.h
+  test/nullable_returns.g.cpp
+  test/nullable_returns.g.h
+  test/primitive.g.cpp
+  test/primitive.g.h
+  test/void_arg_flutter.g.cpp
+  test/void_arg_flutter.g.h
+  test/void_arg_host.g.cpp
+  test/void_arg_host.g.h
+  test/voidflutter.g.cpp
+  test/voidflutter.g.h
+  test/voidhost.g.cpp
+  test/voidhost.g.h
+
   ${PLUGIN_SOURCES}
 )
 apply_standard_settings(${TEST_RUNNER})
diff --git a/packages/pigeon/platform_tests/windows_unit_tests/windows/test/pigeon_test.cpp b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/pigeon_test.cpp
index a74d8e5..e5ab587 100644
--- a/packages/pigeon/platform_tests/windows_unit_tests/windows/test/pigeon_test.cpp
+++ b/packages/pigeon/platform_tests/windows_unit_tests/windows/test/pigeon_test.cpp
@@ -10,6 +10,7 @@
 #include <memory>
 #include <string>
 
+#include "message.g.h"
 #include "windows_unit_tests_plugin.h"
 
 namespace windows_unit_tests {
@@ -19,13 +20,17 @@
 
 using flutter::EncodableMap;
 using flutter::EncodableValue;
+using ::testing::ByMove;
 using ::testing::DoAll;
 using ::testing::Pointee;
 using ::testing::Return;
 using ::testing::SetArgPointee;
+using namespace messageTest;
 
 class MockMethodResult : public flutter::MethodResult<> {
  public:
+  ~MockMethodResult() = default;
+
   MOCK_METHOD(void, SuccessInternal, (const EncodableValue* result),
               (override));
   MOCK_METHOD(void, ErrorInternal,
@@ -35,6 +40,44 @@
   MOCK_METHOD(void, NotImplementedInternal, (), (override));
 };
 
+class MockBinaryMessenger : public flutter::BinaryMessenger {
+ public:
+  ~MockBinaryMessenger() = default;
+
+  MOCK_METHOD(void, Send,
+              (const std::string& channel, const uint8_t* message,
+               size_t message_size, flutter::BinaryReply reply),
+              (override, const));
+  MOCK_METHOD(void, SetMessageHandler,
+              (const std::string& channel,
+               flutter::BinaryMessageHandler handler),
+              (override));
+};
+
+class MockApi : public Api {
+ public:
+  ~MockApi() = default;
+
+  MOCK_METHOD(std::optional<FlutterError>, initialize, (), (override));
+  MOCK_METHOD(ErrorOr<std::unique_ptr<SearchReply>>, search,
+              (const SearchRequest&), (override));
+};
+
+class Writer : public flutter::ByteStreamWriter {
+ public:
+  void WriteByte(uint8_t byte) override { data_.push_back(byte); }
+  void WriteBytes(const uint8_t* bytes, size_t length) override {
+    for (size_t i = 0; i < length; ++i) {
+      data_.push_back(bytes[i]);
+    }
+  }
+  void WriteAlignment(uint8_t alignment) override {
+    while (data_.size() % alignment != 0) {
+      data_.push_back(0);
+    }
+  }
+  std::vector<uint8_t> data_;
+};
 }  // namespace
 
 TEST(PigeonTests, Placeholder) {
@@ -49,5 +92,58 @@
                           std::move(result));
 }
 
+TEST(PigeonTests, CallInitialize) {
+  MockBinaryMessenger mock_messenger;
+  MockApi mock_api;
+  flutter::BinaryMessageHandler handler;
+  EXPECT_CALL(
+      mock_messenger,
+      SetMessageHandler("dev.flutter.pigeon.Api.initialize", testing::_))
+      .Times(1)
+      .WillOnce(testing::SaveArg<1>(&handler));
+  EXPECT_CALL(mock_messenger,
+              SetMessageHandler("dev.flutter.pigeon.Api.search", testing::_))
+      .Times(1);
+  EXPECT_CALL(mock_api, initialize());
+  Api::SetUp(&mock_messenger, &mock_api);
+  bool did_call_reply = false;
+  flutter::BinaryReply reply = [&did_call_reply](const uint8_t* data,
+                                                 size_t size) {
+    did_call_reply = true;
+  };
+  handler(nullptr, 0, reply);
+  EXPECT_TRUE(did_call_reply);
+}
+
+TEST(PigeonTests, CallSearch) {
+  MockBinaryMessenger mock_messenger;
+  MockApi mock_api;
+  flutter::BinaryMessageHandler handler;
+  EXPECT_CALL(
+      mock_messenger,
+      SetMessageHandler("dev.flutter.pigeon.Api.initialize", testing::_))
+      .Times(1);
+  EXPECT_CALL(mock_messenger,
+              SetMessageHandler("dev.flutter.pigeon.Api.search", testing::_))
+      .Times(1)
+      .WillOnce(testing::SaveArg<1>(&handler));
+  EXPECT_CALL(mock_api, search(testing::_))
+      .WillOnce(Return(ByMove(ErrorOr<SearchReply>::MakeWithUniquePtr(
+          std::make_unique<SearchReply>()))));
+  Api::SetUp(&mock_messenger, &mock_api);
+  bool did_call_reply = false;
+  flutter::BinaryReply reply = [&did_call_reply](const uint8_t* data,
+                                                 size_t size) {
+    did_call_reply = true;
+  };
+  SearchRequest request;
+  Writer writer;
+  flutter::EncodableList args;
+  args.push_back(flutter::CustomEncodableValue(request));
+  ApiCodecSerializer::GetInstance().WriteValue(args, &writer);
+  handler(writer.data_.data(), writer.data_.size(), reply);
+  EXPECT_TRUE(did_call_reply);
+}
+
 }  // namespace test
 }  // namespace windows_unit_tests
diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml
index 25ef681..714c09d 100644
--- a/packages/pigeon/pubspec.yaml
+++ b/packages/pigeon/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Code generator tool to make communication between Flutter and the host platform type-safe and easier.
 repository: https://github.com/flutter/packages/tree/main/packages/pigeon
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3Apigeon
-version: 3.0.4 # This must match the version in lib/generator_tools.dart
+version: 3.1.0 # This must match the version in lib/generator_tools.dart
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/pigeon/test/cpp_generator_test.dart b/packages/pigeon/test/cpp_generator_test.dart
new file mode 100644
index 0000000..d7cf69f
--- /dev/null
+++ b/packages/pigeon/test/cpp_generator_test.dart
@@ -0,0 +1,88 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:pigeon/ast.dart';
+import 'package:pigeon/cpp_generator.dart';
+import 'package:pigeon/pigeon.dart' show Error;
+import 'package:test/test.dart';
+
+void main() {
+  test('gen one api', () {
+    final Root root = Root(apis: <Api>[
+      Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+        Method(
+          name: 'doSomething',
+          arguments: <NamedType>[
+            NamedType(
+                type: const TypeDeclaration(
+                  baseName: 'Input',
+                  isNullable: false,
+                ),
+                name: 'input',
+                offset: null)
+          ],
+          returnType:
+              const TypeDeclaration(baseName: 'Output', isNullable: false),
+          isAsynchronous: false,
+        )
+      ])
+    ], classes: <Class>[
+      Class(name: 'Input', fields: <NamedType>[
+        NamedType(
+            type: const TypeDeclaration(
+              baseName: 'String',
+              isNullable: true,
+            ),
+            name: 'input',
+            offset: null)
+      ]),
+      Class(name: 'Output', fields: <NamedType>[
+        NamedType(
+            type: const TypeDeclaration(
+              baseName: 'String',
+              isNullable: true,
+            ),
+            name: 'output',
+            offset: null)
+      ])
+    ], enums: <Enum>[]);
+    {
+      final StringBuffer sink = StringBuffer();
+      generateCppHeader('', const CppOptions(), root, sink);
+      final String code = sink.toString();
+      expect(code, contains('class Input'));
+      expect(code, contains('class Output'));
+      expect(code, contains('class Api'));
+    }
+    {
+      final StringBuffer sink = StringBuffer();
+      generateCppSource(const CppOptions(), root, sink);
+      final String code = sink.toString();
+      expect(code, contains('Input::Input()'));
+      expect(code, contains('Output::Output'));
+      expect(
+          code,
+          contains(
+              'void Api::SetUp(flutter::BinaryMessenger* binary_messenger, Api* api)'));
+    }
+  });
+
+  test('doesn\'t support nullable fields', () {
+    final Root root = Root(
+      apis: <Api>[],
+      classes: <Class>[
+        Class(name: 'Foo', fields: <NamedType>[
+          NamedType(
+              name: 'foo',
+              type: const TypeDeclaration(baseName: 'int', isNullable: false))
+        ])
+      ],
+      enums: <Enum>[],
+    );
+    final List<Error> errors = validateCpp(const CppOptions(), root);
+    expect(errors.length, 1);
+    expect(errors[0].message, contains('foo'));
+    expect(errors[0].message, contains('Foo'));
+  });
+}
diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart
index 9fa597a..0d22c08 100644
--- a/packages/pigeon/test/pigeon_lib_test.dart
+++ b/packages/pigeon/test/pigeon_lib_test.dart
@@ -92,6 +92,18 @@
     expect(opts.objcSourceOut, equals('foo.m'));
   });
 
+  test('parse args - experimental_cpp_header_out', () {
+    final PigeonOptions opts =
+        Pigeon.parseArgs(<String>['--experimental_cpp_header_out', 'foo.h']);
+    expect(opts.cppHeaderOut, equals('foo.h'));
+  });
+
+  test('parse args - experimental_cpp_source_out', () {
+    final PigeonOptions opts =
+        Pigeon.parseArgs(<String>['--experimental_cpp_source_out', 'foo.cpp']);
+    expect(opts.cppSourceOut, equals('foo.cpp'));
+  });
+
   test('parse args - one_language', () {
     final PigeonOptions opts = Pigeon.parseArgs(<String>['--one_language']);
     expect(opts.oneLanguage, isTrue);
@@ -397,6 +409,26 @@
     expect(buffer.toString(), startsWith('// Copyright 2013'));
   });
 
+  test('C++ header generater copyright flag', () {
+    final Root root = Root(apis: <Api>[], classes: <Class>[], enums: <Enum>[]);
+    const PigeonOptions options = PigeonOptions(
+        cppHeaderOut: 'Foo.h', copyrightHeader: './copyright_header.txt');
+    const CppHeaderGenerator cppHeaderGenerator = CppHeaderGenerator();
+    final StringBuffer buffer = StringBuffer();
+    cppHeaderGenerator.generate(buffer, options, root);
+    expect(buffer.toString(), startsWith('// Copyright 2013'));
+  });
+
+  test('C++ source generater copyright flag', () {
+    final Root root = Root(apis: <Api>[], classes: <Class>[], enums: <Enum>[]);
+    const PigeonOptions options =
+        PigeonOptions(copyrightHeader: './copyright_header.txt');
+    const CppSourceGenerator cppSourceGenerator = CppSourceGenerator();
+    final StringBuffer buffer = StringBuffer();
+    cppSourceGenerator.generate(buffer, options, root);
+    expect(buffer.toString(), startsWith('// Copyright 2013'));
+  });
+
   test('nested enum', () {
     const String code = '''
 enum NestedEnum { one, two }
diff --git a/packages/pigeon/tool/run_tests.dart b/packages/pigeon/tool/run_tests.dart
index 59a7803..c96c684 100644
--- a/packages/pigeon/tool/run_tests.dart
+++ b/packages/pigeon/tool/run_tests.dart
@@ -204,6 +204,7 @@
     {required String input,
     String? cppHeaderOut,
     String? cppSourceOut,
+    String? cppNamespace,
     String? dartOut,
     String? dartTestOut,
     bool streamOutput = true}) async {
@@ -218,16 +219,22 @@
   ];
   if (cppHeaderOut != null) {
     args.addAll(<String>[
-      '--objc_header_out', // TODO(gaaclarke): Switch to c++.
+      '--experimental_cpp_header_out',
       cppHeaderOut,
     ]);
   }
   if (cppSourceOut != null) {
     args.addAll(<String>[
-      '--objc_source_out', // TODO(gaaclarke): Switch to c++.
+      '--experimental_cpp_source_out',
       cppSourceOut,
     ]);
   }
+  if (cppNamespace != null) {
+    args.addAll(<String>[
+      '--cpp_namespace',
+      cppNamespace,
+    ]);
+  }
   if (dartOut != null) {
     args.addAll(<String>['--dart_out', dartOut]);
   }
@@ -254,13 +261,35 @@
 
 Future<int> _runWindowsUnitTests() async {
   const String windowsUnitTestsPath = './platform_tests/windows_unit_tests';
-  final int generateCode = await _runPigeon(
-    input: './pigeons/message.dart',
-    cppHeaderOut: '$windowsUnitTestsPath/windows/test/message.g.h',
-    cppSourceOut: '$windowsUnitTestsPath/windows/test/message.g.cpp',
-  );
-  if (generateCode != 0) {
-    return generateCode;
+  const List<String> tests = <String>[
+    'message',
+    'all_datatypes',
+    'all_void',
+    'async_handlers',
+    'enum',
+    'host2flutter',
+    'list',
+    'multiple_arity',
+    // Removed until supported by C++ generator.
+    // 'non_null_fields',
+    'nullable_returns',
+    'primitive',
+    'void_arg_flutter',
+    'void_arg_host',
+    'voidflutter',
+    'voidhost'
+  ];
+  int generateCode = 0;
+
+  for (final String test in tests) {
+    generateCode = await _runPigeon(
+        input: './pigeons/$test.dart',
+        cppHeaderOut: '$windowsUnitTestsPath/windows/test/$test.g.h',
+        cppSourceOut: '$windowsUnitTestsPath/windows/test/$test.g.cpp',
+        cppNamespace: '${test}Test');
+    if (generateCode != 0) {
+      return generateCode;
+    }
   }
 
   final Process compile = await _streamOutput(Process.start(