Advertisement

Using a reference to get a pointer from application

Started by March 24, 2010 08:47 PM
4 comments, last by pjmendes 14 years, 11 months ago
Hi all, Recently I've began to integrate AngelScript into my application, but have hit a snag. I want to access some fields of an object handled by the application and returned by a function. To that, I've created a simple wrapper in C++ for that object, and registered both the wrapper and the function. All compiles and runs, but the values returned are not the correct ones. Maybe you can show me where? The wrapper class:
class GUIWidgetWrapReference
{
    public:
        GUIWidgetWrapReference() : objWrapped(0), refCount(1) { std::cout << "GUIWidgetWrapReference()\n"; }
        GUIWidgetWrapReference(gcn::Widget& obj) : objWrapped(&obj), refCount(1) { std::cout << "GUIWidgetWrapReference(&)\n"; }
        ~GUIWidgetWrapReference() { std::cout << "~GUIWidgetWrapReference()\n"; }

        // script specific stuff
        void AddRef() { std::cout << "AddRef\n"; refCount++; } // Increase the reference counter
        void Release() { std::cout << "Release\n"; if( --refCount == 0 ) delete this; } // Decrease ref count and delete if it reaches 0

        //
        int getX() { return objWrapped->getX(); }
        int getY() { return objWrapped->getY(); }
        int getWidth() { return objWrapped->getWidth(); }
        int getHeight() { return objWrapped->getHeight(); }
        Vector2 getPosition() { return Vector2(objWrapped->getX(), objWrapped->getY()); }
        Vector2 getDimensions() { return Vector2(objWrapped->getWidth(), objWrapped->getHeight()); }

    public:
        gcn::Widget* objWrapped;

    private:
        int refCount;
};



The registration for the wrapper (factory methods were purposely commented for the engine is not responsible for creating or managing objects of the type):

static void registerGUIWidgetAS(asIScriptEngine* engine)
        {
            int result;

            // register as reference
            // http://www.angelcode.com/angelscript/sdk/docs/manual/doc_reg_basicref.html
            result = engine->RegisterObjectType("GUIWidget", 0, asOBJ_REF); assert( result >= 0 );

            // Registering the factory behaviour
            //result = engine->RegisterObjectBehaviour("GUIWidget", asBEHAVE_FACTORY, "GUIWidget@ f()", asFUNCTION(GUIWidgetDefaultFactory), asCALL_CDECL); assert( result >= 0 );
            ////result = engine->RegisterObjectBehaviour("GUIWidget", asBEHAVE_FACTORY, "GUIWidget@ f(const GUIWidget& in)", asFUNCTION(GUIWidgetCopyFactory), asCALL_CDECL); assert( result >= 0 );
            //result = engine->RegisterObjectBehaviour("GUIWidget", asBEHAVE_FACTORY, "GUIWidget@ f(GUIWidget@)", asFUNCTION(GUIWidgetCopyFactory), asCALL_CDECL); assert( result >= 0 );

            // Registering the addref/release behaviours
            result = engine->RegisterObjectBehaviour("GUIWidget", asBEHAVE_ADDREF, "void f()", asMETHOD(GUIWidgetWrapReference,AddRef), asCALL_THISCALL); assert( result >= 0 );
            result = engine->RegisterObjectBehaviour("GUIWidget", asBEHAVE_RELEASE, "void f()", asMETHOD(GUIWidgetWrapReference,Release), asCALL_THISCALL); assert( result >= 0 );

            // register operators
            result = engine->RegisterObjectMethod("GUIWidget", "Vector2 getPosition()", asMETHOD(GUIWidgetWrapReference, getPosition), asCALL_THISCALL); assert( result >= 0 );
            result = engine->RegisterObjectMethod("GUIWidget", "Vector2 getDimensions()", asMETHOD(GUIWidgetWrapReference, getDimensions), asCALL_THISCALL); assert( result >= 0 );
            result = engine->RegisterObjectMethod("GUIWidget", "int getX()", asMETHOD(GUIWidgetWrapReference, getX), asCALL_THISCALL); assert( result >= 0 );
            result = engine->RegisterObjectMethod("GUIWidget", "int getY()", asMETHOD(GUIWidgetWrapReference, getY), asCALL_THISCALL); assert( result >= 0 );
            result = engine->RegisterObjectMethod("GUIWidget", "int getWidth()", asMETHOD(GUIWidgetWrapReference, getWidth), asCALL_THISCALL); assert( result >= 0 );
            result = engine->RegisterObjectMethod("GUIWidget", "int getHeight()", asMETHOD(GUIWidgetWrapReference, getHeight), asCALL_THISCALL); assert( result >= 0 );

            result = engine->RegisterObjectMethod("GUIWidget", "GUIWidget& opAssign(const GUIWidget& in)", asMETHODPR(GUIWidgetWrapReference, operator=, (const GUIWidgetWrapReference&), GUIWidgetWrapReference&), asCALL_THISCALL); assert( result >= 0 );
        }



The registration for the function I want to call (registered in another class, after this one has been registered):
result = engine->RegisterObjectMethod("GraphicalUserInterface", "GUIWidget& addLabel(string& in, string& in, Vector2& in, Vector2& in)", asMETHOD(GraphicalUserInterface,addLabel), asCALL_THISCALL); assert( result >= 0 );



The function to call (just the header):
gcn::Widget* addLabel(std::string id, std::string caption, Vector2 position, Vector2 dimensions);


Usage in the script:

    uint guideHorizontalBeginning = 10;
    GUIWidget@ label = gui.addLabel("labelTop", "Choose Action", Vector2(guideHorizontalBeginning, 5), Vector2(-1,-1));

    print("label.getX() "+ label.getX() +"\n");
    print("label.getWidth() "+ label.getWidth() +"\n");


After running the code, in the output console, only the "AddRef" and "Release" for the "GUIWidgetWrapReference" print, neither of the constructors do (so they are not called - I suspect this is the problem...), and the "label" values have absurd values (whereas in the function, after object creation, the values are correct). Thanks in advance.
Game Development Blog: http://pjmendes.blogspot.com/
Got some ideas from these threads,

Proper registration for this type?
Registering data pointed to by a level N pointer
Register a reference type

and I think I've figured it out:

Got rid of the wrapper, and moved the exposed class methods into static classes:
class GUIWidgetAS{    public:        void dummyRef() { std::cout << "dummy\n"; }        // functions for GUIWidget - STATIC VERSION        static int getX(const gcn::Widget* widget) { return widget->getX(); }        static int getY(const gcn::Widget* widget) { return widget->getY(); }        static int getWidth(const gcn::Widget* widget) { return widget->getWidth(); }        static int getHeight(const gcn::Widget* widget) { return widget->getHeight(); }        Vector2 getPosition(const gcn::Widget* widget) const { return Vector2(widget->getX(), widget->getY()); }        Vector2 getDimensions(const gcn::Widget* widget) const { return Vector2(widget->getWidth(), widget->getHeight()); }};


Reference Type Registration:
Changed the registration code for the exposed reference type, calling dummy functions for "AddRef" and "Release" for the reference, as well as registering the reference functions as global and receiving the class I want to expose as an object, declaring the methods using the "asCALL_CDECL_OBJLAST" flag:
static void registerGUIWidgetAS(asIScriptEngine* engine)        {            int result;            // register as reference            // http://www.angelcode.com/angelscript/sdk/docs/manual/doc_reg_basicref.html            result = engine->RegisterObjectType("GUIWidget", 0, asOBJ_REF); assert( result >= 0 );            // Registering the "AddRef/Release" as dummy functions            result = engine->RegisterObjectBehaviour("GUIWidget", asBEHAVE_ADDREF, "void f()", asMETHOD(GUIWidgetAS,dummyRef), asCALL_THISCALL); assert( result >= 0 );            result = engine->RegisterObjectBehaviour("GUIWidget", asBEHAVE_RELEASE, "void f()", asMETHOD(GUIWidgetAS,dummyRef), asCALL_THISCALL); assert( result >= 0 );            // register operators            result = engine->RegisterObjectMethod("GUIWidget", "uint getX() const", asFUNCTION(GUIWidgetAS::getX), asCALL_CDECL_OBJLAST); ScriptEngine::handleResult(ScriptEngine::ASActions::RegisterObjectMethod, result);            result = engine->RegisterObjectMethod("GUIWidget", "uint getY() const", asFUNCTION(GUIWidgetAS::getY), asCALL_CDECL_OBJLAST); ScriptEngine::handleResult(ScriptEngine::ASActions::RegisterObjectMethod, result);            result = engine->RegisterObjectMethod("GUIWidget", "uint getWidth() const", asFUNCTION(GUIWidgetAS::getWidth), asCALL_CDECL_OBJLAST); ScriptEngine::handleResult(ScriptEngine::ASActions::RegisterObjectMethod, result);            result = engine->RegisterObjectMethod("GUIWidget", "uint getHeight() const", asFUNCTION(GUIWidgetAS::getHeight), asCALL_CDECL_OBJLAST); ScriptEngine::handleResult(ScriptEngine::ASActions::RegisterObjectMethod, result);        }


Changed the registration for the exposed function that returns the reference type (from "GUIWidget&" to "GUIWidget@"):
result = engine->RegisterObjectMethod("GraphicalUserInterface", "GUIWidget@ addButton(string& in, string& in, Vector2& in, Vector2& in)", asMETHOD(GraphicalUserInterface,addButton), asCALL_THISCALL); assert( result >= 0 );


Hopefully I'm not doing anything wrong :)

P.S: there is a lot of information on how to correctly use AngelScript on the forum, but it is very spread out and sometimes tough to find (have to try different keywords when searching), and while the manual and add-ons provide help, there are still gaps in finding how to do things. A Wiki would go a long way as to interlink the information, help newbies such as me and speed the learning process (and a couple of tutorials on the most common things to do would be nice, such as exposing engine functionality).
It really wouldn't involve much more than copy+pasting a few select posts from this forum.
Game Development Blog: http://pjmendes.blogspot.com/
Advertisement
I have a wiki for AngelScript already:

AngelScript Wiki

There's unfortunately not a whole lot of information there yet, though I try to add the things that is of interest but that doesn't really fit in the manual.

I would very much appreciate help in adding more information to the wiki.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Didn't notice the wiki was open for public edition. I might give it a shot once I become more confident with binding.

By the way, what should I register to be able to do this in script:

Sprite@ ball;  // global pointer to spritevoid initialize() {    ball = engine.createSprite();}void update(){    ball.setPosition(10,10); // NULL POINTER access exception!}


I suppose I should register the "opAssign" operator, but I'm doing it wrong. How do I implement a "static" copy operator?

Registration:
result = scriptEngine->RegisterObjectMethod("Foo", "Foo@ opAssign(const Foo@& in)", asFUNCTION(Foo::operatorAssign), asCALL_CDECL_OBJLAST);


Foo::operatorAssign:
static Foo* operatorAssign(const Foo* originalFoo, Foo* thisFoo) { std::cout << "assign\n"; return new Foo(originalFoo); }


...operatorAssign is never called!
Game Development Blog: http://pjmendes.blogspot.com/
I believe the null pointer exception is actually in the initialize function, not the update function. You can check this by calling GetExceptionFunction() on the context when it returns asEXECUTION_EXCEPTION. You can also get the line number, and the entire call stack if you want.

Your script is slightly incorrect. The initialization function is doing a value assignment, but in reality you want it to do a handle assignment.

Sprite@ ball;  // global pointer to spritevoid initialize() {    // Handle assignment to have ball point to the new object     @ball = engine.createSprite();}void update(){    ball.setPosition(10,10); // no more null pointer exception}


For this, there is no need to register the opAssign operator, since it won't be used anyway. However, if you want to register the opAssign operator you would do it like this:

result = scriptEngine->RegisterObjectMethod("Foo", "Foo& opAssign(const Foo & in)", asMETHOD(Foo, operator=), asCALL_THISCALL);


The Foo::operator= is implemented exactly as you would normally implement it in C++, i.e.

Foo &Foo::operator=(const Foo &other){  // Do the value assignment  this->prop = other.prop;  return *this;}


Of course, if you do not have the operator= and don't want to change your class you can implement it with a wrapper similarly to what you tried doing

Foo &Foo_opAssign(const Foo &other, Foo *self){  self->prop = other.prop;  return *self;}result = scriptEngine->RegisterObjectMethod("Foo", "Foo& opAssign(const Foo & in)", asFUNCTION(Foo_opAssign), asCALL_CDECL_OBJLAST);


Regards,
Andreas

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Thanks for the helpful info, that was exactly it! Just adding the @ for the handle fixed everything.

Thanks for the tips in registering the assignment operator, they're very helpful as well! (as my knowledge in this area of C++ is still incomplete)

Some insight on the error, for others that may stumble into the same problem:
Having the incorrect syntax - "ball = engine.createSprite();" - and the application registration for the assignment operator, I had the "null pointer exception" error.
By removing the application registration for the assignment operator, I went back to the message "object has no copy operator" in "initialize" (due to incorrectly trying to retrieve a value instead of a handle, as WitchLord pointed).

The script will not load further after this exception, and attempting to call functions that would be loaded after this point in code will fail (with error "asNO_FUNCTION" when calling "asIScriptModule::GetFunctionIdByName").

As I was calling a function "update" defined in the script after "initialize" (and NOT CORRECTLY CHECKING FOR ALL ERRRORS AND THEIR MEANINGS :P), as "update" didn't exist, I got lots of cascading errors after that.

Hope this may be helpful. If I can think of a tutorial or help page that incorporates this into something of general use, I'll add it to the wiki.

Regards,
Paulo
Game Development Blog: http://pjmendes.blogspot.com/

This topic is closed to new replies.

Advertisement