Sol与C++嵌入开发:无法访问usertype字段问题求助
Sol2绑定CSGO客户端类到Lua的nil错误排查与解决
问题背景
使用Sol2(搭配Lua 5.4)绑定CSGO客户端类到Lua时,C++侧逻辑可正常运行,但Lua脚本执行时触发如下错误:
[sol2] An error occurred and has been passed to an error handler: sol: runtime error: test.lua:11: attempt to concatenate a nil value (field 'm_ClassID')
stack traceback:
test.lua:11: in main chunk
C++绑定代码
namespace lua { namespace lua_client { valve::ClientClass* GetAllClasses( ) { auto client_class = csgo::base_client->GetAllClasses( ); if ( !client_class ) return nullptr; return client_class; } valve::ClientClass* GetClientClass( ) { auto client_class = csgo::base_client->GetAllClasses( ); while ( client_class ) { if ( !strcmp( client_class->m_pNetworkName, "CBaseEntity" ) ) return client_class; client_class = client_class->m_pNext; } return nullptr; } void init( ) { // setup our usertype(s) auto recv_prop_usertype = sol_state.new_usertype< valve::RecvProp >( "RecvProp", sol::constructors< valve::RecvProp( ) >( ) ); recv_prop_usertype.set( "m_pVarName", sol::readonly( &valve::RecvProp::m_pVarName ) ); recv_prop_usertype.set( "m_RecvType", sol::readonly( &valve::RecvProp::m_RecvType ) ); recv_prop_usertype.set( "m_Flags", sol::readonly( &valve::RecvProp::m_Flags ) ); recv_prop_usertype.set( "m_StringBufferSize", sol::readonly( &valve::RecvProp::m_StringBufferSize ) ); recv_prop_usertype.set( "m_bInsideArray", sol::readonly( &valve::RecvProp::m_bInsideArray ) ); recv_prop_usertype.set( "m_pExtraData", sol::readonly( &valve::RecvProp::m_pExtraData ) ); recv_prop_usertype.set( "m_pArrayProp", sol::readonly( &valve::RecvProp::m_pArrayProp ) ); recv_prop_usertype.set( "m_pDataTable", sol::readonly( &valve::RecvProp::m_pDataTable ) ); recv_prop_usertype.set( "m_Offset", sol::readonly( &valve::RecvProp::m_Offset ) ); recv_prop_usertype.set( "m_ElementStride", sol::readonly( &valve::RecvProp::m_ElementStride ) ); recv_prop_usertype.set( "m_nElements", sol::readonly( &valve::RecvProp::m_nElements ) ); recv_prop_usertype.set( "m_pParentArrayPropName", sol::readonly( &valve::RecvProp::m_pParentArrayPropName ) ); auto recv_table_usertype = sol_state.new_usertype< valve::RecvTable >( "RecvTable", sol::constructors< valve::RecvTable( ) >( ) ); recv_table_usertype.set( "m_pProps", sol::readonly( &valve::RecvTable::m_pProps ) ); // BUG!!!: attempt to index a nil value (local 'prop') recv_table_usertype.set( "m_nProps", sol::readonly( &valve::RecvTable::m_nProps ) ); recv_table_usertype.set( "m_pNetTableName", sol::readonly( &valve::RecvTable::m_pNetTableName ) ); auto client_class_usertype = sol_state.new_usertype< valve::ClientClass >( "ClientClass" ); client_class_usertype.set( "m_pNetworkName", sol::readonly( &valve::ClientClass::m_pNetworkName ) ); client_class_usertype.set( "m_pRecvTable", sol::readonly( &valve::ClientClass::m_pRecvTable ) ); client_class_usertype.set( "m_pNext", sol::readonly( &valve::ClientClass::m_pNext ) ); client_class_usertype.set( "m_ClassID", sol::readonly( &valve::ClientClass::m_ClassID ) ); // setup our table(s) auto client_table = sol_state.create_table( "client" ); client_table[ "GetAllClasses" ] = GetAllClasses; client_table[ "GetClientClass" ] = GetClientClass; } } }
Lua测试脚本
-- example script to dump netvars local client_class = client.GetAllClasses( ) -- is clientclass still valid? while client_class do local table = client_class.m_pRecvTable -- do we have a valid table? if table ~= nil then print( "" .. table.m_ClassID ) end client_class = client_class.m_pNext end
错误分析
- 字段归属错误:Lua脚本第11行
print( "" .. table.m_ClassID )中,table是RecvTable对象,但m_ClassID是ClientClass的成员,RecvTable并无此字段,因此访问返回nil,导致字符串拼接时出错。 - 数组指针绑定问题:C++代码中直接将
RecvTable::m_pProps(C风格数组指针)暴露给Lua,Sol2无法自动将C数组转换为Lua可识别的结构,后续访问m_pProps会触发nil错误。
解决思路
1. 修正Lua脚本的字段访问
将Lua脚本中错误的字段访问改为对应对象的正确成员:
-- example script to dump netvars local client_class = client.GetAllClasses( ) while client_class do local recv_table = client_class.m_pRecvTable if recv_table ~= nil then -- 访问ClientClass的m_ClassID,而非RecvTable的 print( client_class.m_ClassID .. " | " .. recv_table.m_pNetTableName ) end client_class = client_class.m_pNext end
2. 修复RecvTable::m_pProps的绑定
Sol2无法直接处理C风格数组指针,需通过自定义方法提供安全的数组访问:
// 在init()函数中修改RecvTable的绑定 auto recv_table_usertype = sol_state.new_usertype< valve::RecvTable >( "RecvTable", sol::constructors< valve::RecvTable( ) >( ) ); // 移除直接绑定m_pProps的代码 recv_table_usertype.set( "m_nProps", sol::readonly( &valve::RecvTable::m_nProps ) ); recv_table_usertype.set( "m_pNetTableName", sol::readonly( &valve::RecvTable::m_pNetTableName ) ); // 添加自定义方法获取指定索引的RecvProp recv_table_usertype.set("get_prop", [](valve::RecvTable* table, int index) { // 边界检查,避免越界访问 if (table == nullptr || index < 0 || index >= table->m_nProps) { return static_cast<valve::RecvProp*>(nullptr); } return &table->m_pProps[index]; });
之后Lua中可安全访问属性:
if recv_table ~= nil then for i = 0, recv_table.m_nProps - 1 do local prop = recv_table:get_prop(i) if prop ~= nil then print( "Prop " .. i .. ": " .. prop.m_pVarName ) end end end
3. 强化空指针安全
在C++的绑定函数中添加空指针检查包装,确保返回给Lua的对象有效性:
// 修改GetAllClasses为带空指针检查的包装 client_table[ "GetAllClasses" ] = []() { auto cls = csgo::base_client->GetAllClasses(); if (cls == nullptr) { return sol::nil; } return cls; };
内容的提问来源于stack exchange,提问作者TOFHR
相关产品推荐
相关产品推荐

