iosxamarin.iosarm64method-swizzling

Xamarin method swizzle failing on device when calling original method


The following code works in simulator but fails on device when calling _originalSetTextMethod(self, stringParam).

I suspect the issue is related to this, but can't determine the appropriate syntax in C#: https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaTouch64BitGuide/ConvertingYourAppto64-Bit/ConvertingYourAppto64-Bit.html#//apple_ref/doc/uid/TP40013501-CH3-SW22

Can anybody please offer suggestions as to how I can get this running on device? Thanks.

public static class SetTextSwizzle
{
    [DllImport("/usr/lib/libobjc.dylib")]
    extern static IntPtr class_getInstanceMethod(IntPtr classHandle, IntPtr Selector);

    [DllImport("/usr/lib/libobjc.dylib")]
    extern static IntPtr imp_implementationWithBlock(ref BlockLiteral block);

    [DllImport("/usr/lib/libobjc.dylib")]
    extern static OriginalDelegate method_setImplementation(IntPtr method, IntPtr imp);

    static OriginalDelegate _originalSetTextMethod;

    [MonoNativeFunctionWrapper]
    public delegate void OriginalDelegate(IntPtr one, IntPtr two);
    delegate void CaptureDelegate(IntPtr block, IntPtr self, IntPtr paramOne);

    public static void Initialise() {
        OverrideSetText();
    }

    static void OverrideSetText()
    {
        var method = class_getInstanceMethod(new UILabel().ClassHandle, new Selector("setText:").Handle);
        var block_value = new BlockLiteral();
        CaptureDelegate d = SetTextAndFont;
        block_value.SetupBlock(d, null);
        var imp = imp_implementationWithBlock(ref block_value);
        _originalSetTextMethod = method_setImplementation(method, imp);
    }

    [MonoPInvokeCallback(typeof(CaptureDelegate))]
    static void SetTextAndFont(IntPtr block, IntPtr self, IntPtr stringParam)
    {
        var label = (UILabel)Runtime.GetNSObject(self);
        label?.SetFont();
        _originalSetTextMethod(self, stringParam);
    }
}

Solution

  • This is the code that ended up working for me. It is slightly convoluted as it needs to avoid accidental cyclical calls.

    using System;
    using ObjCRuntime;
    using UIKit;
    using System.Runtime.InteropServices;
    using Foundation;
    using System.Diagnostics;
    
    public static class SetTextSwizzle
        {
            [DllImport("/usr/lib/libobjc.dylib")]
            extern static IntPtr class_getInstanceMethod(IntPtr classHandle, IntPtr Selector);
    
            [DllImport("/usr/lib/libobjc.dylib")]
            extern static IntPtr method_getImplementation(IntPtr method);
    
            [DllImport("/usr/lib/libobjc.dylib")]
            extern static IntPtr class_addMethod(IntPtr classPointer, IntPtr selector, IntPtr implementation, char[] typeEncoding);
    
            [DllImport("/usr/lib/libobjc.dylib")]
            extern static IntPtr imp_implementationWithBlock(ref BlockLiteral block);
    
            [DllImport("/usr/lib/libobjc.dylib")]
            extern static IntPtr method_setImplementation(IntPtr method, IntPtr imp);
    
            [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
            static extern void CallOriginalMethod(IntPtr receiver, IntPtr selector, IntPtr symbol);
    
            delegate void CaptureDelegate(IntPtr block, IntPtr self, IntPtr paramOne);
    
            static readonly char[] _objCVoidReturnEncoding = { 'v', '@', ':' };
            static readonly IntPtr _setTextHandle = new Selector("setText:").Handle;
            static readonly IntPtr _setAttributedTextHandle = new Selector("setAttributedText:").Handle;
            static readonly IntPtr _setTextOrigHandle = new Selector("setTextOrig:").Handle;
            static readonly IntPtr _setAttributedTextOrigHandle = new Selector("setAttributedTextOrig:").Handle;
    
            public static void Initialise() {
                OverrideSetText();
                OverrideSetAttributedText();
                OverrideTextFieldSetText();
                OverrideTextViewSetText();
                OverrideTextViewSetAttributedText();
            }
    
            static IntPtr GetNewImplementation(CaptureDelegate newMethod) {
                var block_value = new BlockLiteral();
                block_value.SetupBlock(newMethod, null);
                return imp_implementationWithBlock(ref block_value);
            }
    
            static void OverrideSetText()
            {
                var originalMethod = class_getInstanceMethod(new UILabel().ClassHandle, _setTextHandle);
                var originalImplementation = method_getImplementation(originalMethod);
                class_addMethod(new UILabel().ClassHandle, _setTextOrigHandle, originalImplementation, _objCVoidReturnEncoding);
                method_setImplementation(originalMethod, GetNewImplementation(SetTextAndFont));
            }
    
            [MonoPInvokeCallback(typeof(CaptureDelegate))]
            static void SetTextAndFont(IntPtr block, IntPtr self, IntPtr stringParam)
            {
                var label = (UILabel)Runtime.GetNSObject(self);
                if (label != null)
                {
                    label.SetFont();
                    CallOriginalMethod(label.Handle, _setTextOrigHandle, stringParam);
                }
            }
    
    
            static void OverrideSetAttributedText()
            {
                var originalMethod = class_getInstanceMethod(new UILabel().ClassHandle, _setAttributedTextHandle);
                var originalImplementation = method_getImplementation(originalMethod);
                class_addMethod(new UILabel().ClassHandle, _setAttributedTextOrigHandle, originalImplementation, _objCVoidReturnEncoding);
                method_setImplementation(originalMethod, GetNewImplementation(SetTextAttributedAndFont));
            }
    
            [MonoPInvokeCallback(typeof(CaptureDelegate))]
            static void SetTextAttributedAndFont(IntPtr block, IntPtr self, IntPtr attrStringParam)
            {
                var label = (UILabel)Runtime.GetNSObject(self);
                var attrSringObject = (NSAttributedString)Runtime.GetNSObject(attrStringParam);
                if (label != null && attrSringObject != null)
                {
                    var customFontString = label.AttributedStringWithCustomFont(attrSringObject);
                    CallOriginalMethod(label.Handle, _setAttributedTextOrigHandle, customFontString.Handle);
                }
            }
    
    
            static void OverrideTextFieldSetText()
            {
                var originalMethod = class_getInstanceMethod(new UITextField().ClassHandle, _setTextHandle);
                var originalImplementation = method_getImplementation(originalMethod);
                class_addMethod(new UITextField().ClassHandle, _setTextOrigHandle, originalImplementation, _objCVoidReturnEncoding);
                method_setImplementation(originalMethod, GetNewImplementation(SetTextFieldTextAndFont));
            }
    
            [MonoPInvokeCallback(typeof(CaptureDelegate))]
            static void SetTextFieldTextAndFont(IntPtr block, IntPtr self, IntPtr stringParam)
            {
                var field = (UITextField)Runtime.GetNSObject(self);
                if (field != null)
                {
                    field.SetFont();
                    CallOriginalMethod(field.Handle, _setTextOrigHandle, stringParam);
                }
            }
    
    
            static void OverrideTextViewSetText()
            {
                var originalMethod = class_getInstanceMethod(new UITextView().ClassHandle, _setTextHandle);
                var originalImplementation = method_getImplementation(originalMethod);
                class_addMethod(new UITextView().ClassHandle, _setTextOrigHandle, originalImplementation, _objCVoidReturnEncoding);
                method_setImplementation(originalMethod, GetNewImplementation(SetTextViewTextAndFont));
            }
    
            [MonoPInvokeCallback(typeof(CaptureDelegate))]
            static void SetTextViewTextAndFont(IntPtr block, IntPtr self, IntPtr stringParam)
            {
                var textView = (UITextView)Runtime.GetNSObject(self);
                if (textView != null)
                {
                    textView.SetFont();
                    CallOriginalMethod(textView.Handle, _setTextOrigHandle, stringParam);
                }
            }
    
    
            static void OverrideTextViewSetAttributedText()
            {
                var originalMethod = class_getInstanceMethod(new UITextView().ClassHandle, _setAttributedTextHandle);
                var originalImplementation = method_getImplementation(originalMethod);
                class_addMethod(new UITextView().ClassHandle, _setAttributedTextOrigHandle, originalImplementation, _objCVoidReturnEncoding);
                method_setImplementation(originalMethod, GetNewImplementation(TextViewSetTextAttributedAndFont));
            }
    
            [MonoPInvokeCallback(typeof(CaptureDelegate))]
            static void TextViewSetTextAttributedAndFont(IntPtr block, IntPtr self, IntPtr attrStringParam)
            {
                var textView = (UITextView)Runtime.GetNSObject(self);
                var attrSringObject = (NSAttributedString)Runtime.GetNSObject(attrStringParam);
                if (textView != null && attrSringObject != null)
                {
                    var customFontString = textView.AttributedStringWithCustomFont(attrSringObject);
                    CallOriginalMethod(textView.Handle, _setAttributedTextOrigHandle, customFontString.Handle);
                }
            }
        }```