用 lua 调用 Windows 的 API

  云风的 BLOG

« 糟糕的 DELL 鼠标 | 返回首页 | 手机收不到短信了 »

用 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 指针。

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

TrackBack

如果你想引用这篇文章,请复制下面的链接发送引用通告(GBK)
http://blog.codingnow.com/mt/mt-tb.cgi/165

链入链接:用 lua 调用 Windows 的 API:

» 关于 _alloca from Stone
_alloca [Read More]

Comments

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

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

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

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

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

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

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


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

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

http://luaforge.net/frs/?group_id=96

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

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

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

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

int *arg=(int*)_alloca(2*sizeof(int));
arg=(int*)check(arg);


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

迷惑中。

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

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

我的项目用 lua

lua的速度的确不错,我想问一下云风大哥你现在的游戏是用lua还是python做脚本的?


你可能感兴趣的:(用 lua 调用 Windows 的 API)