Jump to content
  • Advertisement
Sign in to follow this  
sfb

Problem passing script-created instances to C++ objects.

This topic is 3290 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'll start off with the quick description of the problem. I have a script that creates a CGameSpawnRequestEvent and submits it to the CGameEventServer via that classes postEvent() method. When I call CGameEventServer's postEvent() with the object I have created in the script my application receives an exception from AngelScript from the CallSystemFunction method: Null pointer access. My event hierarchy is as follows: * IGameEvent ** CGameSpawnRequestEvent ** CGameUnspawnRequestEvent Here's how I bind CGameEventServer:
	void bindCGameEventServer() {
		asIScriptEngine *engine = ScriptManager::getInstance().getEngine();
		int r;
		nlinfo("Binding CGameEventServer");
		// Register Object Type
		engine->RegisterObjectType("CGameEventServer", sizeof(WWCOMMON::CGameEventServer), asOBJ_REF);

		// Register Behaviors, omit factory behavior so this interface cannot be created.
		engine->RegisterObjectBehaviour("CGameEventServer", asBEHAVE_ADDREF, "void f()", asMETHOD(asRefDummy,addRef), asCALL_THISCALL);
		engine->RegisterObjectBehaviour("CGameEventServer", asBEHAVE_RELEASE, "void f()", asMETHOD(asRefDummy,release), asCALL_THISCALL);

		r = engine->RegisterGlobalFunction("CGameEventServer &getGameEventServer()", asFUNCTION(getGameEventServer), asCALL_CDECL); nlassert(r>=0);
		r = engine->RegisterObjectMethod("CGameEventServer", "void processEventQueue()", asMETHODPR(WWCOMMON::CGameEventServer, processEventQueue, (void), void), asCALL_THISCALL); nlassert(r>=0);
		r = engine->RegisterObjectMethod("CGameEventServer", "void postEvent(IGameEvent@)", asMETHODPR(WWCOMMON::CGameEventServer, postEvent, (WWCOMMON::IGameEvent*), void), asCALL_THISCALL); nlassert(r>=0);
	}


Here's how I bind my events:
	template<class T>
	void registerIGameEvent(std::string typeName) {
		asIScriptEngine *engine = ScriptManager::getInstance().getEngine();
		int r;
		r = engine->RegisterObjectMethod(typeName.c_str(), "uint32 getPlayerID()", asMETHODPR(T, getPlayerID, (void) const, uint32), asCALL_THISCALL); nlassert(r>=0);
		r = engine->RegisterObjectMethod(typeName.c_str(), "void setPlayerID(uint32)", asMETHODPR(T, setPlayerID, (uint32), void), asCALL_THISCALL); nlassert(r>=0);
		r = engine->RegisterObjectMethod(typeName.c_str(), "uint16 getId()", asMETHODPR(T, getId, (void) const, uint16), asCALL_THISCALL); nlassert(r>=0);
	}

	void bindIGameEvent() {
		asIScriptEngine *engine = ScriptManager::getInstance().getEngine();
		nlinfo("Binding IGameEvent");
		//REGISTER_TYPE(WWCOMMON::IGameEvent, engine);
		engine->RegisterObjectType("IGameEvent", sizeof(WWCOMMON::IGameEvent), asOBJ_REF);

		// Register Behaviors, omit factory behavior so this interface cannot be created.
		engine->RegisterObjectBehaviour("IGameEvent", asBEHAVE_ADDREF, "void f()", asMETHOD(asRefDummy,addRef), asCALL_THISCALL);
		engine->RegisterObjectBehaviour("IGameEvent", asBEHAVE_RELEASE, "void f()", asMETHOD(asRefDummy,release), asCALL_THISCALL);

		registerIGameEvent<WWCOMMON::IGameEvent>("IGameEvent");
	}

	void bindCGameSpawnRequestEvent() {
		asIScriptEngine *engine = ScriptManager::getInstance().getEngine();
		int r;
		nlinfo("Binding CGameSpawnRequestEvent");		
		// Register Object Type
		engine->RegisterObjectType("CGameSpawnRequestEvent", sizeof(WWCOMMON::CGameSpawnRequestEvent), asOBJ_REF);
		
		// Register Behaviors.
		engine->RegisterObjectBehaviour("CGameSpawnRequestEvent", asBEHAVE_ADDREF, "void f()", asMETHOD(asRefDummy,addRef), asCALL_THISCALL);
		engine->RegisterObjectBehaviour("CGameSpawnRequestEvent", asBEHAVE_RELEASE, "void f()", asMETHOD(asRefDummy,release), asCALL_THISCALL);
		engine->RegisterObjectBehaviour("CGameSpawnRequestEvent", asBEHAVE_FACTORY, "CGameSpawnRequestEvent @f()", asFUNCTION((asCreateFactory<WWCOMMON::CGameSpawnRequestEvent>)), asCALL_CDECL);

		// Register parents methods.
		registerIGameEvent<WWCOMMON::CGameSpawnRequestEvent>("CGameSpawnRequestEvent");
		
		// Register inheritance.
		r = engine->RegisterObjectBehaviour("CGameSpawnRequestEvent", asBEHAVE_IMPLICIT_REF_CAST, "IGameEvent@ f()", asFUNCTION((refCast<WWCOMMON::CGameSpawnRequestEvent,WWCOMMON::IGameEvent>)), asCALL_CDECL_OBJLAST); nlassert(r>=0);
		r = engine->RegisterObjectBehaviour("IGameEvent", asBEHAVE_REF_CAST, "CGameSpawnRequestEvent@ f()", asFUNCTION((refCast<WWCOMMON::IGameEvent,WWCOMMON::CGameSpawnRequestEvent>)), asCALL_CDECL_OBJLAST); nlassert(r>=0);
	}

	void bindCGameUnspawnRequestEvent() {
		asIScriptEngine *engine = ScriptManager::getInstance().getEngine();
		int r;
		nlinfo("Binding CGameUnspawnRequestEvent");
		// Register Object Type
		engine->RegisterObjectType("CGameUnspawnRequestEvent", sizeof(WWCOMMON::CGameUnspawnRequestEvent), asOBJ_REF);

		// Register Behaviors.
		engine->RegisterObjectBehaviour("CGameUnspawnRequestEvent", asBEHAVE_ADDREF, "void f()", asMETHOD(asRefDummy,addRef), asCALL_THISCALL);
		engine->RegisterObjectBehaviour("CGameUnspawnRequestEvent", asBEHAVE_RELEASE, "void f()", asMETHOD(asRefDummy,release), asCALL_THISCALL);
		engine->RegisterObjectBehaviour("CGameUnspawnRequestEvent", asBEHAVE_FACTORY, "CGameUnspawnRequestEvent @f()", asFUNCTION((asCreateFactory<WWCOMMON::CGameUnspawnRequestEvent>)), asCALL_CDECL);

		// Register parents methods.
		registerIGameEvent<WWCOMMON::CGameUnspawnRequestEvent>("CGameUnspawnRequestEvent");

		// Register inheritance.
		r = engine->RegisterObjectBehaviour("CGameUnspawnRequestEvent", asBEHAVE_IMPLICIT_REF_CAST, "IGameEvent@ f()", asFUNCTION((refCast<WWCOMMON::CGameUnspawnRequestEvent,WWCOMMON::IGameEvent>)), asCALL_CDECL_OBJLAST); nlassert(r>=0);
		r = engine->RegisterObjectBehaviour("IGameEvent", asBEHAVE_REF_CAST, "CGameUnspawnRequestEvent@ f()", asFUNCTION((refCast<WWCOMMON::IGameEvent,WWCOMMON::CGameUnspawnRequestEvent>)), asCALL_CDECL_OBJLAST); nlassert(r>=0);
	}


And finally here's the script:
void handleGameEvent(IGameEvent @gameEvent) {
	// First we'll receive an IGameEvent object and cast it to a CGameSpawnRequestEvent.
	// Note: this makes an assumption that we're always getting this type, you should
	//       always check IGameEvent::getId() to verify the type of event being received.
	CGameSpawnRequestEvent @spawnEvt = cast<CGameSpawnRequestEvent>(gameEvent);
	NelInfo("handling game event: " + spawnEvt.getId());

	// Now, in response, craft a CGameUnspawnRequestEvent.
	NelInfo("Sending New Game Unspawn Event.");
	CGameUnspawnRequestEvent @unspawnEvt = CGameUnspawnRequestEvent();
	unspawnEvt.setPlayerID(300);
	NelInfo("handling unspawn event: " + unspawnEvt.getId());


	// Send it using the game event server.
	CGameEventServer @ges = getGameEventServer();	
	ges.postEvent(unspawnEvt);
}


The exception only occurs when I make the
ges.postEvent(unspawnEvt)
call. One odd thing is that I called postEvent() with spawnEvt, created in C++ and passed to the script, and it resubmits the event anew with my game event server. So it appears to be only plaguing objects created in the script. A little bit of debug exploration in CallSystemFunction showed me that the exception gets set at line 131 on as_callfunc_x86 (and I'm running 2.17.2) and one of the curious things is that pointer "func" points to WWCOMMON::IGameEvent::getId(void) - now that I'm deep in the bowels of AngelScript code I have no idea if this is relevant. Am I missing somethign crucial in passing instances of C++ classes created in the script back to C++? Thanks, sfb /s [Edited by - sfb on November 5, 2009 9:53:15 AM]

Share this post


Link to post
Share on other sites
Advertisement
So upon further review it appears to only plague my CGameUnspawnRequestEvent. If I do the following:

[source lang="cpp]
// AngelScript source.
CGameEventServer @ges = getGameEventServer();

// This posts a new event which gets processed appropriately next time around:
CGameSpawnRequestEvent @spawnEvt = CGameSpawnRequestEvent();
ges.postEvent(spawnEvt);

// Throws a "Null pointer access" text exception in AngelScript:
CGameUnspawnRequestEvent @unspawnEvt = CGameUnspawnRequestEvent();
ges.postEvent(unspawnEvt);



So contrary to my previous assumption, it's not that the CGameSpawnRequestEvent worked because it was passed from C++ - it's that CGameUnspawnRequestEvent doesn't work. So I'm wondering if I'm skipping something in binding.

Share this post


Link to post
Share on other sites
So I continued to investigate and now I can receive CGameSpawnEvents via the IGameEvent interface into an AS function and create either a Spawn or Unspawn and pass it to my event server. I narrowed down the specific problem to the cast operator.


void handleGameEvent(IGameEvent @gameEvent) {
// Get the event server.
CGameEventServer @ges = getGameEventServer();

// Comment this out and everything is happy.
CGameSpawnRequestEvent @spawnEvt = cast<CGameSpawnRequestEvent>(gameEvent);

// This works when I'm casting the IGameEvent to CGameSpawnRequestEvent
CGameSpawnRequestEvent @newSpawn = CGameSpawnRequestEvent();
ges.postEvent(newSpawn);

// This only works if the cast is commented out.
CGameUnspawnRequestEvent @newUnspawn = CGameUnspawnRequestEvent();
ges.postEvent(newUnspawn);
}



Unfortunately events have properties specific to their nature so I kind of need to be able to cast to them.

Share this post


Link to post
Share on other sites
How have you implemented the refCast template function?

It is strange that it works for one of the event types but not for the other.

I need to do some investigation on this. It could be a bug in the script compiler that is causing the problems.

Share this post


Link to post
Share on other sites
I've tried to reproduce your problem, but everything worked as expected without any null pointer accesses.

Here's the code I wrote based on your description. Perhaps you can take a look at it, to see if there is something that I've done differently that what you do.


static asIScriptEngine *g_engine = 0;

namespace WWCOMMON
{
class IGameEvent
{
public:
virtual asUINT getPlayerID() const {return 0;}
virtual void setPlayerID(asUINT) {};
virtual asWORD getId() const {return 0;}
};

class CGameSpawnRequestEvent : public IGameEvent
{
public:
};

class CGameUnspawnRequestEvent : public IGameEvent
{
public:
};

class CGameEventServer
{
public:
void processEventQueue() {}
void postEvent(IGameEvent *ev) {}
};




WWCOMMON::CGameEventServer g_server;

WWCOMMON::CGameEventServer &getGameEventServer()
{
return g_server;
}

class asRefDummy
{
public:
void addRef() {}
void release() {}
};

template<class T>
void registerIGameEvent(std::string typeName)
{
asIScriptEngine *engine = g_engine;
int r;
r = engine->RegisterObjectMethod(typeName.c_str(), "uint32 getPlayerID()", asMETHODPR(T, getPlayerID, (void) const, asUINT), asCALL_THISCALL); assert(r>=0);
r = engine->RegisterObjectMethod(typeName.c_str(), "void setPlayerID(uint32)", asMETHODPR(T, setPlayerID, (asUINT), void), asCALL_THISCALL); assert(r>=0);
r = engine->RegisterObjectMethod(typeName.c_str(), "uint16 getId()", asMETHODPR(T, getId, (void) const, asWORD), asCALL_THISCALL); assert(r>=0);
}

template<class T>
T* asCreateFactory()
{
return new T();
}

template<class A, class B>
B* refCast(A *a)
{
// If the handle already is a null handle, then just return the null handle
if( !a ) return 0;

// Now try to dynamically cast the pointer to the wanted type
B* b = dynamic_cast<B*>(a);
/* if( b != 0 )
{
// Since the cast was made, we need to increase the ref counter for the returned handle
b->addref();
}
*/
return b;
}

void bindIGameEvent()
{
asIScriptEngine *engine = g_engine;
//REGISTER_TYPE(WWCOMMON::IGameEvent, engine);
engine->RegisterObjectType("IGameEvent", sizeof(WWCOMMON::IGameEvent), asOBJ_REF);

// Register Behaviors, omit factory behavior so this interface cannot be created.
engine->RegisterObjectBehaviour("IGameEvent", asBEHAVE_ADDREF, "void f()", asMETHOD(asRefDummy,addRef), asCALL_THISCALL);
engine->RegisterObjectBehaviour("IGameEvent", asBEHAVE_RELEASE, "void f()", asMETHOD(asRefDummy,release), asCALL_THISCALL);

registerIGameEvent<WWCOMMON::IGameEvent>("IGameEvent");
}

void bindCGameSpawnRequestEvent()
{
asIScriptEngine *engine = g_engine;
int r;
// Register Object Type
engine->RegisterObjectType("CGameSpawnRequestEvent", sizeof(WWCOMMON::CGameSpawnRequestEvent), asOBJ_REF);

// Register Behaviors.
engine->RegisterObjectBehaviour("CGameSpawnRequestEvent", asBEHAVE_ADDREF, "void f()", asMETHOD(asRefDummy,addRef), asCALL_THISCALL);
engine->RegisterObjectBehaviour("CGameSpawnRequestEvent", asBEHAVE_RELEASE, "void f()", asMETHOD(asRefDummy,release), asCALL_THISCALL);
engine->RegisterObjectBehaviour("CGameSpawnRequestEvent", asBEHAVE_FACTORY, "CGameSpawnRequestEvent @f()", asFUNCTION((asCreateFactory<WWCOMMON::CGameSpawnRequestEvent>)), asCALL_CDECL);

// Register parents methods.
registerIGameEvent<WWCOMMON::CGameSpawnRequestEvent>("CGameSpawnRequestEvent");

// Register inheritance.
r = engine->RegisterObjectBehaviour("CGameSpawnRequestEvent", asBEHAVE_IMPLICIT_REF_CAST, "IGameEvent@ f()", asFUNCTION((refCast<WWCOMMON::CGameSpawnRequestEvent,WWCOMMON::IGameEvent>)), asCALL_CDECL_OBJLAST); assert(r>=0);
r = engine->RegisterObjectBehaviour("IGameEvent", asBEHAVE_REF_CAST, "CGameSpawnRequestEvent@ f()", asFUNCTION((refCast<WWCOMMON::IGameEvent,WWCOMMON::CGameSpawnRequestEvent>)), asCALL_CDECL_OBJLAST); assert(r>=0);
}

void bindCGameUnspawnRequestEvent()
{
asIScriptEngine *engine = g_engine;
int r;
// Register Object Type
engine->RegisterObjectType("CGameUnspawnRequestEvent", sizeof(WWCOMMON::CGameUnspawnRequestEvent), asOBJ_REF);

// Register Behaviors.
engine->RegisterObjectBehaviour("CGameUnspawnRequestEvent", asBEHAVE_ADDREF, "void f()", asMETHOD(asRefDummy,addRef), asCALL_THISCALL);
engine->RegisterObjectBehaviour("CGameUnspawnRequestEvent", asBEHAVE_RELEASE, "void f()", asMETHOD(asRefDummy,release), asCALL_THISCALL);
engine->RegisterObjectBehaviour("CGameUnspawnRequestEvent", asBEHAVE_FACTORY, "CGameUnspawnRequestEvent @f()", asFUNCTION((asCreateFactory<WWCOMMON::CGameUnspawnRequestEvent>)), asCALL_CDECL);

// Register parents methods.
registerIGameEvent<WWCOMMON::CGameUnspawnRequestEvent>("CGameUnspawnRequestEvent");

// Register inheritance.
r = engine->RegisterObjectBehaviour("CGameUnspawnRequestEvent", asBEHAVE_IMPLICIT_REF_CAST, "IGameEvent@ f()", asFUNCTION((refCast<WWCOMMON::CGameUnspawnRequestEvent,WWCOMMON::IGameEvent>)), asCALL_CDECL_OBJLAST); assert(r>=0);
r = engine->RegisterObjectBehaviour("IGameEvent", asBEHAVE_REF_CAST, "CGameUnspawnRequestEvent@ f()", asFUNCTION((refCast<WWCOMMON::IGameEvent,WWCOMMON::CGameUnspawnRequestEvent>)), asCALL_CDECL_OBJLAST); assert(r>=0);
}



void bindCGameEventServer()
{
asIScriptEngine *engine = g_engine;
int r;
// Register Object Type
engine->RegisterObjectType("CGameEventServer", sizeof(WWCOMMON::CGameEventServer), asOBJ_REF);

// Register Behaviors, omit factory behavior so this interface cannot be created.
engine->RegisterObjectBehaviour("CGameEventServer", asBEHAVE_ADDREF, "void f()", asMETHOD(asRefDummy,addRef), asCALL_THISCALL);
engine->RegisterObjectBehaviour("CGameEventServer", asBEHAVE_RELEASE, "void f()", asMETHOD(asRefDummy,release), asCALL_THISCALL);

r = engine->RegisterGlobalFunction("CGameEventServer &getGameEventServer()", asFUNCTION(getGameEventServer), asCALL_CDECL); assert(r>=0);
r = engine->RegisterObjectMethod("CGameEventServer", "void processEventQueue()", asMETHODPR(WWCOMMON::CGameEventServer, processEventQueue, (void), void), asCALL_THISCALL); assert(r>=0);
r = engine->RegisterObjectMethod("CGameEventServer", "void postEvent(IGameEvent@)", asMETHODPR(WWCOMMON::CGameEventServer, postEvent, (WWCOMMON::IGameEvent*), void), asCALL_THISCALL); assert(r>=0);
}
}

bool Test2()
{
bool fail = false;
int r;
COutStream out;

g_engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
g_engine->SetMessageCallback(asMETHOD(COutStream, Callback), &out, asCALL_THISCALL);

WWCOMMON::bindIGameEvent();
WWCOMMON::bindCGameSpawnRequestEvent();
WWCOMMON::bindCGameUnspawnRequestEvent();
WWCOMMON::bindCGameEventServer();


const char *script =
"void handleGameEvent(IGameEvent @gameEvent) {\n"
"// Get the event server.\n"
"CGameEventServer @ges = getGameEventServer();\n"
"\n"
"// Comment this out and everything is happy.\n"
"CGameSpawnRequestEvent @spawnEvt = cast<CGameSpawnRequestEvent>(gameEvent);\n"
"\n"
"// This works when I'm casting the IGameEvent to CGameSpawnRequestEvent\n"
"CGameSpawnRequestEvent @newSpawn = CGameSpawnRequestEvent();\n"
"ges.postEvent(newSpawn);\n"
"\n"
"// This only works if the cast is commented out.\n"
"CGameUnspawnRequestEvent @newUnspawn = CGameUnspawnRequestEvent();\n"
"ges.postEvent(newUnspawn);\n"
"}\n";

asIScriptModule *mod = g_engine->GetModule(0, asGM_ALWAYS_CREATE);
mod->AddScriptSection("script", script);
r = mod->Build();
if( r < 0 )
fail = true;

asIScriptContext *ctx = g_engine->CreateContext();
ctx->Prepare(mod->GetFunctionIdByName("handleGameEvent"));

WWCOMMON::IGameEvent *ev = new WWCOMMON::CGameSpawnRequestEvent();
ctx->SetArgAddress(0, ev);
r = ctx->Execute();
if( r != asEXECUTION_FINISHED )
fail = true;

ctx->Release();

g_engine->Release();

return fail;
}



Out of curiosity, how are you handling the memory management since you're using dummy functions for the AddRef and Release behaviours?

In the code above, there are a lot of memory leaks due to the event objects never getting destroyed.

Share this post


Link to post
Share on other sites
Quote:
Original post by WitchLord
I've tried to reproduce your problem, but everything worked as expected without any null pointer accesses.

Here's the code I wrote based on your description. Perhaps you can take a look at it, to see if there is something that I've done differently that what you do.


I couldn't find any real difference except where you did ctx->SetArgAddress(0, ev);. Even stranger yet the problem has gone away for me. The best I can guess is that I had to clean recompile? It's confusing because it only exhibited the problem when I explicitly casted a type. Whenever I explicitly casted a type all implicit casts for other types derived from the same base-class started failing. I'll just be thankful it's gone, I guess.


Quote:
Out of curiosity, how are you handling the memory management since you're using dummy functions for the AddRef and Release behaviours?

In the code above, there are a lot of memory leaks due to the event objects never getting destroyed.


In my actual code I'm wrapping the call to my event listener with my CSmartPtr and I'm using the *() overload to get a pointer. Ultimately I'll end up using a mixture of an object proxy (like SiCrane's) and some magic with my custom smart pointers.

And yes, it's ugly but fortunately it's not in my actual game yet, just a test/sample. As always though I'm very open to suggestion on better ways to handle what I'm doing. My code can be found at http://www.opennel.org/fisheye/browse/Werewolf/trunk under wwscript (the lib that handles binding and executing script functions) and samples (where my sample execution is.)

And thanks WitchLord for the fantastic scripting language and the informative and thorough responses. You're among the best project maintainers I've had the privilege to come across.

Thanks,
sfb

Share this post


Link to post
Share on other sites
Since it no longer happens for you I'll close this bug issue. But don't hesitate to let me know if it happens again in the future.

Thanks for the compliments. I really love working on AngelScript, and my other projects on AngelCode. The feedback from the community is extremely rewarding. I only wish I could do this for a living, instead of just a few hours here and there.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!