{"id":15724315,"url":"https://github.com/cedlemo/ocaml_ffi_evas","last_synced_at":"2025-03-31T01:14:19.424Z","repository":{"id":144939721,"uuid":"63630684","full_name":"cedlemo/ocaml_ffi_evas","owner":"cedlemo","description":"Simple example on how to use the ocaml ffi with evas from efl","archived":false,"fork":false,"pushed_at":"2016-08-24T20:24:56.000Z","size":509,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-06T06:24:14.808Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cedlemo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-07-18T19:24:31.000Z","updated_at":"2017-04-22T23:14:31.000Z","dependencies_parsed_at":"2023-04-04T21:03:14.063Z","dependency_job_id":null,"html_url":"https://github.com/cedlemo/ocaml_ffi_evas","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedlemo%2Focaml_ffi_evas","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedlemo%2Focaml_ffi_evas/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedlemo%2Focaml_ffi_evas/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedlemo%2Focaml_ffi_evas/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cedlemo","download_url":"https://codeload.github.com/cedlemo/ocaml_ffi_evas/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246399798,"owners_count":20770908,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-03T22:16:09.182Z","updated_at":"2025-03-31T01:14:19.393Z","avatar_url":"https://github.com/cedlemo.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Using FFI with OCaml\n\nIn the [Chap 19 Foreign Function Interface](https://realworldocaml.org/v1/en/html/foreign-function-interface.html) of\nthe book Real World OCaml, the authors use the library NCurse as example.\n\nHere I use Ecore / Evas from the [Enlightenment Libraries](https://www.enlightenment.org/) know as E.F.L.\n\n## Table of content\n\n* [Presentation of the C library](#presentation-of-the-c-library)\n  *  [The C functions to use](#the-c-functions-to-use)\n  *  [The code to reproduce](#the-code-to-reproduce)\n* [Create the OCaml bindings](#create-the-ocaml-bindings)\n  * [The Ecore_evas module: simple implementation](#the-ecore-evas-module-:-simple-implementation)\n  * [The main OCaml file](#the-main-ocaml-file)\n  * [The Ecore_evas module: a more subtle implementation](#the-ecore_evas-module:-a-more-subtle-implementation)\n    * [The string_opt type](#the-string_opt-type)\n    * [Create a view for a bool type](#create-a-view-for-a-bool-type)\n    * [Manage C function pointers](#manage-c-functions-pointers)\n  * [The Ecore_evas module: C stubs generation and static bindings](#the-ecore_evas-module:-c-stubs-generation-and-static-bindings)\n    * [The bindings functor](#the-bindings-functor)\n    * [The stubs generator](#the-stubs-generator)\n    * [Compiling the bindings](#compiling-the-bindings)\n    * [Using the static bindings](#using-the-static-bindings)\n    * [Build static bindings with myocamlbuild.ml](#build-static-bindings-with-myocamlbuild.ml)\n\n## Presentation of the C library\n\n### The C functions to use\n\n  * void ecore_main_loop_quit(void)\n  * void ecore_main_loop_begin(void)\n  * int ecore_evas_init(void)\n  * int ecore_evas_shutdown(void)\n  * Ecore_Evas * ecore_evas_new(const char *, int, int, int, int, const char *)\n  * ecore_evas_title_set(const Ecore_Evas *, const char *);\n  * ecore_evas_alpah_set(const Ecore_Evas *, Eina_Bool)\n  * ecore_evas_show(ee);\n  * ecore_evas_free(ee);\n\n### The code to reproduce\n\nIt is just a little transparent window.\n\n```c\n/* Compile with:\n * Normal shell (bash, sh, zsh):\n * gcc $(pkg-config --libs --cflags ecore-evas ecore) -o evas_ecore_window_c evas_ecore_window.c\n * Fish shell:\n * eval gcc (pkg-config --libs --cflags ecore-evas ecore) -o evas_ecore_window_c evas_ecore_window.c\n */\n#include \u003cstdio.h\u003e\n#include \u003cstdlib.h\u003e\n\n#include \u003cEcore.h\u003e\n#include \u003cEcore_Evas.h\u003e\n\nint main(int argc, char **argv)\n{\n    if(!ecore_evas_init())\n        return EXIT_FAILURE;\n\n    Ecore_Evas * window;\n    Evas *canevas;\n\n    window = ecore_evas_new(NULL, 10, 10, 320, 320, NULL);\n    if(!window)\n        return EXIT_FAILURE;\n\n    ecore_evas_alpha_set(window, EINA_TRUE);\n    ecore_evas_show(window);\n    ecore_main_loop_begin();\n    ecore_evas_free(window);\n\n    ecore_evas_shutdown();\n\n    return EXIT_SUCCESS;\n}\n```\n\n## Create the OCaml bindings\n\n###  The Ecore_evas module : simple implementation\n\n*  ecore_evas.ml\n\n```ocaml\nopen Ctypes\nopen Foreign\n\nlet ecore_main_loop_begin =\n  foreign \"ecore_main_loop_begin\" (void @-\u003e returning void)\nlet ecore_main_loop_quit =\n  foreign \"ecore_main_loop_quit\" (void @-\u003e returning void)\nlet ecore_evas_init =\n  foreign \"ecore_evas_init\" (void @-\u003e returning int)\nlet ecore_evas_shutdown =\n  foreign \"ecore_evas_shutdown\" (void @-\u003e returning int)\n\ntype ecore_evas = unit ptr\nlet ecore_evas : ecore_evas typ = ptr void\n\nlet ecore_evas_new =\n  foreign \"ecore_evas_new\" (ptr char @-\u003e int @-\u003e int @-\u003e int @-\u003e int @-\u003e ptr char @-\u003e returning ecore_evas)\nlet ecore_evas_title_set =\n  foreign \"ecore_evas_title_set\" (ecore_evas @-\u003e string @-\u003e returning void)\nlet ecore_evas_show =\n  foreign \"ecore_evas_show\" (ecore_evas @-\u003e returning void)\nlet ecore_evas_free =\n  foreign \"ecore_evas_free\" (ecore_evas @-\u003e returning void)\nlet ecore_evas_alpha_set =\n  foreign \"ecore_evas_alpha_set\" (ecore_evas @-\u003e int @-\u003e returning void)\n```\n\nWe generate the module interface file from the module.\n\n```bash\ncorebuild -pkg ctypes.foreign ecore_evas.inferred.mli\ncp _build/ecore_evas.inferred.mli ./\n```\n\n### The main OCaml file\n\n```ocaml\nopen Ecore_evas\nopen Ctypes\n\nlet initialize_subsystem () =\n  if (ecore_evas_init () != 0) then\n    print_endline \"Ecore Evas initialized\"\n  else\n    print_endline \"Unable to initialize Ecore Evas system\"\n\nlet  () =\n  initialize_subsystem ();\n  print_endline \"Creating ee\";\n  let ee = ecore_evas_new (from_voidp char null) 50 50 300 300 (from_voidp char null) in\n  ecore_evas_title_set ee \"This is a test\";\n  ecore_evas_alpha_set ee 1;\n  print_endline \"Showing ee\";\n  ecore_evas_show ee;\n  ecore_main_loop_begin ();\n  ecore_evas_free ee;\n  ignore(ecore_evas_shutdown ())\n```\n\nThis file can be build and run with:\n\n```bash\ncorebuild -pkg ctypes.foreign -lflags -cclib,-lecore_evas -lflags -cclib,-lecore ecore_evas_window.native\n./ecore_evas_window.native\n```\n\n### The Ecore_evas module: a more subtle implementation:\n\n#### The string_opt type\n\nPreviously when I needed to pass a C `char *` I used in the OCaml bindings both\n`char ptr` and `string`. That is because I wanted to use Null pointers in the\nfunction `ecore_evas_new`. In Ctypes there is the type `string_opt` which can\nsupport a pointer of char and a Null pointer.\n\n*  in ecore_evas.ml\n\n```\nlet ecore_evas_new =\n  foreign \"ecore_evas_new\" (string_opt @-\u003e int @-\u003e int @-\u003e int @-\u003e int @-\u003e string_opt @-\u003e returning ecore_evas)\n\nlet ecore_evas_title_set =\n  foreign \"ecore_evas_title_set\" (ecore_evas @-\u003e string_opt @-\u003e returning void)\n```\n\n*  in ecore_evas_window.ml\n\n```\nlet ee = ecore_evas_new None 50 50 300 300 None in\necore_evas_title_set ee (Some \"This is a test\");\n```\n\n#### create a view for a bool type.\n\nIn the EFL, there is are boolean value `EINA_TRUE` which is an integer equal to\none and `EINA_FALSE` whose value is 0. The idea is to bind those values to the\n`true` and `false` boolean values of OCaml.\n\nFor this I will use  a \"view\". The `Ctypes.view` function create a new C type\ndescriptions with the instructions to be used when OCaml read or write those\nkind of C values.\n\n*  example with the Ctypes.string :\n\n```\nlet string =\n  view\n  (char ptr)\n  ~read:string_of_char_ptr\n  ~write:char_ptr_of_string\n```\n\n*  boolean value which can be 0 (false) or 1 (true):\n\n```\nlet bool =\n   view\n   int\n   ~read:((\u003c\u003e)0)\n   ~write:(fun b -\u003e compare b false)\n```\n\n*  in the ecore_evas.ml\n\n```\nlet eina_bool =\n  view ~read:((\u003c\u003e) 0) ~write:(fun b -\u003e compare b false) int\n\nlet ecore_evas_init =\n  foreign \"ecore_evas_init\" (void @-\u003e returning eina_bool)\nlet ecore_evas_shutdown =\n  foreign \"ecore_evas_shutdown\" (void @-\u003e returning eina_bool)\n\nlet ecore_evas_alpha_set =\n  foreign \"ecore_evas_alpha_set\" (ecore_evas @-\u003e eina_bool @-\u003e returning void)\n```\n\n* Usage in the ecore_evas_window.ml:\n\n```\n  ecore_evas_alpha_set ee true;\n```\nIt exits an internal bool representation :\n*  https://github.com/ocamllabs/ocaml-ctypes/issues/24\n*  https://github.com/ocamllabs/ocaml-ctypes/commit/d662070db22b99c74879c01da8db54bedc270e8b\n\n#### Manage C function pointers\n\nAs an example, I will implement with `CTypes` this function:\n\n```c\nEAPI void ecore_evas_callback_delete_request_set(Ecore_Evas *ee,\n\t\t\t\t\t\t Ecore_Evas_Event_Cb func)\n```\nIt sets a callback for `Ecore_Evas` delete request events. Basicly when a user\nhit the close button in the title bar of a window, it generates this kind of\nevent.\n\n*  Parameters\n  *  ee\tThe Ecore_Evas to set callbacks on\n  *  func\tThe function to call\n\nIn our module, we first implement the callback as a new type:\n\n```ocaml\nlet ecore_evas_event_cb = ecore_evas @-\u003e returning void\n```\nIt means that `ecore_evas_event_cb` is an OCaml function that takes one\nparameters and that returns nothing.\n\nThen we can use this type in the foreign description:\n\n```ocaml\nlet ecore_evas_callback_delete_request_set =\n  foreign \"ecore_evas_callback_delete_request_set\" (ecore_evas @-\u003e funptr ecore_evas_event_cb @-\u003e returning void)\n```\nHere I define an OCaml function `ecore_evas_callback_delete_request_set` that\nneeds two parameters , one of the type `ecore_evas` and one OCaml function that will be\ntransformed in a C function pointer thanks to funptr.\n\nIn the following OCaml code, I use this callback:\n\n```ocaml\nlet delete_event_cb ee =\n  print_endline \"Bye Bye\";\n  ecore_main_loop_quit ()\n```\nIn order to print a message and exit the main loop when a delete event appears.\n\nHere is the full working code:\n\n```ocaml\nopen Ecore_evas\n(* compile with:\n  * corebuild -pkg ctypes.foreign -lflags -cclib,-lecore_evas -lflags -cclib,-lecore ecore_evas_window.native\n  *)\nopen Ctypes\nlet initialize_subsystem () =\n  if (ecore_evas_init () != false) then\n    print_endline \"Ecore Evas initialized\"\n  else\n    print_endline \"Unable to initialize Ecore Evas system\"\n\nlet delete_event_cb ee =\n  print_endline \"Bye Bye\";\n  ecore_main_loop_quit ()\n\nlet  () =\n  initialize_subsystem ();\n  print_endline \"Creating ee\";\n  let ee = ecore_evas_new None 50 50 300 300 None in\n  ecore_evas_title_set ee (Some \"This is a test\");\n  ecore_evas_alpha_set ee true;\n  ecore_evas_callback_delete_request_set ee delete_event_cb;\n  print_endline \"Showing ee\";\n  ecore_evas_show ee;\n  ecore_main_loop_begin ();\n  ecore_evas_free ee;\n  ignore(ecore_evas_shutdown ())\n```\n\n### The Ecore_evas module: C stubs generation and static bindings\n\nresources :\n*  http://simonjbeaumont.com/posts/ocaml-ctypes/\n*  https://github.com/simonjbeaumont/ocaml-pci\n*  https://github.com/simonjbeaumont/ocaml-flock\n*  https://ocaml.io/w/Ctypes#Static_and_dynamic_binding_strategies\n\nThe idea is to create a functor which defines the bindings, use this functor\nwith the `Cstubs` functions in order to generate the C code and the corresponding\nocaml code.\n\n#### The Bindings functor\n\n```ocaml\nopen Ctypes\n\nmodule Bindings (F : Cstubs.FOREIGN) = struct\n  open F\n  let eina_bool =\n    view ~read:((\u003c\u003e) 0) ~write:(fun b -\u003e compare b false) int\n\n  let ecore_main_loop_begin =\n    foreign \"ecore_main_loop_begin\" (void @-\u003e returning void)\n  let ecore_main_loop_quit =\n    foreign \"ecore_main_loop_quit\" (void @-\u003e returning void)\n  let ecore_evas_init =\n    foreign \"ecore_evas_init\" (void @-\u003e returning eina_bool)\n  let ecore_evas_shutdown =\n    foreign \"ecore_evas_shutdown\" (void @-\u003e returning eina_bool)\n\n  type ecore_evas = unit ptr\n  let ecore_evas : ecore_evas typ = ptr void\n\n  let ecore_evas_new =\n    foreign \"ecore_evas_new\" (string_opt @-\u003e int @-\u003e int @-\u003e int @-\u003e int @-\u003e string_opt @-\u003e returning ecore_evas)\n\n  let ecore_evas_title_set =\n    foreign \"ecore_evas_title_set\" (ecore_evas @-\u003e string_opt @-\u003e returning void)\n\n  let ecore_evas_show =\n    foreign \"ecore_evas_show\" (ecore_evas @-\u003e returning void)\n\n  let ecore_evas_free =\n    foreign \"ecore_evas_free\" (ecore_evas @-\u003e returning void)\n\n  let ecore_evas_alpha_set =\n    foreign \"ecore_evas_alpha_set\" (ecore_evas @-\u003e eina_bool @-\u003e returning void)\nend\n```\nNothing fancy here, I just put the previous code in a functor.\n\n#### The stubs generator\n\nNow we will use this functor in order to generate the C or OCaml code.\n\n```ocaml\nlet _ =\n  let prefix = \"ecore_evas_stubs\" in\n  let generate_ml, generate_c = ref false, ref false in\n  Arg.(parse [ (\"-ml\", Set generate_ml, \"Generate ML\");\n                (\"-c\", Set generate_c, \"Generate C\") ])\n    (fun _ -\u003e failwith \"unexpected anonymous argument\")\n    \"stubgen [-ml|-c]\";\n  match !generate_ml, !generate_c with\n  | false, false\n  | true, true -\u003e\n    failwith \"Exactly one of -ml and -c must be specified\"\n  | true, false -\u003e\n    Cstubs.write_ml Format.std_formatter ~prefix (module Ecore_evas.Bindings)\n  | false, true -\u003e\n      print_endline \"#include \u003cEcore.h\u003e\\n#include \u003cEcore_Evas.h\u003e\";\n    Cstubs.write_c Format.std_formatter ~prefix (module Ecore_evas.Bindings)\n```\n\nThis program is simple, it takes one argument that can be `-ml` or `-c`.\n\n#### Compiling the bindings\n\n#### Using the static bindings\n\n#### Build static bindings with myocamlbuild.ml\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcedlemo%2Focaml_ffi_evas","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcedlemo%2Focaml_ffi_evas","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcedlemo%2Focaml_ffi_evas/lists"}