分享

云风的 BLOG: 用 lua 调用 Windows 的 API

 quasiceo 2014-01-04

用 lua 调用 Windows 的 API

昨天同事谈起能否给一个从 lua 中调用 Windows API 的简单方案。一开始觉得,如果是一个通用方案,那么至少需要先给出一个类似 windows.h 的原型声明,然后从 lua 来解析这些原型。大约写了几十行程序就实现了。后来又想了一下,似乎可以用一个更简单的方式,绕过原型,更简洁(但不保证安全)的方法来做到。

其间的问题就只有一个,每个 api 的参数都不一样,如何自动生成 C 中匹配的函数指针。似乎 C++ 的 template 是一个正统的解决方案。不过思考过几分钟以后,就被我否决了。实际用到的解决方案比较诡异:

先用 alloca 分配出正确的参数空间,再立刻填充这些参数,接下来以无参数的形式调用 api 。这样做,对于 __stdcall 的函数是没有问题的。好在 api 大多也是这样。

我写了这样一段程序来验证我的想法:

#include <stdio.h>
#include <malloc.h>
#include <assert.h>

typedef void (__stdcall *func_call)();

void __stdcall foo(int a,int b)
{
    printf("%d,%d\n",a,b);
}

void check(void *arg)
{
    assert((void**)arg-&arg==1);
}

void test()
{
    int *arg=(int*)_alloca(2*sizeof(int));
    arg[0]=1;
    arg[1]=2;
    check(arg);
    ((func_call)foo)();
}


void main()
{
    test();
}

这个方法唯一的漏洞,可能存在于 _alloca 并不能正确的分配出需要的空间。因为由于某些(对齐?)因素,我们不能保证分配出来的空间正好符合后面的函数调用需要的位置。个人感觉,这个问题在大多数编译器上不会出现。不过安全起见,我写了个 check 函数运行时检查。

用这个程序验证无误后,就写了个简单的 lua 扩展。使用起来大约是这样的:

opendll = require("api.opendll")

getprocaddress =require("api.procaddress")

user32=opendll("user32.dll")

MessageBox=getprocaddress(user32,"MessageBoxA")

MessageBox(nil,"hello","test",0)

有点意思 :) 另外我还测试了 FindWindow , ShowWindow 等,都工作的很正常。

这个方案初步解决了 dll 中 api 的调用问题,但还并不实用。比如我们需要写一套 dll 管理模块(直接用 lua 完成即可)。更重要的是需要解决 api 调用中无处不在的 C struct 的传递问题。这个问题又分两类,一类是作为输入参数的 struct ,一类是作为输出参数的 struct 如 (GetWindowRect) 。我们可以用 lua 的 table 去模拟 struct 。作为输入参数做 lua table 到 c struct 的转换;而作为输出参数则做 c struct 到 lua table 的回转。或者干脆用 userdata 直接映射 struct ,再用 metable 去读写之。

另一个需要解决的问题是,有些 api 为了返回多个参数,以传入指针的形式接收返回值。lua 里是没有指针的概念的。简单的解决方法是统一用 struct 的方式解决,把单一指针看成是一个只有一个成员的 struct 指针。

因为做这个东西纯属娱乐,目前项目中并不会用到,所以我也就没有继续深入下去了。

云风 提交于 July 27, 2006 02:58 PM | 固定链接

TrackBack

Comments

debug 下都会崩溃原因是_chkesp出错不过可以无视,因为这种调用方式和常用的调用方式本身就不一样如果非要解决这个崩溃可以改成#ifdef _DEBUG
__asm call fc
#else
ret=fc();
#endif
这样vc就不检查堆栈了。debug/release都没问题了。

Posted by: blknit | (23) August 1, 2012 05:50 PM

debug 下都会崩溃原因是_chkesp出错不过可以无视,因为这种调用方式和常用的调用方式本身就不一样如果非要解决这个崩溃可以改成#ifdef _DEBUG
__asm call fc
#else
ret=fc();
#endif
这样vc就不检查堆栈了。debug/release都没问题了。

Posted by: blknit | (22) August 1, 2012 05:50 PM

Luafor Windows中有个alien模块,可以研究下它啊

Posted by: David | (21) October 19, 2009 05:23 PM

类里面的非static函数怎么用这种方法调用,呵呵

Posted by: chengjie | (20) April 23, 2008 05:19 PM

实现之后,运行会出现对话框,但是对话框确定后会异常

Posted by: yippeesoft | (19) July 24, 2007 10:27 AM

和C/Invoke挺象的
http://www./cinvoke/lua.html

Posted by: Anonymous | (18) June 10, 2007 06:37 AM

在这个简易的实现中,返回值是用户指定的。也就是 getprocaddress 的第3个参数传入。我把这个参数放到 upvalueindex 1 的地方了。

这就是为什么用了一个 `lua_pushvalue(L,3)`

比如希望 api 返回一个 bool 值,那么就在 getprocaddress 调用时第三个参数传一个 true 。

Posted by: Cloud [TypeKey Profile Page] | (17) April 19, 2007 05:46 PM

LuaApiCall中,你是如何获得API返回类型的?
lua_type(L,lua_upvalueindex(1))???


是lua_pushvalue(L,3)?为什么呢?

Posted by: huo | (16) April 19, 2007 02:29 PM

看了一下 luaforge 上的那个实现,跟我这里说的不是一回事 :) 他只是把常用的 api 逐个包装了一下。

Posted by: Cloud [TypeKey Profile Page] | (15) August 28, 2006 05:00 PM

http:///frs/?group_id=96

Posted by: Lua有一个扩展可以调用Win32 API | (14) August 28, 2006 04:21 PM

1. 很多 C 代码本身就是依赖 alloca 不用释放的特性写的。

2. 堆栈溢出的问题并非 alloca 直接引起的,这里调用 alloca 的本质跟直接调用函数,把参数压栈是一致的。

3. 更保险的做法前面已经写过了,就是让 check 返回 &arg, 然后根据这个返回值调整参数的位置。代码如下:

<pre>
void* check(void *arg)
{
return (void*)(&arg+1);
}

int *arg=(int*)_alloca(2*sizeof(int));
arg=(int*)check(arg);
</pre>

Posted by: Cloud [TypeKey Profile Page] | (13) August 2, 2006 12:31 PM

在vs2005下,alloca推荐使用malloca替代,而malloca在debug模式下是分配在heap上,只有release模式下是在stack上,所以需要使用freea释放。 alloca的行为可能导致堆栈溢出。

至于为什么使用alloca在vs2005 debug下会crash,是因为check的assert出错(==9)。

Posted by: Siney | (12) August 2, 2006 11:26 AM

应该跟 debug 模式没关系,crash 可以找找 crash 的原因,我相信是找的出来的。

alloca 不可能在 heap 上分配,如果那样,函数退出的时候就无法释放。

ps. 我用 vc6 在 debug 下试过了可以通过。

Posted by: Cloud [TypeKey Profile Page] | (11) August 1, 2006 11:57 PM

这个程序似乎只能在release模式下工作(我的vs2005),否则会crash,因为debug模式下alloca分配的内存是来自heap.

Posted by: Siney | (10) August 1, 2006 11:38 PM

如果实在觉得 alloca 不可信,可以改造 check 函数,把 &arg 返回,再根据这个返回值来修整参数的位置。

其实这里提到的用法很正常,所有 C 程序员都用过。那就是 printf 的参数。还有我们使用的 VA_LIST 这个系列的宏,都依赖了函数调用的编译器实现规则。

Posted by: Cloud [TypeKey Profile Page] | (9) July 31, 2006 05:28 PM

如果你对系统提供的 alloca 不自信,还是需要写一个类似 alloca 的函数,完成需要的功能。所以,调用 alloca 并不是错。

而这里,"根据特定平台的ABI来生成caller parameter", 并不是 alloca 做的,alloca 做的只是分配 caller parameter 需要的空间。

Posted by: Cloud [TypeKey Profile Page] | (8) July 31, 2006 05:19 PM

哦,这样的话,如果需要根据特定平台的ABI来生成caller parameter的话,我感觉与其不自信的依赖alloca(这个实现完全依赖于编译器),不如根据特定平台用汇编完成这个调用函数。

Posted by: Bennie | (7) July 31, 2006 04:44 PM

回 yufeng, 指针减法和整数减法是不同的。assert((void**)arg-&arg==1) 指地址相差一个指针位,指针占据的字节数不一定是 32bit(当然在这里的 Windows 特定平台是 32bit) 也可能是 16bit 或者 64bit 。

回 devcpp, 每个编译器可能不一样,所以才写 check 函数检查以下。

回 Bennie, 这个并不是只用在windows 上,也不只适用于 IA32 平台。而且用汇编的意义也不大。

Posted by: Cloud [TypeKey Profile Page] | (6) July 30, 2006 01:25 PM

请问 assert((void**)arg-&arg==1);
这个怎么解释.

在我的vc6上
int x=(void**)arg;
int y=&arg;
assert(x-y==4);

迷惑中。

Posted by: yufeng | (5) July 29, 2006 12:01 PM

我用devcpp编译了,assert((void**)arg-&arg==4);才可以通过断言.而且程序输出是"2359104,2008950864",怎么回事啊?

Posted by: gmcat | (4) July 29, 2006 11:25 AM

既然都用到了alloca,又是在windows上调用api,为什么不直接使用嵌入式汇编调整一下esp?

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多