GithubHelp home page GithubHelp logo

hxcpp-guide's People

Contributors

jcward avatar ruby0x1 avatar thomasuster avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hxcpp-guide's Issues

What about native extensions?

I've seen google group theme about native extensions formalization over different haxe frameworks or something like that... I just can't find it right now :(

Conditionally use a compiler flag for only a particular library?

I am having an issue with using -std=c++11 with libraries that have .c files. But it is required by another library.

 - /usr/local/lib/haxe/lib/linc_nanovg/git/linc/linc_nanovg.cpp
 - /usr/local/lib/haxe/lib/linc_nanovg/git/lib/nanovg/src/nanovg.c
Error: error: invalid argument '-std=c++11' not allowed with 'C'

CFFI / CFFI Prime : everything I know

This is a scratch pad issue. Just a brain dump of everything I know. Organization comes later.

There are three basic methods for getting Haxe to directly interoperate with C++.

  1. CFFI (legacy)
  2. CFFI Prime
  3. C++ Externs

See this answer for C++ externs:
https://stackoverflow.com/questions/34168457/creating-a-haxe-hxcpp-wrapper-for-a-c-library

and LINC is a good example:
http://snowkit.github.io/linc/#native-extern-tips

C++ Externs are probably the easiest and simplest thing to set up, the disadvantage is you are limited to C++ targets. If that's not a problem for you, you probably want C++ Externs. If you need other targets like neko and NodeJS, you might consider CFFI / CFFI Prime.


CFFI (legacy) and CFFI Prime are ways to get C++ (and neko, and perhaps other targets as well) to talk directly to the Haxe layer of your code.

Both are used in the SteamWrap library, if you want a practical example:
https://github.com/larsiusprime/steamwrap

Today we'll discuss CFFI and CFFI PRIME

Both of these are C Foreign Function Interfaces, that allow the C++ and Haxe sides to communicate. The old version of CFFI (referred to as just plain "CFFI" from hereon) differs from CFFI PRIME in that it is slower and relies on boxed dynamic typing. CFFI PRIME's advantage is that it does not rely on boxing values -- you can take the same value type and pass it transparently between C++ and Haxe; this is not only faster but avoids extra allocations which can cause GC activity. Also, for non C++ targets such as neko, CFFI PRIME is backwards-compatible and transparently degrades into the legacy CFFI behavior, so for a large subset of cases CFFI PRIME is probably better. In practice there are some cases where you still might want to use regular CFFI, but otherwise CFFI PRIME is recommended wherever it will work.

CFFI PRIME builds on top of the concepts of CFFI, so we'll discuss CFFI first.
#1. CFFI

So say you have this Haxe class:

Class Foo
{
    public var ForeignFunction:Dynamic;
    public function new() {}
}

Right now, calling Foo.ForeignFunction() will do nothing as it's not defined (in fact it's null so it will crash). Instead, we would like to make it connect to a C++ class.

So over in C++ land we create another file:

#include <hx/CFFI.h>

class Foreign
{
    value CPP_ForeignFunction(value haxeVal)
    {
        int intVal = val_int(haxeVal);
        printf("CPP: intVal = %d\n",intVal);
        intVal++;
        value returnVal = alloc_int(intVal);
        return returnVal;
    }
    DEFINE_PRIM(CPP_ForeignFunction, 1);
}

This function will receive an int, print out its value, add one to it, and return it.

Let me explain the code.

First, we're including the HXCPP header file for the CFFI class.
Next, we're defining our C++ function for Haxe to talk to.

value CPP_ForeignFunction(value haxeVal)

You'll notice that the return type here is "value", and it also takes 1 parameter of type "value", and "value" is not a native C++ type. This is a special CFFI type that can (basically) hold anything, it's like a Haxe Dynamic. Think of this as a box that has to be unwrapped before you can use it in C++, and has to have something put into it before you can hand it back to Haxe.

CFFI comes with several functions for boxing and unboxing "value" types:

PLEASE CORRECT IF WRONG:
val_int
val_string
val_object
val_bool
val_float
(etc)

There are also several utility functions for checking if a value type is one of these types underneath, since calling val_int() on an underlying string could cause issues.

PLEASE CORRECT IF WRONG:
val_is_int
val_is_string
val_is_object
val_is_bool
val_is_float
(etc)

So when you want to get a value out of your boxed types from haxe, you do:

int intVal = val_int(haxeVal);

And when you want to pass something back, you do call one of these functions to box it back up:

PLEASE CORRECT IF WRONG:
alloc_int
alloc_bool
alloc_string
alloc_object
alloc_float
(etc)

    value returnVal = alloc_int(intVal);
    return returnVal;

I split that into two lines so you can clearly see that the type of the returnVal here is "value". Even if your original C++ type was an int, or a string, or a bool, it all goes back to Haxe as a "value."

Finally, look at this line at the bottom of the C++ function:

DEFINE_PRIM(CPP_ForeignFunction, 1);

That defines a "primitive" for Haxe to be able to recognize. You pass it the function name (NOTE: not a string representation of the identifier, just the identifier itself) and how many parameters it should receive from Haxe. In this case, it will receive one parameter.

Okay, great, we set up this function that's totally ready to talk to Haxe, but we still haven't bridged the gap.

Let's go back to our Haxe class:

Class Foo
{
    public var ForeignFunction:Dynamic;
    public function new() {}
}

We add a few things:

package foo;
import cpp.Lib;

Class Foo
{
    public var ForeignFunction:Dynamic;
    public function new()
    {
        ForeignFunction = cpp.Lib.load("foo", "CPP_ForeignFunction",1);
    }
}

We've done three things here --

  1. Add a package to our class (NOTE: someone tell me why/if this is necessary)
  2. Import cpp.Lib from the Haxe standard library
  3. In the constructor, load the C++ function into our "ForeignFunction" member variable

This is the meat of it:

        ForeignFunction = cpp.Lib.load("foo", "CPP_ForeignFunction",1);

The cpp.Lib.load command takes three things --

  1. Your class' package name
  2. The name of your function on the C++ side (is this right?)
  3. The number of parameters you are passing to C++ (is this right?)

And that's it! You're done. Now you can do this:

var myFoo = new Foo();
var result = myFoo.ForeignFunction(1);     //outputs "CPP: Intval = 1";
trace(result);                             //outputs "2";

THINGS THAT SHOULD BE CONFIRMED/ADDED/BLAH BLAH:

  • I haven't actually tested this example
  • Double check the boiler plate
  • Haven't touched on the build process at all
  • Anything special needed for Neko?
  • Does this work with things like C# and JS and Java?
  • Need to enumerate all the val_int, val_string, alloc_int, alloc_string, val_is_int, val_is_string, etc.
  • Explain any special cases when dealing with "objects" on the c++ side
  • Explain how to work with fields on "objects" on the c++ side
  • GC issues, dangerous behavior, etc

RANDOM NOTE: Strings

Strings are something that are important not to get confused about. There are at least three types of strings:

  • C++ style strings (std::string)
  • C style strings (const char *)
  • Haxe style strings (String in Haxe, HxString in C++)

I've noticed that strings are often passed back to Haxe from CFFI in this manner:

alloc_string("STRING CONSTANT");
const char * myCString = getCStringSomehow();
alloc_string(myCString);
std::ostringstream myStream;
myStream << "DO " << "SOME " << "BUSINESS " << "LOGIC " << someVal << otherVal;
return alloc_string(myStream.str().c_str());

So it seems that with CFFI at least you're passing back const char * rather than std::string or std::ostringstream
#2. CFFI PRIME

CFFI PRIME works very much like CFFI except that it has performance benefits and can communicate more directly because it doesn't have to box its values. In practice this means there are some trickier edge cases -- I've generally found CFFI to be a bit more flexible. The "E" in "PRIME" doesn't stand for anything, as far as I can tell it's a pun on the "DEFINE_PRIM" call, indicating that CFFI PRIME is better ;P

So back to our previous example, we had this in Haxe:

package foo;
import cpp.Lib;

Class Foo
{
    public var ForeignFunction:Dynamic;
    public function new()
    {
        ForeignFunction = cpp.Lib.load("foo", "CPP_ForeignFunction",1);
    }
}

and this in C++:

#include <hx/CFFI.h>

class Foreign
{
    value CPP_ForeignFunction(value haxeVal)
    {
        int intVal = val_int(haxeVal);
        printf("CPP: intVal = %d\n",intVal);
        intVal++;
        value returnVal = alloc_int(intVal);
        return returnVal;
    }
    DEFINE_PRIM(CPP_ForeignFunction, 1);
}

Let's start by changing some stuff in Haxe:

package foo;
import foo.helpers.Loader;

Class Foo
{
    public var ForeignFunction = Loader.load("CPP_ForeignFunction","ii");
    public function new(){}
}

This is a lot simpler to write. Instead of typing ForeignFunction as Dynamic, and loading it up at runtime, here we load it up at compile time, and I believe it will wind up as strongly typed, to boot.

Similarly to CFFI, you give the name of the C++ side function you want, but instead of providing the number of parameters, you provide the number and the type encoded in a special string:

public var ForeignFunction = Loader.load("CPP_ForeignFunction","ii");

So "ii" here means -- takes an int, returns an int. CFFI doesn't specify anything about the return type, but CFFI Prime does, and requires explicit type information.

You build your type string like this:

  • integer: "i"
  • float: "f"
  • c-string: "c"
  • object: "o"
  • Haxe string: "s"
  • void: "v"

So if you have a function that takes 3 integers and returns 1 float, it would be "iiif", or that takes a float, an integer, a string, and returns an object it would be "fico" (assuming you are passing the string to C++ as a const char *).

(this is apparently a "java style signature or something?" someone else can fill this in)

Of course, this requires the help of a macro class ("Loader"), which we'll define here:

package foo.helpers;

#if macro
import haxe.macro.Expr;
#end

class Loader
{
   #if cpp
   public static function __init__()
   {
      cpp.Lib.pushDllSearchPath( "" + cpp.Lib.getBinDirectory() );
      cpp.Lib.pushDllSearchPath( "ndll/" + cpp.Lib.getBinDirectory() );
      cpp.Lib.pushDllSearchPath( "project/ndll/" + cpp.Lib.getBinDirectory() );
   }
   #end

   public static inline macro function load(inName2:Expr, inSig:Expr)
   {
      return macro cpp.Prime.load("foo", $inName2, $inSig, false);
   }
}

(It seems to me that this sort of macro should be standard, it seems a little cumbersome to have to provide it yourself; also I'm not sure of what the syntax is without the helper macro. Perhaps it's just hooked to deeply into the build process to be of non-framework-specific use; anyways, I'll let someone else fill this detail in)

Now let's go to the C++ side and make some changes:

#include <hx/CFFIPrime.h>

class Foreign
{
    int CPP_ForeignFunction(int haxeVal)
    {
        printf("CPP: intVal = %d\n",intVal);
        intVal++;
        return intVal;
    }
    DEFINE_PRIME1(CPP_ForeignFunction);
}

First of all, we changed the include to CFFIPrime.h.
Second, we removed the "value" type and exchanged for the strong types -- in this case int for both the return and argument value. This must match the signature supplied on the Haxe side or you will get a compile-time error (CFFI did not enforce this, so you could send an int, and then try to read it as a string, and possibly cause run time errors).

Notice we do not have to unbox anything. We can just use the value directly. Also, we don't need to box anything up to return it -- we just pass it right back to Haxe.

The "DEFINE_PRIM" has now become "DEFINE_PRIME1" -- instead of putting the number of parameters received as an argument to "DEFINE_PRIME()" you instead call a variant of "DEFINE_PRIME" that includes the number of parameters, in this case "DEFINE_PRIME1()".

NOTE: I believe if you have a CFFI PRIME function that returns void you need to add a "v" to the end of "DEFINE_PRIME" so, "DEFINE_PRIME1v" for "take one parameter, return void" -- this needs checking.

Now we go back to Haxe:

var myFoo = new Foo();
var result = myFoo.ForeignFunction.call(1);     //outputs "CPP: Intval = 1";
trace(result);                                 //outputs "2";

The only difference in this case is you need to add ".call()" to invoke your CFFI PRIME function.
(I think there's a way to avoid this with an abstract or a macro but I don't know how yet.)

THINGS I NOTICED BUT DON'T TOTALLY UNDERSTAND ABOUT CFFI PRIME YET:

  • CFFI PRIME calls that take no parameters, ie, void foo() and int foo()
    • Haven't been able to get these to work yet
    • Not sure of the exact signature -- "v" for void foo()? "i" for int foo()? Or is it "vv" and "iv"
  • Dealing with CFFI PRIME strings, particularly HxString's
    • passing back HxString("OK") from CFFI PRIME seems to work, but Hugh says it might have a GC problem
    • passing back HxString(constCharLocalVariable) from CFFI PRIME results in garbage on the Haxe side of things because the local variable gets killed before Haxe can see it
    • passing back a const char * variable to Haxe seems to work, but then it's a special cpp. package string type and not a real haxe String, so you can't do things like .split() to it and I don't know how to convert it properly
    • all in all I'm entirely unsure of the GC implications of unwise string usage with CFFI Prime
  • The build process. Does it have to make an .ndll, can it do something else? How can you set up a minimal library that will work with any framework? Does include.xml work with any haxelib, or is it just an OpenFL/NME thing? I'm not quite sure yet. In any case I just edited the steamwrap library in place, which uses the OpenFL paradigm.

Tear down call for hxcpp?

I would like to know corresponding tear-down calls for the following setup calls?

extern "C" void hxcpp_set_top_of_stack();
extern "C" const char *hxRunLibrary();

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.