Reputation: 1650
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
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