See what's going on with flipcode!




This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

 

  Function Binding
  Submitted by



In the latest issue of the Game Developer magazine I've read they dealt with "Dungeon Siege" in their regular "post mortem" section; as one of the key things in the success of their development they quoted the scripting system, and more precisely the function exporting facilities (the so called FuBi) that easily allowed its interaction with the C++ binaries. As surely many of you know those were first sketched by Scott Bilas in one of the chapters of "Game Programming Gems", and subsequently in some articles of him and GDC presentations. What really distressed me about those publications is the titanic effort put in simulating the calling conventions of the given platform, where it looks like a kind of job up to the compiler itself... in some points Mr Bilas mentions the possibility of using templates and macros to parse a crude parameter buffer and perform the call, but discards the idea as "inefficient and inconvenient". Honestly what I think is absolutely inconvenient is hacking down portability and going through this entire "call frame"-handling hell when the compiler can do it sweet and easy. I'd like to bring up a discussion about this in this forum, so I've made a rough draft of a template & macro based implementation, obviously not as complete as Bilas', but might be used as basis for a more consistent alternative: FB.h. Bilas' implementation allows identifying exported functions just by means of the __declspec(dllexport) attribute, and then gathers the exported function info from the compilation results, "a posteriori". A similar functionality in just one step can be achieved by the use of the registration macros defined in the enclosed FB.h. In a practical use case:

void chimp(void);
int titi(void);
void macaco(int);

__FB_REGISTRATE_FUNCTION(chimp) __FB_REGISTRATE_FUNCTION(titi) __FB_REGISTRATE_FUNCTION(macaco)

class Banana {

int m_peel;

public:

inline Banana(int peel): m_peel(peel) { }

int gorilla(); void orangutan();

};

__FB_REGISTRATE_METHOD(Banana, gorilla) __FB_REGISTRATE_METHOD(Banana, orangutan)



Test code explicitly using those bindings is provided here Those macros are meant to embody real registration operations that have actual code, therefore it must be somehow guaranteed that it will be performed before the execution of any function-binding related operation. A possible solution is to do it in the constructor of a custom-named global object that'll replace the macro, but then either it violates the "one definition rule" when the header is included from several files or it is declared static and because of having internal linkage in every translation unit the constructor is called several times, leading to a exception in the registration. IMHO a convenient way to solve it is using local static variables to set a common anchor point, as done, for instance, in the __FB_REGISTRATE_FUNCTION macro:

namespace FB
{

struct FunctionRegistration {

template <typename R inline FunctionRegistration(std::string name, R (*ptr)()) { Function0p<R::registrate(name, ptr); }

template <typename R, typename P0 inline FunctionRegistration(std::string name, R (*ptr)(P0)) { Function1p<R, P0::registrate(name, ptr); }

};

}

#define __FB_REGISTRATE_FUNCTION(function_name) \ static struct RegistrateFunction_##function_name \ { \ \ inline RegistrateFunction_##function_name() { static FB::FunctionRegistration s_functionRegistration(#function_name, &##function_name); } \ \ \ } registrateFunction_##function_name;

This definitely allows the exporting attributes to be specified in the headers, in a rather descriptive way. Nevertheless relaying on static global variables to do this introduces the "static initialisation order fiasco" in case any function binding operation is performed in the initialisation of static objects. However that seems a bearable flaw taking into account that neither scripting nor RPC operations are likely to be executed at initialisation time. Now that scripting is that fashionable in game development ;) hope you may find this topic interesting. The code was tested both with Intel Compiler 6.0 and gcc 2.95.3 for PC and CodeWarrior 7.1 for Mac. I'd like to thank Jaap Suter for his kind help with the revision of this code. If there is any lame flaw, Taito's "Bust a Move" is to blame, hehe Cheers,


Matt


Currently browsing [fbinding_cotd.zip] (2,406 bytes) - [FB.h] - (9,289 bytes)

#ifndef __FB_H__
#define __FB_H__

#include <map> #include <string>



namespace FB {

struct FunctionCall {

virtual ~FunctionCall(void) throw() { } virtual void execute(void* res, void* pars) const = 0;

};

struct MethodCall {

virtual ~MethodCall(void) throw() { } virtual void execute(void* instance, void* res, void* pars) const = 0;

};

template <typename Base> class Registry {

typedef std::map<std::string, Base*> RegistryMap;

RegistryMap m_map;

Registry(void) { } Registry(const Registry&) { } Registry& operator=(const Registry&) { return *this; }

public:

~Registry(void) throw() { for(RegistryMap::iterator iit = m_map.begin(); iit != m_map.end(); ++iit) delete (*iit).second; }

inline static Registry& getInstance(void) { static Registry s_registry; return s_registry; } void registrate(std::string name, Base* call) { RegistryMap::iterator iit = m_map.find(name); if(iit != m_map.end()) throw "binding already registered";

m_map[name] = call; } Base* operator[](std::string name) const { RegistryMap::const_iterator iit = m_map.find(name); if(iit == m_map.end()) return 0; else return (*iit).second; }

};

template <typename R> class Function0p: public FunctionCall { public: typedef R (*Type)(); private: Type m_ptr; Function0p(Type ptr): m_ptr(ptr) { } Function0p(const Function0p&) { } Function0p& operator=(const Function0p&) { return *this; } public: void execute(void* res, void*) const { *(reinterpret_cast<R*>(res)) = (*m_ptr)(); } static void registrate(std::string name, Type ptr) { Function0p* function = new Function0p(ptr); try { Registry<FunctionCall>::getInstance().registrate(name, function); } catch(...) { delete function; throw; } }

};

template <> class Function0p<void>: public FunctionCall { public: typedef void (*Type)(); private: Type m_ptr; Function0p(Type ptr): m_ptr(ptr) { } Function0p(const Function0p&) { } Function0p& operator=(const Function0p&) { return *this; } public: void execute(void*, void*) const { (*m_ptr)(); } static void registrate(std::string name, Type ptr) { Function0p* function = new Function0p(ptr); try { Registry<FunctionCall>::getInstance().registrate(name, function); } catch(...) { delete function; throw; } }

};

template <typename R, typename P0> class Function1p: public FunctionCall { public:

#pragma pack(push) #pragma pack(1) struct Pars { P0 p0; }; #pragma pack(pop) typedef R (*Type)(P0); private: Type m_ptr; Function1p(Type ptr): m_ptr(ptr) { } Function1p(const Function1p&) { } Function1p& operator=(const Function1p&) { return *this; } public: void execute(void* res, void* pars) const { Pars& lean = *reinterpret_cast<Pars*>(pars); *(reinterpret_cast<R*>(res)) = (*m_ptr)(lean.p0); } static void registrate(std::string name, Type ptr) { Function1p* function = new Function1p(ptr); try { Registry<FunctionCall>::getInstance().registrate(name, function); } catch(...) { delete function; throw; } }

};

template <typename P0> class Function1p<void, P0>: public FunctionCall { public:

#pragma pack(push) #pragma pack(1) struct Pars { P0 p0; }; #pragma pack(pop) typedef void (*Type)(P0); private: Type m_ptr; Function1p(Type ptr): m_ptr(ptr) { } Function1p(const Function1p&) { } Function1p& operator=(const Function1p&) { return *this; } public: void execute(void*, void* pars) const { Pars& lean = *reinterpret_cast<Pars*>(pars);

(*m_ptr)(lean.p0); } static void registrate(std::string name, Type ptr) { Function1p* function = new Function1p(ptr); try { Registry<FunctionCall>::getInstance().registrate(name, function); } catch(...) { delete function; throw; } }

};

template <typename C, typename R> class Method0p: public MethodCall { public: typedef R (C::*Type)(); private: Type m_ptr; Method0p(Type ptr): m_ptr(ptr) { } Method0p(const Method0p&) { } Method0p& operator=(const Method0p&) { return *this; } public: void execute(void* instance, void* res, void*) const { *(reinterpret_cast<R*>(res)) = (reinterpret_cast<C*>(instance)->*m_ptr)(); } static void registrate(std::string name, Type ptr) { Method0p* method = new Method0p(ptr); try { Registry<MethodCall>::getInstance().registrate(name, method); } catch(...) { delete method; throw; } }

};

template <typename C> class Method0p<C, void>: public MethodCall { public: typedef void (C::*Type)(); private: Type m_ptr; Method0p(Type ptr): m_ptr(ptr) { } Method0p(const Method0p&) { } Method0p& operator=(const Method0p&) { return *this; } public: void execute(void* instance, void*, void*) const { (reinterpret_cast<C*>(instance)->*m_ptr)(); } static void registrate(std::string name, Type ptr) { Method0p* method = new Method0p(ptr); try { Registry<MethodCall>::getInstance().registrate(name, method); } catch(...) { delete method; throw; } }

};

template <typename C, typename R, typename P0> class Method1p: public MethodCall { public:

#pragma pack(push) #pragma pack(1) struct Pars { P0 p0; }; #pragma pack(pop) typedef R (C::*Type)(P0); private: Type m_ptr; Method1p(Type ptr): m_ptr(ptr) { } Method1p(const Method1p&) { } Method1p& operator=(const Method1p&) { return *this; } public: void execute(void* instance, void* res, void*) const { Pars& lean = *reinterpret_cast<Pars*>(pars); *(reinterpret_cast<R*>(res)) = (reinterpret_cast<C*>(instance)->*m_ptr)(lean.p0); } static void registrate(std::string name, Type ptr) { Method1p* method = new Method1p(ptr); try { Registry<MethodCall>::getInstance().registrate(name, method); } catch(...) { delete method; throw; } }

};

template <typename C, typename P0> class Method1p<C, void, P0>: public MethodCall { public:

#pragma pack(push) #pragma pack(1) struct Pars { P0 p0; }; #pragma pack(pop) typedef void (C::*Type)(P0); private: Type m_ptr; Method1p(Type ptr): m_ptr(ptr) { } Method1p(const Method1p&) { } Method1p& operator=(const Method1p&) { return *this; } public: void execute(void* instance, void*, void* pars) const { Pars& lean = *reinterpret_cast<Pars*>(pars);

(reinterpret_cast<C*>(instance)->*m_ptr)(lean.p0); } static void registrate(std::string name, Type ptr) { Method1p* method = new Method1p(ptr); try { Registry<MethodCall>::getInstance().registrate(name, method); } catch(...) { delete method; throw; } }

};

template <typename R> void registrateFunction(std::string name, R (*ptr)()) { Function0p<R>::registrate(name, ptr); }

template <typename C, typename R> void registrateMethod(std::string name, R (C::*ptr)()) { Method0p<C, R>::registrate(name, ptr); }

struct FunctionRegistration {

template <typename R> inline FunctionRegistration(std::string name, R (*ptr)()) { Function0p<R>::registrate(name, ptr); } template <typename R, typename P0> inline FunctionRegistration(std::string name, R (*ptr)(P0)) { Function1p<R, P0>::registrate(name, ptr); } };

struct MethodRegistration {

template <typename C, typename R> inline MethodRegistration(std::string name, R (C::*ptr)()) { Method0p<C, R>::registrate(name, ptr); } template <typename C, typename R, typename P0> inline MethodRegistration(std::string name, R (C::*ptr)(P0)) { Method1p<C, R, P0>::registrate(name, ptr); } };

}

#define __FB_REGISTRATE_FUNCTION(function_name) \ static struct RegistrateFunction_##function_name \ { \ \ inline RegistrateFunction_##function_name() { static FB::FunctionRegistration s_functionRegistration(#function_name, &##function_name); } \ \ \ } registrateFunction_##function_name;

#define __FB_REGISTRATE_METHOD(class_name, method_name) \ static struct RegistrateMethod_##class_name_##method_name \ { \ \ inline RegistrateMethod_##class_name_##method_name () { static FB::MethodRegistration s_methodRegistration(#class_name "_" #method_name, &##class_name::##method_name); } \ \ \ } registrateMethod_##class_name_##method_name;

#define __FB_CALL_FUNCTION(function_name, res, pars) FB::Registry<FB::FunctionCall>::getInstance()[#function_name]->execute(res, pars) #define __FB_CALL_METHOD(class_name, method_name, instance, res, pars) FB::Registry<FB::MethodCall>::getInstance()[#class_name "_" #method_name]->execute(instance, res, pars);



#endif

The zip file viewer built into the Developer Toolbox made use of the zlib library, as well as the zlibdll source additions.

 

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.