https://github.com/cedlemo/ocaml_ffi_evas
Simple example on how to use the ocaml ffi with evas from efl
https://github.com/cedlemo/ocaml_ffi_evas
Last synced: about 1 year ago
JSON representation
Simple example on how to use the ocaml ffi with evas from efl
- Host: GitHub
- URL: https://github.com/cedlemo/ocaml_ffi_evas
- Owner: cedlemo
- Created: 2016-07-18T19:24:31.000Z (almost 10 years ago)
- Default Branch: master
- Last Pushed: 2016-08-24T20:24:56.000Z (almost 10 years ago)
- Last Synced: 2025-02-06T06:24:14.808Z (over 1 year ago)
- Language: OCaml
- Size: 497 KB
- Stars: 4
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Using FFI with OCaml
In the [Chap 19 Foreign Function Interface](https://realworldocaml.org/v1/en/html/foreign-function-interface.html) of
the book Real World OCaml, the authors use the library NCurse as example.
Here I use Ecore / Evas from the [Enlightenment Libraries](https://www.enlightenment.org/) know as E.F.L.
## Table of content
* [Presentation of the C library](#presentation-of-the-c-library)
* [The C functions to use](#the-c-functions-to-use)
* [The code to reproduce](#the-code-to-reproduce)
* [Create the OCaml bindings](#create-the-ocaml-bindings)
* [The Ecore_evas module: simple implementation](#the-ecore-evas-module-:-simple-implementation)
* [The main OCaml file](#the-main-ocaml-file)
* [The Ecore_evas module: a more subtle implementation](#the-ecore_evas-module:-a-more-subtle-implementation)
* [The string_opt type](#the-string_opt-type)
* [Create a view for a bool type](#create-a-view-for-a-bool-type)
* [Manage C function pointers](#manage-c-functions-pointers)
* [The Ecore_evas module: C stubs generation and static bindings](#the-ecore_evas-module:-c-stubs-generation-and-static-bindings)
* [The bindings functor](#the-bindings-functor)
* [The stubs generator](#the-stubs-generator)
* [Compiling the bindings](#compiling-the-bindings)
* [Using the static bindings](#using-the-static-bindings)
* [Build static bindings with myocamlbuild.ml](#build-static-bindings-with-myocamlbuild.ml)
## Presentation of the C library
### The C functions to use
* void ecore_main_loop_quit(void)
* void ecore_main_loop_begin(void)
* int ecore_evas_init(void)
* int ecore_evas_shutdown(void)
* Ecore_Evas * ecore_evas_new(const char *, int, int, int, int, const char *)
* ecore_evas_title_set(const Ecore_Evas *, const char *);
* ecore_evas_alpah_set(const Ecore_Evas *, Eina_Bool)
* ecore_evas_show(ee);
* ecore_evas_free(ee);
### The code to reproduce
It is just a little transparent window.
```c
/* Compile with:
* Normal shell (bash, sh, zsh):
* gcc $(pkg-config --libs --cflags ecore-evas ecore) -o evas_ecore_window_c evas_ecore_window.c
* Fish shell:
* eval gcc (pkg-config --libs --cflags ecore-evas ecore) -o evas_ecore_window_c evas_ecore_window.c
*/
#include
#include
#include
#include
int main(int argc, char **argv)
{
if(!ecore_evas_init())
return EXIT_FAILURE;
Ecore_Evas * window;
Evas *canevas;
window = ecore_evas_new(NULL, 10, 10, 320, 320, NULL);
if(!window)
return EXIT_FAILURE;
ecore_evas_alpha_set(window, EINA_TRUE);
ecore_evas_show(window);
ecore_main_loop_begin();
ecore_evas_free(window);
ecore_evas_shutdown();
return EXIT_SUCCESS;
}
```
## Create the OCaml bindings
### The Ecore_evas module : simple implementation
* ecore_evas.ml
```ocaml
open Ctypes
open Foreign
let ecore_main_loop_begin =
foreign "ecore_main_loop_begin" (void @-> returning void)
let ecore_main_loop_quit =
foreign "ecore_main_loop_quit" (void @-> returning void)
let ecore_evas_init =
foreign "ecore_evas_init" (void @-> returning int)
let ecore_evas_shutdown =
foreign "ecore_evas_shutdown" (void @-> returning int)
type ecore_evas = unit ptr
let ecore_evas : ecore_evas typ = ptr void
let ecore_evas_new =
foreign "ecore_evas_new" (ptr char @-> int @-> int @-> int @-> int @-> ptr char @-> returning ecore_evas)
let ecore_evas_title_set =
foreign "ecore_evas_title_set" (ecore_evas @-> string @-> returning void)
let ecore_evas_show =
foreign "ecore_evas_show" (ecore_evas @-> returning void)
let ecore_evas_free =
foreign "ecore_evas_free" (ecore_evas @-> returning void)
let ecore_evas_alpha_set =
foreign "ecore_evas_alpha_set" (ecore_evas @-> int @-> returning void)
```
We generate the module interface file from the module.
```bash
corebuild -pkg ctypes.foreign ecore_evas.inferred.mli
cp _build/ecore_evas.inferred.mli ./
```
### The main OCaml file
```ocaml
open Ecore_evas
open Ctypes
let initialize_subsystem () =
if (ecore_evas_init () != 0) then
print_endline "Ecore Evas initialized"
else
print_endline "Unable to initialize Ecore Evas system"
let () =
initialize_subsystem ();
print_endline "Creating ee";
let ee = ecore_evas_new (from_voidp char null) 50 50 300 300 (from_voidp char null) in
ecore_evas_title_set ee "This is a test";
ecore_evas_alpha_set ee 1;
print_endline "Showing ee";
ecore_evas_show ee;
ecore_main_loop_begin ();
ecore_evas_free ee;
ignore(ecore_evas_shutdown ())
```
This file can be build and run with:
```bash
corebuild -pkg ctypes.foreign -lflags -cclib,-lecore_evas -lflags -cclib,-lecore ecore_evas_window.native
./ecore_evas_window.native
```
### The Ecore_evas module: a more subtle implementation:
#### The string_opt type
Previously when I needed to pass a C `char *` I used in the OCaml bindings both
`char ptr` and `string`. That is because I wanted to use Null pointers in the
function `ecore_evas_new`. In Ctypes there is the type `string_opt` which can
support a pointer of char and a Null pointer.
* in ecore_evas.ml
```
let ecore_evas_new =
foreign "ecore_evas_new" (string_opt @-> int @-> int @-> int @-> int @-> string_opt @-> returning ecore_evas)
let ecore_evas_title_set =
foreign "ecore_evas_title_set" (ecore_evas @-> string_opt @-> returning void)
```
* in ecore_evas_window.ml
```
let ee = ecore_evas_new None 50 50 300 300 None in
ecore_evas_title_set ee (Some "This is a test");
```
#### create a view for a bool type.
In the EFL, there is are boolean value `EINA_TRUE` which is an integer equal to
one and `EINA_FALSE` whose value is 0. The idea is to bind those values to the
`true` and `false` boolean values of OCaml.
For this I will use a "view". The `Ctypes.view` function create a new C type
descriptions with the instructions to be used when OCaml read or write those
kind of C values.
* example with the Ctypes.string :
```
let string =
view
(char ptr)
~read:string_of_char_ptr
~write:char_ptr_of_string
```
* boolean value which can be 0 (false) or 1 (true):
```
let bool =
view
int
~read:((<>)0)
~write:(fun b -> compare b false)
```
* in the ecore_evas.ml
```
let eina_bool =
view ~read:((<>) 0) ~write:(fun b -> compare b false) int
let ecore_evas_init =
foreign "ecore_evas_init" (void @-> returning eina_bool)
let ecore_evas_shutdown =
foreign "ecore_evas_shutdown" (void @-> returning eina_bool)
let ecore_evas_alpha_set =
foreign "ecore_evas_alpha_set" (ecore_evas @-> eina_bool @-> returning void)
```
* Usage in the ecore_evas_window.ml:
```
ecore_evas_alpha_set ee true;
```
It exits an internal bool representation :
* https://github.com/ocamllabs/ocaml-ctypes/issues/24
* https://github.com/ocamllabs/ocaml-ctypes/commit/d662070db22b99c74879c01da8db54bedc270e8b
#### Manage C function pointers
As an example, I will implement with `CTypes` this function:
```c
EAPI void ecore_evas_callback_delete_request_set(Ecore_Evas *ee,
Ecore_Evas_Event_Cb func)
```
It sets a callback for `Ecore_Evas` delete request events. Basicly when a user
hit the close button in the title bar of a window, it generates this kind of
event.
* Parameters
* ee The Ecore_Evas to set callbacks on
* func The function to call
In our module, we first implement the callback as a new type:
```ocaml
let ecore_evas_event_cb = ecore_evas @-> returning void
```
It means that `ecore_evas_event_cb` is an OCaml function that takes one
parameters and that returns nothing.
Then we can use this type in the foreign description:
```ocaml
let ecore_evas_callback_delete_request_set =
foreign "ecore_evas_callback_delete_request_set" (ecore_evas @-> funptr ecore_evas_event_cb @-> returning void)
```
Here I define an OCaml function `ecore_evas_callback_delete_request_set` that
needs two parameters , one of the type `ecore_evas` and one OCaml function that will be
transformed in a C function pointer thanks to funptr.
In the following OCaml code, I use this callback:
```ocaml
let delete_event_cb ee =
print_endline "Bye Bye";
ecore_main_loop_quit ()
```
In order to print a message and exit the main loop when a delete event appears.
Here is the full working code:
```ocaml
open Ecore_evas
(* compile with:
* corebuild -pkg ctypes.foreign -lflags -cclib,-lecore_evas -lflags -cclib,-lecore ecore_evas_window.native
*)
open Ctypes
let initialize_subsystem () =
if (ecore_evas_init () != false) then
print_endline "Ecore Evas initialized"
else
print_endline "Unable to initialize Ecore Evas system"
let delete_event_cb ee =
print_endline "Bye Bye";
ecore_main_loop_quit ()
let () =
initialize_subsystem ();
print_endline "Creating ee";
let ee = ecore_evas_new None 50 50 300 300 None in
ecore_evas_title_set ee (Some "This is a test");
ecore_evas_alpha_set ee true;
ecore_evas_callback_delete_request_set ee delete_event_cb;
print_endline "Showing ee";
ecore_evas_show ee;
ecore_main_loop_begin ();
ecore_evas_free ee;
ignore(ecore_evas_shutdown ())
```
### The Ecore_evas module: C stubs generation and static bindings
resources :
* http://simonjbeaumont.com/posts/ocaml-ctypes/
* https://github.com/simonjbeaumont/ocaml-pci
* https://github.com/simonjbeaumont/ocaml-flock
* https://ocaml.io/w/Ctypes#Static_and_dynamic_binding_strategies
The idea is to create a functor which defines the bindings, use this functor
with the `Cstubs` functions in order to generate the C code and the corresponding
ocaml code.
#### The Bindings functor
```ocaml
open Ctypes
module Bindings (F : Cstubs.FOREIGN) = struct
open F
let eina_bool =
view ~read:((<>) 0) ~write:(fun b -> compare b false) int
let ecore_main_loop_begin =
foreign "ecore_main_loop_begin" (void @-> returning void)
let ecore_main_loop_quit =
foreign "ecore_main_loop_quit" (void @-> returning void)
let ecore_evas_init =
foreign "ecore_evas_init" (void @-> returning eina_bool)
let ecore_evas_shutdown =
foreign "ecore_evas_shutdown" (void @-> returning eina_bool)
type ecore_evas = unit ptr
let ecore_evas : ecore_evas typ = ptr void
let ecore_evas_new =
foreign "ecore_evas_new" (string_opt @-> int @-> int @-> int @-> int @-> string_opt @-> returning ecore_evas)
let ecore_evas_title_set =
foreign "ecore_evas_title_set" (ecore_evas @-> string_opt @-> returning void)
let ecore_evas_show =
foreign "ecore_evas_show" (ecore_evas @-> returning void)
let ecore_evas_free =
foreign "ecore_evas_free" (ecore_evas @-> returning void)
let ecore_evas_alpha_set =
foreign "ecore_evas_alpha_set" (ecore_evas @-> eina_bool @-> returning void)
end
```
Nothing fancy here, I just put the previous code in a functor.
#### The stubs generator
Now we will use this functor in order to generate the C or OCaml code.
```ocaml
let _ =
let prefix = "ecore_evas_stubs" in
let generate_ml, generate_c = ref false, ref false in
Arg.(parse [ ("-ml", Set generate_ml, "Generate ML");
("-c", Set generate_c, "Generate C") ])
(fun _ -> failwith "unexpected anonymous argument")
"stubgen [-ml|-c]";
match !generate_ml, !generate_c with
| false, false
| true, true ->
failwith "Exactly one of -ml and -c must be specified"
| true, false ->
Cstubs.write_ml Format.std_formatter ~prefix (module Ecore_evas.Bindings)
| false, true ->
print_endline "#include \n#include ";
Cstubs.write_c Format.std_formatter ~prefix (module Ecore_evas.Bindings)
```
This program is simple, it takes one argument that can be `-ml` or `-c`.
#### Compiling the bindings
#### Using the static bindings
#### Build static bindings with myocamlbuild.ml