通过函数名称字符串发起调用/函数名反射

37 篇文章 3 订阅

python

在 python 当中,一切都是对象。函数也是第一等公民。

如果要使用函数名称字符串发起调用,目标在于通过字符串找到函数对象。

函数

def func(a):
    print("global function func", a)

显示的从全局命名空间中查找

python 当中,全局的命名空间可使用内置函数`global`获取,`global` 返回一个全局的名字对象字典。

globals()["func"](3)

使用内置的解释器

脚本语言都是解释执行,而且一般都提供一个解释器接口。比如 `python,javascript,shell` 中的 `eval`.`lua`自带的`loadstring`. `redis`当中也使用`eval` 来执行`lua`脚本。

eval("func")(3)

将字符串转换成字节码

`python` 内置的`compile` 函数能够将源码字符串转换成字节码,字节码对象再交由解释器执行,这其实是上一种方式的细化。

code = compile("func('hello')","_", "eval")
eval(code)

方法

class T:
    def __init__(self):
        pass
    def f(self, a):
        print("method f", a)
obj = T()

普通方法需要通过对象来调用,所以首要条件要获取这样一个对象。获取目的对象的方式可以使用上边提到的 3 中获取全局变量的方法。

剩下的工作在于通过方法字符串结合目标对象找到方法对象。

`getattr`

`getattr`触发属性的查找。

getattr(obj, "f")(3)

`operator`

内置的`operator`触发属性的查找。

import operator
operator.attrgetter("f")(obj)(3)

`operator.methodcaller` 更是直接发起方法调用。

operator.methodcaller("f", 3)(obj)

C/C++

C函数

其实最终都是根据符号(字符串名称)来查找,在 `python` 当中,找的是对象;在 `C/C++` 当中找的地址罢了。
 

//oo.c
#include <stdio.h>

void func()
{
    printf("this is func\n");
}

int main()
{
    char* funcname = "func";
    //如何根据 funcname 来调用func 函数?

    return 0;
}

我们知道,`Linux`上的`elf` 可执行文件在运行时,会构建一个**全局符号表**。全局符号表通过 `elf`自身的动态符号表`.dynsym` 和所有直接和间接依赖的动态库中的 `.dynsym` 来构建,当然里边涉及很多细节,比如全局符号介入(global symbol interpose)等。获取了全局符号表之后,就能从全局符号表当中查找到函数符号的对应地址。

void *dlopen(const char *filename, int flags);

第一个参数传 NULL 时,返回的就是全局符号表。

所以只需要包含 `#include <dlfcn.h>`,代码也很明白。
 

void* handle = dlopen(NULL, RTLD_LAZY);
    if(handle == NULL)
    {
        printf("%s\n", dlerror());
        return 1;
    }

    void* (*fptr)() = dlsym(handle, "func");  
    if(fptr != NULL)
    {
        fptr();
    }
    else
    {
        printf("%s\n", dlerror());
    }


编译执行

gcc oo.c -ldl -o oo.out && ./oo.out

等等,出错了,好像也没这么简单。

./oo.out: undefined symbol: func

提示未找到符号。

readelf --dyn-syms oo.out

 看看动态符号表当中,还真没这个符号。

其实这牵扯到另外一个问题,主模块(编译成 .out 的那个二进制模块)当中的符号,和普通的动态库 `.so`当中的不同。普通的动态库中的符号,只要没有`static` 限制为未局部的,都会成为导出符号,出现在 `.dynsym` 当中。但是,**主模块呢,只有在其他模块,比如 `A .so`中使用和主模块中同名的符号(不论这个符号在`A.so` 还是 `B.so` 当中是否有定义)**,主模块的符号才会成为导出符号,出现在 `.dynsym` 当中。

这个动作由链接器完成,使用 `-Wl,--export-dynamic` 链接器选项即可,或者使用 `-rdynamic` 选项来表达相同的含义。

gcc -Wl,--export-dynamic oo.c -ldl -o oo.out && ./oo.out
#this is func

`C++` 类方法

//o.cpp
#include <stdio.h>
#include <malloc.h>

class T
{
    int x;

    T(int i);
    void f();
};

T::T(int i)
{
    x = i;
}
void T::f()
{
    printf("x is %d\n", this->x);
}
T* getobj()
{
    return static_cast<T*>(malloc(sizeof(T)));
}
void freeobj(void *p)
{
    free(p);
}

C++ 的源代码可以编译成`.so` 动态库,给 C 调用。到了 `.so` 这一层,就只用考虑 `abi`.

g++ -fpic -shared o.cpp -o libo.so

 在`abi`的维度上,只要兼容,任何语言都能实现互操作。
 

//maino.cpp
#include <stdio.h>
#include <dlfcn.h>


int main(int argc, char* argv[], char* en[])
{
    void* handle = dlopen(NULL, RTLD_LAZY);
    if(handle == NULL)
    {
        printf("%s\n", dlerror());
        return 0;
    }

    void* (*getobj)() = dlsym(handle, "_Z6getobjv");  //T* getobj()
    if(getobj != NULL)
    {
        void* obj = getobj();

        void (*ctor)(void*, int i) = dlsym(handle, "_ZN1TC1Ei");  //T::T(int)
        ctor(obj, 4);

        void (*f)() = dlsym(handle, "_ZN1T1fEv");  //void T::f()
        f(obj);
    
        void (*freeobj)(void*p) = dlsym(handle, "_Z7freeobjPv"); //void freeobj(void *p)
        freeobj(obj);
    }
    else
    {
        printf("%s\n", dlerror());
    }

    return 0;
}

构建并执行:

gcc maino.c ./libo.so -ldl -o maino.out && ./maino.out
#x is 4

**几个细节的阐述**

1. C++ 中存在的名称修饰(name mangling),包含在动态符号表当中的都是名称修饰之后的名字。装饰之后的名字可以通过 `objdump`来获取,使用 `c++filt` 验证。
2. 类 T 中所有的内容全部都是 `private`访问控制的,但是这些访问控制都是给编译器编译期来使用限制的,运行时的动态链接都是靠符号来查找。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值