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

Started by
5 comments, last by WitchLord 14 years, 5 months ago
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]
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.
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.
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.

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

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.

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

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

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