From cfcce4be3d0169573ab9767b8592b59134a3e763 Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Tue, 18 Apr 2017 11:57:21 -0400 Subject: [PATCH] [objc] Add support for writing to exposed, managed fields (#136) --- objcgen/objcgenerator.cs | 124 +++++++++++++++--------- tests/managed/fields.cs | 45 +++++++++ tests/managed/managed.csproj | 1 + tests/objc-cli/libmanaged/Tests/Tests.m | 58 +++++++++++ 4 files changed, 184 insertions(+), 44 deletions(-) create mode 100644 tests/managed/fields.cs diff --git a/objcgen/objcgenerator.cs b/objcgen/objcgenerator.cs index 208b8f8..98772ed 100644 --- a/objcgen/objcgenerator.cs +++ b/objcgen/objcgenerator.cs @@ -301,51 +301,55 @@ namespace ObjC { implementation.WriteLine ($"\t\tvoid* __args [{pcount}];"); for (int i = 0; i < pcount; i++) { var p = parameters [i]; - - var pt = p.ParameterType; - var is_by_ref = pt.IsByRef; - if (is_by_ref) - pt = pt.GetElementType (); - - switch (Type.GetTypeCode (pt)) { - case TypeCode.String: - if (is_by_ref) { - implementation.WriteLine ($"\t\tMonoString* __string = *{p.Name} ? mono_string_new (__mono_context.domain, [*{p.Name} UTF8String]) : nil;"); - implementation.WriteLine ($"\t\t__args [{i}] = &__string;"); - post.AppendLine ($"\t\t*{p.Name} = mono_embeddinator_get_nsstring (__string);"); - } else - implementation.WriteLine ($"\t\t__args [{i}] = {p.Name} ? mono_string_new (__mono_context.domain, [{p.Name} UTF8String]) : nil;"); - break; - case TypeCode.Boolean: - case TypeCode.Char: - case TypeCode.SByte: - case TypeCode.Int16: - case TypeCode.Int32: - case TypeCode.Int64: - case TypeCode.Byte: - case TypeCode.UInt16: - case TypeCode.UInt32: - case TypeCode.UInt64: - case TypeCode.Single: - case TypeCode.Double: - if (is_by_ref) - implementation.WriteLine ($"\t\t__args [{i}] = {p.Name};"); - else - implementation.WriteLine ($"\t\t__args [{i}] = &{p.Name};"); - break; - default: - if (pt.IsValueType) - implementation.WriteLine ($"\t\t__args [{i}] = mono_object_unbox (mono_gchandle_get_target ({p.Name}->_object->_handle));"); - else if (types.Contains (pt)) - implementation.WriteLine ($"\t\t__args [{i}] = mono_gchandle_get_target ({p.Name}->_object->_handle);"); - else - throw new NotImplementedException ($"Converting type {pt.FullName} to mono code"); - break; - } + GenerateArgument (p.Name, $"__args[{i}]", p.ParameterType, ref post); } postInvoke = post.ToString (); } + void GenerateArgument (string paramaterName, string argumentName, Type t, ref StringBuilder post) + { + var is_by_ref = t.IsByRef; + if (is_by_ref) + t = t.GetElementType (); + + switch (Type.GetTypeCode (t)) { + case TypeCode.String: + if (is_by_ref) { + implementation.WriteLine ($"\t\tMonoString* __string = *{paramaterName} ? mono_string_new (__mono_context.domain, [*{paramaterName} UTF8String]) : nil;"); + implementation.WriteLine ($"\t\t{argumentName} = &__string;"); + post.AppendLine ($"\t\t*{paramaterName} = mono_embeddinator_get_nsstring (__string);"); + } else + implementation.WriteLine ($"\t\t{argumentName} = {paramaterName} ? mono_string_new (__mono_context.domain, [{paramaterName} UTF8String]) : nil;"); + break; + case TypeCode.Boolean: + case TypeCode.Char: + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Byte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + if (is_by_ref) + implementation.WriteLine ($"\t\t{argumentName} = {paramaterName};"); + else + implementation.WriteLine ($"\t\t{argumentName} = &{paramaterName};"); + break; + default: + if (t.IsValueType) + implementation.WriteLine ($"\t\t{argumentName} = mono_object_unbox (mono_gchandle_get_target ({paramaterName}->_object->_handle));"); + else + if (types.Contains (t)) + implementation.WriteLine ($"\t\t{argumentName} = {paramaterName} ? mono_gchandle_get_target ({paramaterName}->_object->_handle): nil;"); + else + throw new NotImplementedException ($"Converting type {t.FullName} to mono code"); + break; + } + } + protected override void Generate (PropertyInfo pi) { var getter = pi.GetGetMethod (); @@ -376,7 +380,7 @@ namespace ObjC { protected void Generate (FieldInfo fi) { - bool read_only = ((fi.Attributes & FieldAttributes.InitOnly) == FieldAttributes.InitOnly); + bool read_only = fi.IsInitOnly || fi.IsLiteral; headers.Write ("@property (nonatomic"); if (fi.IsStatic) @@ -384,8 +388,12 @@ namespace ObjC { if (read_only) headers.Write (", readonly"); var ft = fi.FieldType; + var bound = types.Contains (ft); + if (bound && ft.IsValueType) + headers.Write (", nonnull"); + var field_type = GetTypeName (ft); - if (types.Contains (ft)) + if (bound) field_type += " *"; var name = CamelCase (fi.Name); @@ -416,13 +424,41 @@ namespace ObjC { instance = "__instance"; } implementation.WriteLine ($"\tMonoObject* __result = mono_field_get_value_object (__mono_context.domain, __field, {instance});"); + if (types.Contains (ft)) { + implementation.WriteLine ("\tif (!__result)"); + implementation.WriteLine ("\t\treturn nil;"); + } ReturnValue (fi.FieldType); implementation.WriteLine ("}"); implementation.WriteLine (); if (read_only) return; - // TODO write support + implementation.Write (fi.IsStatic ? '+' : '-'); + implementation.WriteLine ($" (void) set{fi.Name}:({field_type})value"); + implementation.WriteLine ("{"); + implementation.WriteLine ("\tstatic MonoClassField* __field = nil;"); + implementation.WriteLine ("\tif (!__field) {"); + implementation.WriteLine ("#if TOKENLOOKUP"); + aname = type.Assembly.GetName ().Name; + implementation.WriteLine ($"\t\t__field = mono_class_get_field ({managed_type_name}_class, 0x{fi.MetadataToken:X8});"); + implementation.WriteLine ("#else"); + implementation.WriteLine ($"\t\tconst char __field_name [] = \"{fi.Name}\";"); + implementation.WriteLine ($"\t\t__field = mono_class_get_field_from_name ({managed_type_name}_class, __field_name);"); + implementation.WriteLine ("#endif"); + implementation.WriteLine ("\t}"); + StringBuilder sb = null; + implementation.WriteLine ($"\t\tvoid* __value;"); + GenerateArgument ("value", "__value", fi.FieldType, ref sb); + if (fi.IsStatic) { + implementation.WriteLine ($"\tMonoVTable *__vtable = mono_class_vtable (__mono_context.domain, {managed_type_name}_class);"); + implementation.WriteLine ("\tmono_field_static_set_value (__vtable, __field, __value);"); + } else { + implementation.WriteLine ($"\tMonoObject* __instance = mono_gchandle_get_target (_object->_handle);"); + implementation.WriteLine ("\tmono_field_set_value (__instance, __field, __value);"); + } + implementation.WriteLine ("}"); + implementation.WriteLine (); } public string GetReturnType (Type declaringType, Type returnType) diff --git a/tests/managed/fields.cs b/tests/managed/fields.cs new file mode 100644 index 0000000..fe75013 --- /dev/null +++ b/tests/managed/fields.cs @@ -0,0 +1,45 @@ +using System; + +namespace Fields { + + public class Class { + + // read only + public const long MaxLong = Int64.MaxValue; + + public static Class Scratch = new Class (true); + + // read/write + public static int Integer; + + public bool Boolean; + + public Struct Structure; + + public Class (bool enabled) + { + Boolean = enabled; + } + } + + public struct Struct { + + // read only + public static readonly Struct Empty; + + // read/write + public static Struct Scratch = new Struct (); + + public static int Integer; + + public bool Boolean; + + public Class Class; + + public Struct (bool enabled) + { + Boolean = enabled; + Class = new Class (false); + } + } +} diff --git a/tests/managed/managed.csproj b/tests/managed/managed.csproj index 9ab0e16..17bbc2d 100644 --- a/tests/managed/managed.csproj +++ b/tests/managed/managed.csproj @@ -38,6 +38,7 @@ + \ No newline at end of file diff --git a/tests/objc-cli/libmanaged/Tests/Tests.m b/tests/objc-cli/libmanaged/Tests/Tests.m index f6b5e99..1b31daf 100644 --- a/tests/objc-cli/libmanaged/Tests/Tests.m +++ b/tests/objc-cli/libmanaged/Tests/Tests.m @@ -194,6 +194,64 @@ XCTAssert (s == Enums_ShortEnumMin, "out enum 2"); } +- (void) testFieldsInReference { + XCTAssert ([Fields_Class maxLong] == LONG_MAX, "class const"); + + XCTAssert (Fields_Class.integer == 0, "static field unset"); + Fields_Class.integer = 1; + XCTAssert (Fields_Class.integer == 1, "static field set"); + + XCTAssertTrue (Fields_Class.scratch.boolean, "scratch default"); + + Fields_Class.scratch = [[Fields_Class alloc] initWithEnabled:false]; + XCTAssertFalse (Fields_Class.scratch.boolean, "scratch re-assign"); + + Fields_Class *ref1 = [[Fields_Class alloc] initWithEnabled:true]; + XCTAssertTrue (ref1.boolean, "init / boolean / true"); + ref1.boolean = false; + XCTAssertFalse (ref1.boolean, "init / boolean / set 1"); + + XCTAssertNotNil (ref1.structure, "init / class initialized 1"); + XCTAssertFalse (ref1.structure.boolean, "init / class / boolean / default"); + ref1.structure = [[Fields_Struct alloc] initWithEnabled:true]; + XCTAssertTrue (ref1.structure.boolean, "init / class / boolean / true"); + + Fields_Class *ref2 = [[Fields_Class alloc] initWithEnabled:false]; + XCTAssertNotNil ([ref2 structure], "init / class initialized 2"); + XCTAssertFalse ([ref2 boolean], "init / boolean / false"); +} + +- (void) testFieldsInValueType { + XCTAssert (Fields_Struct.integer == 0, "static valuetype field unset"); + Fields_Struct.integer = 1; + XCTAssert (Fields_Struct.integer == 1, "static valuetype field set"); + + XCTAssertFalse (Fields_Struct.scratch.boolean, "scratch default"); + + Fields_Struct.scratch = [[Fields_Struct alloc] initWithEnabled:true]; + XCTAssertTrue (Fields_Struct.scratch.boolean, "scratch re-assign"); + + Fields_Struct *empty = [Fields_Struct empty]; + XCTAssertNotNil (empty, "empty / struct static readonly"); + XCTAssertNil ([empty class], "empty / class uninitialized"); + + Fields_Struct *struct1 = [[Fields_Struct alloc] initWithEnabled:true]; + XCTAssertTrue (struct1.boolean, "init / boolean / true"); + struct1.boolean = false; + XCTAssertFalse (struct1.boolean, "init / boolean / set 1"); + + XCTAssertNotNil (struct1.class, "init / class initialized 1"); + XCTAssertFalse (struct1.class.boolean, "init / class / boolean / default"); + struct1.class = nil; + XCTAssertNil (struct1.class, "init / class set 1"); + struct1.class = [[Fields_Class alloc] initWithEnabled:true]; + XCTAssertTrue (struct1.class.boolean, "init / class / boolean / true"); + + Fields_Struct *struct2 = [[Fields_Struct alloc] initWithEnabled:false]; + XCTAssertNotNil ([struct2 class], "init / class initialized 2"); + XCTAssertFalse ([struct2 boolean], "init / boolean / false"); +} + - (void)testStaticCallPerformance { const int iterations = 1000000; [self measureBlock:^{