Глава 22 Интерфейс C с OCaml
- 22.1 Обзор и информация о компиляции
- 22.2 Тип value
- 22.3 Представление типов данных OCaml
- 22.4 Операции над значениями
- 22.5 Совместимость с сборщиком мусора
- 22.6 Полный пример
- 22.7 Расширенная тема: обратные вызовы из C в OCaml
- 22.8 Расширенный пример с обратными вызовами
- 22.9 Расширенная тема: пользовательские блоки
- 22.10 Расширенная тема: Bigarrays и интерфейс OCaml-C
- 22.11 Расширенная тема: более быстрый вызов C
- 22.12 Расширенная тема: многопоточность
- 22.13 Расширенная тема: интерфейс с API Windows Unicode
- 22.14 Создание смешанных C/OCaml библиотек: ocamlmklib
- 22.15 Предостережения: внутренний API среды выполнения
В этой главе описывается, как пользовательские примитивы, написанные на C, могут быть связаны с кодом OCaml и вызваны из функций OCaml, а также как эти функции C могут вызывать код OCaml.
22.1 Обзор и информация о компиляции
22.1.1 Объявление примитивов
|
Пользовательские примитивы объявляются в файле реализации или в модульном выражении struct…end с помощью ключевого слова external:
external name : type = C-function-name
Это определяет имя значения name как функцию с типом type, которая выполняется путем вызова указанной функции C. Например, вот как объявляется примитив seek_in в стандартном модуле библиотеки Stdlib:
external seek_in : in_channel -> int -> unit = "caml_ml_seek_in"
Примитивы с несколькими аргументами всегда являются каррированными. Функция C необязательно имеет то же имя, что и ML-функция.
Таким образом определенные внешние функции могут быть указаны в файлах интерфейса или в сигнатурах sig…end как обычные значения
val name : type
тем самым скрывая их реализацию как функций C, или явно как «явные» внешние функции
external name : type = C-function-name
Последний вариант немного более эффективен, так как он позволяет клиентам модуля вызывать напрямую функцию C вместо обращения через соответствующую функцию OCaml. С другой стороны, его не следует использовать в модулях библиотек, если они имеют побочные эффекты на верхнем уровне, поскольку этот прямой вызов вмешивается в алгоритм компоновщика по удалению неиспользуемых модулей из библиотек во время компоновки.
Арность (количество аргументов) примитива автоматически определяется из его типа OCaml в объявлении external путем подсчета числа стрелок функций в типе. Например, seek_in выше имеет арность 2, и функция C caml_ml_seek_in вызывается с двумя аргументами. Аналогично,
external seek_in_pair: in_channel * int -> unit = "caml_ml_seek_in_pair"
имеет арность 1, и функция C caml_ml_seek_in_pair получает один аргумент (который является парой значений OCaml).
Сокращения типов не раскрываются при определении арности примитива. Например,
type int_endo = int -> int
external f : int_endo -> int_endo = "f"
external g : (int -> int) -> (int -> int) = "f"
f имеет арность 1, но g имеет арность 2. Это позволяет примитиву возвращать функциональное значение (как в примере f выше): просто запомните, чтобы назвать функциональный возвращаемый тип в сокращении типа.
Язык допускает внешние объявления с одним или двумя строками флагов в дополнение к имени функции C. Эти флаги зарезервированы для реализации стандартной библиотеки.
22.1.2 Реализация примитивов
Пользовательские примитивы с арностью n ≤ 5 реализуются функциями C, которые принимают n аргументов типа value и возвращают результат типа value. Тип value — это тип представлений значений OCaml. Он кодирует объекты нескольких базовых типов (целые числа, числа с плавающей точкой, строки, ...) а также структуры данных OCaml. Тип value и связанные с ним функции и макросы преобразования подробно описаны ниже. Например, вот объявление для функции C, реализующей примитив In_channel.input, который принимает 4 аргумента:
CAMLprim value input(value channel, value buffer, value offset, value length)
{
...
}
Когда примитивная функция применяется в программе OCaml, функция C вызывается со значениями выражений, к которым применяется примитив, как аргументы. Возвращаемое значение функции передается обратно программе OCaml в качестве результата применения функции.
Пользовательские примитивы с арностью больше 5 должны реализовываться двумя функциями C. Первая функция, используемая совместно с компилятором байткода ocamlc, получает два аргумента: указатель на массив значений OCaml (значения аргументов) и целое число, являющееся количеством предоставленных аргументов. Другая функция, используемая совместно с компилятором кода нативных функций ocamlopt, принимает свои аргументы непосредственно. Например, вот две функции C для 7-аргументного примитива Nat.add_nat:
CAMLprim value add_nat_native(value nat1, value ofs1, value len1,
value nat2, value ofs2, value len2,
value carry_in)
{
...
}
CAMLprim value add_nat_bytecode(value * argv, int argn)
{
return add_nat_native(argv[0], argv[1], argv[2], argv[3],
argv[4], argv[5], argv[6]);
}
Имена двух функций C должны быть указаны в объявлении примитива следующим образом:
external name : type =
bytecode-C-function-name native-code-C-function-name
Например, в случае add_nat объявление следующее:
external add_nat: nat -> int -> int -> nat -> int -> int -> int -> int
= "add_nat_bytecode" "add_nat_native"
Реализация пользовательского примитива — это фактически две отдельные задачи: с одной стороны, декодирование аргументов для извлечения значений C из заданных значений OCaml и кодирование возвращаемого значения как значения OCaml; с другой стороны, фактическое вычисление результата из аргументов. За исключением очень простых примитивов, часто предпочтительнее иметь две отдельные функции C для реализации этих двух задач. Первая функция фактически реализует примитив, принимая значения C как аргументы и возвращая значение C. Вторая функция, часто называемая «функцией-оболочкой», представляет собой простую обертку вокруг первой функции, которая преобразует свои аргументы из значений OCaml в значения C, вызывает первую функцию и преобразует возвращаемое значение C в значение OCaml. Например, вот код-обёртка для примитива Int64.float_of_bits:
CAMLprim value caml_int64_float_of_bits(value vi)
{
return caml_copy_double(caml_int64_float_of_bits_unboxed(Int64_val(vi)));
}
(Здесь caml_copy_double и Int64_val являются функциями и макросами преобразования для типа value, которые будут описаны позже. Макрос CAMLprim расширяется до необходимых директив компилятора для обеспечения того, что функция экспортируется и доступна из OCaml.) Трудная работа выполняется функцией caml_int64_float_of_bits_unboxed, которая объявлена так:
double caml_int64_float_of_bits_unboxed(int64_t i)
{
...
}
Для написания кода C, который работает со значениями OCaml, предоставляются следующие файлы заголовков:
| Файл для включения | Предоставляет |
| caml/mlvalues.h | определение типа value и макросы преобразования |
| caml/alloc.h | функции выделения (для создания структурированных объектов OCaml) |
| caml/memory.h | разнообразные функции и макросы, связанные с памятью (для интерфейса сборщика мусора, модификации структур на месте и т. д.). |
| caml/fail.h | функции для поднятия исключений (см. раздел 22.4.5) |
| caml/callback.h | обратные вызовы из C в OCaml (см. раздел 22.7). |
| caml/custom.h | операции над пользовательскими блоками (см. раздел 22.9). |
| caml/intext.h | операции для написания пользовательских функций сериализации и десериализации для пользовательских блоков (см. раздел 22.9). |
| caml/threads.h | операции для взаимодействия в условиях наличия нескольких потоков (см. раздел 22.12). |
Эти файлы находятся в подкаталоге caml/ каталога стандартной библиотеки OCaml, который возвращается командой ocamlc -where (обычно /usr/local/lib/ocaml или /usr/lib/ocaml).
22.1.3 Статическая линковка кода C с кодом OCaml
Система выполнения OCaml состоит из трёх основных частей: интерпретатора байткода, менеджера памяти и набора функций C, которые реализуют основные операции. Некоторые инструкции байткода предназначены для вызова этих функций C, определяемых по их смещению в таблице функций (таблице примитивов).
В режиме по умолчанию линковщик OCaml генерирует байткод для стандартной системы выполнения со стандартным набором примитивов. Ссылки на примитивы, которые не входят в этот стандартный набор, приводят к ошибке «недоступный C-примитив». (За исключением случаев поддержки динамической загрузки C-библиотек — см. раздел 22.1.4 ниже.)
В режиме «пользовательской системы выполнения» линковщик OCaml сканирует объектные файлы и определяет набор необходимых примитивов. Затем он строит подходящую систему выполнения, вызвав линковщик нативного кода со следующими данными:
- таблицей необходимых примитивов;
- библиотекой, которая предоставляет интерпретатор байткода, менеджер памяти и стандартные примитивы;
- библиотеками и объектные файлы (.o-файлы), указанные в командной строке для линковщика OCaml, которые предоставляют реализации пользовательских примитивов.
Это создаёт систему выполнения с необходимыми примитивами. Линковщик OCaml генерирует байткод для этой пользовательской системы выполнения. Байткод добавляется в конец пользовательской системы выполнения, так что он будет автоматически выполнен при запуске выходного файла (пользовательская система выполнения + байткод).
Для линковки в режиме «пользовательской системы выполнения» выполните команду ocamlc со следующими параметрами:
- параметром -custom;
- названиями необходимых объектных файлов OCaml (.cmo и .cma файлы);
- названиями C-объектных файлов и библиотек (.o и .a файлы), которые реализуют необходимые примитивы. В Unix и Windows, библиотека с именем libимя.a (соответственно, .lib) находящаяся в одном из стандартных каталогов библиотек, также может быть указана как -cclib -lимя.
Если вы используете компилятор нативного кода ocamlopt, флаг -custom не требуется, так как заключительная фаза линковки ocamlopt всегда создаёт автономный исполняемый файл. Для создания смешанного OCaml/C исполняемого файла выполните команду ocamlopt со следующими параметрами:
- названиями необходимых нативных объектных файлов OCaml (.cmx и .cmxa файлы);
- названиями C-объектных файлов и библиотек (.o, .a, .so или .dll файлы), которые реализуют необходимые примитивы.
Начиная с Objective Caml 3.00, можно записать параметр -custom, а также имена C-библиотек в файл библиотеки OCaml .cma или .cmxa. Например, рассмотрим библиотеку OCaml mylib.cma, созданную из объектных файлов OCaml a.cmo и b.cmo, которые ссылаются на код C в libmylib.a. Если библиотека построена следующим образом:
ocamlc -a -o mylib.cma -custom a.cmo b.cmo -cclib -lmylib
пользователи библиотеки могут просто связать её с mylib.cma:
ocamlc -o myprog mylib.cma ...
и система автоматически добавит параметры -custom и -cclib -lmylib, достигая того же эффекта, что и
ocamlc -o myprog -custom a.cmo b.cmo ... -cclib -lmylib
Альтернатива, конечно, состоит в том, чтобы создать библиотеку без дополнительных параметров:
ocamlc -a -o mylib.cma a.cmo b.cmo
и затем попросить пользователей предоставить параметры -custom и -cclib -lmylib самим при линковке:
ocamlc -o myprog -custom mylib.cma ... -cclib -lmylib
Однако первый вариант удобнее для конечных пользователей библиотеки.
22.1.4 Динамическая линковка кода C с кодом OCaml
Начиная с Objective Caml 3.03, предлагается альтернатива статической линковке кода C, используя код -custom. В этом режиме линковщик OCaml генерирует чистый исполняемый файл байткода (без встроенной пользовательской системы выполнения), который просто записывает имена динамически загружаемых библиотек, содержащих код C. Стандартная система выполнения OCaml ocamlrun затем динамически загружает эти библиотеки и разрешает ссылки на необходимые примитивы перед выполнением байткода.
Эта функция в настоящее время доступна на всех платформах, поддерживаемых OCaml, кроме Cygwin 64 бит.
Для динамической линковки кода C с кодом OCaml, код C должен быть скомпилирован в динамическую библиотеку (под Unix) или DLL (под Windows). Это включает в себя 1- компиляцию C-файлов с соответствующими флагами компилятора C для создания кода, независимого от позиции (когда это требуется операционной системой), и 2- создание динамической библиотеки из полученных объектных файлов. Полученный файл динамической библиотеки или DLL должен быть установлен в место, где ocamlrun может найти его позже при запуске программы (см. раздел 15.3). Наконец (шаг 3), выполните команду ocamlc со следующими параметрами:
- названиями необходимых объектных файлов OCaml (.cmo и .cma файлы);
- названиями C-динамических библиотек (.so или .dll файлы), которые реализуют необходимые примитивы. В Unix и Windows, библиотека с именем dllимя.so (соответственно, .dll) находящаяся в одном из стандартных каталогов библиотек, также может быть указана как -dllib -lимя.
Не устанавливайте флаг -custom, иначе вы вернётесь к статической линковке, как описано в разделе 22.1.3. Инструмент ocamlmklib (см. раздел 22.14) автоматизирует шаги 2 и 3.
Как и в случае со статической линковкой, можно (и рекомендуется) записать имена C-библиотек в архив библиотеки OCaml .cma. Рассмотрим снова библиотеку OCaml mylib.cma, созданную из объектных файлов OCaml a.cmo и b.cmo, которые ссылаются на код C в dllmylib.so. Если библиотека построена следующим образом:
ocamlc -a -o mylib.cma a.cmo b.cmo -dllib -lmylib
пользователи библиотеки могут просто связать её с mylib.cma:
ocamlc -o myprog mylib.cma ...
и система автоматически добавит опцию -dllib -lmylib, достигнув того же эффекта, что и
ocamlc -o myprog a.cmo b.cmo ... -dllib -lmylib
Используя этот механизм, пользователи библиотеки mylib.cma не должны знать, что она ссылается на код C, а также о том, должен ли этот код C быть статически связан (с использованием -custom) или динамически связан.
22.1.5 Выбор между статической и динамической связью
После описания двух различных способов связи кода C с кодом OCaml, мы теперь рассмотрим преимущества и недостатки каждого из них, чтобы помочь разработчикам смешанных библиотек OCaml/C принять решение.
Основное преимущество динамической связи заключается в том, что она сохраняет платформенную независимость исполняемых файлов байткода. То есть, исполняемый файл байткода не содержит машинного кода и, следовательно, может быть скомпилирован на платформе A и выполнен на других платформах B, C, ..., при условии, что необходимые общие библиотеки доступны на всех этих платформах. В отличие от этого, исполняемые файлы, сгенерированные с помощью ocamlc -custom, работают только на той платформе, на которой они были созданы, потому что они содержат адаптированную к данной платформе систему выполнения. Кроме того, динамическая связь приводит к созданию более компактных исполняемых файлов.
Другим преимуществом динамической связи является то, что конечным пользователям библиотеки не требуется устанавливать на свои компьютеры компилятор C, компоновщик C и библиотеки времени выполнения C. Это не проблема под Unix и Cygwin, но многие пользователи Windows неохотно устанавливают Microsoft Visual C только для того, чтобы иметь возможность использовать ocamlc -custom.
Существует два недостатка динамической связи. Первый заключается в том, что полученный исполняемый файл не является автономным: он требует, чтобы общие библиотеки, а также ocamlrun, были установлены на машине, на которой выполняется код. Если вы хотите распространять автономный исполняемый файл, лучше связать его статически, используя ocamlc -custom -ccopt -static или ocamlopt -ccopt -static. Динамическая связь также вызывает проблему «DLL-адов»: необходимо принять меры для обеспечения того, что при запуске будут найдены правильные версии общих библиотек.
Второй недостаток динамической связи заключается в том, что она усложняет построение библиотеки. Флаги компилятора и компоновщика C для компиляции в позиционно-независимый код и создания общей библиотеки сильно различаются на разных системах Unix. Кроме того, динамическая связь не поддерживается на всех системах Unix, что требует добавления в Makefile библиотеки запасного варианта — статической связи. Команда ocamlmklib (см. раздел 22.14) пытается скрыть некоторые из этих зависимостей от системы.
В заключение: динамическая связь очень рекомендуется под native Windows, так как проблем с переносимостью нет, и она значительно удобнее для конечных пользователей. Под Unix динамическую связь следует рассмотреть для зрелых, часто используемых библиотек, поскольку она повышает платформенную независимость исполняемых файлов байткода. Для новых или редко используемых библиотек статическая связь намного проще настраивается для переноса.
22.1.6 Построение автономных систем выполнения пользовательского типа
Иногда неудобно создавать систему выполнения пользовательского типа каждый раз, когда код OCaml связывается с C-библиотеками, как это делает ocamlc -custom. Во-первых, построение системы выполнения на некоторых системах (с плохими компоновщиками или медленными удалёнными файловыми системами) происходит медленно; во-вторых, теряется платформенная независимость файлов байткода, что вынуждает выполнять одну связку ocamlc -custom на каждой интересующей платформе.
Альтернативой ocamlc -custom является отдельное построение системы выполнения пользовательского типа, интегрирующей необходимые C-библиотеки, а затем генерация «чистых» исполняемых файлов байткода (не содержащих собственной системы выполнения), которые могут выполняться в этой системе выполнения. Это достигается с помощью флагов -make-runtime и -use-runtime для ocamlc. Например, чтобы построить систему выполнения пользовательского типа, интегрирующую части C-библиотек «Unix» и «Threads», выполните:
ocamlc -make-runtime -o /home/me/ocamlunixrun unix.cma threads.cma
Чтобы сгенерировать исполняемый файл байткода, который выполняется в этой системе выполнения, выполните:
ocamlc -use-runtime /home/me/ocamlunixrun -o myprog \
unix.cma threads.cma your .cmo and .cma files
Исполняемый файл байткода myprog затем можно запустить обычным способом: myprog args или /home/me/ocamlunixrun myprog args.
Обратите внимание, что библиотеки байткода unix.cma и threads.cma должны быть указаны дважды: при построении системы выполнения (чтобы ocamlc знал, какие C-примитивы требуются) и также при построении исполняемого файла байткода (чтобы байт-код из unix.cma и threads.cma был фактически подключён).
22.2 Тип value
Все объекты OCaml представляются типом C value, определённым в файле заголовков caml/mlvalues.h, а также макросами для обработки значений этого типа. Объект типа value может быть:
- неупакованным целым числом;
- или указателем на блок внутри кучи, выделенный с помощью одной из
caml_alloc_*функций, описанных в разделе 22.4.4.
22.2.1 Целочисленные значения
Целочисленные значения кодируют 63-битные знакомые целые числа (31-битные на 32-битных архитектурах). Они не упакованы (не выделены).
22.2.2 Блоки
Блоки в куче собираются мусором, и поэтому имеют жёсткие ограничения на структуру. Каждый блок включает заголовок, содержащий размер блока (в словах) и метку блока. Метка определяет структуру содержимого блоков. Метка, меньшая чем No_scan_tag, указывает на структурированный блок, содержащий корректно сформированные значения, которые рекурсивно просматриваются сборщиком мусора. Метка, равная или большая чем No_scan_tag, указывает на необрабатываемый блок, содержимое которого не просматривается сборщиком мусора. Для удобства таких полиморфных примитивов, как равенство и структурированный ввод-вывод, структурированные и необрабатываемые блоки далее классифицируются по меткам следующим образом:
| Метка | Содержимое блока |
| 0 до No_scan_tag−1 | Структурированный блок (массив объектов OCaml). Каждый элемент — это value. |
| Closure_tag | Закрытие, представляющее функциональное значение. Первое слово — указатель на код, оставшиеся слова — value, содержащие окружение. |
| String_tag | Строка символов или последовательность байтов. |
| Double_tag | Число с двойной точностью с плавающей запятой. |
| Double_array_tag | Массив или запись чисел с двойной точностью с плавающей запятой. |
| Abstract_tag | Блок, представляющий абстрактный тип данных. |
| Custom_tag | Блок, представляющий абстрактный тип данных с пользовательскими функциями финализации, сравнения, хэширования, сериализации и десериализации. |
22.2.3 Указатели за пределами кучи
В более ранних версиях OCaml было возможно использовать выровненные по слову указатели на адреса за пределами кучи в качестве значений OCaml, просто преобразовав указатель к типу value. Это использование больше не поддерживается начиная с OCaml 5.0.
Правильный способ обработки указателей на блоки вне кучи из OCaml — хранить эти указатели в блоках OCaml с меткой Abstract_tag или Custom_tag, а затем использовать блоки в качестве значений OCaml.
Вот пример инкапсуляции указателей на внекучевые указатели типа C ty * внутри блоков Abstract_tag. Раздел 22.6 даёт более полный пример с использованием блоков Custom_tag.
/* Create an OCaml value encapsulating the pointer p */
static value val_of_typtr(ty * p)
{
value v = caml_alloc(1, Abstract_tag);
*((ty **) Data_abstract_val(v)) = p;
return v;
}
/* Extract the pointer encapsulated in the given OCaml value */
static ty * typtr_of_val(value v)
{
return *((ty **) Data_abstract_val(v));
}
В качестве альтернативы указатели вне кучи можно рассматривать как «родные» целые числа, то есть упакованные 32-битные целые числа на 32-битной платформе и упакованные 64-битные целые числа на 64-битной платформе.
/* Create an OCaml value encapsulating the pointer p */
static value val_of_typtr(ty * p)
{
return caml_copy_nativeint((intnat) p);
}
/* Extract the pointer encapsulated in the given OCaml value */
static ty * typtr_of_val(value v)
{
return (ty *) Nativeint_val(v);
}
Для указателей, выровненных по крайней мере на 2 байта (гарантируется, что младший бит равен нулю), существует ещё одно допустимое представление в виде помеченного целого числа OCaml.
/* Create an OCaml value encapsulating the pointer p */
static value val_of_typtr(ty * p)
{
assert (((uintptr_t) p & 1) == 0); /* check correct alignment */
return (value) p | 1;
}
/* Extract the pointer encapsulated in the given OCaml value */
static ty * typtr_of_val(value v)
{
return (ty *) (v & ~1);
}
22.3 Представление типов данных OCaml
В этом разделе описывается, как типы данных OCaml закодированы в типе value.
22.3.1 Атомарные типы
| Тип OCaml | Кодирование |
| int | Неупакованные целочисленные значения. |
| char | Неупакованные целочисленные значения (код ASCII). |
| float | Блоки с меткой Double_tag. |
| bytes | Блоки с меткой String_tag. |
| string | Блоки с меткой String_tag. |
| int32 | Блоки с меткой Custom_tag. |
| int64 | Блоки с меткой Custom_tag. |
| nativeint | Блоки с меткой Custom_tag. |
22.3.2 Кортежи и записи
Кортежи представляются указателями на блоки с меткой 0.
Записи также представляются блоками с меткой ноль. Порядок меток в объявлении типа записи определяет расположение полей записи: значение, связанное с первой объявленной меткой, хранится в поле 0 блока, значение, связанное со второй меткой, — в поле 1 и так далее.
В целях оптимизации записи, у которых все поля имеют статический тип float, представляются как массивы чисел с плавающей точкой с меткой Double_array_tag. (См. раздел ниже об массивах.)
В целях другой оптимизации, типы записей, допускающие разворачивание, представляются особым образом; типы записей, допускающие разворачивание, — это неизменяемые типы записей, у которых только одно поле. Тип, допускающий разворачивание, будет представлен одним из двух способов: упакованным или распакованным. Упакованные типы записей представляются как описано выше (блоком с меткой 0 или Double_array_tag). Распакованный тип записи представляется непосредственно значением его поля (т. е. нет блока, представляющего саму запись).
Представление выбирается в соответствии со следующим, в порядке убывания приоритета:
- Атрибут ([@@boxed] или [@@unboxed]) в объявлении типа.
- Параметр компилятора (-unboxed-types или -no-unboxed-types).
- Представление по умолчанию. В данной версии OCaml по умолчанию используется упакованное представление.
22.3.3 Массивы
Массивы целых чисел и указателей представляются как кортежи, то есть как указатели на блоки с меткой 0. К ним обращаются с помощью макроса Field для чтения и функции caml_modify для записи.
Массивы чисел с плавающей точкой (типа float array) имеют специальное, неупакованное, более эффективное представление. Эти массивы представляются указателями на блоки с меткой Double_array_tag. К ним следует обращаться с помощью макросов Double_field и Store_double_field.
22.3.4 Конкретные типы данных
Конструируемые термины представляются либо неупакованными целыми числами (для константных конструкторов), либо блоками, чья метка кодирует конструктор (для неконстантных конструкторов). Константные и неконстантные конструкторы для заданного конкретного типа нумеруются отдельно, начиная с 0, в порядке их появления в объявлении конкретного типа. Константный конструктор представляется неупакованным целым числом, равным его номеру конструктора. Неконстантный конструктор, объявленный с n аргументами, представляется блоком размера n с меткой номера конструктора; n полей содержат его аргументы. Пример:
| Конструируемый терм | Представление |
| () | Val_int(0) |
| false | Val_int(0) |
| true | Val_int(1) |
| [] | Val_int(0) |
| h::t | Блок размера = 2 и меткой = 0; первое поле содержит h, второе поле t. |
Для удобства в caml/mlvalues.h определены макросы Val_unit, Val_false и Val_true для обозначения (), false и true.
Следующий пример иллюстрирует присвоение целых чисел и меток блоков конструкторам:
type t = | A (* First constant constructor -> integer "Val_int(0)" *) | B of string (* First non-constant constructor -> block with tag 0 *) | C (* Second constant constructor -> integer "Val_int(1)" *) | D of bool (* Second non-constant constructor -> block with tag 1 *) | E of t * t (* Third non-constant constructor -> block with tag 2 *)
В целях оптимизации, конкретные типы данных, допускающие разворачивание, представляются особым образом; конкретный тип данных допускает разворачивание, если у него ровно один конструктор, и этот конструктор имеет ровно один аргумент. Конкретные типы данных, допускающие разворачивание, представляются теми же способами, что и типы записей, допускающие разворачивание: см. описание в разделе 22.3.2.
22.3.5 Объекты
Объекты представляются блоками с меткой Object_tag. Первое поле блока относится к классу объекта и набору методов, в формате, который трудно использовать из C. Второе поле содержит уникальный идентификатор объекта, используемый для сравнений. Остальные поля объекта содержат значения переменных экземпляра объекта. Небезопасно напрямую обращаться к переменным экземпляра, так как система типов не гарантирует, какие переменные экземпляра содержатся в объекте.
Публичный метод из объекта можно извлечь, используя функцию C caml_get_public_method (объявлена в <caml/mlvalues.h>.) Поскольку метки публичных методов хэшируются так же, как и метки вариантов, а методы являются функциями, принимающими self в качестве первого аргумента, если вы хотите выполнить вызов метода foo#bar со стороны C, вы должны вызвать:
callback(caml_get_public_method(foo, hash_variant("bar")), foo);
22.3.6 Полиморфные варианты
Как и конструируемые термины, значения полиморфных вариантов представляются либо целыми числами (для полиморфных вариантов без аргументов), либо блоками (для полиморфных вариантов с аргументом). В отличие от конструируемых терминов, конструкторы вариантов не нумеруются, начиная с 0, а идентифицируются хэш-значением (целое число OCaml), вычисленным функцией C hash_variant (объявлена в <caml/mlvalues.h>): хэш-значение для конструктора варианта, например, VConstr, — hash_variant("VConstr").
Значение варианта `VConstr представляется как hash_variant("VConstr"). Значение варианта `VConstr(v) представляется блоком размера 2 и меткой 0, где поле номер 0 содержит hash_variant("VConstr"), а поле номер 1 содержит v.
В отличие от конструируемых значений, значения полиморфных вариантов, принимающие несколько аргументов, не уплотняются. То есть `VConstr(v, w) представляется блоком размера 2, поле номер 1 которого содержит представление пары (v, w), а не блоком размера 3, содержащим v и w в полях 1 и 2.
22.4 Операции над значениями
22.4.1 Тесты типов
- Is_long(v) является истинным, если значение v — целое число, ложным — в противном случае.
- Is_block(v) является истинным, если значение v — указатель на блок, и ложным, если это целое число.
- Is_none(v) является истинным, если значение v равно None.
- Is_some(v) является истинным, если значение v (предполагается, что оно типа опция) соответствует конструктору Some.
22.4.2 Операции над целыми числами
- Val_long(l) возвращает значение, кодирующее целое число long int l.
- Long_val(v) возвращает целое число long int, закодированное в значении v.
- Val_int(i) возвращает значение, кодирующее целое число int i.
- Int_val(v) возвращает целое число int, закодированное в значении v.
- Val_bool(x) возвращает булево значение OCaml, представляющее логическое значение целочисленного значения C x.
- Bool_val(v) возвращает 0, если v — булево значение OCaml false, 1, если v — true.
- Val_true, Val_false представляют булевы значения OCaml true и false.
- Val_none представляет значение OCaml None.
22.4.3 Доступ к блокам
- Wosize_val(v) возвращает размер блока v в словах, исключая заголовок.
- Tag_val(v) возвращает метку блока v.
- Field(v, n) возвращает значение, содержащееся в n-ом поле структурированного блока v. Поля нумеруются от 0 до Wosize_val(v)−1.
- Store_field(b, n, v) сохраняет значение v в поле с номером n значения b, которое должно быть структурированным блоком.
- Code_val(v) возвращает часть кода замыкания v.
- caml_string_length(v) возвращает длину (количество байтов) строки или последовательности байтов v.
- Byte(v, n) возвращает n-ый байт строки или последовательности байтов v с типом char. Байты нумеруются с 0 до string_length(v)−1.
- Byte_u(v, n) возвращает n-ый байт строки или последовательности байтов v с типом unsigned char. Байты нумеруются с 0 до string_length(v)−1.
- String_val(v) возвращает указатель на первый байт строки v с типом const char *. Этот указатель является корректной C-строкой: после последнего байта в строке есть нулевой байт. Однако, строки OCaml могут содержать вложенные нулевые байты, что может вызвать проблемы у стандартных C-функций для работы со строками.
- Bytes_val(v) возвращает указатель на первый байт последовательности байтов v с типом unsigned char *.
- Double_val(v) возвращает число с плавающей точкой, содержащееся в значении v, с типом double.
- Double_field(v, n) возвращает n-ый элемент массива чисел с плавающей точкой v (блок с меткой Double_array_tag).
- Store_double_field(v, n, d) сохраняет число с двойной точностью с плавающей точкой d в n-ый элемент массива чисел с плавающей точкой v.
- Data_custom_val(v) возвращает указатель на часть данных пользовательского блока v. Этот указатель имеет тип void * и должен быть преобразован к типу данных, содержащихся в пользовательском блоке.
- Int32_val(v) возвращает 32-битное целое число, содержащееся в int32 v.
- Int64_val(v) возвращает 64-битное целое число, содержащееся в int64 v.
- Nativeint_val(v) возвращает длинное целое число, содержащееся в nativeint v.
- caml_field_unboxed(v) возвращает значение поля значения v любого необрамленного типа (запись или конкретный тип данных).
- caml_field_boxed(v) возвращает значение поля значения v любого обрамленного типа (запись или конкретный тип данных).
- caml_field_unboxable(v) вызывает либо caml_field_unboxed, либо caml_field_boxed в соответствии с предпочтительным представлением типов, допускающих разворачивание, в текущей версии OCaml.
- Some_val(v) возвращает аргумент \var{x} значения v в виде Some(x).
Выражения Field(v, n), Byte(v, n) и Byte_u(v, n) являются корректными l-значениями. Следовательно, им можно присвоить значение, что приведёт к изменению значения v на месте. Присвоение непосредственно Field(v, n) требует осторожности, чтобы не запутать сборщик мусора (см. ниже).
22.4.4 Выделение блоков
Простой интерфейс
- Atom(t) возвращает «атом» (блок нулевого размера) с тэгом t. Блоки нулевого размера предварительно выделены вне кучи. Неправильно пытаться выделить блок нулевого размера с помощью следующих функций. Например, Atom(0) представляет пустой массив.
- caml_alloc(n, t) возвращает свежий блок размера n с тэгом t. Если t меньше No_scan_tag, то поля блока инициализируются допустимым значением для удовлетворения ограничений сборки мусора.
- caml_alloc_tuple(n) возвращает свежий блок размера n слов с тэгом 0.
- caml_alloc_string(n) возвращает значение последовательности байтов (или строки) длиной n байтов. Последовательность изначально содержит неинициализированные байты.
- caml_alloc_initialized_string(n, p) возвращает значение последовательности байтов (или строки) длиной n байтов. Значение инициализируется из n байтов, начиная с адреса p.
- caml_copy_string(s) возвращает строковое или байтовое значение последовательности, содержащее копию нуль-терминированной строки C s (a char *).
- caml_copy_double(d) возвращает значение с плавающей точкой, инициализированное с double d.
- caml_copy_int32(i), caml_copy_int64(i) и caml_copy_nativeint(i) возвращают значение типа OCaml int32, int64 и nativeint соответственно, инициализированные целым числом i.
- caml_alloc_array(f, a) выделяет массив значений, вызывая функцию f над каждым элементом входного массива a для его преобразования в значение. Массив a представляет собой массив указателей, завершаемый нулевым указателем. Функция f получает каждый указатель в качестве аргумента и возвращает значение. Блок с тэгом ноль, возвращаемый alloc_array(f, a), заполняется значениями, возвращаемыми последовательными вызовами f. (Эта функция не должна использоваться для построения массива чисел с плавающей точкой.)
- caml_copy_string_array(p) выделяет массив строк или последовательностей байтов, скопированных из указателя на массив строк p (a char **). p должен быть нуль-терминированным.
- caml_alloc_float_array(n) выделяет массив чисел с плавающей точкой размера n. Массив изначально содержит неинициализированные значения.
- caml_alloc_unboxed(v) возвращает значение (любого типа без упаковки), поле которого является значением v.
- caml_alloc_boxed(v) выделяет и возвращает значение (любого упакованного типа), поле которого является значением v.
- caml_alloc_unboxable(v) вызывает либо caml_alloc_unboxed, либо caml_alloc_boxed в соответствии с исходной представленностью типов без упаковки в текущей версии OCaml.
- caml_alloc_some(v) выделяет блок, представляющий Some(v).
Интерфейс низкого уровня
Следующие функции немного эффективнее, чем caml_alloc, но также намного сложнее в использовании.
С точки зрения функций выделения, блоки делятся в зависимости от их размера на блоки нулевого размера, малые блоки (с размером меньше или равным Max_young_wosize) и большие блоки (с размером больше Max_young_wosize). Константа Max_young_wosize объявлена в файле заголовков mlvalues.h. Гарантируется, что она будет по крайней мере 64 (слова), так что любой блок с постоянным размером, меньшим или равным 64, можно считать малым. Для блоков, размер которых вычисляется во время выполнения, размер необходимо сравнить с Max_young_wosize, чтобы определить правильную процедуру выделения.
- caml_alloc_small(n, t) возвращает свежий малый блок размера n ≤ Max_young_wosize слов с тэгом t. Если этот блок является структурированным блоком (т.е. если t < No_scan_tag), то поля блока (изначально содержащие мусор) должны быть инициализированы допустимыми значениями (используя прямую присваивание полям блока) перед следующим выделением.
-
caml_alloc_shr(n, t) возвращает свежий блок размера n с тэгом t. Размер блока может быть больше
Max_young_wosize. (Он также может быть меньше, но в этом случае более эффективно вызвать caml_alloc_small вместо caml_alloc_shr.) Если этот блок является структурированным блоком (т.е. если t < No_scan_tag), то поля блока (изначально содержащие мусор) должны быть инициализированы допустимыми значениями (используя функцию caml_initialize, описанную ниже) перед следующим выделением.
22.4.5 Поднятие исключений
Предоставлены две функции для подъёма двух стандартных исключений:
-
caml_failwith(s), где s — нуль-терминированная строка C (с типом
char *), поднимает исключение Failure с аргументом s. -
caml_invalid_argument(s), где s — нуль-терминированная строка C (с типом
char *), поднимает исключение Invalid_argument с аргументом s.
Поднятие произвольных исключений из C более тонкое: идентификатор исключения динамически выделяется программой OCaml и, следовательно, должен передаваться функции C с помощью механизма регистрации, описанного ниже в разделе 22.7.3. После того, как идентификатор исключения получен в C, следующие функции фактически поднимают исключение:
- caml_raise_constant(id) вызывает исключение id без аргумента;
- caml_raise_with_arg(id, v) вызывает исключение id с аргументом значением OCaml v;
- caml_raise_with_args(id, n, v) вызывает исключение id с аргументами значениями OCaml v[0], …, v[n-1];
- caml_raise_with_string(id, s), где s — строка C с нулевым завершением, вызывает исключение id с копией строки C s в качестве аргумента.
22.5 Взаимодействие с сборщиком мусора
Неиспользуемые блоки в куче автоматически освобождаются сборщиком мусора. Это требует некоторого взаимодействия со C-кодом, который манипулирует блоками, выделенными в куче.
22.5.1 Простой интерфейс
Все макросы, описанные в этом разделе, объявлены в заголовочном файле memory.h.
Существует шесть макросов CAMLparam: CAMLparam0 до CAMLparam5, которые принимают от нуля до пяти аргументов соответственно. Если ваша функция имеет не более 5 параметров типа value, используйте соответствующие макросы с этими параметрами в качестве аргументов. Если ваша функция имеет более 5 параметров типа value, используйте CAMLparam5 с пятью из этих параметров и используйте один или несколько вызовов макросов CAMLxparam для оставшихся параметров (CAMLxparam1 до CAMLxparam5).
Макросы CAMLreturn, CAMLreturn0 и CAMLreturnT используются для замены ключевого слова C return. Каждое вхождение return x должно быть заменено на CAMLreturn (x), если x имеет тип value, или CAMLreturnT (t, x) (где t — тип x); каждое вхождение return без аргумента должно быть заменено на CAMLreturn0. Если ваша C-функция является процедурой (т. е. если она возвращает void), вы должны вставить CAMLreturn0 в конце (чтобы заменить неявный return C).
Примечание:
некоторые компиляторы C выдают ложные предупреждения об unused переменных caml__dummy_xxx при каждом использовании CAMLparam и CAMLlocal. Вы должны их игнорировать.
Пример:
void foo (value v1, value v2, value v3)
{
CAMLparam3 (v1, v2, v3);
...
CAMLreturn0;
}
Примечание:
если ваша функция является примитивом с более чем 5 аргументами для использования с исполняющей средой байткода, её аргументы не являются value и не должны быть объявлены (они имеют типы value * и int).
Макросы CAMLlocal1 до CAMLlocal5 объявляют и инициализируют от одной до пяти локальных переменных типа value. Имена переменных указываются в качестве аргументов макросам. CAMLlocalN(x, n) объявляет и инициализирует локальную переменную типа value [n]. Если у вас больше 5 локальных переменных, вы можете использовать несколько вызовов этих макросов.
Пример:
CAMLprim value bar (value v1, value v2, value v3)
{
CAMLparam3 (v1, v2, v3);
CAMLlocal1 (result);
result = caml_alloc (3, 0);
...
CAMLreturn (result);
}
Store_field (b, n, v) сохраняет значение v в поле с номером n значения b, которое должно быть блоком (т. е. Is_block(b) должно быть истинным).
Пример:
CAMLprim value bar (value v1, value v2, value v3)
{
CAMLparam3 (v1, v2, v3);
CAMLlocal1 (result);
result = caml_alloc (3, 0);
Store_field (result, 0, v1);
Store_field (result, 1, v2);
Store_field (result, 2, v3);
CAMLreturn (result);
}
Предупреждение:
Первый аргумент Store_field и Store_double_field должен быть переменной, объявленной с помощью CAMLparam* или параметром, объявленным с помощью CAMLlocal*, чтобы гарантировать, что сборка мусора, вызванная вычислением других аргументов, не сделает первый аргумент недействительным после его вычисления.
Использование с CAMLlocalN:
Массивы значений, объявленные с помощью CAMLlocalN, не должны записываться с помощью Store_field. Вместо этого используйте обычную синтаксическую конструкцию массивов C.
Это же относится к любой памяти вне кучи OCaml, которая содержит значение и не гарантирует, что она будет достижима — до тех пор, пока она содержит такое значение — из другой зарегистрированной глобальной переменной или местоположения, локальной переменной, объявленной с помощью CAMLlocal, или параметра функции, объявленного с помощью CAMLparam.
Регистрация глобальной переменной v достигается вызовом caml_register_global_root(&v) непосредственно перед или после того, как в v будет впервые сохранено корректное значение; аналогично, регистрация произвольного местоположения p достигается вызовом caml_register_global_root(p).
Необходимо воздерживаться от вызова функций или макросов OCaml во время регистрации и хранения значения. Также запрещено хранить в переменной v (аналогично, в местоположении p) значения, которые не являются допустимыми.
Регистрация вызывает обновление содержимого переменной или ячейки памяти сборщиком мусора всякий раз, когда значение в такой переменной или ячейке перемещается в куче OCaml. При наличии потоков следует позаботиться о надлежащей синхронизации с исполняемой средой OCaml, чтобы избежать гонки с сборщиком мусора при чтении или записи значения. (См. раздел 22.12.2.)
Зарегистрированную глобальную переменную v можно дезактивировать, вызвав caml_remove_global_root(&v).
Если содержимое глобальной переменной v редко изменяется после регистрации, лучшая производительность может быть достигнута путем вызова caml_register_generational_global_root(&v) для регистрации v (после ее инициализации допустимым значением value, но до любого выделения или вызова функций GC) и caml_remove_generational_global_root(&v) для дезактивации. В этом случае вы не должны изменять значение v напрямую, а должны использовать caml_modify_generational_global_root(&v,x) для установки его в x. Сборщик мусора использует гарантию, что v не изменяется между вызовами caml_modify_generational_global_root, чтобы реже его сканировать. Это улучшает производительность, если модификации v происходят реже, чем незначительные сборки.
Примечание:
Макросы CAML используют идентификаторы (локальные переменные, идентификаторы типов, метки структур), которые начинаются с caml__. Не используйте в своих программах идентификаторы, начинающиеся с caml__.
22.5.2 Низкоуровневый интерфейс
Теперь мы дадим правила GC, соответствующие функциям низкоуровневого выделения caml_alloc_small и caml_alloc_shr. Вы можете игнорировать эти правила, если будете придерживаться упрощенной функции выделения caml_alloc.
Field(v, n) = vn;
Если блок был выделен с помощью caml_alloc_shr, заполнение выполняется с помощью функции caml_initialize:
caml_initialize(&Field(v, n), vn);
Следующее выделение может вызвать сборку мусора. Сборщик мусора предполагает, что все структурированные блоки содержат корректные значения. Недавно созданные блоки содержат случайные данные, которые, как правило, не представляют собой корректные значения.
Если вам действительно нужно выделить память до того, как поля получат свои окончательные значения, сначала инициализируйте ее постоянным значением (например, Val_unit), затем выделите память, а затем измените поля на правильное значение (см. правило 6).
Field(v, n) = w;
безопасно только если v — это блок, только что выделенный функцией caml_alloc_small; то есть если выделение не происходило между выделением v и присваиванием полю. Во всех остальных случаях никогда не присваивайте напрямую. Если блок был только что выделен с помощью caml_alloc_shr, используйте caml_initialize для присваивания значения полю в первый раз:
caml_initialize(&Field(v, n), w);
В противном случае вы обновляете поле, которое ранее содержало корректное значение; затем вызовите функцию caml_modify:
caml_modify(&Field(v, n), w);
Чтобы проиллюстрировать вышеуказанные правила, вот функция C, которая создает и возвращает список, содержащий два целых числа, заданных в качестве параметров. Сначала мы напишем ее с использованием упрощенных функций выделения:
value alloc_list_int(int i1, int i2)
{
CAMLparam0 ();
CAMLlocal2 (result, r);
r = caml_alloc(2, 0); /* Allocate a cons cell */
Store_field(r, 0, Val_int(i2)); /* car = the integer i2 */
Store_field(r, 1, Val_int(0)); /* cdr = the empty list [] */
result = caml_alloc(2, 0); /* Allocate the other cons cell */
Store_field(result, 0, Val_int(i1)); /* car = the integer i1 */
Store_field(result, 1, r); /* cdr = the first cons cell */
CAMLreturn (result);
}
Здесь регистрация result строго не нужна, поскольку после получения значения не происходит выделения памяти, но проще и безопаснее просто зарегистрировать все локальные переменные, имеющие тип value.
Вот та же функция, написанная с использованием низкоуровневых функций выделения. Мы замечаем, что ячейки cons являются небольшими блоками и могут быть выделены с помощью caml_alloc_small, и заполнены путем прямого присваивания в их полях.
value alloc_list_int(int i1, int i2)
{
CAMLparam0 ();
CAMLlocal2 (result, r);
r = caml_alloc_small(2, 0); /* Allocate a cons cell */
Field(r, 0) = Val_int(i2); /* car = the integer i2 */
Field(r, 1) = Val_int(0); /* cdr = the empty list [] */
result = caml_alloc_small(2, 0); /* Allocate the other cons cell */
Field(result, 0) = Val_int(i1); /* car = the integer i1 */
Field(result, 1) = r; /* cdr = the first cons cell */
CAMLreturn (result);
}
В двух примерах выше список строится снизу вверх. Вот альтернативный способ, который происходит сверху вниз. Он менее эффективен, но иллюстрирует использование caml_modify.
value alloc_list_int(int i1, int i2)
{
CAMLparam0 ();
CAMLlocal2 (tail, r);
r = caml_alloc_small(2, 0); /* Allocate a cons cell */
Field(r, 0) = Val_int(i1); /* car = the integer i1 */
Field(r, 1) = Val_int(0); /* A dummy value
tail = caml_alloc_small(2, 0); /* Allocate the other cons cell */
Field(tail, 0) = Val_int(i2); /* car = the integer i2 */
Field(tail, 1) = Val_int(0); /* cdr = the empty list [] */
caml_modify(&Field(r, 1), tail); /* cdr of the result = tail */
CAMLreturn (r);
}
Было бы неправильно выполнить Field(r, 1) = tail напрямую, поскольку выделение tail произошло с момента выделения r.
22.5.3 Необработанные действия и асинхронные исключения
Начиная с версии 4.10, функция выделения гарантированно не вызывает никакие обратные вызовы OCaml из C, включая финализаторы и обработчики сигналов, а вместо этого откладывает их выполнение.
Функция caml_process_pending_actions из <caml/signals.h> выполняет все ожидающие обработчики сигналов и финализаторы, обратные вызовы Memprof, запрошенные незначительные и основные сборки мусора. В частности, она может поднимать асинхронные исключения. Рекомендуется регулярно вызывать ее в безопасных точках внутри длительно выполняющихся неблокирующих кодов C.
Предоставляется вариант caml_process_pending_actions_exn, который возвращает исключение вместо непосредственного поднятия его в код OCaml. Его результат должен быть проверен с помощью Is_exception_result, за которым следует Extract_exception, если это необходимо. Обычно он используется для очистки перед повторным подъемом:
CAMLlocal1(exn);
...
exn = caml_process_pending_actions_exn();
if(Is_exception_result(exn)) {
exn = Extract_exception(exn);
...cleanup...
caml_raise(exn);
}
Правильное использование исключительного возврата, особенно при наличии сборки мусора, подробно описано в разделе 22.7.1.
22.6 Полный пример
Этот раздел описывает, как функции из библиотеки Unix curses могут быть доступны для программ OCaml. Прежде всего, вот интерфейс curses.ml, который объявляет примитивы и типы данных curses:
(* File curses.ml -- declaration of primitives and data types *)
type window (* The type "window" remains abstract *)
external initscr: unit -> window = "caml_curses_initscr"
external endwin: unit -> unit = "caml_curses_endwin"
external refresh: unit -> unit = "caml_curses_refresh"
external wrefresh : window -> unit = "caml_curses_wrefresh"
external newwin: int -> int -> int -> int -> window = "caml_curses_newwin"
external addch: char -> unit = "caml_curses_addch"
external mvwaddch: window -> int -> int -> char -> unit = "caml_curses_mvwaddch"
external addstr: string -> unit = "caml_curses_addstr"
external mvwaddstr: window -> int -> int -> string -> unit
= "caml_curses_mvwaddstr"
(* lots more omitted *)
Для компиляции этого интерфейса:
ocamlc -c curses.ml
Для реализации этих функций нам просто нужно предоставить код-заглушку; основные функции уже реализованы в библиотеке curses. Файл кода-заглушки curses_stubs.c выглядит следующим образом:
/* File curses_stubs.c -- stub code for curses */
#include <curses.h>
#include <caml/mlvalues.h>
#include <caml/memory.h>
#include <caml/alloc.h>
#include <caml/custom.h>
/* Encapsulation of opaque window handles (of type WINDOW *)
as OCaml custom blocks. */
static struct custom_operations curses_window_ops = {
"fr.inria.caml.curses_windows",
custom_finalize_default,
custom_compare_default,
custom_hash_default,
custom_serialize_default,
custom_deserialize_default,
custom_compare_ext_default,
custom_fixed_length_default
};
/* Accessing the WINDOW * part of an OCaml custom block */
#define Window_val(v) (*((WINDOW **) Data_custom_val(v)))
/* Allocating an OCaml custom block to hold the given WINDOW * */
static value alloc_window(WINDOW * w)
{
value v = caml_alloc_custom(&curses_window_ops, sizeof(WINDOW *), 0, 1);
Window_val(v) = w;
return v;
}
CAMLprim value caml_curses_initscr(value unit)
{
CAMLparam1 (unit);
CAMLreturn (alloc_window(initscr()));
}
CAMLprim value caml_curses_endwin(value unit)
{
CAMLparam1 (unit);
endwin();
CAMLreturn (Val_unit);
}
CAMLprim value caml_curses_refresh(value unit)
{
CAMLparam1 (unit);
refresh();
CAMLreturn (Val_unit);
}
CAMLprim value caml_curses_wrefresh(value win)
{
CAMLparam1 (win);
wrefresh(Window_val(win));
CAMLreturn (Val_unit);
}
CAMLprim value caml_curses_newwin(value nlines, value ncols, value x0, value y0)
{
CAMLparam4 (nlines, ncols, x0, y0);
CAMLreturn (alloc_window(newwin(Int_val(nlines), Int_val(ncols),
Int_val(x0), Int_val(y0))));
}
CAMLprim value caml_curses_addch(value c)
{
CAMLparam1 (c);
addch(Int_val(c)); /* Characters are encoded like integers */
CAMLreturn (Val_unit);
}
CAMLprim value caml_curses_mvwaddch(value win, value x, value y, value c)
{
CAMLparam4 (win, x, y, c);
mvwaddch(Window_val(win), Int_val(x), Int_val(y), Int_val(c));
CAMLreturn (Val_unit);
}
CAMLprim value caml_curses_addstr(value s)
{
CAMLparam1 (s);
addstr(String_val(s));
CAMLreturn (Val_unit);
}
CAMLprim value caml_curses_mvwaddstr(value win, value x, value y, value s)
{
CAMLparam4 (win, x, y, s);
mvwaddstr(Window_val(win), Int_val(x), Int_val(y), String_val(s));
CAMLreturn (Val_unit);
}
/* This goes on for pages. */
Файл curses_stubs.c можно скомпилировать с помощью:
cc -c -I`ocamlc -where` curses_stubs.c
или, еще проще,
ocamlc -c curses_stubs.c
(При передаче файла .c команда ocamlc просто вызывает компилятор C с правильным параметром -I.)
Теперь вот пример программы OCaml prog.ml, которая использует модуль curses:
(* File prog.ml -- main program using curses *) open Curses;; let main_window = initscr () in let small_window = newwin 10 5 20 10 in mvwaddstr main_window 10 2 "Hello"; mvwaddstr small_window 4 3 "world"; refresh(); Unix.sleep 5; endwin()
Для компиляции и компоновки этой программы выполните:
ocamlc -custom -o prog unix.cma curses.cmo prog.ml curses_stubs.o -cclib -lcurses
(В некоторых системах может потребоваться использовать -cclib -lcurses -cclib -ltermcap или -cclib -ltermcap вместо -cclib -lcurses.)
22.7 Дополнительная тема: обратные вызовы из C в OCaml
До сих пор мы описывали, как вызывать функции C из OCaml. В этом разделе мы покажем, как функции C могут вызывать функции OCaml, либо в качестве обратных вызовов (OCaml вызывает C, который вызывает OCaml), либо с основной программой, написанной на C.
22.7.1 Применение замыканий OCaml из C
Функции C могут применять значения функций OCaml (замыкания) к значениям OCaml. Для выполнения приложений предоставляются следующие функции:
- caml_callback(f, a) применяет функциональное значение f к значению a и возвращает значение, возвращенное f.
- caml_callback2(f, a, b) применяет функциональное значение f (которое предполагается являющимся функцией OCaml с двумя аргументами, использующей каррирование) к a и b.
- caml_callback3(f, a, b, c) применяет функциональное значение f (функция OCaml с тремя аргументами, использующая каррирование) к a, b и c.
- caml_callbackN(f, n, args) применяет функциональное значение f к n аргументам, содержащимся в массиве C значений args.
Если функция f не возвращает результат, а вызывает исключение, которое выходит за пределы области применения, то это исключение распространяется на следующий окружающий фрагмент OCaml-кода, пропуская C-код. То есть, если функция OCaml f вызывает C-функцию g, которая вызывает функцию OCaml h, которая вызывает исключение, то выполнение g прерывается, и исключение распространяется обратно в f.
Если C-код хочет перехватить исключения, выходящие за пределы функции OCaml, он может использовать функции caml_callback_exn, caml_callback2_exn, caml_callback3_exn, caml_callbackN_exn. Эти функции принимают те же аргументы, что и их аналоги без _exn, но перехватывают выходящие исключения и возвращают их в C-код. Возвращаемое значение v функций caml_callback*_exn должно быть проверено с помощью макроса Is_exception_result(v). Если макрос возвращает «false», исключение не произошло, и v — это значение, возвращенное функцией OCaml. Если Is_exception_result(v) возвращает «true», исключение вышло за пределы, и его значение (описатель исключения) можно восстановить с помощью Extract_exception(v).
Предупреждение:
Если функция OCaml возвратилась с исключением, Extract_exception должна быть применена к результату исключения до вызова функции, которая может вызвать сборку мусора. В противном случае, если v доступен во время сбора мусора, среда выполнения может аварийно завершиться, так как v не содержит действительного значения.
Пример:
CAMLprim value call_caml_f_ex(value closure, value arg)
{
CAMLparam2(closure, arg);
CAMLlocal2(res, tmp);
res = caml_callback_exn(closure, arg);
if(Is_exception_result(res)) {
res = Extract_exception(res);
tmp = caml_alloc(3, 0); /* Safe to allocate: res contains valid value. */
...
}
CAMLreturn (res);
}
22.7.2 Получение или регистрация замыканий OCaml для использования в C-функциях
Существует два способа получения значений функций OCaml (замыканий), которые передаются в функции callback, описанные выше. Один способ — передать функцию OCaml в качестве аргумента примитивной функции. Например, если OCaml-код содержит объявление
external apply : ('a -> 'b) -> 'a -> 'b = "caml_apply"
соответствующий C-заглушка может быть написан следующим образом:
CAMLprim value caml_apply(value vf, value vx)
{
CAMLparam2(vf, vx);
CAMLlocal1(vy);
vy = caml_callback(vf, vx);
CAMLreturn(vy);
}
Другой вариант — использовать механизм регистрации, предоставляемый OCaml. Этот механизм регистрации позволяет OCaml-коду регистрировать функции OCaml под каким-либо глобальным именем, а C-коду извлекать соответствующее замыкание по этому глобальному имени.
В OCaml-стороне регистрация выполняется путем вычисления Callback.register n v. Здесь n — глобальное имя (любая строка), а v — значение OCaml. Например:
let f x = print_string "f is applied to "; print_int x; print_newline()
let _ = Callback.register "test function" f
В C-стороне указатель на значение, зарегистрированное под именем n, получается путем вызова caml_named_value(n). Затем возвращенный указатель должен быть разыменован, чтобы восстановить фактическое значение OCaml. Если значение не зарегистрировано под именем n, возвращается нулевой указатель. Например, вот C-обертка, которая вызывает функцию OCaml f выше:
void call_caml_f(int arg)
{
caml_callback(*caml_named_value("test function"), Val_int(arg));
}
Указатель, возвращаемый caml_named_value, является постоянным и может безопасно кэшироваться в C-переменной для избежания многократных поисков имени. Указанное значение нельзя изменить из C. Однако оно может измениться во время сбора мусора, поэтому его всегда необходимо пересчитывать в момент использования. Вот более эффективный вариант call_caml_f выше, который вызывает caml_named_value только один раз:
void call_caml_f(int arg)
{
static const value * closure_f = NULL;
if (closure_f == NULL) {
/* First time around, look up by name */
closure_f = caml_named_value("test function");
}
caml_callback(*closure_f, Val_int(arg));
}
22.7.3 Регистрация исключений OCaml для использования в C-функциях
Описанный выше механизм регистрации также может использоваться для передачи идентификаторов исключений из OCaml в C. OCaml-код регистрирует исключение, вычисляя Callback.register_exception n exn, где n — произвольное имя, а exn — значение исключения, подлежащего регистрации. Например:
exception Error of string
let _ = Callback.register_exception "test exception" (Error "any string")
Затем C-код может восстановить идентификатор исключения с помощью caml_named_value и передать его в качестве первого аргумента функциям raise_constant, raise_with_arg и raise_with_string (описанные в разделе 22.4.5) для фактического возбуждения исключения. Например, вот функция C, которая вызывает исключение Error с заданным аргументом:
void raise_error(char * msg)
{
caml_raise_with_string(*caml_named_value("test exception"), msg);
}
22.7.4 Главная программа на C
В обычном режиме работы смешанная OCaml/C-программа запускается путем выполнения OCaml-кода инициализации, которая затем может продолжить вызывать C-функции. Мы говорим, что основной программой является OCaml-код. В некоторых приложениях желательно, чтобы C-код выполнял роль основной программы, вызывая OCaml-функции по мере необходимости. Это можно сделать следующим образом:
- C-часть программы должна предоставить функцию main, которая переопределит стандартную функцию main, предоставляемую системой выполнения OCaml. Выполнение начнется в пользовательской функции main, как и в обычной C-программе.
- В какой-то момент C-код должен вызвать caml_main(argv) для инициализации OCaml-кода. Аргумент argv — это C-массив строк (тип char **), завершаемый указателем NULL, который представляет аргументы командной строки, переданные в качестве второго аргумента функции main. Массив OCaml Sys.argv будет инициализирован из этого параметра. Для компилятора байткода, argv[0] и argv[1] также используются для поиска файла, содержащего байткод.
- Вызов caml_main инициализирует систему выполнения OCaml, загружает байткод (в случае компилятора байткода) и выполняет код инициализации OCaml-программы. Как правило, этот код инициализации регистрирует функции обратного вызова, используя Callback.register. После завершения кода инициализации OCaml управление возвращается в C-код, который вызвал caml_main.
- Затем C-код может вызывать OCaml-функции, используя механизм обратного вызова (см. раздел 22.7.1).
22.7.5 Встраивание OCaml-кода в C-код
Компилятор байткода в режиме пользовательской среды выполнения (ocamlc -custom) обычно добавляет байткод к исполняемому файлу, содержащему пользовательскую среду выполнения. Это имеет два следствия. Во-первых, окончательный этап компоновки должен выполняться с помощью ocamlc. Во-вторых, библиотека времени выполнения OCaml должна уметь находить имя исполняемого файла из аргументов командной строки. При использовании caml_main(argv), как в разделе 22.7.4, это означает, что argv[0] или argv[1] должны содержать имя исполняемого файла.
END_OF_DOCUMENT_MARKERАльтернативный способ — встраивание байткода в код на C. Для этой цели предназначены опции -output-obj и -output-complete-obj компилятора ocamlc. Они заставляют компилятор ocamlc сгенерировать файл C-объекта (.o-файл, .obj в Windows), содержащий байткод для OCaml-части программы, а также функцию caml_startup. Файл C-объекта, сгенерированный с помощью ocamlc -output-complete-obj, также содержит библиотеки времени выполнения и автосвязи. Полученный C-объектный файл, созданный с помощью ocamlc -output-obj или ocamlc -output-complete-obj, затем может быть связан с кодом на C с помощью стандартного компилятора C или сохранён в C-библиотеке.
Функция caml_startup должна вызываться из основной C-программы для инициализации OCaml-времени выполнения и выполнения OCaml-кода инициализации. Как и caml_main, она принимает один параметр argv, содержащий параметры командной строки. В отличие от caml_main, этот параметр argv используется только для инициализации Sys.argv, но не для поиска имени исполняемого файла.
Функция caml_startup вызывает обработчик необработанных исключений (или входит в отладчик, если выполнение происходит в режиме ocamldebug), если исключение вылетает из инициализатора модуля верхнего уровня. Такие исключения можно перехватывать в коде C, используя функцию caml_startup_exn и проверяя результат с помощью Is_exception_result (за которым следует Extract_exception, если необходимо).
Опции -output-obj и -output-complete-obj также можно использовать для получения файла исходного кода на C. Более интересно, что эти опции могут напрямую сгенерировать разделяемую библиотеку (.so-файл, .dll в Windows), содержащую OCaml-код, OCaml-систему времени выполнения и любой другой статический C-код, переданный в ocamlc (.o, .a соответственно, .obj, .lib). Это использование -output-obj и -output-complete-obj очень похоже на обычный этап связывания, но вместо создания основной программы, автоматически запускающей OCaml-код, оно генерирует разделяемую библиотеку, которая может запускать OCaml-код по требованию. Три возможных варианта поведения -output-obj и -output-complete-obj (для создания файла исходного кода C .c, файла C-объекта .o, разделяемой библиотеки .so) выбираются в соответствии с расширением результирующего файла (указанного с помощью -o).
Компилятор кода нативного кода ocamlopt также поддерживает опции -output-obj и -output-complete-obj, вызывая генерацию файла C-объекта или разделяемой библиотеки, содержащей код нативного кода для всех OCaml-модулей в командной строке, а также OCaml-код запуска. Инициализация выполняется путем вызова caml_startup (или caml_startup_exn), как и в случае компилятора байткода. Файл, созданный с помощью ocamlopt -output-complete-obj, также содержит библиотеки времени выполнения и автосвязи.
На заключительном этапе связывания, помимо объектного файла, созданного с помощью -output-obj, вам необходимо предоставить OCaml-библиотеку времени выполнения (libcamlrun.a для байткода, libasmrun.a для кода нативного кода), а также все C-библиотеки, необходимые OCaml-библиотекам, которые используются. Например, предположим, что OCaml-часть вашей программы использует библиотеку Unix. С ocamlc вы должны сделать:
ocamlc -output-obj -o camlcode.o unix.cma other .cmo and .cma files
cc -o myprog C objects and libraries \
camlcode.o -L‘ocamlc -where‘ -lunix -lcamlrun
С ocamlopt вы должны сделать:
ocamlopt -output-obj -o camlcode.o unix.cmxa other .cmx and .cmxa files
cc -o myprog C objects and libraries \
camlcode.o -L‘ocamlc -where‘ -lunix -lasmrun
На заключительном этапе связывания, помимо объектного файла, созданного с помощью -output-complete-obj, вам нужно будет предоставить только C-библиотеки, необходимые OCaml-времени выполнения.
Например, предположим, что OCaml-часть вашей программы использует библиотеку Unix. С ocamlc вы должны сделать:
ocamlc -output-complete-obj -o camlcode.o unix.cma other .cmo and .cma files
cc -o myprog C objects and libraries \
camlcode.o C libraries required by the runtime, eg -lm -ldl -lcurses -lpthread
С ocamlopt вы должны сделать:
ocamlopt -output-complete-obj -o camlcode.o unix.cmxa other .cmx and .cmxa files
cc -o myprog C objects and libraries \
camlcode.o C libraries required by the runtime, eg -lm -ldl
Предупреждение:
На некоторых платформах на заключительном этапе связывания, объединяющем объектный файл, созданный с помощью опций -output-obj и -output-complete-obj, и остальную часть программы, требуются специальные опции. Эти опции показаны в файле конфигурации Makefile.config, сгенерированном во время компиляции OCaml, как переменная OC_LDFLAGS.
- Windows с компилятором MSVC: объектный файл, созданный OCaml, был скомпилирован со флагом /MD, и поэтому все другие объектные файлы, связанные с ним, также должны быть скомпилированы с /MD.
- Другие системы: вам может потребоваться добавить одну или обе опции -lm и -ldl в зависимости от вашей ОС и C-компилятора.
Стеки следов.
Когда OCaml-байткод, созданный с помощью ocamlc -g, встраивается в C-программу, информация об отладке не включается, и поэтому невозможно вывести стеки следов при необработанных исключениях. Это не так, когда код нативного кода, созданный с помощью ocamlopt -g, встраивается в C-программу: информация о стеке следов доступна, но механизм следов стека необходимо включить программно. Это можно сделать со стороны OCaml, вызвав Printexc.record_backtrace true в инициализации одного из OCaml-модулей. Это также можно сделать со стороны C, вызвав caml_record_backtraces(1); в OCaml-C-связывающем коде. (caml_record_backtraces объявлен в backtrace.h)
Выгрузка времени выполнения.
В случае, если разделяемая библиотека, созданная с помощью -output-obj, должна загружаться и выгружаться многократно одним процессом, необходимо позаботиться о явной выгрузке OCaml-времени выполнения, чтобы избежать утечек различных системных ресурсов.
С версии 4.05, функция caml_shutdown может использоваться для плавного завершения работы времени выполнения, что эквивалентно следующему:
- Выполнение функций, зарегистрированных с помощью Stdlib.at_exit.
- Вызов финализации выделенных пользовательских блоков (см. раздел 22.9). Например, Stdlib.in_channel и Stdlib.out_channel представлены пользовательскими блоками, которые содержат дескрипторы файлов, которые необходимо освободить.
- Выгрузка зависимых разделяемых библиотек, загруженных временем выполнения, включая плагины dynlink.
- Освобождение блоков памяти, выделенных временем выполнения с помощью malloc. В C-примитивах рекомендуется использовать функции caml_stat_* из memory.h для управления статическими (то есть не перемещаемыми) блоками памяти кучи, так как все блоки, выделенные этими функциями, автоматически освобождаются caml_shutdown. Для обеспечения совместимости со старыми C-заглушками, которые неправильно использовали caml_stat_*, это поведение включается только если время выполнения запущено со специализированной функцией caml_startup_pooled.
Поскольку разделяемая библиотека может иметь несколько клиентов одновременно, для удобства caml_startup (и caml_startup_pooled) могут быть вызваны несколько раз, при условии, что каждый такой вызов сопровождается соответствующим вызовом caml_shutdown (вложенным образом). Время выполнения будет выгружено, как только не останется незакрытых вызовов caml_startup.
После выгрузки времени выполнения его нельзя запустить снова без повторной загрузки разделяемой библиотеки и повторной инициализации её статических данных. Поэтому на данный момент этот механизм полезен только для создания перезагружаемых разделяемых библиотек.
Обработка сигналов Unix.
В зависимости от целевой платформы и операционной системы, система времени выполнения нативного кода может установить обработчики сигналов для одного или нескольких сигналов SIGSEGV, SIGTRAP и SIGFPE, когда вызывается caml_startup, и сбросить эти сигналы до их стандартного поведения при вызове caml_shutdown. Основная программа, написанная на C, не должна пытаться обрабатывать эти сигналы самостоятельно.
22.8 Расширенный пример с обратными вызовами
Этот раздел иллюстрирует возможности обратных вызовов, описанные в разделе 22.7. Мы собираемся упаковать некоторые функции OCaml таким образом, чтобы их можно было связать с кодом C и вызывать из C, как любые функции C. Функции OCaml определены в следующем исходном файле OCaml mod.ml:
(* File mod.ml -- some "useful" OCaml functions *) let rec fib n = if n < 2 then 1 else fib(n-1) + fib(n-2) let format_result n = Printf.sprintf "Result is: %d\n" n (* Export those two functions to C *) let _ = Callback.register "fib" fib let _ = Callback.register "format_result" format_result
Вот фрагмент кода на C для вызова этих функций из C:
/* File modwrap.c -- wrappers around the OCaml functions */
#include <stdio.h>
#include <string.h>
#include <caml/mlvalues.h>
#include <caml/callback.h>
int fib(int n)
{
static const value * fib_closure = NULL;
if (fib_closure == NULL) fib_closure = caml_named_value("fib");
return Int_val(caml_callback(*fib_closure, Val_int(n)));
}
char * format_result(int n)
{
static const value * format_result_closure = NULL;
if (format_result_closure == NULL)
format_result_closure = caml_named_value("format_result");
return strdup(String_val(caml_callback(*format_result_closure, Val_int(n))));
/* We copy the C string returned by String_val to the C heap
so that it remains valid after garbage collection. */
}
Теперь мы компилируем код OCaml в объектный файл C и помещаем его в библиотеку C вместе с фрагментом кода в modwrap.c и системой времени выполнения OCaml:
ocamlc -custom -output-obj -o modcaml.o mod.ml
ocamlc -c modwrap.c
cp `ocamlc -where`/libcamlrun.a mod.a && chmod +w mod.a
ar r mod.a modcaml.o modwrap.o
(Можно также использовать ocamlopt -output-obj вместо ocamlc -custom -output-obj. В этом случае замените libcamlrun.a (библиотека времени выполнения байткода) на libasmrun.a (библиотека времени выполнения нативного кода).)
Теперь мы можем использовать две функции fib и format_result в любой программе C, как обычные функции C. Просто помните, что нужно вызвать caml_startup (или caml_startup_exn) один раз перед этим.
/* File main.c -- a sample client for the OCaml functions */
#include <stdio.h>
#include <caml/callback.h>
extern int fib(int n);
extern char * format_result(int n);
int main(int argc, char ** argv)
{
int result;
/* Initialize OCaml code */
caml_startup(argv);
/* Do some computation */
result = fib(10);
printf("fib(10) = %s\n", format_result(result));
return 0;
}
Чтобы собрать всю программу, просто вызовите компилятор C следующим образом:
cc -o prog -I `ocamlc -where` main.c mod.a -lcurses
(На некоторых машинах вам может потребоваться использовать -ltermcap или -lcurses -ltermcap вместо -lcurses.)
22.9 Расширенная тема: пользовательские блоки
Блоки с тегом Custom_tag содержат как произвольные пользовательские данные, так и указатель на структуру C с типом struct custom_operations, которая связывает предоставленные пользователем функции финализации, сравнения, хеширования, сериализации и десериализации с этим блоком.
22.9.1 Структура struct custom_operations
Структура struct custom_operations определена в <caml/custom.h> и содержит следующие поля:
-
char *identifier
Нуль-терминированная строка символов, используемая в качестве идентификатора для операций сериализации и десериализации. -
void (*finalize)(value v)
Поле finalize содержит указатель на функцию C, которая вызывается, когда блок становится недоступным и его нужно освободить. Блок передается в качестве первого аргумента функции. Поле finalize также может иметь значение custom_finalize_default, чтобы указать, что функция финализации не ассоциирована с блоком. -
int (*compare)(value v1, value v2)
Поле compare содержит указатель на функцию C, которая вызывается всякий раз, когда два пользовательских блока сравниваются с помощью универсальных операторов сравнения OCaml (=, <>, <=, >=, <, > и compare). Функция C должна возвращать 0, если данные в двух блоках структурно равны, отрицательное целое число, если данные первого блока меньше данных второго блока, и положительное целое число, если данные первого блока больше данных второго блока.Поле compare может быть установлено в custom_compare_default; эта функция сравнения по умолчанию просто вызывает исключение Failure.
-
int (*compare_ext)(value v1, value v2)
(С версии 3.12.1) Поле compare_ext содержит указатель на функцию C, которая вызывается всякий раз, когда один пользовательский блок и одно целочисленное значение без упаковки сравниваются с помощью универсальных операторов сравнения OCaml (=, <>, <=, >=, <, > и compare). Как и в случае с полем compare, функция C должна возвращать 0, если оба аргумента структурно равны, отрицательное целое число, если первый аргумент меньше второго аргумента, и положительное целое число, если первый аргумент больше второго аргумента.Поле compare_ext может быть установлено в custom_compare_ext_default; эта функция сравнения по умолчанию просто вызывает исключение Failure.
-
intnat (*hash)(value v)
Поле hash содержит указатель на функцию C, которая вызывается всякий раз, когда к пользовательскому блоку применяется универсальный оператор хеширования OCaml (см. модуль Hashtbl). Функция C может возвращать произвольное целое число, представляющее значение хеша данных, содержащихся в заданном пользовательском блоке. Значение хеша должно быть совместимо с функцией compare в том смысле, что два структурно равных данных (то есть два пользовательских блока, для которых compare возвращает 0) должны иметь одинаковое значение хеша.Поле hash может быть установлено в custom_hash_default, в этом случае пользовательский блок игнорируется во время вычисления хеша.
-
void (*serialize)(value v, uintnat * bsize_32, uintnat * bsize_64)
Поле serialize содержит указатель на функцию C, которая вызывается всякий раз, когда пользовательский блок необходимо сериализовать (маршаллировать) с помощью функций OCaml output_value или Marshal.to_.... Для пользовательского блока эти функции сначала записывают идентификатор блока (как задано полем identifier) в выходной поток, а затем вызывают функцию serialize, предоставленную пользователем. Эта функция отвечает за запись данных, содержащихся в пользовательском блоке, используя функции serialize_..., определённые в <caml/intext.h> и перечисленные ниже. Функция serialize, предоставленная пользователем, должна затем сохранить в своих параметрах bsize_32 и bsize_64 размеры в байтах части данных пользовательского блока для архитектуры 32 бит и 64 бит соответственно.Поле serialize может быть установлено в custom_serialize_default, в этом случае при попытке сериализовать пользовательский блок генерируется исключение Failure.
-
uintnat (*deserialize)(void * dst)
Поле deserialize содержит указатель на функцию C, которая вызывается всякий раз, когда пользовательский блок с идентификатором identifier необходимо десериализовать (размаршаллировать) с помощью функций OCaml input_value или Marshal.from_.... Эта функция, предоставленная пользователем, отвечает за считывание данных, записанных операцией serialize, используя функции deserialize_..., определённые в <caml/intext.h> и перечисленные ниже. Она должна затем восстановить часть данных пользовательского блока и сохранить её по указателю, переданному в качестве аргумента dst. Наконец, она возвращает размер в байтах части данных пользовательского блока. Этот размер должен быть идентичен результату wsize_32 операции serialize для 32-битной архитектуры или wsize_64 для 64-битной архитектуры.Поле deserialize может быть установлено в custom_deserialize_default, чтобы указать, что десериализация не поддерживается. В этом случае не регистрируйте struct custom_operations с десериализатором, используя register_custom_operations (см. ниже).
-
const struct custom_fixed_length* fixed_length
(С версии 4.08.0) Обычно, в сериализованном выходе зарезервировано место для записи полей bsize_32 и bsize_64, возвращаемых функцией serialize. Однако для очень коротких пользовательских блоков это место может быть больше, чем сами данные! В целях экономии места, если функция serialize всегда возвращает одинаковые значения для bsize_32 и bsize_64, то эти значения могут быть указаны в структуре fixed_length, и не занимают места в сериализованном выходе.
Примечание: функции finalize, compare, hash, serialize и deserialize, прикреплённые к описателям пользовательских блоков, никогда не должны вызывать сборку мусора. Внутри этих функций не вызывайте ни одну из функций выделения OCaml и не делайте обратных вызовов в код OCaml. Не используйте CAMLparam для регистрации параметров этих функций и не используйте CAMLreturn для возврата результата.
22.9.2 Выделение пользовательских блоков
Пользовательские блоки должны выделяться с помощью caml_alloc_custom или caml_alloc_custom_mem:
возвращает свежий пользовательский блок с местом для size байт пользовательских данных, а связанные операции задаются ops (указатель на struct custom_operations, обычно статически выделяется как глобальная переменная C).
Два параметра used и max используются для управления скоростью сбора мусора, когда финализированный объект содержит указатели на ресурсы, находящиеся вне кучи. Как правило, инкрементальный основной коллектор OCaml регулирует свою скорость относительно скорости выделения памяти программой. Чем быстрее программа выделяет память, тем усерднее работает сборщик мусора, чтобы быстро освободить недоступные блоки и избежать большого количества «плавающего мусора» (неопределённых объектов, которые сборщик мусора ещё не собрал).
Обычно скорость выделения памяти измеряется подсчётом размера выделенных блоков в куче. Однако часто бывает, что финализированные объекты содержат указатели на блоки памяти вне кучи и другие ресурсы (например, дескрипторы файлов, растровые изображения X Windows и т. д.). Для этих блоков размер блоков в куче не является хорошей мерой количества ресурсов, выделенных программой.
Два аргумента used и max дают сборщику мусора представление о том, сколько ресурсов вне кучи потребляет финализированный блок, который выделяется: вы предоставляете количество ресурсов, выделенных для этого объекта, в качестве параметра used, и максимальное количество, которое вы хотите видеть в плавающем мусоре, в качестве параметра max. Единицы измерения произвольные: сборщик мусора заботится только о соотношении used / max.
Например, если вы выделяете финализированный блок, содержащий растровое изображение X Windows размером w на h пикселей, и не хотите иметь более 1 мегапикселя несобранных растровых изображений, укажите used = w * h и max = 1000000.
Другой способ описать эффект параметров used и max — в терминах полных циклов сбора мусора. Если вы выделяете много пользовательских блоков с used / max = 1 / N, сборщик мусора будет выполнять один полный цикл (осмотр каждого объекта в куче и вызов функций финализации для тех, которые недоступны) каждые N выделений. Например, если used = 1 и max = 1000, сборщик мусора будет выполнять один полный цикл по крайней мере каждые 1000 выделений пользовательских блоков.
Если ваши финализированные блоки не содержат указателей на ресурсы вне кучи, или предыдущее обсуждение вам не очень понятно, просто возьмите used = 0 и max = 1. Но если вы позже обнаружите, что функции финализации не вызываются «достаточно часто», рассмотрите возможность увеличения отношения used / max.
Используйте эту функцию, когда ваш пользовательский блок содержит только память вне кучи (память, выделенная с помощью malloc или caml_stat_alloc) и не содержит других ресурсов. used должно быть количеством байтов памяти вне кучи, которые хранятся в вашем пользовательском блоке. Эта функция работает так же, как caml_alloc_custom, за исключением того, что параметр max контролируется пользователем (через параметры custom_major_ratio, custom_minor_ratio и custom_minor_max_size) и пропорционален размерам кучи. Она доступна с версии OCaml 4.08.0.
22.9.3 Доступ к пользовательским блокам
К части данных пользовательского блока v можно получить доступ через указатель Data_custom_val(v). Этот указатель имеет тип void * и должен быть приведён к фактическому типу данных, хранящихся в пользовательском блоке.
Содержимое пользовательских блоков не сканируется сборщиком мусора и, следовательно, не должно содержать никаких указателей внутри кучи OCaml. Другими словами, никогда не храните OCaml value в пользовательском блоке и не используйте Field, Store_field и caml_modify для доступа к части данных пользовательского блока. Наоборот, в пользовательский блок можно поместить любую структуру данных C (не содержащую указателей на кучу).
22.9.4 Написание функций пользовательской сериализации и десериализации
Следующие функции, определённые в <caml/intext.h>, предназначены для записи и повторного чтения содержимого пользовательских блоков портативным способом. Эти функции обрабатывают преобразования порядка байтов, например, когда данные записываются на машине с порядком байтов little-endian и считываются на машине с порядком байтов big-endian.
| Функция | Действие |
| caml_serialize_int_1 | Запись целого числа размером 1 байт |
| caml_serialize_int_2 | Запись целого числа размером 2 байта |
| caml_serialize_int_4 | Запись целого числа размером 4 байта |
| caml_serialize_int_8 | Запись целого числа размером 8 байт |
| caml_serialize_float_4 | Запись числа с плавающей точкой размером 4 байта |
| caml_serialize_float_8 | Запись числа с плавающей точкой размером 8 байт |
| caml_serialize_block_1 | Запись массива величин размером 1 байт |
| caml_serialize_block_2 | Запись массива величин размером 2 байта |
| caml_serialize_block_4 | Запись массива величин размером 4 байта |
| caml_serialize_block_8 | Запись массива величин размером 8 байт |
| caml_deserialize_uint_1 | Чтение беззнакового целого числа размером 1 байт |
| caml_deserialize_sint_1 | Чтение знакового целого числа размером 1 байт |
| caml_deserialize_uint_2 | Чтение беззнакового целого числа размером 2 байта |
| caml_deserialize_sint_2 | Чтение знакового целого числа размером 2 байта |
| caml_deserialize_uint_4 | Чтение беззнакового целого числа размером 4 байта |
| caml_deserialize_sint_4 | Чтение знакового целого числа размером 4 байта |
| caml_deserialize_uint_8 | Чтение беззнакового целого числа размером 8 байт |
| caml_deserialize_sint_8 | Чтение знакового целого числа размером 8 байт |
| caml_deserialize_float_4 | Чтение числа с плавающей точкой размером 4 байта |
| caml_deserialize_float_8 | Чтение числа с плавающей точкой размером 8 байт |
| caml_deserialize_block_1 | Чтение массива величин размером 1 байт |
| caml_deserialize_block_2 | Чтение массива величин размером 2 байта |
| caml_deserialize_block_4 | Чтение массива величин размером 4 байта |
| caml_deserialize_block_8 | Чтение массива величин размером 8 байт |
| caml_deserialize_error | Вызов ошибки при десериализации; input_value или Marshal.from_... генерируют исключение Failure после очистки своих внутренних структур данных |
Функции сериализации прикреплены к пользовательским блокам, к которым они применяются. Очевидно, что функции десериализации не могут быть прикреплены таким образом, так как пользовательский блок ещё не существует, когда начинается десериализация! Таким образом, структуры struct custom_operations, содержащие функции десериализации, должны быть зарегистрированы в десериализаторе заранее с помощью функции register_custom_operations, объявленной в <caml/custom.h>. Десериализация выполняется путём считывания идентификатора из входного потока, выделения пользовательского блока заданного размера из входного потока, поиска зарегистрированных блоков struct custom_operation для блока с тем же идентификатором и вызова его функции deserialize для заполнения части данных пользовательского блока.
22.9.5 Выбор идентификаторов
Идентификаторы в struct custom_operations необходимо выбирать тщательно, поскольку они должны однозначно идентифицировать структуру данных для операций сериализации и десериализации. В частности, рекомендуется включать номер версии в идентификатор; таким образом, формат данных можно изменить позже, но при этом можно предоставить обратно совместимые функции десериализации.
Идентификаторы, начинающиеся с _ (символа подчеркивания), зарезервированы для системы времени выполнения OCaml; не используйте их для пользовательских данных. Рекомендуется использовать URL (http://mymachine.mydomain.com/mylibrary/version-number) или имя пакета в стиле Java (com.mydomain.mymachine.mylibrary.version-number) в качестве идентификаторов, чтобы свести к минимуму риск коллизий идентификаторов.
22.9.6 Блоки finalization
Пользовательские блоки обобщают блоки finalization, которые присутствовали в OCaml до версии 3.00. Для обратной совместимости формат пользовательских блоков совместим с форматом блоков finalization, и функция alloc_final по-прежнему доступна для выделения пользовательского блока с заданной функцией finalization, но с функциями сравнения, хеширования и сериализации по умолчанию. Функция caml_alloc_final(n, f, used, max) возвращает новый пользовательский блок размером n+1 слов с функцией finalization f. Первое слово зарезервировано для хранения пользовательских операций; остальные n слов доступны для ваших данных. Два параметра used и max используются для управления скоростью сбора мусора, как описано для caml_alloc_custom.
22.10 Дополнительная тема: Bigarrays и интерфейс OCaml-C
В этом разделе объясняется, как код C-заглушки, который связывает код C или Fortran с кодом OCaml, может использовать Bigarrays.
22.10.1 Файл заголовков
Файл заголовков <caml/bigarray.h> должен быть включен в файл C-заглушки. Он объявляет функции, константы и макросы, обсуждаемые ниже.
22.10.2 Доступ к OCaml bigarray из C или Fortran
Если v — значение OCaml value, представляющее Bigarray, выражение Caml_ba_data_val(v) возвращает указатель на часть данных массива. Этот указатель имеет тип void * и может быть преобразован к соответствующему типу C для массива (например, double [], char [][10] и т.д.).
Различные характеристики OCaml Bigarray можно получить из C следующим образом:
| Выражение C | Возвращает |
| Caml_ba_array_val(v)->num_dims | число измерений |
| Caml_ba_array_val(v)->dim[i] | i-е измерение |
| Caml_ba_array_val(v)->flags & BIGARRAY_KIND_MASK | тип элементов массива |
Тип элементов массива — одна из следующих констант:
| Константа | Тип элементов |
| CAML_BA_FLOAT32 | 32-битные числа с плавающей точкой одинарной точности |
| CAML_BA_FLOAT64 | 64-битные числа с плавающей точкой двойной точности |
| CAML_BA_SINT8 | 8-битные целые числа со знаком |
| CAML_BA_UINT8 | 8-битные целые числа без знака |
| CAML_BA_SINT16 | 16-битные целые числа со знаком |
| CAML_BA_UINT16 | 16-битные целые числа без знака |
| CAML_BA_INT32 | 32-битные целые числа со знаком |
| CAML_BA_INT64 | 64-битные целые числа со знаком |
| CAML_BA_CAML_INT | 31- или 63-битные целые числа со знаком |
| CAML_BA_NATIVE_INT | 32- или 64-битные целые числа (родные для платформы) |
| CAML_BA_COMPLEX32 | 32-битные комплексные числа одинарной точности |
| CAML_BA_COMPLEX64 | 64-битные комплексные числа двойной точности |
| CAML_BA_CHAR | 8-битные символы |
Предупреждение:
Caml_ba_array_val(v) всегда необходимо дереферировать немедленно и не сохранять нигде, включая локальные переменные. Она разрешает производный указатель: это не действительное значение OCaml, но указывает на область памяти, управляемую системой GC. По этой причине это значение не должно храниться в любой области памяти, которая может быть активна во время GC.
В следующем примере показано передача двумерного Bigarray в функцию C и функцию Fortran.
extern void my_c_function(double * data, int dimx, int dimy);
extern void my_fortran_function_(double * data, int * dimx, int * dimy);
CAMLprim value caml_stub(value bigarray)
{
int dimx = Caml_ba_array_val(bigarray)->dim[0];
int dimy = Caml_ba_array_val(bigarray)->dim[1];
/* C passes scalar parameters by value */
my_c_function(Caml_ba_data_val(bigarray), dimx, dimy);
/* Fortran passes all parameters by reference */
my_fortran_function_(Caml_ba_data_val(bigarray), &dimx, &dimy);
return Val_unit;
}
22.10.3 Обёртка массива C или Fortran в качестве OCaml Bigarray
Указатель p на уже выделенный массив C или Fortran можно обернуть и вернуть в OCaml в виде Bigarray, используя функции caml_ba_alloc или caml_ba_alloc_dims.
-
caml_ba_alloc(kind | layout, numdims, p, dims)
Возвращает OCaml Bigarray, который оборачивает данные, на которые указывает p. kind — тип элементов массива (одна из констант типа CAML_BA_ выше). layout — CAML_BA_C_LAYOUT для массива с расположением C и CAML_BA_FORTRAN_LAYOUT для массива с расположением Fortran. numdims — число измерений в массиве. dims — массив из numdims целых чисел, задающих размеры массива в каждом измерении.
-
caml_ba_alloc_dims(kind | layout, numdims, p, (long) dim1, (long) dim2, …, (long) dimnumdims)
Аналогично caml_ba_alloc, но размеры массива в каждом измерении перечислены в качестве дополнительных аргументов в вызове функции, а не передаются в виде массива.
Следующий пример демонстрирует, как статически выделенные массивы C и Fortran могут быть доступны для OCaml.
extern long my_c_array[100][200];
extern float my_fortran_array_[300][400];
CAMLprim value caml_get_c_array(value unit)
{
long dims[2];
dims[0] = 100; dims[1] = 200;
return caml_ba_alloc(CAML_BA_NATIVE_INT | CAML_BA_C_LAYOUT,
2, my_c_array, dims);
}
CAMLprim value caml_get_fortran_array(value unit)
{
return caml_ba_alloc_dims(CAML_BA_FLOAT32 | CAML_BA_FORTRAN_LAYOUT,
2, my_fortran_array_, 300L, 400L);
}
22.11 Дополнительная тема: более быстрый вызов C
В этом разделе описывается, как сделать вызовы функций C более быстрыми.
Примечание: это применимо только к собственному компилятору. Поэтому всякий раз, когда вы используете любой из этих методов, вам необходимо предоставить альтернативную заглушку байткода, которая игнорирует все специальные аннотации.
22.11.1 Передача необработанных значений
Ранее мы говорили, что все объекты OCaml представлены типом C value, и для декодирования данных из типа value необходимо использовать макросы, такие как Int_val. Однако можно указать компилятору OCaml нативный код, чтобы он выполнял эту работу за нас и передавал аргументы в функцию C без упаковки. Аналогично, можно указать OCaml ожидать результат без упаковки и упаковать его за нас.
Цель состоит в том, чтобы, позволив «ocamlopt» обрабатывать упаковку, он часто мог решить полностью её подавить.
Например, рассмотрим этот пример:
external foo : float -> float -> float = "foo"
let f a b =
let len = Array.length a in
assert (Array.length b = len);
let res = Array.make len 0. in
for i = 0 to len - 1 do
res.(i) <- foo a.(i) b.(i)
done
Массивы чисел с плавающей точкой в OCaml передаются без упаковки, однако функция C foo ожидает свои аргументы в виде упакованных чисел с плавающей точкой и возвращает упакованное число с плавающей точкой. Поэтому компилятор OCaml не имеет выбора, кроме как упаковать a.(i) и b.(i) и распаковать результат функции foo. Это приводит к выделению 3 * len временных значений с плавающей точкой.
Теперь, если мы добавим аннотацию аргументов и результата с помощью [@unboxed], компилятор нативного кода сможет избежать всех этих выделений:
external foo : (float [@unboxed]) -> (float [@unboxed]) -> (float [@unboxed]) = "foo_byte" "foo"
В этом случае функции C должны выглядеть следующим образом:
CAMLprim double foo(double a, double b)
{
...
}
CAMLprim value foo_byte(value a, value b)
{
return caml_copy_double(foo(Double_val(a), Double_val(b)))
}
Для удобства, когда все аргументы и результат аннотированы с помощью [@unboxed], можно поместить атрибут только один раз в само объявление. Таким образом, мы можем записать вместо этого:
external foo : float -> float -> float = "foo_byte" "foo" [@@unboxed]
Следующая таблица обобщает, какие типы OCaml могут быть переданы без упаковки и какие типы C следует использовать в соответствии:
| Тип OCaml | Тип C |
| float | double |
| int32 | int32_t |
| int64 | int64_t |
| nativeint | intnat |
Аналогично, можно передавать неупакованные целые числа OCaml между OCaml и C. Это делается путем аннотирования аргументов и/или результата с помощью [@untagged]:
external f : string -> (int [@untagged]) = "f_byte" "f"
Соответствующим типом C должен быть intnat.
Примечание: не используйте тип C int в соответствии с (int [@untagged]). Это связано с тем, что их размер часто отличается.
22.11.2 Прямой вызов C
Чтобы иметь возможность запускать сборщик мусора в середине функции C, компилятор OCaml нативного кода генерирует некоторый служебный код вокруг вызовов C. Технически он оборачивает каждый вызов C функцией C caml_c_call, которая является частью среды выполнения OCaml.
Для небольших функций, которые вызываются многократно, это косвенное обращение может значительно повлиять на производительность. Однако это не требуется, если мы знаем, что функция C не выделяет память, не вызывает исключения и не освобождает блокировку домена (см. раздел 22.12.2). Мы можем сообщить об этом компилятору OCaml нативного кода, аннотировав внешнее объявление атрибутом [@@noalloc]:
external bar : int -> int -> int = "foo" [@@noalloc]
В этом случае вызов bar из OCaml так же эффективен, как и вызов любой другой функции OCaml, за исключением того, что компилятор OCaml не может встраивать функции C...
22.11.3 Пример: вызов функций C-библиотеки без косвенного обращения
Используя эти атрибуты, можно вызывать функции C-библиотеки без косвенного обращения. Например, многие математические функции определены таким образом в стандартной библиотеке OCaml:
external sqrt : float -> float = "caml_sqrt_float" "sqrt" [@@unboxed] [@@noalloc] (** Square root. *) external exp : float -> float = "caml_exp_float" "exp" [@@unboxed] [@@noalloc] (** Exponential. *) external log : float -> float = "caml_log_float" "log" [@@unboxed] [@@noalloc] (** Natural logarithm. *)
22.12 Расширенная тема: многопоточность
Использование нескольких потоков (конкурентность с общей памятью) в смешанном приложении OCaml/C требует специальных мер предосторожности, которые описаны в этом разделе.
22.12.1 Регистрация потоков, созданных из C
Обратные вызовы из C в OCaml возможны только в том случае, если вызывающий поток известен системе выполнения OCaml. Потоки, созданные из OCaml (через функцию Thread.create библиотеки системных потоков), автоматически известны системе выполнения. Если приложение создаёт дополнительные потоки из C и хочет выполнить обратный вызов в код OCaml из этих потоков, оно должно сначала зарегистрировать их в системе выполнения. Следующие функции объявлены в файле заголовков <caml/threads.h>.
- caml_c_thread_register() регистрирует вызывающий поток в системе выполнения OCaml. Возвращает 1 при успехе, 0 при ошибке. Регистрация уже зарегистрированного потока ничего не делает и возвращает 0.
- caml_c_thread_unregister() должна быть вызвана перед завершением потока, чтобы выписаться из системы выполнения OCaml. Возвращает 1 при успехе, 0 при ошибке. Если вызывающий поток ранее не был зарегистрирован, ничего не делает и возвращает 0.
22.12.2 Параллельное выполнение длительных функций C с systhreads
Домены — это единицы параллелизма для программ OCaml. При использовании библиотеки systhreads несколько потоков могут быть прикреплены к одному домену. Однако в любой момент времени не более одного из этих потоков может выполнять код OCaml или C-код, использующий систему выполнения OCaml в пределах данного домена. Технически это обеспечивается «блокировкой домена», которую любой поток должен удерживать, выполняя такой код в пределах домена.
Когда OCaml вызывает C-код, реализующий примитив, блокировка домена удерживается, поэтому C-код имеет полный доступ к возможностям системы выполнения. Однако ни один другой поток в том же домене не может выполнять код OCaml одновременно с C-кодом примитива.
Если C-примитив работает долго или выполняет потенциально блокирующие операции ввода-вывода, он может явно освободить блокировку домена, что позволяет другим потокам OCaml в том же домене работать параллельно с его операциями. C-код должен снова захватить блокировку домена перед возвратом в OCaml. Это достигается с помощью следующих функций, объявленных в файле заголовков <caml/threads.h>.
- caml_release_runtime_system() Вызывающий поток освобождает блокировку домена и другие ресурсы OCaml, позволяя другим потокам выполнять код OCaml параллельно с выполнением вызывающего потока.
- caml_acquire_runtime_system() Вызывающий поток снова захватывает блокировку домена и другие ресурсы OCaml. Он может блокироваться, пока ни один другой поток в том же домене не использует систему выполнения OCaml.
Эти функции выполняют опрос на наличие ожидающих сигналов, вызывая асинхронные обратные вызовы (раздел 22.5.3) перед освобождением и после захвата блокировки. Таким образом, они могут выполнять произвольный код OCaml, включая поднятие асинхронного исключения.
После вызова caml_release_runtime_system() и до вызова caml_acquire_runtime_system() C-код не должен обращаться к данным OCaml, ни вызывать ни одну функцию системы выполнения, ни обращаться обратно в код OCaml. Следовательно, аргументы, предоставленные OCaml примитиву C, должны быть скопированы в структуры данных C перед вызовом caml_release_runtime_system(), а результаты, которые должны быть возвращены OCaml, должны быть закодированы в виде значений OCaml после возврата caml_acquire_runtime_system().
Пример: следующая C-функция вызывает gethostbyname для поиска IP-адреса имени хоста. Функция gethostbyname может блокироваться длительное время, поэтому мы выбираем освободить систему выполнения OCaml во время её выполнения.
CAMLprim stub_gethostbyname(value vname)
{
CAMLparam1 (vname);
CAMLlocal1 (vres);
struct hostent * h;
char * name;
/* Copy the string argument to a C string, allocated outside the
OCaml heap. */
name = caml_stat_strdup(String_val(vname));
/* Release the OCaml run-time system */
caml_release_runtime_system();
/* Resolve the name */
h = gethostbyname(name);
/* Free the copy of the string, which we might as well do before
acquiring the runtime system to benefit from parallelism. */
caml_stat_free(name);
/* Re-acquire the OCaml run-time system */
caml_acquire_runtime_system();
/* Encode the relevant fields of h as the OCaml value vres */
... /* Omitted */
/* Return to OCaml */
CAMLreturn (vres);
}
Макрос Caml_state вычисляет переменную состояния домена и проверяет в режиме отладки, удерживается ли блокировка домена. Такая проверка также выполняется в обычном режиме в ключевых точках входа в API C; поэтому вызов некоторых функций и макросов среды выполнения без правильного владения блокировкой домена может привести к фатальной ошибке: no domain lock held. Вариант Caml_state_opt не выполняет никакой проверки, но вычисляет NULL при отсутствии блокировки домена. Это позволяет определить, удерживает ли поток, принадлежащий домену, в настоящее время блокировку своего домена для различных целей.
Обратные вызовы из C в OCaml должны выполняться при удержании блокировки домена системы выполнения OCaml. Это естественно, если обратный вызов выполняется C-примитивом, который не освобождал систему выполнения. Если C-примитив ранее освободил систему выполнения, или обратный вызов выполняется из другого C-кода, который не вызывался из OCaml (например, цикл обработки событий в приложении GUI), система выполнения должна быть захвачена перед обратным вызовом и освобождена после:
caml_acquire_runtime_system(); /* Resolve OCaml function vfun to be invoked */ /* Build OCaml argument varg to the callback */ vres = callback(vfun, varg); /* Copy relevant parts of result vres to C data structures */ caml_release_runtime_system();
Примечание: функции acquire и release, описанные выше, были введены в OCaml 3.12. Более старые коды используют следующие исторические имена, объявленные в <caml/signals.h>:
- caml_enter_blocking_section в качестве псевдонима для caml_release_runtime_system
- caml_leave_blocking_section в качестве псевдонима для caml_acquire_runtime_system
Интуиция: «блок блокировки» — это фрагмент кода на C, который не использует систему выполнения OCaml, обычно операция блокирующего ввода-вывода.
22.13 Расширенная тема: взаимодействие с Windows-API Unicode
Этот раздел содержит некоторые общие рекомендации по написанию C-заглушек, которые используют Windows-API Unicode.
Система OCaml под Windows может быть настроена во время компиляции в одном из двух режимов:
- режим legacy: Предполагается, что все имена путей, переменные среды, аргументы командной строки и т. д. со стороны OCaml закодированы с использованием текущей 8-битной кодовой страницы системы.
- режим Unicode: Предполагается, что все имена путей, переменные среды, аргументы командной строки и т. д. со стороны OCaml закодированы в UTF-8.
В дальнейшем мы говорим, что строка имеет кодировку OCaml, если она закодирована в UTF-8 в режиме Unicode, в текущей кодовой странице в режиме legacy или является произвольной строкой под Unix. Строка имеет платформенную кодировку, если она закодирована в UTF-16 под Windows или является произвольной строкой под Unix.
С точки зрения автора C-заглушек, проблемы взаимодействия с Windows-API Unicode двояки:
- Windows-API использует кодировку UTF-16 для поддержки Unicode. Система выполнения выполняет необходимые преобразования, так что программисту OCaml нужно работать только с кодировкой OCaml. C-заглушки, которые вызывают Windows-API Unicode, должны использовать определённые функции системы выполнения для выполнения необходимых преобразований совместимым способом.
- При написании заглушек, которые должны компилироваться как под Windows, так и под Unix, заглушки должны быть написаны таким образом, чтобы допускать необходимые преобразования под Windows, но также работать под Unix, где обычно ничего особенного не нужно делать для поддержки Unicode.
Базовый тип символа C под Windows — WCHAR, шириной два байта, а под Unix — char, шириной один байт. Тип char_os определён в <caml/misc.h> и обозначает конкретный тип символа C для каждой платформы. Строки в кодировке платформы имеют тип char_os *.
Следующие функции предоставляются для написания совместимых C-заглушек. Для их использования необходимо включить как <caml/misc.h>, так и <caml/osdeps.h>.
-
char_os* caml_stat_strdup_to_os(const char *) копирует аргумент, переводя кодировку из OCaml в платформенную. Эта функция обычно используется для преобразования char *, лежащего в основе строки OCaml, перед передачей её в API операционной системы, принимающей аргумент Unicode. Под Unix, она эквивалентна caml_stat_strdup.
Примечание: Для максимальной обратной совместимости в режиме Unicode, если аргумент не является допустимой строкой UTF-8, эта функция перейдёт к предположению, что она закодирована в текущей кодовой странице.
- char* caml_stat_strdup_of_os(const char_os *) копирует аргумент, переводя кодировку из платформенной в кодировку OCaml. Это обратная функция caml_stat_strdup_to_os. Эта функция обычно используется для преобразования строки, полученной из операционной системы, перед её передачей коду OCaml. Под Unix, она эквивалентна caml_stat_strdup.
- value caml_copy_string_of_os(char_os *) выделяет строку OCaml с содержимым, равным строке аргумента, преобразованной в кодировку OCaml. Эта функция по существу эквивалентна caml_stat_strdup_of_os, за которой следует caml_copy_string, за исключением того, что она избегает выделения промежуточной строки, возвращаемой caml_stat_strdup_of_os. Под Unix, она эквивалентна caml_copy_string.
Примечание: Строки, возвращаемые caml_stat_strdup_to_os и caml_stat_strdup_of_os, выделены с помощью caml_stat_alloc, поэтому их необходимо освободить с помощью caml_stat_free, когда они больше не нужны.
Пример
Мы хотим связать функцию getenv таким образом, чтобы она работала как под Unix, так и под Windows. Под Unix, у этой функции прототип:
char *getenv(const char *);
В то время как версия Unicode под Windows имеет прототип:
WCHAR *_wgetenv(const WCHAR *);
В терминах char_os, обе функции принимают аргумент типа char_os * и возвращают результат того же типа. Мы начинаем с выбора правильной реализации функции для связывания:
#ifdef _WIN32 #define getenv_os _wgetenv #else #define getenv_os getenv #endif
Остальная часть связывания одинакова для обеих платформ:
#include <caml/mlvalues.h>
#include <caml/misc.h>
#include <caml/alloc.h>
#include <caml/fail.h>
#include <caml/osdeps.h>
#include <stdlib.h>
CAMLprim value stub_getenv(value var_name)
{
CAMLparam1(var_name);
CAMLlocal1(var_value);
char_os *var_name_os, *var_value_os;
var_name_os = caml_stat_strdup_to_os(String_val(var_name));
var_value_os = getenv_os(var_name_os);
caml_stat_free(var_name_os);
if (var_value_os == NULL)
caml_raise_not_found();
var_value = caml_copy_string_of_os(var_value_os);
CAMLreturn(var_value);
}
22.14 Создание смешанных C/OCaml библиотек: ocamlmklib
Команда ocamlmklib облегчает создание библиотек, содержащих как код OCaml, так и код C, и пригодных как для статической, так и для динамической компоновки. Эта команда доступна под Windows начиная с Objective Caml 3.11 и на других операционных системах начиная с Objective Caml 3.03.
Команда ocamlmklib принимает три типа аргументов:
- OCaml-исходные файлы и файлы-объекты (.cmo, .cmx, .ml), составляющие OCaml-часть библиотеки;
- C-файлы-объекты (.o, .a, соответственно, .obj, .lib), составляющие C-часть библиотеки;
- Поддерживающие библиотеки для C-части (-llib).
Она генерирует следующие результаты:
- OCaml-библиотека байткода .cma, включающая .cmo и .ml OCaml-файлы, переданные в качестве аргументов, и автоматически ссылающаяся на C-библиотеку, сгенерированную из C-файлов-объектов.
- OCaml-библиотека кода машинных инструкций .cmxa, включающая .cmx и .ml OCaml-файлы, переданные в качестве аргументов, и автоматически ссылающаяся на C-библиотеку, сгенерированную из C-файлов-объектов.
- Если динамическая компоновка поддерживается на целевой платформе, .so (соответственно, .dll) общая библиотека, созданная из C-файлов-объектов, переданных в качестве аргументов, и автоматически ссылающаяся на поддерживающие библиотеки.
- C-статическая библиотека .a (соответственно, .lib), созданная из C-файлов-объектов.
Кроме того, распознаются следующие опции:
- -cclib, -ccopt, -I, -linkall
- Эти параметры передаются как есть в ocamlc или ocamlopt. Обратитесь к документации этих команд.
- -rpath, -R, -Wl,-rpath, -Wl,-R
- Эти параметры передаются как есть компилятору C. Обратитесь к документации компилятора C.
- -custom
- Принудительно создать статически скомпилированную библиотеку только, даже если динамическая компоновка поддерживается.
- -failsafe
- Вернуться к построению статически скомпилированной библиотеки, если при построении динамической библиотеки возникла проблема (например, некоторые библиотеки поддержки недоступны как динамические библиотеки).
- -Ldir
- Добавить dir в путь поиска библиотек поддержки (-llib).
- -ocamlc cmd
- Использовать cmd вместо ocamlc для вызова компилятора байткода.
- -ocamlopt cmd
- Использовать cmd вместо ocamlopt для вызова компилятора кода нативном.
- -o output
- Установить имя сгенерированной библиотеки OCaml. ocamlmklib сгенерирует output.cma и/или output.cmxa. Если не указано, по умолчанию используется a.
- -oc outputc
- Установите имя сгенерированной C библиотеки. ocamlmklib сгенерирует liboutputc.so (если поддерживаются динамические библиотеки) и liboutputc.a. Если не указано, по умолчанию используется имя вывода, заданное с -o.
В операционной системе Windows с использованием родных приложений также учитывается следующая переменная среды:
- OCAML_FLEXLINK
- Альтернативное исполняемое приложение, используемое вместо заданного значения. В основном используется для загрузки.
Пример
Рассмотрим интерфейс OCaml к стандартной библиотеке C libz для чтения и записи сжатых файлов. Предположим, что эта библиотека находится в /usr/local/zlib. Этот интерфейс состоит из части OCaml zip.cmo/zip.cmx и части C zipstubs.o, содержащей код заглушки вокруг точек входа в libz. Следующая команда создает библиотеки OCaml zip.cma и zip.cmxa, а также соответствующие библиотеки C dllzip.so и libzip.a:
ocamlmklib -o zip zip.cmo zip.cmx zipstubs.o -lz -L/usr/local/zlib
Если поддерживаются динамические библиотеки, выполняются следующие команды:
ocamlc -a -o zip.cma zip.cmo -dllib -lzip \
-cclib -lzip -cclib -lz -ccopt -L/usr/local/zlib
ocamlopt -a -o zip.cmxa zip.cmx -cclib -lzip \
-cclib -lzip -cclib -lz -ccopt -L/usr/local/zlib
gcc -shared -o dllzip.so zipstubs.o -lz -L/usr/local/zlib
ar rc libzip.a zipstubs.o
Примечание: Этот пример относится к системе Unix. Точные командные строки могут отличаться в других системах.
Если динамические библиотеки не поддерживаются, выполняются следующие команды:
ocamlc -a -custom -o zip.cma zip.cmo -cclib -lzip \
-cclib -lz -ccopt -L/usr/local/zlib
ocamlopt -a -o zip.cmxa zip.cmx -lzip \
-cclib -lz -ccopt -L/usr/local/zlib
ar rc libzip.a zipstubs.o
Вместо одновременного построения библиотеки байткода, библиотеки кода нативного уровня и библиотек C, ocamlmklib можно вызвать три раза, чтобы построить каждый отдельный. Таким образом,
ocamlmklib -o zip zip.cmo -lz -L/usr/local/zlib
создает библиотеку байткода zip.cma, и
ocamlmklib -o zip zip.cmx -lz -L/usr/local/zlib
создает библиотеку кода нативного уровня zip.cmxa, и
ocamlmklib -o zip zipstubs.o -lz -L/usr/local/zlib
создает библиотеки C dllzip.so и libzip.a. Обратите внимание, что библиотеки поддержки (-lz) и соответствующие параметры (-L/usr/local/zlib) должны быть указаны во всех трех вызовах ocamlmklib, потому что они необходимы в разное время в зависимости от того, поддерживаются ли динамические библиотеки.
22.15 Предостережения: внутренний API выполнения
Не все заголовки, доступные в каталоге caml/, были описаны в предыдущих разделах. Все эти незадокументированные заголовки являются частью внутреннего API выполнения, для которого нет гарантии стабильности. Если вам действительно нужен доступ к этому внутреннему API выполнения, в этом разделе приводятся некоторые рекомендации, которые помогут вам написать код, который, возможно, не будет ломаться при каждом обновлении OCaml.
Примечание
Программистам, которые используют внутренний API для задачи, которую они считают реалистичной и полезной, рекомендуется открыть запрос на улучшение в системе отслеживания ошибок.
22.15.1 Внутренние переменные и CAML_INTERNALS
Начиная с OCaml 4.04, можно получить доступ ко всем частям внутреннего API выполнения, определив макрос CAML_INTERNALS перед загрузкой заголовков caml. Если этот макрос не определен, части внутреннего API выполнения скрыты.
Если вы используете внутренние переменные C, не переопределяйте их вручную. Вы должны импортировать эти переменные, включив соответствующие заголовочные файлы. Представление этих переменных уже изменилось один раз в OCaml 4.10 и по-прежнему развивается. Если ваш код опирается на такие внутренние и хрупкие свойства, он в какой-то момент сломается.
Например, вместо переопределения caml_young_limit:
extern int caml_young_limit;
что ломается в OCaml ≥ 4.10, вы должны включить заголовок minor_gc:
#include <caml/minor_gc.h>
22.15.2 Макросы версии OCaml
Наконец, если включение правильных заголовков недостаточно или если вам необходимо поддерживать версии, более старые, чем OCaml 4.04, заголовочный файл caml/version.h должен помочь вам определить собственный слой совместимости. Этот файл предоставляет несколько макросов, определяющих текущую версию OCaml. В частности, макрос OCAML_VERSION описывает текущую версию, ее формат — MmmPP. Например, если вам нужен какой-то особый способ обработки версий, более ранних, чем 4.10.0, вы можете написать
#include <caml/version.h> #if OCAML_VERSION >= 41000 ... #else ... #endif
© 1995-2022 INRIA.
https://v2.ocaml.org/releases/5.0/htmlman/intfc.html