You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

错误分析

  1. 字段归属错误:Lua脚本第11行print( "" .. table.m_ClassID )中,table是RecvTable对象,但m_ClassID是ClientClass的成员,RecvTable并无此字段,因此访问返回nil,导致字符串拼接时出错。
  2. 数组指针绑定问题: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 01:33:09