1

I am attempting a method swizzle in Obj-C but I would like to pass it a pure C function. This means I need to somehow assign a selector and/or manually build an objc_method struct. Maybe somehow leverage NSInvocation?

My understanding is that due to the fact that Obj-C is a strict superset of C and therefor fully compatible.

What I have going now:

main.m :

#include....

CFStringRef strRet(void) {
    return CFSTR("retString");
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        SEL _strRet = sel_registerName("strRet");
        //I also tried: SEL _strRet = NSSelectorFromString(@"strRet");

        Class bundle = objc_getClass("NSBundle");

        method_exchangeImplementations(
            class_getInstanceMethod(bundle, sel_registerName("anySelector")), 
            class_getInstanceMethod(bundle, sel_registerName("_strRet")
        );

I have tried putting the C function inside @implementation (which I would like to avoid) and even then it did not work.

Jlegend
  • 531
  • 1
  • 6
  • 19
  • correct me if i understood wrong. You want to pass pure C function ?? in your code snippet no C function shown. can you modify you query little more code ?? – ntshetty Dec 20 '17 at 06:39

2 Answers2

3

You can't swizzle a C function per se; swizzling is based on method lookup which goes through method descriptions (which are represented by the Method type by the runtime functions) and C functions do not have a method description.

However the implementation of a method is just a C function. Such a C function must take a minimum of two arguments, being the object the method is invoked on (the Objective-C implicit parameter self) and the selector (the Objective-C implicit parameter _cmd). When you swizzle a method the replacement implementation, a C function, must have exactly the same type as the original – complete with the two implicit arguments – so your strRet() would not be suitable as is, you would need to change it to:

CFStringRef strRet(NSObject *self, CMD sel, void)
{
   return CFSTR("retString");
}

So you have three main choices:

  1. The easiest way is to define a method whose body is your "pure" C function, then swizzle the recommended way (taking care to handle inheritance correctly, see this answer).

  2. If you really want to write a C function and that C function does not need to call the original implementation of the method then:

    (a) You need to convert your C function into one which can be used as a method implementation. You can:

    • If you are writing/have the source of the C function you simply define it to take the two implicit arguments as above. Take the address of this function and cast it to IMP, which is just a typedef for a C function pointer of the appropriate type, for use below.
    • If you are using a C function whose definition you cannot change then you can do one of:
      • Write a C wrapper function which takes the extra arguments, ignores them and calls your target C function. Take the address of this wrapper function and cast it to IMP for use below.
      • Wrap the call to your C function in a block and use imp_implementationWithBlock() to produce an IMP value from it. You can read this article for a description of using imp_implementationWithBlock().

    (b) use method_setImplementation() to set the implementation to the IMP value you produced in (a).

  3. If you really want to write a C function and that C function does need to call the original implementation of the method then you will need to add a method to your class whose implementation is your C function – modified/wrapped as in (2), then swizzle your added method with your original method as under (1) so that the original implementation is still available as a method. To add a method you use class_addMethod()

HTH

CRD
  • 52,522
  • 5
  • 70
  • 86
  • 1
    You can also use imp_implementationWithBlock(). It makes it easy to wrap a c function in a block that can be plugged in as a method in the runtime. – bbum Dec 20 '17 at 16:18
  • 2
    @bbum - Duh, forgot that one. Edited the answer to include it as an option. Thanks. – CRD Dec 20 '17 at 18:45
  • This is great! Thank you for the resources! – Jlegend Dec 20 '17 at 22:01
2

The key here is finding a mechanism that maps between the function pointer and your context. The simplest way to do that is by generating a new function pointer. You can use imp_implementationWithBlock(), MABlockClosure, or roll your own.

The simplest mechanism to create a new function pointer I've found is to remap the entire function to a new address space. The new resulting address can be used as a key to the required data.

#import <mach/mach_init.h>
#import <mach/vm_map.h>

void *remap_address(void* address, int page_count)
{
    vm_address_t source_address = (vm_address_t) address;
    vm_address_t source_page = source_address & ~PAGE_MASK;

    vm_address_t destination_page = 0;
    vm_prot_t cur_prot;
    vm_prot_t max_prot;
    kern_return_t status = vm_remap(mach_task_self(),
                                &destination_page,
                                PAGE_SIZE*(page_count ? page_count : 4),
                                0,
                                VM_FLAGS_ANYWHERE,
                                mach_task_self(),
                                source_page,
                                FALSE,
                                &cur_prot,
                                &max_prot,
                                VM_INHERIT_NONE);

    if (status != KERN_SUCCESS)
    {
        return NULL;
    }

    vm_address_t destination_address = destination_page | (source_address & PAGE_MASK);

    return (void*) destination_address;
}

Note that page_count should be large enough to contain all of your original function. Also, remember to handle pages that aren't required anymore and note that it takes a lot more memory per invocation than MABlockClosure.

(Tested on iOS)

jjrscott
  • 1,386
  • 12
  • 16