Not logged in, Join Here! or Log In Below:  
 
News Articles Search    
 

 Home / General Programming / Simple Scripting Account Manager
 
Archive Notice: This thread is old and no longer active. It is here for reference purposes. This thread was created on an older version of the flipcode forums, before the site closed in 2005. Please keep that in mind as you view this thread, as many of the topics and opinions may be outdated.
 
zRXer

April 21, 2005, 04:56 PM

Hello all.

First, a bit of backstory. A while ago I was looking into an easy way to implement scripting in a game that I was working on (just a simple brickout clone). Obviously, the point wasn't that I needed any real scripting ability in a game as simple as brickout, I was just interested in learning about it. As I read about the various tecniques available, like interpreted languages like Python, and "script-specific" type things like Angelscript, I realized 2 things. First, that it can be pretty tedious implement scripting that allows scripts to call C++ code, and second, that this sort of thing interested me in general.

So, I decided I would write my own scripting solution. I once read about a C compiler called lcc, that one can retarget to various architectures. So, I retargeted that to a simple stack-based assembly-like language. I wrote an assembler and linker for my new compiler, and finally a virtual machine to run it. As of now it all works pretty well, though I kind of got bored with implementing, for instance, all of the bitwise operators for chars, shorts, ints, and whatnot, so I now just add operations to the virtual machine when I need one.

Then, I tried to make it as easy as possible to interface with C++. Now, the way I did this is pretty much a hack, but I was going for a quick, easy solution. I have a special function, call it "ExternalCall", that, when called from a script, is trapped (think DOS interrupts). Then, based on the first argument to ExternalCall, the remaining arguments are passed to whichever function the first argument references. Now, obviously this would require that ExternalCall know which value means which function. So, I provided a few functions to tell ExternalCall which functions to call.

Here is a bit of code showing how you could call a script, and how you might register a function for such a script to use.

  1.  
  2. //In cMyScript.h
  3. class cMyScript :
  4.  public cScript
  5. {
  6.  protected:
  7.   int Double(int i)
  8.   {
  9.     return i * 2;
  10.   }
  11.  
  12.  public:
  13.   void RegisterDouble()
  14.   {
  15.     //Registering the Double() function:
  16.     //Tell the VM what parameters the function takes.
  17.     SCRIPT_PARAM_LIST ParamList;
  18.     SCRIPT_PARAM IntParam(SCRIPT_INT, "iInput");
  19.  
  20.     ParamList.push_back(IntParam);
  21.  
  22.     //Tell the script how it can call the function, and the code what the
  23.     //C++ function name is.  Also include return and parameter list.
  24.     RegisterFunction("CPP_Double", "Double", SCRIPT_INT, ParamList);
  25.  
  26.     ExportScriptInterface("cppdouble.h");
  27.     ExportCodeInterface("myscript.i.h", "cMyScript", "cMyScript.h");
  28.     return;
  29.   }
  30. };
  31.  
  32. //in main.cpp
  33. #include "cMyScript.h"
  34. #include "myscript.i.h"
  35.  
  36. int main()
  37. {
  38.   cMyScript s;
  39.   s.Load("square.lccbc.bc");
  40.   s.RegisterDouble();
  41.  
  42.   s.AddArgument(2);
  43.   LCCOBJECT ret = s.Call();
  44.  
  45.   //ret.i now contains the result of calling the script
  46.   return ret.i;
  47. }
  48.  
  49. //the actual script square.c:
  50. #include "cppdouble.h"
  51.  
  52. int main(int i)
  53. {
  54.   i = CPP_Double(i);
  55.   i = i * i;
  56.   return i;
  57. }
  58.  
  59.  

So, that deserves description, yes? Basically, the Export functions generate an interface that the script can use. It outputs a header file to include in the script, and a header file to include in the source. So, you first compile and execute your program, being sure to call RegisterDouble(). Then, you can remove that call, include the headers generated, and from then on you can call CPP_Double() from your scripts.

One main problem with this is that, if you want to call functions, you must be able to access them from the class cMyScript (or whatever you derive from cScript). An example: I have a class cPaddleAI that works the ai for a paddle that plays brickout for you (which makes brickout a lot less fun, hehe). Within the cPaddleAI class I have, for instance, a cPaddle * m_pPaddle member pointing to the cPaddle that is actually playing. Then, I register a function like so:

  1.  
  2. ...
  3. cPaddleAI::RegisterAll()
  4. {
  5.   ...
  6.   void RegisterFunction("Paddle_SetPosition", "m_pPaddle->SetPosition", ...);
  7.   ...
  8. }
  9.  


As you can see, the interface generated uses the second string to build a function call in the interface header and code. This was the only way I could make it work, though I know there is a better way (AngelScript has a way better way, for instance).

In actuality I didn't make it that much easier to set up the interface, I suppose, though since I wrote the method I understand it and can set it up really quickly. But, I did learn a lot about parsing, compiling, assembling, linking, and scripting.

I can't post a link to the actual library yet, as I am not at home, but I will soon, I hope.

If you have any comments, I would welcome them, as well as ideas on how to make the interface easier to use (though I obviously haven't given you much to go on, hehe), and anything you would want to see in a simple scripting engine.

Thanks
-zRXer

 
Dave

April 22, 2005, 11:41 AM

I've done it in the past using templates and overloading.
Here's a quick example of the style I mean:

  1.  
  2. template <class R, class P1, class P2>
  3. class Command2Params
  4. {
  5.     typedef R (Actor::*MethodType)(P1, P2);
  6.  
  7.     Command2Params( const std::string &name, MethodType method ) : method_(method), name_(name) {}
  8.  
  9.     void ProcessCommand(const std::vector<Parameter> &params, Actor &object)
  10.     {
  11.         P1 p1 = params[0];
  12.         P2 p2 = params[1];
  13.  
  14.         R result = object.(*method_)(p1, p2);
  15.     }
  16.  
  17. private:
  18.  
  19.     MethodType method_;
  20.     std::string name_;
  21. }
  22.  
  23. template <class R, class P1, class P2>
  24. void registerFunction( const std::string &name, R (Actor::*vfunc)(P1, P2) )
  25. {
  26.     AddCommand(new Command2Params<R,P1,P2>(name, vfunc));
  27. }
  28.  
  29.  


Then registration becomes really simple, obiviously you have to create the corresponding overloads to the functions that you want to call.

  1.  
  2. registerFunction( "function", &Actor::function );
  3.  

 
zRXer

April 23, 2005, 01:53 AM

All right. I realize that this is seemingly not a topic of interest, but I will post a bit more.

I decided to ease the pain of interfacing with a simple dialog based MFC program. I also wrote a project builder in the same way (which basically writes a makefile and builds the associated project for you.) Again, I can't post a link yet, but hopefully by Monday I will have something workable with decent documentation, so those of you who are interested can try it out.

As a question (though i suppose a new thread might be in order), my library as of now utilizes , amongst others. What can I do to ensure that I do not get a "already defined" type link error when using this library in code that also uses the STL? As of now I am just /FORCE ing the compilation, which works, but is not a solution, just a quick hack. Any help is appreciated.

Dave, I guess I don't really understand what you mean with the above code, but that is likely because I am looking at it in the context of my own scripting code. Any clarification is welcome!

 
Dave

April 24, 2005, 07:36 PM

Right ok I'll try and make it more like context of your example...

We have a system kind of like unreal engine in the respect that our classes derive from a common base class and Actors are objects with IDs

The function that you want to be called in script has to be a method on a object derive from this class for object identification, RTTI, and remote networking calls.

For example :

  1.  
  2. class Doubler : public Actor
  3. {
  4. int double ( int i ); // etc ...
  5. }
  6.  


When registering your classes you have to type out the parameter list and return results, my system allows the compiler to work this out for you. That is but using generic programming it works out the "RegisterDouble" programming for your code in a re-useable way

So along with the version of the class with 2 parameters (in my previous post)
I would copy and paste another version with only one param.

  1.  
  2. template <class R, class P1>
  3. class Command1Params
  4. {
  5.     typedef R (Actor::*MethodType)(P1);
  6.  
  7.     Command2Params( const std::string &name, MethodType method ) : method_(method), name_(name) {}
  8.  
  9.     // Where parameters and object are recieved from script
  10.     void ProcessCommand(const std::vector<Parameter> &params, Actor &object)
  11.     {
  12.         P1 p1 = params[0];
  13.         R result = object.(*method_)(p1);
  14.         SetResult(result);
  15.     }
  16.  
  17. private:
  18.  
  19.     MethodType method_;
  20.     std::string name_;
  21. }
  22.  
  23. template <class R, class P1>
  24. void registerFunction( const std::string &name, R (Actor::*vfunc)(P1) )
  25. {
  26.     AddCommand(new Command1Params<R,P1>(name, vfunc));
  27. }
  28.  


So my main functions would look like...

  1.  
  2. int main ()
  3. {
  4.     registerFunction("CPP_Double", &Doubler::Double); // must be called before any script
  5.      
  6.     Doubler d;
  7.     int i = 100;
  8.     int result;
  9.  
  10.     LinkVariable("i", 100);
  11.     SetObject(d);
  12.  
  13.     ExecuteScript("whatever script");
  14.     GetResult(result);
  15. }
  16.  


Registering more functions is just a matter of typing out the register function line rather than having to hand craft functions like your RegisterDouble.

For Example:
  1.  
  2. int main()
  3. {
  4.     registerFunction("CPP_Double", &Doubler::Double);
  5.     registerFunction("Double", &Doubler::Double);
  6.     registerFunction("DotProduct", &Vector::DotProduct);
  7.  
  8.     ...
  9. }
  10.  



This obiviously isnt the exact code I've created, I cant post that cos its owned by the company I work for. It is a starting point for using template programming to integrate with scripting languages.

For similar things look at Boost::Python or Luabind. They use similar techniques to register their classes and methods

 
IronChefC++

April 26, 2005, 02:37 AM

I suggest checking out the Boost.Python library to see a very nice scripting interface:

http://www.boost.org/libs/python/doc/index.html

 
Dave

April 26, 2005, 06:20 PM

Thats what I said!

 
IronChefC++

April 26, 2005, 06:22 PM

Shoot. I skipped the second half. So I change my position to "Listen to Dave about that Boost.Python thing!"

 
Dave

April 26, 2005, 06:29 PM

np :)

 
This thread contains 8 messages.
 
 
Hosting by Solid Eight Studios, maker of PhotoTangler Collage Maker.