浅谈在Swift中关于函数指针的实现
Swift没有什么?
苹果工程师给我建的唯一一堵墙是:在Swift中没有任何办法获得一个函数的指针:
注意,C函数指针不会导入到Swift中(来自“UsingSwiftwithCocoaandObjective-C“)
但是我们怎么知道这种情况下钩子的地址和跳到哪呢?让我们深入了解一下,并且看看Swift的func在字节码层面上的是什么。
当你给一个函数传递一个泛型参数时,Swift并没有直接传递它的地址,而是一个指向trampoline函数(见下文)并带有一些函数元数据信息的指针。并且trampoline自己是包装原始函数的结构的一部分。
这是什么意思?
让我们用它来举个例子:
funccall_function(f:()->Int){
letb=f()
}
funcsomeFunction()->Int{
return0
}
在Swift里我们只写call_function(someFunction).
但是Swift编译器处理代码后,性能比调用call_function(&someFunction)好很多
structswift_func_wrapper*wrapper= .../*configurewrapperforsomeFunction()*/ structswift_func_type_metadata*type_metadata=.../*informationaboutfunction'sargumentsandreturntype*/ call_function(wrapper->trampoline,type_metadata);
一个包装器的结构如下:
structswift_func_wrapper{
uint64_t**trampoline_ptr_ptr;//=&trampoline_ptr
uint64_t*trampoline_ptr;
structswift_func_object*object;
}
什么是swift_func_object类型?为了创建对象,Swift实时使用了一个全局的叫metadata[N]的的常量(每一个function调用都是唯一的,似的你的func作为一个泛型的参数,所以对于如下的代码:
funccallf(f:()->()){
f();
}
callf(someFunction);
callf(someFunction);
常量metadata和metadata2会被创建).
一个metadata[N]的结构有点儿像这样this:
structmetadata{
uint64_t*destructor_func;
uint64_t*unknown0;
constchartype:1;//I'mnotsureaboutthisandpadding,
charpadding[7]; //maybeit'sjustauint64_ttoo...
uint64_t*self;
}
最初metadataN只有2个字段集合:destructor_func和type。前者是一个函数指针,将用作为使用swift_allocObject()创建的对象分配内存。后者是对象类型识别器(函数或方法的0x40或者'@'),并且是(某种形式)被swift_allocObject()用来创建一个正确的对象给我们的func:
swift_allocObject(&metadata2->type,0x20,0x7);
一旦func对象被创建,它拥有下面的结构:
structswift_func_object{
uint64_t*original_type_ptr;
uint64_t*unknown0;
uint64_tfunction_address;
uint64_t*self;
}
第一个字段是一个指针,用来对应metadata[N]->type的值,第二个字段似乎是0x4|1<<24(0x100000004)并且暗示一些可能(我不知道是什么)。 function_address是我们实际挂钩感兴趣的地方,并且self是(立即)自己的指针(如果我们的对象表示一个普通的函数,这个字段是NULL)。
好,那么这段我从框架开始如何?事实上,我不明白为什么Swift运行时需要它们,但不论如何,这就是它们原生态的样子:
void*someFunction_Trampoline(void*unknown,void*arg,structswift_func_object*desc)
{
void*target_function=(void*)desc->function_address;
uint64_t*self=desc->self;
swift_retain_noresult(desc->self);//yeah,retainingselfiscool!
swift_release(desc);
_swift_Trampoline(unknown,arg,target_function,self);
returnunknown;
}
void*_swift_Trampoline(void*unknown,void*arg,void*target_function,void*self)
{
target_function(arg,self);
returnunknown;
}
让我们创建它
想象一下,在你的Swift代码中有这些函数:
functakesFunc<T>(f:T){
...
}
funcsomeFunction(){
...
}
而且你想像这样生成它们:
takesFunc(someFunction)
这一行代码会转换成相当大的C程序:
structswift_func_wrapper*wrapper=malloc(sizeof(*wrapper));
wrapper->trampoline_ptr =&someFunction_Trampoline;
wrapper->trampoline_ptr_ptr=&(wrapper.trampoline);
wrapper->object=({
//let'ssaythemetadataforthisfunctionis`metadata2`
structswift_func_object*object=swift_allocObject(&metadata2->type,0x20,0x7);
object->function_address=&someFunction;
object->self=NULL;
object;
});
//globalconstantforthetypeofsomeFunction'sarguments
constvoid*arg_type=&kSomeFunctionArgumentsTypeDescription;
//globalconstantforthereturntypeofsomeFunction
constvoid*return_type=&kSomeFunctionReturnTypeDescription;
structswift_func_type_metadata*type_metadata=swift_getFunctionTypeMetadata(arg_type,return_type);
takesFunc(wrapper->trampoline_ptr,type_metadata);
结构体“swift_func_type_metadata”很不透明,因此我也没太多可以说的。
回到函数指针
既然我们已经知道函数怎样作为一个泛型类型参数表示,让我们借助这个打到你的目的:获取一个真正指向函数的指针!
我们要做的只是需要注意,我们已经拥有一个作为第一个参数传递的trampoline_ptr指针域地址,所以object域的偏移量只是0x8。其他的所有都很容易组合:
uint64_t_rd_get_func_impl(void*trampoline_ptr)
{
structswift_func_object*obj=(structswift_func_object*)*(uint64_t*)(trampoline_ptr+0x8);
returnobj->function_address;
}
看起来是时候写写
rd_route( _rd_get_func_impl(firstFunction), _rd_get_func_impl(secondFunction), nil )
但我们怎样从Swift中调用这些C函数呢?
为此,我们将使用Swift非公开的特性:允许我们提供给C函数一个Swift接口的@asmname属性。用法如下:
@asmname("_rd_get_func_impl")
funcrd_get_func_impl<Q>(Q)->UInt64;
@asmname("rd_route")
funcrd_route(UInt64,UInt64,CMutablePointer<UInt64>)->CInt;
这就是我们在Swift中使用rd_route()需要的一切。
但是它不能处理任何函数!
也就是说,你不能用rd_route()钩住任何带有泛型参数的函数(这可能是Swift的bug,也可能不是,我还没弄清楚)。但是你可以使用extensions轻松的覆盖它们,直接指定参数的类型:
classDemoClass{
classfunctemplate<T:CVarArg>(arg:T,_num:Int)->String{
return"\(arg)and\(num)";
}
}
DemoClass.template("Test",5)//"Testand5"
extensionDemoClass{
classfunctemplate(arg:String,_num:Int)->String{
return"{String}";
}
classfunctemplate(arg:Int,_num:Int)->String{
return"{Int}";
}
}
--Yourextension'smethodsforStringandIntwillbepreferredovertheoriginalones*/
DemoClass.template("Test",5)--"{String}"
DemoClass.template(42,5)--"{Int}"
--Butforothertypes`template(T,Int)`willbeused
DemoClass.template(["Array","Item"],5)---"[Array,Item]and5"
SWRoute
为了在Swift里轻松地勾住函数,我创建了一个名为SWRoute的封装体—它只是一个小类和一个我们之前写过的C函数:
_rd_get_func_impl():
classSwiftRoute{
classfuncreplace<MethodT>(functiontargetMethod:MethodT,withreplacement:MethodT)->Int
{
returnInt(rd_route(rd_get_func_impl(targetMethod),rd_get_func_impl(replacement),nil));
}
}
注意,我们无偿进行类型检查因为Swift需要目标方法和替换具有相同的MethoT类型。
而且我们也无法使用一个复制的原始实现,因此我只能把nil作为另一个参数传给函数rd_route()。如果你对如何把这个指针集成到Swift代码有自己的看法,麻烦告诉我!
你可以在资源库中找到大量SWRoute的实例。
这就是所有的了。