Jump to content

  • Log In with Google      Sign In   
  • Create Account

- - - - -

This changes when returning value objects


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
11 replies to this topic

#1 Heuristics   Members   -  Reputation: 147

Like
0Likes
Like

Posted 11 March 2013 - 02:41 AM

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

 

 

 



Sponsor:

#2 Andreas Jonsson   Moderators   -  Reputation: 3376

Like
0Likes
Like

Posted 11 March 2013 - 06:48 AM

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

#3 Heuristics   Members   -  Reputation: 147

Like
0Likes
Like

Posted 11 March 2013 - 06:51 AM

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



#4 Heuristics   Members   -  Reputation: 147

Like
0Likes
Like

Posted 11 March 2013 - 07:56 AM

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.



#5 Andreas Jonsson   Moderators   -  Reputation: 3376

Like
1Likes
Like

Posted 11 March 2013 - 11:23 AM

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

#6 Heuristics   Members   -  Reputation: 147

Like
0Likes
Like

Posted 12 March 2013 - 03:22 AM

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, 12 March 2013 - 06:57 AM.


#7 Heuristics   Members   -  Reputation: 147

Like
0Likes
Like

Posted 12 March 2013 - 06:17 AM

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, 12 March 2013 - 06:19 AM.


#8 Andreas Jonsson   Moderators   -  Reputation: 3376

Like
0Likes
Like

Posted 12 March 2013 - 08:58 AM

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

#9 Heuristics   Members   -  Reputation: 147

Like
0Likes
Like

Posted 25 March 2013 - 12:10 PM

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.



#10 Andreas Jonsson   Moderators   -  Reputation: 3376

Like
0Likes
Like

Posted 25 March 2013 - 06:46 PM

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

#11 Heuristics   Members   -  Reputation: 147

Like
0Likes
Like

Posted 26 March 2013 - 02:47 AM

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? 



#12 Andreas Jonsson   Moderators   -  Reputation: 3376

Like
0Likes
Like

Posted 26 March 2013 - 04:30 PM

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.


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




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS