<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>~iany/ Programming</title><link>https://blog.iany.me/zh/tags/programming/</link><description>Programming的最新内容 «~iany/»</description><language>zh-CN</language><managingEditor>me@iany.me (Ian Yang)</managingEditor><webMaster>me@iany.me (Ian Yang)</webMaster><copyright>CC-BY-SA 4.0</copyright><lastBuildDate>Sun, 29 Oct 2017 16:58:27 +0800</lastBuildDate><atom:link href="https://blog.iany.me/zh/tags/programming/index.xml" rel="self" type="application/rss+xml"/><item><title>Lua C API userdata 和 light userdata</title><link>https://blog.iany.me/zh/2017/10/lua-c-api-userdata/</link><pubDate>Sun, 29 Oct 2017 16:58:27 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/zh/2017/10/lua-c-api-userdata/</guid><description>&lt;p&gt;填半年前挖的坑。分享 Lua C API 中的 userdata 和 light userdata。&lt;/p&gt;
&lt;p&gt;在编程过程中，经常会需要给一块数据分配一个唯一句柄，通过句柄能够读取或者操作这块数据。原因主要有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据内聚性很强，封装在一起方便传递，减少参数数量。&lt;/li&gt;
&lt;li&gt;隐藏数据的内部结构，通过 API 提供操作接口。&lt;/li&gt;
&lt;li&gt;减少数据拷贝。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最典型的就是 C 中的指针了。但句柄并不一定就必须是指针，比如 Linux 系统中的 fd 可以当作是 IO 设备的句柄。&lt;/p&gt;
&lt;p&gt;在 Lua C API 中提供了 userdata 和 light userdata 可以让 C 返回一个句柄给 Lua，而 Lua 可以将句柄再通过在 C 中注册的方法传回 C。&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Lua Userdata" class="kg-image" loading="lazy" src="https://blog.iany.me/2025/09/lua-c-api-userdata/lua-c-api-userdata_hu_2c64654bc21f5270.png" srcset="https://blog.iany.me/2025/09/lua-c-api-userdata/lua-c-api-userdata_hu_1f5c022c2333fcb8.png 400w, https://blog.iany.me/2025/09/lua-c-api-userdata/lua-c-api-userdata_hu_2c64654bc21f5270.png 710w" sizes="(max-width: 400px) 100vw, 710px" /&gt;
&lt;figcaption &gt;Lua Userdata&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="区别"&gt;区别&lt;/h2&gt;
&lt;p&gt;Userdata 和 light userdata 的区别是 Userdata 通过 Lua API 分配一片内存，这片内存通过 Lua GC 自动回收。而 light userdata 只是一个 &lt;code&gt;void *&lt;/code&gt; 类型的值，具体这个值怎么产生，怎么回收需要自己管理。&lt;/p&gt;
&lt;p&gt;虽说叫 light userdata，但是其实 Userdata 也是非常轻量的，相比 light userdata 只是多了内存分配和 GC 时的内存回收。因为自动回收的特性，特别适合数据生命周期和 userdata 对象在 Lua 中存活周期一致的场景。&lt;/p&gt;
&lt;p&gt;而 light userdata 适合数据周期大于 userdata 对象存活周期的场景，在 Lua 中只是获得数据的引用，但是并不管理数据的生命周期。比如在 Cocos2d-X 中有自己的 GC 机制，所以应该使用 light userdata 将 node 的指针返回给 Lua。还有一种情况是句柄并不是指针，但是可以强转成 &lt;code&gt;void*&lt;/code&gt; 也是可以通过 light userdata 的，比如将 fd 封装成 light userdata，比如在没有 64 位整数的 Lua 版本但 &lt;code&gt;void*&lt;/code&gt; 是 64 位的环境下实现 64 位整数库。&lt;/p&gt;
&lt;p&gt;另一个区别是整个 Lua 虚拟机中的所有 light userdata 是共享一个元表的，而 userdata 可以单独设置。如果需要元表也必须使用 userdata。&lt;/p&gt;
&lt;h2 id="示例"&gt;示例&lt;/h2&gt;
&lt;p&gt;以下所有示例代码和编译脚本都可以在该 &lt;a href="https://doitian.coding.net/public/lua-c-api-intro/lua-c-api-intro/git/files/userdata"&gt;Git 仓库的 userdata 分支&lt;/a&gt;找到。&lt;/p&gt;
&lt;h3 id="userdata-示例"&gt;Userdata 示例&lt;/h3&gt;
&lt;p&gt;Userdata 的 API 主要是 &lt;code&gt;lua_newuserdata&lt;/code&gt; 和 &lt;code&gt;lua_touserdata&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;以实现简单的复数为例，创建时调用 &lt;code&gt;lua_newuserdata&lt;/code&gt; 分配内存并把新的 userdata 压入栈。然后通过返回的内存指针进行初始化。&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://doitian.coding.net/public/lua-c-api-intro/lua-c-api-intro/git/files/userdata/userdata.c"&gt;userdata.c&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c"&gt;struct Complex {
lua_Number real;
lua_Number imag;
};
typedef struct Complex Complex;
static int complex_new(lua_State* L) {
Complex* comp = lua_newuserdata(L, sizeof(Complex));
comp-&amp;gt;real = lua_tonumber(L, 1);
comp-&amp;gt;imag = lua_tonumber(L, 2);
return 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过 &lt;code&gt;lua_touserdata&lt;/code&gt; 获得栈上的 userdata 对应的内存指针。下面是俩个复数相加返回一个新的复数的例子&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c"&gt;static int complex_add(lua_State* L) {
Complex* a = lua_touserdata(L, 1);
Complex* b = lua_touserdata(L, 2);
Complex* comp = lua_newuserdata(L, sizeof(Complex));
comp-&amp;gt;real = a-&amp;gt;real + b-&amp;gt;real;
comp-&amp;gt;imag = a-&amp;gt;imag + b-&amp;gt;imag;
return 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="light-userdata-示例"&gt;Light userdata 示例&lt;/h3&gt;
&lt;p&gt;Light userdata 的 API 主要是 &lt;code&gt;lua_pushlightuserdata&lt;/code&gt; 和 &lt;code&gt;lua_touserdata&lt;/code&gt;。注意获取栈上的 userdata 或者 light userdata 对应的指针式相同的 API。&lt;/p&gt;
&lt;p&gt;以封装 C FILE API 为例，打开文件时将 &lt;code&gt;FILE *&lt;/code&gt; 作为 lightuser data 返回。这里因为 &lt;code&gt;FILE *&lt;/code&gt; 是由 libc 来管理生命周期的，所以不能使用 &lt;code&gt;lua_newuserdata&lt;/code&gt; 来分配内存再初始化。&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://doitian.coding.net/public/lua-c-api-intro/lua-c-api-intro/git/files/userdata/light_userdata.c"&gt;light_userdata.c&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c"&gt;static int file_open(lua_State* L) {
const char* path = lua_tostring(L, 1);
const char* mode = &amp;quot;w+&amp;quot;;
FILE* file = fopen(path, mode);
if (file == NULL) {
lua_pushstring(L, strerror(errno));
lua_error(L);
return 0;
}
lua_pushlightuserdata(L, file);
return 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了释放资源，必须提供相应的 API，在使用完毕时调用。&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c"&gt;static int file_close(lua_State* L) {
FILE* f = lua_touserdata(L, 1);
if (f != NULL) {
fclose(f);
}
return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然这个例子中，更安全的方法是用 userdata，把 FILE* 设置在 userdata 分配的内存中，并通过元表设置在 GC 是自动调用 &lt;code&gt;fclose&lt;/code&gt;。&lt;/p&gt;</description><category domain="https://blog.iany.me/zh/">~iany/</category><category domain="https://blog.iany.me/zh/series/lua-c-api/">Lua C API</category><category domain="https://blog.iany.me/zh/tags/c/">C</category><category domain="https://blog.iany.me/zh/tags/lua/">Lua</category><category domain="https://blog.iany.me/zh/tags/programming/">Programming</category></item><item><title>Lua C API 简介</title><link>https://blog.iany.me/zh/2017/02/lua-c-api-intro/</link><pubDate>Sat, 18 Feb 2017 05:35:59 +0800</pubDate><author>me@iany.me (Ian Yang)</author><guid>https://blog.iany.me/zh/2017/02/lua-c-api-intro/</guid><description>&lt;p&gt;公司主要用 skynet 和 cocos2d-x Lua 来开发游戏。两者都采用了嵌入 Lua 来开发。因为性能，要和原生代码交互等原因，需要在 Lua 和其它语言之间进行交互。最近做了挺多这样的工作，积累了一些心得，会陆续总结分享出来。&lt;/p&gt;
&lt;p&gt;这一篇是 Lua C API 的简单介绍。&lt;/p&gt;
&lt;p&gt;使用 Lua C API 有两个场景。一是把 Lua 嵌入在其它语言中，只要能链接 C 库并调用 C 方法都可以使用。另一种是开发 Lua C 模块。&lt;/p&gt;
&lt;h2 id="lua-c-api"&gt;Lua C API&lt;/h2&gt;
&lt;p&gt;先介绍如何在 C 中嵌入 Lua。下面的例子中初始化了 Lua 虚拟机，并执行了一段 Lua 代码。&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://doitian.coding.net/public/lua-c-api-intro/lua-c-api-intro/git/files/userdata/lua-c-api-template.c"&gt;lua-c-api-template.c&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c"&gt;#include &amp;lt;lua.h&amp;gt;
#include &amp;lt;lauxlib.h&amp;gt;
#include &amp;lt;lualib.h&amp;gt;
#define QUOTE(...) #__VA_ARGS__
static const char *lua_code = QUOTE(
print(&amp;quot;Hello, Lua C API&amp;quot;)
);
int main(int argc, char* argv[]) {
int status = 0;
lua_State *L = luaL_newstate();
luaL_openlibs(L);
status = luaL_loadstring(L, lua_code) || lua_pcall(L, 0, 0, 0);
if (status) {
fprintf(stderr, &amp;quot;%s&amp;quot;, lua_tostring(L, -1));
lua_pop(L, 1);
}
lua_close(L);
return status;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面代码要求使用至少 Lua 5.1，否则 &lt;code&gt;luaL_newstate&lt;/code&gt; 需要改成 &lt;code&gt;lua_open&lt;/code&gt;, &lt;code&gt;luaL_openlibs&lt;/code&gt; 要拆成单独的各个标准库加载方法比如 &lt;code&gt;luaopen_io&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;编译需要引用 Lua 头文件并链接 Lua 库。本文所有示例和编译脚本都放在 &lt;a href="https://doitian.coding.net/public/lua-c-api-intro/lua-c-api-intro/git/files"&gt;这个 Git 仓库&lt;/a&gt; 中。&lt;/p&gt;
&lt;p&gt;Lua C API 的核心就是操作栈，所有的操作都是通过栈实现的。访问栈可以用正数或者负数。每次函数调用会标记当前栈顶的位置，之后压入的元素位置从 1 开始。下面会提到函数的参数会首先压入栈，所以正数 i 引用的栈位置就是第 i 个参数。负数就是从栈顶开始数的位置，-1 就是栈顶元素，-2 就是栈顶下面一个元素，依此类推。&lt;/p&gt;
&lt;p&gt;使用栈要注意，谁负责压入就要负责弹出，很多 Lua C API 出现错误都是栈操作不当引起的。&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Lua 栈" class="kg-image" loading="lazy" src="https://blog.iany.me/2025/09/lua-c-api-intro/lua_stack_hu_c87fb585503730b7.png" srcset="https://blog.iany.me/2025/09/lua-c-api-intro/lua_stack_hu_8e82cace9613e022.png 400w, https://blog.iany.me/2025/09/lua-c-api-intro/lua_stack_hu_75787f2278fb9652.png 800w, https://blog.iany.me/2025/09/lua-c-api-intro/lua_stack_hu_c87fb585503730b7.png 843w" sizes="(max-width: 800px) 100vw, 843px" /&gt;
&lt;figcaption &gt;Lua 栈&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;查看 C API 的文档重要一部分就是查看其对栈操作的约定。 以设置全局变量的 API &lt;code&gt;lua_setglobal&lt;/code&gt; 为例&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;void lua_setglobal (lua_State *L, const char *name);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Pops a value from the stack and sets it as the new value of global name.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;执行该方法需要把全局变量的值压入栈，调用成功后会被自动弹出。下面是使用的例子，注释中是等价的 Lua 代码。完整代码点击文件名查看。&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://doitian.coding.net/public/lua-c-api-intro/lua-c-api-intro/git/files/master/globals.c"&gt;globals.c&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c"&gt;// g_int = 10
lua_pushinteger(L, 10);
lua_setglobal(L, &amp;quot;g_int&amp;quot;);
// g_number = 3.14
lua_pushnumber(L, (lua_Number)3.14);
lua_setglobal(L, &amp;quot;g_number&amp;quot;);
// g_true = true
// g_false = false
lua_pushboolean(L, 1);
lua_setglobal(L, &amp;quot;g_true&amp;quot;);
lua_pushboolean(L, 0);
lua_setglobal(L, &amp;quot;g_false&amp;quot;);
// g_string = &amp;quot;global set from C API&amp;quot;
lua_pushstring(L, &amp;quot;global set from C API&amp;quot;);
lua_setglobal(L, &amp;quot;g_string&amp;quot;);
// g_table = { name = &amp;quot;table set from C API&amp;quot; }
lua_newtable(L);
lua_pushstring(L, &amp;quot;table set from C API&amp;quot;);
lua_setfield(L, -2, &amp;quot;name&amp;quot;);
lua_setglobal(L, &amp;quot;g_table&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以最复杂的 &lt;code&gt;g_table&lt;/code&gt; 为例说明栈的变化。&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Lua 栈变化示例" class="kg-image" loading="lazy" src="https://blog.iany.me/2025/09/lua-c-api-intro/lua_setglobal_hu_b61a27160d652929.png" srcset="https://blog.iany.me/2025/09/lua-c-api-intro/lua_setglobal_hu_de2110dfbb0e0301.png 400w, https://blog.iany.me/2025/09/lua-c-api-intro/lua_setglobal_hu_4cc4f31f5a19cbd5.png 800w, https://blog.iany.me/2025/09/lua-c-api-intro/lua_setglobal_hu_b61a27160d652929.png 1144w" sizes="(max-width: 800px) 100vw, 1144px" /&gt;
&lt;figcaption &gt;Lua 栈变化示例&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="function"&gt;Function&lt;/h2&gt;
&lt;h3 id="c-中调用-lua-方法"&gt;C 中调用 Lua 方法&lt;/h3&gt;
&lt;p&gt;上面的例子用到了 integer, float, boolean, string, table 等数据类型。Lua 和 C 之间还可以通过函数互调来共享逻辑。&lt;/p&gt;
&lt;p&gt;C 中调用 Lua 方法或者其它 C 模块定义的方法可以使用 &lt;code&gt;lua_call&lt;/code&gt; 或者 &lt;code&gt;lua_pcall&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;调用的栈约定是一致的，先把要调用的函数入栈，然后按顺序从第 1 个参数开始压入栈，有多个参数的话这个时候栈顶应该是最后一个参数。&lt;/p&gt;
&lt;p&gt;然后使用 &lt;code&gt;lua_call&lt;/code&gt; 或者 &lt;code&gt;lua_pcall&lt;/code&gt;。需要手动指定参数的个数和要保留的返回结果的个数。和在 Lua 中方法调用相同，指定的个数小于实际返回结果个数的话，多余的被丢弃，指定的个数多于实际个数的话，多出来的赋值 nil。&lt;/p&gt;
&lt;p&gt;调用的函数没有出现错误的话，结果是一致的，函数和所有参数被弹出栈，指定数量的返回结果被依次压入栈，也就是最后一个返回结果会在栈顶。&lt;/p&gt;
&lt;p&gt;如果出错了，&lt;code&gt;lua_call&lt;/code&gt; 行为和 Lua 中直接调用一个方法然后出错一致，会直接通过 &lt;code&gt;longjump&lt;/code&gt; 直接跳到被 &lt;code&gt;pcall/xpcall&lt;/code&gt; 被捕获的地方。&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;lua_pcall&lt;/code&gt; 和 &lt;code&gt;xpcall&lt;/code&gt; 一致&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果最后一个参数不为 0，则会调用对应栈位置的函数来处理错误&lt;/li&gt;
&lt;li&gt;把函数和所有参数弹出栈，再把错误压入栈。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;举例说明，首先定义一个方法方便演示&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-lua"&gt;function identity(...)
return table.unpack({...})
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下面是 &lt;code&gt;lua_call&lt;/code&gt; 和 &lt;code&gt;lua_pcall&lt;/code&gt; 的例子。&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://doitian.coding.net/public/lua-c-api-intro/lua-c-api-intro/git/files/master/call-lua-function.c"&gt;call-lua-function.c&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c"&gt;lua_getglobal(L, &amp;quot;identity&amp;quot;); // identity
lua_pushinteger(L, 1); // identity, 1
lua_call(L, 1, 2);
// r1, r2 = identity(1)
// stack: 1, nil
printf(
&amp;quot;r1, r2 = %d, %s\n&amp;quot;,
(int)lua_tointeger(L, -2),
lua_isnil(L, -1) ? &amp;quot;nil&amp;quot; : &amp;quot;not nil&amp;quot;
);
lua_pop(L, 2);
lua_getglobal(L, &amp;quot;identity&amp;quot;); // identity
lua_pushinteger(L, 1); // identity, 1
lua_pushinteger(L, 2); // identity, 1, 2
status = lua_pcall(L, 2, 1, 0);
if (status) {
fprintf(stderr, &amp;quot;%s&amp;quot;, lua_tostring(L, -1));
lua_pop(L, 1);
} else {
printf(&amp;quot;r1 = %d\n&amp;quot;, (int)lua_tointeger(L, -1));
lua_pop(L, 1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;lua_call&lt;/code&gt; 调用的方法实际返回一个结果，但是声明了要两个返回结果，所以第二个是 nil。而 &lt;code&gt;lua_pcall&lt;/code&gt; 通过 C 返回值来判断是否有错误。这两个方法都可以通过传 &lt;code&gt;LUA_MULTRET&lt;/code&gt; 作为返回结果的数量，这样所有实际的返回值都会留在栈上，调用者需要自己对比栈上元素数量差来判断有多少个实际的返回值。&lt;/p&gt;
&lt;h3 id="lua-c-方法"&gt;Lua C 方法&lt;/h3&gt;
&lt;p&gt;通过 &lt;code&gt;lua_pushcfunction&lt;/code&gt; 可以把 C 方法压入栈中，在使用上和 Lua 实现的方法完全一样。&lt;/p&gt;
&lt;p&gt;一个 Lua 的 C 方法定义如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef int (*lua_CFunction) (lua_State *L);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;API &lt;code&gt;lua_pushcfunction&lt;/code&gt; 的使用本身比较简单，关键是如果实现 C 方法。在进入 C 方法后，栈的状态是有 n 个元素在栈顶，位置从 1 到 n 分别对应第 1 到第 n 个参数。这时通过 &lt;code&gt;lua_gettop&lt;/code&gt; 可以获得参数的数量。&lt;/p&gt;
&lt;p&gt;然后可以在 C 方法里做任何事情，在方法返回前遵循约定操作栈：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;把返回的 Lua value 依次压入栈，第一个返回结果最先入栈。&lt;/li&gt;
&lt;li&gt;将结果个数作为 C 方法的返回值返回，0 个返回结果就返回 0&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Lua C 方法" class="kg-image" loading="lazy" src="https://blog.iany.me/2025/09/lua-c-api-intro/lua_cfunction_hu_e1dfe2e0b690aa44.png" srcset="https://blog.iany.me/2025/09/lua-c-api-intro/lua_cfunction_hu_9c8596aca4952f51.png 400w, https://blog.iany.me/2025/09/lua-c-api-intro/lua_cfunction_hu_d2ff7bdc40b3f7be.png 800w, https://blog.iany.me/2025/09/lua-c-api-intro/lua_cfunction_hu_f5356f30d4674b3e.png 1200w, https://blog.iany.me/2025/09/lua-c-api-intro/lua_cfunction_hu_e1dfe2e0b690aa44.png 1314w" sizes="(max-width: 1200px) 100vw, 1314px" /&gt;
&lt;figcaption &gt;Lua C 方法&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;下面是一个用 C 实现的 &lt;code&gt;string_split&lt;/code&gt; 方法示例，其中 &lt;code&gt;string_split&lt;/code&gt; 的具体实现可以点击文件名查看完整文件。&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://doitian.coding.net/public/lua-c-api-intro/lua-c-api-intro/git/files/master/cfunction.c"&gt;cfunction.c&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c"&gt;/***
* Split 字符串.
*
* @param str 字符串
* @param sep 分隔符，只会使用第一个字符
* @param[opt] count 最多进行 count - 1 次分隔，默认不限制。小于 1 的值都当成 1
* @return ... 以多值的方式返回各部分
*/
static int l_string_split(lua_State* L) {
size_t len = 0;
const char* str = NULL;
const char* sep = NULL;
lua_Integer count = LUA_MAXINTEGER;
int argc = lua_gettop(L);
if (argc == 0) {
return 0;
}
if (argc &amp;gt; 1) {
sep = lua_tostring(L, 2);
}
if (argc &amp;gt; 2) {
count = lua_tointeger(L, 3);
}
if (sep != NULL &amp;amp;&amp;amp; count &amp;gt; 1) {
str = lua_tolstring(L, 1, &amp;amp;len);
if (str != NULL) {
return string_split(L, str, len, *sep, count);
}
}
// just returns str
lua_pushvalue(L, 1);
return 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="lua-c-闭包方法"&gt;Lua C 闭包方法&lt;/h3&gt;
&lt;p&gt;API &lt;code&gt;lua_pushcclosure&lt;/code&gt; 同样将一个 C Function 压入栈，不过可以关联一些在多次调用间共享的变量，也就是 Lua 中的闭包。而这些被绑定的变量在 Lua 中被称作 upvalue。&lt;/p&gt;
&lt;p&gt;在 C 方法中想要绑定 upvalue 必须把它们全压入栈，API &lt;code&gt;lua_pushcclosure&lt;/code&gt; 中指定 upvalue 的数量和要绑定 C 方法，并把完成绑定的方法压入栈。&lt;/p&gt;
&lt;p&gt;在 C 方法中要访问这些绑定的 upvalue 要借助 &lt;code&gt;pseudo-index&lt;/code&gt;。在 Lua 会分配一些数字为 &lt;code&gt;pseudo-index&lt;/code&gt;。使用这些数字作为参数，不是根据位置是查找栈上元素，而是访问约定的特殊位置的元素。比如 &lt;code&gt;LUA_REGISTRYINDEX&lt;/code&gt; 就是一个 &lt;code&gt;pseudo-index&lt;/code&gt;，它指向一个全局的 table。像 Lua 里的全局变量实际都是存放在 &lt;code&gt;LUA_REGISTRYINDEX&lt;/code&gt; 里的一个子 table 中的。&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;lua_upvalueindex&lt;/code&gt; 会返回 upvalue 的 &lt;code&gt;pseudo-index&lt;/code&gt;。从 1 开始按照入栈顺序编号。通过 &lt;code&gt;lua_to*&lt;/code&gt; 和 &lt;code&gt;lua_replace&lt;/code&gt; 就可以读取和修改这些在多次调用间共享的变量了。&lt;/p&gt;
&lt;figure class="kg-image-card"&gt;
&lt;img alt="Lua C 闭包方法" class="kg-image" loading="lazy" src="https://blog.iany.me/2025/09/lua-c-api-intro/lua_cclosure_hu_e2949988dfdb69ca.png" srcset="https://blog.iany.me/2025/09/lua-c-api-intro/lua_cclosure_hu_a4c2f540b7b0f49d.png 400w, https://blog.iany.me/2025/09/lua-c-api-intro/lua_cclosure_hu_16d1fe9bbef819ad.png 800w, https://blog.iany.me/2025/09/lua-c-api-intro/lua_cclosure_hu_f26015713de1c52c.png 1200w, https://blog.iany.me/2025/09/lua-c-api-intro/lua_cclosure_hu_4c7c83294d21d7c5.png 1600w, https://blog.iany.me/2025/09/lua-c-api-intro/lua_cclosure_hu_e2949988dfdb69ca.png 1674w" sizes="(max-width: 1600px) 100vw, 1674px" /&gt;
&lt;figcaption &gt;Lua C 闭包方法&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;下面是一个用 C 闭包实现的随机数发生器的例子。&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://doitian.coding.net/public/lua-c-api-intro/lua-c-api-intro/git/files/master/cclosure.c"&gt;cclosure.c&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c"&gt;// 算法提取自 POSIX.1-2001 rand()实现
static int l_random_next(lua_State* L) {
uint32_t seed = (uint32_t)lua_tointeger(L, lua_upvalueindex(1));
int32_t next_number;
seed = seed * (uint32_t)1103515245 + (uint32_t)12345;
next_number = (int32_t)((uint32_t)seed / (int32_t)65536) % (int32_t)32768;
// update upvalue
lua_pushinteger(L, seed);
lua_replace(L, lua_upvalueindex(1));
lua_pushinteger(L, next_number);
return 1;
}
/***
* 给 Lua 返回一个用来生成随机序列的函数.
*
* @function random_generator
* @tparam integer seed 随机种子
* @treturn function generator 每次调用返回一个 `[0, 32768)` 区间内的随机数
*/
static int l_random_generator(lua_State* L) {
lua_Integer seed = (lua_Integer)lua_tointeger(L, 1);
lua_pushinteger(L, seed);
lua_pushcclosure(L, l_random_next, 1);
return 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="c-模块"&gt;C 模块&lt;/h2&gt;
&lt;p&gt;上面都是以嵌入 Lua 直接操作栈来进行交互，Lua 中也可以把 C 编译成动态链接库使用 &lt;code&gt;require&lt;/code&gt; 来加载。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require &amp;quot;cjson&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面这行 Lua 代码会去 &lt;code&gt;LUA_CPATH&lt;/code&gt; 指定的目录下去查找名字是 cjson (cjson.so 或者 cjson.dll 等等) 的动态链接库。如果找到了，会动态加载并将其中的 &lt;code&gt;luaopen_cjson&lt;/code&gt; 作为 Lua C Function 调用。调用返回的结果就是上面这行 &lt;code&gt;require&lt;/code&gt; 的返回结果。当然已经加载过的模块是不会重复调用的。&lt;/p&gt;
&lt;p&gt;有时候需要把大模块分拆，如果都分开编译成不同的动态链接库维护起来会很麻烦。所以不同于普通的 Lua 模块查找逻辑，当碰到含点的模块名时会对 C 模块做特殊处理。&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-lua"&gt;require &amp;quot;cjson.safe&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的代码如果没有找到 &lt;code&gt;cjson/safe.so&lt;/code&gt; 的话，会再去查找 &lt;code&gt;cjson.so&lt;/code&gt;，这样 &lt;code&gt;cjson&lt;/code&gt;, &lt;code&gt;cjson.safe&lt;/code&gt;, &lt;code&gt;cjson.another.module&lt;/code&gt; 都可以共享一个动态链接库。对应的入口函数就是把点全部替换成下划线，然后在前面加上 &lt;code&gt;luaopen_&lt;/code&gt;，比如 &lt;code&gt;luaopen_cjson_safe&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;下面把之前的 &lt;code&gt;string_split&lt;/code&gt; 变成模块作为示例。这里只贴出了入口方法，完整文件点击文件名查看。其中的 &lt;code&gt;l_string_split&lt;/code&gt; 就是上面定义的 C Function。注意入口方法必须 export 才能被动态加载，DLL 应该用 &lt;code&gt;__declspec(dllexport)&lt;/code&gt;，so 应该用 &lt;code&gt;extern&lt;/code&gt;。下面的例子定义了一个宏 &lt;code&gt;STRING_SPLIT_EXPORT&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;※ &lt;a href="https://doitian.coding.net/public/lua-c-api-intro/lua-c-api-intro/git/files/master/csrc/string_split.c"&gt;string_split.c&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c"&gt;#ifdef _MSC_VER
#define STRING_SPLIT_EXPORT __declspec(dllexport)
#else
#define STRING_SPLIT_EXPORT extern
#endif
STRING_SPLIT_EXPORT int luaopen_string_split(lua_State *L) {
lua_createtable(L, 0, 1);
lua_pushcfunction(L, l_string_split);
lua_setfield(L, -2, &amp;quot;string_split&amp;quot;);
/** 方法很多的时候可以用 luaL_newlib 来注册
luaL_Reg l[] = {
{ &amp;quot;string_split&amp;quot;, l_string_split },
{ NULL, NULL }
};
luaL_newlib(L, l);
*/
return 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试的话，在编译出来的动态链接库所在目录执行正确版本的 Lua：&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-lua"&gt;local string_split = require &amp;quot;string_split&amp;quot;.string_split
local parts = {string_split(&amp;quot;Hello, Lua C API&amp;quot;, &amp;quot; &amp;quot;, 2)}
print(#parts) --&amp;gt; 2
for i = 1, #parts do
print(parts[i])
end
--&amp;gt; Hello,
--&amp;gt; Lua C API
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="通过-preload-注册-c-模块"&gt;通过 preload 注册 C 模块&lt;/h2&gt;
&lt;p&gt;像在 cocos2d-x 中，单独编译动态链接库并不是个很方便的选择，因为必须为所有可能用到的平台都生成对应的动态库，这种情况下可以把 C 代码和项目一起编译，使用 Lua C API 来注册。&lt;/p&gt;
&lt;p&gt;一般会用到两种方法。&lt;/p&gt;
&lt;p&gt;一种是 cocos2d-x Lua 中采用的直接把模块应该返回的 table 注册成全局变量，使用时不需要 &lt;code&gt;require&lt;/code&gt; 直接用全局变量就可以了，所以有一堆的全局变量，&lt;code&gt;cc&lt;/code&gt;, &lt;code&gt;display&lt;/code&gt; 等等。&lt;/p&gt;
&lt;p&gt;滥用全局变量会有很多问题，luacheck 静态检查工具需要手动加入例外像，造成 &lt;code&gt;_G&lt;/code&gt; 表查找变慢等等。其实 Lua 提供了很好的解决方案，一个是 &lt;code&gt;package.loaded&lt;/code&gt;，一个是 &lt;code&gt;package.preload&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;表 &lt;code&gt;package.loaded&lt;/code&gt; 是所有通过 &lt;code&gt;require&lt;/code&gt; 加载过的模块的返回值。重复 &lt;code&gt;require&lt;/code&gt; 会直接返回 &lt;code&gt;package.loaded&lt;/code&gt; 中以模块名为 key 的值。如果使用 C API 直接把模块的返回值以模块名为 key 添加到这个表中，那么 &lt;code&gt;require&lt;/code&gt; 就会返回预先填入的值。不过这种方法有个问题就是一般在开发环境中会实现代码重新加载的功能，这个功能一般就是清空 &lt;code&gt;package.loaded&lt;/code&gt;，这样之后的 &lt;code&gt;require&lt;/code&gt; 就会重新从文件中读取。如果有模块是直接在这里添加的，需要在清空后再次添加。&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;package.preload&lt;/code&gt; 是更加推荐的做法。这个表也是使用模块名为 key，不过对应的值必须是 function。当 &lt;code&gt;package.loaded&lt;/code&gt; 查找失败后，&lt;code&gt;require&lt;/code&gt; 会优先查找 &lt;code&gt;package.preload&lt;/code&gt; 这个表，如果找到了对应的 key，则会调用其对应的方法，而方法的返回值就作为 &lt;code&gt;require&lt;/code&gt; 的返回值，并同时插入到 &lt;code&gt;package.loaded&lt;/code&gt; 中。&lt;/p&gt;
&lt;p&gt;下面的例子就是通过 &lt;code&gt;preload&lt;/code&gt; 来注册 &lt;code&gt;string_split&lt;/code&gt; 模块，这样 &lt;code&gt;string_split&lt;/code&gt; 就可以和主程序一起编译了。&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-c"&gt;lua_getglobal(L, &amp;quot;package&amp;quot;);
lua_getfield(L, -1, &amp;quot;preload&amp;quot;);
lua_pushcfunction(L, luaopen_string_split);
lua_setfield(L, -2, &amp;quot;string_split&amp;quot;);
lua_pop(L, 2);
&lt;/code&gt;&lt;/pre&gt;</description><category domain="https://blog.iany.me/zh/">~iany/</category><category domain="https://blog.iany.me/zh/series/lua-c-api/">Lua C API</category><category domain="https://blog.iany.me/zh/tags/c/">C</category><category domain="https://blog.iany.me/zh/tags/lua/">Lua</category><category domain="https://blog.iany.me/zh/tags/programming/">Programming</category></item></channel></rss>