COMMUNITY

Wrap C library in haxecpp?

Hello,

I am currently porting Haxe to Nintendo Switch homebrew (devkitA64), using hxcpp. I was able to run a hello world, and now I would like to write a small wrapper for the Switch homebrew C library libnx.

I started simple with a simple function: consoleInit(). For that, I wrote a Console class : https://pastebin.com/fQ3YPqKm

However I feel like this is wrong - I should not have to duplicate the class in two. The thing is that the cppFileCode directive only works on top of “real” classes, and not extern ones, which is why I had to make two classes (consoleInit is in switch.h).

Any pointers? Is there a way to add project-wide includes to avoid that scenario?
Thanks!

Not to sound like a broken record on this forum, but you could try with ammer. :slight_smile:

The process for defining ammer definitions is fairly simple – you need to map the C types to the Haxe types you want. For most common cases, there is a 1-to-1 mapping (e.g. const char* is a String). You can also define data types that map to library-specific types in C, like the PrintConsole in your code example.

For your library, the definition might looks something like:

class Nx extends ammer.Library<"nx"> {
  // ...
}
@:ammer.struct // for alloc and free
class PrintConsole extends ammer.Pointer<"PrintConsole", Nx> {
  public function consoleInit(_:ammer.ffi.This):Void;
}
class Main {
  public static function main():Void {
    var console = PrintConsole.alloc();
    console.consoleInit();
    // ...
  }
}
-lib ammer
-D ammer.lib.nx.include=path/to/include/directory
-D ammer.lib.nx.library=path/to/lib/directory
-D ammer.lib.nx.headers=switch.h,nx.h,and_so_on.h
-main Main
-cpp out.cpp

(There is as of yet no way to create the type definitions automatically, since that would involve parsing the header files and essentially having a full C parser. Maybe one day.)

The manual should hopefully provide enough information.

I should specify that the Switch platform can only run statically linked binaries, and there is no way to load code at runtime. Does ammer work in that case ? Because I see shared objects and dylib and such :smiley:

You should be in luck because on C++ I rely on the native compilation rather than dynamically-loaded FFI (like on HashLink and Lua). hxcpp links libraries dynamically by default, but I believe this is configurable. And in fact, I assume you must have already linked the library statically for your code example to work. Did you put a configuration flag in your build to make the linking static? Does the compiler toolchain for Switch do static linking only?

Did you put a configuration flag in your build to make the linking static

Yes !

Does the compiler toolchain for Switch do static linking only?

Yes, although I did not include the linking step in my nxhpp toolchain yet (because there are multiple steps and a custom linker script) - for now I output a libMain.a that I then link in a standard homebrew using the standard Makefile :see_no_evil:

The end goal is to get rid of that “wrapper” so that the Haxe toolchain produces everything.

Let me try Ammer, that looks sweet !

1 Like

@Aurel300 the library is absolutely trying to link with -L, which does not work because aarch64-none-elf-ar does not support -L

How can I remove that?

The actual linking paths for C++ are specified here. For now I think you should try to modify those to whatever you’d have in Build.xml if you were doing this manually. I think just providing the full path to a .a file makes it link statically? The -L argument comes from the libpath directive. If the absolute path to .a is provided, I think that directive can be removed completely.

Edit: alternatively, you could let the build fail, then edit the produced Build.xml file directly and re-build using haxelib hxcpp run Build.xml.

You could use @:include on native classes to ensure the switch header file is included, so your example would look like this.

@:include('switch.h')
@:native('PrintConsole')
extern class PrintConsole
{
	//
}

@:include('switch.h')
extern class ConsoleNative
{
	@:native('consoleInit')
	static function consoleInit(_console : cpp.Star<PrintConsole>)
}

class Console
{
	public static function init()
	{
		ConsoleNative.consoleInit(null);
	}
}

Also, is your hxcpp toolchain publically available? About a month ago I got hxcpp running on the psvita homebrew sdk and I’d love to see what changes were done to get it running on the switch!

The -L argument comes from the libpath directive. If the absolute path to .a is provided, I think that directive can be removed completely.

Yeah I will add libnx at the toolchain level (in hxcpp directly). So I need to fork Ammer then, to remove that directive.

alternatively, you could let the build fail, then edit the produced Build.xml file directly and re-build using haxelib hxcpp run Build.xml .

I really want to reduce the steps as much as possible, ideally run haxe --cpp -D libnx and have everything magically work (including linking step)

You could use @:include on native classes to ensure the switch header file is included, so your example would look like this.

I tried that but it adds #include "switch.h", which is wrong (it needs to be #include <switch.h>) (include path is added at toolchain level)

Also, do your hxcpp toolchain publically available?

Not yet but I did very little changes - since devkitA64 is based on newlib, the linux toolchain with aarch64-none-elf prefix worked out of the box. The whole hxcpp standard library built without any change to the code, which is awesome

So I need to fork Ammer then, to remove that directive.

I assume you got ammer via haxelib git or git clone, so the change should be easy to make locally. I do actually want static linking support in ammer, I just haven’t had a need for it yet. So if the .a thing works in your project, I’ll add some configuration flags to make it painless. (Something like -D ammer.lib.nx.link=static, and maybe an extra path to the .a object, if the naming is not consistent.)

Yeah I’ll do that :+1:

Does Ammer support C++ classes as well? I have a C++ library that I want to bind. I know I can do it using “extern” but if I can be consistent in the project and use the same mechanism for everything (libnx + my library) that would be great

Yes, although I haven’t tested it very thoroughly. In the test suite I just use some template instantiations (C++ side and ammer side). You can specify that a library is meant to be C++ with -D ammer.lib.thelibname.abi=cpp. Please report any issues you find!

1 Like

Thats good to hear. The vita’s SDK also provides a newlib and pthreads libraries but they don’t work that great / aren’t complete. Had to do a fair bit of commenting and stubbing out to get it to compile and not crash on startup.

I was able to remove the bad AR arguments by removing the directives in PatchCpp.hx. It’s kinda hacky but it does the job in the meantime.

Now it fails when linking because it doesn’t find w_init in Console.cpp. That function is in ammer_nx.cpp. It looks like that file is not included in the final libMain.a, any idea why?

Hmm, that’s strange. I can’t reproduce the error on OS X, even with a statically-linked C++ library. I assume Console is the name of the library/class you are using to define it? And it has a method called init? Couple of things to check:

  • Does the generated Console.cpp reference one of the ammer/externs/....h files?
  • Does the cpp file corresponding to that header file (i.e. src/ammer/externs/....cpp) in turn have something like #define AMMER_CODE_0 #include "../ammer/ammer_native.cpp.c" #undef AMMER_CODE_0 ?

I am happy to help you debug, but maybe it’d be better to PM at this point, either over messages here or e.g. on Discord (Aurel300#1177).

I feel stupid now, it worked after a clean of the generated source code :man_facepalming:

And the code runs on the console :tada:

I added you on Discord in case I have more questions

@Aidan63 the code is here if you want to see: https://github.com/retronx-team/switch-haxe (link to the hxcpp fork in the readme)

1 Like