<html> <head> <title>How to expose C++ functions to Ruby Scripts</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <link rel="stylesheet" href="highlight.css" type="text/css"> </head> <body bgcolor="#FFFFFF" text="#000000"> <p><font size="6"><i><b>How to expose C++ functions to Ruby Scripts</b></i></font></p> <p align="left"><font size="4">As we want to expose much functionality to the script side, we also want to be able to call C++ functions from Ruby. In order to do this, we intercept unknown function calls on the Ruby side. The parameters are converted on the C++ side. There we know the name of the function to call, the object to call them on, and the parameters. But, how do we actually call the correct C++ function. The answer lies in the class object. The class object will contain the necessary meta-data to reroute the function call to the correct C++ function. The class object defines the interface of the class to the script side. This is done in the same file as the class definition was performed (the additional CPP file). Let's go back to our Simple class from earlier HowTos:</font></p> <pre><span class="dir">#include <zeitgeist/leaf.h> </span> <span class="key">class </span>Simple : <span class="key">public </span>zeitgeist::Leaf { <span class="key">public</span>: Simple(); <span class="key">virtual </span>~Simple(); <span class="typ">void </span>DoSomething(); <span class="typ">void </span>PrintString(<span class="typ">const </span>std::string& s); <span class="typ">void </span>PrintInt(<span class="typ">int </span>i); <span class="typ">void </span>PrintFloat(<span class="typ">float </span>f); <span class="typ">void </span>PrintBool(<span class="typ">bool </span>b); }; DECLARE_CLASS(Simple); </pre> <p><font size="4">Now, let's say we want to expose the DoSomething() method. simple_c.cpp would look like this:</font></p> <pre><span class="dir">#include </span><span class="dstr">"simple.h"</span><span class="dir"></span> <span class="key">using namespace </span>zeitgeist; FUNCTION(doSomething) { <span class="key">if </span>(in.size() == <span class="num">0</span>) { Simple *simple = <span class="key">static_cast</span><Simple*>(obj); simple->DoSomething(); } } <span class="typ">void </span>CLASS(Simple)::DefineClass() { DEFINE_BASECLASS(zeitgeist/Leaf); DEFINE_FUNCTION(doSomething); } </pre> <p align="left"><font size="4">Yes, you guessed right ... more helper macros. Every function is declared using the FUNCTION()-macro. As a parameter it takes the name of the function. I like to always have Ruby-side functions start with a lower-case letter and C++-side functions with a capital letter. The function macro just declares a function, which takes two parameters: obj and in. obj is the object we are calling the function on (basically, the 'this' or 'self' pointer). in is an STL vector of input parameters. The vector holds boost::any types, which have to be cast back. Within the function, we check if the number of parameters match (in our case 0 parameters). We cast the obj pointer to the correct type and call the appropriate function. In DefineClass() we also have to define the function using the DEFINE_FUNCTION() macro. After this has been done (and the class object is registered with the Core), we can execute the following script-code:</font></p> <p align="left"><font size="4">mySimpleObj = new ('Simple', 'test');</font></p> <p align="left"><font size="4">mySimpleObj.doSomething();</font></p> <p align="left"><font size="4">This would then call the C++ member function DoSomething(). Now, how about passing some parameters. Let's marshall the PrintInt() function:</font></p> <pre><span class="dir">#include </span><span class="dstr">"simple.h"</span><span class="dir"></span> <span class="key">using namespace </span>zeitgeist; <span class="key">using namespace </span>boost; FUNCTION(printInt) { <span class="key">if </span>(in.size() == 1) { Simple *simple = <span class="key">static_cast</span><Simple*>(obj); simple->PrintInt(any_cast<int>(in[0])); } }</pre> <p align="left"><font size="4">The above function obviously also would need to be defined in DefineClass(). Now, we see that any_cast is used to retrieve the type (!) of the parameter stored in the vector 'in' at position 0 (the first parameter from left to right maps to position 0). It should be noted, that this way of marshalling is not safe, meaning that exceptions will occur if a wrong parameter has been issued script-side. So, please be careful when doing this ... and take care to pass correct parameters on the script side. The problem is, that Ruby is dynamically typed, whereas C++ is statically typed. Also, no types are coerced automatically. So, if you cast to an int and somebody uses a float in the script, this will likely cause an error!</font></p> <p align="left"><font size="4">Another note is that in the current implementation NO return parameters can be passed to the script side. This is unfortunate, but would have created yet another (major) source of typing errors. One is dangerous enough. Also, scripts should be kept linear to make them easier to read.</font></p> <pre> </pre> </body> </html>