Заставить NSInvocation вызывать конкретный IMP

Я ищу способ сделать NSInvocation вызовом определенного IMP. По умолчанию он вызывает «самый низкий» IMP, который может найти (т. е. самую последнюю переопределенную версию), но я ищу способ заставить его вызывать IMP из более высокого уровня в цепочке наследования. IMP, который я хочу вызвать, определяется динамически, иначе я мог бы использовать ключевое слово super или что-то в этом роде.

Моя мысль состояла в том, чтобы использовать механизм -forwardInvocation: для захвата сообщения (простой и уже работающий), а затем изменить IMP так, чтобы он перешел к методу, который не является ни реализацией super, ни реализацией самого дальнего потомка. (жесткий)

Единственное, что я нашел, что отдаленно близко, это AspectObjectiveC, но для этого требуется libffi, что делает его не -совместимость с iOS. В идеале я бы хотел, чтобы это была кросс-платформа.

Любые идеи?

отказ от ответственности: я просто экспериментирую


Опробовать идею @bbum о функции батута

Так что я думаю, что у меня в основном все готово; У меня есть следующий батут, который правильно добавляется через class_addMethod(), и он действительно вводится:

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
}

Я убедился, что и правильное self, и правильный IMP являются правильными вещами до JMP, и параметр _cmd также входит правильно. (другими словами, я правильно добавил этот метод).

Однако что-то происходит. Иногда я ловлю себя на том, что перескакиваю к методу (обычно не правильному) с нулевыми self и _cmd. В других случаях я просто падаю в глуши с EXC_BAD_ACCESS. Идеи? (давно я ничего не делал на ассемблере...) Тестирую на x86_64.


person Dave DeLong    schedule 19.02.2011    source источник


Ответы (7)


Учитывая, что у вас уже есть IMP, вам просто нужен способ сделать очень сырую переадресацию вызова метода в указанный IMP. И учитывая, что вы готовы использовать решение, подобное NSInvocation, вы также можете создать аналогичный прокси-класс.

Если бы я столкнулся с этим, я бы создал простой класс проксирования, содержащий вызываемый IMP и целевой объект (вам нужно будет установить параметр self). Затем я бы написал функцию батута на ассемблере, которая принимает первый аргумент, предполагает, что это экземпляр класса-посредника, захватывает self, вставляет его в регистр, содержащий аргумент 0, захватывает IMP и *JMPs к нему как хвост вызов.

Имея батут в руках, вы затем добавите этот батут в качестве IMP для любого селектора в классе прокси, который вы хотите перенаправить в конкретный IMP....

Для достижения такого универсального механизма важно избегать чего-либо, связанного с перезаписью фрейма стека. Избегайте C ABI. Избегайте движущихся аргументов о.

person bbum    schedule 19.02.2011
comment
Я обновил свой вопрос с помощью функции батута (которая не совсем работает). Я был бы признателен, если бы вы взглянули на него. - person Dave DeLong; 22.02.2011
comment
Вы не можете сделать это из C... это должна быть сборка, и вам придется немного поиграть в игру с получением правильного IMP. - person bbum; 22.02.2011

NSInvocation — это просто объектное представление отправки сообщения. Таким образом, он не может вызвать конкретный IMP больше, чем обычная отправка сообщения. Чтобы вызов вызывал конкретный IMP, вам нужно либо написать собственный класс NSInvocation, который проходит процедуру вызова IMP, либо вам нужно написать трамплин, который реализует поведение, а затем создать вызов, который представляет сообщение для батута (т.е. вы практически не будете использовать NSInvocation для чего-либо).

person Chuck    schedule 19.02.2011

Добавлено задним числом, для справки:

Вы можете сделать это с помощью частного API. Поместите эту категорию в удобное место:

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

и вуаля, он делает именно то, что вы ожидаете. Я выкопал эту жемчужину из одной из старых записей в блоге Майка Эша.

Подобные трюки с частным API отлично подходят для исследований или внутреннего кода. Просто не забудьте удалить его из ваших сборок, связанных с магазином приложений.

person rgeorge    schedule 05.02.2013

Непроверенная идея:

Не могли бы вы использовать object_setClass(), чтобы принудительно выбрать IMP, который вы хотите? То есть…

- (void)forwardInvocation:(NSInvocation *)invocation {
    id target = [invocation target];
    Class targetClass = classWithTheImpIWant();
    Class originalClass = objc_setClass(target, targetClass);
    [invocation invoke];
    objc_setClass(target, originalClass);
}
person BJ Homer    schedule 19.02.2011
comment
+1 Очень интересная идея. К сожалению, если цель IMP вызывает какие-либо другие методы, они перейдут к версиям того же уровня, что и цель IMP, а не самого дальнего потомка. - person Dave DeLong; 19.02.2011
comment
Не говоря уже о полном хаосе, который будет нанесен чему-либо параллельному, если вы пойдете и испортите иерархию классов таким образом... (но мне это нравится! :) - person bbum; 19.02.2011

Я думаю, что ваш лучший выбор - использовать libffi. Вы видели порт для iOS на https://github.com/landonf/libffi-ios? ? Я не пробовал порт, но я успешно вызвал IMP с произвольными аргументами на Mac.

Взгляните на JSCocoa https://github.com/parmanoir/jscocoa, он содержит код, который поможет вам подготовиться структура ffi_cif из Method, а также содержит версию libffi, которая должна компилироваться на iOS. (тоже не проверял)

person tonklon    schedule 19.02.2011

Вам, вероятно, следует взглянуть на то, как мы реализуем определенный метод для экземпляра объекта в https://github.com/tuenti/TMInstanceMethodSwizzler

По сути, вы используете метод для всего объекта класса, поэтому, когда он вызывается, он ищет в словаре, какова реализация, которая должна быть вызвана для целевого объекта, возвращаясь к исходной реализации, если она не найдена.

Вы также можете использовать закрытый метод invokeWithImp:, но это не рекомендуется, если вы собираетесь отправить приложение в магазин.

person user3480335    schedule 31.03.2014

вы можете добавить IMP в класс, используя class_addMethod под новым селектором, и вызвать этот селектор.

временный метод не может быть удален.

person Moritz    schedule 08.10.2014