Angelscript Crash or Reporting null pointer access

Started by
10 comments, last by WitchLord 8 years, 8 months ago

void Spell(Spell& spell, Character& ast, Character& trg){
Party @part=ast.GetParty();
	for (int i = 0; i<part.GetEnemies(); i++){
		Character &chara=cast<Character>(part.GetEnemy(i));
		@chara=null;
	}
	@part=null;
}

void Spell(Spell& spell, Character& ast, Character& trg){
for (int i = 0; i<ast.party.GetEnemies(); i++){
		Character @handle=cast<Character>(ast.party.GetEnemy(0));
		int dmg = ast.Stat("Mag").VECI2().y + spell.Stat("Power").VECI2().x;
		dmg = dmg * (265 - handle.Stat("Spr").VECI2().y) / 4;
		dmg = dmg * spell.Stat("Power").VECI2().x / 256;
		dmg = dmg * (Random(0, 32) + 240) / 256;

		dmg = Mul(dmg, (handle.Res(spell.Stat("Element").ST()).FT()));

		dmg = Mul(dmg, (((handle.Status("Shell").VECI().z>0) ? ShellMod : 1)));
		handle.ReceiveDamage(ast, dmg);
	}
}

First codebox won't complain but crashes only if that cast exists, the crash occurs only on second run or exiting application.

Will complain about nullpointer access on dmg = dmg * (265 - handle.Stat("Spr").VECI2().y) / 4;
and crash the program if I run the function again or if I exit the application, no ability to trace it properly.

party is registered like


int Party::GetCharacters(){ return m_chars.elms(); }
asIScriptObject *Party::GetCharacter(int i){ return m_chars[i].GetController(); }
BaseEntity *Party::InsertCharacter(){ return &m_chars.New(); }

int Party::GetEnemies(){ return m_enemies.elms(); }
asIScriptObject *Party::GetEnemy(int i){ return m_enemies[i].GetController(); }
BaseEntity *Party::InsertEnemy(){ return &m_enemies.New(); }

void Party::Register(CScriptMgr &m){
	// Registering the class method
	
        MYASSERT(m.RegClass<Party>("Party", asOBJ_REF|asOBJ_NOCOUNT));
	MYASSERT(m.RegClassMethod("Party", "int GetCharacters()", asMETHOD(Party, GetCharacters)));
	MYASSERT(m.RegClassMethod("Party", "IController @GetCharacter(int i)", asMETHOD(Party, GetCharacter)));
	MYASSERT(m.RegClassMethod("Party", "BaseEntity &InsertCharacter()", asMETHOD(Party, InsertCharacter)));
	MYASSERT(m.RegClassMethod("Party", "int GetEnemies()", asMETHOD(Party, GetEnemies)));
	MYASSERT(m.RegClassMethod("Party", "IController @GetEnemy(int i)", asMETHOD(Party, GetEnemy)));
	MYASSERT(m.RegClassMethod("Party", "BaseEntity &InsertEnemy()", asMETHOD(Party, InsertEnemy)));
}

Character is extending in angelscript like


class Character : IController
{

Character(){}
Character(BaseEntity @obj)
{
@o = obj;
SetSpells();
}

BaseEntity @o;
}

and IController is registered through C++ by

MYASSERT(m.GetEngine()->RegisterInterface("IController") >= 0);

so my question would be, any ideas what I am doing wrong? do I have to register it in some special way to extend and cast the object from IController to Character?

or is it the Get functions from party that is wrong? eg I am using asIScriptObject as return type.

Advertisement

asIScriptObject *Party::GetEnemy(int i){ return m_enemies[i].GetController(); }
MYASSERT(m.RegClassMethod("Party", "IController @GetEnemy(int i)", asMETHOD(Party, GetEnemy)));

The GetEnemy method is registered to return a handle, but the implementation doesn't increment the ref counter for that handle. This causes the object to be destroyed prematurely as AngelScript will release the handle it received from the method when it is done with it.

You can either change the implementation of the method to increment the ref counter, before returning it. Or change the registration to use auto-handle to tell AngelScript to do it for you.


// manually increment the ref counter
asIScriptObject *Party::GetEnemy(int i){ asIScriptObject *obj = m_enemies[i].GetController(); if( obj ) obj->AddRef(); return obj; }

or


// tell AngelScript to do it using @+
MYASSERT(m.RegClassMethod("Party", "IController @+ GetEnemy(int i)", asMETHOD(Party, GetEnemy)));

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

is one or the other method more prefered? Pros or Cons with either methods?

Tried both of them, its atleast not crashing anymore, but it still reports access null pointer, I guess I am doing something wrong still.

just to clarify the asIScriptObject does not return null from C++ side.

It's mostly about preference.

Personally I prefer my code to be explicit in what it does, so I would chose the first option.

There is a bit more run-time overhead with the use of auto-handles, but unless you're calling the methods millions of times you're not likely to notice this extra overhead.

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

Ok, so I figured I had to try adding @ before all calls to the handle, but then I get object handle is not allowed for this type, does this error occur if an object inside IController derived class does not support it, or is there something more I need to add?

without adding the @ before those calls it still complains for some reason about null pointer access.

and from my earlier post

"just to clarify the asIScriptObject does not return null from C++ side."

and thank you so much for the quick replies.

Before I go on. Did you change the code already? Was the crashes fixed, or are they still happening?


Ok, so I figured I had to try adding @ before all calls to the handle, but then I get object handle is not allowed for this type, does this error occur if an object inside IController derived class does not support it, or is there something more I need to add?

Sounds like you ended up @ in the wrong places. The @ is used to signal that you want to operate on the handle itself rather than the object it refers to. When accessing methods or properties of the object you should not use the @ symbol.


without adding the @ before those calls it still complains for some reason about null pointer access.

The null pointer access, might be due to a different cause. Are you using multiple script modules? Is your Character class declared in different scripts?

If you compile the same script in two different modules, any class declared in the script will get two different identifies in the two modules even though they have the exact same implementation. This is what I suspect is causing the null pointer access. The dynamic cast<Character> is failing because Character class in the script you're running is not the same Character class used to create the object instance.

In order for two different modules to share classes, the class must be declared as shared.

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

Crashes is solved.

Yes I found out that doing @ before doing something was obviously wrong.

I do compile into multiple modules, but then again each module contains all the code needed across the files, in this case do I still need to declare them as shared?

After sharing the Character class I noticed that my global variables can't be used anymore, how would I approach to workaround this?


I do compile into multiple modules, but then again each module contains all the code needed across the files, in this case do I still need to declare them as shared?

Yes. As I explained in the previous post. Even though the source code is the same, each module will get their own compiled version of the class, unless they are declared as shared.


After sharing the Character class I noticed that my global variables can't be used anymore, how would I approach to workaround this?

Correct, shared classes cannot use global variables since the global variables cannot be shared (at least not yet).

Instead of sharing the classes themselves, you should consider sharing only a common shared interface. That way you don't have to include the implementation of each class in every module, and the classes can still use global variables in the modules where they are implemented.

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 am asuming you mean shared interface IController in this case, I am currently using the C++ side RegisterInterface, which is not shared I take it, and doing shared interface IController in angelscript makes the C++ side complain registering the Party class.

identifier 'IController' is not a data type in global namespace

Failed in call to function 'RegisterObjectMethod' with 'Party' and 'IController @+ GetCharacter(int i)' (Code: -10)

No, you misunderstood.

The interface 'IController' that you registered with RegisterInterface is shared, and you should continue to register it.

What I meant was that your scripts that need to inspect or modify the characters should do so through a shared interface instead of the actual class.

The shared interface can be the IController interface you register from the application if you don't mind that the application define the methods that should be implemented, or it can be a shared interface declared in the script.

If you chose to use an interface declared in the script the implementation of the Character class would then extend both the script declared interface and the application registered IController interface.

For example:

// Declare a shared interface. This declaration must be included in all scripts
shared interface ICharacter
{
    type Stat(const string &in);
    type Res(type);
    type Status(const string &in);
    void ReceiveDamage(ICharacter @, int);
}
 
// The Character class extends both the ICharacter interface and the IController interface
// This implementation only needs to be done in one script where the actual behavior of the character should be defined
class Character : ICharacter, IController
{
   Character(){}
   Character(BaseEntity @obj)
   {
     @o = obj;
     SetSpells();
   }
 
   // Implement the methods from the ICharacter interface
   type Stat(const string &in) { return ...; }
   type Res(type) { return ...; }
   type Status(const string &in) { return ...; }
   void ReceiveDamage(ICharacter @, int) { ... }
 
   BaseEntity @o;
}
 
// The scripts that should interact with the characters should do so through the ICharacter interface
void Spell(Spell& spell, ICharacter& ast, ICharacter& trg){
for (int i = 0; i<ast.party.GetEnemies(); i++){
        // Now that ICharacter is a shared interface, this dynamic cast will function properly  
        // even if the actual implementation of the class is done in a different script module
        ICharacter @handle=cast<ICharacter>(ast.party.GetEnemy(0));
        int dmg = ast.Stat("Mag").VECI2().y + spell.Stat("Power").VECI2().x;
        dmg = dmg * (265 - handle.Stat("Spr").VECI2().y) / 4;
        dmg = dmg * spell.Stat("Power").VECI2().x / 256;
        dmg = dmg * (Random(0, 32) + 240) / 256;

        dmg = Mul(dmg, (handle.Res(spell.Stat("Element").ST()).FT()));

        dmg = Mul(dmg, (((handle.Status("Shell").VECI().z>0) ? ShellMod : 1)));
        handle.ReceiveDamage(ast, dmg);
    }
}

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