2011-02-19 14 views
9

Estoy buscando una forma de hacer que un NSInvocation invoque un IMP específico. Por defecto, invoca el IMP "más bajo" que puede encontrar (es decir, la versión reemplazada más recientemente), pero estoy buscando una forma de invocar un IMP desde un nivel superior en la cadena de herencia. El IMP que deseo invocar se determina dinámicamente, o de lo contrario podría usar la palabra clave super o algo así.Hacer que NSInvocation invoque un IMP específico

Mi idea era utilizar el mecanismo -forwardInvocation: para capturar un mensaje (fácil y ya trabajan) y luego alterar el IMP por lo que pasa a un método que no es ni la aplicación ni super aplicación del descendiente más lejano. (difícil)

Lo único que he encontrado que se acerca remotamente es AspectObjectiveC, pero eso requiere libffi, lo que hace que no sea compatible con iOS. Idealmente me gustaría que esto sea multiplataforma.

¿Alguna idea?

exención de responsabilidad: sólo estoy experimentando


Probando idea de @ bbum de una función de trampolín

así que creo que tengo cosas en su mayoría creados; Tengo la siguiente cama elástica que se agrega correctamente a través de class_addMethod(), y que se pone entré:

id dd_trampolineFunction(id self, SEL _cmd, ...) { 
    IMP imp = [self retrieveTheProperIMP]; 
    self = [self retrieveTheProperSelfObject]; 
    asm(
     "jmp %0\n" 
     : 
     : "r" (imp) 
     ); 
    return nil; //to shut up the compiler 
} 

He verificado que tanto el auto adecuado y la IMP adecuada son las cosas correctas antes de la JMP, y el parámetro _cmd también está entrando correctamente. (en otras palabras, agregué correctamente este método).

Sin embargo, algo está sucediendo. A veces me encuentro saltando a un método (generalmente no el correcto) con un nil self y _cmd. Otras veces, simplemente me estrellaré en el medio de la nada con un EXC_BAD_ACCESS. Ideas? (Ha pasado mucho tiempo desde que hice algo en ensamblaje ...) Estoy probando esto en x86_64.

Respuesta

3

Dado que ya tiene el IMP, sólo hay una manera de hacer un delantero muy crudo de la llamada al método a dicho IMP. Y dado que está dispuesto a utilizar una solución tipo NSInvocation, también podría crear una clase de proxy similar.

Si me enfrentara con esto, crearía una clase de proxying simple que contuviera el IMP que se llamará y el objeto de destino (deberá configurar el parámetro self). Entonces, escribiría una función de trampolín en ensamblado que toma el primer argumento, supone que es una instancia de la clase de proxying, toma el self, lo rellena en el argumento de retención de registro 0, toma el IMP y * JMP como una cola llamada.

Con cama elástica en la mano, usted entonces añadir que el trampolín como un IMP para cualquier selector de la clase proxy que desea desviar a un IMP en particular ....

para lograr cualquier tipo de mecanismo genérico como esto , la clave es evitar todo lo que tenga que ver con la reescritura del marco de la pila. Evita el C ABI. Evita mover argumentos sobre.

+0

He actualizado mi pregunta con mi función de trampolín (que no está funcionando del todo). Te agradecería si pudieras echarle un vistazo. –

+0

No puede hacerlo desde C ... tendrá que ser ensamblado y tendrá que jugar un poco con el IMP correcto. – bbum

4

NSInvocación es solo una representación de objeto de un mensaje enviado. Como tal, no puede invocar un IMP específico más de lo que podría hacerlo un envío de mensaje normal. Para que una invocación invoque un IMP específico, deberá escribir una clase NSInvocation personalizada que pase por la rutina de llamadas a IMP o deberá escribir un trampolín que implemente el comportamiento y luego crear una invocación que represente un mensaje para el trampolín (es decir, básicamente no estarías usando NSInvocation para nada).

3

Una idea no probada:

Podría utilizar object_setClass() para forzar la selección de la IMP que desea? Es decir ...

- (void)forwardInvocation:(NSInvocation *)invocation { 
    id target = [invocation target]; 
    Class targetClass = classWithTheImpIWant(); 
    Class originalClass = objc_setClass(target, targetClass); 
    [invocation invoke]; 
    objc_setClass(target, originalClass); 
} 
+0

+1 Una idea muy interesante. Desafortunadamente, si el objetivo 'IMP' invoca cualquier otro método, ellos irían a las versiones al mismo nivel que el objetivo' IMP', y no al descendiente más lejano. –

+2

Sin mencionar el caos absoluto que se cosecharía sobre cualquier cosa concurrente si vas y lo arruinas con la jerarquía de clases de tal manera ... (¡pero me gusta! :) – bbum

2

Creo que su mejor opción es usar libffi. ¿Has visto el puerto de iOS al https://github.com/landonf/libffi-ios? No he probado el puerto, pero he invocado con éxito IMP con argumentos arbitrarios en la Mac.

Tenga una mirada en JSCocoa https://github.com/parmanoir/jscocoa que incluye código para ayudarle a preparar una estructura ffi_cif de un Method y también contiene una versión de libffi que debe compilar en IOS. (No hemos probado tampoco)

3

Añadido mucho después del hecho, como referencia:

Usted puede hacerlo con la API privada. Coloque esta categoría en un lugar conveniente:

@interface NSInvocation (naughty) 
-(void)invokeUsingIMP:(IMP)imp; 
@end 

y listo, hace exactamente lo que usted esperaría. Saqué esta joya de una de las antiguas publicaciones de blog de Mike Ash.

Los trucos de API privados como este son geniales para la investigación o el código interno. Solo recuerda eliminarlo de tus compilaciones vinculadas a la tienda de aplicaciones.

0

Usted probablemente debería echar un vistazo a la forma en que SWIZZLE la implementación de un cierto método en una instancia de un objeto en https://github.com/tuenti/TMInstanceMethodSwizzler

Básicamente, se SWIZZLE el método para todos los objetos de una clase por lo que cuando la llamada se vea en un diccionario, cuál es la implementación que se debe invocar para el objeto de destino, volviendo a la implementación original si no se encuentra.

También puede utilizar el método invokeWithImp: privado, pero esto se desaconseja si tiene la intención de enviar la aplicación a la tienda.

0

puede agregar el IMP a la clase usando class_addMethod bajo un nuevo selector e invocar ese selector.

Aunque el método temporal no puede eliminarse.

Cuestiones relacionadas