4

I am trying to understand the advantages and disadvantages of a Foreign Function Interface (FFI) (in which the high-level languagd can call most C functions directly and can manipulate C data structures) vs. a C/C++/etc. API, in which the native function is passed runtime-specific data structures and must use runtime-provided APIs to manipulate them.

To me, the first of only two advantages of the latter approach seems to be in languages that do not support proper encapsulation, in which case a native extension can encapsulate unsafe code in a way that would otherwise be difficult. But Python has ctypes and CFFI, Ruby has FFI, and LuaJIT has its FFI (on which CFFI is based). This only really seems valid for VMs that must securely execute untrusted code (Javascript, Lua, Dart) or that are purely interpreted.

Java only has JNI built-in, but that is changing. .NET family languages can take advantage of a superb FFI (P/Invoke).

In particular, interfacing C with OCaml and HHVM requires writing C and C++ code respectively, even though both are compiled. Why?

Edit: My question is "What advantages and disadvantages does an FFI have compared to a C API?" One or the other is necessary; my question is about their respective advantages and disadvantages.

3 Answers 3

3

Take a complex C library which was written ages ago, not written specifically to be used by Lua or Python or whatever, with an enormous number of functions.

You can:

  1. Try to write a wrapper dylib over it in C which calls the other C library and focuses on all the binding work necessary to make that a proper and idiomatic Python or Lua module, e.g.
  2. Just be able to use the library right away in Lua or Python or any other language through the FFI and test it, play with it, experiment, start wrapping it, etc.

The second option is pretty appealing to me. Also you don't have to use the FFI directly. You can still, for example, use LuaJIT's FFI to directly call C functions behind a nice Lua table which conforms to Lua idioms (1-based indexing, e.g.). I actually find that takes far less time wrapping FFI code to be idiomatic than exporting a proper Lua module which has to translate everything to tables and numbers and so on using Lua's C API to build a conforming Lua module.

The FFI lets you do more of your work directly in the language you're targeting. The alternative will inevitably require you to spend a whole lot more time in C.

Take OpenGL as an example and let's just pretend there were no OGL Lua modules already available. In that case, with LuaJIT's FFI, you could be drawing shapes to a viewport in no time by just directly calling OpenGL functions through the FFI. Soon you might start wrapping it into Lua tables with nice functions suitable for your application. Otherwise you might have to spend an entire weekend or longer binding and translating a boatload of OpenGL functions, constants, etc. in C to build a miniature Lua module with only a small subset of OGL functionality and then import that just to get a triangle on a screen, only to then have to bind more functions and so on.

It is far, far more time-consuming to get started trying to export a conforming module to your language as opposed to just being able to call C APIs directly from it right off the bat. That applies even if you use things to help with the binding like Boost.Python or LuaBind. It's an extremely time-consuming process to do properly, and FFI bypasses all of that.

I actually think FFI is the way to go and would prefer that over exporting modules specific to a language. As an example, some users wrapped my SDK to allow them to call its C functions from C#. I never had to write a C# or .NET module to let them do that. They just directly call my C functions from C#'s FFI which lets them import C functions from dylibs and call them right away.

2

FFIs are based on C. They look like C. And therefore, FFI-based interfaces are limited to C.

I'll use Lua as an example, since that's what I'm most familiar with.

If you were using the Lua C API, you could expose a system to Lua that looks and behaves like Lua expects it to. It could expose objects that Lua code could store and call like other Lua stuff: variable_name.member_func(...) and so forth. You could even implement function overloading.

That's not possible directly through FFI. Oh sure, LuaJIT's FFI does allow you to apply metatables to types. But that happens after the FFI part when you load the data structures. It requires manual work in the script. Whereas doing it in the C API requires zero script work; a proper API is built for you.

And it's important to examine what a "proper API" would be. Like most scripting languages, Lua allows lots of things C does not. With the Lua C API, you can expose to Lua C functions that:

  • Take arbitrary numbers of parameters.
  • Return arbitrary numbers of values.
  • Deal directly with Lua parameter values (userdata, tables, functions, etc).
  • Deliver errors through the standard Lua error mechanism.
  • Return data that is compatible with Lua's generic for statement.

FFI-built interfaces can't do any of that directly. Again, you can always write wrappers and so forth for them, but then you're writing a wrapper.

And just for added interest, LuaJIT breaks Lua's standing conventions about array indexing. In regular Lua, arrays are always 1-based. But with LuaJIT's FFI, arrays you allocate there are zero-based like C. Whatever you may want to say about which convention is better, having both conventions simultaneously is far worse than either.

FFIs have a tendency towards that kind of interface. Because they're biased towards C, they will sometimes conflict with the conventions of the scripting language.

Then there's the issue of safety. With an FFI, particularly one like LuaJIT, you're basically allowing C code to be written in Lua. So instead of isolating the unsafe elements of your program in some C libraries, you're allowing the unsafe aspects of C to infiltrate your scripting environment. And rather than getting Lua errors which can be handled and recovered from, you usually get hard crashes.

Ones that are going to be much harder to debug since most scripting environments don't have debuggers as good as C/C++ ones.

Depending on your needs, these safety issues may be completely unacceptable. I wouldn't want people to be able to crash Wikipedia's Lua script processor on the server, just because someone accidentally did a bad FFI cast in a Lua script.

At the end of the day, FFI is best for interfacing with systems that were never intended to be accessed by the host language. As such, an oddball interface is better than no interface at all. You can isolate the weirdness to make it look more like the host language.

But if you're writing a system that you are by design exposing to a scripting language, you will give your script coders a much more reasonable programming experience. This doesn't mean you can't employ FFI to make some things faster (large arrays, etc). But if you can avoid it, you shouldn't just fling a raw DLL interface at someone if you can help it.

1
  • 1
    Also note that FFI can be MUCH faster if the scripting language is JIT-compiled.
    – Demi
    Commented Jun 16, 2016 at 3:12
1

I try to explain my experience with Java and the JNI.

With Java you have a great language to create fast well and cross platform working applications. It's quite easy to understand and it works fine. But you know, if we're trying to use something like Audio IO , than Java fails. For this case we're using the JNI to implement IO over C++. For such things in my Opinion , C++ is really great but.... for creating fast well working and cross platform working Applications C++ is not very... Handy. .. So as small conclusion:

Interfacing:

  • Gives us the opportunity to create IO applications, yeaaah :)

  • you get the option to optimize critical parts best and the rest is optimised automatic.

  • Big Minus for Java is, that too many calls from the JNI in one second can Produce a heavy overhead. I talked with someone about it and he told me, that the JNI is not working anymore well with a call every 100 microseconds.( [But normal if you're not developing time critical application it works fine...]

  • Sometimes it is unhandy and or it is untransparent; There can be problems

-The use of a language like java itself makes untransparent code. You do not know what the JVM is doing.

Non interfacing:

+The code is complete transparent and you can optimize how much you want to.

  • Eg. Graphical Applications are unhandy to program in C++ either you have to invent much new which already exists(Much Work!)

If there are any further questions please comment I will improve this question [Hope it helps :)]!

4
  • That doesn't really help. You could do the same if Java had an FFI (which it will -- and this is planned to replace JNI for most uses). I am asking what JNI can do that an FFI can't.
    – Demi
    Commented May 13, 2016 at 5:02
  • Ph sorry than I miss understanded Commented May 13, 2016 at 5:07
  • What do you mean with C API? Commented May 13, 2016 at 13:50
  • With JNI, one writes code in a native language (such as C or C++) and the VM provides an API for use by such code. With an FFI, one writes code in the higher-level language and the VM translates the arguments to match what the C function expects.
    – Demi
    Commented May 13, 2016 at 18:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.