[ffigen] Correctly handle strings with null characters (#454)

* String fix mostly working using NSString.dataUsingEncoding:

* Handle case where NSData wasn't parsed

* Fix formatting (apparently Dart at HEAD disagrees with v2.18)
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart b/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
index ff96125..270cd18 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
@@ -300,20 +300,33 @@
     _interfaceRegistry[interface.originalName] = interface;
   }
 
+  ObjCInterface get nsData {
+    return _interfaceRegistry["NSData"] ??
+        (ObjCInterface(
+          originalName: "NSData",
+          builtInFunctions: this,
+          isBuiltIn: true,
+        ));
+  }
+
   void generateNSStringUtils(Writer w, StringBuffer s) {
-    // Generate a constructor that wraps stringWithCString, and a toString
-    // method that wraps UTF8String.
+    // Generate a constructor that wraps stringWithCharacters, and a toString
+    // method that wraps dataUsingEncoding.
     s.write('''
   factory NSString(${w.className} _lib, String str) {
-    final cstr = str.toNativeUtf8();
-    final nsstr = stringWithCString_encoding_(_lib, cstr.cast(), 4 /* UTF8 */);
+    final cstr = str.toNativeUtf16();
+    final nsstr = stringWithCharacters_length_(_lib, cstr.cast(), str.length);
     ${w.ffiPkgLibraryPrefix}.calloc.free(cstr);
     return nsstr;
   }
 
   @override
-  String toString() =>
-      (UTF8String).cast<${w.ffiPkgLibraryPrefix}.Utf8>().toDartString();
+  String toString() {
+    final data = dataUsingEncoding_(
+        0x94000100 /* NSUTF16LittleEndianStringEncoding */);
+    return data.bytes.cast<${w.ffiPkgLibraryPrefix}.Utf16>().toDartString(
+        length: length);
+  }
 ''');
   }
 
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
index a6099b8..68da95d 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
@@ -51,19 +51,25 @@
   ObjCInterface({
     String? usr,
     required String originalName,
-    required String name,
-    required this.lookupName,
+    String? name,
+    String? lookupName,
     String? dartDoc,
     required this.builtInFunctions,
     required this.isBuiltIn,
-  }) : super(
+  })  : lookupName = lookupName ?? originalName,
+        super(
           usr: usr,
           originalName: originalName,
-          name: name,
+          name: name ?? originalName,
           dartDoc: dartDoc,
-        );
+        ) {
+    if (isBuiltIn) {
+      builtInFunctions.registerInterface(this);
+    }
+  }
 
   bool get isNSString => isBuiltIn && originalName == "NSString";
+  bool get isNSData => isBuiltIn && originalName == "NSData";
 
   @override
   BindingString toBindingString(Writer w) {
@@ -224,10 +230,6 @@
     dependencies.add(this);
     builtInFunctions.addDependencies(dependencies);
 
-    if (isBuiltIn) {
-      builtInFunctions.registerInterface(this);
-    }
-
     _classObject = ObjCInternalGlobal(
         '_class_$originalName',
         (Writer w) => '${builtInFunctions.getClass.name}("$lookupName")',
@@ -241,6 +243,10 @@
       _addNSStringMethods();
     }
 
+    if (isNSData) {
+      _addNSDataMethods();
+    }
+
     if (superType != null) {
       superType!.addDependencies(dependencies);
       _copyClassMethodsFromSuperType();
@@ -290,20 +296,39 @@
 
   void _addNSStringMethods() {
     addMethod(ObjCMethod(
-      originalName: 'stringWithCString:encoding:',
+      originalName: 'stringWithCharacters:length:',
       kind: ObjCMethodKind.method,
       isClass: true,
       returnType: this,
       params_: [
-        ObjCMethodParam(PointerType(charType), 'cString'),
-        ObjCMethodParam(unsignedIntType, 'enc'),
+        ObjCMethodParam(PointerType(wCharType), 'characters'),
+        ObjCMethodParam(unsignedIntType, 'length'),
       ],
     ));
     addMethod(ObjCMethod(
-      originalName: 'UTF8String',
+      originalName: 'dataUsingEncoding:',
+      kind: ObjCMethodKind.method,
+      isClass: false,
+      returnType: builtInFunctions.nsData,
+      params_: [
+        ObjCMethodParam(unsignedIntType, 'encoding'),
+      ],
+    ));
+    addMethod(ObjCMethod(
+      originalName: 'length',
       kind: ObjCMethodKind.propertyGetter,
       isClass: false,
-      returnType: PointerType(charType),
+      returnType: unsignedIntType,
+      params_: [],
+    ));
+  }
+
+  void _addNSDataMethods() {
+    addMethod(ObjCMethod(
+      originalName: 'bytes',
+      kind: ObjCMethodKind.propertyGetter,
+      isClass: false,
+      returnType: PointerType(voidType),
       params_: [],
     ));
   }
diff --git a/pkgs/ffigen/test/native_objc_test/string_test.dart b/pkgs/ffigen/test/native_objc_test/string_test.dart
index 458621e..068e5ed 100644
--- a/pkgs/ffigen/test/native_objc_test/string_test.dart
+++ b/pkgs/ffigen/test/native_objc_test/string_test.dart
@@ -25,16 +25,17 @@
       generateBindingsForCoverage('string');
     });
 
-    // TODO(#329): Add 'Embedded\u0000Null'.
-    for (final s in ['Hello', '🇵🇬']) {
+    for (final s in ['Hello', '🇵🇬', 'Embedded\u0000Null']) {
       test('NSString to/from Dart string [$s]', () {
         final ns1 = NSString(lib, s);
-        expect(ns1.toString(), s);
         expect(ns1.length, s.length);
+        expect(ns1.toString().length, s.length);
+        expect(ns1.toString(), s);
 
         final ns2 = s.toNSString(lib);
-        expect(ns2.toString(), s);
         expect(ns2.length, s.length);
+        expect(ns2.toString().length, s.length);
+        expect(ns2.toString(), s);
       });
     }