Links

Categories

Tags


« | Main | »

Using the integrated binding code generator

By Jewe | November 8, 2011

This is a short introduction to the binding code generator integrated in JewelScript.

This article is about using the C++ code generator to create binding code for your existing C++ classes. For general information on how native C/C++ classes interface with JewelScript, you should read this article first.

Introduction

The binding code generator integrated into the JewelScript library allows you to easily bind your external C++ code to the script runtime, and to use your external functions and classes from within JewelScript.

When creating binding code for your C++ class, the code generator obviously has to make certain assumptions about your C++ class. These may not always match your purpose or intention. However, in many cases, the generated output will just work. It usually does with simple member functions. Everything that gets passed or returns ints, floats or strings will usually work out-of-the-box.

For more complex functions using complex types, however, you’ll probably have to tailor the generated output to suit your needs. It’s not perfect, but it still saves a lot of effort compared to writing the binding code from scratch.

Keep in mind that most of your code should not be placed in the binding code file, as the file might get replaced if you ever wish to run the code generator again to update your class.

The best way to implement the binding is to include the header file of your C++ class, and make only minimal changes to the binding code file.

How the binding is done

Lets say you want to write a native binding for your CPoint C++ class. This class could look something like this:

C++:
class CPoint
{
public:
    CPoint();
    CPoint(const CPoint& src);
    CPoint(int x, int y);
    bool IsInside(const CRect& r);
    int X();
    int Y();

protected:
    int m_x;
    int m_y;
};

Admittedly a very simple class, but it’ll do for the sake of demonstration.

To create a native binding for this C++ class, you start a new text file and declare the same class in JewelScript using the native modifier keyword. This will tell the compiler that instead of implementing this class in script code, you want binding code generated for it:

JewelScript:
// forward declare class we haven't written yet
native class System::Drawing::Rect;

// create native binding for class Point
native class System::Drawing::Point
{
    ["Represents a two-dimensional coordinate on the screen."]

    method Point();                     ["Constructs a point at coordinates (0,0)."]
    method Point(const Point);          ["Constructs a copy of the given point."]
    method Point(int x, int y);         ["Constructs a point at the given coordinates."]
    method int IsInside(const Rect r);  ["Returns true if this point is inside the rectangle."]

    accessor int X();                   ["Returns this point's X-coordinate."]
    accessor int Y();                   ["Returns this point's Y-coordinate."]
}

You can also define constants, but no variables or code in such a class declaration.

When you compile this declaration and invoke the binding code generator afterwards, the JewelScript library will generate a C++ source file containing the binding code for this class. An example of the generated C++ code can be viewed here: bind_Point.cpp

The name of the output file will be the JewelScript class name, preceded by “bind_”. Similarily, the code generator will assume that your native C++ class name is the JewelScript class name, preceded by “C”. So in our example, the binding code will try to bind a C++ class “CPoint” to the language. If your native class has a different name, this can easily be changed afterwards by doing a search & replace in any text editor. Furthermore, the code generator will assume the function names as declared in the JewelScript class are the same as in the C++ class (with some exceptions discussed below).

If your new class is using other user-defined classes, you need to either import or forward declare them. If your new class is going to use a native type you haven’t yet written (e.g. a Point method using a Rect as an argument), you can forward declare them as shown in the example above.

How to invoke the binding code generator

The first implementation of the generator was always active. Which basically meant, you could just compile a piece of JewelScript code containing the native keyword in any application embedding the compiler, and the application would output some C++ files.

However, since developers would probably not want their final product to have that sort of functionality built-in, this has been changed with the second update of the code generator. The API function JCLGenerateBindings() has been added to give developers control over binding code generation.

In addition, the binding code generator, as well as the HTML documentation generator, will be stripped from release builds of the library now.

To invoke the binding code generator, simply call JCLGenerateBindings() after compiling script code in the debug build of your application.

Tip: The JILRun runtime environment and the JILRunOnly demo application both have a “-bind” option. You can use these tools to create binding code for your own native C++ classes.

Conventions for argument conversion

The JewelScript types int and float will be passed as C++ int and double to the native function. JewelScript strings will be passed as const char pointers to the native function. All other types will be passed as pointers to native objects to the native function, if those types can be converted to native pointers.

Types that can be converted to native pointers are all native classes, including the built-in classes. Types that can’t be converted are script classes, delegates or cofunction thread contexts. If the type can’t be converted to a native pointer, it will be passed as a JILHandle to the native function.

If you need to pass objects as C++ reference to your native code instead of pointers, you’ll have to edit the binding code and dereference the pointer.

Instead of: _this->Func(arg_0);
Use this:    _this->Func(*arg_0);

Do NOT store pointers passed to your C++ function permanently in your class! The pointer is only guaranteed to be valid until your native function returns. This is because there is no permanent reference count for the pointer passed to your function, so the object could be destroyed anytime after your function returns. You should copy the data if needed.

The following only applies to native objects passed to your native function:

If you explicitly wish to store references to these objects permanently in your class, you’ll need to edit the binding code. You would obtain the JILHandle of the object instead of it’s native pointer and pass it to your native class. The native class must keep the handle stored for as long as the reference is needed. There are accessor functions in the API that allow you to access the handle’s native pointer.

If you also wish to return such a stored reference, you’ll again need to edit the binding code to return the handle instead of the native pointer.

I recommend that you read my article on handling handles if you want to do this.

Conventions for return values

To return int and float to JewelScript, your native function should return C++ int and double. To return strings to JewelScript your native function should return a const char pointer. All other types must be returned as pointers to native objects by the native function.

In case those types cannot be created from native pointers, the native function is expected to return them as a JILHandle. Native functions that return pointers to native objects (except strings) must allocate their return values as new instances (using operator new), because the binding code will create a new JILHandle for these objects.

C++:
CPoint* CTest::GetPoint()
{
    return &m_MyPoint;              // WRONG!
}

CPoint* CTest::GetPoint()
{
    return new CPoint(m_MyPoint);   // RIGHT!
}

If you explicitly wish to return a reference to your value and not a copy, you need to create a JILHandle for the value in your class and keep it around as long as you need the value. Additionally, you’ll need to edit the binding code to return that handle.

Convertor methods

The native function name for convertor methods will be made up from the result type name of the convertor appended by “_convertor”:

C++:
JILFloat CTest::float_convertor();

Constants

You can also add constants to the native class, if they have the type int, float, or string. You cannot specify their value in your native class declaration, though. Instead, the code generator will create C++ code that allows you to define the constant’s value at run-time.

JewelScript:
native class Test
{
    function int Hello();
    const int kMyConst;
}

The binding code will try to define the constant kMyConst to the value of CTest::kMyConst, but of course you can adapt this to suit your needs.

Delegates

Your native class declaration may also contain delegate type declarations. This is useful if you want to create native functions that accept a delegate as a parameter. Delegates will always be passed as JILHandle pointers to the native function, since this is the only way they can be executed.

JewelScript:
native class Test
{
    delegate int MyDelegate(int);
    function int DoWork(MyDelegate);
}

The generated C++ binding code will try to call int CTest::DoWork( JILHandle* ).

Package import

JewelScript’s native type interface supports a feature called package import. Basically your native type can return a string containing a list of other native classes that it has dependencies to. The compiler will use this information to import all specified native classes before importing yours.

When using the binding code generator, this list will automatically be created for you.

Topics: docs | Comments Off on Using the integrated binding code generator

Comments are closed.