Sign in to follow this  
Heuristics

This changes when returning value objects

Recommended Posts

Heuristics    147

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());}
}

 

 

 

Share this post


Link to post
Share on other sites
Heuristics    147

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.

Share this post


Link to post
Share on other sites
WitchLord    4677

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.

Share this post


Link to post
Share on other sites
Heuristics    147

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.

Edited by Heuristics

Share this post


Link to post
Share on other sites
Heuristics    147

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 );
}
Edited by Heuristics

Share this post


Link to post
Share on other sites
WitchLord    4677

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. 

Share this post


Link to post
Share on other sites
Heuristics    147

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.

Share this post


Link to post
Share on other sites
WitchLord    4677

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.

Share this post


Link to post
Share on other sites
Heuristics    147

Oh, that is worrying. Is it possible for end user like myself know how a value type should be registered? Is there a method end users can use to deduce how it should be done in all cases? 

Share this post


Link to post
Share on other sites
WitchLord    4677

Until I'm able to confirm otherwise the way to register the value types is to follow the documentation. Fortunately the case you reported is probably not that frequently occurring, and can easily be remedied by adding an explicit constructor to the class if needed.

 

Unfortunately the ABI is not something that is standardized. Every combination of compiler, operative system and CPU has its own ABI. With AngelScript I keep a common interface so that a type can be registered the same way on all platforms, but unfortunately the C++ language doesn't quite provide all the information needed to automatically determine how the compiler handles a type in the ABI.

 

The new template functions introduced with C++11 does help a bit, but it seems that they are not perfect either.

 

In this case the best would probably be to have some code generator that can parse the C++ code and determine the correct way to register the class that way. Some attempts to implement something like this has been done before, but I'm not sure if any of these attempts managed a perfect implementation, nor do I know of any that is maintained to this day.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this