如何向Lua脚本传递Unicode UTF-8格式的命令行参数?
Windows下Lua命令行参数UTF-8乱码解决方案
核心原因
标准Lua(5.4.8)的启动入口lua.c使用了ANSI版本的main函数,在Windows环境下,系统会将UTF-16编码的命令行参数转成系统默认ANSI代码页的字符串,非ASCII字符在此过程中会被损坏,导致你看到的????乱码。修改控制台代码页(chcp 65001)无法解决这个问题——因为数据在进入Lua之前就已经被转码破坏了。
简便解决方案:LuaJIT + FFI 直接调用Windows API
如果可以切换到LuaJIT,这是最不需要C语言开发经验的方案:利用LuaJIT的FFI特性直接调用Windows Unicode API,获取原始UTF-16命令行参数,再转换为UTF-8。
代码示例
local ffi = require("ffi") -- 声明需要调用的Windows API ffi.cdef[[ typedef wchar_t WCHAR; WCHAR* GetCommandLineW(void); int CommandLineToArgvW(WCHAR* lpCmdLine, int* pNumArgs); void LocalFree(void* hMem); int WideCharToMultiByte(unsigned int CodePage, unsigned long dwFlags, const WCHAR* lpWideCharStr, int cchWideChar, char* lpMultiByteStr, int cbMultiByte, const char* lpDefaultChar, int* lpUsedDefaultChar); ]] local kernel32 = ffi.load("kernel32") local crt = ffi.C -- 获取UTF-8编码的命令行参数列表 local function get_utf8_argv() local cmdline = kernel32.GetCommandLineW() local argc = ffi.new("int[1]") local argv = kernel32.CommandLineToArgvW(cmdline, argc) if argv == nil then return {} end local args = {} for i = 0, argc[0]-1 do -- 将UTF-16转UTF-8 local utf16_str = argv[i] local utf16_len = crt.wcslen(utf16_str) -- 先计算所需UTF-8缓冲区大小 local utf8_len = crt.WideCharToMultiByte(65001, 0, utf16_str, utf16_len, nil, 0, nil, nil) local utf8_buf = ffi.new("char[?]", utf8_len + 1) crt.WideCharToMultiByte(65001, 0, utf16_str, utf16_len, utf8_buf, utf8_len, nil, nil) utf8_buf[utf8_len] = 0 -- 确保字符串以NULL结尾 table.insert(args, ffi.string(utf8_buf)) end kernel32.LocalFree(argv) table.remove(args, 1) -- 移除第一个元素(程序路径),和原生arg表对齐 return args end -- 测试使用 local utf8_args = get_utf8_argv() if utf8_args[1] then io.open("test.txt", "wb"):write(utf8_args[1]) end
使用方法
- 下载并使用LuaJIT替换标准Lua执行环境;
- 运行上述脚本,传入Unicode参数即可正确输出UTF-8内容。
标准Lua下的替代方案
如果必须使用标准Lua(非LuaJIT),可以选择:
- 使用预编译扩展库:比如
winapi库(提供了访问Windows Unicode API的Lua接口),直接调用其获取命令行参数的方法; - 轻量DLL实现:编写一个简单的C DLL,封装
GetCommandLineW和CommandLineToArgvW逻辑,将UTF-16参数转成UTF-8后暴露给Lua。以下是极简DLL代码示例(需用MinGW或MSVC编译):
#include <lua.h> #include <lauxlib.h> #include <windows.h> static int l_get_utf8_argv(lua_State *L) { LPWSTR cmdline = GetCommandLineW(); int argc; LPWSTR *argv = CommandLineToArgvW(cmdline, &argc); if (!argv) return 0; lua_createtable(L, argc-1, 0); for (int i = 1; i < argc; i++) { int utf8_len = WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL); char *utf8_buf = (char*)malloc(utf8_len); WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, utf8_buf, utf8_len, NULL, NULL); lua_pushstring(L, utf8_buf); lua_rawseti(L, -2, i); free(utf8_buf); } LocalFree(argv); return 1; } int __declspec(dllexport) luaopen_winargv(lua_State *L) { lua_register(L, "get_utf8_argv", l_get_utf8_argv); return 0; }
编译后在Lua中加载DLL即可调用get_utf8_argv()获取正确参数。
总结
- 标准Lua因使用ANSI API导致命令行Unicode参数丢失,必须通过Unicode API直接获取原始数据;
- LuaJIT + FFI是零编译成本的最优方案;
- 标准Lua下可借助预编译扩展或简单DLL解决问题。
内容的提问来源于stack exchange,提问作者Badr Elmers
相关产品推荐
相关产品推荐

