Advertisement

Crash in AddRefScriptObject

Started by October 13, 2014 02:25 PM
1 comment, last by Myran 10 years, 1 month ago
I want to be able to instantiate different classes based on a string, to do that I use this C++ function:

void InstantiateClass(asIScriptGeneric *gen) 
{
	auto name = (std::string*)gen->GetArgObject(0);
	auto params = (SValue*)gen->GetArgAddress(1);


	auto mod = gen->GetEngine()->GetModule("Scripts");
	auto type = mod->GetObjectTypeByName(name->c_str());

	if (!type)
	{
		printf("%s '%s'\n", "Couldn't find class", name->c_str());
		
		auto ret = CScriptHandle();
		*(CScriptHandle*)gen->GetAddressOfReturnLocation() = ret;
		return;
	}

	auto tName = std::string(type->GetName());
	auto factory = type->GetFactoryByDecl((tName + "@ " + tName + "(SValue& params)").c_str());

	if (!factory)
	{
		printf("%s '%s' %s\n", "Couldn't find class", name->c_str(), "factory function");

		auto ret = CScriptHandle();
		*(CScriptHandle*)gen->GetAddressOfReturnLocation() = ret;
		return;
	}

	
	auto ctx = gen->GetEngine()->CreateContext();

	ScriptEngine::PrepareContext(ctx, factory);
	int r = ctx->SetArgObject(0, params); assert(r >= 0);
	ScriptEngine::VerifyScriptExecution(ctx, ctx->Execute());
	

	auto bObj = *((asIScriptObject**)ctx->GetAddressOfReturnValue());
	ScriptEngine::UnprepareContext(ctx);

	auto ret = CScriptHandle(bObj, type);
	ctx->Release();

	*(CScriptHandle*)gen->GetAddressOfReturnLocation() = ret;
}
This has worked well for me for a while now, but now I'm getting a crash when trying to use it on some really simple classes. It seems like when the CScriptHandle does engine->AddRefScriptObject it will crash if the class doesn't contain an object handle.
So doing it on this class works:

class Explode : IAction
{
	IAction@ dummy;
	
	Explode(SValue& params)
	{
	}
	
	bool DoAction(Actor@ owner, vec2 pos, vec2 dir)
	{
		return true;
	}
	
	void Update(int dt, int cooldown)
	{
	}
}
But if I remove IAction@ dummy, I get a crash in "void asCScriptEngine::CallObjectMethod(void *obj, asSSystemFunctionInterface *i, asCScriptFunction *s) const".

I see a couple of errors in your code. It is probably by luck that it hasn't given you trouble before.

void InstantiateClass(asIScriptGeneric *gen)
{
    auto name = (std::string*)gen->GetArgObject(0);
    auto params = (SValue*)gen->GetArgAddress(1);


    auto mod = gen->GetEngine()->GetModule("Scripts");
    auto type = mod->GetObjectTypeByName(name->c_str());

    if (!type)
    {
        printf("%s '%s'\n", "Couldn't find class", name->c_str());
        
        //auto ret = CScriptHandle();
        //*(CScriptHandle*)gen->GetAddressOfReturnLocation() = ret;  // <-- The memory is not yet initialized, so you cannot do an assignment on it
        new( gen->GetAddressOfReturnLocation() ) CScriptHandle();   // <-- You must initialize the memory using the placement new() operator
        return;
    }

    auto tName = std::string(type->GetName());
    auto factory = type->GetFactoryByDecl((tName + "@ " + tName + "(SValue& params)").c_str());

    if (!factory)
    {
        printf("%s '%s' %s\n", "Couldn't find class", name->c_str(), "factory function");

        //auto ret = CScriptHandle();
        //*(CScriptHandle*)gen->GetAddressOfReturnLocation() = ret;  // <-- The memory is not yet initialized, so you cannot do an assignment on it
        new( gen->GetAddressOfReturnLocation() ) CScriptHandle();   // <-- You must initialize the memory using the placement new() operator
        return;
    }

    
    auto ctx = gen->GetEngine()->CreateContext();

    ScriptEngine::PrepareContext(ctx, factory);
    int r = ctx->SetArgObject(0, params); assert(r >= 0);
    ScriptEngine::VerifyScriptExecution(ctx, ctx->Execute());
    

    auto bObj = *((asIScriptObject**)ctx->GetAddressOfReturnValue());
 
    // ScriptEngine::UnprepareContext(ctx); // <-- This releases the reference to the object held by the context, thus destroying it before you give it to the CScriptHandle
    // auto ret = CScriptHandle(bObj, type); // <-- The script handle will increment the ref count to take ownership of the object
    // ctx->Release(); // <-- Destroying the context automatically unprepares it, so there is no need to explicitly call Unprepare() unless you're using context pooling
    // *(CScriptHandle*)gen->GetAddressOfReturnLocation() = ret; // <-- The memory is not yet initialized so this assignment is incorrect
 
    new( gen->GetAddressOfReturnLocation() ) CScriptHandle(bObj, type); // <-- Use placement new to initalize the memory
    ctx->Release(); // <-- Release the context after initializing the script handle to avoid it being destroyed too early
}

The fact that it doesn't crash when the script class has a handle as member is because that member causes the class to be garbage collected as it can potentially form circular references. This in turn makes the garbage collector hold on to a reference to the object in order to properly control the life time, which is how the object was kept alive long enough for the CScriptHandle to call AddRefScriptObject on it. When the class doesn't have the handle, the script object was destroyed immediately upon ctx->Unprepare() thus turning the bObj pointer invalid even before you could pass it to the CScriptHandle instance.

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

Advertisement

Aha, thank you. That makes a lot of sense.

This topic is closed to new replies.

Advertisement