0

I'm using https://github.com/nodejs/http-parser, the callbacks it uses are like this

struct http_parser_settings {
  http_cb      on_message_begin;
  http_data_cb on_url;
  http_data_cb on_status;
  http_data_cb on_header_field;
  http_data_cb on_header_value;
  http_cb      on_headers_complete;
  http_data_cb on_body;
  http_cb      on_message_complete;
  /* When on_chunk_header is called, the current chunk length is stored
  * in parser->content_length.
  */
  http_cb      on_chunk_header;
  http_cb      on_chunk_complete;
};

The main callback type is defined here

typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);

I'm trying to find a way to pass either an Objective-C block or method as the function pointer in the parser_settings. However it lets me use only a C-function, which doesn't suit me because I also need to access the state of an Objective-C object in the callback

At the moment my solution is as follows:

int onHeaderField(http_parser* _, const char* at, size_t length) {
    // Need to access state here, so doesn't work for me as a c function
    char header[length];
    strncpy(header, at, length);
    NSLog(@"Header %s", header);
    return 0;
    
}

...

- (void)method {

    http_parser_settings settings;
    settings.on_header_field = onHeaderField; // rather than func would like to set a block/method to capture and access self
    size_t nparsed = http_parser_execute(self.parser, &parserSettings, charData, messageLength)

}

How would I go about accessing self from the callback passed to http_parser_execute?

2
  • From the documentation of HTTP Parser: "For cases where it is necessary to pass local information to/from a callback, the http_parser object's data field can be used." Commented Aug 13, 2022 at 8:31
  • Thanks for highlighting that @Willeke! I had seen that but didn't think I'd be able to do what I wanted- but I am able to. Cheers! Commented Aug 13, 2022 at 22:03

1 Answer 1

1

Technically you can "extract" an Objective-C method implementation in form of a C-pointer with use of class_getMethodImplementation, however these implementations have objc_msgSend-like signature and always require the receiver as an argument, thus not really usable outside of Objective-C world:

NSString *str = @"Hollow World";
SEL sel = @selector(isEqualToString:);
Method meth = class_getInstanceMethod([str class], sel);

typedef BOOL(*IsEqualToStringPtr)(NSString *, SEL, NSString *);
IsEqualToStringPtr impl = (IsEqualToStringPtr)method_getImplementation(meth);

NSLog(@"Is equal? %@", impl(str, sel, @"Hello, World!") ? @"YES" : @"NO"); // prints "NO"
NSLog(@"Is equal? %@", impl(str, sel, @"Hollow World") ? @"YES" : @"NO"); // prints "YES"

Having that said, neither blocks nor Objective-C methods are directly convertible to a C function pointer (they are pointers to structures under the hood), especially when you want to complement it with any kind of context/state.

The simplest thing you can do is to use a global/statically allocated block variable which can be accessed from a C function without altering it's signature:

static int(^StaticBlock)(http_parser *parser, const char *at, size_t length);
static int my_callback(http_parser *parser, const char *at, size_t length) {
    return StaticBlock(parser, at, length);
}

...

- (void)someObjectiveCMethod {
    __weak typeof(self) weakSelf = self;
    StaticBlock = ^(http_parser *parser, const char *at, size_t length) {
        if (!weakSelf) {
            return -1;
        }
        __strong typeof(weakSelf) strongSelf = weakSelf;
        strongSelf.mprpty += length; 
        NSLog(@"Hello from Objective-C");
        return 8;
    };
    http_parser_settings settings;
    settings.on_header_field = my_callback;
}

The only viable alternative I can think of is using C++ lambdas. However it's still a big challenge when you need to access current state/context, let alone it will require you to switch to Objective-C++. If you are ok with it, first you need to rename your Objective-C file from SomeClass.m into SomeClass.mm. This way you tell Clang that the source code is Objective-C++ now and the compiler should accept a C++ code. Next, if your C library doesn't have C++ guards, you may want to wrap the C includes with extern "C" expression (otherwise linker would not be able to locate C symbols, because C++ mangles them):

extern "C" {
    #include <c_header.h>
}

Now the tricky part: lambda expressions return special objects, closures, which can be seamlessly converted to C function pointers only if they don't capture anything from surrounding context. In our scenario it's not the case and it will require extra steps to convert it to a C pointer. Add this code somewhere in your *.mm file:

template<typename L>
struct c_functor_factory : c_functor_factory<decltype(&L::operator())> {};

template<typename R, typename F, typename ...Args>
struct c_functor_factory<R(F::*)(Args...) const> {
    
    using pointer = typename std::add_pointer<R(Args...)>::type;

    static pointer make_cptr(F&& func) {
        static F instance = std::forward<F>(func);
        return [](Args... args) {
            return instance(std::forward<Args>(args)...);
        };
    }
};

template<typename L>
inline static typename c_functor_factory<L>::pointer make_cptr(L&& lambda) {
    return c_functor_factory<L>::make_cptr(std::forward<L>(lambda));
}

In fact this solution is not much far from the global C function solution I suggested above. When a closure is passed as an argument here, this template function just perfect-forwards it to a statically allocated variable. As a result the static closure can be called from a capture-less lambda, which in turn is converted to a C function pointer.

Finally, you can make use of C++ lambda expressions and pass them as C function pointers anywhere in your Objective-C code:

- (void)someObjectiveCMethod {
    __weak typeof(self) weakSelf = self;
    const auto cptr = make_cptr([weakSelf](http_parser *parser, const char *at, size_t length) {
        if (!weakSelf) {
            return -1;
        }
        __strong typeof(weakSelf) strongSelf = weakSelf;
        strongSelf.num += val;
        NSLog(@"Hello from Objective-C++, %s!", at);
        return 32;
    });
    http_parser_settings settings;
    settings.on_header_field = my_callback;
}

Unlike the previous one, C++ solution is much more reliable, because each time your code hits the lambda expression, it emits a new closure object. In both cases, however, the function objects have static storage duration, thus make sure you don't pass any strong pointer in the body of it (otherwise it will never be released).

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.