"Value of ESP was not properly saved..."

Started by
7 comments, last by derefed 16 years, 8 months ago
I just set up my ScriptingEngine class and gave it a test run, but the app crashes and gives the following error when trying to Execute() a script: "Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call." It suggested that differing calling conventions could be to blame, so I went and checked a few things. The only function I registered to the AS engine was "Print", and so I figured maybe that was the cause. However, I removed all calls to Print from my test script and still the error occured. I've stepped through the code with a debugger. All of the AS engine config and setup checks out fine. In fact, if I take out the call to execute a script function, the program runs smoothly. It's the actual call to Execute() that kills it. I'm thinking that perhaps it is because "Print" resides within a class that the calling conventions are getting screwed up; however, I'm not sure how to make it work. Do I change asCALL_CDECL to something different? Here are some code snippets: The .as script

void PrintSomething()
{
   Print("The script is working!");
}


ScriptingEngine

void ScriptingEngine::ConfigureEngine()
{

   int r;
   
   RegisterScriptString(engine);
   
   r = engine->RegisterGlobalFunction("void Print(string &str)", asMETHOD(ScriptingEngine,PrintString), asCALL_CDECL); assert (r >= 0);

}

void ScriptingEngine::PrintString(string &str)
{
   output.append(str);
}


Main program

// run the test script!
      int r = 0;
      
      r = script_engine.Prepare_Function_By_Name("PrintSomething");
      if (r < 0)
      {
         output.append("Failed!");
         return;
      }
      
      r = script_engine.Execute();
      if (r < 0)
      {
         output.append("Failed!");
         return;
      }
      
      output.append(script_engine.Grab_Output());


Advertisement
That usually happens when you have the wrong calling conventions on functions (the __declspec(dll***) stuff) or if you have it building against the wrong runtime libs.

For example, if your app is built as a "Multithreaded", make sure your scripting library DLL is built as a "Multithreaded DLL"... make sure they are all playing together properly.

EDIT: I guess it wouldn't hurt to actually ASK if you are loading the script stuff from a DLL.
This line:
 r = engine->RegisterGlobalFunction("void Print(string &str)", asMETHOD(ScriptingEngine,PrintString), asCALL_CDECL);

is non-kosher. You're registering a member function as a global function, and you're also lying to AS by saying it's a __cdecl function when it's actually a __thiscall function.
Quote:Original post by SiCrane
This line:
 r = engine->RegisterGlobalFunction("void Print(string &str)", asMETHOD(ScriptingEngine,PrintString), asCALL_CDECL);

is non-kosher. You're registering a member function as a global function, and you're also lying to AS by saying it's a __cdecl function when it's actually a __thiscall function.


Can I send in asCALL_THISCALL, even though the docs say that RegisterGlobalFunction says that callconv "Must be either asCALL_CDECL, asCALL_STDCALL, or asCALL_GENERIC"?

EDIT: Indeed, I cannot. The assertion that r >= 0 fails. So how do I properly register a method? Do I use RegisterObjectMethod, or is that for something else entirely?
Ah yes, this error. Don't worry, you're not alone!

AFAIK, you cannot register just the method of an object.

You either need a wrapper around it, or you register the whole object as non instantiable (sp?) object, register each method you want exposed, and then register it as a global object.

for example
m_script.RegisterObject("TRenderer");	m_script.RegisterObjectMethod("TRenderer","void RenderText(const string& text, int x, int y, int color)",asMETHOD(TRenderer,RenderText));	m_script.RegisterObjectMethod("TRenderer","int RGB(int8 r, int8 g, int8 b)",asMETHOD(TRenderer,BuildRGB));	m_script.RegisterAsGlobal("TRenderer Screen",(void*)m_renderer);

where those functions are:
void TScript::RegisterObject( std::string declaration,int size/*=0*/,asDWORD flags/*=0*/ ){	int r = m_engine->RegisterObjectType(declaration.c_str(),size,flags);	r < 0 ? throw TFatalException("Failed to register object" + declaration + " with Angel Script. \n\n In:\n TScript::RegisterObject") : ++r;}void TScript::RegisterObjectMethod( std::string obj, std::string declaration, asUPtr fnPtr){	int r = m_engine->RegisterObjectMethod(obj.c_str(),declaration.c_str(),fnPtr,asCALL_THISCALL);		r < 0 ? throw TFatalException("Failed to register object method " + declaration + " with Angel Script. \n\n In:\n TScript::RegisterObjectMethod") : ++r;}void TScript::RegisterAsGlobal( std::string declaration, void* ptr ){	if(ptr)	{		int r = m_engine->RegisterGlobalProperty(declaration.c_str(),ptr);		r < 0 ? throw TFatalException("Failed to register global property" + declaration + " with Angel Script. \n\n In:\n TScript::RegisterGloalProperty") : ++r;	}	else	{		throw TFatalException("Failed to register global property " + declaration + " with Angel Script. Reason: Void function pointer. \n\n In:\n TScript::RegisterGloalProperty");	}}void TScript::RegisterAsGlobal( std::string declaration, asUPtr fnPtr){	int r = m_engine->RegisterGlobalFunction(declaration.c_str(), fnPtr ,asCALL_CDECL);	r < 0 ? throw TFatalException("Failed to register function " + declaration + " with Angel Script. \n\n In:\n TScript::RegisterAsGlobal") : ++r;}


Does this help at all?
Ah, making progress now! That all worked for the most part, _Sigma. However, still having a problem with using it...

My game is abstracting all the AS stuff into the ScriptingEngine class. The way I want it to work is as follows: there's one ScriptingEngine object for the entire game; you call Init("script.as") so it loads the script, which will have all the script functions for the entire game; you call Prepare() and SetArgs() and Execute() on the ScriptingEngine object; you gather return values with GetReturnValue(), but more importantly, anything that the script "prints" gets shoved into ScriptingEngine's "output" string, and can be gathered by calling GrabOutput().

Now, my PrintString() function, which is the one that the script uses as "Print()" to print stuff, is a method of ScriptingEngine. Following your example code, I have my app set up as follows:

   r = engine->RegisterObjectType("ScriptingEngine", 0, 0);   r = engine->RegisterObjectMethod("ScriptingEngine", "void Print(string &str)", asMETHOD(ScriptingEngine,PrintString), asCALL_THISCALL);      r = engine->RegisterGlobalProperty("ScriptingEngine engine", (void *)this);


Before adding the last line, I would no longer get the dreaded "ESP" error. The ScriptingEngine couldn't compile my script, however, as I didn't give it a ScriptingEngine object to call Print with. What I need to do is send in "this"; that way, it works according to my design. However, after adding that third line there, I began to receive the ESP error once again. Am I doing it incorrectly, or can you simply not send "this" in as an object for the script to manipulate? If not, I fear my design won't work.

Here's the script, for reference, in case I called it wrong:
void PrintSomething(){   engine.Print("The script is working!");}
Your current code snippets seem to be correct, you'll need to do some debugging to figure out the exact problem.

Have you tried setting a breakpoint inside the ScriptingEngine::PrintString method? Do you get a properly initialized string in the parameter? Is the this pointer in the method pointing to the ScriptingEngine that you've initialized?

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

You can use this, but you don't need the cast to void*.

//register the kernel for interaction with the script.	m_script.RegisterObject("TKernel");	m_script.RegisterObjectMethod("TKernel","void Pause()",asMETHOD(TKernel,PauseScript));	m_script.RegisterAsGlobal("TKernel Engine",this);

for example,
TKernel holds an instance of my scripting class, and I want to register TKernel (ie this) with m_script.
AHA! FINALLY! IT WORKS!

I stepped through the debugger and couldn't find anything amiss with my code... however, I started stepping into some AS functions, and it turns out that it crashed when trying to call my LineCallback() function. So I went and investigated said registration of function, as I suspected that I had not set it properly, being that it's in a class. That wasn't it though, cause I set it right. But as I was looking through the docs, I noticed something else: I had left out the parameter in my LineCallback() that takes an asIScriptContext pointer! When I had first written the function, I intended for it to not take such a parameter, as I had my context within my class. But of course, that was an error, because we're dealing with function pointers here and they require that the function declaration be the same.

You can't imagine the thrill and excitement I had when my program said "The script is working!", just as the script had instructed. It looks like I'm finally ready to begin using this wonderous library in my game.

Thank you so much for all your help, WitchLord and _Sigma. You guys are great. Hopefully from here on in I won't have quite as many problems. But who knows? Such is the life of a programmer. ;)

This topic is closed to new replies.

Advertisement