用 lua 调用 Windows 的 API



掌叔
2010-01-15 08:37:14

摘自:云风的 BLOG

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

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

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

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

#include
#include
#include

typedef void (__stdcall *func_call)();

void __stdcall foo(int a,int b)
{
printf("%d,%d
",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 指针。

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