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:
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 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
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
@: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
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();
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 , 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!
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
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. †
† 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)
Hey fantastic news! 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 Extension → Build 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)
@haxiomic So what is the minimal hxml to compile to watchos and watchsimulator now? The changes you made was merged - and I should have them by haxelib update right?