Lua绑定进阶篇

作者: 最怕认真 | 来源:发表于2016-10-09 16:20 被阅读1050次

    之前已经写过两篇文章
    《Cocos2dx Lua 绑定》详细介绍了,如何在lua中调用c++;
    《Cocos2dx 插入广告》详细介绍了,对于打包成apk需要注意的事项;
    而对于今天这篇文章,是在对lua绑定有了进一步的理解之后做的一篇详细记录
    准备工作请参考第一篇文章,里面有详细的介绍。

    还是用一个简单的例子,来开始这篇文章
    本文是基于cocos3.6的版本,vs2012
    首先建立一个cocoslua工程,然后在

    工程\frameworks\runtime-src\Classes
    

    下建立一个Custom文件夹放我们自定义的c++代码文件,并且在Custom下再建立一个Lua文件夹,用来放lua和c++的桥接文件,接着回到vs2012中,建立对应的筛选器(筛选器和实际的物理路径不是一个意思,筛选器只是方便我们管理代码而已,筛选器A完全可以把B文件夹里的文件放进来,这个设定我倒是不太明白为什么)
    如此之后,vs2012是这个结构了

    工程目录.png

    我们这里写两个自定义类,Test和Student,你为我为什么是Student,我只好告诉你,我英语不好。。只会这个单词!

    Test.h

    class Test
    {
    private:
        int _handler;
    public:
        int test();
        void run();
        void registerScript(int handler);
        static Test* create();
    };
    

    Test.cpp

    #include "Test.h"
    #include "cocos2d.h"
    #include "CCLuaEngine.h"
    #include "Student.h"
    
    int Test::test()
    {
        return 100;
    }
    
    Test* Test::create()
    {
        Test * p = new Test();
        return p;
    }
    
    void Test::registerScript(int handler)
    {
        _handler = handler;
    }
    
    void Test::run()
    {
        auto engine = cocos2d::LuaEngine::getInstance();
        //_handler的三个参数,第一个是一个指针,用来传递对象,第二个是bool类型,第三个是int类型
        auto st = Student::create(10,"nevermore");
        engine->getLuaStack()->pushObject((cocos2d::Ref*)st,"Ref");
        engine->getLuaStack()->pushBoolean(true);
        engine->getLuaStack()->pushInt(32);
    
        //执行_handler函数,并且说明该函数需要的参数个数
        engine->getLuaStack()->executeFunctionByHandler(_handler,3);
    }
    
    函数 解释
    static Test* create() 在lua中对于对象的操作实际都是用指针来完成的,所以需要一个create函数来创建一个对象
    int test() 简单的返回一个数字,没有特殊的意义
    void registerScript(int handler) 将一个lua函数注册到这里来,将在run中执行这个函数
    void run() 传递参数给_handler并执行_handler

    在run函数中需要传递一个student对象,这里贴上student的代码

    Student.h

    #include "cocos2d.h"
    class Student:public cocos2d::Ref
    {
    private:
        int _age;
        std::string _name;
    public:
        static Student * create(int age,std::string name);
        std::string getName();
        void setName(std::string);
        int getAge();
        void setAge(int);
    };
    

    Student.cpp

    #include "Student.h"
    
    Student* Student::create(int age,std::string name)
    {
        Student * st = new Student();
        st->setAge(age);
        st->setName(name);
        return st;
    }
    
    void Student::setAge(int age)
    {
        _age = age;
    }
    
    void Student::setName(std::string name)
    {
        _name = name;
    }
    
    int Student::getAge()
    {
        return _age;
    }
    
    std::string Student::getName()
    {
        return _name;
    }
    

    写好了c++代码之后,我们需要做的,便是生成桥接文件,对于桥接文件的生成,使用的tolua的工具
    我们首先找到

    E:\LuaGame_3\frameworks\cocos2d-x\tools\tolua
    
    tolua.png

    这个文件夹下有很多ini文件,以及有一个叫做 genbindings.py 的文件,这个文件的作用,就是生成桥接文件的关键,这是一个python文件

    def main():
    
        cur_platform= '??'
        llvm_path = '??'
        ndk_root = _check_ndk_root_env()
        # del the " in the path
        ndk_root = re.sub(r"\"", "", ndk_root)
        python_bin = _check_python_bin_env()
    
        platform = sys.platform
        if platform == 'win32':
            cur_platform = 'windows'
        elif platform == 'darwin':
            cur_platform = platform
        elif 'linux' in platform:
            cur_platform = 'linux'
        else:
            print 'Your platform is not supported!'
            sys.exit(1)
    
        if platform == 'win32':
            x86_llvm_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/llvm-3.3/prebuilt', '%s' % cur_platform))
            if not os.path.exists(x86_llvm_path):
                x86_llvm_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/llvm-3.4/prebuilt', '%s' % cur_platform))
        else:
            x86_llvm_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/llvm-3.3/prebuilt', '%s-%s' % (cur_platform, 'x86')))
            if not os.path.exists(x86_llvm_path):
                x86_llvm_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/llvm-3.4/prebuilt', '%s-%s' % (cur_platform, 'x86')))
    
        x64_llvm_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/llvm-3.3/prebuilt', '%s-%s' % (cur_platform, 'x86_64')))
        if not os.path.exists(x64_llvm_path):
            x64_llvm_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/llvm-3.4/prebuilt', '%s-%s' % (cur_platform, 'x86_64')))
    
        if os.path.isdir(x86_llvm_path):
            llvm_path = x86_llvm_path
        elif os.path.isdir(x64_llvm_path):
            llvm_path = x64_llvm_path
        else:
            print 'llvm toolchain not found!'
            print 'path: %s or path: %s are not valid! ' % (x86_llvm_path, x64_llvm_path)
            sys.exit(1)
    
        project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
        cocos_root = os.path.abspath(os.path.join(project_root, ''))
        cxx_generator_root = os.path.abspath(os.path.join(project_root, 'tools/bindings-generator'))
    
        # save config to file
        config = ConfigParser.ConfigParser()
        config.set('DEFAULT', 'androidndkdir', ndk_root)
        config.set('DEFAULT', 'clangllvmdir', llvm_path)
        config.set('DEFAULT', 'cocosdir', cocos_root)
        config.set('DEFAULT', 'cxxgeneratordir', cxx_generator_root)
        config.set('DEFAULT', 'extra_flags', '')
    
        # To fix parse error on windows, we must difine __WCHAR_MAX__ and undefine __MINGW32__ .
        if platform == 'win32':
            config.set('DEFAULT', 'extra_flags', '-D__WCHAR_MAX__=0x7fffffff -U__MINGW32__')
    
        conf_ini_file = os.path.abspath(os.path.join(os.path.dirname(__file__), 'userconf.ini'))
    
        print 'generating userconf.ini...'
        with open(conf_ini_file, 'w') as configfile:
          config.write(configfile)
    
    
        # set proper environment variables
        if 'linux' in platform or platform == 'darwin':
            os.putenv('LD_LIBRARY_PATH', '%s/libclang' % cxx_generator_root)
        if platform == 'win32':
            path_env = os.environ['PATH']
            os.putenv('PATH', r'%s;%s\libclang;%s\tools\win32;' % (path_env, cxx_generator_root, cxx_generator_root))
    
    
        try:
    
            tolua_root = '%s/tools/tolua' % project_root
            output_dir = '%s/../runtime-src/Classes/Custom/Lua' % project_root
    
            cmd_args = {#'cocos2dx.ini' : ('cocos2d-x', 'lua_cocos2dx_auto'), \
                        # 'cocos2dx_extension.ini' : ('cocos2dx_extension', 'lua_cocos2dx_extension_auto'), \
                        # 'cocos2dx_ui.ini' : ('cocos2dx_ui', 'lua_cocos2dx_ui_auto'), \
                        # 'cocos2dx_studio.ini' : ('cocos2dx_studio', 'lua_cocos2dx_studio_auto'), \
                        # 'cocos2dx_spine.ini' : ('cocos2dx_spine', 'lua_cocos2dx_spine_auto'), \
                        # 'cocos2dx_physics.ini' : ('cocos2dx_physics', 'lua_cocos2dx_physics_auto'), \
                        # 'cocos2dx_experimental_video.ini' : ('cocos2dx_experimental_video', 'lua_cocos2dx_experimental_video_auto'), \
                        # 'cocos2dx_experimental.ini' : ('cocos2dx_experimental', 'lua_cocos2dx_experimental_auto'), \
                        # 'cocos2dx_controller.ini' : ('cocos2dx_controller', 'lua_cocos2dx_controller_auto'), \
                        # 'cocos2dx_cocosbuilder.ini': ('cocos2dx_cocosbuilder', 'lua_cocos2dx_cocosbuilder_auto'), \
                        # 'cocos2dx_cocosdenshion.ini': ('cocos2dx_cocosdenshion', 'lua_cocos2dx_cocosdenshion_auto'), \
                        # 'cocos2dx_3d.ini': ('cocos2dx_3d', 'lua_cocos2dx_3d_auto'), \
                        # 'cocos2dx_audioengine.ini': ('cocos2dx_audioengine', 'lua_cocos2dx_audioengine_auto'), \
                        # 'cocos2dx_csloader.ini' : ('cocos2dx_csloader', 'lua_cocos2dx_csloader_auto'), \
                        # 'cocos2dx_experimental_webview.ini' : ('cocos2dx_experimental_webview', 'lua_cocos2dx_experimental_webview_auto'), \
                        'custom_test.ini' : ('custom_test', 'custom_test'), \
                        'custom_student.ini' : ('custom_student', 'custom_student'), \
                        }
            target = 'lua'
            generator_py = '%s/generator.py' % cxx_generator_root
            for key in cmd_args.keys():
                args = cmd_args[key]
                cfg = '%s/%s' % (tolua_root, key)
                print 'Generating bindings for %s...' % (key[:-4])
                command = '%s %s %s -s %s -t %s -o %s -n %s' % (python_bin, generator_py, cfg, args[0], target, output_dir, args[1])
                _run_cmd(command)
    
            print '---------------------------------'
            print 'Generating lua bindings succeeds.'
            print '---------------------------------'
    
        except Exception as e:
            if e.__class__.__name__ == 'CmdError':
                print '---------------------------------'
                print 'Generating lua bindings fails.'
                print '---------------------------------'
                sys.exit(1)
            else:
                raise
    
    
    # -------------- main --------------
    if __name__ == '__main__':
        main()
    

    对于我们来说要修改的地方一个是output_dir ,一个是cmd_args

    • output_dir
      这是桥接文件的输出目录,还记得我们之前说的Lua文件夹么,它的实际路径是
    工程目录\frameworks\runtime-src\Classes\Custom\Lua
    

    如何定位到这个目录?代码中是这么写的

    output_dir = '%s/../runtime-src/Classes/Custom/Lua' % project_root
    

    %s在python中是用来格式化的,和lua中的string.format是一个意思
    print '%s,你好'%'最怕认真'将输出最怕认真,你好
    project_root在代码中是这样写的

    project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
    

    这是求路径的代码
    os.path.dirname(__file__)得到的是当前文件路径,也就是genbindings.py 的路径

    E:\LuaGame_3\frameworks\cocos2d-x\tools\tolua
    

    而..的意思是返回上一层路径,所以最终project_root 指向的是

    E:\LuaGame_3\frameworks\cocos2d-x
    

    如此,对于output_dir 要指向我们的lua文件夹,就很简单了

    • cmd_args
      这是一个数组,每一个item由3部分组成
    'custom_test.ini' : ('custom_test', 'custom_test'), 
    
    内容 含义
    第一部分custom_test.ini 我们配置的关键,是用来配置我们的c++代码到桥接文件的说明文件
    第二部分custom_test custom_test.ini的section名称,不明白没关系,等下我们会详细说下ini文件
    第三部分custom_test 导出的桥接文件的文件名

    ini文件要怎么写?
    我们首先在外层复制一个cocos2dx.ini文件,更名为custom_test.ini

    ini文件.png

    对于这个文件有5个地方需要修改

    第N部分 解释
    1 这个就是上面我们说的section了
    2 和1一致
    3 包含到哪个模块中,比如写 = cc那么调用test的时候就是cc.Test,如果不写就直接Test来调用
    4 需要生成桥接文件的头文件,cocosdier是在genbindings中赋值的,最终跟踪其就是project_root
    5 需要生成桥接文件的名称,如果有多个,则用空格隔开,但是不推荐多个文件共用一个配置文件

    按照如此我们就配置好了我们的Test文件的ini文件,接着再依葫芦画瓢写好我们的Student的ini文件

    Student.png

    接着我们回到genbindings的cmd_args中,我们用#注释了很大一部分内容,是因为这些文件都已经生成过了,我们没必要再生成一遍,我们只要添加我们需要生成的两个文件就行了,添加好了这两行,就在genbindings所在的目录下右键执行cmd,然后在cmd中运行genbindings,当出现

    ---------------------------------
    Generating lua bindings succeeds.
    ---------------------------------
    

    表示生成成功了,失败的话多半是ini那五部分内容写错了,仔细找下都不会太难解决的。
    然后在我们的Lua文件下就生成了对应的4个文件,2个cpp2个hpp,hpp和h文件是一个意思

    lua.png

    然后在vs2012的lua筛选器中
    右键-添加现有项-将4个文件包含进来

    lua包含.png

    对于2个cpp文件,其中

    #include "Test.h"和#include "Student.h"修改成正确的路径

    然后打开AppDelegate.cpp文件,把我们的c++类注册到lua中去
    在函数applicationDidFinishLaunching里调用了函数register_all_packages,在这个register_all_packages里完成我们的注册,首先在AppDelegate.cpp添加我们的2个hpp头文件

    #include "Custom\Lua\custom_test.hpp"
    #include "Custom\Lua\custom_student.hpp"
    

    register_all_packages改写如下

    static int register_all_packages(lua_State* L)
    {
        register_all_custom_test(L);
        register_all_custom_student(L);
        return 0; //flag for packages manager
    }
    

    同时在函数applicationDidFinishLaunching里传入lua_State
    如此,我们所有的准备工作算是完成了,然后编译,如果有错误根据错误提示改正,一般按照文章内容一步步做的话是能编译通过的,接下来就是使用了。
    首先测试test函数,我们在一个场景中如下测试

        local t = Test:create()
        local res = t:test()
        local txt = ccui.Text:create()
        txt:setFontSize(70)
        txt:setPosition(568,320)
        self:addChild(txt)
        txt:setString(tostring(res))
    

    用一个文本来显示返回值,运行

    test函数.png

    可以看到文本成功的显示了100

    然后再来测试我们的注册函数到c++的功能

    function MainScene:func(student, bool, int)
        print(student:getAge())
        print(student:getName())
        print(bool)
        print(int)
    end
    

    添加一个函数,然后在test函数测试后面加两句

    t:registerScript(handler(self, self.func))
    t:run()
    

    运行,不出意料的话,应该是报错的,这是因为传递函数和int这种基础类型不一样,对于这个错误我在c++如何调用lua函数有详细的说明,按照修正以后,编译运行

    函数注册测试.png

    可以看到输出的值和我们在c++往里面传递的值是一样的,至此,绑定的所有过程就完成了,掌握了这些我们就可以写底层的通信,复杂的计算,然后在lua中去调用,同时发挥lua和c++各自的优点!

    打包android apk的注意事项

    以上是完成了在win32平台的所有工作,但是我们打包成apk的时候,会提示找不到文件,我们在上面添加了2个自定义文件,又生成了2个桥接文件(都是指cpp文件),所以需要配置这几个文件的路径
    找到目录

    E:\LuaGame_3\frameworks\runtime-src\proj.android\jni
    

    这个目录有一个android.mk文件

    mk文件.png

    需要添加这4个cpp的路径,以及新加的文件夹的路径,如此,打包apk才能顺利通过

    相关文章

      网友评论

        本文标题:Lua绑定进阶篇

        本文链接:https://www.haomeiwen.com/subject/lwwpyttx.html