COMMUNITY

Missing hxcpp.h in cpp compilation

I have compiled haxe into cpp, but the resulting source code does not contain hxcpp.h and it is imported in two places by it:

#include <hxcpp.h>

What is that file, and is it supposed to come with the compile?

My build.hxml looks like:

-cp src
-main src.Main
-cpp bin/cpp

It’s included in the hxcpp library that’s automatically used when compiling to cpp (hxcpp/hxcpp.h at master · HaxeFoundation/hxcpp · GitHub), it also contains the build tool that’ll build your project into an executable or dll.

In my scenario I am looking to consume the source code in Xcode, so I guess I need to add that dependency into the cpp code.

Consuming the generated code directly like this can be tricky – the canonical way to do it is to expose certain classes with @:nativeGen and link against the binary output (add -D dll_link or -D static_link to your hxml generate a binary lib instead of an exe). See the extern-lib and extern-use examples here: hxcpp/test at master · HaxeFoundation/hxcpp · GitHub

When interacting with haxe you have to consider two things: haxe’s garbage collector and haxe’s event loop: When calling into haxe code you first need to initialize haxe’s internal structures with hx::Init() and then need to ‘attach’ the garbage collecting context which you can do with RAII and hx::NativeAttach autoAttach;. If you want things like haxe.Timer to work and haxe multi-threading you must spin the event loop (which I don’t have an example for)

I’ve created a library to handle all this: GitHub - haxiomic/haxe-c-bridge: Easily interact with haxe classes from C with an automatically generated C header
You just add @:build(HaxeCBridge.expose()) to classes you want to be accessible and it generates a dependency-free single C header with all exposed functions alongside the binary output. It makes sure the GC doesn’t kill objects passed outside of haxe-land and haxe code is executed in a separate thread which spins its own event loop, so timers, GC and multithreading will all work. The haxe-thread is synchronized when calling haxe code so you can safely call exposed haxe functions from any thread.

Works with Swift bridging headers so you can interact with your haxe library from Swift when building iOS and mac apps

To compile haxe-code for iPhone/iPad ARM CPUs, you can add this to your hxml

-D iphoneos
-D HXCPP_ARM64

Or

-D simulator
-D HXCPP_M64

for simulator binaries for desktop use

You may also need to set the iPhone SDK:

# leaving SDK empty should map to the default
# if you have errors about missing SDK then explicitly set the SDK version on your system (e.g. -D IPHONE_VER=12.2)
-D IPHONE_VER=

List of hxml flags for compiling to different platforms:

1 Like

Last I ran into this was because my hxcpp was outdated, so it’s probably worth double checking you have an up to date version installed.

Thank you for the detailed instructions and for haxe-c-bridge haxiomic.

back2dos, I have used the most recent version, 4.2.1

1 Like
class Dependency {
	public static var description = "I am a non attributed class in haxe";
	public static function getDescription() {
		return "I am a no attributed class in haxe";
	}
}

@:build(HaxeCBridge.expose())
class AttributedDependency {
	public static function getDescription() {
		return "I am a attributed class in haxe";
	}
}

I get:

HaxeString src_Main_src_AttributedDependency_getDescription();

With

-cp src
-main src.Main
--dce full
-D HXCPP_M64
-D simulator
--library hxcpp
-D static_link
-cpp bin/mycpp

… I only had public static var fields at first in my AttributedDependency, but when I changed to public static function it got generated (in addition to const char* src_Main_initializeHaxeThread(HaxeExceptionCallback unhandledExceptionCallback); and void src_Main_stopHaxeThreadIfRunning(bool waitOnScheduledEvents); methods described).

If I understand correctly, it should be enough to have the generated libMain.a and the src_Main.h in the same folder inside the the Xcode project folder, and select a bridging file?

I’ts not working with watchsimulator or watchos, which is my use case:

Error: In file included from ./src/src/DependencyA.cpp:5:
include/src/DependencyA.h:54:3: error: found ‘<::’ after a template name which forms the digraph ‘<:’ (aka ‘[’) and a ‘:’, did you mean ‘< ::’?
HX_DO_RTTI_ALL;
^
/usr/local/lib/haxe/lib/hxcpp/4,2,1/include/hx/Macros.h:92:26: note: expanded from macro ‘HX_DO_RTTI_ALL’
static ::hx::ObjectPtr<::hx::Class_obj> __mClass;
^
In file included from ./src/src/DependencyA.cpp:5:
include/src/DependencyA.h:54:3: error: found ‘<::’ after a template name which forms the digraph ‘<:’ (aka ‘[’) and a ‘:’, did you mean ‘< ::’?
/usr/local/lib/haxe/lib/hxcpp/4,2,1/include/hx/Macros.h:93:19: note: expanded from macro ‘HX_DO_RTTI_ALL’
::hx::ObjectPtr<::hx::Class_obj > __GetClass() const { return __mClass; }
^
In file included from ./src/src/DependencyA.cpp:5:
include/src/DependencyA.h:54:3: error: found ‘<::’ after a template name which forms the digraph ‘<:’ (aka ‘[’) and a ‘:’, did you mean ‘< ::’?
/usr/local/lib/haxe/lib/hxcpp/4,2,1/include/hx/Macros.h:94:33: note: expanded from macro ‘HX_DO_RTTI_ALL’
inline static ::hx::ObjectPtr<::hx::Class_obj> &__SGetClass() { return __mClass; }
^
3 errors generated.
Error: Build failed

(But it works for: -D iphoneos and -D simulator)

Ahh interesting, thanks for the update! Looks like changes in watchos have broken hxcpp compilation. The fix is simply adding -std=c++11 I’ve created an PR (hxcpp#953). A workaround until we have the fix merged upstream is to inject the flag manually. Not ideal I know! You can add build commands using the @:buildXml metadata. For example, add the following to your Main class and it should compile for watchos :slight_smile:

@:buildXml('
<compiler id="WatchOS"  >
	<flag value="-std=c++11" />
</compiler>
<compiler id="WatchSimulator"  >
	<flag value="-std=c++11" />
</compiler>
')
class Main {
	static function main() {
		trace('haxe thread started!');
	}
}

A couple minor tweaks we can make file to simplify things a little in the meantime:

  • -cp src makes src/ a root class path, so that files within are in the root package (package;) and we can drop package src and src.Main
  • --library hxcpp is inferred from just having -cpp
    So hxml can be
-cp src
-main Main
--dce full
-D watchos
-D static_link
-cpp bin/mycpp

and then we don’t need package src in Main

Xcode keeps telling me that:

Undefined symbols for architecture x86_64:
“_src_Main_src_AttributedDependency_getDescription”, referenced from:
MyApp_WatchKit_Extension.ContentView.init() → MyApp_WatchKit_Extension.ContentView in ContentView.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

When I have no or some flag like HXCPP_ARM64, it changes the architecture part of the message to what I think is depending what’s used in the compile in haxe, but it will not build on the device I have or in simulator. This is when i call src_Main_src_AttributedDependency_getDescription();

Hey @Lelelo1, that looks like either:

Xcode isn’t linking to the compiled lib, or the compiled lib uses a different architecture to the one you’re currently building for. By default simulator uses i386 for me

I’ve created a working example project you can test against:
ExampleHaxeWatch.zip

cd into the haxe-code directory and run

haxelib install build.hxml
haxe build.hxml

Then open the xcode project and trying building for simulator and device :slight_smile:, simulator works for me, I don’t have a device to test however (and I’m on an older Xcode) so let me know how it goes for you!

(If your simulator is using x86_64 you can add -D HXCPP_M64 for 64bit)

I had to make a couple other changes to that PR, so my example uses the patched version of hxcpp in my PR

I also had to add -lc++ to the “other linker flags” in Xcode

Thank you for the sample

I get:

ExampleHaxeWatch/haxe-code/.haxelib/haxe-c-bridge/0,5,2/HaxeCBridge.hx:1591: characters 10-23 : Abstract<sys.thread.Thread> has no field initEventLoop (Suggestion: runWithEventLoop)
ExampleHaxeWatch/haxe-code/.haxelib/haxe-c-bridge/0,5,2/HaxeCBridge.hx:1598: characters 14-18 : Class<haxe.EntryPoint> has no field init

when I run haxe build.hxml

Ahh, sorry about that, I had changes that I hadn’t pushed to haxelib – thank you for flagging this up!

If you run haxelib update it will pull the latest haxe-c-bridge and should work after

(Alternative you can copy in the HaxeCBridge.hx file from github)

I am still having some trouble. If I understand correctly the .a lib needs to support 2 architectures for me:

Screenshot 2021-04-17 at 12.26.22

When I add the needed flags for these:

# # watchsimulator build, i386 by default (add HXCPP_M64 for x86_64)

-D watchsimulator
-D static_link
-D HXCPP_ARM64
-D HXCPP_M64

…I think haxe takes the second flag HXCPP_M64 overwrites HXCPP_ARM64, so I get this message instead:

Screenshot 2021-04-17 at 12.56.04

where i386 is lost by specifying a flag

I need some way of using multiple architecture flags, or handle different lib.a:s?

Hey,

This is a new-to-xcode 12 thing (I’m still on 11): it wants the lib to include all possible architectures you could use, that’s a great hint if you’re publishing the .a file to devs who might require those but doesn’t make sense just testing locally in simulator on your machine. The solution is to remove VALID_ARCHS from Build settings (in User-Defined). Then it should just require whatever arch your simulator requires
The solution is to exclude the architectures you don’t need: Xcode 12, building for iOS Simulator, but linking in object file built for iOS, for architecture arm64 - Stack Overflow

However, you can indeed make a .a file that includes multiple architectures if you want :), the trick is that you build them all individually and combine them into a single file at the end. This is what I do in the example build.hxml with a tool called lipo. haxe can run multiple compiles with the --next flag, the idea is you separate compiler flags with --next and it just executes each block one by one. Often times you want some flags to apply to all, to do this you use --each after the shared flags (I do this in the example build.hxml too). When you’ve got all your separate .a files, you can use --cmd to call lipo (also in build.hxml). Having a single .a file is useful because you can share it with others and it will work with both simulator and device builds. The downside is the increased compile time of multiple builds. In the past I’ve used an Xcode ‘runscript’ to execute the haxe build for just the specific architecture currently being targeted which makes things nice and fast

In your case, I recommend just deleting the new VALID_ARCHS thing, and just using lipo to create a single simulator/device .a file so you don’t have to fiddle with Xcode config much

Thanks a lot!
I think I understand how to add multiple architectures like you described (using --next, change paths and add to lipo statement). However, I can’t seem to compile ARM64 correctly. When I specify HXCPP_ARM64, default (i368) and HXCPP_M64 - I get a i368 clashing with other i368 architecture error from haxe.
When I only specify HXCPP_ARM64 - Xcode says arm64 is missing. So it seems i368 arch is in fact made from that flag…?

Also I think VALID_ARCHS from Xcode 12 and up is removed.

I am trying to setup the simulator part for now only. But I will add the release architecture variants later.
In the end I want to support as many apple watches as possible.

This is my bad again, I thought I’d pushed up arm64 support earlier after adding it, -.- sorry about that.

If you get the latest changes on my hxcpp patch branch HXCPP_ARM64 will now work. haxelib hxcpp update should do it, but you can also cd into the hxcpp git directory and do git pull. †

As for VALID_ARCHS, you’re right and I wasn’t reading thoroughly enough, seems like you can exclude architectures instead or enable ONLY_ACTIVE_ARCH: Xcode 12, building for iOS Simulator, but linking in object file built for iOS, for architecture arm64 - Stack Overflow


† I’m not happy with hxcpp’s current approach of requiring new architectures defined in the config before they work so I’ve also added a new flag so you can set whatever arch you like with -D HXCPP_ARCH=arm64. Opened an issue and will work to make this work on all targets in the future (current added for watchos and watchsimulator)

The sample worked now after updating!

This is what the watch simulator archs section looked like for me from editing the sample:

--each

# # watchsimulator build, i386 by default (add HXCPP_M64 for x86_64)

-D watchsimulator
-cpp bin/watchsim_i368

--next

-D watchsimulator
-D HXCPP_M64
-cpp bin/watchsim_x86_64

--next
 
 -D watchsimulator
-D HXCPP_ARM64
-cpp bin/watchsim_arm64

Lipo section:
--cmd lipo bin/watchos/libExampleLibrary.watchos.a bin/watchsim_i368/libExampleLibrary.watchsimulator.a bin/watchsim_x86_64/libExampleLibrary.watchsimulator.a bin/watchsim_arm64/libExampleLibrary.watchsimulator.a -create -output bin/libExampleLibrary.a

I think I needed to have the watchsimulator used for every simulator arch flag

Hey fantastic news! :slight_smile: Sorry it was a bit of a journey getting there; I think the hxcpp watchos pipeline was in need of some TLC. Hopefully those PRs will merge soon and you can go back to mainline hxcpp. iOS and macOS should be a smoother experience because they get more attention.

If you find compile-time is too slow for iterating on local test builds I think the thing to do is enable ONLY_ACTIVE_ARCH and then you can compile just for that until you’re ready to release. You can have Xcode run the haxe compile with the right platform and arch flags for the current dev build. I’ve extended the example project with the following changes:

  • created a new hxml file called build-dev.hxml which is used to run just a single build (and build-release.hxml to run all builds and lipo together)
  • added a “Run Script” at the top of ExampleHaxeWatch WatchKit ExtensionBuild Phases. That script calls `haxe build-dev.hxml:
# compile haxe libray for the current platform and arch 
cd haxe-code

# install dependencies if not already installed
if [ -z $(haxelib list) ]; then
    haxelib install build-dev.hxml --always
    # setup the git version of hxcpp
    haxelib set hxcpp git
    yes | haxelib run hxcpp
fi

# EFFECTIVE_PLATFORM_NAME is strings like "-watchos" "-watchsimulator" or "" for macOS
# NATIVE_ARCH should contain the -arch flag
# see more here: https://pewpewthespells.com/blog/buildsettings.html
haxe build-dev.hxml -D ${EFFECTIVE_PLATFORM_NAME#*-} -D HXCPP_ARCH=$NATIVE_ARCH

Hxcpp ensures the haxe code is only recompiled when changes are detected so there’s negligible overhead of this script when you’re just editing the Swift code

Download new sample: ExampleHaxeWatch 2.zip (You may need to make the arch tweaks for Xcode 12, only tested in 11)