fractor
fractor

Reputation: 1650

Xamarin method swizzling isn't working. Why not?

I have the following Xamarin.Mac code:

[Register("Swizzler")]
public class Swizzler : NSObject
{
    [DllImport("/usr/lib/libobjc.dylib")] public static extern IntPtr class_getInstanceMethod(IntPtr classHandle, IntPtr Selector);
    [DllImport("/usr/lib/libobjc.dylib")] public static extern bool method_exchangeImplementations(IntPtr m1, IntPtr m2);

    public void AttemptSwizzle()
    {
        var swizzledClassPtr = Class.GetHandle("Swizzled");
        var swizzlerClassPtr = Class.GetHandle("Swizzler");
        SwizzleInstanceMethod(swizzledClassPtr, new Selector("originalMethod"), swizzlerClassPtr, new Selector("newMethod"));

        var swizzled = new Swizzled();
        swizzled.PerformSelector(new Selector("originalMethod"));
    }

    internal void SwizzleInstanceMethod(IntPtr originalClassPtr, Selector originalSelector, IntPtr newClassPtr, Selector newSelector)
    {
        var originalMethod = class_getInstanceMethod(originalClassPtr, originalSelector.Handle);
        var swizzledMethod = class_getInstanceMethod(newClassPtr, newSelector.Handle);

        method_exchangeImplementations(originalMethod, swizzledMethod);
    }

    [Export("newMethod")]
    public void NewMethod()
    {
        Console.WriteLine("New method called");
    }
}

[Register("Swizzled")]
internal class Swizzled : NSObject
{
    [Export("originalMethod")]
    public void OriginalMethod()
    {
        Console.WriteLine("Original method called");
    }
}

Code sample at https://github.com/alataffective/XamarinSwizzler.

When calling new Swizzler().AttemptSwizzle() I get the following output:

SomeMethod called

That is, the swizzling isn't happening. Why not?

Upvotes: 0

Views: 210

Answers (1)

Jiri Volejnik
Jiri Volejnik

Reputation: 1099

The problem is that you exchange implementations of exported C# methods. Exports share a single implementation, that performs it's own mapping from selectors to C# methods. Therefore, switching them has no effect. You can check it yourself when you call method_getImplementation() for both exported methods and compare returned values.

Just try swizzling original Objective-C methods of original Objective-C classes and you'll see that it works:

        public void AttemptSwizzle()
        {
            var swizzledClassPtr = Class.GetHandle("NSWindow");
            var swizzlerClassPtr = Class.GetHandle("NSWindow");
            SwizzleInstanceMethod(swizzledClassPtr, new Selector("title"), swizzlerClassPtr, new Selector("tabbingIdentifier"));

            var swizzled = new NSWindow();
            swizzled.TabbingIdentifier = "TabbingIdentifier";
            var result = swizzled.Title;
            Console.WriteLine($"title:{result}");
        }

If you want to swizzle original Objective-C method with a plain, not exported C# one, you still can, making use of the following methods:

Marshal.GetFunctionPointerForDelegate()
method_setImplementation()
method_getImplementation()

Upvotes: 0

Related Questions