Search code examples
c++haxe

Access C++ class from Haxe using extern


I want to access C++ code from a Haxe program. I am trying to use extern class but am facing some issues. I am trying to compile using haxe -main Cpp_Extern -cpp Cpp_Extern.

My Haxe code:

@:include("./Rectangle.cpp")
@:extern("Rectangle*")
extern class Rectangle
{
    @:native("set_values") public function set_values(w : Int, h : Int) : Void;
    @:native("new Rectangle") public static function create() : Rectangle;
}

class Cpp_Extern
{
    public static function main()
    {
        Rectangle.set_values(10,20);
    }
}

Code for C++

#include <iostream>

class Rectangle
{
  public:
  void set_values (int x, int y) 
    {
        std::cout << "x = " << x << "\n";
        std::cout << "y = " << y << "\n";
    }
};

int main()
{
    Rectangle one;
    return 0;
}

The error is

C:\Users\ila5\Desktop\CPP\Cpp_Extern>haxe -main Cpp_Extern -cpp Cpp_Extern
haxelib run hxcpp Build.xml haxe -Dhaxe3="1" -Dhaxe_ver="3.201" -Dhxcpp_api_level="321" -I"C:\\HaxeToolkit\\haxe\\extraLibs/" -I"" -I"C:\\HaxeToolkit\\haxe\\std/cpp/_std/" -I"C:\\HaxeToolkit\\haxe\\std/"
cl.exe -Iinclude -nologo -O2 /WX- /fp:precise -DHX_WINDOWS -D_USING_V140_SDK71_ -GR -FS -Oy- -c -EHs -GS- -arch:SSE -IC:/HaxeToolkit/haxe/lib/hxcpp/3,2,205/include -DHXCPP_VISIT_ALLOCS -DHXCPP_API_LEVEL=321 -D_CRT_SECURE_NO_DEPRECATE -D_ALLOW_MSC_VER_MISMATCH -D_ALLOW_ITERATOR_DEBUG_LEVEL_MISMATCH -wd4996 -MT ./src/Cpp_Extern.cpp -FoC:/Users/ila5/Desktop/CPP/Cpp_Extern/Cpp_Extern/obj/msvc19xp/2c1b12bd_Cpp_Extern.obj
Cpp_Extern.cpp
./src/Cpp_Extern.cpp(30): error C2440: 'initializing': cannot convert from 'Rectangle *' to 'Rectangle'
./src/Cpp_Extern.cpp(30): note: No constructor could take the source type, or constructor overload resolution was ambiguous
./src/Cpp_Extern.cpp(34): error C2819: type 'Rectangle' does not have an overloaded member 'operator ->'
C:/Users/ila5/Desktop/CPP/Cpp_Extern/Rectangle.cpp(4): note: see declaration of 'Rectangle'
./src/Cpp_Extern.cpp(34): note: did you intend to use '.' instead?
./src/Cpp_Extern.cpp(34): error C2232: '->Rectangle::set_values': left operand has 'class' type, use '.'

Error: Build failed

Solution

  • You need to add the @:include and @:extern metadata to your extern class so that hxcpp can point to the correct file and resolve when compiling.

    The @:include meta tag allows you to point to a file, which is placed above your extern class definition. The file needs to be relative to your build output directory.

    The @:extern meta tag tells hxcpp to resolve the class definition to the name of the class you expect to see in C++. So, if you're creating a pointer, the class would be instantiated using the new keyword, and the extern definition would be the following:

    @:include("includes/Rectangle.cpp")
    @:extern("Rectangle*")
    extern class Rectangle
    {
        @:native("set_values") public function set_values(w : Int, h : Int) : Void;
        @:native("new Rectangle") public static function create() : Rectangle;
    }
    

    You cannot use the new function definition with an extern class, because it is not a regular Haxe class. Instead you need to make a static function that will do the instantion, which will return the extern class type into Haxe context, allowing you to then access its member variables.

    If you have problems including a file for hxcpp to find, what you can do is use an XML file, which you would use to tell hxcpp what folders to find.

    On Windows, you can use the <files id="haxe"> tag followed by the contents <compilerflag value="-I/../includes/" />. Of course, close this tag off and make sure that the includes folder is in the root of your project.

    This will generate an additional compiler flag telling the Haxe Compiler to look in an additional folder for C++ source files to include. You can then remove the include/ prefix in your @:include just before your extern class definition.

    To then tell hxcpp to use the XML file, you need to use the @:buildXml meta tag above the main entry point, which should contain the following: <include name="${haxelib:myCustomRepo}/../Build.xml" /> Again, this needs to be relative to the root of your project.

    In the above XML example, I use the ${haxelib:myCustomRepo} which tells Haxe to look for the path of the given haxelib. You can set a haxelib repository development directory using the command: haxelib dev myCustomRepo ./ that may help you in this situation. This will entirely depend on if you use that route.

    You don't have to use XML files, but it's recommended for larger projects, especially when building across platforms and targets.

    I also noticed you are missing a constructor in your C++ code, so don't forget to put that in. In addition, your main entry point code should now look like this:

    @:buildXml('<include name="${haxelib:myCustomRepo}/../Build.xml" />') //only if you are using XML
    class Main {
        public static function main() {
            var rect = Rectangle.create();
            rect.set_values(10, 20);
        }
    }
    

    There are two ways you can instantiate objects in C++. You can use the new keyword in which case the returning object should be a pointer to the type. So new Rectangle() will return a Rectangle* in C++. If you use Rectangle.create() in Haxe, and assign it to a variable, that's what you effectively get.

    You can also instantiate, or rather declare, using Rectangle rect; in C++. This is accessed like a struct, so instead of using -> to access members, you use a full stop '.'

    For this to work in Haxe, you need to add the @:structAccess metadata on the extern. In addition, you need to change the @:native from being @:native("Rectangle*") to @:native("Rectangle&"). This makes sure that the initial variable will always be used as a reference.

    The & means to use a variable as a reference, unlike pointers, which is a reference.