SWIG-like C++ interface generator for Haxe/OpenFL

I created issue on GitHub long time ago. I write here about how this works to get some attention. If official lime/OpenFL supports something like this in the future, that would make my life easier.

Currently to be able to use C/C++ functions from Haxe, you should write C wrapper functions and export them with DEFINE_PRIME. In all C functions you should also convert CFFI value to C++ pointer properly with CFFI API functions such as val_data and alloc_abstract. Same for arrays. I found it too troublesome to do this for all functions so I was looking for a way to automate. I couldn’t find existing tool that can do this, so I wrote one by myself. linc would work on hxcpp, but I was looking for a way that works on all native targets.

My tools is integrated with lime tools. It reads metadata of haxe classes and generates C++ source files which contains wrapper functions. You could specify how you convert each Haxe type to C++ type in xml file, and using typedefs you could tell the generator which type you want to use, such as const char* (CString). I used typedefs because it looked impossible to set metadata to function parameters.



For example, the generator generates code like this for lime::Window::Alert:

void lime_Window_Alert(value inHandle, HxString message, HxString title) {
  lime::Window* arg1 = (lime::Window*)NULL;
  const char* arg2;
  arg2 = (const char*)NULL;
  const char* arg3;
  arg3 = (const char*)NULL;
arg1 = val_to_Window(inHandle);

  if (arg1 == NULL) {
    val_throw(alloc_string("Invalid this pointer"));
  return;
  }
arg2 = message.c_str();
arg3 = title.c_str();
  arg1->Alert(arg2, arg3);
}

DEFINE_PRIME3v(lime_Window_Alert);
1 Like


Thanks, but can I specify how Haxe types are mapped to C++ types with those tools? (Things I defined in Common.xml)

Based on hxcpp’s conventions, ExtensionBoilerplate uses val_* functions to convert from Haxe types to C++. For instance, val_int() takes a Haxe Int and returns a C++ int. To convert from C++ to Haxe, it uses alloc_* functions. For instance, alloc_float() converts from a C++ double to a Haxe Float.

You can define custom val_* or alloc_* functions in any of your project’s header files, and they’ll automatically be available to ExternalInterface.cpp. The name of the val_* and alloc_* functions should match the Haxe types, not the C++ types, but their return/argument types should match the C++.

Also note that CFFIAPI.h already defines a bunch of common types, so you don’t have to worry about those.

I was not sure how these tools works. README in ExtensionBoilerplate says “…uses a naive method of identifying functions; if a function declaration spans multiple line, the function will not be recognized.” and “…does not generate any Haxe code. For that, see inthebox-macros.”.

So,

  1. Should I create both C++ header files and haxe souce files to use those tools? That seems to require more work than my tool.
    2 Can I check for errors before passing arguments to native functions? This might be required in some cases to avoid native crash.

I was not sure if I could allocate temporary variables on stack without copying, though that seems possible without copies thanks to return value optimization.

Yes, this setup requires both, and yes, there’s definitely room for improvement.

You could do so in any custom val_* function you wrote. Otherwise, you’d have to do it on a case-by-case basis, which might defeat the point. (Or it might not, if it only fails in specific cases.)


By the way, why does your library have to be part of Lime? It’s a build macro, right? Why not just make a library with that build macro?

Because I started writing it as an improvement to existing lime macro. It would be possible to release this as a separate library.

I suggest releasing it as a separate library. The OpenFL team doesn’t like to accept new features without extensive testing, and they rarely have time for that.

i would suggest the following approach to reduce the effort for generating the DEFINES for each.

This is defined in ExternalInterface.h

static value math_extension_createobject_method (value inputValue) {

 return math_extension::createObject(val_int(inputValue));

}

DEFINE_PRIM (math_extension_createobject_method, 1);

The method helps to create the C++ object and then wrap it as abstract and pass it back.
value createObject(int i)
{
switch (i)
{
case 1:
return ObjectToAbstract(new Rectangle(),kindRectangle);
break;
case 2:
return ObjectToAbstract(new Point(),kindPoint);
break;
default:
break;
}
}

create a haxe class to the extern.

> @:include(“Rectangle.h”)
> @:native("Rectangle")*
> @:keep
> @:native(“cpp::Reference”)
> extern class Rectangle
> {

> public function values(w : Int, h : Int) : Void;
> public function pointvalues(p:Point) : Void;
**> public function area() : Int; **
> public function fire():Void;
> }

Now create a haxe wrapper to the extern class

class RectangleClass
{
var _o:Dynamic;
private function new(d:Dynamic)
{
_o = d;
}

 public function set_values(w : Int, h : Int) : Void
 {
      this.getHandle().values(w,h);
 }

 public function set_point_values(pointClass:PointClass) : Void
 {
    this.getHandle().pointvalues(pointClass.getHandle());
 }

 public function area() : Int
 {
     return this.getHandle().area();
 }

 public function fire():Void
 {
    this.getHandle().fire();
 }

 public function getHandle():Rectangle
 {
     var r:Rectangle = untyped __cpp__("(Rectangle*){0}->__GetHandle()",_o);
     return r;
 }

 public static function createObject():RectangleClass
 {
     var r = Math_extension.createObject(1);
     var r1 = new RectangleClass(r); 
     return r1;
 }

 public static function createObjectFrom(o:Dynamic):RectangleClass
 {
     var r1 = new RectangleClass(o);
     return r1;
 }

}