Original address: Http://blog.segmentfault.com/hongliang/1190000000631630Cocos2d-x best practices for invoking custom C + + classes and functions under LUA
About Cocos2d-x under the LUA call C + + documentation looked a lot, but no one really to explain this matter to understand, I myself is a beginner, groping for a while, summed up as follows:
Cocos2d-x Lua calls C + + it seems so complicated that all the documents on the Web are not clear because there are 5 levels of knowledge:
1, in the pure C environment, register C function into the LUA environment, understand the nature of the mutual invocation between LUA and C
2. In the Cocos2d-x project, register the pure C function into the LUA environment to understand how Cocos2d-x created the LUA environment and how to get the environment and continue to customize it
3. Understand why you should use tolua++ to register C + + classes
4, in the pure C + + environment, using tolua++ to register a C + + class into the LUA environment, understand the use of tolua++
5, in the Cocos2d-x project, the use of cocos2d-x register their own way to register the custom C + + class into the LUA environment, understand how cocos2d-x through the Bindings-generator script to encapsulate tolua++ usage to save work
Only the first 4 layers are understood, and the Bindings-generator script is used at the end of the day. and the online document, either only to explain the 1th layer, or only spoon to tell you how to use the 5th Layer Bindings-generator script, not only the important knowledge points are not mentioned, the sample code is often not concise, which let me see C + + It's a headache for dizzying to understand (not that I'm not C + +, but that I don't accept C + + 's design philosophy to avoid it). So the next tutorial I will explain each layer of knowledge, the sample code is not complete rigorous, but try to use the most concise way to the key point of the program to say clearly.
First layer: pure C environment, register C function into LUA environment
Looking directly at the code is a lot more clear than talking about a lot of concepts. Create a A.lua and a A.C file, as follows, and see what's going on:
A.lua
print(foo(99))
A.c
#include <lua.h>#include <lualib.h>#include <lauxlib.h>*l) { 1;} 0;}
How about this code is simple? One can see that the simple cannot be simpler. I'm particularly annoying. The sample code is also the error of judgment and add code comments, the original look at the code will not be enough, but also add so many colorful interference items, purely increase the learning burden.
Use GCC to compile and execute it at the command line:
GCC a. C-llua &&./a. Out
Note that the -llua
option is necessary because you want to connect to the LUA library.
After reading the code above, it is much easier to explain:
1, to register into the LUA environment, the function needs to be defined as this kind:int xxx(lua_State *L)
2, use lua_tonumber
, and lua_tostring
other functions, to obtain the incoming parameters, for example, lua_tonumber(L, 1)
is to get the first parameter passed in, and the type is a number
3, using lua_pushnumber
, and lua_pushstring
so on, to press the return value into the LUA environment, because LUA support function returns multiple values, so you can push multiple return values into the LUA environment
4. The number returned by the final function indicates how many return values have been pressed into the LUA environment
5, using the lua_register
macro definition to register this function into the LUA environment, LUA script can use it, done! It's so easy!
Second layer: In cocos2d-x environment, register C function into LUA environment
Also simple:
1. In the frameworks/runtime-src/Classes/
directory, locate the AppDelegate.cpp
file. If the frameworks directory does not exist, you need to refer to this blog: write Lua with the Cocos code IDE, how to live in harmony with C + + code in your project
The key code in the AppDelegate.cpp file is as follows:
"' C + +
Auto engine = Luaengine::getinstance ();
Scriptenginemanager::getinstance ()->setscriptengine (engine);
luastack* stack = Engine->getluastack (); Stack->setxxteakeyandsign ("2dxLua", strlen ("Xxtea", strlen ( function//engine->getluastack();//register_custom_function (Stack->getluastate ());
You can see that Cocos2d-x has set aside the place for us to register a custom c function, so write it after the comment code: "Cpplua_ State*L = stack->getluastate (); Lua_register("Test_lua_bind", test_lua_bind);
You can also get the ScriptEngineManager
current object from scratch by the class LuaEngine
, and then the getLuaStack()
method gets the encapsulated object, and then the LuaStack
getLuaState()
original lua_State
struct pointer is called. As long as you know the entrance location, everything else is not a problem, or quite simple.
If you are interested, you can take a look at the ScriptEngineManager
detailed definition of the class in the frameworks/cocos2d-x/cocos/base/CCScriptSupport.h
file.
BTW: There is also a small knowledge point, the custom code inserted in the AppDelegate.cpp is written as far as possible in front of the COCOS2D_DEBUG
macro definition, because the code that executes in the debug environment and in the real machine environment is different:
#if (cocos2d_debug>0) if (StartRunTime ()) true; #true;
2, Next, find a place to test_lua_bind
write the function definition, even if you are done. If the pursuit of the elegant file organization, it should be said to create a new. c file, but this would not be able to put themselves into the compilation phase of the mire, so do not pursue elegance, and at the end of the AppDelegate.cpp file is written on the definition of the function can be, simple and clear:
*l) { 1); 1; Lua_pushnumber (L, number); 1;}
3, finished, you can now use the function in the Main.lua file test_lua_bind()
:
local i = test_lua_bind(99) print("lua bind: " .. tostring(i))
4, if it is a new. c file? AppDelegate.cpp
test_lua_bind
Delete the code of the function definition in the file and add it behind the head #include
:
#include "Test_lua_bind.h"
frameworks/runtime-src/Classes
create the file under the directory test_lua_bind.h
, as follows:
"C" {#include "lua.h"#include "lualib.h"}int Test_lua_bind (lua_state *l);
To create test_lua_bind.c
the file again, the content is unchanged:
#include "test_lua_bind.h"*l) { 1); 1;}
At this point cocos compile -p mac
compile with the command, you will find that the test_lua_bind.c
file is not compiled. This is of course, the ordinary C + + project is to use makefile to specify which. c/cpp files, the current cocos2d-x project although there is no makefile file, but also to follow this principle, that is, there must be a place to specify all the files to be compiled, It needs to be added in this place so that the test_lua_bind.c
whole project compiles as part of the project.
The answer is that the Cocos2d-x project does not use makefile, but rather intelligently uses the project files associated with the specific environment as a command-line compilation environment, such as using Xcode engineering files when compiling iOS or Macs, and using files when compiling Android Android.mk
.
So, after you've added test_lua_bind.h
and test_lua_bind.c
files, open the project with Xcode and add the files to the project.
Note that you should never tick "copy items into Destination Group ' folder (if needed)" because the cocos2d-x Xcode catalog organization is not a regular structure, and once this is checked, the two files are copied to frameworks/runtime-src/proj.ios_mac
directory, the original frameworks/runtime-src/Classes
directory of the file is discarded, such an organization will be messy, and will affect the Android side of the reference to the two files.
test_lua_bind.h
test_lua_bind.cpp
After adding the files to the Xcode project and then to the command line, the cocos compile -p mac
compilation will be successful.
There are other articles on the Internet that also need to modify the Xcode project "User Headers Path", this test is not required, even if the two files into the new folder do not need, as long as the addition of Xcode project can be, Because Xcode does not organize files in the form of folders, it has its own set of things called "Group". After several years of iOS development, this feature of Xcode is still familiar.
Speaking of this can not help but to insert a sentence on the online all cocos2d-x documents, the level of learning cocos2d-x is very good and bad, most people seem to be interested in the game programming beginners, they mostly poor foundation, and even a large number of people have not done mobile app development, They learn cocos2d-x only want to know it but do not want to know why, to tell them they also do not understand (because of poor programming basis), so many cocos2d-x articles on the Web are only 123 steps, and do not tell you why do so, A large number of documents, including the official cocos2d-x, are based on this idea, both Chinese and English. I look at these articles is particularly painful, while looking at the heart is always thinking, "Why do Ah", "This step is for what ah", "How so Troublesome ah", "This step is obviously not the best practice ah", "to solve this matter why should be so troublesome", "there is a better way?" So I this beginner to see Cocos2d-x documents become not pure learning, but learning, questioning, verification, reflection, optimization of the process, to others, cocos2d-x is easier to get started, to me here instead became a relatively difficult to start, after the introduction of easier, Because of the spam and invalid information in the document is too much, others can take a single full, after the understanding after the slowly culling, I must from the beginning to identify the garbage, only to retain the best practice, this is the blog write a longer reason.
Pulled away. Anyway, after the above steps, I completed the registration of C functions into the LUA environment in the COCOS2D-X project. At this point, it's completely understanding the invocation relationship between LUA and C functions, as well as the use of custom C functions in the Cocos2d-x LUA environment. But this is not enough, because a regular project is the need for a good organizational structure, global C function flying is certainly not possible, the better case is that all C functions are organized in LUA to register the module, a better case is to register C + + class into Lua, and C + + Classes are also registered in the LUA environment as a LUA module. This is actually the way cocos2d-x himself registers himself in the LUA environment.
Layer Three: Learn why you should use tolua++ to register C + + classes
Because the nature of Lua is C, not the API that C++,lua provides to C is also based on process-oriented C functions, to register C + + class into Lua to form a table environment is not very easy to do at once, because it needs to bend around the C + + Classes become a variety of other types registered into LUA, which is equivalent to using process-oriented thinking to maintain an object-oriented environment. The details of this is not to delve into, in short, because of this, so simply handwritten lua_register()
code to register C + + class is not feasible, expensive, so need to use tolua++ this tool.
This layer of knowledge seems simple, but in fact is very important, only to understand the manual lua_register()
to register C + + class difficulty, to understand the need to use tolua++ such tools. Only by understanding the necessity of using tolua++ tools, will you dive into the heart to accept the advantages and disadvantages of tolua++ itself calmly. Only see the shortcomings of the tolua++ itself and the use of trouble, will really understand bindings-generator
the benefits of cocos2d-x using scripts. Only by understanding the benefits of the bindings-generator
script can we understand some of the inconveniences of the use of the script itself.
Layer Fourth: In a pure C + + environment, use tolua++ to register a C + + class into the LUA environment
Although the ultimate method is to use the bindings-generator
script to register the C + + class into the Cocos2d-x LUA environment, but understand the use of tolua++ itself is very necessary, only to know the tolua++ original usage, to better understand cocos2d-x is how to put their C + + Classes are registered into the LUA environment, which not only makes programming ideas clearer, but also for the future in the source code to find a variety of interface documents in the process will not be able to understand that a lot of tolua_beginmodule
, tolua_function
what meaning. One of the major obstacles to improving the programmer's learning is to ignore the smattering code and not to root planing the bottom.
The standard practice for using tolua++ is to:
1, prepare their own C + + class, how to write how to write
2, imitation of this kind of. h file, change a. pkg file, the specific format to follow the provisions of tolua++, such as the removal of all private members, etc.
3. Build a C + + class that is designed to bridge between C + + and LUA, use special function signatures to write its. h file,. cpp files do not write, wait for tolua++ to generate
4, to this bridge C + + class write a. pkg file, according to tolua++ special format to write, the purpose is to really do the C + + class to define in
5. Use tolua++ to generate the. cpp file for the bridging class at the command line
6, the program entry reference this bridge class, execute the generated bridge function, the LUA environment can use the real C + + class
tolua++ this kind of handwriting. pkg file way old and uncomfortable, so I did not study carefully, this set of processes 10 years ago in that era is not too much of a problem, how the author rules on how to use it, but in the 2014 today, any program architecture design is focused on low learning costs, Light weight, in line with the past habits, so tolua++ use I think is actually uncomfortable.
Below I take the minimum code to go through the tolua++ process, note that this is in a pure C + + environment, with any framework does not matter, do not consider memory release and other details:
MyClass.h
class MyClass {public: MyClass() {}; int foo(int i);};
MyClass.cpp
#include "MyClass.h"int MyClass::foo(int i){ return i + 100;}
Myclass.pkg
class MyClass{ MyClass(); int foo(int i);};
MyLuaModule.h
"C" {#include "tolua++.h"}#include "MyClass.h"tolua_api int tolua_myluamodule_open (lua_state* tolua_s);
Myluamodule.pkg
$#include "MyLuaModule.h"$pfile "MyClass.pkg"
Main.cpp
<<<Lauxlib.h>} #include "MyLuaModule.h" int main () {lua_state *l = Lua_open (); Lual_ Openlibs (L); Tolua_myluamodule_open (L); Lual_dofile (L, "Main.lua"); Lua_close (L); return 0;}
Main.lua
local test = MyClass:new()print(test:foo(99))
Execute at the command line first:
tolua++-o myluamodule. cpp myluamodule. Pkg
This command is used to generate the bridging file MyLuaModule.cpp. Note the order of the-o parameter in the command line can not be placed arbitrarily, from this little thing can also be seen tolua++ ancient and difficult to use
Once the MyLuaModule.cpp file is generated, you can see the large number of bridges in it, such as, and tolua_beginmodule
tolua_function
so on. Later see these things are not unfamiliar, understand these functions just tolua++ used to do bridge the necessary code, simply look at the code, understand how tolua++ MyClass this C + + class registered into Lua:
Next, compile with g++:
g++ MyClass. cpp myluamodule. cpp main. cpp-llua-ltolua++
By default, the a.out
file is generated and executed, and you can see the results of the Main.lua execution:
At this point, the operation of the principle of tolua++ in the heart is translucent, nothing is:
1, write the class you write well
2, write a. pkg file, tell tolua++ what interfaces this class exposes to the LUA environment
3. Write a bridged. h and. pkg file, and let tolua++ to generate the bridge code
4, use this bridge code in the program, the class is registered into the LUA environment
Layer Fifth: Use Cocos2d-x to register C + + classes into the LUA environment
Cocos2d-x in the 2.x version is using tolua++ and. pkg files to register themselves in the LUA environment. However, this method is obviously clumsy, it is necessary to write the. pkg files that are actually doing things, and also to write the. pkg files and. h files that are bridged, and the workload is large and boring. So starting with the Cocos2d-x 3.x, the Bindings-generator script was used instead of the tolua++.
The working mechanism of the Bindings-generator script is:
1, do not have to write a bridge. Pkg and. h files, directly define an INI file, tell the script which classes of which methods to expose, register to the LUA Environment module name is what, it is equal to the original each class multiplied by 3 files of the workload into all classes only need 1. ini file
2. The method of generating the tolua++ tool is determined, and the Python script dynamically parses the C + + class and automatically generates the bridged. h and. CPP code without invoking the tolua++ command.
3, although the tolua++ command is no longer called, but the bottom layer still uses tolua++ library functions, for example tolua_function
, Bindings-generator script generated code is almost the same as the use of the tolua++ tool generated
The Bindings-generator script has mastered the initiative of generating tolua++ Bridge code, not only saving a lot of. Pkg and. h files, but also better inserting custom code to achieve some special purposes in the COCOS2D-X environment, such as memory recycling. So Cocos2d-x from the 3.x began to give up the tolua++ and. Pkg and instead of writing their own bindings-generator script is very commendable clever practice.
Next, how to use the Bindings-generator script:
1, write their own C + + class, according to Cocos2d-x rules, inherit the Cocos2d::ref class, in order to use the Cocos2d-x memory recovery mechanism. Of course not, but not recommended, otherwise the release of objects in the LUA environment is troublesome.
2. Write an. ini file so that bindings-generator can tell how the C + + class is exposed based on this configuration file.
3, modify the Bindings-generator script, let it read this. ini file
4, execute Bindings-generator script, generate bridge C + + class method
5. Use Xcode to add the custom C + + class and the resulting bridging file to the project, otherwise it will not compile
6, modify the AppDelegate.cpp, execute the bridging method, the custom C + + class is registered into the LUA environment
Look at the steps very much, in fact, are ruthless and simple. Take one step at a step below.
The first is the custom C + + class. I'm used to saving files in the frameworks/runtime-src/Classes/
directory:
Frameworks/runtime-src/classes/myclass.h
#include "cocos2d.h"using namespace cocos2d;class MyClass : public Ref{public: MyClass() {}; ~MyClass() {}; bool init() { return true; }; CREATE_FUNC(MyClass); int foo(int i);};
Frameworks/runtime-src/classes/myclass.cpp
#include "MyClass.h"int MyClass::foo(int i){ return i + 100;}
Then write the. ini file. In the frameworks/cocos2d-x/tools/lua/
directory you can see the genbindings.py
script and a lot of. ini files, these are the actual execution environment of Bindings-generator. Find a little. ini file, copy it, and rename it to Myclass.ini. Most of the content can be done without change, here only the important parts that must be changed:
Frameworks/cocos2d-x/tools/tolua/myclass.ini
[MyClass] prefix MyClassMy% (Cocosdir) s/. /runtime-src/classes/myclass.hMyClass
That is, specify the location of the MyClass.h file in Myclass.ini, specify the class to expose, and specify the module name to register into the LUA environment.
Notice, this place I stepped on a hole. If there is a macro definition in the. ini configuration file, macro_judgement = ...
be especially careful that I copied it from the file for the first time, and cocos2dx_controller.ini
did not notice that the resulting macro_judgement
bridging class file was added to the add-on macro, which only works on iOS and Android platforms and is not valid for Mac platforms. Pay special attention to this.
Then modify the genbindings.py
file in the vicinity of Line 129, add the Myclass.ini file:
frameworks/cocos2d-x/tools/tolua/genbindings.py
cmd_args = {‘cocos2dx.ini‘ : (‘cocos2d-x‘, ‘lua_cocos2dx_auto‘), ‘MyClass.ini‘ : (‘MyClass‘, ‘lua_MyClass_auto‘), ...
(In fact, this step can be omitted, as long as the genbindings.py script automatically search all INI files in the current directory, do not know the future Cocos2d-x team will not be optimized)
At this point, the preparation for bridging the file is done, and the genbindings.py script is executed:
Python genbindings.py
(On the Mac system may encounter the lack of YAML, cheetah package problems, install these Python package is simple, first sudo easy_install pip
, the PIP installed, and then with the PIP various pip search
, sudo pip install
it can be)
After the genbindings.py script is executed successfully, the frameworks/cocos2d-x/cocos/scripting/lua-bindings/auto/
newly generated file is seen in the directory:
Every time the execution of the genbindings.py script is very long, because it to re-process all of the. ini files, it is recommended to boldly modify the script file, flexible processing, so that it can only process the required. ini file at a time, such as this looks like:
Look at the frameworks/cocos2d-x/cocos/scripting/lua-bindings/auto/
generated C + + Bridge file under the directory lua_MyClass_auto.cpp
, with the name of the registration function register_all_MyClass()
, which is the key function for registering the MyClass class into the LUA environment:
To edit a file frameworks/runtime-src/Classes/AppDelegate.cpp
, first add a reference to the file in the file header lua_MyClass_auto.hpp
:
Then add a call to the function in the correct code location register_all_MyClass
:
Finally, before compiling, add the newly added C + + files to the Xcode project so that the compilation environment knows they exist:
There is also a small hole, because the file lua_MyClass_auto.cpp
to reference the file MyClass.h
, and the two files belong to different sub-projects, do not know each other the search path of the header file, so you need to manually modify cocos2d_lua_bindings.xcodeproj
the configuration of the sub-project User Header Search Paths
. Special attention to a total of a few ../
:
Finally, the entire project can be recompiled with cocos compile -p mac
commands, and the compilation must be successful without an accident.
To modify the Main.lua file, try calling the MyClass class:
local test = my.MyClass:create()print("lua bind: " .. test:foo(99))
Then execute the program (with cocos rum -p mac
or in the Cocos Code IDE), witness the miracle of the moment ~ ~ ~ I rub?! Program Crashes! For Mao?
This is the biggest pit I have encountered as a cocos2d-x beginner, pit me for a full day and a half, specific research details are not detailed, in short, the culprit is the cocos2d-x framework of the CCLuaEngine.cpp
file in this code:
The reason is that when the function is executeScriptFile
executed, the stack in the current LUA environment is cleaned up, and when the register_all_MyClass
function is called, the LUA stack is fully empty, and the function executes inside the tolua_module
function call and crashes:
The solution is to modify the AppDelegate.cpp to look like this:
The code in the text form is as follows:
AppDelegate.cpp
stack->getLuaState();lua_getglobal(L, "_G");register_all_MyClass(L);lua_settop(L, 0);
Recompile and execute, the program executes correctly:
At this point, it is completely clear how you should bind a C function or C + + class to the LUA environment in the Cocos2d-x project, and if you are interested, you can further explore the workings of Lua's internal metatable, the generation and release of class objects, and garbage collection. I myself also just contact cocos2d-x less than one weeks, understand not deep, the above will inevitably be useful words or understand the wrong place, if there are errors please forgive.