• Create Account

## Application function returning a funcdef handle crashes when called in AS

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.

17 replies to this topic

### #1Violet CLM  Members

Posted 21 June 2013 - 01:53 AM

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?

### #2Andreas Jonsson  Moderators

Posted 21 June 2013 - 02:20 PM

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.

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

### #3Violet CLM  Members

Posted 21 June 2013 - 03:39 PM

It is a asIScriptFunction*, though, as I said... (foo.getBehavior() is TriggerableBlock) evaluates to true.

### #4Andreas Jonsson  Moderators

Posted 21 June 2013 - 05:28 PM

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.

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

### #5Violet CLM  Members

Posted 21 June 2013 - 11:50 PM

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.

### #6Andreas Jonsson  Moderators

Posted 22 June 2013 - 09:27 AM

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.

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

### #7Violet CLM  Members

Posted 25 June 2013 - 08:11 PM

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.

### #8Andreas Jonsson  Moderators

Posted 26 June 2013 - 05:55 PM

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

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

### #9Violet CLM  Members

Posted 26 June 2013 - 07:53 PM

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, 26 June 2013 - 10:08 PM.

### #10Andreas Jonsson  Moderators

Posted 27 June 2013 - 04:54 PM

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.

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

### #11Violet CLM  Members

Posted 27 June 2013 - 05:26 PM

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.

### #12Andreas Jonsson  Moderators

Posted 27 June 2013 - 06:05 PM

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);
"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);
"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;
{
if( funcPtr == 0 ) return;

if( strcmp(funcPtr->GetName(), "test") == 0 )

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"
"} \n"
"void test() \n"
"{ \n"
"} \n";
r = mod->Build();
if( r < 0 )
TEST_FAILED;

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

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, 27 June 2013 - 07:20 PM.

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

### #13Violet CLM  Members

Posted 27 June 2013 - 08:00 PM

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, 27 June 2013 - 08:03 PM.

### #14Andreas Jonsson  Moderators

Posted 27 June 2013 - 08:14 PM

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.

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

### #15Violet CLM  Members

Posted 02 July 2013 - 03:34 PM

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?

### #16Andreas Jonsson  Moderators

Posted 02 July 2013 - 04:16 PM

It appears to be a bug. I'll look into it.

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

### #17Andreas Jonsson  Moderators

Posted 02 July 2013 - 06:11 PM

I've fixed the bug in revision 1660.

Thanks,

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

### #18Violet CLM  Members

Posted 09 July 2013 - 02:17 PM

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. 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.

Edited by Violet CLM, 09 July 2013 - 02:48 PM.

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.