• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
Violet CLM

Application function returning a funcdef handle crashes when called in AS

17 posts in this topic

Using the latest AS release. Relevant application code:
typedef void (*AiProcPtr) (int);

//...

AiProcPtr getBehaviorPointer(Omonster* obj) {
	return AiProcPtr((*(unsigned int*)obj) & 0x7FFFFFFF); //the property is at offset zero within the Omonster class
}
void setBehaviorPointerToASFunc(unsigned int func, Omonster* obj) {
	obj->procPtr = (AiProcPtr)(func | 0x80000000); //the top bit is used to identify the pointer as pointing to an AngelScript function, not a native application function. it's irrelevant here.
}

//...

ASengine->RegisterObjectType("jjOBJ", sizeof(Omonster), asOBJ_REF | asOBJ_NOCOUNT  );
ASengine->RegisterFuncdef("void jjBEHAVIOR(jjOBJ@)");
ASengine->RegisterObjectMethod("jjOBJ", "jjBEHAVIOR@ getBehavior()", asFUNCTION(getBehaviorPointer), asCALL_CDECL_OBJLAST);
ASengine->RegisterObjectMethod("jjOBJ", "void setBehavior(jjBEHAVIOR@)", asFUNCTIONPR(setBehaviorPointerToASFunc, (unsigned int, Omonster*), void), asCALL_CDECL_OBJLAST);
 
Relevant AngelScript code:
void TriggerableBlock(jjOBJ@ obj) {
}

//...

void checkBehavior() {
	jjOBJ@ foo = jjObjects[1]; //indexed property accessor that returns a jjOBJ/Omonster
	if (foo.getBehavior() is null) foo.setBehavior(TriggerableBlock);
}
The seventh time or so that checkBehavior() is called from within the application, the application totally crashes, having tried to read memory from an address either much too small or much too large. Depending on the context getBehavior() is called in -- a global asIScriptContext*, one created just for calling that one AngelScript function that one time, whatever -- sometimes it'll only take four calls to getBehavior() or even just one, but it's seven or eight in this particular context. The crashing does not occur if getBehavior() returns 0, but it does for all other numbers. I get similar results with a simple application function to return a constant number, e.g. 7, treated by AngelScript as returning a funcdef. getBehavior() does work, though, prior to the crash... after foo.setBehavior(TriggerableBlock); is used, (foo.getBehavior() is TriggerableBlock) evaluates to true.
 
So what am I doing wrong? How do I get a function to return a funcdef, without everything crashing? As far as I can tell from debug information, the crash sometimes takes place somewhere in asCContext::Prepare(asIScriptFunction *func), somewhere around m_initialFunction->AddRef();. Other times it seems to happen while calling getBehavior(). I really don't know what's going on.
 
 
 
Sidenote: If I name the function set_behavior instead of setBehavior, I can have overloaded set_behavior functions written as functions, but AngelScript refuses to let me use them as property accessors? foo.set_behavior(TriggerableBlock); is fine but foo.behavior = TriggerableBlock; gives a compilation error. Accessors can't be overloaded? sad.png
0

Share this post


Link to post
Share on other sites

The returned funcdef handle must be an asIScriptFunction pointer. AngelScript will call methods on the asIScriptFunction (or really the underlying asCScriptFunction object), so if you return a pointer anything else you will have unexpected behaviours and even application crashes. 

 

 

On the side note: Correct, accessors cannot be overloaded. Only one set and one get accessor is currently accepted for each virtual property.

0

Share this post


Link to post
Share on other sites

I think then that the problem is that you're not updating the refCounter of the asIScriptFunction object properly.

 

setBehaviorPointerToASFunc() needs to release the handle of the old object stored in obj->procPtr since it is being overwritten.

 

getBehaviorPointer() needs to increase the refCounter of the handle that is returned, since AngelScript will release it later.

 

Also, your setBehaviourPointerToASFunc stores the pointer in a member, but your getBehaviourPointer doesn't use that member. This looks strange, but if you garantee that the procPtr is the first member of the Omonster class, and the Omonster class doesn't have any virtual functions then it should be ok.

0

Share this post


Link to post
Share on other sites
Hmm... I guess that makes sense. Functions having reference counters seems strange, but when funcdefs get involved, okay. I'll give it a try.

Is it possible to write casts involving funcdefs? I haven't been able to get any to work.
0

Share this post


Link to post
Share on other sites

It may not make sense for C++ functions that don't change, but the script functions are actually objects that are created and destroyed, so here it is necessary to keep track of the reference count so that the objects aren't destroyed prematurely.

 

Casts involving funcdefs work just like other casts. What exactly is it that you've tried but didn't work.

0

Share this post


Link to post
Share on other sites

Casts involving funcdefs work just like other casts. What exactly is it that you've tried but didn't work.

C++:
	ASengine->RegisterFuncdef("void jjBEHAVIOR(jjOBJ@)");

	ASengine->RegisterFuncdef("void DifferentFunctionPointer()");
	ASengine->RegisterObjectBehaviour("jjBEHAVIOR", asBEHAVE_IMPLICIT_REF_CAST, "DifferentFunctionPointer@ a()", asFUNCTION(someConversionFunction), asCALL_CDECL_OBJLAST);
	ASengine->RegisterGlobalFunction("void someFunctionTakingAFunctionPointer(DifferentFunctionPointer@)", asFUNCTION(someFunction), asCALL_CDECL);

	ASengine->RegisterObjectType("MiscRefObject", 0, asOBJ_REF | asOBJ_NOCOUNT  );
	ASengine->RegisterObjectBehaviour("jjBEHAVIOR", asBEHAVE_IMPLICIT_REF_CAST, "MiscRefObject@ b()", asFUNCTION(someConversionFunction), asCALL_CDECL_OBJLAST);
	ASengine->RegisterGlobalFunction("void someFunctionTakingAnObject(MiscRefObject@)", asFUNCTION(someFunction), asCALL_CDECL);

	ASengine->RegisterObjectBehaviour("jjBEHAVIOR", asBEHAVE_IMPLICIT_VALUE_CAST, "uint d()", asFUNCTION(someConversionFunction), asCALL_CDECL_OBJLAST);
	ASengine->RegisterGlobalFunction("void someFunctionTakingAUint(uint)", asFUNCTION(someFunction), asCALL_CDECL);
AngelScript:
void TriggerableBlock(jjOBJ@ obj) {
	//do stuff
}

//...

	someFunctionTakingAFunctionPointer(TriggerableBlock);
	someFunctionTakingAnObject(TriggerableBlock);
	someFunctionTakingAUint(TriggerableBlock);
Each gives an error akin to No matching signatures to 'someFunctionTakingAFunctionPointer(::TriggerableBlock)'. Typing "@TriggerableBlock" instead of "TriggerableBlock" gives the same results.

If I don't use _IMPLICIT_ casts:
	someFunctionTakingAFunctionPointer(cast<DifferentFunctionPointer>(TriggerableBlock));
	someFunctionTakingAnObject(cast<MiscRefObject>(TriggerableBlock));
	someFunctionTakingAUint(cast<uint>(TriggerableBlock));
The first two give No matching signatures to 'someFunctionTakingAFunctionPointer(<unrecognized token>)' and No matching signatures to 'someFunctionTakingAnObject(<unrecognized token>)'. The last gives Illegal target type for reference cast.

Also,
ASengine->RegisterGlobalProperty("DifferentFunctionPointer f", 0);
just gives me a registration error.
0

Share this post


Link to post
Share on other sites

It's illegal to register additional behaviours or methods on funcdefs. The funcdefs are built-in types and cannot be modified. It would appear that I need to add verifications for this so that AngelScript doesn't return success in this is attempted.

 

I need to look into the reason behind the error 'No matching signatures to 'someFunctionTakingAFunctionPointer(<unrecognized token>)'. It might be a bug in the code.

 

The error message Illegal target type for the uint is because a primitives and other value types are not reference types, thus are not acceptable for reference casts. 

 

The RegisterGlobalProeprty() fails because you're not informing the address of the property that you're registering. If you're just registering a dummy, then at least give a false address different from 0, e.g.:

 

ASengine->RegisterGlobalProperty("DifferentFunctionPointer f", (void*)1);

 

Regards,

Andreas

0

Share this post


Link to post
Share on other sites

It's illegal to register additional behaviours or methods on funcdefs. The funcdefs are built-in types and cannot be modified. It would appear that I need to add verifications for this so that AngelScript doesn't return success in this is attempted.

But isn't RegisterObjectBehavior the function used to allow casting of a reference type, as shown here? How do I cast a funcdef, if I can't use asBEHAVE_IMPLICIT_REF_CAST, if I can't register a behavior?

The RegisterGlobalProeprty() fails because you're not informing the address of the property that you're registering. If you're just registering a dummy, then at least give a false address different from 0

Nope,
	ASengine->RegisterFuncdef("void DifferentFunctionPointer()");
    ASengine->RegisterGlobalProperty("DifferentFunctionPointer f", (void*)1);
still gives a registration error. EDIT: Oh, wait, I needed to add an @. Let's see if I can make this work... EDIT2: Nope, no help. I can register arbitrary numbers for funcdefs to point to, but if I try to pass one of those arbitrary fundefs as an argument to a function, the program crashes. Apparently passing a pointer in AngelScript involves trying to read the data being pointed to. Edited by Violet CLM
0

Share this post


Link to post
Share on other sites

Yes, RegisterObjectBehaviour() is used for that. But only for application types. Funcdefs are not application types, they are built-in types and cannot be modified by the application. 

 

I'll look into the reason behind RegisterGlobalProperty failing when attempting to register a funcdef property.

0

Share this post


Link to post
Share on other sites

Yes, RegisterObjectBehaviour() is used for that. But only for application types. Funcdefs are not application types, they are built-in types and cannot be modified by the application.

I understand that, but earlier you said "Casts involving funcdefs work just like other casts. What exactly is it that you've tried but didn't work." I showed you what I've tried, and you agreed that it doesn't work, but that doesn't get me any closer to knowing what does work.

I'll look into the reason behind RegisterGlobalProperty failing when attempting to register a funcdef property.

No, the registering works just fine. It's only the passing as an argument that crashes.
0

Share this post


Link to post
Share on other sites

RegisterGlobalProperty() with funcdef works correctly, what was missing in your code was the @. Here's an example on how to register a property with handle to a funcdef and assigning a function pointer to it.

engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
engine->SetMessageCallback(asMETHOD(COutStream, Callback), &out, asCALL_THISCALL);
 
asIScriptFunction *f = 0;
engine->RegisterFuncdef("void myfunc()");
r = engine->RegisterGlobalProperty("myfunc @f", &f);
if( r < 0 )
  TEST_FAILED;
 
mod = engine->GetModule("mod", asGM_ALWAYS_CREATE);
mod->AddScriptSection("test",
  "void func() {} \n");
mod->Build();
 
r = ExecuteString(engine, "@f = func; \n", mod);
if( r != asEXECUTION_FINISHED )
  TEST_FAILED;
 
if( f == 0 )
  TEST_FAILED;
if( strcmp(f->GetName(), "func") != 0 )
  TEST_FAILED;
 
f->Release();
f = 0;
 
engine->Release();

When I said that casts for funcdefs work just like other casts, I meant in the script language. I.e, you can use the cast<type>(expr) operator on them. Here's a working example with both explicit and implicit casts:

engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
engine->SetMessageCallback(asMETHOD(COutStream, Callback), &out, asCALL_THISCALL);
engine->RegisterGlobalFunction("void assert(bool)", asFUNCTION(Assert), asCALL_GENERIC);
 
mod = engine->GetModule("mod", asGM_ALWAYS_CREATE);
mod->AddScriptSection("test",
  "funcdef void myfunc1(); \n"
  "funcdef void myfunc2(); \n"
  "funcdef void myfunc3(); \n"
  "bool called = false; \n"
  "void func() { called = true; } \n"
  "void main() \n"
  "{ \n"
  "  myfunc1 @f1 = func; \n"
  "  myfunc2 @f2 = cast<myfunc2>(f1); \n" // explicit cast
  "  myfunc3 @f3 = f2; \n"                // implicit cast
  "  assert( f1 is f2 ); \n"
  "  assert( f2 is f3 ); \n"
  "  assert( f3 is func ); \n"
  "  f3(); \n"
  "  assert( called ); \n"
  "} \n");
r = mod->Build();
if( r < 0 )
  TEST_FAILED;
 
r = ExecuteString(engine, "main()", mod);
if( r != asEXECUTION_FINISHED )
  TEST_FAILED;
 
engine->Release();

Here's an example of a registered function that receives a function pointer:

bool receivedFuncPtrIsOK = false;
void ReceiveFuncPtr(asIScriptFunction *funcPtr)
{
  if( funcPtr == 0 ) return;
 
  if( strcmp(funcPtr->GetName(), "test") == 0 ) 
    receivedFuncPtrIsOK = true;
 
  funcPtr->Release();
}
 
//---------------------
r = engine->RegisterFuncdef("void AppCallback()");

r = engine->RegisterGlobalFunction("void ReceiveFuncPtr(AppCallback @)", asFUNCTION(ReceiveFuncPtr), asCALL_CDECL); assert( r >= 0 );

script = 
  "void main() \n"
  "{ \n"
  " AppCallback @func = @test; \n"
  "   func(); \n"
  "   ReceiveFuncPtr(func); \n"
  "} \n"
  "void test() \n"
  "{ \n"
  "} \n";
mod->AddScriptSection("script", script);
r = mod->Build();
if( r < 0 )
  TEST_FAILED;
 

r = ExecuteString(engine, "main()", mod);
if( r != asEXECUTION_FINISHED )
TEST_FAILED;
 
if( !receivedFuncPtrIsOK )
  TEST_FAILED;
 



I've confirmed that engine->RegisterObjectBehaviour("jjBEHAVIOR", asBEHAVE_IMPLICIT_REF_CAST, "DifferentFunctionPointer@ a()", asFUNCTION(0), asCALL_CDECL_OBJLAST); doesn't return an error. I'll have have that fixed.

 

I've now fixed this in revision 1657.

Edited by Andreas Jonsson
0

Share this post


Link to post
Share on other sites
Maybe I should back up a bit, and express the issue in terms of what I would like the result to be, rather than in terms of what happens when I try specific things in order to get there. I want the following code to work in AngelScript:
void main() {
  someObject.someProperty = foo;
  someObject.someProperty = bar;
}

void foo() {
  //do stuff
}
Where "bar" is defined within the application, and is an arbitrary number, not even necessarily a pointer to a location that exists within the application's allotted memory. Maybe 0, maybe 1, maybe 0x87654321. I don't really care what type it has within AngelScript so long as I can send that arbitrary number back into the application again. I think I can make this other style work:
void main() {
  someObject.setProperty(foo);
  someObject.setProperty(bar);
}

void foo() {
  //do stuff
}
That works because methods, unlike property accessors, may be successfully overloaded. That's not nearly as pretty, though, and it makes retrieving the value (and being able to compare it both to "foo" and to "bar") pretty cumbersome. Edited by Violet CLM
0

Share this post


Link to post
Share on other sites

The latter is the only thing that will work at the moment. The scriptany add-on does it this way, and allows you to store both value types and reference types within a single generic container.

0

Share this post


Link to post
Share on other sites
Thanks for all the help so far... I'm trying some things out and will have more to report later. In the meantime, speaking of funcdefs, one of my collaborators is having an issue with namespaces. The following code apparently worked a couple versions ago, but doesn't now:
funcdef void simpleFuncDef();

namespace foo {
void simpleFunction() { }
}

void takeSimpleFuncDef(simpleFuncDef@ f) { }

void main() {
takeSimpleFuncDef(foo::simpleFunction);
}

//ERR : No matching signatures to 'takeSimpleFuncDef(foo::simpleFunction)'
//INFO : Candidates are:
//INFO : void takeSimpleFuncDef(simpleFuncDef@)
Likewise:
funcdef void simpleFuncDef();

namespace foo {
void simpleFunction() { }
}

void main() {
simpleFuncDef@ bar = foo::simpleFunction;
}

//ERR : Can't implicitly convert from '<null handle>' to 'simpleFuncDef@&'.
In each case, adding or removing "@" to "foo::simpleFunction" changes nothing. Is this the intended behavior, or was it working before and not now?
0

Share this post


Link to post
Share on other sites

Maybe I should back up a bit, and express the issue in terms of what I would like the result to be, rather than in terms of what happens when I try specific things in order to get there. I want the following code to work in AngelScript:

void main() {
  someObject.someProperty = foo;
  someObject.someProperty = bar;
}

void foo() {
  //do stuff
}
Where "bar" is defined within the application, and is an arbitrary number, not even necessarily a pointer to a location that exists within the application's allotted memory. Maybe 0, maybe 1, maybe 0x87654321. I don't really care what type it has within AngelScript so long as I can send that arbitrary number back into the application again.

For the record, this seems to be working now. smile.png Solution:
  • Register all the arbitary numbers ("bar") as an enum, since that's the easiest way to define true constants in AngelScript, as opposed to read-only values.
  • As ever, register a single funcdef for the AngelScript functions ("foo").
  • Create and register a pointer-length value type ("someproperty"). Overload its opAssign and opEquals methods to accept both funcdef instances and enum instances, and also register an implicit value cast from value type to enum. (That last one not really needed, but it means that other functions need only be overloaded twice -- funcdef or enum -- not thrice -- funcdef, enum, or value type. Less bother.)
  • Add checks in a whole lot of application-internal places to determine when and where addRef() and release() need to be called, including the constructor, destructor, and opAssign for the value type.
Thanks again for your time, both in helping me out and in the more general development process. smile.png Edited by Violet CLM
0

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  
Followers 0