Introduction to GameMonkey Script Part 2
Embedding GameMonkey
Creating a host-bound functionNow that you know how to call a scripted function from native code, you may be wondering how you export your own functions to GameMonkey Script. The reasons for wanting to do so are understandable; some people may want the speed of native code (although GM is fast, it's still slower than native code), others may want to expose aspects of their game or engine to the script. Whatever your reasons, you need to know how to export your own functions. Unlike some scripting languages (for example, AngelScript), functions must be wrapped for use by the VM. The GM Script VM doesn't have the ability to translate the parameters from the script into a form understandable by your native compiled code - one reason being that function calls and their parameters aren't known until runtime, unlike in C++ where everything is checked during compilation. The process within the wrapped a function usually involves the following steps:
Here's the code I will be examining for a simple native version of myMultiply: int GM_CDECL gm_myMultiply( gmThread *a_thread ) { // Check the number of parameters passed is correct if (a_thread->GetNumParams() != 2 ) return GM_EXCEPTION; // Local vars to hold data from params int a_x = 0; int a_y = 0; // Check params are valid types if (a_thread->ParamType(0) != GM_INT) return GM_EXCEPTION; if (a_thread->ParamType(1) != GM_INT) return GM_EXCEPTION; // Get data from params a_x = a_thread->Param(0).m_value.m_int; a_x = a_thread->Param(1).m_value.m_int; // perform calculation int ret = a_x * a_y; // return value a_thread->PushInt( ret ); return GM_OK; } Like its scripted counterpart, this function takes two parameters and returns the sum of their values. The 'wrapped' function first checks that the number of parameters it has been passed is correct; if not we return a scripted exception, next it checks to see that the parameters are the correct type (in this case, int). It then reads the values of these parameters from the thread - you will notice that the parameters are held again in the gmVariable structure. After the parameter data has been collected and converted you can now perform the main body of the function; in this example it is a simple multiplication but in your games it is likely that you will call another function in your engine. You will notice that a lot of the code to check parameters is repetitive and can be cumbersome to write. Fortunately for us, the GameMonkey authors have provided several macros to aid our task. int GM_CDECL gm_myMultiply( gmThread *a_thread ) { GM_CHECK_NUM_PARAMS(2); GM_CHECK_INT_PARAM( a_x, 0 ); GM_CHECK_INT_PARAM( a_y, 1 ); int ret = a_x * a_y; a_thread->PushInt( ret ); return GM_OK; } The GM_CHECK_NUM_PARAMS macro is fairly self-explanatory; it checks the number of parameters from the thread and will return a GM_EXCEPTION if the number of supplied parameters is lower than those you want. It should be noted, however, that if you pass more parameters than you are requesting then you will not get this error. One assumes that this feature is provided to allow for variable argument functions to be used with the macro. The GM_CHECK_*_PARAM macros are similar, but they also declare the variables and fill them for you. These macros make the code much simpler to read while still performing the same function. However it must be noted that as these are macros they require specific variable names to work. In the example above, your thread pointer must be stored in the variable a_thread, this naming convention is used throughout GameMonkey Script and I have stuck to it in these articles. |