• About
    • Resume
A Game Developer also plays some guitar

Category Archives: Ok Computer

This is only about computer, computer science.

Lua相关问题整理(4) – 让Lua的eval函数支持赋值语句

November 28, 2010 12:22 pm / Leave a Comment / Benny Chen

上一篇文章提到了在Lua中实现类似于JavaScript中的eval函数,遗憾是该eval函数不支持赋值语句,原因是Lua的赋值运算符是不支持返回值。所以如果要让该eval函数也支持赋值语句,就需要一个额外的工作,让它鉴别一个语句是不是赋值语句,如果是,则return的是被赋值后变量的值。为此,我写了一个isAssignmentExpression函数,比较粗糙,不过够用了,基本思想是检测语句中第一个出现的”=“操作符,且该”=“不能在一对引号当中。

-- Lua code
function isAssignmentExpression( str )
	local i = 1;
	local curChar;
	local quotesType = "none" -- none, single or double
	local isEscaping = false

	curChar = string.sub( str, 1, 1 )
	while ( curChar ~= "" ) do
		if ( curChar == "'" and
			 isEscaping == false and
			 quotesType ~= "double" )
		then
			if ( quotesType == "single" )
			then quotesType = "none"
			elseif ( quotesType == "none" )
			then quotesType = "single"
			end
		end

		if ( curChar == """ and
			 isEscaping == false and
			 quotesType ~= "single" )
		then
			if ( quotesType == "double" )
			then quotesType = "none"
			elseif ( quotesType == "none" )
			then quotesType = "double"
			end
		end

		if ( curChar == "\" and isEscaping == false )
		then isEscaping = true
		else isEscaping = false
		end

		if ( curChar == "=" and quotesType == "none" )
		then
			if ( string.sub( str, i+1, i+1 ) ~= "=" )
			then
				return true, string.sub( str, 1, i - 1 )
			else
				return false
			end
		end

		i = i + 1
		curChar = string.sub( str, i, i )
	end

	return false
end

function eval( str )
	local bAssign
	local var
	bAssign, var = isAssignmentExpression( str )
	if ( bAssign )
	then
		print( "Assignment, var=" .. var )
		loadstring( str )()
		return loadstring( "return " .. var )()
	else
		return loadstring( "return " .. str )()
	end
end

-- 以下是一组测试
print( eval( "3+4" ) )
-- 7
function Multiply( a, b )
	return a*b
end
print( eval( "Multiply( 3, 4 )" ) ) 
-- 12
print( eval( "i" ) ) 
-- nil
print( eval( "i = 1" ) ) 
-- Assignment, var=i    
-- 1
print( eval( "i = i + 1" ) ) 
-- Assignment, var=i    
-- 2
print( eval( "i" ) ) 
-- 2
print( eval( "i+1" ) ) 
-- 3
print( eval( "i, j = 4, 5" ) ) 
-- Assignment, var=i, j    
-- 4	   5
print( eval( "i = {}" ) ) 
-- Assignment, var=i    
-- table: 003CD818
print( eval( "i[ "0" ] = 0" ) ) 
-- Assignment, var=i[ "0" ]     
-- 0
print( eval( "i[ "\"0=" ] = 1" ) ) 
-- Assignment, var=i[ ""0=" ]     
-- 1
print( eval( "i.name="hello"" ) ) 
-- Assignment, var=i.name     
-- hello
print( eval( "i[0], i.name = 4" ) ) 
-- Assignment, var=i[0], i.name     
-- 4	  nil
print( eval( "i == 10" ) ) 
-- false
Posted in: Game Programming, Lua / Tagged: eval, lua, 赋值语句

Lua相关问题整理(3)

November 27, 2010 5:04 pm / Leave a Comment / Benny Chen
  1. 在注册给Lua的C函数中为Lua提供默认参数

    使用luaL_optstring, luaL_optnumber, luaL_optinteger等Lua API,如下示例,函数有一个默认字符串参数,默认值为””,这样在Lua中调用whatever的时候,whatever()或者whatever( “whatever”)均可。(Oh…whatever…随便…都行…)

    // C code
    int Whatever( lua_State *L )
    {
    	string str = luaL_optstring( L, 1, "" );
    	//...omitted code...
    }
    lua_register( L, "whatever", Whatever );
    
  2. 建立Lua字符串到C enum的映射

  3. 使用luaL_checkoption这个Lua API,它可以把从Lua传来的string转换为相应的C string array中的index,从而可以建立Lua字符串和C enum的映射。以下是个简单的示例:

    // C code
    enum PlayerType
    {
    	PLAYER_TYPE_UNDEFINED = -1,
    	PLAYER_TYPE_KING = 0, // 主公
    	PLAYER_TYPE_INSURGENT, // 反贼
    	PLAYER_TYPE_LOYAL, // 忠臣
    	PLAYER_TYPE_TREACHEROUS, //内奸-_^
    	NUM_PLAYER_TYPE // just a sentinel
    };
    
    const char * const PlayerTypeList[NUM_PLAYER_TYPE + 1] =
    {
    	"KING",
    	"INSURGENT",
    	"LOYAL",
    	"TREACHEROUS",
    	NULL
    };
    
    static int testPlayerType( lua_State *L )
    {
    	PlayerType type = static_cast< PlayerType >( 
    						luaL_checkoption( L, 1, 
    						"INSURGENT", PlayerTypeList ) );
    	std::cout << "Type index is " << type 
    		      << " - " << PlayerTypeList[type] 
    		      << std::endl;
    	return 0;
    }
    
    lua_register( L, "setPlayerType", setPlayerType )
    

    首先enum PlayerType定义了一组角色类型,来自人人都爱的三国杀:-)。

    接着PlayerTypeList定义了一个字符串数组,给Lua使用。注意需要保证enum和字符串数组的对应,比如PlayerTypeList[PLAYER_TYPE_KING]是“KING”,同时,PlayerTypeList必须以NULL结尾。

    在定义给Lua的函数testPlayerType中,就可以用luaL_checkoption将Lua传来的字符串参数转换为相应enum的值。luaL_checkoption还支持默认参数,比如在上面例子中,将第三个参数设为“INSURGENT”,如果Lua中没有提供任何参数,则PlayerType就为与“INSURGENT”相对应的PLAYER_TYPE_INSURGENT。

    以下是一组测试及结果:

    --Lua code
    testPlayerType( "KING" ) -- Type index is 0 - KING
    testPlayerType() -- Type index is 1 - INSURGENT
    testPlayerType( "whatever" ) -- bad argument #1 to 'testPlayerType' (invalid option 'whatever')
    
  4. 在Lua中实现eval函数

    众所周知,JavaScript中有一个著名的eval函数,它用于把一个字符串当作一段JS代码去执行,在Lua中没有提供类似的函数,但稍微包装下Lua的库函数loadstring即可实现,以下是代码。

    --Lua code
    function eval( str )
    	local func = loadstring( "return " ..str );
    	return func()
    end
    

    这样已经可以了,不过相比于JS的eval函数,功能稍微差一些,因为它不支持赋值语句,这是Lua语言天然的原因,因为Lua的赋值运算符没有返回值,在其他语言中常见传递赋值的“i=j=1”(先赋值j=1,然后将(j=1)的返回值j赋值给i),在Lua中是不允许的。所以当eval执行的是赋值运算(比如i=1)的时候,return i=1就会出错。

    下面是一些测试例子:

    --Lua code
    print( eval( "3+4" ) ) -- OK, 打印7
    
    function Multiply( a, b )
    	return a*b
    end
    
    print( eval( "Multiply( 3, 4 )" ) ) -- OK,打印12
    
    print( eval( "i = 1" ) ) -- 错误, attempt to call a nil value
    i = 1
    print( eval( "i" ) ) -- OK,打印1
    print( eval( "i = i + 1" ) ) -- 错误, attempt to call a nil value
    
  5. 实现luaL_checkbool

    不知道为什么Lua的API没有提供luaL_checkbook函数,不过很容易实现:

    // C code
    BOOL luaL_checkbool( lua_State *luaVM, int numArg )
    {
    	BOOL b = FALSE;
    	if ( lua_type( L, numArg ) == LUA_TBOOLEAN )
    	{
    		b = lua_toboolean( L, numArg );
    	}
    	else
    	{
    		luaL_typerror( L, numArg, lua_typename( L, LUA_TBOOLEAN ) )
    	}
    	return b;
    }
    
Posted in: C++, Game Programming, Lua / Tagged: enum, eval, lua, luaL_checkbool, 默认参数

Lua相关问题整理(2)- 如何在C中为Lua提供同步调用接口

November 7, 2010 10:25 pm / Leave a Comment / Benny Chen

这个问题的具体描述是——C注册给Lua一个函数,但Lua调用该C函数并不能立即获得结果(比如需要访问远程服务器获取值),如何能让Lua停止并等待,直到获取到结果后,才继续执行接下来的脚本。

举个例子,比如说有一个C函数login,我们试图通过调用该函数以执行用户的登录操作并获取验证结果。首先看下面这段C代码:

// C code
int login( lua_State *L )
{
	string user = luaL_checkstring( L, 1 );
	string password = luaL_checkstring( L, 2 );

	// 该函数将user和password发送到服务器后立即返回,
	// 绝不要在此处阻塞,这将严重影响效率
	sendAuthenticationInfo( user, password );

	return 0;
}
// 注册给Lua
lua_register( L, "login", login );

可以看到,因为该函数需要访问远程的登录服务器,在系统中一般都采取异步操作(让系统阻塞等待结果是不可接受的)。但是这样Lua开发人员调用login函数时,也只能异步等待结果,下面是Lua中处理登录操作的代码。

-- Lua code
-- 当用户点击“登陆”按钮后,执行该函数
function onClickLoginBtn()
	local user = ...
	local password = ...
	login( user, password ) -- 只是发送login命令
end

--当获取login结果后的回调函数
function onGetLoginResult( bPass )
	if bPass == true then
		print( "Authentication succeeded." )
		...omitted code...
	else
		print( "Authentication failed." )
		...omitted code...
	end
end

这段Lua代码很好理解,首先onClickLoginBtn是一个按钮事件处理函数,当用户点击了”登录“按钮后,会触发该函数。该函数首先从界面上获取用户输入的user和password,然后调用了上面C中所定义的login函数。正如前面的C代码所写的,login函数不会马上得到认证结果,所以执行后马上退出。另一个函数是onGetLoginResult,这个Lua函数需要当C系统中获取到认证结果后被回调执行,以真正执行login的后续操作。所以,我们还需要在C中添加回调的代码。

// C code
// 当服务器端返回认证结果后
BOOL loginResult = ...
lua_getglobal( L, "onGetLoginResult" );
lua_pushboolean( L, loginResult );
lua_call( L, 1, 0 );

在这里,我们在C中hardcode了回调onGetLoginResult的代码,这很丑陋,不过可以避免,比如可以给前面注册给Lua的login函数增加一个参数callbackFunctionName,以让Lua显式的告诉系统当获取登录结果后的回调函数名称。

再次回到本文一开始所提出的问题,尽管在系统中的异步调用不可避免,但我们希望在Lua中能够有同步机制,即Lua脚本在得到验证结果后才允许被继续执行,如何才能做到这一点呢。

Lua有一个非常棒的coroutine机制,在Lua代码中可以通过协同程序来进行多线程,可以使用coroutine.yield和coroutine.resume来对协同程序进行挂起和恢复。需要达到上面所提出的目标,只需在系统中使用与coroutine.yield和coroutine.resume相对应的Lua C API——lua_yield和lua_resume即可。如下所示,login函数有些小改变:

// C code
int login( lua_State *L )
{
	string user = luaL_checkstring( L, 1 );
	string password = luaL_checkstring( L, 2 );
	sendAuthenticationInfo( user, password );
	return lua_yield( L, 0 );
}
// 注册给Lua
lua_register( L, "login", login );

可以看到,与前面的区别只有login函数的最后一句,调用了lua_yield后再返回,而不是return 0,这样就可以达到阻塞Lua脚本的目的。事实是,lua_yield是一个比较特殊的函数,它只能作为注册的C函数的返回值使用,否则调用失败。

当系统获得服务器端的登陆验证结果后,通过lua_resume即可恢复之前被阻塞的Lua。

// C code
// 当服务器端返回认证结果后
BOOL loginResult = ...
lua_pushboolean( L, loginResult );
lua_resume( L, 1 );

上面的代码是当你的系统中只有一个Lua虚拟机的情形,如果使用了多个Lua虚拟机,事情稍微有一点点复杂,login函数还需要将当前调用的lua_State存储下来,以便lua_resume的时候,可以知道恢复的是哪一个被阻塞的虚拟机。

OK,当C中有这样的实现后,Lua程序人员将会为此而高兴,因为Lua代码变得如此简洁。

-- Lua code
-- 当用户点击“登陆”按钮后,执行该函数
function onClickLoginBtn()
	local user = ...
	local password = ...
	if login( user, password ) == true then
		print( "Authentication succeeded." )
		...omitted code...
	else
		print( "Authentication failed." )
		...omitted code...
	end
end

系统or平台开发人员应当尽可能的为用户考虑,正如上面第二种解决方法所追求的那样。

Posted in: C++, Game Programming, Lua / Tagged: C++, coroutine, login, lua, resume, yield, 同步调用

Lua相关问题整理(1)

November 7, 2010 10:17 pm / Leave a Comment / Benny Chen
  1. (Under Linux)cannot find ‘dlsym’ ‘dlopen’ ‘dlerror’ ‘dlclose’

    需要同时链接”dl“库

  2. (Under Linux)编译lua报错luaconf.h:275:31: error: readline/readline.h: No such file or directory

    需要下载并安装GNU Readline Library

  3. PANIC: unprotected error in call to Lua API (unable to get ModuleFileName)

    1: 不推荐的解决方式:将Project Properties->Configuration Properties->General下的Character Set从unicode改成multi-set;
    2: 彻底的解决方式,参考此链接:http://lua-users.org/lists/lua-l/2006-06/msg00427.html

  4. 如何将Lua文本文件转化为Lua块文件(chunk file)

    调用LuaAPI – lua_dump

    关于lua_dump: about lua_dump: Dumps a function as a binary chunk. Receives a Lua function on the top of the stack and produces a binary chunk that, if loaded again, results in a function equivalent to the one dumped. As it produces parts of the chunk, lua_dump calls function writer (see lua_Writer) with the given data to write them.

  5. 如何在C中调用Lua脚本层的库函数

    void BeginLuaLibCall( lua_State *L, const string &libName, const string &functionName )
    {
         lua_pushstring( L, libName.c_str() );
         lua_gettable( L, LUA_GLOBALSINDEX );
         lua_pushstring( L, functionName.c_str() );
         lua_gettable( L, -2 );
    }
    
    void EndLuaLibCall( lua_State *L )
    {
         lua_pop( L, -1 );
    }
    

    示例:调用table.getn(该函数用来获取一个table的size)

    // 利用上面的函数
    BeginLuaLibCall( L, "table", "getn" );
    // 假设你的脚本中有一个table变量myTable,获取它到栈顶
    lua_getglobal( L, "myTable" ); 
    // 执行table.getn( t )
    lua_call( L, 1, 1 ); 
    // 打印结果
    std::cout << lua_tonumber( L, -1 ) ) << std::endl;
    // 将结果弹出栈
    lua_pop( L, -1 );
    // 将名叫“table”的table弹出栈
    EndLuaLibCall( L);
    
Posted in: Game Programming, Lua / Tagged: chunk, dl, function, library, lua, readline

关于从DLL导出模板函数

November 1, 2010 12:10 am / Leave a Comment / Benny Chen

模板在C++中本来就是个比较复杂的领域,而当它和DLL结合到一起时,事情变得更加有点复杂而有趣,最近就遇到了这样一个问题。

我有一个用于生成单件的类模板,它声明在一个DLL项目中,如下。

#if defined MY_EXPORTS
#define EXPORT_API __declspec(dllexport)
#else
#define EXPORT_API __declspec(dllimport)
#endif

template < typename T >
class Singleton
{
public:
	EXPORT_API static T& GetSingleton()
	{
		static T singleton;
		return singleton;
	}

// ...省去其它代码,比如隐藏构造函数,禁止复制构造和赋值函数..
};

我试图导出GetSingleton()函数,因为我希望在其他导入该DLL的模块中能够通过调用这个函数而得到单例。基于该Singleton类模板定义一个单件的类很简单,只需要继承实例化的类即可,比如我在DLL内部定义了两个单件类,LogManager和ResourceManager,他们的定义看起来像这样。

class LogManager : public Singleton< LogManager >
{
// ......
}
class ResourceManager : public Singleton< ResourceManager >
{
// 该类因为需要记录日志,会使用LogManager类
// ......
}

OK,DLL编译通过。然后,我在另一个项目模块中导入该DLL,我需要在这个模块中使用LogManager类和ResourceManager类,由于它们都是单件,正如前面所说的,我通过GetSingleton()函数得到单例,以访问它们的其它接口。

LogManager::GetSingleton()->...
ResourceManager::GetSingleton()->...

然而,状况出现了,build该模块时出现了链接错误:unresolved external symbol “public: static class ResourceManager & __cdecl Singleton::GetSingleton(void)”” ,提示死活链接不到ResourceManager的GetSingleton()函数,然而同样是使用Singleton模板,LogManager的GetSingelton()函数却是链接成功的。

如果足够了解template的机制,就很容易找到问题所在。问题的原因是,除非你显示的实例化(或者调用)了某个模板函数,否则这个模板函数在编译生成代码中(.obj)是根本不存在的。所以,尽管我们为模板函数打上了export的标签,但其实什么也没有导出。而LogManager能链接成功,是因为它在DLL中(ResourceManager中)有使用,从而编译器生成了LogManager::GetSingleton()并导出。

所以,为了能够同样也导出ResourceManager::GetSingleton(),我们必须在DLL中提前显示的实例化它(explicit instantiation)。

LogManager和ResourceManager都是在DLL内部定义的单件类,那能不能在DLL外部也基于Singleton模板快速定义单件类呢?可以,不过GetSingleton这个函数对于其它模块就是隐形的了(根本不存在),你只能在每一个单件类中重新定义一个GetSingleton函数,以避免链接错误。

这个问题有没有完美的解决方案呢,即我们能否让一个模板函数完全导出,使得在别的模块中也可以自由的基于任意类型实例化函数呢?

去google上试图寻求答案,无果。不过说这个问题还真不少,比如CodeProject上的这篇文章:http://www.codeproject.com/KB/cpp/MemberTemplateDLL.aspx 。它提出可以使用inline的方式来解决这个问题,但不保证结果,因为本来编译器对于inline的处理就是个未知的行为。他同时还提出,这可能是一个跟编译器相关的问题,因为对于模板的处理是个跟编译器密切相关的行为。该作者在VC6.0上遇到了这个问题,我的是在VS2005(VC8.0)上,在其他不同平台或版本的编译器上会怎样我也没有测试过。

总之,记住很重要的一点,当导出一个模板函数时,需要格外的警惕。在MSVC编译器上,只有在DLL中被显示的实例化的版本,相应的函数才会被导出。

PS:写于Costa Coffee@中关村,卡布奇诺喝得有点儿恶心了:-)

Posted in: C++ / Tagged: DLL, Singleton, 模板函数

DX 2010 June不再支持VS 2005

October 24, 2010 11:25 pm / Leave a Comment / Benny Chen

用cmake生成OGRE的项目文件,却出不来Sample项目。cmake的输出信息中提示Skipping samples build,因为Dependecies中的OIS没有编译成功,有以下错误:

dxguid.lib(dxguid.obj) : fatal error LNK1103: debugging information corrupt; recompile module

google了一下,发现很多都是说因为某个版本的DX不再支持VC6了,但我用的是VS 2005,不过DX SDK版本是最新刚刚下载的,猜想出现这个错误是不是也是因为DX和VS版本兼容的问题。

果然,我的DX版本是June 2010,官方网站上申明这个版本开始支持VS 2010,但不再支持VS 2005了。

The June 2010 DirectX SDK includes support for Visual Studio 2010. The DirectX SDK will continue to support Visual Studio 2008 as well. However, Visual Studio 2005 will no longer be supported.

解决办法:

    • 我一直是使用VS2005,好吧,我承认它或许有些过时了,换VS 2008/2010
    • 用一个较早版本的DirectX SDK,February 2010 release是最后一个支持VS 2005的版本
    • 一个将就的解决方案,将对Dependencies的编译从debug换成release
      Posted in: Computer Graphics / Tagged: dx, ogre, sample, support, vs 2005

      开始看《Game Engine Architecture》

      October 19, 2010 9:21 pm / Leave a Comment / Benny Chen

      国庆前就在淘宝上痛下血本拍下了《Game Engine Architecture》,本来想在国庆长假内好好消化这本书,但快递公司居然也休了7天的长假,自从10月1日临晨后,物流信息就停止了更新。结果就是,回老家过了一个漫长而慵懒的十一假期。

      从去年就开始关注这本书,据说好评如潮。我最近一年多一直干的事情就是开发game engine,但我对目前开发的很多架构不太满意,也有很多疑惑,虽然觉得有些地方很不完善,却又没法找到好的方法解决,所以希望能得到一些比较系统比较权威的帮助以作参考。于是买下了这本书,据说在国外是作为本科生的教材,所以讲得比较基础,不过比较全面、系统。

      书很厚,800多页,每晚回去看一些,争取两个月内能看完。

      这本书在google book上有预览,不过不是很清楚。

      Posted in: Game Programming / Tagged: game engine architecture

      Using Blocked Dropbox under Linux

      September 5, 2010 12:19 pm / 1 Comment / Benny Chen

      Recently I’ve been starting to use Ubuntu. I am just a newbie to Linux, so everything seems hard for me on the first time. No matter how familiar we are with Windows, when transferred to the Linux world, we become a computer idiot again. But everything is also fresh and attractive for me. When I get my first Ubuntu 10.04 (the latest version at this time) work on my laptop, I was totally shocked, really bloody cool~ (I learned the word “bloody” from a British TV show called IT Crowd, which I am watching recently)

      Dropbox is a perfect software to sync your files among different computers and I use it quite often under Windows. And now under Linux, I want it, too. But here in China, Dropbox is violently blocked. So in order to use it, I need to get out of the firewall first.

      Get an SSH tunnel
      It’s a common way to use SSH to get over the wall. Getting an SSH tunnel is easy, there are some for free. And you could also buy one for the sake of stability and speed. I bought a Puff (which is a kind of software using SSH) account from China’s biggest C2C shopping site Taobao.com.

      Use gSTM as the SSH tunnel manager under Linux
      Get gSTM from here: http://sourceforge.net/projects/gstm/
      Configure gSTM:

      • click ‘Add’ button to add a tunnel;
      • input the host and login username of the tunnel you get from the last step;
      • click ‘Add’ button to add a port redirection, set Type=dynamic, Port=7070 (the common SSH port), To host=n/a, To Port=n/a.

      Start the tunnel you’ve just added. If you set your browser’s proxy as SOCK5, 127.0.0.1:7070, you should be able to surf the sites like Facebook and Twitter now. If so, certainly you can log on to www.dropbox.com too!

      Download Dropbox for Linux
      Go to https://www.dropbox.com/downloading, and download a Dropbox for linux. I’m using Ubuntu, so I downloaded the Ubuntu (x86 .deb). Double click the downloaded .deb file, it will start the Ubuntu’s package installer to install Dropbox.

      Download Dropbox daemon
      After installation and starting Dropbox, it will prompt you to download the Dropbox daemon, but the proxy we set up before only works for the browser, so I cannot get the downloading work. The problem here is how could I let an arbitrary software to access the Internet with proxy. The solution is to use ProxyChains.

      Use ProxyChains
      Install ProxyChains: sudo apt-get install ProxyChains
      Configuration

      • open configuration file ( /etc/proxychains.conf )
      • modify the last line from “socks4 127.0.0.1 9050” to “socks5 127.0.0.1 7070”

      Now you should be able to use the SSH proxy to download Dropbox daemon.
      Open terminal, and enter: proxychains dropbox start -i

      This will start Dropbox with the SSH proxy. After the daemon is downloaded and installed, we could enjoy Dropbox again.

      Posted in: Linux / Tagged: blocked, daemon, dropbox, gstm, linux, proxychains, ssh

      小心DLL链接静态库时的内存错误

      September 2, 2010 10:05 pm / 2 Comments / Benny Chen

      最近写的模块,在独立的应用程序中测试是没问题的,但把它装配成DLL后,再在另一个应用程序中调用时却出现了内存错误。程序的模块链接关系大概是这样的:

      module就是我所写的模块,在这里被封装为DLL,因为要使用json相关的功能,该DLL链接了一个静态库 (jsoncpp.lib)。最后在应用程序中导入并使用module.dll,同时因为在应用程序中也需要用到json,所以应用程序也链接了jsoncpp.lib。

      以下用一些伪代码来描述这些模块间的调用关系,以具现出这个错误。

      jsoncpp.lib为c++提供了功能齐全的json操作,其核心的类是Json::Value。(阅读本篇文章你无需了解太多json)

      module.dll中导出了一个接口:

      //ModuleClass.h
      #include "json/value.h"
      
      #if defined MODULE_EXPORTS
      #define MODULE_EXPORTS __declspec(dllexport)
      #else
      #define MODULE_EXPORTS __declspec(dllimport)
      #endif
      
      class ModuleClass
      {
      public:
      	MODULE_EXPORTS void AllocSomeMemory( Json::Value &root )
      	{
      		// 这将申请一些内存,因为会new出一个Json::Value,并append到root上
      		root.append( "testString" );
      	}
      };
      

      应用程序:

      #include "json/value.h"
      #include "ModuleClass.h"
      int main()
      {
      	Json::Value root;
      	ModuleClass::AllocSomeMemory( root );
      }
      

      在Debug模式下,当main函数执行完毕,对Json::Value root进行析构时,程序便出现了异常。分析下,很显然,调用ModuleClass::MallocMemoryHere时申请的内存,是在module.dll中申请的,而对这些内存的析构则是在应用程序(.exe)中进行的(析构root会同时析构append在root上的所有子Json::Value)。不过,这是异常的真正原因么?

      追踪到异常的出错点:dbgheap.c文件中那句ASSERT语句。

      /*
      * If this ASSERT fails, a bad pointer has been passed in. It may be
      * totally bogus, or it may have been allocated from another heap.
      * The pointer MUST come from the 'local' heap.
      */
      _ASSERTE(_CrtIsValidHeapPointer(pUserData));
      

      注释中的最后一句话”The pointer MUST come from the ‘local’ heap“引起了我的警惕,难道对于内存的申请和释放不是在同一个heap上,除了‘local’ heap还有一个什么heap么。

      去MSDN上搜索了关于_CrtIsValidHeapPointer,似乎找到了答案,以下这段话是MSDN上对于_CrtIsValidHeapPointer的介绍:

      The _CrtIsValidHeapPointer function is used to ensure that a specific memory address is within the local heap. The local heap refers to the heap created and managed by a particular instance of the C run-time library. If a dynamic-link library (DLL) contains a static link to the run-time library, it has its own instance of the run-time heap, and therefore its own heap, independent of the application’s local heap. When _DEBUG is not defined, calls to _CrtIsValidHeapPointer are removed during preprocessing.

      注意字体加粗的部分,这不正应对我的情形么?!错误不在于DLL中申请的内存在EXE中释放,而在于如果这个DLL拥有一个静态链接,它就会拥有独立的运行时堆,独立于应用程序的堆。这样对于内存申请和释放并不是在同一个堆上进行的,当然出错了。

      解决:虽然MSDN上最后说,如果把项目改成release的,这个ASSERT就将避免,但这是放纵内存泄露,最好的解决办法是将静态链接也改成动态链接,这样就使得DLL能够和应用程序共享同一个堆,错误也得以避免。

      于是,我修改了jsoncpp的项目配置,生成jsoncpp的动态链接库,而不是使用静态库,重新导入到module.dll中,错误解决。

      Posted in: C++, Some Experiences / Tagged: _CrtIsValidHeapPointer, DLL, 静态库

      Static Initialization Order Fiasco

      August 19, 2010 12:59 pm / Leave a Comment / Benny Chen

      Static Initialization Order Fiasco (SIOF),我也是最近才知道了这个说法,因为在开发程序的时候被它bug了:对于一个static变量,不管它是全局的或者是类的成员变量,访问它的时候不一定总是成功的,甚至会造成程序crash,因为不能保证它在被访问时已经被初始化了(跟初始化的顺序有关,所以称为初始化顺序的Fiasco)。以下将制造一个非常简单的SIOF情形:

      Whatever.h

      #include <vector>
      #include <string>
      class Whatever
      {
      public:
      	Whatever()
      	{
      		cout << "Construct Whatever" << endl;
      		Display();
      	}
      	~Whatever()
      	{
      		cout << "Destruct Whatever" << endl;
      		Display();
      	}
      	void Display()
      	{
      		cout << "static int:" << i << endl;
      		cout << "static string:" << m_str << endl;
      		cout << "static vector:" << m_vec.front() << endl;
      	}
      
      private:
      	static int i;
      	static std::string m_str;
      	static std::vector<char> m_vec;
      };
      

      Whatever.cpp

      #include "Whatever.h"
      
      int Whatever::i = 500;
      string Whatever::m_str = "something";
      vector<char> Whatever::m_vec = vector<char>( 10, 'a' );
      

      一个简单的类,Whatever,包含几个static成员变量,然后在构造函数和析构函数中都分别打印这些静态变量的值,乍一看似乎没什么问题,但却有潜在的SIOF的风险。我们容易默认为在调用Whatever的构造函数的时候,Whatever空间中的static的成员变量已经被初始化了,其实不然,现在制造一个SIOF引起crash的情形:

      #include "Whatever.h"
      Whatever g_whatever; 
      int main()
      {
      	...
      }
      

      因为g_whatever是global变量,所以最先被初始化,在调用Whatever的构造函数的时候,Whatever空间的静态成员变量还未被初始化,所以访问这些静态变量肯定出错。在VS的编译器下测试的结果:

      Construct Whatever
      static int:5
      static string:
      (调用m_vec.front()导致程序crash)

      奇怪的是对于int这种built-in的类型却能得到正确的值,不知编译器在背后都做了哪些手脚,猜想可能是在程序编译的时候他们就被值替换了。而string和vector应该都属于自定义类型(初始化需要调用构造函数),未初始化之前访问肯定是错误的,所以打印出的string是个空值,而访问一个空的vector的front元素则直接造成程序crash。

      根据初始化和释放的对称关系,所以在析构函数中访问这些静态变量同样也是失败的,因为在析构g_whatever的时候,Whatever空间的静态变量已经被解决掉了。

      SIOF是非常难于检测的问题,这个例子是一种最简单的情形,在我的项目中,我并没有定义什么global的成员,但是因为使用了很多前置声明(forward declaration),还有一些Singleton,造成了一个非常隐蔽的SIOF,花了很大的力气才找到,痛苦的过程。

      要解决SIOF问题,需要用一个function来包装static变量,即利用函数内static变量的construct-on-first-use特性。

      修改后的Whatever.h

      class Whatever
      {
      public:
      	Whatever()
      	{
      		cout << "Construct Whatever" << endl;
      		Display();
      	}
      	~Whatever()
      	{
      		cout << "Destruct Whatever" << endl;
      		Display();
      	}
      	void Display()
      	{
      		cout << "static vector:" << GetStaticVector().front() << endl;
      	}
      
      private:
      	vector<char>& GetStaticVector()
      	{
      		static vector<char> vec = vector<char>( 10, 'a' );
      		return vec;
      	}
      };
      

      用GetStaticVector来包装之前所需要的静态的vector,就能保证在调用的时候,它一定已经被初始化了。再次运行之前的测试程序,OK了。

      总之,我们对于static变量的使用要保持一颗警惕的心,如果不确定在使用时它是否已经被初始化,就要使用函数包装static变量来防止Static Initialization Order FIASCO!
      Fiasco, what a cool word.

      Posted in: C++ / Tagged: crash, static initialization order fiasco, 初始化, 崩溃, 静态变量, 顺序

      Post Navigation

      ← Older Posts
      Newer Posts →

      LinkedIn

      Milan Petrovic

      Categories

      • In My Life (25)
        • A Day in the Life (8)
        • English Learning (2)
        • Learn a Word (7)
        • Something In The Way (8)
      • Music Heaven (8)
        • Guitar (1)
        • In Concert (1)
        • Lyrics (3)
      • OK Computer (54)
        • 3D (3)
        • C++ (10)
        • Computer Graphics (15)
        • Game Programming (23)
        • iOS (6)
        • Linux (1)
        • Lua (9)
        • My Projects (3)
        • Some Experiences (9)
        • Talking in Code (2)
        • Unity (2)
      • Quotations (2)
      • Uncategorized (1)
      • Visca Barça (24)
        • FCB BJ (5)

      Recent Posts

      • [译]优化你的手机游戏(没有延迟的,才是健康的)- 一篇给游戏美术设计师读的文章
      • 新浪微博API for MOAI
      • 稍后继续
      • Unity Developer ++
      • Another Thread @ Moai Forum
      • 1st Day of Golden Week
      • 为SyntaxHighlighter添加新语言
      • 基于Lua的State Pattern
      • Class Diagram of Pacman
      • 基于Moai的Pacman

      Recent Comments

      • 约修亚_RK on 为SyntaxHighlighter添加新语言
      • 爱装的小男孩 on 小心DLL链接静态库时的内存错误
      • happyfire on Game Loop的几种实现方式
      • William on 新浪微博API for MOAI
      • Benny Chen on 新浪微博API for MOAI
      • your man on 新浪微博API for MOAI
      • 你家男人 on 稍后继续
      • 逍遥 on 关于对std::vector的遍历
      • papa on Unity Developer ++
      • T客网 ︱ Techpot » Blog Archive » iOS开发与OpenGL ES相关问题整理(1) on iOS开发与OpenGL ES相关问题整理(1)

      Tags

      2d 3D 3dsmax 3ds max air Apply architecture Asia tour barca Beijing bilbao binary search blocked bob boost bruce springsteen C++ capo CGContextDrawImage Champions League Change DLL DX10 eval exporter flash framework frustum culling game game engine iniesta ios linux lua Moai opengles pacman plug-in plugin 北京 导出插件 崩溃 巴萨 游戏引擎 踢球
      © Copyright 2026 - A Game Developer
      Infinity Theme by DesignCoral / WordPress