vinniefalco / LuaBridge

A lightweight, dependency-free library for binding Lua to C++
1.63k stars 346 forks source link

constructor/addConstructor functions do not compile with Visual Studio 2010 #8

Closed psyinf closed 12 years ago

psyinf commented 12 years ago

A simple example: class TestClass { public: TestClass(int id) :_Id(id) {} virtual ~TestClass() {}

void run()
{}
int _Id;

};

... luabridge::scope s(lua_state); s .beginClass ("TestClass") .addConstructor<void (*) (int)> () .addMethod("run", &TestClass::run);

will not compile on Visual Studio 2010 (and 2005 I guess) with the following error: error C2783: 'luabridge::Namespace::Class &luabridge::Namespace::Class::addConstructor(void)' : could not deduce template argument for 'C'

Is there a workaround to explicetly tell the compiler the Type of C?

vinniefalco commented 12 years ago

Are you on the develop branch, or the master branch? I rolled back my experimental changes in the master branch, because I checked it in there by accident. It was supposed to go into develop (its there now). I fixed the repo yesterday morning.

As stated in the docs, there's no way to take the address of a constructor so you have to explicitly give it the template argument. I believe you want s.constructor <void (*)(void)> ().

Also you're missing the template argument to beginClass(), but I think its because Github interpreted it as an HTML tag. I'm guessing you meant s.class_ <TestClass> ("TestClass")

psyinf commented 12 years ago

Sorry for pasting the wrong example above, the HTML indeed reinterpreted the <> Actually I've tried both, the master and the develop branch.

Also s.constructor <void (*)(void)> () gives the exact same error. It seems VS is too stupid to deduce.

EDIT: I've just reverted to master with the following example code I get a similar error: Additionally, the example doesn' t compile. class TestClass { public: TestClass() {} void run() {} };

luabridge::scope s(luastate); s.class<TestClass>("TestClass") .constructor <void (*)(void)> () .method("run", &TestClass::run); error C2783: 'luabridge::class<T> &luabridge::class<T>::constructor(void)' : could not deduce template argument for 'SharedPtr'

vinniefalco commented 12 years ago

Ahh...My mistake. There are two template parameters to constructor(). The first one is the function signature, and the second one is the type of container you want to use. Currently, in order for Lua objects to construct class instances, they need to be wrapped in some sort of container (to resolve the lifetime issues when C++ and Lua share objects). You can use the luabridge::shared_ptr, or you can use your own. Be aware though, that unless you used the shared_ptr that comes with LuaBridge, the container must be of the intrusive variety. A boost::shared_ptr or std::shared_ptr will not work. You should be able to use .constructor <void (*)(void), luabridge::shared_ptr> ()

vinniefalco commented 12 years ago

You might want to try compiling the LuaBridgeDemo (https://github.com/vinniefalco/LuaBridgeDemo) it should definitely work in Visual Studio since that's what I use regularly and I just finished working on it.

psyinf commented 12 years ago

Thanks so far. I managed to get the develop branch compiling with the TestClass* as second template parameter. I'll also check on your second advise. After trying a dozen of other lua bind libraries I must say I really like this one thumbs up

vinniefalco commented 12 years ago

Thanks! If you used TestClass* as the container that's fine. The usual caveats regarding lifetime management will apply. I'm not sure if it will get garbage collected properly though, support for the containers is still in development. You might get a better result if you just write a static or global C function to create the object and register that instead.

psyinf commented 12 years ago

I've just tried. Indeed it doesn't get garbage collected, so I tried boost::shared_ptr for the type, which doesn't work, I want to use static/globals as much as possible (because this was my motivation to move to a binding library at all). Any chance that there will be updated documentation for the refactored version soon?

My motivation is simple: I have plugins that are derived from a base class an need to run a virtual machine for each instance. As the lua vm has to access this base/derived class I wrapped the functions into a Helper class. Any suggestions to do that in a sane way?

vinniefalco commented 12 years ago

boost::shared_ptr will definitely not work. I've tried all sorts of templates to get it to work but there is just no way. The problem is that the transition from Lua back to C creates a barrier that strips away the type information (it also strips away the const information but I am able to retain that). Because of this, attempting to receive a pointer to an object in a boost::shared_ptr, or std::shared_ptr results in a new hidden reference count (instead of using the original one). As you can imagine, this causes undefined behavior since the reference counts are out of sync.

You should be able to use the luabridge::shared_ptr (in shared_ptr.h) as the container for now. I've updated LuaBridgeDemo, it passes all the tests and it uses the new code. I am in the process of building a new container that is intrusive (that is, it stores the reference count on the object instead of in a global hash map). When this is done you will be able to do the following:

class YourClass : public luabridge::RefCountedObject
{
};

A new container called RefCountedObjectPtr<> will be provided that you can pass in the constructor. Note that this solution does require changing your class declaration. I realize this might not be possible in some cases (for example, when the class in question comes from a third party library). The only choices then are to use a luabridge::shared_ptr, or to wrap the class around a RefCountedObject.

I will update the documentation as soon as something is stable. But be aware that the API has not really changed (although most of the code was rewritten). All I did was change the function names around so they are readable and don't resemble C++ keywords (like class). This function from the Tests suite really says almost everything:

void addToState (lua_State *L)
{
  getGlobalNamespace (L)
    .addFunction ("testSucceeded", &testSucceeded)
    .addFunction ("testAFnCalled", &testAFnCalled)
    .addFunction ("testBFnCalled", &testBFnCalled)
    .addFunction ("testRetInt", &testRetInt)
    .addFunction ("testRetFloat", &testRetFloat)
    .addFunction ("testRetConstCharPtr", &testRetConstCharPtr)
    .addFunction ("testRetStdString", &testRetStdString)
    .addFunction ("testParamInt", &testParamInt)
    .addFunction ("testParamBool", &testParamBool)
    .addFunction ("testParamFloat", &testParamFloat)
    .addFunction ("testParamConstCharPtr", &testParamConstCharPtr)
    .addFunction ("testParamStdString", &testParamStdString)
    .addFunction ("testParamStdStringRef", &testParamStdStringRef)
    .beginClass <A> ("A")
      .addConstructor <void (*) (const string &), luabridge::shared_ptr <A> > ()
      .addMethod ("testVirtual", &A::testVirtual)
      .addMethod ("getName", &A::getName)
      .addMethod ("testSucceeded", &A::testSucceeded)
      .addMethod ("__add", &A::operator+)
      .addData ("testProp", &A::testProp)
      .addProperty ("testProp2", &A::testPropGet, &A::testPropSet)
      .addStaticMethod ("testStatic", &A::testStatic)
      .addStaticData ("testStaticProp", &A::testStaticProp)
      .addStaticProperty ("testStaticProp2", &A::testStaticPropGet, &A::testStaticPropSet)
    .endClass ()
    .deriveClass <B, A> ("B")
      .addConstructor <void (*) (const string &), luabridge::shared_ptr <B> > ()
      .addStaticMethod ("testStatic2", &B::testStatic2)
    .endClass ()
    .addFunction ("testParamAPtr", &testParamAPtr)
    .addFunction ("testParamAPtrConst", &testParamAPtrConst)
    .addFunction ("testParamConstAPtr", &testParamConstAPtr)
    .addFunction ("testParamSharedPtrA", &testParamSharedPtrA)
    .addFunction ("testRetSharedPtrA", &testRetSharedPtrA)
    .addFunction ("testRetSharedPtrConstA", &testRetSharedPtrConstA)
  ;
}

The one area which is problematic, as you have discovered, is this issue of the containers and object lifetime management. It is something that I had to struggle with in my own application as well and the subject of my current work in LuaBridge (the next step of course is bridging the Lua back to C++ but that is a separate issue).

I would like to better understand your use-case so that I can make sure to support it fully. The basic question is of lifetime management. Who creates your objects? Lua, or the C++ code? Or both? Who owns the object? There are three choices for object ownership:

I will work on getting the RefCountedObject into place. It will appear in LuaBridgeDemo first so you can see how it is used and determine what the fit is for your project.

vinniefalco commented 12 years ago

I made progress on the documentation, although it is unfinished. The explanation of the Stack, and the container objects needs more:

https://github.com/vinniefalco/LuaBridgeDemo/tree/master/LuaBridge

psyinf commented 12 years ago

Thank you for your efforts. One problem for me is that I'm still experimenting with the flow of data. What I currently have is base class for an object that is part of a simulation. Those objects are allowed to execute lua scripts that can change the state of the executed object. In the old implementation (which I'm refactoring right now) all calls were made to static functions that retrieved the associated object id from the lua_state. So there was no real coupling to the object class.

My idea now is to build an "interface" class that wraps up the static functions in object oriented way. Each instance of this class should be associated with exactly one object. So lifetime in an ideal world would be managed the following way:

If an object is created it creates a lua VM. The lua VM creates one interface class that exists as long as the VM exists. If the object is released, the lua VM is about to be destroyed. At this point the interface class instance should be destroyed.

Currently I don't see a way to create the interface class myself and just pass a reference to the VM. This way I could control the lifetime easily by myself, and wouldn't have to pass around any static pointers to the associated object. Instead I could simply instanciate an interface with the appropriate data.

Some words on the garbage collection: I really have to run it manually, because the standard gc will take an uncontrollable amount of time, which I cannot afford as I have to execute many object VMs in frame that should end after a specified time.

Also there some serious issue with the memory managment. For reasons I cannot elaborate, I have to supply my own alloc/free implenentation to the lua VM. I hope that won't conflict with luabridge.

Anyways, I'm going to dig deeper into your advices and will check your updates. If you wan't some more detailed information we can discuss this via email. I have a strong interest in making luabridge a yet more powerful tool than it already is.

vinniefalco commented 12 years ago

Oh! What you are doing is quite simple. If you have class A registered and you have an already existing instance that you wish to expose to Lua just do this:

luabridge::Stack <A*>::push (L, &a);
lua_setglobal (L, "a");

Now you have an object named "a" in the given lua_State. Since it was pushed as a pointer, the lifetime is managed by C++. Your object will never get garbage collected since 1) it was put into the global table, and 2) a pointer has no destructor. The "Stack" section of the docs really do need improvement, this usage is not obvious.

LuaBridge doesn't care about what custom memory allocator you provide in the call to lua_open. I'm not sure if it will fully support custom operator new and delete on classes (haven't tried that) although from what you have described, it sounds like you aren't using that and won't need it.

vinniefalco commented 12 years ago

I'm putting the finishing touches on the documentation. I was able to resolve all of the outstanding issues as well as making the container completely generic. I'd like to work with you and help you get everything you need up and running. Your feedback is valuable for improving this library.

Please email me at vinnie.falco@gmail.com

vinniefalco commented 12 years ago

This is all fully fixed in the develop branch. Example code is provided in the documentation to directly address your use-case. Also, this example code is in the LuaBridgeDemo (NewTests.cpp). If there are any new bugs with it please open a new issue. Thanks!