Thanks, as seems increasingly usual, to Joshua Granick for writing about another awesome Haxe feature that works so well with NME, but no one seems to know about: Inline C++!
This seems so obvious! It compiles to C so why can’t we throw our own stuff in there? Obviously you sacrifice the niceties of a higher level language like HaXe: Vague compiler errors and manual memory management abound, but it’s worth it sometimes.
For example: My first thought, since I’ve been implementing around it for some time, was to try and implement game saves on my TouchPad. I actually tried to use NME’s extension system to implement this a few weeks ago and got nowhere, so I wasn’t sure what to expect. I wanted to call into the webOS API to get a safe path using PDL_GetDataFilePath(), then write a string out to a file, and in a second function be able to read that string back.
So how do we accomplish this? It’s thanks to compiler metadata flags in HaXe, which you can review in full here, but we’re only really interested in @:headerCode and @:functionCode. The former is used to add includes to the top of the C++ output file, and the latter to inject your C++ in to the top of a HaXe function.
So first lets import the things we need:
#if webos @:headerCode ("#include \"PDL.h\"\n#include <stdio.h>") #end
So we’re importing the PDL library and stdio. Notice I’ve put them both in a single headerCode statement: I had trouble using multiple statements, only the first would be included.
Next we want to actually look at writing a function that bridges between C++ and Haxe, which has a lot of intricacies. If you’ve ever looked at making an NME extension, you’ll know that there’s a lot of converting of variables when they’re passed between languages, and this is no different: Strings are completely different structure, but the conversion is made easier for us sometimes. Lets look at a function which runs PDL_GetDataFilePath and returns the result.
#if webos @:functionCode (" PDL_Init(0); char saveDir [256]; if (PDL_GetDataFilePath(\"\", saveDir, 256) == PDL_NOERROR) { PDL_Quit(); #ifdef HX_UTF8_STRINGS return Dynamic( String(saveDir,strlen(saveDir)).dup() ); #else return Dynamic( String(saveDir,strlen(saveDir)) ); #endif } PDL_Quit(); return HX_CSTRING(\"PDL error\"); ") #end private function getDataPath():String { return ""; }
Let’s go through the code. First you’ll notice we have the @:functionCode header filled with C++, above a Haxe function called getDataPath, which returns a string. At build time our C++ will be moved down in to the lower function, above any code already within the function, so to make the Haxe compiler happy before that we have to have a return statement in our empty Haxe function although it’ll never be run in practice.
Now to the C. It’s all fairly normal until it gets to the business of returning the value: Like I said, Haxe strings are very different beasts. To return a string literal, you can just use HX_CSTRING(“MyLiteral”), as with the “PDL Error” message at the end of the code. However, returning a string from a variable is much stranger. The block which returns a good value above, within the compiler conditionals checking HX_UTF8_STRING, is actually taken from the implementation of the type conversion methods used for NME extensions, so I’m not going to pretend to explain it, but it gets your string back to HaXe successfully.
I’m sure you can imagine the function for reading data from a file: It’s much like the above, but with standard C file IO (fopen, fread, fclose etc), so lets skip over that and go straight to writing to the file which showcases the last few interesting things I ran across.
public var dataPath(default, null):String; #if webos @:functionCode (" FILE * fp = fopen(this->dataPath.__CStr(), \"w+\"); if (fwrite(data.__CStr(), sizeof(char), strlen(data.__CStr()), fp) != strlen(data.__CStr())) { fclose(fp); return false; } fclose(fp); return true; ") #end public function setSaveData(data:String):Bool { return false; }
The most interesting part of the above function is that we not only pass a String in to the function, we also access an instance variable which contains the path gotten earlier. Notice that both the Haxe strings actually have a function which will neatly convert them to a C string, as in dataPath.__CStr() and data.__CStr(), so this way around is much simpler than returning strings. Also notice I return a bool without difficulty: I think many of the basic types are fine to pass around, but Strings were the big thorn in my side.
That’s basically it! This is a really neat way to accomplish an otherwise complex task, and the fact you can call in to native libraries across multiple devices makes it indispensable for anyone wanting to publish an NME game.
– B