non basetype return values 1.9.2 bails out

Started by
6 comments, last by zola 19 years, 6 months ago
Hi I have a problem in my app. when I call script methods that have return types of basic types (int float etc.) every thing works fine. but when calling script methods with return value of some registered type execution of that methods fails. Example: script

Vector3 getVector()
{
  Vector3 v;
  v.x=1;
  v.y=2;
  v.z=3;
  return v;
}
the vector class is registered like this:

class Vector3
{
public :
inline Vector3(){}

inline Vector3( f32 fX, f32 fY, f32 fZ ) 
: x( fX ), y( fY ), z( fZ ){}
float x,y,z;
}

//registration

r=engine->RegisterObjectType("Vector3",sizeof(Vector3),asOBJ_CLASS_CDA);assert(r>=0);
r=engine->RegisterObjectProperty("Vector3","float x",offsetof(Vector3,x));assert(r>=0);
r=engine->RegisterObjectProperty("Vector3","float y",offsetof(Vector3,y));assert(r>=0);
r=engine->RegisterObjectProperty("Vector3","float z",offsetof(Vector3,z));assert(r>=0);

when i call context-Execute() my app bails out with an access violation error. I can use the Vector class in script methods without any problems even constructors work like a charm. The only exception is that i can't return them. any ideas what might cause that? regards Tom
Advertisement
Hmm, I'm not sure. It might be a bug in AngelScript. I'll make some tests and come back to you.

Meanwhile I would like to point out that you've made a minor error when registering the class: You used asOBJ_CLASS_CDA, but your class only has the constructor defined so you should really use asOBJ_CLASS_C. In this case it doesn't make a difference because all classes larger than 8 bytes are treated the same by MSVC++ but you might have a problem with other classes, especially if you intend to port your application to other platforms/compilers.

Be right back...

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

Bad luck. I wrote a test program and I wasn't able to reproduce the problem, neither in debug mode nor in release mode. I used version 1.9.2.

#include "utils.h"#define TESTNAME "TestVector3"class Vector3{public:	inline Vector3() {}	inline Vector3( float fX, float fY, float fZ ) : x( fX ), y( fY ), z( fZ ) {}	float x,y,z;};static char *script ="Vector3 TestVector3()  \n""{                      \n""  Vector3 v;           \n""  v.x=1;               \n""  v.y=2;               \n""  v.z=3;               \n""  return v;            \n""}                      \n";bool TestVector3(){	bool fail = false;	int r;	asIScriptEngine *engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);	r = engine->RegisterObjectType("Vector3",sizeof(Vector3),asOBJ_CLASS_C);assert(r>=0);	r = engine->RegisterObjectProperty("Vector3","float x",offsetof(Vector3,x));assert(r>=0);	r = engine->RegisterObjectProperty("Vector3","float y",offsetof(Vector3,y));assert(r>=0);	r = engine->RegisterObjectProperty("Vector3","float z",offsetof(Vector3,z));assert(r>=0);	Vector3 v;	engine->RegisterGlobalProperty("Vector3 v", &v);	COutStream out;	engine->AddScriptSection(0, TESTNAME, script, strlen(script));	r = engine->Build(0, &out);	if( r < 0 )	{		printf("%s: Failed to build\n", TESTNAME);		fail = true;	}	else	{		r = engine->ExecuteString(0, "v = TestVector3();", &out);		if( r < 0 )		{			printf("%s: ExecuteString() failed %d\n", TESTNAME, r);			fail = true;		}		if( v.x != 1 || v.y != 2 || v.z != 3 )		{			printf("%s: Failed to assign correct Vector3\n", TESTNAME);			fail = true;		}	}	engine->Release();	return fail;}


I even tried registering the type with asOBJ_CLASS_CDA, but that didn't change anything. And neither should it, because internally AngelScript treats all registered types equally. The flag is only important when interacting with application registered functions/methods.

Could you give the above test a try and see if it works on your machine? As far as I know the only difference between our setup is that you use MSVC++ 7 while I use MSVC++ 6.

I probably should install MSVC++ 7 as well, now that I have it, but I'm in the middle of a project and don't want to risk incompatibility between versions.

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

Yep Your method works :)

But I did not give You my complete setup.
Here's a test that doesn't work
#include "utils.h"#include <math.h>#include <new>#define TESTNAME "TestVector3"#define asFUNCTION4(c,r,f,p) asFunctionPtr((asFUNCTION_t)( (r(*)p)c::f) )class Vector3{public:	inline Vector3() {}	inline Vector3( float fX, float fY, float fZ ) : x( fX ), y( fY ), z( fZ ) {}	inline Vector3(const Vector3& other){ x=other.x; y=other.y; z=other.z; }	float x,y,z;	// asBEHAVE_CONSTRUCT	static void initVector3(Vector3* me);	// asBEHAVE_CONSTRUCT	static void initVector3(float x, float y, float z, Vector3* me);	// asBEHAVE_CONSTRUCT	static void initVector3(const Vector3& other, Vector3* me); 	// asBEHAVE_DESTRUCT	static void destroyVector3(Vector3* me);	// asBEHAVE_ASSIGNMENT	static Vector3& copyVector3(Vector3& left, Vector3& right);};// asBEHAVE_CONSTRUCTvoid Vector3::initVector3(Vector3* me){	new(me) Vector3(0,0,0);}// asBEHAVE_CONSTRUCTvoid Vector3::initVector3(float x, float y, float z, Vector3* me){	new(me) Vector3(x,y,z);}// asBEHAVE_CONSTRUCTvoid Vector3::initVector3(const Vector3& other, Vector3* me){	new(me) Vector3(other);}// asBEHAVE_DESTRUCTvoid Vector3::destroyVector3(Vector3* me){	me->~Vector3();}// asBEHAVE_ASSIGNMENTVector3& Vector3::copyVector3(Vector3& other, Vector3& me){	me=other;	return me;}static char *script ="Vector3 TestVector3()  \n""{                      \n""  Vector3 v;           \n""  v.x=1;               \n""  v.y=2;               \n""  v.z=3;               \n""  return v;            \n""}                      \n";bool TestVector3(){	bool fail = false;	int r;	asIScriptEngine *engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);	r = engine->RegisterObjectType("Vector3",sizeof(Vector3),asOBJ_CLASS_CDA);assert(r>=0);	r = engine->RegisterObjectProperty("Vector3","float x",offsetof(Vector3,x));assert(r>=0);	r = engine->RegisterObjectProperty("Vector3","float y",offsetof(Vector3,y));assert(r>=0);	r = engine->RegisterObjectProperty("Vector3","float z",offsetof(Vector3,z));assert(r>=0);	r=engine->RegisterObjectBehaviour("Vector3",asBEHAVE_CONSTRUCT,"void initVector3()",asFUNCTION4( Vector3,void,initVector3,(Vector3*) ),asCALL_CDECL_OBJLAST); assert( r >= 0 );	r=engine->RegisterObjectBehaviour("Vector3",asBEHAVE_CONSTRUCT,"void initVector3(float,float,float)",asFUNCTION4( Vector3,void,initVector3,(float,float,float,Vector3*) ),asCALL_CDECL_OBJLAST); assert( r >= 0 );	r=engine->RegisterObjectBehaviour("Vector3",asBEHAVE_CONSTRUCT,"void initVector3(const Vector3&)",asFUNCTION4( Vector3,void,initVector3,(const Vector3&,Vector3*) ),asCALL_CDECL_OBJLAST); assert( r >= 0 );	r=engine->RegisterObjectBehaviour("Vector3",asBEHAVE_DESTRUCT,"void destroyVector3()",asFUNCTION4( Vector3,void,destroyVector3,(Vector3*) ),asCALL_CDECL_OBJLAST); assert( r >= 0 );	r=engine->RegisterObjectBehaviour("Vector3",asBEHAVE_ASSIGNMENT,"Vector3& copyVector3(Vector3& o)",asFUNCTION4( Vector3,Vector3,copyVector3,(Vector3&,Vector3&)),asCALL_CDECL_OBJLAST); assert( r >= 0 );	Vector3 v;	engine->RegisterGlobalProperty("Vector3 v", &v);	COutStream out;	engine->AddScriptSection(0, TESTNAME, script, strlen(script));	r = engine->Build(0, &out);	asIScriptContext *ctx = 0;	r = engine->CreateContext(&ctx);	int funcID = engine->GetFunctionIDByDecl(0,"Vector3 TestVector3()");	ctx->Prepare(funcID);	ctx->Execute();	r=ctx->GetReturnValue((asDWORD*)&v,int(ceil(sizeof(v)/4.0)));	engine->Release();	return fail;}


It bails out when the constructor is called. So I guess I'm doing something complete wrong here. Btw the error does not occure if I use the ExecuteString method your using in the tests.

Maybe You see where my missconception is.

Thanks for Your time
Tom
Ok, now I see the problem.

1. You still register the type with asOBJ_CLASS_CDA. It should be asOBJ_CLASS_C. It doesn't matter that you register a destructor and assignment operator, it's what the C++ class has defined that determines what flag to use.

2. You are registering a destructor and assignment behaviour function, but they don't do anything that AngelScript wouldn't do by itself. You can omit those for improved performance.

3. I'm afraid I haven't been able to make the interface with script functions very easy to use in this case. What is happening is that AngelScript is expecting you to allocate the memory for the returned object and pass a pointer to it as the first parameter. This is exactly how C++ works.

	asIScriptContext *ctx;	engine->CreateContext(&ctx);	ctx->Prepare(engine->GetFunctionIDByDecl(0, "Vector3 TestVector3()"));	Vector3 retVal;	Vector3 *retAddress = &v;	ctx->SetArguments(0, (asDWORD*)&retAddress, 1);	ctx->Execute();


ctx->GetReturnValue() will in this case return the same address that you sent in.

The scenario is true for all registered object types, independently of how they are registered. I'm trying to come up with a good way of simplifying the process of calling script functions. I will probably end up writing a special function with inline assembly that copies arguments sent to it to the context stack, much like the way AngelScript calls application registered functions.

NOTE: I did discover a bug in this case, and that is the SetArguments() fail, you'll have to remove the following two lines from asCContext::SetArguments() to get it working correctly:

	if( stackPos >= argumentsSize ) return asERROR;	if( stackPos + count - 1 >= argumentsSize ) return asERROR;



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

Ah, ok I begin to understand, thanks.

This is then the way to go:

-- if I have a script function that returns a registered object I'll have to prepare the memory that will hold the data myself. Using ctx->SetArgument i tell the engine where to store the data. The return value must be in this case the first argument by convention.

( I assume in C++ the compiler does this for me then :)
what about the case when i don't use the return value of a function? does the c compiler allocate room for the return value too? I'm just curious.)

-- if I have a script function that returns basetypes I get them with ctx->GetReturnValue, because the return value can be stored in some register no memory must be provided.

I don't know if this is a correct explanation, just guessing here, I'm not a compiler guy ;)

Anyway what You suggested works just fine, thanks again.

Cheers
Tom
You are correct in all of your assumptions, including the ones about the C++ compiler.

If a C++ function returns an object by value (and C++ can't find a way to store it in the registry) C++ will allocate space on the stack and pass the memory address as the first parameter. This is done even if the return value isn't used. If the return type has a destructor it is also called before terminating the statement.

In AngelScript I've simplified it a little as I require all registered types to be returned in memory like this. C++ on the other hand only return objects larger than 8 bytes or that have a defined constructor, destructor, or assignment operator in memory. Small simple structs are returned just like primitive types in the EDX:EAX registers. The most problematic part is that not all compilers do it the same way, GNU C for example doesn't care if the class has a constructor, or assignment operator, it only checks if the class has a destructor.

If you are interested in these things you can read the article I wrote about calling conventions. It's not very descriptive but it shows the differences between calling conventions and how MSVC++ and GNU C differs in handling them.

One more thing that you might run in to later. The memory that you pass to the script engine is assumed to be uninitialized. Should you for example pass a pointer to an initialized string the allocated buffer will be lost as the script function overwrites the memory. The value that is returned should be destroyed as normal.

If I'm able to do what I want to do, the C++ compiler will be responsible for all this. The application will just get a function pointer from the context and call it with all parameters necessary. It will then be as easy to call script functions as functions dynamically loaded from a dll.

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 link to the article. I feel that I should have at least a rudimentary knowledge about the approaches the various compilers take.

Quote:Original post by WitchLord
If I'm able to do what I want to do, the C++ compiler will be responsible for all this. The application will just get a function pointer from the context and call it with all parameters necessary. It will then be as easy to call script functions as functions dynamically loaded from a dll.


Aye :) cheers to that! Even though the usage is quite easy today this should really give the last sceptic a reason to take a close look at angelscript.

Keep up the good work.
Tom

This topic is closed to new replies.

Advertisement