void Specialize(AutoIndentStringBuilder sb, ObjCMethod method, List<Exception> exceptions)
        {
            var isGeneric = method.DeclaringType.IsGeneric;

            switch (method.CurrentTrampoline) {
            case Trampoline.Retain:
                sb.WriteLine ("-(id) retain");
                sb.WriteLine ("{");
                sb.WriteLine ("return xamarin_retain_trampoline (self, _cmd);");
                sb.WriteLine ("}");
                sb.WriteLine ();
                return;
            case Trampoline.Release:
                sb.WriteLine ("-(void) release");
                sb.WriteLine ("{");
                sb.WriteLine ("xamarin_release_trampoline (self, _cmd);");
                sb.WriteLine ("}");
                sb.WriteLine ();
                return;
            case Trampoline.GetGCHandle:
                sb.WriteLine ("-(int) xamarinGetGCHandle");
                sb.WriteLine ("{");
                sb.WriteLine ("return __monoObjectGCHandle.gc_handle;");
                sb.WriteLine ("}");
                sb.WriteLine ();
                return;
            case Trampoline.SetGCHandle:
                sb.WriteLine ("-(void) xamarinSetGCHandle: (int) gc_handle");
                sb.WriteLine ("{");
                sb.WriteLine ("__monoObjectGCHandle.gc_handle = gc_handle;");
                sb.WriteLine ("__monoObjectGCHandle.native_object = self;");
                sb.WriteLine ("}");
                sb.WriteLine ();
                return;
            case Trampoline.Constructor:
                if (isGeneric) {
                    sb.WriteLine (GetObjCSignature (method, exceptions));
                    sb.WriteLine ("{");
                    sb.WriteLine ("xamarin_throw_product_exception (4126, \"Cannot construct an instance of the type '{0}' from Objective-C because the type is generic.\");\n", method.DeclaringType.Type.FullName.Replace ("/", "+"));
                    sb.WriteLine ("return self;");
                    sb.WriteLine ("}");
                    return;
                }
                break;
            #if MONOMAC
            case Trampoline.CopyWithZone1:
                sb.AppendLine ("-(id) copyWithZone: (NSZone *) zone");
                sb.AppendLine ("{");
                sb.AppendLine ("id rv;");
                sb.AppendLine ("int gchandle;");
                sb.AppendLine ();
                sb.AppendLine ("gchandle = xamarin_get_gchandle_with_flags (self);");
                sb.AppendLine ("if (gchandle != 0)");
                sb.Indent ().AppendLine ("xamarin_set_gchandle (self, 0);").Unindent ();
                // Call the base class implementation
                sb.AppendLine ("rv = [super copyWithZone: zone];");
                sb.AppendLine ();
                sb.AppendLine ("if (gchandle != 0)");
                sb.Indent ().AppendLine ("xamarin_set_gchandle (self, gchandle);").Unindent ();
                sb.AppendLine ();
                sb.AppendLine ("return rv;");
                sb.AppendLine ("}");
                return;
            case Trampoline.CopyWithZone2:
                sb.AppendLine ("-(id) copyWithZone: (NSZone *) zone");
                sb.AppendLine ("{");
                sb.AppendLine ("return xamarin_copyWithZone_trampoline2 (self, _cmd, zone);");
                sb.AppendLine ("}");
                return;
            #endif
            }

            var rettype = string.Empty;
            var returntype = method.Method.ReturnType;
            var isStatic = method.IsStatic;
            var isInstanceCategory = method.IsCategoryInstance;
            var isCtor = false;
            var num_arg = method.Method.Parameters.Count;
            var descriptiveMethodName = method.DescriptiveMethodName;
            var name = GetUniqueTrampolineName ("native_to_managed_trampoline_" + descriptiveMethodName);
            var isVoid = returntype.FullName == "System.Void";
            var arguments = new List<string> ();
            var merge_bodies = true;

            switch (method.CurrentTrampoline) {
            case Trampoline.None:
            case Trampoline.Normal:
            case Trampoline.Static:
            case Trampoline.Single:
            case Trampoline.Double:
            case Trampoline.Long:
            case Trampoline.StaticLong:
            case Trampoline.StaticDouble:
            case Trampoline.StaticSingle:
            case Trampoline.X86_DoubleABI_StaticStretTrampoline:
            case Trampoline.X86_DoubleABI_StretTrampoline:
            case Trampoline.StaticStret:
            case Trampoline.Stret:
                switch (returntype.FullName) {
                case "System.Int64":
                    rettype = "long long";
                    break;
                case "System.UInt64":
                    rettype = "unsigned long long";
                    break;
                case "System.Single":
                    rettype = "float";
                    break;
                case "System.Double":
                    rettype = "double";
                    break;
                default:
                    rettype = ToObjCParameterType (returntype, descriptiveMethodName, exceptions, method.Method);
                    break;
                }
                break;
            case Trampoline.Constructor:
                rettype = "id";
                isCtor = true;
                break;
            default:
                return;
            }

            comment.Clear ();
            nslog_start.Clear ();
            nslog_end.Clear ();
            copyback.Clear ();
            invoke.Clear ();
            setup_call_stack.Clear ();
            body.Clear ();
            setup_return.Clear ();

            counter++;

            body.WriteLine ("{");

            var indent = merge_bodies ? sb.Indentation : sb.Indentation + 1;
            body.Indentation = indent;
            copyback.Indentation = indent;
            invoke.Indentation = indent;
            setup_call_stack.Indentation = indent;
            setup_return.Indentation = indent;

            // A comment describing the managed signature
            if (trace) {
                nslog_start.Indentation = sb.Indentation;
                comment.Indentation = sb.Indentation;
                nslog_end.Indentation = sb.Indentation;

                comment.AppendFormat ("// {2} {0}.{1} (", method.Method.DeclaringType.FullName, method.Method.Name, method.Method.ReturnType.FullName);
                for (int i = 0; i < num_arg; i++) {
                    var param = method.Method.Parameters [i];
                    if (i > 0)
                        comment.Append (", ");
                    comment.AppendFormat ("{0} {1}", param.ParameterType.FullName, param.Name);
                }
                comment.AppendLine (")");
                comment.AppendLine ("// ArgumentSemantic: {0} IsStatic: {1} Selector: '{2}' Signature: '{3}'", method.ArgumentSemantic, method.IsStatic, method.Selector, method.Signature);
            }

            // a couple of debug printfs
            if (trace) {
                StringBuilder args = new StringBuilder ();
                nslog_start.AppendFormat ("NSLog (@\"{0} (this: %@, sel: %@", name);
                for (int i = 0; i < num_arg; i++) {
                    var type = method.Method.Parameters [i].ParameterType;
                    bool isRef = type.IsByReference;
                    if (isRef)
                        type = type.GetElementType ();
                    var td = type.Resolve ();

                    nslog_start.AppendFormat (", {0}: ", method.Method.Parameters [i].Name);
                    args.Append (", ");
                    switch (type.FullName) {
                    case "System.Drawing.RectangleF":
                        if (isRef) {
                            nslog_start.Append ("%p : %@");
            #if MMP
                            args.AppendFormat ("p{0}, p{0} ? NSStringFromRect (*p{0}) : @\"NULL\"", i);
            #else
                            args.AppendFormat ("p{0}, p{0} ? NSStringFromCGRect (*p{0}) : @\"NULL\"", i);
            #endif
                        } else {
                            nslog_start.Append ("%@");
            #if MMP
                            args.AppendFormat ("NSStringFromRect (p{0})", i);
            #else
                            args.AppendFormat ("NSStringFromCGRect (p{0})", i);
            #endif
                        }
                        break;
                    case "System.Drawing.PointF":
                        if (isRef) {
                            nslog_start.Append ("%p: %@");
            #if MMP
                            args.AppendFormat ("p{0}, p{0} ? NSStringFromPoint (*p{0}) : @\"NULL\"", i);
            #else
                            args.AppendFormat ("p{0}, p{0} ? NSStringFromCGPoint (*p{0}) : @\"NULL\"", i);
            #endif
                        } else {
                            nslog_start.Append ("%@");
            #if MMP
                            args.AppendFormat ("NSStringFromPoint (p{0})", i);
            #else
                            args.AppendFormat ("NSStringFromCGPoint (p{0})", i);
            #endif
                        }
                        break;
                    default:
                        bool unknown;
                        var spec = GetPrintfFormatSpecifier (td, out unknown);
                        if (unknown) {
                            nslog_start.AppendFormat ("%{0}", spec);
                            args.AppendFormat ("&p{0}", i);
                        } else if (isRef) {
                            nslog_start.AppendFormat ("%p *= %{0}", spec);
                            args.AppendFormat ("p{0}, *p{0}", i);
                        } else {
                            nslog_start.AppendFormat ("%{0}", spec);
                            args.AppendFormat ("p{0}", i);
                        }
                        break;
                    }
                }

                string ret_arg = string.Empty;
                nslog_end.Append (nslog_start.ToString ());
                if (!isVoid) {
                    bool unknown;
                    var spec = GetPrintfFormatSpecifier (method.Method.ReturnType.Resolve (), out unknown);
                    if (!unknown) {
                        nslog_end.Append (" ret: %");
                        nslog_end.Append (spec);
                        ret_arg = ", res";
                    }
                }
                nslog_end.Append (") END\", self, NSStringFromSelector (_cmd)");
                nslog_end.Append (args.ToString ());
                nslog_end.Append (ret_arg);
                nslog_end.AppendLine (");");

                nslog_start.Append (") START\", self, NSStringFromSelector (_cmd)");
                nslog_start.Append (args.ToString ());
                nslog_start.AppendLine (");");
            }

            // prepare the parameters
            var baseMethod = GetBaseMethodInTypeHierarchy (method.Method);
            for (int i = 0; i < num_arg; i++) {
                var param = method.Method.Parameters [i];
                var paramBase = baseMethod.Parameters [i];
                var type = method.Parameters [i];
                var objctype = ToObjCParameterType (type, descriptiveMethodName, exceptions, method.Method);
                var original_objctype = objctype;
                var isRef = type.IsByReference;
                var isOut = param.IsOut || paramBase.IsOut;
                var isArray = type is ArrayType;
                var isNativeEnum = false;
                var td = type.Resolve ();
                var isVariadic = i + 1 == num_arg && method.IsVariadic;

                if (isRef) {
                    type = type.GetElementType ();
                    td = type.Resolve ();
                    original_objctype = ToObjCParameterType (type, descriptiveMethodName, exceptions,  method.Method);
                    objctype = ToObjCParameterType (type, descriptiveMethodName, exceptions, method.Method) + "*";
                } else if (td.IsEnum) {
                    type = SharedStatic.GetEnumUnderlyingType (td);
                    isNativeEnum = IsDualBuild && SharedStatic.HasAttribute (td, ObjCRuntime, StringConstants.NativeAttribute);
                    td = type.Resolve ();
                }

                switch (type.FullName) {
                case "System.Int64":
                case "System.UInt64":
                    // We already show MT4145 if the underlying enum type isn't a long or ulong
                    if (isNativeEnum) {
                        string tp;
                        string ntp;
                        if (type.FullName == "System.UInt64") {
                            tp = "unsigned long long";
                            ntp = "NSUInteger";
                        } else {
                            tp = "long long";
                            ntp = "NSInteger";
                        }

                        if (isRef || isOut) {
                            setup_call_stack.AppendLine ("{1} nativeEnum{0} = 0;", i, tp);
                            setup_call_stack.AppendLine ("arg_ptrs [{0}] = &nativeEnum{0};", i);
                            copyback.AppendLine ("*p{0} = ({1}) nativeEnum{0};", ntp);
                        } else {
                            setup_call_stack.AppendLine ("{1} nativeEnum{0} = p{0};", i, tp);
                            setup_call_stack.AppendLine ("arg_ptrs [{0}] = &nativeEnum{0};", i);
                        }
                        break;
                    }
                    goto case "System.SByte";
                case "System.SByte":
                case "System.Byte":
                case "System.Char":
                case "System.Int16":
                case "System.UInt16":
                case "System.Int32":
                case "System.UInt32":
                case "System.Single":
                case "System.Double":
                case "System.Boolean":
                    if (isRef || isOut) {
                        // The isOut semantics isn't quite correct here: we pass the actual input value to managed code.
                        // In theory we should create a temp location and then use a writeback when done instead.
                        // This should be safe though, since managed code (at least C#) can't actually observe the value.
                        setup_call_stack.AppendLine ("arg_ptrs [{0}] = p{0};", i);
                    } else {
                        setup_call_stack.AppendLine ("arg_ptrs [{0}] = &p{0};", i);
                    }
                    break;
                case "System.IntPtr":
                    if (isVariadic) {
                        setup_call_stack.AppendLine ("va_list a{0};", i);
                        setup_call_stack.AppendLine ("va_start (a{0}, p{1});", i, i - 1);
                        setup_call_stack.AppendLine ("arg_ptrs [{0}] = &a{0};", i);
                        copyback.AppendLine ("va_end (a{0});", i);
                    } else if (isOut) {
                        setup_call_stack.AppendLine ("{1} a{0} = 0;", i, objctype);
                        setup_call_stack.AppendLine ("arg_ptrs [{0}] = &a{0};", i);
                        copyback.AppendLine ("*p{0} = a{0};", i);
                    } else if (isRef) {
                        setup_call_stack.AppendLine ("arg_ptrs [{0}] = p{0};", i);
                    } else {
                        setup_call_stack.AppendLine ("{1} a{0} = p{0};", i, original_objctype);
                        setup_call_stack.AppendLine ("arg_ptrs [{0}] = &a{0};", i);
                    }
                    break;
                case "ObjCRuntime.Selector":
                case CompatNamespace + ".ObjCRuntime.Selector":
                    if (isRef) {
                        if (isOut) {
                            setup_call_stack.AppendLine ("void *a{0} = NULL;", i);
                        } else {
                            setup_call_stack.AppendLine ("void *a{0} = *p{0} ? xamarin_get_selector (*p{0}) : NULL;", i);
                        }
                        setup_call_stack.AppendLine ("arg_ptrs [{0}] = &a{0};", i);
                        copyback.AppendLine ("*p{0} = a{0};", i);
                    } else {
                        setup_call_stack.AppendLine ("arg_ptrs [{0}] = p{0} ? xamarin_get_selector (p{0}) : NULL;", i);
                    }
                    break;
                case "ObjCRuntime.Class":
                case CompatNamespace + ".ObjCRuntime.Class":
                    if (isRef) {
                        if (isOut) {
                            setup_call_stack.AppendLine ("void *a{0} = NULL;", i);
                        } else {
                            setup_call_stack.AppendLine ("void *a{0} = *p{0} ? xamarin_get_class (*p{0}) : NULL;", i);
                        }
                        setup_call_stack.AppendLine ("arg_ptrs [{0}] = &a{0};", i);
                        copyback.AppendLine ("*p{0} = a{0};", i);
                    } else {
                        setup_call_stack.AppendLine ("arg_ptrs [{0}] = p{0} ?  xamarin_get_class (p{0}) : NULL;", i);
                    }
                    break;
                case "System.String":
                    // This should always be an NSString and never char*
                    if (isRef) {
                        if (isOut) {
                            setup_call_stack.AppendLine ("MonoString *a{0} = NULL;", i);
                        } else {
                            setup_call_stack.AppendLine ("MonoString *a{0} = *p{0} ? mono_string_new (mono_domain_get (), [(*p{0}) UTF8String]) : NULL;", i);
                        }
                        setup_call_stack.AppendLine ("arg_ptrs [{0}] = &a{0};", i);
                        copyback.AppendLine ("char *str{0} = mono_string_to_utf8 (a{0});", i);
                        copyback.AppendLine ("*p{0} = [[NSString alloc] initWithUTF8String:str{0}];", i);
                        copyback.AppendLine ("[*p{0} autorelease];", i);
                        copyback.AppendLine ("mono_free (str{0});", i);
                    } else {
                        setup_call_stack.AppendLine ("arg_ptrs [{0}] = p{0} ? mono_string_new (mono_domain_get (), [p{0} UTF8String]) : NULL;", i);
                    }
                    break;
                default:
                    if (isArray) {
                        var elementType = ((ArrayType)type).ElementType;
                        var isNativeObject = false;

                        setup_call_stack.AppendLine ("if (p{0}) {{", i);
                        setup_call_stack.AppendLine ("NSArray *arr = (NSArray *) p{0};", i);
                        if (Driver.EnableDebug)
                            setup_call_stack.AppendLine ("xamarin_check_objc_type (p{0}, [NSArray class], _cmd, self, {0}, managed_method);", i);
                        setup_call_stack.AppendLine ("MonoClass *e_class;");
                        setup_call_stack.AppendLine ("MonoArray *marr;");
                        setup_call_stack.AppendLine ("MonoType *p;");
                        setup_call_stack.AppendLine ("int j;", i);
                        setup_call_stack.AppendLine ("p = xamarin_get_parameter_type (managed_method, {0});", i);
                        setup_call_stack.AppendLine ("e_class = mono_class_get_element_class (mono_class_from_mono_type (p));");
                        setup_call_stack.AppendLine ("marr = mono_array_new (mono_domain_get (), e_class, [arr count]);", i);
                        setup_call_stack.AppendLine ("for (j = 0; j < [arr count]; j++) {{", i);
                        if (elementType.FullName == "System.String") {
                            setup_call_stack.AppendLine ("NSString *sv = (NSString *) [arr objectAtIndex: j];", i);
                            setup_call_stack.AppendLine ("mono_array_set (marr, MonoString *, j, mono_string_new (mono_domain_get (), [sv UTF8String]));", i);
                        } else if (IsNSObject (elementType) || (elementType.Namespace == "System" && elementType.Name == "Object") || (isNativeObject = SharedStatic.IsNativeObject (elementType))) {
                            setup_call_stack.AppendLine ("NSObject *nobj = [arr objectAtIndex: j];");
                            setup_call_stack.AppendLine ("MonoObject *mobj{0} = NULL;", i);
                            setup_call_stack.AppendLine ("if (nobj) {");
                            if (isNativeObject) {
                                TypeDefinition nativeObjType = elementType.Resolve ();

                                if (nativeObjType.IsInterface) {
                                    var wrapper_type = GetProtocolAttributeWrapperType (nativeObjType);
                                    if (wrapper_type == null)
                                        throw ErrorHelper.CreateError (4125, "The registrar found an invalid type '{0}' in signature for method '{1}': " +
                                            "The interface must have a Protocol attribute specifying its wrapper type.",
                                            td.FullName, descriptiveMethodName);

                                    nativeObjType = wrapper_type.Resolve ();
                                }

                                // verify that the type has a ctor with two parameters
                                if (!HasIntPtrBoolCtor (nativeObjType))
                                    throw ErrorHelper.CreateError (4103,
                                        "The registrar found an invalid type `{0}` in signature for method `{1}`: " +
                                        "The type implements INativeObject, but does not have a constructor that takes " +
                                        "two (IntPtr, bool) arguments.", nativeObjType.FullName, descriptiveMethodName);

                                if (nativeObjType.IsInterface) {
                                    setup_call_stack.AppendLine ("mobj{0} = xamarin_get_inative_object_static (nobj, false, \"{1}\", \"{2}\");", i, GetAssemblyQualifiedName (nativeObjType), GetAssemblyQualifiedName (elementType));
                                } else {
                                    // find the MonoClass for this parameter
                                    setup_call_stack.AppendLine ("MonoType *type{0};", i);
                                    setup_call_stack.AppendLine ("type{0} = xamarin_get_parameter_type (managed_method, {0});", i);
                                    setup_call_stack.AppendLine ("mobj{0} = xamarin_get_inative_object_dynamic (nobj, false, mono_type_get_object (mono_domain_get (), mono_class_get_type (e_class)));", i);
                                }
                            } else {
                                setup_call_stack.AppendLine ("mobj{0} = xamarin_get_managed_object_for_ptr_fast (nobj);", i);
                            }
                            if (Driver.EnableDebug) {
                                setup_call_stack.AppendLine ("xamarin_verify_parameter (mobj{0}, _cmd, self, nobj, {0}, e_class, managed_method);", i);
                            }
                            setup_call_stack.AppendLine ("}");
                            setup_call_stack.AppendLine ("mono_array_set (marr, MonoObject *, j, mobj{0});", i);
                        } else {
                            throw ErrorHelper.CreateError (4111, method.Method, "The registrar cannot build a signature for type `{0}' in method `{1}`.", type.FullName, descriptiveMethodName);
                        }
                        setup_call_stack.AppendLine ("}");
                        setup_call_stack.AppendLine ("arg_ptrs [{0}] = marr;", i);
                        setup_call_stack.AppendLine ("} else {");
                        setup_call_stack.AppendLine ("arg_ptrs [{0}] = NULL;", i);
                        setup_call_stack.AppendLine ("}");
                    } else if (IsNSObject (type)) {
                        if (isRef) {
                            setup_call_stack.AppendLine ("MonoObject *mobj{0} = NULL;", i);
                            if (!isOut) {
                                setup_call_stack.AppendLine ("NSObject *nsobj{0} = *(NSObject **) p{0};", i);
                                setup_call_stack.AppendLine ("if (nsobj{0}) {{", i);
                                setup_call_stack.AppendLine ("MonoType *paramtype{0} = xamarin_get_parameter_type (managed_method, {0});", i);
                                setup_call_stack.AppendLine ("mobj{0} = xamarin_get_nsobject_with_type_for_ptr (nsobj{0}, false, paramtype{0});", i);
                                if (Driver.EnableDebug) {
                                    setup_call_stack.AppendLine ("xamarin_verify_parameter (mobj{0}, _cmd, self, nsobj{0}, {0}, mono_class_from_mono_type (paramtype{0}), managed_method);", i);
                                }
                                setup_call_stack.AppendLine ("}");
                            }

                            // argument semantics?
                            setup_call_stack.AppendLine ("arg_ptrs [{0}] = (int *) &mobj{0};", i);
                            copyback.AppendLine ("void * handle{0} = NULL;", i);
                            copyback.AppendLine ("if (mobj{0} != NULL)", i);
                            copyback.AppendLine ("handle{0} = xamarin_get_nsobject_handle (mobj{0});", i);
                            copyback.AppendLine ("*p{0} = (id) handle{0};", i);
                        } else {
                            setup_call_stack.AppendLine ("NSObject *nsobj{0} = (NSObject *) p{0};", i);
                            if (method.ArgumentSemantic == ArgumentSemantic.Copy) {
                                setup_call_stack.AppendLine ("nsobj{0} = [nsobj{0} copy];", i);
                                setup_call_stack.AppendLine ("[nsobj{0} autorelease];", i);
                            }
                            setup_call_stack.AppendLine ("MonoObject *mobj{0} = NULL;", i);
                            setup_call_stack.AppendLine ("int32_t created{0} = false;", i);
                            setup_call_stack.AppendLine ("if (nsobj{0}) {{", i);
                            setup_call_stack.AppendLine ("MonoType *paramtype{0} = xamarin_get_parameter_type (managed_method, {0});", i);
                            setup_call_stack.AppendLine ("mobj{0} = xamarin_get_nsobject_with_type_for_ptr_created (nsobj{0}, false, paramtype{0}, &created{0});", i);
                            if (Driver.EnableDebug) {
                                setup_call_stack.AppendLine ("xamarin_verify_parameter (mobj{0}, _cmd, self, nsobj{0}, {0}, mono_class_from_mono_type (paramtype{0}), managed_method);", i);
                            }
                            setup_call_stack.AppendLine ("}");
                            setup_call_stack.AppendLine ("arg_ptrs [{0}] = mobj{0};", i);

                            if (SharedStatic.HasAttribute (paramBase, ObjCRuntime, StringConstants.TransientAttribute)) {
                                copyback.AppendLine ("if (created{0})", i);
                                copyback.Indentation++;
                                copyback.AppendLine ("xamarin_dispose (mobj{0});", i);
                                copyback.Indentation--;

                            }
                        }
                    } else if (SharedStatic.IsNativeObject (td)) {
                        TypeDefinition nativeObjType = td;

                        if (td.IsInterface) {
                            var wrapper_type = GetProtocolAttributeWrapperType (td);
                            if (wrapper_type == null)
                                throw ErrorHelper.CreateError (4125, "The registrar found an invalid type '{0}' in signature for method '{1}': " +
                                                              "The interface must have a Protocol attribute specifying its wrapper type.",
                                                              td.FullName, descriptiveMethodName);

                            nativeObjType = wrapper_type.Resolve ();
                        }

                        // verify that the type has a ctor with two parameters
                        if (!HasIntPtrBoolCtor (nativeObjType))
                            throw ErrorHelper.CreateError (4103,
                                                          "The registrar found an invalid type `{0}` in signature for method `{1}`: " +
                                "The type implements INativeObject, but does not have a constructor that takes " +
                                "two (IntPtr, bool) arguments.", nativeObjType.FullName, descriptiveMethodName);

                        if (!td.IsInterface) {
                            // find the MonoClass for this parameter
                            setup_call_stack.AppendLine ("MonoType *type{0};", i);
                            setup_call_stack.AppendLine ("type{0} = xamarin_get_parameter_type (managed_method, {0});", i);
                        }
                        if (isRef) {
                            setup_call_stack.AppendLine ("MonoObject *inobj{0};", i);
                            if (isOut) {
                                setup_call_stack.AppendLine ("inobj{0} = NULL;", i);
                            } else if (td.IsInterface) {
                                setup_call_stack.AppendLine ("inobj{0} = xamarin_get_inative_object_static (*p{0}, false, \"{1}\", \"{2}\");", i, GetAssemblyQualifiedName (nativeObjType), GetAssemblyQualifiedName (td));
                            } else {
                                setup_call_stack.AppendLine ("inobj{0} = xamarin_get_inative_object_dynamic (*p{0}, false, mono_type_get_object (mono_domain_get (), type{0}));", i);
                            }
                            setup_call_stack.AppendLine ("arg_ptrs [{0}] = &inobj{0};", i);
                            copyback.AppendLine ("id handle{0} = nil;", i);
                            copyback.AppendLine ("if (inobj{0} != NULL)", i);
                            copyback.AppendLine ("handle{0} = xamarin_get_handle_for_inativeobject (inobj{0});", i);
                            copyback.AppendLine ("*p{0} = (id) handle{0};", i);
                        } else {
                            if (td.IsInterface) {
                                setup_call_stack.AppendLine ("arg_ptrs [{0}] = xamarin_get_inative_object_static (p{0}, false, \"{1}\", \"{2}\");", i, GetAssemblyQualifiedName (nativeObjType), GetAssemblyQualifiedName (td));
                            } else {
                                setup_call_stack.AppendLine ("arg_ptrs [{0}] = xamarin_get_inative_object_dynamic (p{0}, false, mono_type_get_object (mono_domain_get (), type{0}));", i);
                            }
                        }
                    } else if (type.IsValueType) {
                        if (isRef || isOut) {
                            // The isOut semantics isn't quite correct here: we pass the actual input value to managed code.
                            // In theory we should create a temp location and then use a writeback when done instead.
                            // This should be safe though, since managed code (at least C#) can't actually observe the value.
                            setup_call_stack.AppendLine ("arg_ptrs [{0}] = p{0};", i);
                        } else {
                            setup_call_stack.AppendLine ("arg_ptrs [{0}] = &p{0};", i);
                        }
                    } else if (td.BaseType.FullName == "System.MulticastDelegate") {
                        if (isRef) {
                            throw ErrorHelper.CreateError (4110,
                                                          "The registrar cannot marshal the out parameter of type `{0}` in signature for method `{1}`.",
                                                          type.FullName, descriptiveMethodName);
                        } else {
                            // Bug #4858 (also related: #4718)
                            setup_call_stack.AppendLine ("if (p{0}) {{", i);
                            setup_call_stack.AppendLine ("arg_ptrs [{0}] = (void *) xamarin_get_delegate_for_block_parameter (managed_method, {0}, p{0});", i);
                            setup_call_stack.AppendLine ("} else {");
                            setup_call_stack.AppendLine ("arg_ptrs [{0}] = NULL;", i);
                            setup_call_stack.AppendLine ("}");
                        }
                    } else {
                        throw ErrorHelper.CreateError (4105,
                                                      "The registrar cannot marshal the parameter of type `{0}` in signature for method `{1}`.",
                                                      type.FullName, descriptiveMethodName);
                    }
                    break;
                }
            }

            // the actual invoke
            if (isCtor) {
                invoke.AppendLine ("mthis = mono_object_new (mono_domain_get (), mono_method_get_class (managed_method));", counter);
                invoke.AppendLine ("uint8_t flags = NSObjectFlagsNativeRef;");
                invoke.AppendLine ("xamarin_set_nsobject_handle (mthis, self);");
                invoke.AppendLine ("xamarin_set_nsobject_flags (mthis, flags);");
            }

            if (!isVoid)
                invoke.AppendFormat ("{0} retval = ", "MonoObject *");

            invoke.AppendLine ("mono_runtime_invoke (managed_method, {0}, arg_ptrs, NULL);", isStatic ? "NULL" : "mthis");

            if (isCtor)
                invoke.AppendLine ("xamarin_create_managed_ref (self, mthis, true);");

            // prepare the return value
            if (!isVoid) {
                setup_return.AppendLine ("{0} res;", rettype);
                var isArray = returntype is ArrayType;
                var type = returntype.Resolve () ?? returntype;
                var retain = method.RetainReturnValue;

                if (returntype.IsValueType) {
                    setup_return.AppendLine ("res = *({0} *) mono_object_unbox ((MonoObject *) retval);", rettype);
                } else if (isArray) {
                    var elementType = ((ArrayType) returntype).ElementType;

                    setup_return.AppendLine ("if (retval) {");
                    setup_return.AppendLine ("int length = mono_array_length ((MonoArray *) retval);");
                    setup_return.AppendLine ("int i;");
                    setup_return.AppendLine ("id *buf = (id *) malloc (sizeof (void *) * length);");
                    setup_return.AppendLine ("for (i = 0; i < length; i++) {");
                    setup_return.AppendLine ("MonoObject *value = mono_array_get ((MonoArray *) retval, MonoObject *, i);");

                    if (elementType.FullName == "System.String") {
                        setup_return.AppendLine ("char *str = mono_string_to_utf8 ((MonoString *) value);");
                        setup_return.AppendLine ("NSString *sv = [[NSString alloc] initWithUTF8String:str];");
                        setup_return.AppendLine ("[sv autorelease];");
                        setup_return.AppendLine ("mono_free (str);");
                        setup_return.AppendLine ("buf [i] = sv;");
                    } else if (IsNSObject (elementType)) {
                        setup_return.AppendLine ("buf [i] = xamarin_get_nsobject_handle ((MonoObject *) value);");
                    } else if (IsINativeObject (elementType)) {
                        setup_return.AppendLine ("buf [i] = xamarin_get_handle_for_inativeobject ((MonoObject *) value);");
                    } else {
                        throw ErrorHelper.CreateError (4111, method.Method, "The registrar cannot build a signature for type `{0}' in method `{1}`.", returntype.FullName, descriptiveMethodName);
                    }

                    setup_return.AppendLine ("}");

                    setup_return.AppendLine ("NSArray *arr = [[NSArray alloc] initWithObjects: buf count: length];");
                    setup_return.AppendLine ("free (buf);");
                    if (!retain)
                        setup_return.AppendLine ("[arr autorelease];");
                    setup_return.AppendLine ("res = arr;");
                    setup_return.AppendLine ("} else {");
                    setup_return.AppendLine ("res = NULL;");
                    setup_return.AppendLine ("}");
                    setup_return.AppendLine ("xamarin_framework_peer_lock ();");
                    setup_return.AppendLine ("mt_dummy_use (retval);");
                    setup_return.AppendLine ("xamarin_framework_peer_unlock ();");
                } else {
                    setup_return.AppendLine ("if (!retval) {");
                    setup_return.AppendLine ("res = NULL;");
                    setup_return.AppendLine ("} else {");

                    if (IsNSObject (type)) {
                        setup_return.AppendLine ("id retobj;");
                        setup_return.AppendLine ("retobj = xamarin_get_nsobject_handle (retval);");
                        setup_return.AppendLine ("xamarin_framework_peer_lock ();");
                        setup_return.AppendLine ("[retobj retain];");
                        setup_return.AppendLine ("xamarin_framework_peer_unlock ();");
                        if (!retain)
                            setup_return.AppendLine ("[retobj autorelease];");
                        setup_return.AppendLine ("mt_dummy_use (retval);");
                        setup_return.AppendLine ("res = retobj;");
                    } else if (type.IsPlatformType ("ObjCRuntime", "Selector")) {
                        setup_return.AppendLine ("res = xamarin_get_selector_handle (retval);");
                    } else if (type.IsPlatformType ("ObjCRuntime", "Class")) {
                        setup_return.AppendLine ("res = xamarin_get_class_handle (retval);");
                    } else if (SharedStatic.IsNativeObject (type)) {
                        setup_return.AppendLine ("{0} retobj;", rettype);
                        setup_return.AppendLine ("retobj = xamarin_get_handle_for_inativeobject ((MonoObject *) retval);");
                        setup_return.AppendLine ("xamarin_framework_peer_lock ();");
                        setup_return.AppendLine ("[retobj retain];");
                        setup_return.AppendLine ("xamarin_framework_peer_unlock ();");
                        if (!retain)
                            setup_return.AppendLine ("[retobj autorelease];");
                        setup_return.AppendLine ("mt_dummy_use (retval);");
                        setup_return.AppendLine ("res = retobj;");
                    } else if (type.FullName == "System.String") {
                        // This should always be an NSString and never char*
                        setup_return.AppendLine ("char *str = mono_string_to_utf8 ((MonoString *) retval);");
                        setup_return.AppendLine ("NSString *nsstr = [[NSString alloc] initWithUTF8String:str];");
                        if (!retain)
                            setup_return.AppendLine ("[nsstr autorelease];");
                        setup_return.AppendLine ("mono_free (str);");
                        setup_return.AppendLine ("res = nsstr;");
                    } else if (SharedStatic.IsDelegate (type.Resolve ())) {
                        setup_return.AppendLine ("res = xamarin_get_block_for_delegate (managed_method, retval);");
                    } else {
                        throw ErrorHelper.CreateError (4104,
                            "The registrar cannot marshal the return value for type `{0}` in signature for method `{1}`.",
                            returntype.FullName, descriptiveMethodName);
                    }

                    setup_return.AppendLine ("}");
                }
            }

            // Write out everything
            if (merge_bodies) {
                body.WriteLine ("MonoMethod *managed_method = *managed_method_ptr;");
            } else {
                if (!isGeneric)
                    body.Write ("static ");
                body.WriteLine ("MonoMethod *managed_method = NULL;");
            }

            if (comment.Length > 0)
                body.WriteLine (comment.ToString ());
            if (isInstanceCategory)
                body.WriteLine ("id p0 = self;");
            body.WriteLine ("void *arg_ptrs [{0}];", num_arg);
            if (!isStatic || isInstanceCategory)
                body.WriteLine ("MonoObject *mthis;");

            body.WriteLine ("if (mono_domain_get () == NULL)");
            body.Indent ();
            body.WriteLine ("mono_jit_thread_attach (NULL);");
            body.Unindent ();

            if (isCtor) {
                body.WriteLine ("if (xamarin_try_get_nsobject (self)) {");
                body.WriteLine ("*call_super = true;");
                body.WriteLine ("return self;");
                body.WriteLine ("}");
            }

            if ((!isStatic || isInstanceCategory) && !isCtor) {
                body.WriteLine ("mthis = NULL;");
                body.WriteLine ("if (self) {");
                body.WriteLine ("mthis = xamarin_get_managed_object_for_ptr_fast (self);");
                body.WriteLine ("}");
            }

            // no locking should be required here, it doesn't matter if we overwrite the field (it'll be the same value).
            body.WriteLine ("if (!managed_method) {");
            if (num_arg > 0) {
                body.Write ("const char *paramptr[{0}] = {{ ", num_arg);
                for (int i = 0; i < num_arg; i++) {
                    string paramtype;
                    if (isGeneric) {
                        paramtype = GetAssemblyQualifiedName (method.Method.Parameters [i].ParameterType);
                    } else {
                        paramtype = GetAssemblyQualifiedName (method.Parameters [i]);
                    }

                    if (merge_bodies) {
                        body.Write ("r{0}", arguments.Count);
                        arguments.Add (paramtype);
                    } else {
                        body.Write ("\"{0}\"", paramtype);
                    }
                    if (i < num_arg - 1)
                        body.Write (", ");
                }
                body.WriteLine (" };");
            }

            body.Write ("managed_method = ");
            if (isGeneric)
                body.Write ("xamarin_get_reflection_method_method (xamarin_get_generic_method_direct (mthis, ");
            else
                body.Write ("xamarin_get_reflection_method_method (xamarin_get_method_direct(");

            if (merge_bodies) {
                body.WriteLine ("r{2}, r{3}, {0}, {1}));",
                    num_arg, num_arg > 0 ? "paramptr" : "NULL",
                    arguments.Count, arguments.Count + 1);
                arguments.Add (GetAssemblyQualifiedName (method.Method.DeclaringType));
                arguments.Add (method.Method.Name);
            } else {
                body.WriteLine ("\"{0}\", \"{1}\", {2}, {3}));",
                    GetAssemblyQualifiedName (method.Method.DeclaringType),
                    method.Method.Name, num_arg, num_arg > 0 ? "paramptr" : "NULL");
            }
            if (merge_bodies)
                body.WriteLine ("*managed_method_ptr = managed_method;");
            body.WriteLine ("}");

            if (!isStatic && !isInstanceCategory && !isCtor)
                body.WriteLine ("xamarin_check_for_gced_object (mthis, _cmd, self, managed_method);");

            if (trace)
                body.AppendLine (nslog_start);

            body.AppendLine (setup_call_stack);
            body.AppendLine (invoke);
            body.AppendLine (copyback);
            body.AppendLine (setup_return);

            if (trace )
                body.AppendLine (nslog_end);

            if (isCtor) {
                body.WriteLine ("return self;");
            } else if (isVoid) {
                body.WriteLine ("return;");
            } else {
                body.WriteLine ("return res;");
            }
            body.WriteLine ("}");

            /* We merge duplicated bodies (based on the signature of the method and the entire body) */

            var objc_signature = new StringBuilder ().Append (rettype).Append (":");
            for (int i = 0; i < method.Method.Parameters.Count; i++)
                objc_signature.Append (ToObjCParameterType (method.Method.Parameters [i].ParameterType, descriptiveMethodName, exceptions, method.Method)).Append (":");

            Body existing;
            Body b = new Body () {
                Code = body.ToString (),
                Signature = objc_signature.ToString (),
            };

            if (merge_bodies && bodies.TryGetValue (b, out existing)) {
                /* We already have an identical trampoline, use it instead */
                b = existing;
            } else {
                /* Need to create a new trampoline */
                if (merge_bodies)
                    bodies [b] = b;
                b.Name = "native_to_managed_trampoline_" + bodies.Count.ToString ();

                if (merge_bodies) {
                    methods.Append ("static ");
                    methods.Append (rettype).Append (" ").Append (b.Name).Append (" (id self, SEL _cmd, MonoMethod **managed_method_ptr");
                    for (int i = (isInstanceCategory ? 1 : 0); i < method.Method.Parameters.Count; i++) {
                        methods.Append (", ").Append (ToObjCParameterType (method.Method.Parameters [i].ParameterType, descriptiveMethodName, exceptions, method.Method));
                        methods.Append (" ").Append ("p").Append (i.ToString ());
                    }
                    for (int i = 0; i < arguments.Count; i++)
                        methods.Append (", const char *").Append ("r").Append (i.ToString ());
                    if (isCtor)
                        methods.Append (", bool* call_super");
                    methods.AppendLine (")");
                    methods.AppendLine (body);
                    methods.AppendLine ();
                }
            }
            b.Count++;

            sb.WriteLine ();
            sb.WriteLine (GetObjCSignature (method, exceptions));
            if (merge_bodies) {
                sb.WriteLine ("{");
                if (!isGeneric)
                    sb.Write ("static ");
                sb.WriteLine ("MonoMethod *managed_method = NULL;");
                if (isCtor) {
                    sb.WriteLine ("bool call_super = false;");
                    sb.Write ("id rv = ");
                } else if (!isVoid) {
                    sb.Write ("return ");
                }
                sb.Write (b.Name);
                sb.Write (" (self, _cmd, &managed_method");
                var paramCount = method.Method.Parameters.Count;
                if (isInstanceCategory)
                    paramCount--;
                for (int i = 0; i < paramCount; i++)
                    sb.Write (", p{0}", i);
                for (int i = 0; i < arguments.Count; i++)
                    sb.Write (", \"").Write (arguments [i]).Write ("\"");
                if (isCtor)
                    sb.Write (", &call_super");
                sb.WriteLine (");");
                if (isCtor) {
                    sb.WriteLine ("if (call_super)");
                    sb.Indent ();
                    sb.Write ("rv = [super");
                    var split = method.Selector.Split (':');
                    if (split.Length == 1) {
                        sb.Append (" ");
                        sb.Append (split [0]);
                    } else {
                        for (int i = 0; i < split.Length - 1; i++) {
                            sb.Append (" ");
                            sb.Append (split [i]);
                            sb.Append (":");
                            sb.AppendFormat ("p{0}", i);
                        }
                    }
                    sb.WriteLine ("];");
                    sb.Unindent ();
                    sb.WriteLine ("return rv;");
                }
                sb.WriteLine ("}");
            } else {
                sb.WriteLine (body);
            }
        }