免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
123下一页
最近访问板块 发新帖
查看: 10214 | 回复: 23

运行时动态函数调用(Runtime Dynamic Invoke) [复制链接]

论坛徽章:
0
发表于 2009-09-06 22:09 |显示全部楼层
window版没人气,发到这里吧,思想都是一样的,不过没有linux下的实现,有兴趣的试试看吧。
这阵子对动态解析比较感兴趣,对于动态解释型语言来说,这毫无研究的必要,但对于编译型的语言(主要讨论C/C++),凭着本人目前的水平,还无法想出办法。想法是很简单的,例如,类似这样的win32函数 BOOL Beep(int dwFreq,int dwDuration),静态调用就不说了,动态调用的话,先是加载函数库,然后取得Beep函数的地址,然后去invoke,但是,在编译期,必须显式地给出Beep的函数指针原型才能通过编译,因此,如果想偷懒, 不想给出原型以期达到广泛应用的话,至少现在微软提供的工具是没办法让你编译过的,那么怎么办呢?  比如我就想通过输入 函数名,参数1,参数1类型,参数2,参数2类型...这样的字符串,然后我就优哉游哉的让程序自己去调用我想调用的函数,那怎么办呢?
我们可以思考下微软自己是怎么实现动态链接和加载的呢,首先是加载库,然后取得函数地址,既然我们能得到函数的地址了,那我们就直接call呗,嗯还得传参,这够麻烦的了,如果不传参的话,就没这么难了。没关系,我们看看函数的调用规则吧,压栈基址指针,基址指向栈顶,参数进栈,调用,保存返回值,栈顶指向基址,出栈基址指针。那么,我们自己不也可以模拟下这个过程么?嗯,那就试试看吧
源码如下(win32下的console程序,vs2008下,vc++9.0 编译运行)
#include <stdio.h>
#include <Windows.h>
int main()
{
//加载函数库
HMODULE hMod = LoadLibraryA("Kernel32.dll");
//取得函数地址
FARPROC pfAddr = GetProcAddress(hMod,"Beep");
DWORD nRet ;//返回值
DWORD dwFreq = 750; //频率
DWORD dwDuration = 300;//持续时间
__asm
{
  //注意调用方式,这是stdcall
  push dwDuration//参数压栈,持续时间300毫秒
  push dwFreq //继续压,频率
  //...直到参数全部入栈
  call pfAddr //开始调用   
  mov nRet,eax //取得回值  
}
printf("%d",nRet);//输出返回值
//释放函数库
FreeLibrary(hMod);
return 0;
}
凭着"半拉可及"(俺们东北话)的知识,这个程序居然跑起来了,还不错,嗯,不过还得继续,因为我们忽略了我们想达到的目的中的一些细节,例如传参也要动态的,不光是函数名,还有类型,不能光是int吧?不过这是个好的开端,因为内嵌的汇编可以做循环嘛,可以实现循环的压栈(根据参数个数), 也可以根据参数类型选择相应的操作指令

论坛徽章:
0
发表于 2009-09-06 22:11 |显示全部楼层
能直接描述你的问题么

论坛徽章:
0
发表于 2009-09-06 23:04 |显示全部楼层
有了函数指针,还怕不能动态调用么?

论坛徽章:
7
酉鸡
日期:2013-10-30 17:17:51水瓶座
日期:2014-01-25 14:47:21天秤座
日期:2014-02-20 09:49:50处女座
日期:2014-11-04 17:44:082015年亚洲杯之中国
日期:2015-03-09 17:21:312015亚冠之北京国安
日期:2015-06-01 16:58:552015亚冠之山东鲁能
日期:2015-06-19 11:30:08
发表于 2009-09-07 09:37 |显示全部楼层
可以看看这个网站,有完整的解决方案。
http://www.dyncall.org/

论坛徽章:
0
发表于 2009-09-07 11:02 |显示全部楼层
原帖由 syncpk99 于 2009-9-7 09:37 发表
可以看看这个网站,有完整的解决方案。
http://www.dyncall.org/

嗯,多谢各位,我看了一下,很好,的确是我想要的

论坛徽章:
0
发表于 2009-09-07 14:46 |显示全部楼层
原帖由 syncpk99 于 2009-9-7 09:37 发表
可以看看这个网站,有完整的解决方案。
http://www.dyncall.org/



谢谢,这个网站不错,比起我们必须事先获得参数好多了

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
发表于 2009-09-08 01:48 |显示全部楼层

回复 #1 nicolas.shen 的帖子;回复 #4 syncpk99 的帖子

这库够牛叉的……


要动态(编译后)解析一个运行时得到(比如字符串格式)的函数调用,如:
f(a1,a2, ... an);

比较难处理的是如何传递"数量未知"的参数

详细一点的说, 假设, 解释器实现了:

1. 从脚本中得到一个表示调用的字符串

  1. const char* call_string = script_get_a_call_at_runtime();
复制代码


2. 从该字符串中得到函数名、函数返回类型、各个参数、以及它们的类型:

  1. void* f           = script_parse_function_name(call_string);

  2. typedef struct value
  3. {
  4.     int t;
  5.     union {
  6.         char   c;
  7.         int    i;
  8.         double d;
  9.         /* more */
  10.     } v;
  11. } value;

  12. value return_value;

  13. return_value.t = script_parse_function_return_type(call_string);

  14. int count   = 0;
  15. value* args = script_parse_arguments(call_string,&count);
复制代码


如何将args[0], args[1], ... args[count-1], 传递给f?

stdarg.h 帮不上忙的。 它只能取出"数量未知"的参数, 而不是传入
传入参数的时候, 数量依然在编译时就被固定, 如:
printf("%s %s\n","hello","world"); /* 2个,编译后固定 */


dyncall的处理方式:
1. 抽象出一个参数列表模型, 它被dyncall称为DCCallVM。称为VM有点过头了……

  1. DCCallVM* vm = dcNewCallVM(4096);
复制代码


2. 每次只传入1个参数

  1. dcArgXXX(vm, arg); /* XXX代表传入参数类型 */
复制代码


这是一个重点。 每次只压入1个参数, 就可以组合出传递多个参数 —— 只要重复调用压入参数的函数即可, 如:

  1. for (int i = 0; i< count; ++i ) {
  2.     switch (args[ i ].t) {
  3.     case TYPE_DOUBLE: dcArgDouble(vm, args[ i ].v.d); break;
  4.     case TYPE_INT   : dcArgInt   (vm, args[ i ].v.i); break;
  5.     /* more */
  6.     }
  7. }
复制代码


3. 抽象出一个调用模型, 它在dyncall中是dcCallXXX(XXX代表返回类型)。
重点之一: dcCallXXX 现在是拥有固定数目参数的函数(VM、function)。 数目未知的参数已被VM代替


  1. switch( return_value.t. ) {
  2. case TYPE_INT   : return_value.v.i = dcCallInt(vm,(DCpointer)f); break;
  3. case TYPE_DOUBLE: return_value.v.d = dcCallDouble(vm,(DCpointer)f); break;
  4. /* more */
  5. }
复制代码


现在, 已经可以使用"编译时数目实际参数固定的函数调用", 表达数目未知的函数调用了。


重点之二: 也是该库牛逼的地方……
它直接使用了native assembly code来实现这个调用模型……
将vm中的参数们依次提取, 并合理(依平台、调用约定而定)的传递, 然后调用。

这样做比较麻烦的地方就是移植性。 该库好像也仅支持x86和ppc, 还要兼顾OS和编译器组合。
体力活啊……

这样做的优势在于:
只要写好解释器与dyncall交互的代码, 就可以"直接"调用任意函数(dyncall还提供了load模块,这不是重点,实现不难)了。
说任意函数, 依然有一些限制 —— 参数只能是buildin-type, 不能是aggregates (structures, unions and classes)。
dyncall是可以支持指针参数的。 指针参数的麻烦之处不在dyncall上, 而在解释器如何实现。


说它直接, 是相对于另外一种方式 —— 一种可移植的方式, 但被调用的函数需要包装一下。
比如lua, 就是采用的这种方式。




要在lua中调用一个C函数, 前面的步骤都是类似的:

1. lua抽象出一个参数模型。(lua具体是怎么做的忘了, 所以下面伪得不能再伪的代码
lua的参数模型不仅仅包括参数、还包括返回值。被称为一个stack。

2. 向stack传入和取出参数的API, 都只有固定个参数, 这样在编译时才能写出这个API调用。

  1. LuaStack* stack = luaStack_new();
  2. for (int i = 0;i < count; ++i )
  3.     luaStack_set(stack, args[ i ] ); /* 并不是一个严格的栈 */
复制代码


3. lua的调用模型, 是使用C实现的。 所以它必须是一个参数数目固定的API —— 至少调用时,参数个数是固定的。

  1. luaCall( f, stack );  /* [b]可以编译[/b] */
  2. [code]

  3. btw: lua解释器中, 函数调用完毕时, 还可以从stack中取出(多个)返回值。

  4. 所以, 这个f绝对不能是Beep, 需要包装一次。
  5. 这一次包装, 就把[b]参数列表的解析的工作留给了用户[/b]。[b] lua就不用写不可移植的代码[/b]。

  6. 用户就必须为每一个需要在lua中调用的函数, 实现一个参数数目固定的包装函数。
  7. 这函数签名我还记得 —— typedef int (*lua_CFunction)(lua_State* );

  8. [code]
  9. int BeepWrapper(lua_State* lua) {
  10. /*
  11.     从lua_State中取得参数(上面说的stack包含在lua_State中)。
  12.     这样就将数目未知的参数,通过lua_State一个参数表达出来。 这里需要取2个。
  13.     然后, 调用真正的Beep。
  14.     以及, 将返回值放回stack。 返回返回值的个数。
  15. */
  16. }
复制代码


还需要向lua注册这个BeepWrapper, 使得解释器可以找到f, 也就是上面的伪代码:

  1. lua_CFunction f = script_parse_function_name(call_string);
复制代码



综上:
它们都是抽象出一个参数列表模型, 来将数目未知的参数转换为数目固定的参数。
实现调用时, dyncall直接使用native assembly code, 无须包装、注册; 但需要为不同平台编写不同代码。
而lua将实现调用的工作, 转交给用户。 每一个要在lua中调用的C函数都需要实现一个包装函数,并注册。
但lua、以及用户代码中就不容易出现平台相关代码。

[ 本帖最后由 OwnWaterloo 于 2009-9-8 01:52 编辑 ]

论坛徽章:
7
酉鸡
日期:2013-10-30 17:17:51水瓶座
日期:2014-01-25 14:47:21天秤座
日期:2014-02-20 09:49:50处女座
日期:2014-11-04 17:44:082015年亚洲杯之中国
日期:2015-03-09 17:21:312015亚冠之北京国安
日期:2015-06-01 16:58:552015亚冠之山东鲁能
日期:2015-06-19 11:30:08
发表于 2009-09-08 09:39 |显示全部楼层
楼上也够牛X的,找到这个库N久了,就是一眼也没看过啊

论坛徽章:
0
发表于 2009-09-08 11:01 |显示全部楼层
嗯的确有不少体力活,真是佩服啊

论坛徽章:
0
发表于 2009-09-08 12:11 |显示全部楼层
好像想到个用纯C++实现的办法
验证一下再来回复
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP