Advertisement

This changes when returning value objects

Started by March 11, 2013 08:41 AM
10 comments, last by WitchLord 11 years, 8 months ago

compiler: mingw 4.7.2

os: Windows 7

angelscript: official 2.26.1

I have a problem that I cannot solve, I am hoping that I am just registering something wrong.

The short story of the symptoms:

----------------------

When registering a struct as a value type the value of 'this' changes by a few bytes if a called member function of that struct is returning another registered struct value type, 'this' does not change when returning nothing (void) or when returning float.

This bug goes away if at least 3 int (for example) member variables are added to the registered value struct that is returned, if so then 'this' does not change.

----------------------

Here is a small example that illustrates the problem.

We have 2 structs (Dummy and Test) and we register them in angelscript. 'Test' has a debug printing of 'this' at every function that gets run from it. We then run these lines in angelscript:


void main() {
    Test test;
    test.noReturnValue();
    test.floatReturnValue();
    test.getDummy();
}

This run outputs:


Constructor self address: 0x60e7c4
This print from constructor: 0x60e7c4
This print from noReturnValue function: 0x60e7c4
This print from floatReturnValue function: 0x60e7c4
This print from getDummy function: 0x60e7bc

The application also crashes at the end of the run.

The last lines shows that 'this' changes slightly due to that function call returning a 'Dummy' value.

Here is an example program that is as small as I could make it that still shows the problem (this is c++11 code).


#include <angelscript.h>
#include <add_on/scriptstdstring/scriptstdstring.h>
#include <add_on/scriptbuilder/scriptbuilder.h>
#include <stdio.h>
#include <assert.h>
#include <iostream>

void MessageCallback(const asSMessageInfo *msg, void *param) {
  const char *type = "ERR ";
  if( msg->type == asMSGTYPE_WARNING )
    type = "WARN";
  else if( msg->type == asMSGTYPE_INFORMATION )
    type = "INFO";
  printf("%s (%d, %d) : %s : %s\n", msg->section, msg->row, msg->col, type, msg->message);
}

void print(const std::string &in) {
    std::cout << in << std::endl;
}

struct Dummy {
    static void Constructor(Dummy *self) {new(self) Dummy();}
    static void Destructor(Dummy *memory) {memory->~Dummy();}
    //int a, b, c; // uncomment this line for it to work again
    //int a, b; // will not work, requires at least 3 extra vars
};

struct Test {
    Test() {
        std::cout << "This print from constructor: " << this << std::endl;
    }
    static void Constructor(Test *self) {std::cout << "Constructor self address: " << self << std::endl;new(self) Test();}
    static void Destructor(Test *memory) {memory->~Test();}
    void noReturnValue() {
        std::cout << "This print from noReturnValue function: " << this << std::endl;
    }
    float floatReturnValue() {
        std::cout << "This print from floatReturnValue function: " << this << std::endl;
        return 5.6;
    }
    Dummy getDummy() {
        std::cout << "This print from getDummy function: " << this << std::endl;
        return Dummy();
    }
};

template<typename T>
void reg(asIScriptEngine *engine, std::string name) {
    int r = engine->RegisterObjectType(name.c_str(), sizeof(T), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK ); assert( r >= 0 );
    r = engine->RegisterObjectBehaviour(name.c_str(), asBEHAVE_CONSTRUCT, std::string("void f()").c_str(), asFUNCTION(T::Constructor), asCALL_CDECL_OBJLAST); assert( r >= 0 );
    r = engine->RegisterObjectBehaviour(name.c_str(), asBEHAVE_DESTRUCT, "void f()", asFUNCTION(T::Destructor), asCALL_CDECL_OBJLAST); assert( r >= 0 );
}

std::string app = R"(void main() {
    Test test;
    test.noReturnValue();
    test.floatReturnValue();
    test.getDummy();
})";

int main() {
    asIScriptEngine *engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
    int r = engine->SetMessageCallback(asFUNCTION(MessageCallback), 0, asCALL_CDECL); assert( r >= 0 );
    RegisterStdString(engine);
    r = engine->RegisterGlobalFunction("void print(const string &in)", asFUNCTION(print), asCALL_CDECL); assert( r >= 0 );

    reg<Dummy>(engine, "Dummy");
    reg<Test>(engine, "Test");
    r = engine->RegisterObjectMethod("Test", "Dummy getDummy()", asMETHOD(Test,getDummy), asCALL_THISCALL);assert( r >= 0 );
    r = engine->RegisterObjectMethod("Test", "void noReturnValue()", asMETHOD(Test,noReturnValue), asCALL_THISCALL);assert( r >= 0 );
    r = engine->RegisterObjectMethod("Test", "float floatReturnValue()", asMETHOD(Test,floatReturnValue), asCALL_THISCALL);assert( r >= 0 );

    CScriptBuilder builder;
    r = builder.StartNewModule(engine, "MyModule"); if( r < 0 ) {printf("Unrecoverable error while starting a new module.\n");return 0;}
    r = builder.AddSectionFromMemory("TestSection", app.c_str());if( r < 0 ) {printf("Please correct the errors in the script and try again.\n");return 0;}
    r = builder.BuildModule(); if( r < 0 ) {printf("Please correct the errors in the script and try again.\n");return 0;}

    asIScriptModule *mod = engine->GetModule("MyModule");
    asIScriptFunction *func = mod->GetFunctionByDecl("void main()"); if( func == 0 ) {printf("The script must have the function 'void main()'. Please add it and try again.\n");return 0;}
    asIScriptContext *ctx = engine->CreateContext();
    ctx->Prepare(func);
    r = ctx->Execute(); if( r != asEXECUTION_FINISHED and r == asEXECUTION_EXCEPTION) {printf("An exception '%s' occurred. Please correct the code and try again.\n", ctx->GetExceptionString());}
}

This looks to be related to the change in the ABI that Mingw did for version 4.7. Do you know if this happens with an earlier version of mingw too?

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

Advertisement

I am trying to download a version of mingw 4.6 to check. I'll post the results.

Just tried it with this compiler (mingw 4.6.2) http://sourceforge.net/projects/mingwbuilds/files/host-windows/releases/4.6.2/32-bit/

Same problem happened (crash+wrong 'this' on last line).

I also tried it with 4.6.2 and angelscript 2.25.2 (I had to make a slight change to the AddSectionFromMemory line due to api change).

Same problem happened.

I also did something extra. My old eee 900 laptop did not have any development stuff installed so I wanted to make sure this wasnt due to some random crap I had installed. I installed the latest codeblocks (that includes mingw 4.7.2) and the latest angelscript. The same problem there as well.

I'm sorry for leading you down the wrong path. After a more detailed review of the code sample you posted, I see you're registering the value types incorrectly.

The Dummy struct must be registered like this:

engine->RegisterObjectType("Dummy", sizeof(Dummy), asOBJ_VALUE | asOBJ_APP_CLASS);

You've registered it with the flags asOBJ_APP_CLASS_CDAK, but the structure has none of the traits you're telling AngelScript that it has. I.e. it has no explicit constructor, no destructor, no copy constructor, nor any assignment operator.

The Test struct is also registered with the wrong flags. It should be asOBJ_VALUE | asOBJ_APP_CLASS_C, since it has an explicit constructor.

It is very important to get these flags correct when registering the types, otherwise AngelScript will not be treat these types as the C++ compiler does. And this can cause problems like the ones you're experiencing now.

Since you're using C++11, I believe it is possible to automatize the use of the correct flags with templates. Unfortunately I haven't had the time to look into this, but I remember reading about C++11 introducing the ability for templates to determine the existence of these traits in the classes/structs. If you feel like implementing something like that I would be very interested in including it as an add-on for AngelScript, as a lot of people have difficulties in getting these flags correct.

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

Thank you! I now understand that the way I was thinking about the existence of the constructors destructors etc in classes were wrong. I thought there was no change introduced to a struct when compiled simply because you added a default constructor (I thought the compiler did on its own if you didn't define one), that's why I thought it was fine to just use asOBJ_APP_CLASS_CDAK since then all of them would be called.

Advertisement

Ok, I have now dug deep into gccs type_traits and tr1/type_traits files and this looks like it might work after some simple testing (will need to do more testing, will report back later). Only problem with this one appears to be that I cannot find a c++11 has_trivial_copy function, it appears to be unimplemented, but it does exist in the tr1 (pre c++11 file)


#include <type_traits>
#include <tr1/type_traits>
template<typename T>
void automaticallyRegisterObjectTypeAsValue(asIScriptEngine *engine, std::string name) {
        bool hasConstructor =  std::is_default_constructible<T>::value && !std::has_trivial_default_constructor<T>::value;
        bool hasDestructor = std::is_destructible<T>::value && !std::has_trivial_destructor<T>::value;
        bool hasAssignmentOperator = std::is_copy_assignable<T>::value && !std::has_trivial_copy_assign<T>::value;
        bool hasCopyConstructor = std::is_copy_constructible<T>::value && !std::tr1::has_trivial_copy<T>::value;


       asDWORD flags = asOBJ_VALUE | asOBJ_APP_CLASS;
       if(hasConstructor) flags = flags | asOBJ_APP_CLASS_CONSTRUCTOR;
       if(hasDestructor) flags = flags | asOBJ_APP_CLASS_DESTRUCTOR;
       if(hasAssignmentOperator) flags = flags | asOBJ_APP_CLASS_ASSIGNMENT;
       if(hasCopyConstructor) flags = flags | asOBJ_APP_CLASS_COPY_CONSTRUCTOR;

       int r = engine->RegisterObjectType(name.c_str(), sizeof(T), flags ); assert( r >= 0 );
}

Yes! These were the template functions I remembered reading about.

I'll make some tests on my own and put together a standard helper function. With the pre-C++11 tr1 implementation it may be possible to get a pretty good coverage even for older compilers.

There will still be some flags that needs to be provided manually though, e.g. asOBJ_APP_CLASS_ALLINTS, asOBJ_APP_CLASS_ALLFLOATS, and asOBJ_APP_CLASS_ALIGN8, as don't believe there are any template functions capable of determining these traits.

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

I have seen something that might be a problem here.

If a class that does not define any constructors contains a member variable that does define them then these traits return that the class with the member variable actually does have these functions (even though it does not).


struct TestA {
TestA() {}
};

struct TestB {
TestA a;
};

bool hasConstructor =  std::is_default_constructible<TestB>::value && !std::has_trivial_default_constructor<TestB>::value; // hasConstructor == true

I have a feeling that this is something that angelscript would not like during automatic registration.

Chances are that this type of class really should be registered with asOBJ_APP_CLASS_CONSTRUCTOR. It really depends on how the C++ compiler would handle the class in the native calling conventions, and not my implementation in AngelScript. I'll need to make some tests with the native calling conventions to be sure.

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

This topic is closed to new replies.

Advertisement