Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
139 lines (114 sloc) 7.84 KB

best practices как писать либу с сишными биндингами чтобы она с минимумом проблем собиралась на всех инкарнациях камля.

Scope: ocaml утилиты, сишный код, утилиты для сборки/конфигурирования/установки NB: сейчас тут вперемешку проблемы, решения, жалобы и нытьё

  • OCamlMakefile требует bash/sed т.е. для ocaml/msvc не очень годится

  • ocamlbuild требует bash (ссылка где брать)

  • хинт не забывать про расширения файлов (o/obj, a/lib etc)

  • msvc не понимает c99 - поэтому на gcc сразу давать -std=c89 чтобы отлавливать несоответствия, а также -Wall (и -Wextra -pedantic для полноты ощущений)

    • комментарии //
    • inline
    • переменные только в начале блока
  • на какие define'ы можно ориентироваться (_WIN32 - msvc, mingw, кстати на x64 что?, _MSC_VER - msvc only)

  • пути к заголовочным файлам и либам

    • configure (как правильно делать, делать ли вообще, альтернативы)

    • пример типичного косяка. Mysql Connector/C (aka libmysql):

      • на linux заголовочные файлы в /usr/include/mysql/mysql.h т.е. в коде логично писать #include <mysql/mysql.h>
      • в source же дистрибутиве (который будет использоваться на windows) под виндой путь будет например C:/mysql/include/mysql.h т.е. в коде надо писать #include <header.h> и передавать правильный путь в -I опцию для препроцессора.
    • линковка, импорт либы под msvc не подходят для mingw, и наоборот. import либу по dll'ке можно сгенерировать руками

  • не стоит использовать -custom (deprecated, "загрязняет" все зависимые бинарники)

  • и прочие подобные косяки (перечислить. много их)

Этапы сборки биндингов

  • компиляция и линковка сишного кода и получение stubs - статической (для native и custom byte сборок) и динамической (для byte) библиотек содержащих сишный код переходников и импорты требующихся внешних сишных функций
  • компиляция камлевой стороны (объявления external)
  • линковка камлевой стороны с вписыванием (-cclib) ссылок на stub библиотеки (в виде опций линкера) в тело cma/cmxa модулей (это необязательно, но очень удобно так как избавляет от неоходимости указывать имя stubs во время финальной линковки и стирает различие между обычной камлевой либой и биндингом с точки зрения процесса сборки)
  • линковка программы использующей биндинги. Тут надо предусмотреть случаи когда биндинг уже установлен или используется локально (опции -dllpath для ocamlrun и -I <локальный каталог> для сишного линкера)

Ocamlbuild

myocamlbuild.ml: open Ocamlbuild_plugin open Command open Printf

(**
  [link_stubs name ?cclib stubs] embeds options into ocaml library [name] so that
  further linking with main program will find C [stubs] library
  @param name ocaml library name
  @param stubs C stubs module name
  @param cclib extra -cclib options to pass to linker
*)
let link_stubs name ?(cclib=[]) stubs =
  let stubs_lib = sprintf "lib%s.%s" stubs !Options.ext_lib in
  let stubs_dll = sprintf "dll%s.%s" stubs !Options.ext_dll in
  let cclib = List.flatten & List.map (fun lib -> ["-cclib"; lib]) & ("-l"^stubs) :: cclib in
  List.iter (fun ext ->
    let file = sprintf "file:%s.%s" name ext in
    (* embed -l option into ocaml library so that C linker can find C stubs *)
    flag ["link"; "ocaml"; "library"; file] & atomize cclib;
    dep  ["link"; "ocaml"; "library"; file] [stubs_lib];
    if ext = "cma" then
    begin
      (* embed -l option into ocaml library so that ocamlrun can find dll stubs *)
      flag ["link"; "ocaml"; "library"; "byte"; file] & atomize ["-dllib"; "-l" ^ stubs;];
      dep  ["link"; "ocaml"; "library"; "byte"; file] [stubs_dll]
    end
  ) ["cma"; "cmxa"]


let ocaml_lib_stubs name ?(dir="") ?(cclib=[]) stubs =
  link_stubs name ~cclib stubs;
  (* locally compiled programs (toplevel and tests) will use locally compiled library *)
  ocaml_lib name;
  (* find library locally (ocamlrun and C linker respectively) *)
  flag ["link"; "ocaml"; "byte"; "use_"^name] & atomize ["-dllpath"; !Options.build_dir / dir];
  flag ["link"; "ocaml"; "use_"^name] & atomize ["-I"; (if dir = "" then "." else dir);]


;;


dispatch begin function
| After_rules ->
  ocaml_lib_stubs "mylib" ~cclib:["-lotherlib"] "mylib_stubs";
  dep ["c"; "compile"; "file:mylib_stubs.c"] ["mylib_config.h"];
| _ -> ()
end

libmylib_stubs.clib: mylib_extra.o mylib_stubs.o

Функции link_stubs и ocaml_lib_stubs достаточно обобщённые и портабельные (проверено - правильно работают на linux и ocaml/msvc). В файле .clib перечисляются сишные модули из которых состоит сишная сторона стабов, расширение всегда .o (да, это будет работать и с ocaml/msvc, хотя cl и создаёт объектные файлы с расширением .obj). Обратите внимание на имя файла - на такое соглашение (lib[stubs].clib) завязаны встроенные правила ocamlbuild и это учитывается параметром stubs функции link_stubs). Если сишный код зависит от внешних библиотек то эту зависимость надо указать явно :

  • для сишного компилятора указать пути к заголовочным файлам (тэг include_otherlib_c надо указать для соответствующих сишных файлов в _tags) : flag ["c"; "compile"; "include_otherlib_c"] & atomize ["-ccopt"; "-I" ^ path_to_otherlib_include]
  • вшить -cclib зависимость от внешней библиотеки в результирующий cm{x,}a с помощью параметра ~cclib:["-lotherlib"]

В примере зависимость mylib_stubs.c от (локального) mylib_config.h приведёт к тому что последний будет скопирован предварительно в _build и его сможет найти сишный компилятор.


2011-03-26 13:09