FFI: Documentation
This commit is contained in:
101
src/lib/ffi.pl
101
src/lib/ffi.pl
@@ -1,8 +1,65 @@
|
||||
:- module(ffi, [use_foreign_module/2, foreign_struct/2]).
|
||||
|
||||
/** Foreign Function Interface
|
||||
|
||||
This module contains predicates used to call native code (exposed by the C ABI).
|
||||
It uses [libffi](https://sourceware.org/libffi/) under the hood. The bridge is very simple
|
||||
and is very unsafe and should be used with care. FFI isn't the only way to communicate with
|
||||
the outside world in Prolog: sockets, pipes and HTTP may be good enough for your use case.
|
||||
|
||||
The main predicate is `use_foreign_module/2`. It takes a library name (which depending on the
|
||||
operating system could be a `.so`, `.dylib` or `.dll` file). and a list of functions. Each
|
||||
function is defined by its name, a list of the type of the arguments, and the return argument.
|
||||
|
||||
Types available are: `sint8`, `uint8`, `sint16`, `uint16`, `sint32`, `uint32`, `sint64`,
|
||||
`uint64`, `f32`, `f64`, `cstr`, `void`, `bool`, `ptr` and custom structs, which can be defined
|
||||
with `foreign_struct/2`.
|
||||
|
||||
After that, each function on the lists maps to a predicate created in the ffi module which
|
||||
are used to call the native code.
|
||||
The predicate takes the functor name after the function name. Then, the arguments are the input
|
||||
arguments followed by a return argument. However, functions with return type `void` or `bool`
|
||||
don't have that return argument. Predicates with `void` always succeed and `bool` predicates depend
|
||||
on the return value on the native side.
|
||||
|
||||
```
|
||||
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN, -ReturnArg). % for all return types except void and bool
|
||||
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN). % for void and bool
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
For example, let's see how to define a function from the [raylib](https://www.raylib.com/) library.
|
||||
|
||||
```
|
||||
?- use_foreign_module("./libraylib.so", ['InitWindow'([sint32, sint32, cstr], void)]).
|
||||
```
|
||||
|
||||
This creates a `'InitWindow'` predicate under the ffi module. Now, we can call it:
|
||||
|
||||
```
|
||||
?- ffi:'InitWindow'(800, 600, "Scryer Prolog + Raylib").
|
||||
```
|
||||
|
||||
And a new window should pop up!
|
||||
*/
|
||||
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(error)).
|
||||
|
||||
|
||||
%% foreign_struct(+Name, +Elements).
|
||||
%
|
||||
% Defines a new struct type with name Name, composed of the elements Elements, which is a list
|
||||
% of other types.
|
||||
%
|
||||
% The name of the types doesn't matter, but the order of Elements must match the ones in the
|
||||
% native code.
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
% ```
|
||||
% ?- foreign_struct(color, [uint8, uint8, uint8, uint8]).
|
||||
% ```
|
||||
foreign_struct(Name, Elements) :-
|
||||
'$define_foreign_struct'(Name, Elements).
|
||||
|
||||
@@ -16,10 +73,9 @@ assert_predicate(PredicateDefinition) :-
|
||||
functor(Head, Name, NumInputs),
|
||||
term_variables(Head, TermList),
|
||||
Body = (
|
||||
lists:maplist(ffi:check_input, Inputs, TermList),
|
||||
'$foreign_call'(Name, TermList, _),!
|
||||
),
|
||||
Predicate =.. [:-, Head, Body],
|
||||
Predicate = (Head:-Body),
|
||||
assertz(ffi:Predicate).
|
||||
|
||||
assert_predicate(PredicateDefinition) :-
|
||||
@@ -28,10 +84,9 @@ assert_predicate(PredicateDefinition) :-
|
||||
functor(Head, Name, NumInputs),
|
||||
term_variables(Head, TermList),
|
||||
Body = (
|
||||
lists:maplist(ffi:check_input, Inputs, TermList),
|
||||
'$foreign_call'(Name, TermList, 1),!
|
||||
),
|
||||
Predicate =.. [:-, Head, Body],
|
||||
Predicate = (Head:-Body),
|
||||
assertz(ffi:Predicate).
|
||||
|
||||
assert_predicate(PredicateDefinition) :-
|
||||
@@ -43,41 +98,7 @@ assert_predicate(PredicateDefinition) :-
|
||||
term_variables(Head, TermList),
|
||||
Body = (
|
||||
lists:append(TermListInputs, [TermListReturn], TermList),
|
||||
lists:maplist(ffi:check_input, Inputs, TermListInputs),
|
||||
'$foreign_call'(Name, TermListInputs, TermListReturn),!
|
||||
),
|
||||
Predicate =.. [:-, Head, Body],
|
||||
Predicate = (Head:-Body),
|
||||
assertz(ffi:Predicate).
|
||||
|
||||
check_input(sint8, Var) :-
|
||||
must_be(integer, Var),
|
||||
(
|
||||
(Var > -129, Var < 128) ->
|
||||
true
|
||||
; domain_error(integer_does_not_fit, Var, foreign_call/3)
|
||||
).
|
||||
check_input(sint16, Var) :-
|
||||
must_be(integer, Var),
|
||||
(
|
||||
(Var > -32769, Var < 32768) ->
|
||||
true
|
||||
; domain_error(integer_does_not_fit, Var, foreign_call/3)
|
||||
).
|
||||
check_input(sint32, Var) :-
|
||||
must_be(integer, Var),
|
||||
(
|
||||
(Var > -2147483649, Var < 2147483648) ->
|
||||
true
|
||||
; domain_error(integer_does_not_fit, Var, foreign_call/3)
|
||||
).
|
||||
check_input(sint64, Var) :-
|
||||
must_be(integer, Var).
|
||||
check_input(f32, _Var).
|
||||
check_input(f64, _Var).
|
||||
check_input(cstr, Var) :-
|
||||
must_be(chars, Var).
|
||||
check_input(_, Var).
|
||||
% must_be(list, Var).
|
||||
|
||||
% TODO: assert native predicates.
|
||||
% They MUST validate types
|
||||
|
||||
Reference in New Issue
Block a user