Bug 5954 - Add API to get the selector for an Exported method
Summary: Add API to get the selector for an Exported method
Status: CONFIRMED
Alias: None
Product: iOS
Classification: Xamarin
Component: XI runtime (show other bugs)
Version: 5.2
Hardware: Macintosh Mac OS
: Low enhancement
Target Milestone: Untriaged
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2012-07-02 18:33 UTC by Adam Kemp
Modified: 2016-02-17 17:42 UTC (History)
3 users (show)

See Also:
Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
Example code (48.12 KB, application/zip)
2012-07-02 18:34 UTC, Adam Kemp
Details

Description Adam Kemp 2012-07-02 18:33:36 UTC
I am trying to implement a proxy pattern using the ObjectiveC mechanisms for transparent forwarding of messages. Basically I want an object to respond to a seletor even though there is no real method with that selector. There are three steps to this:
1. Override respondsToSeletor: to return true for the selector you are faking.
2. Override methodSignatureForSelector: to return a method signature for the selector which does not exist. I do this by returning the signature for a compatible method which does exist.
3. Override forwardInvocation: to intercept the actual call to the given method and make it do something entirely different.

When I attempt step #2 in MonoTouch the runtime crashes as soon as I create an instance of my object. The relevant code that causes the crash (though does not appear to actually be executed) is this:

/// BEGIN CODE
private const string MethodSignatureForSelectorSelectorName = "methodSignatureForSelector:";
private static readonly Selector methodSignatureForSelectorSelector = new Selector(MethodSignatureForSelectorSelectorName);

[Export(MethodSignatureForSelectorSelectorName)]
IntPtr MethodSignatureForSelector(Selector sel)
{
    if (sel.Name == DummyActionSelectorName)
    {
        sel = PrototypeMethodSelector;
    }
    
    return Messaging.IntPtr_objc_msgSend_IntPtr(SuperHandle, methodSignatureForSelectorSelector.Handle, sel.Handle);
}
/// END CODE

It appears that the mere existence of this code in a class causes allocating that class to crash the runtime. If I set a breakpoint in that method it never gets hit.

Attached is a .zip containing two sample projects: one written in MonoTouch which demonstrates the crash, and the other written in ObjectiveC which demonstrates the intended behavior. To reproduce the crash just run the MonoTouch application and press the button labeled "Crashes".
Comment 1 Adam Kemp 2012-07-02 18:34:07 UTC
Created attachment 2147 [details]
Example code
Comment 2 Rolf Bjarne Kvinge [MSFT] 2012-07-03 06:35:13 UTC
The issue here is that MonoTouch internally calls methodSignatureForSelector: in our native-to-managed trampoline. This means that every native-to-managed transition calls the methodSignatureForSelector: method - and in your case you've overridden this method to call your own implementation. Since this is also a native-to-managed transition we end up with a stack overflow.

This isn't easy to fix so it will likely take a while before we can support - is there any other way you can do the same thing (why can't you add the actual method? You can also connect to methods dynamically using MonoTouch.ObjCRuntime.Runtime.ConnectMethod [1]).


[1] http://docs.go-mono.com/?link=M%3aMonoTouch.ObjCRuntime.Runtime.ConnectMethod(System.Reflection.MethodInfo%2cMonoTouch.ObjCRuntime.Selector)
Comment 3 Adam Kemp 2012-07-03 15:38:21 UTC
I figured it must be something like that, but without a good call stack it was hard to tell.

The reason we can't just add the method is that we don't know what the method will be until runtime. We have a (cross-platform) dynamic command binding mechanism in our .Net code. It works similarly to the way iOS walks the responder chain looking for an object which responds to a given selector, but that lookup is done using a dictionary of bindings rather than ObjectiveC mechanisms.

What I'm trying to do here is glue the UIMenuController (which uses iOS-style command bindings using respondsToSelector and the responder chain) to our binding mechanism by creating a dummy responder that pretends to respond to a fake selector. I want to give this dummy responder a list of command objects, and for each one of those it should create a UIMenuItem object with a unique (fake) selector to put in the menu. When that menu item is selected the dummy responder should intercept the selector and execute the command for the corresponding command object.

I was planning on doing this by making each element in the command object array correspond to a selector like "fakeCommandSelector0:" or "fakeCommandSelector1:", and then I was going to use the proxy pattern to intercept the calls to those methods and redirect them to method calls on the corresponding command objects.

The ConnectMethod function would only work if I could route some arbitrary number of selectors to the same method and differentiate them by some argument to the method. I don't see a way to do that in ConectMethod.

The only other workaround I can think of so far is to create a series of hardcoded methods like this:

[Export("fakeCommandSelector0:")]
private void Command0(NSObject sender)
{
  ExecuteCommand(0);
}

The problem with that is that I have to set some arbitrary maximum number of commands. For UIMenuController that might make sense (there's only so much room for commands in the menu), but I was hoping to use this pattern in other similar situations where that limit could be a serious issue.

I don't care specifically about being able to override methodSignatureForSelector exactly. What I really need is just a sanctioned way to make my managed object respond to arbitrary (dynamic) selectors even when I don't know how many of them I will need until runtime. Perhaps a separate interface specific to .Net could accomplish this.
Comment 4 Rolf Bjarne Kvinge [MSFT] 2012-07-03 21:05:22 UTC
If I've understood correctly, what you're really missing compared to the equivalent ObjectiveC code is the selector that was invoked. If you knew the selector, then you could figure out what you should eventually invoke (with the ConnectMethod approach).
Comment 5 Adam Kemp 2012-07-03 21:20:19 UTC
The ConnectMethod approach assumes that there is a method to connect. You can't create a method dynamically. What I need is the ability to invent new methods at runtime. That is what the proxy pattern (using methodSignatureForSelector: and forwardInvocation:) allows you to do. It is the same reason MonoTouch itself uses it.

Basically I want to make an object which takes in an arbitrary array defining items that show up in the UIMenuController. Those objects each have a CanExecute method and an Execute method. The UIMenuController assumes that each item in the menu calls a specific selector on an object in the responder chain, but I don't have such a method. I just have an array of objects. I'm trying to glue the two systems together by making an object which inherits from UIResponder and pretends to have methods with fake names, and then when those methods are called I will forward to the Execute method of the corresponding command object.

This wouldn't be as hard if UIMenuController supported an argument for each menu item, but all it gives you is a sender object (which ends up being the UIMenuController object itself). As a result, I can't just bind all of the menu items to a single method and figure out which one was actually selected. They all have to be separate methods with different selectors so that I can distinguish one from the other. That is difficult to do when the set of items is created dynamically at runtime. That is why the proxy approach would be so useful here. It lets me esffectively create new methods dynamically.
Comment 6 Rolf Bjarne Kvinge [MSFT] 2012-09-26 18:06:21 UTC
I believe one solution is to make the actual selector invoked accessible somehow. That way you could bind all menu items to a single method, and switch on the selector used to invoke the method.
Comment 7 Adam Kemp 2012-09-26 18:13:12 UTC
I think that could work, but it would have to be a feature of MonoTouch. Maybe a special variant of the Export macro that expects the function signature to have a Selector argument.

The reason it has to be a feature of MonoTouch is that I'm trying to interoperate with an API that comes from Apple, and their API requires the signature for the methods to be (void)foo:(id), and the argument passed in is just a pointer to the UIMenuController. I wish they had at least given me a user data parameter that would be passed in to the callback when an item is selected, but alas they did not.
Comment 8 Rolf Bjarne Kvinge [MSFT] 2016-02-17 17:42:46 UTC
One way to implement this would be to add a property to the Export attribute to set the index of the parameter for the selector:

    [Export ("foo", SelectorParameterIndex: 0)]

and then that parameter's type would have to be either IntPtr or Selector.

Note You need to log in before you can comment on or make changes to this bug.