Lua Generic Call |
|
const char* errmsg = NULL;
double result;
lua_settop(L, 0);
if(luaL_loadstring(L, "local a,b = ...; return a*b"))
errmsg = lua_tostring(L, -1);
else
{
lua_pushinteger(L, 3);
lua_pushnumber(L, 2.5);
if(lua_pcall(L, 2, 1, 0))
errmsg = lua_tostring(L, -1);
else
result = lua_tonumber(L, -1);
}
double result; const char* errmsg = lua_genpcall(L, "local a,b = ...; return a*b", "%d %f > %lf", 3, 2.5, &result);
GetProcAddress 或 dlsym。但是,对于这项工作,最好遵循 EasyManualLibraryLoad 指南。源代码可以在这里找到 [lgencall.zip]
luaL_error,该函数会抛出异常(或执行 longjmp,这是类似的)。调用者有责任使用 lua_cpcall 或设置 lua_atpanic 来捕获此错误。lua_gencallA,并用 lua_cpcall 包裹起来。发生错误时,异常会被捕获,并将错误消息作为函数结果输出。否则,函数将返回 NULL。lua_gencallA 的 Unicode 或宽字符版本。它的两个字符串参数(脚本块和格式)必须是 wchar_t* 类型。它会将它们转换为相应的 char* 版本并调用 lua_gencallA。格式字符串始终包含 ASCII 字符,因此转换是直接的。脚本字符串可能包含任意字符;最典型的是本地化语言中的文件名和用户消息。该函数支持两种转换方法,可在编译时选择。一种是使用 wctomb 和 mbtowc(基于当前区域设置的系统函数),另一种是使用自定义代码转换为 UTF-8。lua_gencallW 的保护版本。发生错误时,消息字符串将被转换回宽字符并返回。lua_gencall 重定向到 lua_gencallA 或 lua_gencallW,具体取决于是否定义了 UNICODE。也可以在不进行宽字符支持的情况下编译库文件。
NULL,则函数会自动调用 lua_newstate 来创建一个新实例,并且还会调用 lua_close 来释放其内存,除非该实例的指针是通过 %S 选项检索的(见下文)。如果状态被释放并且在此之前发生了错误,函数会分配一个缓冲区来复制错误消息,该缓冲区必须使用 free 关闭。local var1, var2, var3 = ...;,并以返回结果结束:return res1, res2, res2。如果指针为 NULL,则相当于空脚本 ""。printf 或 scanf 格式字符串的字符串,使用 % 字符来描述输入和输出值的变量类型。如果指针为 NULL,则相当于空格式 ""。出于性能原因,存在一个已编译代码块的缓存。它实现为 Lua 注册表中 Lua 表,以代码块字符串为索引。因此,如果您多次调用具有相同脚本字符串的 lua_genpcall,它只会在第一次编译。所有后续调用都将重用缓存版本。缓存表不是弱表,以避免由于垃圾回收而多次重新编译相同的脚本。缺点是当脚本代码块在运行时可以任意更改时,缓存可能会无限增长。例如,这可能发生在服务器解释器执行来自客户端程序的 Lua 代码块时。但是,您可以显式清除缓存,方法是在格式字符串中指定它。
与 printf 甚至更与 scanf 一样,您必须非常小心参数的类型和相应的格式规范。任何不匹配都可能导致意外结果,甚至更糟,导致应用程序崩溃。
格式字符串的通用形式如下:
"[ Directives < ] Inputs [ > Outputs ]"
指令、输入和输出是类似于 printf 和 scanf 格式的字符串,由以百分号开头的格式项组成。指令和输出是可选部分。如果它们存在,则必须用 < 或 > 字符与输入分隔。输入也可以是空字符串。
printf 格式规范一样,每个输入或输出格式项最多包含 6 个字段,如下例所示。指令项的选项较少,稍后将进行解释。任何空白字符(空格、制表符、回车符和换行符)都将被忽略,并可用于提高格式字符串的清晰度。其他字符要么按本章所述进行解释,要么在无效时引发 Lua 错误。%#12.4Ls
标志参数可以是以下字符之一:
lua_newstate 的函数,默认实现为调用标准 realloc 和 free 函数)来分配。使用后您需要 free 它。宽度参数与字符串、字符串列表和数组一起使用。它表示内存缓冲区中的元素或字符数。它可以是以下形式之一:
strlen 确定。对于字符串列表,表示列表在两个连续的零字符之后结束。精度参数用于数值类型,以指示 C 类型的大小(以字节为单位)。这对于数值数组和输出值很重要。因为在这两种情况下,传递的是指向变量的指针而不是值本身。它具有以下形式之一:
大小修饰符参数是数值精度值的替代方法,它改变了值的预期 C 类型。它可以是以下字符之一。请参考下面的大小表进行对应。
最重要的是,转换字符指定了数据类型。它必须是以下字符之一:
对于数值,下表列出了默认和修改后的底层 C 类型:
(default) 'hh' 'h' 'l' 'L'
'f' float --- float double long double (*)
'd','i' int char short long int64_t (*)
'u' unsigned unsigned unsigned unsigned uint64_t (*)
int char short long
'b' C89: int --- char int ---
C99: _Bool
C++: bool
's','z' gencallA: char*
gencallW: wchar_t* (*) --- char* wchar_t* (*) ---
其他对象值的关联 C 类型如下:
ptr 将接收其地址。函数指针具有以下两种原型之一,具体取决于数据方向:TYPE 为前面两表中所述的基本 C 类型。除了 'n' 和 'k' 转换字符外,复合类型为:Width argument (none) number, '*' or '&' number, '*' or '&'
Flag (don't care) (none) '#' or '+'
Input TYPE const TYPE* const TYPE*
Output TYPE* TYPE* TYPE**
luaL_openlibs 来初始化标准库。lua_close 释放 Lua 状态。
lgencall.c 和一个头文件 lgencall.h。还有一个测试文件 testwin.cpp,它包含了下一章的所有测试示例,包括 Windows 头文件 tchar.h。使用此实用程序头文件,可以编写同时适用于 ANSI 和 Unicode 平台的 C++ 代码。主 C 文件包含 ANSI 标准文件以及公共 Lua API 头文件。与其他标准 Lua 库一样,不使用私有功能,并且该文件可以在 C 和 C++ 语言中进行编译。但是,它需要新的 C99 头文件 stdint.h 来定义固定大小的整数。如果您的编译器不支持此功能,则可以在 WWW 上找到几个免费版本。 [1] [2]
源文件可以与应用程序一起编译,或者放置在 Lua 共享库中,如果您愿意重新编译它。
lgencall.h 中定义了 3 个编译宏,用于自定义库以适应您的平台。每个参数都可以通过修改文件本身或在编译器的命令行中指定。头文件中对此进行了简要说明,列出了可能的值。此外,编译还会受到以下标准宏的影响:__cplusplus、INT_MAX、UINT_MAX 和 __STDC_VERSION__。
"Hello World" 程序。lua_genpcall(NULL, "print 'Hello World!'", "%O<");
print 函数。由于未传递 %S 选项,Lua 状态会在调用结束时被释放。我们没有测试返回值,它是一个错误消息。这是同一个示例,但作为更完整、更逼真的实现。
lua_State* L = lua_open(); luaL_openlibs(L); const char* errmsg = lua_genpcall(L, "print 'Hello World!'", ""); if(errmsg) fprintf(stderr, "Lua error: %s\n", errmsg); lua_close(L);
再次是同一个示例,但仅使用 lua_genpcall。
lua_State* L;
lua_Alloc falloc;
lua_genpcall(NULL, NULL, "%O %S %&M<", &L, &falloc);
char* errmsg = lua_genpcall(L, "print 'Hello World!'", "%C<");
if(errmsg)
{
fprintf(stderr, "Lua error: %s\n", errmsg);
falloc(NULL, errmsg, 10, 0);
}
1. 数字
lua_genpcall(L, "for k,v in pairs{...} do print(k, type(v), v) end",
"%i %d %u %f %f", -4, 0xFFFFFFFF, 0xFFFFFFFF,
3.1415926535f, 3.1415926535);
-->
1 number -4
2 number -1
3 number 4294967295
4 number 3.1415927410126
5 number 3.1415926535
number 类型)。由于 Lua 中的所有数字都存储为 double,因此浮点参数 Pi 的值存在截断误差。请注意 %d 和 %u 对于值 0xFFFFFFFF 的行为差异。对于 double 参数,在这里不必指定 %lf 而不是 %f,因为在 C 中将浮点数传递给可变参数函数时,它们总是会被转换为 double。小于 int 的整数也会自动转换为 int。2. 布尔值、nil、简单字符串和轻量级用户数据
lua_genpcall(L, "for k,v in pairs{...} do print(k, type(v), v) end",
"%b %b %n %s %p", 0, 1, "Hello", L);
-->
1 boolean false
2 boolean true
4 string Hello
5 userdata userdata: 0096CE70
true 和 false。nil 参数 %n 仅存在于格式字符串中,没有关联的参数(因为它没有被打印,因为 pairs 函数会跳过 nil 值)。这里假设字符串是零终止的,而最后一个参数 L(Lua 状态)只是一个通用指针的示例。3. C 函数和回调
int cFunction(lua_State* L)
{
printf("%s\n", luaL_checkstring(L, 1));
return 1;
}
void pushMessage(lua_State* L, const void* ptr)
{
lua_pushstring(L, *(const char**)ptr);
}
...
lua_genpcall(L, "local fct, msg = ...; fct(msg)",
"%c %k", cFunction, pushMessage, "Hello from C!");
cFunction 的指针传递。第二个参数是回调参数,由用户函数 pushMessage 和一个字符串组成。请注意,回调函数接收参数的指针而不是参数本身!4. 数值数组
short array[] = { 1,2,3 };
lua_genpcall(L,
"for k,v in pairs{...} do print(k, #v, table.concat(v, ', ')) end",
"%2hd %5.1u %*.*d", array, "Hello",
sizeof(array)/sizeof(array[0]), sizeof(array[0]), array);
-->
1 2 1, 2
2 5 72, 101, 108, 108, 111
3 3 1, 2, 3
5. 高级字符串
unsigned char data[] = { 200, 100, 0, 3, 5, 0 };
lua_genpcall(L, "for k,v in pairs{...} do print(k, v:gsub('.', "
"function(c) return '\\\\'.. c:byte() end)) end",
"%s %6s %*s %ls", "Hello", "P1\0P2", sizeof(data), data, L"�t�");
-->
1 \72\101\108\108\111 5
2 \80\49\0\80\50\0 6
3 \200\100\0\3\5\0 6
4 \195\169\116\195\169 5
... return '\\' ...),然后是 Lua。第一个参数是零终止的字符串;第二个是包含二进制 0 的字符串,由其长度指定。第三个参数是二进制数据数组,其长度通过参数传递。最后一个参数是宽字符字符串,它被转换为 UTF-8 字符串或其他形式的多字节字符串,具体取决于模块的编译方式。6. 字符串列表
lua_genpcall(L,
"for k,v in pairs{...} do print(k, #v, table.concat(v, ',')) end",
"%z %7z %hz %*lz", "s1\0s2\0s3\0", "s4\0\0s5\0",
"c1\0c2\0c3\0", 7, L"w1\0\0w2\0" );
-->
1 3 s1,s2,s3
2 3 s4,,s5
3 3 c1,c2,c3
4 3 w1,,w2
lua_genpcallW 期望宽字符字符串。最后一个列表是宽字符版本,它通过额外的 '*' 参数指定其长度。您肯定已经注意到字符串和字符串列表参数之间的强烈相似性。
1. 数字
char var1; unsigned short var2; int var3;
float var4; double var5;
lua_genpcall(L, "return 1, 2, 3, 4, 5", ">%hhd %hu %d %f %lf",
&var1, &var2, &var3, &var4, &var5);
printf("%d %u %d %f %f\n", var1, var2, var3, var4, var5);
-->
1 2 3 4.000000 5.000000
var1 和 var3 是有符号整数,最后两个是浮点数。在这种情况下,%lf 格式是强制性的!2. 布尔值、nil、简单字符串和轻量级用户数据
bool bool1; int bool2;
const char* str; void* ptr;
lua_genpcall(L, "return true, false, 'dummy', 'Hello', io.stdin",
">%hb %lb %n %+s %p", &bool1, &bool2, &str, &ptr);
printf("%d %d %s %p\n", bool1, bool2, str, ptr);
-->
1 0 Hello 00975598
3. C 函数和回调
void getMessage(lua_State* L, int idx, void* ptr)
{
*(const char**)ptr = lua_tostring(L, idx);
}
...
lua_CFunction fct;
const char* msg;
lua_genpcall(L, "return print, 'Hello World!'",
">%c %k", &fct, getMessage, &msg);
lua_pushstring(L, msg);
fct(L);
Hello World 程序的一种复杂方式。第一个返回值是指向 Lua 注册的 C 函数(全局 print 函数)的指针。第二个值是一个简单字符串,通过回调函数检索,该回调函数在 ptr 参数中接收 msg 变量的地址。然后,我们可以通过将消息推送到 Lua 堆栈并直接通过 C 调用 print 函数来打印消息(这在正常情况下肯定不是一个好习惯)。4. 数值数组
unsigned int int_a[3];
bool bool_a[4];
char* str;
short* pshort;
int short_len;
int bool_len = sizeof(bool_a)/sizeof(bool_a[0]);
lua_genpcall(L, "return {1,2,3,4},{72,101,108,108,111,0}, {5,6,7}, {false,true}",
">%3u %+.1d %#&hd %&.*b", &int_a, &str, &short_len, &pshort,
&bool_len, sizeof(bool_a[0]), &bool_a);
printf("int_a = {%u,%u,%u}\nstr = %s\npshort[%d]=%d\nbool_a = #%d:{%d,%d,%d,%d}\n",
int_a[0], int_a[1], int_a[2], str, short_len-1, pshort[short_len-1],
bool_len, bool_a[0], bool_a[1], bool_a[2], bool_a[3]);
free(pshort);
-->
int_a = {1,2,3}
str = Hello
pshort[2]=7
bool_a = #2:{0,1,204,204}
bool_len 的值必须在调用之前初始化为数组的大小,因为我们使用的是 C 堆栈分配的缓冲区。由于缓冲区比返回的数组大,其最后两个值将保持未初始化。5. 高级字符串
const char *str1;
char *str2;
char str3[10];
unsigned char data[6];
int len = sizeof(data);
wchar_t* wstr;
lua_genpcall(L, "return 'Hello', ' Wor', 'ld!', '\\0\\5\\200\\0', 'Unicode'",
">%+s %#s %*s %&s %+ls", &str1, &str2, sizeof(str3), str3, &len, data, &wstr);
printf("%s%s%s\ndata (%d bytes): %02X %02X %02X %02X %02X\nwstr = %S\n",
str1, str2, str3, len, data[0],data[1],data[2],data[3],data[4], wstr);
free(str2);
-->
Hello World!
data (4 bytes): 00 05 C8 00 00
wstr = Unicode
len 来设置缓冲区大小,并在调用后获取实际数据大小。请注意,总是有一个额外的零字节被复制到目标缓冲区(如果空间足够)。最后一个值是一个宽字符字符串,放在 Lua 堆栈上。6. 字符串列表
void print_string_list(const char* title, const void* data, int fchar){
printf("%-4s = {", title);
if(fchar) {
const char* str = (const char*)data;
while(*str){
printf("'%s', ", str);
str += strlen(str) + 1;
}
}
else {
const wchar_t* str = (const wchar_t*)data;
while(*str) {
printf("'%S', ", str);
str += wcslen(str) + 1;
}
}
printf("}\n");
}
�
const char *str1;
char *str2;
char str3[10];
int len;
wchar_t* wstr;
lua_genpcall(L, "return {1,2,3},{4,5,6},{10,9,8,7},{11,12}",
">%+hz %+&z %*z %#lz", &str1, &len, &str2,
sizeof(str3)/sizeof(str3[0]), &str3, &wstr );
print_string_list("str1", str1, 1);
print_string_list("str2", str2, 1);
printf("len = %d\n", len);
print_string_list("str3", str3, 1);
print_string_list("wstr", wstr, 0);
free(wstr);
-->
str1 = {'1', '2', '3', }
str2 = {'4', '5', '6', }
len = 6
str3 = {'10', '9', '8', '7', }
wstr = {'11', '12', }
print_string_list 只是为了以可读的方式显示检索到的字符串列表。第一个字符串列表类型为 char*,无论是否使用 Unicode 版本,而 str2 和 str3 则不是。第一个列表在 Lua 堆栈上分配;第二个列表也是如此,此外还检索了字符串列表的长度(不包括最后一个零字节)。str3 的缓冲区在 C 堆栈上;因此,其大小作为通过 '*' 标志指示的附加参数传递。最后一个字符串列表是宽字符版本;其缓冲区使用 Lua 分配器分配,需要在使用后调用 free。您一定已经注意到了字符串和字符串列表参数之间的强烈相似性。-- PatrickRapin