|
Lua and OO bindings |
Thomas Fjellstrom
Member #476
June 2000
|
This isn't actually a question. I finally managed to figure out the lua5.2 lib properly. I thought I'd share what I came up with: 1#include <cstdio>
2#include <lua.hpp>
3#include <cerrno>
4#include <cstring>
5#include <stdlib.h>
6
7#include <new>
8
9void lua_stack_dump(lua_State *L);
10
11#define _strify(str) #str
12#define _concat(str1, str2) str1 ## str2
13
14#define register_class(state, class_name, meta_table, class_table) do { \
15 luaL_newmetatable(state, #class_name); \
16 lua_pushvalue(state, -1); \
17 lua_setfield(state, -2, "__index"); \
18 luaL_setfuncs(state, class_table, 0); \
19 luaL_newlib(state, meta_table); \
20 /* lua_pushvalue(state, -1);*/ \
21 lua_setglobal(state, #class_name); \
22 lua_stack_dump(state); \
23} while(0)
24
25#define create_lua_object(state, class_name, obj) do { \
26 void *udata__ = lua_newuserdata(state, sizeof(_concat(Lua,class_name))); \
27 _concat(Lua,class_name) *obj__ = ::new (udata__) _concat(Lua,class_name)(state, obj); \
28 \
29 luaL_getmetatable(state, #class_name); \
30 lua_setmetatable(state, -2); \
31 lua_stack_dump(state); \
32} while(0)
33
34#define get_lua_object(state, class_name) (_concat(Lua,class_name) *)luaL_checkudata(state, 1, #class_name)
35
36#define destroy_lua_object(state, class_name) do { \
37 _concat(Lua, class_name) *udata__ = (_concat(Lua, class_name) *)luaL_checkudata(state, 1, #class_name); \
38 udata__->~_concat(Lua, class_name)(); \
39} while(0)
40
41class Test
42{
43 public:
44 Test() { printf("new Test!\n"); }
45 ~Test() { printf("delete Test!\n"); }
46
47 void it() { printf("TEST IT!\n"); }
48};
49
50class LuaTest
51{
52 public:
53 LuaTest(lua_State *, Test *test) : test(test) { printf("new LuaTest!\n"); }
54 ~LuaTest() { printf("delete LuaTest!\n"); }
55 void it() { test->it(); }
56 private:
57 Test *test;
58};
59
60static int Test_destroy(lua_State *state)
61{
62 printf("Test_destroy\n");
63 destroy_lua_object(state, Test);
64 return 0;
65}
66
67static int Test_it(lua_State *state)
68{
69 printf("Test_it\n");
70 LuaTest *test = get_lua_object(state, Test);
71 test->it();
72 return 0;
73}
74
75static int Test_new(lua_State *state)
76{
77 printf("Test_new begin\n");
78 Test *newTest = new Test();
79 create_lua_object(state, Test, newTest);
80 printf("Test_new end\n");
81 return 1;
82}
83
84static const luaL_Reg test_meta_methods[] = {
85 { "new", &Test_new },
86 { "__gc", &Test_destroy },
87 { 0, 0 }
88};
89
90static const luaL_Reg test_methods[] = {
91 { "it", &Test_it },
92 { 0, 0 }
93};
94
95int main(int argc, char **argv)
96{
97 printf("start\n");
98 lua_State *state = luaL_newstate();
99 luaL_openlibs(state);
100
101 printf("call register_class\n");
102 register_class(state, Test, test_meta_methods, test_methods);
103
104 printf("open lua file\n");
105 FILE *fh = fopen("luaobjtest.lua", "r");
106 if(!fh)
107 {
108 printf("failed to open luaobjtest.lua: %s\n", strerror(errno));
109 return 0;
110 }
111
112 fseek(fh, 0, SEEK_END);
113 long len = ftell(fh);
114 fseek(fh, 0, SEEK_SET);
115
116 char *script = (char *)malloc(len+1);
117 if(!script)
118 {
119 printf("failed to allocate enough memory to load luaobjtest.lua\n");
120 return 0;
121 }
122
123 script[len] = 0;
124 size_t ret = fread(script, 1, len, fh);
125 if(ret != (size_t)len)
126 {
127 printf("failed to read entire luaobjtest.lua file, read %li bytes, expected %li bytes.\n", ret, len);
128 return 0;
129 }
130
131 fclose(fh);
132
133 printf("call luaL_loadstring\n");
134 if(luaL_loadstring(state, script) != LUA_OK)
135 {
136 const char *errstr = luaL_checklstring(state, 1, 0);
137 printf("failed to load luaobjtest.lua: %s\n", errstr);
138 free(script);
139 return 0;
140 }
141
142 printf("call lua_pcall\n");
143 if(lua_pcall(state, 0, LUA_MULTRET, 0) != LUA_OK)
144 {
145 const char *errstr = luaL_checklstring(state, 1, 0);
146 printf("failed to run luaobjtest.lua: %s\n", errstr);
147 free(script);
148 return 0;
149 }
150
151 printf("done!\n");
152
153 return 0;
154}
155
156void lua_stack_dump(lua_State *L)
157{
158 int i;
159 int top = lua_gettop(L);
160 for (i = 1; i <= top; i++) { /* repeat for each level */
161 int t = lua_type(L, i);
162 switch (t) {
163
164 case LUA_TSTRING: /* strings */
165 printf("`%s'", lua_tostring(L, i));
166 break;
167
168 case LUA_TBOOLEAN: /* booleans */
169 printf(lua_toboolean(L, i) ? "true" : "false");
170 break;
171
172 case LUA_TNUMBER: /* numbers */
173 printf("%g", lua_tonumber(L, i));
174 break;
175
176 default: /* other values */
177 printf("%s", lua_typename(L, t));
178 break;
179
180 }
181 printf(" "); /* put a separator */
182 }
183 printf("\n"); /* end the listing */
184}
Maybe it'll help someone here trying to embed lua in their own programs, who also want to use objects from C++. append: Forgot the actual lua script: local test = Test.new(); test:it();
-- |
Dizzy Egg
Member #10,824
March 2009
|
Fjellstrom! This is...you are....nice one man!
---------------------------------------------------- |
Thomas Fjellstrom
Member #476
June 2000
|
Dizzy Egg said: Fjellstrom! This is...you are....nice one man! I take it you've been fighting with lua 5.2 as well? Most of my frustration was due to a typo/thinko in luaL_checkudata, passed 0 instead of 1 as the second argument. tried changing everything but that, thinking it was ok -- |
Dizzy Egg
Member #10,824
March 2009
|
Ahh...err...well I've been applying the 'copy & paste' approach (because I'm a lazy bastard), and have had not much luck so far with 5.2, I've hacked? together a solution, but compared to what you've just shared it looks like...well, a mess.
---------------------------------------------------- |
Thomas Fjellstrom
Member #476
June 2000
|
I.. Yeah, I tried the copy & paste method hoping it would get me at least part way, but almost none of the available code is for 5.2. I may change up the api later to use templates and other magic to make things a bit easier and cleaner. Maybe.. Right now thats just ripped out of a project I started trying to integrate lua into while I was at my moms. I spent days if not a week (or two) trying to get it to work properly. -- |
Dizzy Egg
Member #10,824
March 2009
|
It's much appreciated! And 'hi' to your mum! (and Jasper!)
---------------------------------------------------- |
Edgar Reynaldo
Major Reynaldo
May 2007
|
That stuff looks way over my head. I've got to break out of C++ one of these days My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Thomas Fjellstrom
Member #476
June 2000
|
lua's api really could have been done better. -- |
Edgar Reynaldo
Major Reynaldo
May 2007
|
So it's just a C api? I haven't looked at LUA in ages. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Raidho36
Member #14,628
October 2012
|
LUA's api isn't designed to be used in object-oriented fashion, provided LUA don't support objects. So it's as if you were complaining that OO-coding with plain assembly was hard and it could've done it better. |
Vanneto
Member #8,643
May 2007
|
Nobody is complaining. The only one (again!) that is bitching and moaning is... You. EDIT: And while the API for Lua is in C, the language itself supports OOP. In capitalist America bank robs you. |
Raidho36
Member #14,628
October 2012
|
Hey, I'm not moaning. Also, that's not really an OOP, I mean you could do just the same with C, but that doesn't make C an object-oriented language. |
Polybios
Member #12,293
October 2010
|
I did something quite similar, very much inspired by this wrapper solution here, which also provides some nice templates and ugly macros ( ) for passing arguments : Embedding Lua for Fun and Profit Had to adapt it to 5.2, too, though. As far as I remember, it wasn't more than replacing luaL_openlib with luaL_setfuncs. (edit: There was another thing, in Lua 5.2, tables can have a __gc metamethod while in 5.1 they can't, so I had to allow its execution for the userdata only, as this wrapper uses an userdata object wrapped inside a table for its c++-objects.) The one thing I found quite complicated to do about binding lua and c++ was to track userdata objects which contain other user-data objects, when the container is managed on the C side. You have to keep around a lua reference in your container object on the C-side in order to prevent the contained objects from being garbage-collected... I also hacked the Lua core so I had to declare globals. I don't like the concept of treating everything that isn't anything else as globals implicitly (I mean, I don't think global by default is bad, but you should be required to declare them in order to prevent typos from crashing your program, just as an extra-check). Raidho36 said: I mean you could do just the same with C Last time I checked, C didn't provide anything like meta-methods, which allow you to do constructors, destructors, operator overloading, ... |
Raidho36
Member #14,628
October 2012
|
I was comparing it to LUA's "OO". Seriously, that ain't OO. It's just LUA's loose internal structure allows a lot of things to work, which makes it possible to run sort of OO there. But LUA by itself has no support for it so you'd have to kludge up some kludgingly kludgy kludges to make it work right, just like you'd have to with C. |
Polybios
Member #12,293
October 2010
|
That's sort of right, but I'd still argue that it works better with Lua than with C because you have metatables and metamethods. You simply can't do this implicit- function-calling-OOP-magic with C! |
Thomas Fjellstrom
Member #476
June 2000
|
It's about as good as C++'s OO support. It has some convenient syntax sugar to allow some OO concepts to work, just like lua does \o/ But yes, please keep this as on topic as possible, winging about random crap just because you can is looked down upon. Polybios said: Also, this wrapper doesn't require separate wrapper classes, as yours. I started off without the wrapper classes. I added them thinking not using them was causing issues somehow? I tried a lot of things to get my code to work. But it turns out I want to keep the wrappers anyhow, so I can let lua properly dispose of stuff. The wrapper classes in some instances will be aloud to dispose of their wrapped objects, and you can't really do that by storing a single pointer in the udata. It actually wouldn't take much to rip out the need for the wrapper class in those macros.. Something like: 1#include <cstdio>
2#include <lua.hpp>
3#include <cerrno>
4#include <cstring>
5#include <stdlib.h>
6
7#include <new>
8
9void lua_stack_dump(lua_State *L);
10
11#define _strify(str) #str
12#define _concat(str1, str2) str1 ## str2
13
14#define register_class(state, class_name, meta_table, class_table) do { \
15 luaL_newmetatable(state, #class_name); \
16 lua_pushvalue(state, -1); \
17 lua_setfield(state, -2, "__index"); \
18 luaL_setfuncs(state, class_table, 0); \
19 luaL_newlib(state, meta_table); \
20 lua_setglobal(state, #class_name); \
21} while(0)
22
23#define create_lua_object(state, class_name, obj) do { \
24 void **udata__ = (void **)lua_newuserdata(state, sizeof(class_name *)); \
25 *udata__ = obj; \
26 luaL_getmetatable(state, #class_name); \
27 lua_setmetatable(state, -2); \
28} while(0)
29
30#define get_lua_object(state, class_name) *(class_name **)luaL_checkudata(state, 1, #class_name)
31
32#define destroy_lua_object(state, class_name) do { \
33 class_name **udata__ = (class_name **)luaL_checkudata(state, 1, #class_name); \
34 (*udata__)->~class_name(); \
35} while(0)
36
37class Test
38{
39 public:
40 Test() { printf("new Test!\n"); }
41 ~Test() { printf("delete Test!\n"); }
42
43 void it() { printf("TEST IT!\n"); }
44};
45
46static int Test_destroy(lua_State *state)
47{
48 printf("Test_destroy\n");
49 destroy_lua_object(state, Test);
50 return 0;
51}
52
53static int Test_it(lua_State *state)
54{
55 printf("Test_it\n");
56 Test *test = get_lua_object(state, Test);
57 test->it();
58 return 0;
59}
60
61static int Test_new(lua_State *state)
62{
63 printf("Test_new begin\n");
64 Test *newTest = new Test();
65 create_lua_object(state, Test, newTest);
66 printf("Test_new end\n");
67 return 1;
68}
69
70static const luaL_Reg test_meta_methods[] = {
71 { "new", &Test_new },
72 { "__gc", &Test_destroy },
73 { 0, 0 }
74};
75
76static const luaL_Reg test_methods[] = {
77 { "it", &Test_it },
78 { 0, 0 }
79};
80
81int main(int argc, char **argv)
82{
83 printf("start\n");
84 lua_State *state = luaL_newstate();
85 luaL_openlibs(state);
86
87 printf("call register_class\n");
88 register_class(state, Test, test_meta_methods, test_methods);
89
90 printf("open lua file\n");
91 FILE *fh = fopen("luaobjtest.lua", "r");
92 if(!fh)
93 {
94 printf("failed to open luaobjtest.lua: %s\n", strerror(errno));
95 return 0;
96 }
97
98 fseek(fh, 0, SEEK_END);
99 long len = ftell(fh);
100 fseek(fh, 0, SEEK_SET);
101
102 char *script = (char *)malloc(len+1);
103 if(!script)
104 {
105 printf("failed to allocate enough memory to load luaobjtest.lua\n");
106 return 0;
107 }
108
109 script[len] = 0;
110 size_t ret = fread(script, 1, len, fh);
111 if(ret != (size_t)len)
112 {
113 printf("failed to read entire luaobjtest.lua file, read %li bytes, expected %li bytes.\n", ret, len);
114 return 0;
115 }
116
117 fclose(fh);
118
119 printf("call luaL_loadstring\n");
120 if(luaL_loadstring(state, script) != LUA_OK)
121 {
122 const char *errstr = luaL_checklstring(state, 1, 0);
123 printf("failed to load luaobjtest.lua: %s\n", errstr);
124 free(script);
125 return 0;
126 }
127
128 printf("call lua_pcall\n");
129 if(lua_pcall(state, 0, LUA_MULTRET, 0) != LUA_OK)
130 {
131 const char *errstr = luaL_checklstring(state, 1, 0);
132 printf("failed to run luaobjtest.lua: %s\n", errstr);
133 free(script);
134 return 0;
135 }
136
137 printf("done!\n");
138
139 return 0;
140}
141
142void lua_stack_dump(lua_State *L)
143{
144 int i;
145 int top = lua_gettop(L);
146 for (i = 1; i <= top; i++) { /* repeat for each level */
147 int t = lua_type(L, i);
148 switch (t) {
149
150 case LUA_TSTRING: /* strings */
151 printf("`%s'", lua_tostring(L, i));
152 break;
153
154 case LUA_TBOOLEAN: /* booleans */
155 printf(lua_toboolean(L, i) ? "true" : "false");
156 break;
157
158 case LUA_TNUMBER: /* numbers */
159 printf("%g", lua_tonumber(L, i));
160 break;
161
162 default: /* other values */
163 printf("%s", lua_typename(L, t));
164 break;
165
166 }
167 printf(" "); /* put a separator */
168 }
169 printf("\n"); /* end the listing */
170}
The only issue with that, is if you really want lua calling your destructor? Probably not, so that should probably be removed. Quote: Have you thought about how do deal with that already? I'd be interested in how you do it. I haven't considered that. Even slightly. I'm sure you can do it simply enough, possibly with a nice little "smart_lua_ptr" class to wrap those lua objects? -- |
Polybios
Member #12,293
October 2010
|
Interesting. You could use placement new directly on your C++-object/the userdata with (sizeof(YourClass) and call the destructor explicitly in the __gc method, can't you? Is there a difference between Lua - TestDestroy - ~LuaTest (explicitly) - ~Test (implicitly) and Lua - TestDestroy - ~Test (explicitly)? Isn't it just shorter? But: In what situation would you want the Lua wrapper class to let go of the wrapped C++ object? Wouldn't that be insecure? While a smart pointer is a good idea (of which I haven't thought ), I think it doesn't solve the problem with the references on its own. You'd still need a place to store your lua_smart_ptrs; chances are that you don't want your C++-container-object to store them, because then, you couldn't use it with plain C++ objects anymore (?). Besides, I'd prefer to not have my C++ objects "contaminated" with Lua glue code. You definitively need those references though, because it will inevitably crash when you don't have them (speaking from experience here ). [As soon as Lua runs a GC-cycle, it will collect your CppObjects, because it is unable to "see" that they are "referenced" on the C++ side in your CppContainer.] While I think about it, this container-object-reference-thing is actually the only case for which you definitively need a wrapper class, no? Well, whatever, I just wanted to draw your attention to this problem, because it was the part which I found the most difficult. (By the way, is there a reason you don't use lua_dofile in the example? ) |
Thomas Fjellstrom
Member #476
June 2000
|
The main reason I use pointers (or the wrapper object) rather than explicit objects is, at least in the project I wrote the original code for, most objects will be created in the C++ code, and exposed to lua. And I don't want lua destroying my objects on me. With the wrapper class, I have an extra step where I can put some smarter code to handle the lua destruction event. It could potentially do smarter things. Maybe. Polybios said: But: In what situation would you want the Lua wrapper class to let go of the wrapped C++ object? Wouldn't that be insecure? Yeah, it could be. But its nice to allow it, say the lua code creates an object, and doesn't attach it to anything, and then exits? I'd like the orphans to go away if at all possible. Quote: (By the way, is there a reason you don't use lua_dofile in the example? ) Nope. I yanked the loadstring call from another example that loads from an array of strings. I just forgot about dofile and friends. -- |
Polybios
Member #12,293
October 2010
|
Thomas Fjellstrom said: Ah, okay, understand now. |
Thomas Fjellstrom
Member #476
June 2000
|
In case you're interested, this is the other lua test I made: 1#include <lua.hpp>
2#include <cstring>
3#include <cstdlib>
4
5struct Script {
6 const char *name;
7 const char *script;
8 bool loaded;
9} scripts[] = {
10 { "luatest1", "bar = 123; print(\"luatest1 run!\"); function foo() \n print(\"Hi from luatest1.foo!\");\nend", false },
11 { "luatest2", "bar = 123; print(\"luatest2 run!\"); function foo() \n print(\"Hi from luatest2.foo!\");\nend", false },
12 { "luatest3", "bar = 123; print(\"luatest3 run!\"); function foo() \n print(\"Hi from luatest3.foo!\");\nend", false },
13 { "luatest4", "bar = 123; print(\"luatest4 run!\"); function foo() \n print(\"Hi from luatest4.foo!\");\nend", false }
14};
15
16bool load_script(lua_State *state, const char *name, const char *str)
17{
18 printf("load script %s\n", name);
19
20 if(luaL_loadstring(state, str) != LUA_OK) // 1
21 {
22 const char *errstr = luaL_checklstring(state, 1, 0);
23 printf("failed to load luatest: %s\n", errstr);
24 return false;
25 }
26
27 lua_newtable(state); // script env // 21
28 lua_setglobal(state, name); // set global name for new table // 1
29
30 lua_getglobal(state, name); // 21
31 lua_newtable(state); // metatable // 321
32 lua_getglobal(state, "_G"); // 4321
33 lua_setfield(state, -2, "__index"); // set __index in metatable to _G // 321
34 lua_setmetatable(state, -2); // set metatable for script env // 21
35
36 lua_setupvalue(state, 1, 1); // set env for state // 21
37
38 printf("run script %s\n", name);
39 if(lua_pcall(state, 0, LUA_MULTRET, 0) != LUA_OK) // run script // 1
40 {
41 const char *errstr = luaL_checklstring(state, 1, 0);
42 printf("failed to run script %s: %s\n", name, errstr);
43 return false;
44 }
45
46 return true;
47}
48
49bool run_script(lua_State *state, const char *name)
50{
51 lua_getglobal(state, name);
52 lua_getfield(state, -1, "foo");
53 if(lua_pcall(state, 0, LUA_MULTRET, 0) != LUA_OK)
54 {
55 const char *errstr = luaL_checklstring(state, 1, 0);
56 printf("failed to call %s.foo: %s\n", name, errstr);
57 return false;
58 }
59
60 return true;
61}
62
63int main(int argc, char **argv)
64{
65 lua_State *state = luaL_newstate();
66 luaL_openlibs(state);
67
68 printf("begin\n");
69
70 for(int i = 0; i < sizeof(scripts) / sizeof(Script); i++)
71 {
72 if(!load_script(state, scripts[i].name, scripts[i].script))
73 {
74 printf("failed to load %s :(\n", scripts[i].name);
75 continue;
76 }
77
78 scripts[i].loaded = true;
79 }
80
81 for(int i = 0; i < sizeof(scripts) / sizeof(Script); i++)
82 {
83 if(scripts[i].loaded)
84 {
85 if(!run_script(state, scripts[i].name))
86 {
87 printf("failed to run %s :(\n", scripts[i].name);
88 continue;
89 }
90 }
91 }
92
93 printf("done!\n");
94 return 0;
95}
It loads each script into its own table/namespace in the same lua_State. I intend(ed) to use it to load a bunch of scripts that do certain things, and I can then just run them all in sequence without the overhead of separate states and the like. Oh, and they could potentially share some data if needed. They are partially sandboxed, so a bit safer than just loading a bunch of scripts into the same state, but not quite locked down, which you can do, but I didn't bother with the added complexity. -- |
|