我正在使用https://github.com/nodejs/http-parser,它使用的回调类似于这个
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;
};
主要回调类型在这里定义
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
我正试图找到一种方法来传递Objective-C块或方法作为parser_settings
中的函数指针。然而,它只允许我使用C函数,这不适合我,因为我还需要访问回调中Objective-C对象的状态
目前我的解决方案如下:
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)
}
如何从传递给http_parser_execute
的回调中访问self
?
从技术上讲,您可以"提取物";使用class_getMethodImplementation
的C指针形式的Objective-C方法实现,但是这些实现具有类似objc_msgSend
的签名,并且总是需要接收器作为自变量,因此在Objective-C世界之外无法真正使用:
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"
话虽如此,无论是块还是Objective-C方法都不能直接转换为C函数指针(它们是指向引擎盖下结构的指针(,尤其是当你想用任何类型的上下文/状态来补充它时。
您可以做的最简单的事情是使用全局/静态分配的块变量,该变量可以从C函数访问,而无需更改其签名:
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;
}
我能想到的唯一可行的替代方案是使用C++lambdas。然而,当您需要访问当前状态/上下文时,这仍然是一个巨大的挑战,更不用说需要切换到Objective-C++了。如果可以,首先需要将Objective-C文件从SomeClass.m
重命名为SomeClass.mm
。通过这种方式,您可以告诉Clang源代码现在是Objective-C++,编译器应该接受C++代码。接下来,如果你的C库没有C++保护,你可能想用extern "C"
表达式包装C包含(否则链接器将无法定位C符号,因为C++会破坏它们(:
extern "C" {
#include <c_header.h>
}
现在棘手的部分是:lambda表达式返回特殊的对象闭包,只有当它们不从周围的上下文中捕获任何内容时,这些对象才能无缝转换为C函数指针。在我们的场景中,情况并非如此,需要额外的步骤才能将其转换为C指针。将此代码添加到*.mm
文件中的某个位置:
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));
}
事实上,这个解决方案与我上面提出的全局C函数解决方案相差不远。当闭包作为参数传递到这里时,这个模板函数会将它完美地转发给静态分配的变量。因此,静态闭包可以从无捕获的lambda调用,lambda又被转换为C函数指针。
最后,您可以使用C++lambda表达式,并将它们作为C函数指针传递到Objective-C代码中的任何位置:
- (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;
}
与前面的解决方案不同,C++解决方案要可靠得多,因为每次代码碰到lambda表达式时,它都会发出一个新的闭包对象。然而,在这两种情况下,函数对象都有静态存储持续时间,因此请确保在它的主体中没有传递任何强指针(否则它将永远不会被释放(。