TL;DR: 该程序正在使用多个版本的 C 运行时库。不要那样做。它总是会导致其他正确代码出现神秘症状。
背景
从表面上看,您提供的代码应该可以工作。而且,如果在这里小心建造和链接,我可以让它工作。作为参考,我在 Win7 Pro 上使用 MingW GCC 4.7.2 构建 32 位 Windows。但我相信任何针对 Windows 的编译器都可能出现根本问题。
我将介绍查找此错误的过程,希望对了解我如何解决此问题有所帮助。但是,如果您不耐烦,请跳到最后,然后回到这里看看我是如何到达那里的。
可测试代码
首先,我将您的代码片段包装在足够的样板中以使其完全编译和运行:
#include <lua.h>
#include <lauxlib.h>
#include <stdio.h>
#include <windows.h>
int main(int argc, char **argv) {
AllocConsole();
FILE *fp = freopen("CONOUT$", "w", stdout);
lua_State *L = luaL_newstate();
luaL_openlibs(L);
if(luaL_dostring(L,
"print 'print works!'\n"
"io.write 'io.write works'"
))
{
printf("%s\n", lua_tostring(L, -1));
}
Sleep(5000); // give me 5 seconds to read the console
}
使用 GCC 编译
我在 Windows 上尽可能简单地编译和链接,这还不错,因为我安装了一个用于 Windows 的 Lua 副本,恰好留下环境变量 LUA_DEV 指向它的安装:
gcc -o q15787892 q15787892.c -mwindows -I"%LUA_DEV%\include" "%LUA_DEV%\lua5.1.dll"
-mwindows 标志告诉 GCC(特别是链接器 ld)将可执行文件标记为完整的 Windows GUI 程序。如果没有该标志,您将获得一个已经分配了控制台的控制台程序,并且 AllocConsole() 将简单地为持有命令提示符的程序提供句柄。
结果
有趣的是,对print() 和io.write() 的调用都没有产生输出。我在 Lua 文本中引入了一个语法错误,并注意到它确实输出到了控制台,表明 stdout 确实被正确重定向了。
我在调用freopen() 之前添加了FILE *old=stdout;,之后添加了printf("%p %p %p", fp, stdout, oldstdout);。所有三个指针完全相等,表明freopen() 没有做任何不寻常的事情。
查看 Lua 5.1 实现 print() 函数的源代码,我们发现它只是调用了 fputs(s,stdout)。
那么从main() 调用printf() 怎么可能有效,但使用stdout 的类似调用会失败?
解决方案
如果main() 中的stdout 与luaB_print() 中的stdout 不同,这是可能的。
但是两者都是全局变量,链接器应该使它们相同,对吗?
嗯,不一定。全局变量 stdout 是 C 运行时库的一部分。如果 Lua 核心链接到与程序不同的 C 运行时 DLL,那么 Lua 核心和程序实际上可能引用了名为 stdout 的不同变量。
对Dependency Walker 的快速检查显示我的测试可执行文件链接到 MSVCRT.DLL(MinGW 首选的 C 运行时),但来自 Lua for Windows 的 lua5.1.dll 链接到 MSVCR80.DLL(来自Visual Studio 2005)。
这个问题很容易解决。我将测试用例更改为链接到与 MSVCRT.DLL 链接的 Lua 构建,现在原始代码按预期工作。这是新的构建步骤,现在可以在 BAT 文件中找到,并假设 lua5_1-4_Win32_dll6_lib 包含正确构建的 Lua 核心:
setlocal
set LUADIR="lua5_1_4_Win32_dll6_lib"
gcc -o q15787892 q15787892.c -mwindows -I"%LUADIR%\include" "%LUADIR%\lua5.1.dll"
if not exist lua5.1.dll copy %LUADIR%\lua5.1.dll .