Hi, I want to add a function table to my project with the intent of being able to look up & execute methods using strings. I can't guarantee the signatures of the functions I want to add to the table will all be the same, or even what they are going to be, so I need a generic solution.
However I can't figure out how to store a bunch of random function pointers in one place - how to go from something like
1 2 3 4 5 6 7 8 9
// define the function pointers int(*pFunc)(int)=NULL; void(Class::*pMember)(int,bool)=NULL; bool(Class::*pMember2)(void)const=NULL;
// create some example instances pFunc staticMethod =&someFunction; pMember nonstaticClassMethod =&someClass::someFunction; pMember2 constClassMethod =&someOtherClass::someOtherFunction;
to
1 2 3 4 5 6 7 8 9 10
// pseudocode (I haven't decided exactly which datatypes I'll be using yet) // the multimap class stores a GenericFunctionPointer & indexes it // with 2 strings - the class instance ("" means global) & function name
// i'm also storing the class instances for these two ;) FunctionTable->Add(nonstaticClassMethod,"someClass","someFunction"); FunctionTable->Add(constClassMethod,"someOtherClass","someOtherFunction");
Is it possible? Am I approaching the function table idea from the right angle? Any advice would be appreciated.
edit: One solution I can see would be to have the function pointer point to a wrapper function that calls the function I want, but has no parameters & returns void. Then I could store & retrieve the parameters & result somewhere accessible by both the calling code & the function it's calling, sort of like a software cpu register I guess.
// functiontable now multimap<pFunction,string,string> pFunction function =&someFunctionWrapper; FunctionTable->Add(function,"","ICanHasPie");
// call the function: float aValue =5; bool Result; registerTable->SetRegister(0,aValue); FunctionTable->CallFunction("","ICanHasPie"); Result = registerTable->GetRegister(1); if(Result) { Pie->CutSlice(); }
However whilst that makes the function table pretty straightforward the additional plumbing required on the calling side of things would turn hooking up a function into a fairly heavyweight task with all the wrappers & dealing with registers, which I'd prefer to avoid if possible. Also the registers still dont allow for a generic solution here. At least in their current form. Hmm...
edit2: in fact the bit above is probably complete rubbish - I forgot to think about class function pointers. I suppose maybe it could be dealt with at the wrapper level.. arrgh, I think I'm just shifting the problem around rather than fixing it!
The boost libraries provide a few ways to do what you want. Have a look at Boost.Function. You may have to combine Boost.Function with Boost.Bind or Boost.Lambda to get exactly what you want.
I don't use boost, so I can't help you with specifics. When I found myself needing boost functionality, I switched to C#, which has that kind of functionality built in.
Thanks for the tip Prof. I hadn't even thought about Boost to be honest, I didn't realise I'd wandered into that kind of territory either :shock: though I'm hoping this is something I can achieve without a library. I had a couple of ideas from reading those docs (involving a FuncPtr class with a of bunch function pointer declarations & void* casts, or messing with std::mem_fun/mem_ref) but if those don't work out it's good to know I have something to fall back on without using a scripting library.
I haven't played with function pointers yet, but I think you can do the same way as LUA does. I haven't looked into LUA sources neither, but LUA functions in C++ always have the same signature : int function(lua_State*) ... "lua_State*" beeing a pointer to the stack (it's not actually true, but I'll simplify for the sake of the example) and "int" the number of returned values.
You can use the same system : Create a stack in which you'll push your function arguments (a std::vector, why not), call your wrapper function, retrieve your arguments, call your true function (and optionally, return value(s)). You'll do all the argument checking in the wrapper function : do I have enough arguments, with the required type, ...
int l_Unit_SetAnim( lua_State* luaVM ) { bool error =false; int n = lua_gettop(luaVM);// Get the number of values on the stack // Our function takes a minimum of 2 arguments, plus 3 other optional ones if(n <2) {
LUA::PrintError(luaVM, "Too few arguments in \"UnitSetAnim\" (2 expected : unit id, anim id, (+priority, queued, reset))"); // So if there isn't enough, just return and do nothing return0; } // We then check the value type if(!lua_isnumber(luaVM, 1)) { LUA::PrintError(luaVM, "Argument 1 of UnitSetAnim must be a number (unit id)"); error =true; } if(!lua_isnumber(luaVM, 2)) { LUA::PrintError(luaVM, "Argument 2 of UnitSetAnim must be a number (anim id)"); error =true; } // We fill the stack with nil (NULL) values until there are 5 values if(n <5) for(int i = n; i <5; i++) lua_pushnil(luaVM);
// And we check optional arguments AnimPriority priority = ANIM_PRIORITY_HIGH; if(!lua_isnumber(luaVM, 3)&&!lua_isnil(luaVM, 3)) { LUA::PrintError(luaVM, "Argument 3 of UnitSetAnim must be a number (priority)"); } else priority =(AnimPriority)ToInt(lua_tonumber(luaVM, 3));
bool queued =false; if(!lua_isboolean(luaVM, 4)&&!lua_isnil(luaVM, 4)) { LUA::PrintError(luaVM, "Argument 4 of UnitSetAnim must be a bool (queued)"); } else queued = lua_toboolean(luaVM, 4);
bool reset =false; if(!lua_isboolean(luaVM, 5)&&!lua_isnil(luaVM, 5)) { LUA::PrintError(luaVM, "Argument 5 of UnitSetAnim must be a bool (reset)"); } else reset = lua_toboolean(luaVM, 5);
// Then, if everything went fine, we call our function if(!error) { Unit* u = Frost::mUnitMgr->GetUnitByID(ToInt(lua_tonumber(luaVM, 1))); if(u !=NULL) { bool success = u->SetAnimState((AnimID)ToInt(lua_tonumber(luaVM, 2)), priority, queued, reset); // Push our return value on the stack // I think LUA pushes it on the same stack, and just retrieve // the X last values on the stack, where X is the value returned // by this wrapper function lua_pushboolean(luaVM, success); } } else { // If something went wrong, we just return a nil value // You can make your system smart enough to fill the gap by itself lua_pushnil(luaVM); }
// At last, we return the number of returned value, here 1 return1; }
Cheers Kal, definitely helps. I can see this getting dangerously close to implementing an entire VM architecture which I was trying to avoid as well :) Though thinking about it briefly ('tis time for tea & beer here) I'll probably end up with one anyway as I guess I'll be needing more than just a function table in order to provide access to user-defined types etc.. May be easier to just get it done I guess.
Why don't I just use a scripting library I hear you ask? Well, I want to have a conversion phase that takes script classes & outputs c++ code (though purely script-based functions are still allowed), so all that wrapper code would be generated for me. Ignoring the cost of having to write it myself, this gives me two benefits: less repetitive typing for me & a scalable script system that isn't tied into a predetermined set of c++ functions. None of the off-the-shelf libraries I've seen offer something like that. Which is a shame, because this approach forces me to design a grammar as well, so I lose the benefit of having a well-tested language to base things on.
I'd planned on using Flex & Yacc as that's what I used at uni. I hadn't heard of Antlr, looks a lot more advanced though - I'll certainly check that out before I start.
edit: I've been playing with antlrworks for a couple of hours & like what I've seen so far, what I'm really impressed by is the workflow. Being able to write, run & debug it all in an ide.. that is going to save heaps of time.