Advertisement

Return a String from a function

Started by August 27, 2009 12:13 PM
12 comments, last by WitchLord 15 years, 2 months ago
I have been using AS version 2.8.0 for some time now with the script string add on. I have been using functions of the format void ErrorString(std::string &str) { myMessage->SendError(str); } by registering them with the application like this r = engine->RegisterGlobalFunction("void Error(string ∈)", asFUNCTION(ErrorString), asCALL_CDECL); assert( r >= 0 ); I needed to create a function that will return the date as a string so I created the following std::string& Date() { std::string someDate; ... return someDate; } I then registered this with the script engine like this r = engine->RegisterGlobalFunction("string& Date()", asFUNCTION(Date), asCALL_CDECL); assert( r >= 0 ); Every time I call this function I get a core dump. Can someone point me to the correct method of returning a string from a function. I tried to pass a string from script into the function by reference like this void Date(std::string &theDate) { } But it doesn't update. It's like it is write protected. Thank you, Tony
You're passing a reference to a local variable. When your function returns, the variable that you created goes out of scope, and may or may not be overwritten. This results in undefined behavior.

Is there some reason you don't just return by value? Yes, it will copy the string, but it is the easiest, correct way to do it, and the least error prone. Its unlikely to be that much of a performance impact unless you're calling this function a *lot*.
Advertisement
That was my fault I actually declared someDate globally after fighting with this for a while.

I've tried by value as well. For example

std::string Date()
{
std::string someDate;
...
return someDate;

}

and registered it as

r = engine->RegisterGlobalFunction("string Date()", asFUNCTION(Date), asCALL_CDECL); assert( r >= 0 );

which also core dumps.
After reading some more examples (\sdk\samples\console) and other posts I realized that it is possible to receive an asCSriptString by reference in a function and treat it as an std::string. It is not possible to return an std::string to to the script though. I believe what is happen is the script tries to access the Release() function of an std::string which does exist and causes my app to crash.

I noticed there is a new string implementation addon in 2.17.0 that I believe would work for standard string called "scriptstdstring". I couldn't get this working out of the box in 2.8.0 though.


This is the code that worked for me. The asCScriptString class has also be renamed to CScriptString in later versions.

asCScriptString* GetDate()
{
time_t rawtime;
struct tm* timeinfo;
char buffer[80];

time(&rawtime);
timeinfo = localtime( &rawtime );
strftime(buffer,80,"%m/%d/%y",timeinfo);
asCScriptString *theDate = new asCScriptString(buffer);

return theDate;
}

and I registered the function as follows

r = engine->RegisterGlobalFunction("string& Date()", asFUNCTION(GetDate), asCALL_CDECL); assert( r >= 0 );



I had also mentioned that I had previously tried to modify a string that was passed by reference to my function which was not successful. I now realize that you need to identify the direction of data transfer when using reference types. I has just copied the format of the print example as shown below.

r = engine->RegisterGlobalFunction("void Print(string ∈)", asFUNCTION(PrintString), asCALL_CDECL); assert( r >= 0 );

the ∈ is not a parameter name. It informs the script engine that the parameter is to be passed by reference but not allowed to change. If you want to change the value it must be passed with &inout.

Sorry for the reprint of the docs I just thought it might help someone else out.
Quote: Original post by theoutfield
I believe what is happen is the script tries to access the Release() function of an std::string which does exist and causes my app to crash.


You're absolutely correct.

Quote: Original post by theoutfield
I noticed there is a new string implementation addon in 2.17.0 that I believe would work for standard string called "scriptstdstring". I couldn't get this working out of the box in 2.8.0 though.


The std::string registration was available before too, it was just not an official add-on. In version 2.8.0 you can find it in the test_feature/source/stdstring.cpp file.

I'm sorry I didn't respond sooner, but I'm glad you managed to figure out the problems on your own.

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

Isn't the object that's returned to a script by reference leaked then? We have a similar case in Warsow:
engine->RegisterGlobalFunction("cString &G_LoadFile( cString & )", asFUNCTION(G_LoadFile), asCALL_CDECL);

but when the following script lines are exec'ed:
cString rank = G_LoadFile (...);

the amount of memory used increases and never decreases again.
I guess we should have used handle instead of a reference there, but is there a way we can fix this without breaking the API?
Advertisement
Well spotted. I missed that.

If the function returns a reference to AngelScript, then AS will think that the function is the owner of the reference and will not release it. On the other hand, if the function return a handle, then AS will know that it must release it afterwards.

So the correct way to register the Date function that theoutfield showed is this:

r = engine->RegisterGlobalFunction("string@ Date()", asFUNCTION(GetDate), asCALL_CDECL); assert( r >= 0 );


Making the change to the registration will fix the memory leak without breaking anything else. The implementation of the function doesn't have to be modified, nor do the scripts that use the function.

You can change your registration to:

engine->RegisterGlobalFunction("cString @G_LoadFile( cString & )", asFUNCTION(G_LoadFile), asCALL_CDECL);


and still have the scripts call the function the same way:

cString rank = G_LoadFile (...);


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 a lot, we'll fix this bug in the next Warsow update.
Oh, there's another question concerning long-living objects and strings in particular. In the game, we use a pre-allocated pool of fixed size, where we allocate AS objects in linear fashion and then at the end of each frame, wind it off. The winding off process stops either at the start of the buffer or if it encounters a memory region that was not freed. The later usually happens when script writer assigns value to a global object (typically, a string).
Is there a way we can transparently detect this situation and use a different pool for global strings to prevent exhaustion of the temporary buffer? Or alternatively reallocate an object if gc detects it's a long-lived one?
Do you want to be able to allocate memory from a different pool depending on where the reference will be stored? I don't see how that would be possible since it is not known before hand who the final referer is.

It would be possible to reallocate the memory, but it would require updating all referers so that they point to the new memory instead. This would in turn require the traversal of all global variables to look for the ones that need to be updated.

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