Being Cross-platform with haXe

Exclusive offer: get 50% off this eBook here
haXe 2 Beginner's Guide

haXe 2 Beginner's Guide — Save 50%

Develop exciting applications with this multi-platform programming language

£16.99    £8.50
by Benjamin Dasnois | July 2011 | Open Source Web Development

haXe allows us to target several platforms; so, you may want to take advantage of this feature to be able to use your applications or libraries on several platforms. Unfortunately, there are some drawbacks, but don't worry, we will go through them and see how to work around them.

In this article by Benjamin Dasnois, author of haXe 2 Beginner's Guide: RAW, we will:

  • See what is cross-platform in the standard library
  • Talk about platform-specific packages
  • Learn about their specificities
  • Learn about conditional compilation

So, not only are we going to talk about being cross-platform, but also about platform-specific things. So, if you're ready, let's get started!

 

haXe 2 Beginner's Guide

haXe 2 Beginner's Guide

Develop exciting applications with this multi-platform programming language

        Read more about this book      

(For more resources on this subject, see here.)

What is cross-platform in the library

The standard library includes a lot of classes and methods, which you will need most of the time in a web application. So, let's have a look at the different features. What we call standard library is simply a set of objects, which is available when you install haXe.

Object storage

The standard library offers several structures in which you can store objects. In haXe, you will see that, compared to many other languages, there are not many structures to store objects. This choice has been made because developers should be able to do what they need to do with their objects instead of dealing with a lot of structures, which they have to cast all the time.

The basic structures are array, list, and hash. All of these have a different utility:

  • Objects stored in an array can be directly accessed by using their index
  • Objects in a list are linked together in an ordered way
  • Objects in an hash are tied to what are called "keys", but instead of being an Int, keys are a String

There is also the IntHash structure that is a hash-table using Int values as keys.

These structures can be used seamlessly in all targets. This is also, why the hash only supports String as indexes: some platforms would require a complex class that would impair performances to support any kind of object as indexes.

The Std class

The Std class is a class that contains methods allowing you to do some basic tasks, such as parsing a Float or an Int from a String, transforming a Float to an Int, or obtaining a randomly generated number.

This class can be used on all targets without any problems.

The haxe package

The haxe package (notice the lower-case x) contains a lot of class-specific to haXe such as the haxe.Serializer class that allows one to serialize any object to the haXe serialization format or its twin class haxe.Unserializer that allows one to unserialize objects (that is "reconstruct" them).

This is basically a package offering extended cross-platform functionalities.

The classes in the haxe package can be used on all platforms most of the time.

The haxe.remoting package

This package also contains a remoting package that contains several classes allowing us to use the haXe remoting protocol. This protocol allows several programs supporting it to communicate easily. Some classes in this package are only available for certain targets because of their limitations. For example, a browser environment won't allow one to open a TCP socket, or Flash won't allow one to create a server.

Remoting will be discussed later, as it is a very interesting feature of haXe.

The haxe.rtti package

There's also the rtti package. RTTI means Run Time Type Information. A class can hold information about itself, such as what fields it contains and their declared types. This can be really interesting in some cases, such as, if you want to create automatically generated editors for some objects.

The haxe.Http class

The haxe.Http class is one you are certainly going to use quite often. It allows you to make HTTP requests and retrieve the answer pretty easily without having to deal with the HTTP protocol by yourself. If you don't know what HTTP is, then you should just know that it is the protocol used between web browsers and servers.

On a side-note, the ability to make HTTPS requests depends on the platform. For example, at the moment, Neko doesn't provide any way to make one, whereas it's not a problem at all on JS because this functionality is provided by the browser.

Also, some methods in this class are only available on some platforms. That's why, if you are writing a cross-platform library or program, you should pay attention to what methods you can use on all the platforms you want to target.

You should note that on JS, the haxe.Http class uses HttpRequest objects and as such, they suffer from security restrictions, the most important one being the same-domain policy. This is something that you should keep in mind, when thinking about your solution's architecture.

You can make a simple synchronous request by writing the following:

var answer = Http.requestUrl("http://www.benjamindasnois.com");

It is also possible to make some asynchronous requests as follows:

var myRequest = new Http("http://www.benjamindasnois.com");
myRequest.onData = function (d : String)
{
Lib.println(d);
}
myRequest.request(false);

This method also allows you to get more information about the answer, such as the headers and the return code.

The following is an example displaying the answer's headers:

import haxe.Http;
#if neko
import neko.Lib;
#elseif php
import php.Lib;
#end

class Main
{

static function main()
{
var myRequest = new Http("http://www.benjamindasnois.com");
myRequest.onData = function (d : String)
{
for (k in myRequest.responseHeaders.keys())
{
Lib.println(k + " : " + myRequest.responseHeaders.get(k));
}
};
myRequest.request(false);
}

}

The following is what it displays:

X-Cache : MISS from rack1.tumblr.com
X-Cache-Lookup : MISS from rack1.tumblr.com:80
Via : 1.0 rack1.tumblr.com:80 (squid/2.6.STABLE6)
P3P : CP="ALL ADM DEV PSAi COM OUR OTRo STP IND ONL"
Set-Cookie : tmgioct=h6NSbuBBgVV2IH3qzPEPPQLg; expires=Thu,
02-Jul-2020
23:30:11
GMT; path=/; httponly
ETag : f85901c583a154f897ba718048d779ef
Link : <http://assets.tumblr.com/images/default_avatar_16.gif>;
rel=icon
Vary : Accept-Encoding
Content-Type : text/html; charset=UTF-8
Content-Length : 30091
Server : Apache/2.2.3 (Red Hat)
Date : Mon, 05 Jul 2010 23:31:10 GMT
X-Tumblr-Usec : D=78076
X-Tumblr-User : pignoufou
X-Cache-Auto : hit
Connection : close

Regular expressions and XML handling

haXe offers a cross-platform API for regular expressions and XML that can be used on most targets' target.

Regular expressions

The regular expression API is implemented as the EReg class. You can use this class on any platform to match a RegExp, split a string according to a RegExp, or do some replacement.

This class is available on all targets, but on Flash, it only starts from Flash 9.

The following is an example of a simple function that returns true or false depending on if a RegExp matches a string given as parameter:

public static function matchesHello(str : String) : Bool
{
var helloRegExp = ~/.*hello.*/;
return helloRegExp.match(str);
}

One can also replace what is matched by the RegExp and return this value. This one simply replaces the word "hello" with "bye", so it's a bit of an overkill to use a RegExp to do that, and you will find some more useful ways to use this possibility when making some real programs. Now, at least you will know how to do it:

public static function replaceHello(str : String) : String
{
var helloRegExp = ~/hello/;
helloRegExp.match(str);
return helloRegExp.replace(str, "bye");
}

XML handling

The XML class is available on all platforms. It allows you to parse and emit XML the same way on many targets. Unfortunately, it is implemented using RegExp on most platforms, and therefore can become quite slow on big files. Such problems have already been raised on the JS targets, particularly on some browsers, but you should keep in mind that different browsers perform completely differently.

For example, on the Flash platform, this API is now using the internal Flash XML libraries, which results in some incompatibilities.

The following is an example of how to create a simple XML document:

<pages>
<page id="page1"/>
<page id="page2"/>
</pages>

Now, the haXe code to generate it:

var xmlDoc : Xml;
var xmlRoot : Xml;
xmlDoc = Xml.createDocument(); //Create the document
xmlRoot = Xml.createElement("pages"); //Create the root node
xmlDoc.addChild(xmlRoot); //Add the root node to the document

var page1 : Xml;
page1 = Xml.createElement("page"); //create the first page node
page1.set("id", "page1");
xmlRoot.addChild(page1); //Add it to the root node

var page2 : Xml;
page2 = Xml.createElement("page");
page2.set("id", "page2");
xmlRoot.addChild(page2);

trace(xmlDoc.toString()); //Print the generated XML

Input and output

Input and output are certainly the most important parts of an application; indeed, without them, an application is almost useless.

If you think about how the different targets supported by haXe work and how the user may interact with them, you will quickly come to the conclusion that they use different ways of interacting with the user, which are as follows:

  • JavaScript in the browser uses the DOM
  • Flash has its own API to draw on screen and handle events
  • Neko uses the classic input/output streams (stdin, stdout, stderr) and so do PHP and C++

So, we have three different main interfaces: DOM, Flash, and classic streams.

The DOM interface

The implementation of the DOM interface is available in the js package. This interface is implemented through typedefs. Unfortunately, the API doesn't provide any way to abstract the differences between browsers and you will have to deal with them in most cases by yourself.

This API is simply telling the compiler what objects exist in the DOM environment; so, if you know how to manipulate the DOM in JavaScript, you will be able to manipulate it in haXe. The thing that you should know is that the document object can be accessed through js.Lib.document.

The js package is accessible only when compiling to JS.

The Flash interface

In a way that is similar to how the Flash is implemented in the flash and flash9 packages, the js package implements the DOM interface. When reading this sentence, you may wonder why there are two packages. The reason is pretty simple, the Flash APIs pre and post Flash 9 are different.

You also have to pay attention to the fact that, when compiling to Flash 9, the flash9 package is accessible through the flashpath and not through flash9.

Also, at the time of writing, the documentation for flash and flash9 packages on haxe. org is almost non-existent; but, if you need some documentation, you can refer to the official documentation.

The standard input/output interface

The standard input/output interface refers to the three basic streams that exist on most systems, which are as follows:

  1. stdin (most of the time the keyboard).
  2. stdout (the standard output which is most of the time the console, or, when running as a web-application, the stream sent to the client).
  3. stderr (the standard error output which is most of the time directed to the console or the log file).

Neko, PHP and C++ all make use of this kind of interface. Now, there are two pieces news for you: one good and one bad.

The bad one is that the API for each platform is located in a platform-specific package. So, for example, when targeting Neko, you will have to use the neko package, which is not available in PHP or C++.

The good news is that there is a workaround. Well, indeed, there are three. You just have to continue reading through this article and I'll tell you how to handle that.

 

haXe 2 Beginner's Guide Develop exciting applications with this multi-platform programming language
Published: July 2011
eBook Price: £16.99
Book Price: £27.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on this subject, see here.)

Platform-specific packages

You've already understood that there are some platform-specific packages, let's have an overview of them.

JavaScript

The platform-specific package for JavaScript is the js package. It basically contains only typedefs representing the DOM and has a layout that's not comparable to any one of the other platform-specific package.

Flash

As we've already mentioned, Flash has two platform-specific packages: the first one is flash and it is used when targeting Flash up to Flash 8, or else, you use the flash9 package (but access it by writing flash). Those packages contain definitions for the external classes that are natives in Flash. Their layouts are not comparable to any other platform-specific packages.

Neko

Neko has its own package named neko. It contains a lot of useful classes allowing for example, sockets communication, file-system access, console input and output, and threads management.

Its layout is comparable to the layouts of the C++ and PHP packages.

PHP

The php package is a PHP-specific package. It allows us to manipulate files, write, and read from the console and open sockets.

Its layout is comparable to the layouts of the C++ and Neko packages.

C++

C++ has its specific package named cpp. It allows one to access the filesystem, read, and write from the console and open sockets.

Its layout is comparable to the layouts of the neko and php packages.

Conditional compilation

haXe allows you to do conditional compilation. This means that some parts of the code can be compiled and others can be ignored depending on the flags you give to the compiler and depending on the platform you're targeting.

Conditional compilation instructions always begin with the #if instruction./p>

Conditional compilation depending on flags

You can define a flag on the command line by using the –D switch. So, to define the myway flag, you would use the following: -D myway.

Then, you use it in the following way:

#if myway
//Code here compiled only if myway is defined
#else
//Code here compiled in the other case
#end

There is also a not operator:

#if !myway
//Code here compiled only myway is not defined
#end

Conditional compilation depending on the target

Conditional compilation depending on the target basically works in the same way, except that the name of the target you are compiling to automatically gets set. So, you will have something like the following:

#if cpp
//C++ code
#elseif neko
//Neko code
#elseif php
//PHP code
#else
//Code for other targets
#end

By the way, did you notice the presence of #elseif in the code? It could prove to be pretty useful if you need to create a different implementation of the same feature depending on the platform.

The remap switch

The compiler has a remap switch that allows us to rename a package, for example, imagine the following code:

//Some previous code
myPack.Lib.println("Hello remapping!");
//Some code following

This would obviously not work because the Lib class is not in the myPack package. However, if you compile with the switch --remap myPack:neko when compiling to Neko, this will work. The reason is simple: anywhere you have used the package name myPack; the compiler will replace it with Neko, therefore using the correct package.

This can be used to make some cross-platform code.

On the other hand, it has a big draw-back: when you remap a package, all of the modules it contains are remapped. This actually means that you won't be able to use something that is platform-specific inside this package.

Coding cross-platform using imports

We've already seen the import keyword that allows us to import all the classes of a module. This keyword can be used to write some cross-platform code. Indeed, it is the preferred way to do so, when working with packages that have the same layout. It has the advantage of being easily readable and does not mean adding three lines every time you want to access a platform-specific package.

So, here's a simple example: imagine you want to target Neko and PHP and want to use the println method available in neko.Lib.println and php.Lib.println (these methods allow you to write a line of text on the stdout). As you can see here, regarding what we are going to use, the neko and php package have the same layout (indeed, they have the same layout for quite a lot of their classes). So, we can write the following code:

#if php
import php.Lib;
#elseif neko
import neko.Lib;
#end

class Main

{
public static function main()
{
Lib.println("Hello World");
}
}

In this example, we can use the php.Lib or neko.Lib class by simply using its name (Lib), as the good one is imported depending on the current compilation target. If we didn't do that, the code would have looked like the following:

class Main
{
public static function main()
{
#if php
php.Lib.println("Hello World");
#elseif neko
neko.Lib.println("Hello World");
#endif
}
}

As you can see, this is pretty difficult to read although here we have almost no logic and only one instruction.

Time for action – Welcoming the user on Neko & PHP

We are going to write a simple program that asks the user their name and prints a little welcome message along with the date. This program should work with the same codebase on Neko and PHP.

The following are the steps you should follow to write this program:

  1. Identify what classes you will need to print text and read from the keyboard.
  2. Identify what classes are in a platform-specific package.
  3. Add imports for those classes.
  4. Run and test.
  5. You should get the following output:

The following is the final code you should have produced:

#if neko
import neko.Lib;
import neko.io.File;
#elseif php
import php.Lib;
import php.io.File;
#end

class Main
{

static function main()
{
//Print a prompt
Lib.print("Please enter your name: ");
//Read the answer
var name = File.stdin().readLine();
//Print the welcome message
Lib.println("Welcome " + name);
//Store the date
var date = Date.now();
//Concatenate a message with the two elements from the date
object
Lib.println("Time is: " + Std.string(date.getHours()) + ":" +
Std.string(date.getMinutes()));
}
}

What just happened?

I'm pretty sure that with the commented code, you do understand what's going on, but let's clarify each step we have asked you to think about:

  • Needed classes
    • To write to the console, we will use the neko.Lib class
    • To read from the keyboard, we will use the neko.io.File class
    • To know the date and time, we will use the Date class
  • Identifying platform-specific classes
    • The neko.Lib and neko.io.File classes obviously are platform-specific; their corresponding classes for PHP respectively are php.Lib and php. io.File
    • The Date class is platform-independent and therefore, we won't need to do anything special with it
  • Imports
    We just have a section to do the good important according to the target platform:

    #if neko
    import neko.Lib;
    import neko.io.File;
    #elseif php
    import php.Lib;
    import php.io.File;
    #end

    This one is pretty short, but sometimes you may have many of those classes.

Have a go hero – Handle XML

Now, let's sum-up all we've done and more particularly the XML part.

Imagine you want to create a tool, which allows you to create pages. Each page will be composed of one or several layers and each layer will have an ID.

You want to save and load a page in an XML file that will look like the following:

<page name="My First Page">
<layer id="layer1">
[content]
</layer>
<layer id="layer2">
[content]
</layer>
</page>

For the sake of this example, we will not generate the content parts.

Time for action – Reading from the XML file

We are going to create a function to read from an XML file. So, let's proceed step by step.

  1. First, create the Layer class as follows:

    class Layer
    {
    public var id : String;

    public function new()
    {}
    }

  2. Now create the Page class as follows:

    class Page
    {
    public var name : String;
    public var layers : List<Layer>;

    public function new()
    {
    }
    }

  3. Now, let's create a function to create a page from an XML file. Add the following function to the Page class:

    public static function fromXMLFile(path : String) : Page
    {
    var nPage = new Page();
    var xmlDoc = Xml.parse(neko.io.File.read(path, false).
    readAll().toString());
    nPage.name = xmlDoc.firstElement().get("name");

    return nPage;
    }

  4. As you can see, it is not yet complete. We have to parse a layer from the XML file, so let's do it now. Add the following function in the Layer class:

    public static function fromXMLNode(node : Xml)
    {
    var nLayer : Layer;
    nLayer = new Layer();
    nLayer.id = node.get("id");

    return nLayer;
    }

  5. Now, in the fromXMLFile function, let's add some code to iterate over the nodes named layers, and parse them using the fromXMLNode function:

    public static function fromXMLFile(path : String) : Page
    {
    var nPage = new Page();
    var xmlDoc = Xml.parse(neko.io.File.read
    (path, false).readAll().toString());
    nPage.name = xmlDoc.firstElement().get("name");
    for(l in xmlDoc.firstElement().elementsNamed("layer"))
    {
    nPage.layers.add(Layer.fromXMLNode(l));
    }
    return nPage;
    }

What just happened?

As you see, we are simply expecting our document to respect the structure that we have defined; therefore, it was pretty easy to parse our XML file.

Time for action – Writing to an XML file

  1. We want to be able to write an XML file. To write the XML file, we will follow the same idea. Add the following function in the Layer class:

    public function toXMLNode() : Xml
    {
    var nXml = Xml.createElement("layer");
    nXml.set("id", this.id);
    return nXml;
    }

  2. Now, add the following code in the Page class:

    public function toXMLFile(path : String)
    {
    var xmlDoc = Xml.createDocument();
    var pageNode = Xml.createElement("page");
    pageNode.set("name", this.name);
    xmlDoc.addChild(pageNode);

    for(l in layers)
    {
    pageNode.addChild(l.toXMLNode());
    }

    neko.io.File.write(path, false).writeString(xmlDoc.toString());
    }

You can now save a page and all its layers by calling toXMLFile.

What just happened?

We have created a simple of the function in order to be able to save our page as an XML file by calling toXMLFile.

Testing our sample

The following is a sample main function if you want to try this code. It may also help you to understand how to use it:

public static function main(): Void
{
trace('Hello World');
trace(Page.fromXMLFile("/Users/benjamin/example.xml"));
trace(Page.fromXMLFile("/Users/benjamin/example.xml"));

var p = new Page();
p.name = "Page de test";

var l1 = new Layer();
l1.id="l1";
var l2 = new Layer();
l2.id="l2";

p.layers.add(l1);
p.layers.add(l2);

p.toXMLFile("/Users/benjamin/page1.xml");
}

Making it cross-platform

This code works on the Neko target. Using what we've learned about using imports to make code cross-platform, you can make this code work on PHP very easily. That's something you should try on your own to learn!

Summary

In this article, we've learned about how to do some cross-platform programming with haXe.

Specifically, we covered what is in the Standard Library, how to use conditional compilation and other ways to write cross-platform code, and how to handle XML files.


Further resources on this subject:


haXe 2 Beginner's Guide Develop exciting applications with this multi-platform programming language
Published: July 2011
eBook Price: £16.99
Book Price: £27.99
See more
Select your format and quantity:

About the Author :


Benjamin Dasnois

Benjamin Dasnois has always been fascinated by open source software and as such, has been giving courses about Linux in an IT school in France. In the meantime, Benjamin has been following and working with haXe since its beginning and uses it in his professional life. He now works on an open source project, started the integration of haXe into it, and made it work with the technologies that were already in use.

Books From Packt

Agile Web Application Development with Yii1.1 and PHP5
Agile Web Application Development with Yii1.1 and PHP5

Python 3 Web Development Beginner's Guide
Python 3 Web Development Beginner's Guide

Grok 1.0 Web Development
Grok 1.0 Web Development

Flash Development for Android Cookbook
Flash Development for Android Cookbook

Mastering phpMyAdmin 3.1 for Effective MySQL Management
Mastering phpMyAdmin 3.1 for Effective MySQL Management

jQuery UI 1.7: The User Interface Library for jQuery
jQuery UI 1.7: The User Interface Library for jQuery

Learning Ext JS 3.2
Learning Ext JS 3.2

MODx Web Development - Second Edition
MODx Web Development - Second Edition

Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software