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 <локальный каталог> для сишного линкера)
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