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
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.