10 votes

Faire en sorte que NSInvocation invoque une IMP spécifique

Je cherche un moyen de faire un NSInvocation invoquer un IMP . Par défaut, il invoque le "plus bas" IMP qu'il peut trouver (c'est-à-dire la version la plus récente), mais je cherche un moyen de lui faire invoquer un IMP de plus haut dans la chaîne d'héritage. Les IMP que je veux invoquer est déterminée dynamiquement, sinon je pourrais utiliser la fonction super ou quelque chose comme ça.

J'ai pensé utiliser le -forwardInvocation: mécanisme de capture d'un message (facile et qui fonctionne déjà) et de modifier ensuite le message. IMP Il est donc renvoyé à une méthode qui n'est ni la méthode super ni l'implémentation du descendant le plus éloigné. (dur)

La seule chose que j'ai trouvée qui s'en rapproche de près ou de loin est AspectObjectifC mais cela nécessite libffi, ce qui le rend non compatible avec iOS. Idéalement, j'aimerais que ce soit multiplateforme.

Des idées ?

<em>disclaimer : je ne fais qu'expérimenter</em>


Essayer l'idée de @bbum d'une fonction trampoline

Je pense donc avoir mis les choses en place ; j'ai le trampoline suivant qui est correctement ajouté via class_addMethod() et il est saisi :

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
}

J'ai vérifié que le soi et l'IMP sont les bonnes choses avant le PCS, et que l'IMP est la bonne chose à faire. _cmd Les paramètres sont également correctement introduits. (en d'autres termes, j'ai correctement ajouté cette méthode).

Cependant, il se passe quelque chose. Je me retrouve parfois à passer à une méthode (qui n'est généralement pas la bonne) avec un résultat nul. self y _cmd . D'autres fois, je m'écrase au milieu de nulle part avec un EXC_BAD_ACCESS. Des idées ? (cela fait longtemps que je n'ai rien fait en assembleur...) Je teste cela sur x86_64.

4voto

Chuck Points 138930

NSInvocation est juste une représentation objet d'un envoi de message. En tant que telle, elle ne peut pas plus invoquer un IMP spécifique qu'un envoi de message normal. Pour qu'une invocation appelle un IMP spécifique, il faut soit écrire une classe NSInvocation personnalisée qui passe par la routine d'appel de l'IMP, soit écrire un trampoline qui implémente le comportement, puis créer une invocation qui représente un message pour le trampoline (c'est-à-dire que vous n'utiliseriez pas NSInvocation pour grand-chose).

4voto

rgeorge Points 4568

Ajouté longtemps après les faits, pour référence :

Vous pouvez le faire avec une API privée. Placez cette catégorie dans un endroit pratique :

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

et voilà, il fait exactement ce que vous attendez. J'ai déniché ce petit bijou dans l'un des anciens billets de blog de Mike Ash.

Les astuces d'API privées de ce type sont idéales pour la recherche ou le code interne. N'oubliez pas de l'exclure de vos créations destinées à l'appstore.

3voto

BJ Homer Points 29168

Une idée qui n'a pas fait ses preuves :

Pourriez-vous utiliser object_setClass() pour forcer la sélection du IMP que vous souhaitez ? C'est-à-dire

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

3voto

bbum Points 124887

Étant donné que vous disposez déjà de l'IMP, vous avez simplement besoin d'un moyen d'effectuer une transmission très brute de l'appel de méthode à l'IMP en question. Et étant donné que vous êtes prêt à utiliser une solution de type NSInvocation, vous pouvez également construire une classe proxy similaire.

Si j'étais confronté à cette situation, je créerais une simple classe de proxys qui contiendrait l'IMP à appeler et l'objet cible (vous devrez définir l'élément self ). Ensuite, j'écrirais une fonction trampoline en assembleur qui prend le premier argument, suppose qu'il s'agit d'une instance de la classe mandataire, récupère le paramètre self Le programme de l'utilisateur est un appel de queue, il le place dans le registre contenant l'argument 0, prend l'IMP et le *JMP en tant qu'appel de queue.

Une fois le trampoline en main, vous pouvez ajouter ce trampoline en tant qu'IMP pour tout sélecteur de la classe mandataire que vous souhaitez transmettre à un IMP.... particulier.

Pour obtenir un mécanisme générique de ce type, il convient de éviter tout ce qui a trait à la réécriture de la trame de la pile . Éviter l'ABI C. Évitez de déplacer les arguments.

2voto

tonklon Points 5687

Je pense que le meilleur choix est d'utiliser libffi. Avez-vous vu le portage sur iOS à https://github.com/landonf/libffi-ios ? Je n'ai pas essayé le portage, mais j'ai réussi à invoquer IMP avec des arguments arbitraires sur le Mac.

Jetez un coup d'œil à JSCocoa https://github.com/parmanoir/jscocoa il comprend du code pour vous aider à préparer un ffi_cif à partir d'une structure Method et il contient également une version de libffi qui devrait compiler sur iOS. (Je n'ai pas testé non plus)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X