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`访问控制的,但是这些访问控制都是给编译器编译期来使用限制的,运行时的动态链接都是靠符号来查找。