Gem: A Generic Function Binding Interface

From Game Programming Gems

Hardcover, Charles River Media, August 2000

Edited by Mark DeLoura

Introduction

Scripting engines and network messaging have an important requirement in common: they must be able to interface with the game’s functionality in a type safe, efficient, and convenient way. This gem provides a method for exporting functions and then binding to them dynamically at runtime. It does so without sacrificing runtime speed or convenience.

Requirements

The basic requirement for our scripting engine is that we can call a function and possibly pass it parameters. For this we’ll need to know its name, its location in memory, and the types that it takes. The types for these parameters must be types that we support directly in the scripting engine as part of the language. Let’s assume we support bool, float, int, string, and void.

The basic requirement for our network remote procedure calls (RPC’s) is that we can call a function on a remote machine and possibly pass it parameters. Given that our machines will probably be running the code at different memory addresses, we can’t pass function pointers over the network and must instead convert them into a token that both sides recognize. For this we’ll use a serial ID that can be converted back and forth to an actual function pointer very quickly. Also, for the parameters, we’ll need to know how to recognize strings and memory pointers in the parameters so that the data they point to can be packed at the end of the RPC chunk for hand-off to the network transport.

For convenience, we should be able to simply call an RPC-capable function without having to do any explicit parameter packing from the caller’s code. If the call is meant for another machine, the called function should automatically send its parameters and serial ID to the network transport, then return immediately. If meant for local execution, it would just execute the code directly. The dispatcher on the remote machine would look up functions based on the serial ID and then call them directly after resolving to a function pointer.

Platform Concerns

This is a good place to point out that the sample code provided with this gem is very specific to a particular platform: Visual C++ 6.0 running on an x86 version of Win32. In particular: (1) there’s a little bit of assembly code in here that is obviously x86 specific, (2) the name mangling and unmangling and how calling conventions work is specific to Visual C++ 6.0, and (3) I make use of the specific way that Win32 image (DLL/EXE) exports work.

At the very least, the concepts if not the implementation should still be portable to other platforms. All the x86 assembly code can be converted to any other instruction set, though you’ll need knowledge of the calling conventions of that platform for it to work. Dynamic link libraries are hardly unique to Win32 – all this gem needs is a table that maps exported function names to memory addresses. And finally, it should be possible to figure out how other compilers (especially open source compilers such as GCC) mangle and unmangle names.

Attempt #1

Let’s get back to the task at hand. We are trying to find a way to export game functionality in a generic way so that it can be called from scripts or passed over the network as RPC’s. Here is a really simple solution:

void Foo( void );

void Bar( void );

// ...

enum eFunction

{

FUNCTION_FOO,

FUNCTION_BAR,

// ...

};

struct Function

{

typedef void (*Proc)( void );

const char* m_Name;

Proc m_Proc;

eFunction m_Function;

};

Function g_Functions[] =

{

{ "Foo", Foo, FUNCTION_FOO, },

{ "Bar", Bar, FUNCTION_BAR, },

// ...

};

The eFunction enumeration provides a serialized list of unique ID’s for all available functions. The Function structure maps a text name onto a function pointer and unique ID. And finally the g_Functions array is the set of all published functions in the system. Our example function exports are of course Foo and Bar.

Our imaginary scripting engine can search through the g_Functions array when it’s compiling a script to resolve function calls by name and then call the procedure directly once found – hopefully this lookup would be done through an index for speed. Our imaginary network messaging system could convert function calls into their eFunction ID and use that to resolve the remote procedure call on the other machine. Easy and simple.

Page 1 of 6 | Next page