Глава 20. Интерфейс C с OCaml
- 20.1 Обзор и информация о компиляции
- 20.2 Тип value
- 20.3 Представление типов данных OCaml
- 20.4 Операции над значениями
- 20.5 Совместимость с сборщиком мусора
- 20.6 Полный пример
- 20.7 Расширенная тема: обратные вызовы из C в OCaml
- 20.8 Расширенный пример с обратными вызовами
- 20.9 Расширенная тема: пользовательские блоки
- 20.10 Расширенная тема: Bigarrays и интерфейс OCaml-C
- 20.11 Расширенная тема: более быстрый вызов C
- 20.12 Расширенная тема: многопоточность
- 20.13 Расширенная тема: взаимодействие с API Windows Unicode
- 20.14 Создание смешанных C/OCaml библиотек: ocamlmklib
- 20.15 Предостережения: внутренний API выполнения
В этой главе описывается, как пользовательские примитивы, написанные на C, могут быть связаны с кодом OCaml и вызваны из функций OCaml, а также как эти функции C могут вызывать обратные вызовы в код OCaml.
20.1 Обзор и информация о компиляции
20.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. Эти флаги зарезервированы для реализации стандартной библиотеки.
20.1.2 Реализация примитивов
Пользовательские примитивы с арностью n ≤ 5 реализуются функциями C, которые принимают n аргументов типа value и возвращают результат типа value. Тип value является типом представлений для значений OCaml. Он кодирует объекты нескольких базовых типов (целые числа, числа с плавающей запятой, строки, …) а также структуры данных OCaml. Тип value и связанные с ним функции и макросы преобразования подробно описаны ниже. Например, вот объявление функции C, реализующей примитив input:
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. Например, вот код-заглушка для примитива input:
CAMLprim value input(value channel, value buffer, value offset, value length)
{
return Val_long(getblock((struct channel *) channel,
&Byte(buffer, Long_val(offset)),
Long_val(length)));
}
(Здесь Val_long, Long_val и так далее являются макросами преобразования для типа value, которые будут описаны позже. Макрос CAMLprim расширяется до необходимых директив компилятора, чтобы гарантировать, что функция экспортируется и доступна из OCaml.) Трудная работа выполняется функцией getblock, которая объявляется как:
long getblock(struct channel * channel, char * p, long n)
{
...
}
Для написания кода C, который работает со значениями OCaml, предоставляются следующие файлы заголовков:
| Файл для включения | Предоставляет |
| caml/mlvalues.h | определение типа value и макросы преобразования |
| caml/alloc.h | функции выделения (для создания структурированных объектов OCaml) |
| caml/memory.h | разнообразные функции и макросы, связанные с памятью (для интерфейса сборщика мусора, изменения структур на месте и т. д.) |
| caml/fail.h | функции для возбуждения исключений (см. раздел 20.4.5) |
| caml/callback.h | обратный вызов из C в OCaml (см. раздел 20.7). |
| caml/custom.h | операции над пользовательскими блоками (см. раздел 20.9). |
| caml/intext.h | операции для записи пользовательских функций сериализации и десериализации для пользовательских блоков (см. раздел 20.9). |
| caml/threads.h | операции для взаимодействия при наличии нескольких потоков (см. раздел 20.12). |
Перед включением любого из этих файлов необходимо определить макрос CAML_NAME_SPACE. Например,
#define CAML_NAME_SPACE #include "caml/mlvalues.h" #include "caml/fail.h"
Эти файлы находятся в подкаталоге caml/ каталога стандартной библиотеки OCaml, который возвращает команда ocamlc -where (обычно /usr/local/lib/ocaml или /usr/lib/ocaml).
Примечание: Включение заголовочных файлов без предварительного определения CAML_NAME_SPACE приводит к появлению в области видимости сокращённых имён большинства функций. Эти сокращённые имена устарели и могут быть удалены в будущем, потому что они обычно приводят к конфликтам с именами, определёнными другими библиотеками C.
20.1.3 Статическое связывание кода C с кодом OCaml
Система выполнения OCaml состоит из трёх основных частей: интерпретатора байткода, менеджера памяти и набора функций C, реализующих примитивные операции. Некоторые инструкции байткода предназначены для вызова этих функций C, определяемых по их смещению в таблице функций (таблице примитивов).
В стандартном режиме связующее звено OCaml генерирует байт-код для стандартной системы выполнения со стандартным набором примитивов. Ссылки на примитивы, которые не входят в этот стандартный набор, приводят к ошибке «недоступный примитив C». (Если не поддерживается динамическая загрузка библиотек C — см. раздел 20.1.4 ниже.)
В режиме «пользовательской системы выполнения» связующее звено OCaml просматривает файлы объектного кода и определяет набор необходимых примитивов. Затем оно строит подходящую систему выполнения, вызвав компоновщик кода на родном языке с:
- таблицей необходимых примитивов;
- библиотекой, которая предоставляет интерпретатор байткода, менеджер памяти и стандартные примитивы;
- библиотеками и файлами объектного кода (.o файлы), указанными в командной строке для связующего звена OCaml, которые предоставляют реализации пользовательских примитивов.
Это создаёт систему выполнения с необходимыми примитивами. Связующее звено OCaml генерирует байт-код для этой пользовательской системы выполнения. Байт-код добавляется в конец пользовательской системы выполнения, чтобы он автоматически выполнялся при запуске выходного файла (пользовательская система выполнения + байт-код).
Для связывания в режиме «пользовательской системы выполнения» выполните команду ocamlc с:
- флагом -custom;
- именами требуемых файлов объектного кода OCaml (.cmo и .cma файлы);
- именами файлов объектного кода C и библиотек (.o и .a файлы), которые реализуют необходимые примитивы. Под Unix и Windows библиотека с именем libname.a (соответственно, .lib) в одном из стандартных каталогов библиотек также может быть указана как -cclib -lname.
Если вы используете компилятор кода на родном языке 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
Однако первый вариант удобнее для конечных пользователей библиотеки.
20.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 может найти его позже во время запуска программы (см. раздел 13.3). Наконец (шаг 3), выполните команду ocamlc с:
- именами требуемых файлов объектного кода OCaml (.cmo и .cma файлы);
- именами общих библиотек C (.so или .dll файлы), которые реализуют необходимые примитивы. Под Unix и Windows библиотека с именем dllname.so (соответственно, .dll) в одном из стандартных каталогов библиотек также может быть указана как -dllib -lname.
Не устанавливайте флаг -custom, иначе вы вернётесь к статическому связыванию, как описано в разделе 20.1.3. Инструмент ocamlmklib (см. раздел 20.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) или динамически скомпонована.
20.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 (см. раздел 20.14) пытается скрыть некоторые из этих зависимостей от системы.
В заключение: динамическая компоновка настоятельно рекомендуется при использовании родного порта для Windows, поскольку проблем с переносимостью нет, и это намного удобнее для конечных пользователей. В Unix динамическая компоновка должна рассматриваться для зрелых, часто используемых библиотек, поскольку это улучшает независимость от платформы байткодовых исполняемых файлов. Для новых или редко используемых библиотек статическая компоновка значительно проще для настройки в переносимом виде.
20.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 был фактически связан).
20.2 Тип value
Все OCaml-объекты представляются C-типом value, определённым в файле заголовков caml/mlvalues.h, вместе с макросами для работы с значениями этого типа. Объект типа value может быть:
- неупакованным целым числом;
- или указателем на блок внутри кучи, выделенный с помощью одной из
caml_alloc_*функций, описанных в разделе 20.4.4.
20.2.1 Целочисленные значения
Целочисленные значения кодируют 63-битные знакомые целые числа (31-битные на 32-битных архитектурах). Они неупакованы (не выделены).
20.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 | Блок, представляющий абстрактный тип данных с пользовательскими функциями окончательной обработки, сравнения, хэширования, сериализации и десериализации. |
20.2.3 Указатели вне кучи
В предыдущих версиях OCaml можно было использовать выровненные по слову указатели на адреса вне кучи в качестве OCaml-значений, просто приведённых к типу value. Начиная с OCaml 4.11, такое использование устарело и не будет поддерживаться в OCaml 5.00.
Правильный способ работы с указателями на блоки вне кучи из OCaml — хранение этих указателей в OCaml-блоках с меткой Abstract_tag или Custom_tag, а затем использование этих блоков как OCaml-значений.
Вот пример инкапсуляции указателей вне кучи типа C ty * внутри блоков Abstract_tag. Раздел 20.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);
}
20.3 Представление типов данных OCaml
В этом разделе описывается, как типы данных OCaml кодируются в типе value.
20.3.1 Атомарные типы
| Тип OCaml | Кодирование |
| int | Неупакованные целые значения. |
| char | Неупакованные целые значения (код ASCII). |
| float | Блоки с меткой Double_tag. |
| bytes | Блоки с меткой String_tag. |
| string | Блоки с меткой String_tag. |
| int32 | Блоки с меткой Custom_tag. |
| int64 | Блоки с меткой Custom_tag. |
| nativeint | Блоки с меткой Custom_tag. |
20.3.2 Кортежи и записи
Кортежи представлены указателями на блоки с меткой 0.
Записи также представлены блоками с меткой ноль. Порядок меток в объявлении типа записи определяет расположение полей записи: значение, связанное с первой объявленной меткой, хранится в поле 0 блока, значение, связанное со второй меткой, — в поле 1 и так далее.
В целях оптимизации записи, у которых все поля имеют статический тип float, представляются как массивы чисел с плавающей точкой с меткой Double_array_tag. (См. раздел об массивах ниже.)
В качестве ещё одной оптимизации, для типов записей, допускающих распаковку, используется специальное представление; типы записей, допускающие распаковку, — это неизменяемые типы записей, имеющие только одно поле. Тип, допускающий распаковку, будет представлен одним из двух способов: упакованным или распакованным. Упакованные типы записей представлены, как описано выше (блоком с меткой 0 или Double_array_tag). Распакованный тип записи представлен непосредственно значением его поля (то есть блока, представляющего саму запись, нет).
Представление выбирается в соответствии со следующим списком (в порядке убывания приоритета):
- Атрибут ([@@boxed] или [@@unboxed]) в объявлении типа.
- Параметр компилятора (-unboxed-types или -no-unboxed-types).
- Представление по умолчанию. В текущей версии OCaml представлением по умолчанию является упакованное представление.
20.3.3 Массивы
Массивы целых чисел и указателей представляются как кортежи, то есть как указатели на блоки с меткой 0. Для чтения используются макрос Field, а для записи — функция caml_modify.
Массивы чисел с плавающей точкой (тип float array) имеют специальное, неупакованное, более эффективное представление. Эти массивы представлены указателями на блоки с меткой Double_array_tag. Для работы с ними необходимо использовать макросы Double_field и Store_double_field.
20.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 *)
В целях оптимизации, для типов данных, допускающих распаковку, используется специальное представление; тип данных допускает распаковку, если он имеет ровно один конструктор, и этот конструктор имеет ровно один аргумент. Распаковываемые типы данных представляются так же, как и распаковываемые типы записей: см. описание в разделе 20.3.2.
20.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);
20.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.
20.4 Операции со значениями
20.4.1 Проверка типа
- Is_long(v) истинно, если значение v — это целое число непосредственно, и ложно в противном случае
- Is_block(v) истинно, если значение v — указатель на блок, и ложно, если это целое число непосредственно.
- Is_none(v) истинно, если значение v равно None.
- Is_some(v) истинно, если значение v (предполагается, что оно имеет тип «вариант») соответствует конструктору Some.
20.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.
20.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 с типом char * или, когда OCaml сконфигурирован с -force-safe-string, с типом 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 в соответствии с предпочтительным представлением типов unboxable в текущей версии OCaml.
- Some_val(v) возвращает аргумент \var{x} значения v в форме Some(x).
Выражения Field(v, n), Byte(v, n) и Byte_u(v, n) являются допустимыми l-значениями. Следовательно, они могут быть присвоены, что приводит к изменению значения v на месте. Присваивание напрямую к Field(v, n) следует осуществлять с осторожностью, чтобы не ввести в заблуждение сборщик мусора (см. ниже).
20.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, описанную ниже) перед следующей операцией выделения.
20.4.5 Вызов исключений
Предоставлены две функции для вызова двух стандартных исключений:
-
caml_failwith(s), где s — завершающаяся нулём C-строка (типа
char *), вызывает исключение Failure с аргументом s. -
caml_invalid_argument(s), где s — завершающаяся нулём C-строка (типа
char *), вызывает исключение Invalid_argument с аргументом s.
Вызов произвольных исключений из C более сложен: идентификатор исключения динамически выделяется программой OCaml и поэтому должен передаваться функции C с помощью механизма регистрации, описанного ниже в разделе 20.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 в качестве аргумента.
20.5 Взаимодействие с сборщиком мусора
Неиспользуемые блоки в куче автоматически освобождаются сборщиком мусора. Это требует некоторого сотрудничества со стороны кода C, который манипулирует блоками, выделенными в куче.
20.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 выдают ложные предупреждения об неиспользуемых переменных 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) должно быть true).
Пример:
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, чтобы избежать гонки с сборщиком мусора при чтении или записи значения. (См. раздел 20.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__ в своих программах.
20.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.
20.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);
}
Правильное использование исключительного возврата, особенно при наличии сборки мусора, подробно описано в разделе 20.7.1.
20.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>
#define CAML_NAME_SPACE
#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.)
20.7 Расширенная тема: обратные вызовы из C в OCaml
До сих пор мы описывали, как вызывать функции C из OCaml. В этом разделе показано, как функции C могут вызывать функции OCaml, либо в качестве обратных вызовов (OCaml вызывает C, который вызывает OCaml), либо с основной программой, написанной на C.
20.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);
}
20.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));
}
20.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 (описанным в разделе 20.4.5) для фактического повышения исключения. Например, вот C-функция, которая повышает исключение Error с заданным аргументом:
void raise_error(char * msg)
{
caml_raise_with_string(*caml_named_value("test exception"), msg);
}
20.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-функции с помощью механизма обратного вызова (см. раздел 20.7.1).
20.7.5 Встраивание OCaml-кода в C-код
Байткодовый компилятор в режиме пользовательской среды выполнения (ocamlc -custom) обычно добавляет байткод к исполняемому файлу, содержащему пользовательскую среду выполнения. Это имеет два следствия. Во-первых, заключительный этап компоновки должен выполняться ocamlc. Во-вторых, OCaml-библиотека времени выполнения должна уметь находить имя исполняемого файла из аргументов командной строки. При использовании caml_main(argv), как в разделе 20.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.
- Вызывание окончательной финализации выделенных пользовательских блоков (см. раздел 20.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, не должна пытаться обрабатывать эти сигналы самостоятельно.
20.8 Расширенный пример с обратными вызовами
Этот раздел демонстрирует возможности обратных вызовов, описанные в разделе 20.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.)
20.9 Расширенная тема: пользовательские блоки
Блоки с меткой Custom_tag содержат как произвольные пользовательские данные, так и указатель на структуру C, с типом struct custom_operations, которая связывает предоставленные пользователем функции финализации, сравнения, хеширования, сериализации и десериализации с этим блоком.
20.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 для возврата результата.
20.9.2 Выделение пользовательских блоков
Пользовательские блоки должны выделяться с помощью caml_alloc_custom или caml_alloc_custom_mem:
возвращает свежий пользовательский блок, с местом для size байт пользовательских данных, и чьи связанные операции задаются ops (указатель на struct custom_operations, обычно статически выделяется как глобальная переменная C).
Два параметра used и max используются для управления скоростью сбора мусора, когда завершённый объект содержит указатели на ресурсы, находящиеся вне кучи. В общем случае, инкрементальный сборщик мусора OCaml регулирует свою скорость относительно скорости выделения памяти в программе. Чем быстрее программа выделяет память, тем больше усилий прилагает GC для быстрого возврата недоступных блоков и предотвращения накопления большого количества «плавающего мусора» (неиспользуемых объектов, которые GC ещё не собрал).
Обычно скорость выделения памяти измеряется подсчётом размера выделенных блоков в куче. Однако часто бывает, что завершённые объекты содержат указатели на блоки памяти вне кучи и другие ресурсы (например, дескрипторы файлов, растровые изображения X Windows и т. д.). Для таких блоков размер блоков в куче не является хорошей мерой количества ресурсов, выделенных программой.
Два аргумента used и max дают GC представление о том, сколько ресурсов вне кучи потребляет завершённый блок, который выделяется: вы указываете количество ресурсов, выделенных для этого объекта, как параметр used, а максимальное количество, которое вы хотите видеть в плавающем мусоре, как параметр max. Единицы измерения произвольные: GC заботится только об отношении used / max.
Например, если вы выделяете завершённый блок, содержащий растровое изображение X Windows размером w на h пикселей, и вам не хотелось бы иметь более 1 мегапикселя необработанных растровых изображений, укажите used = w * h и max = 1000000.
Ещё один способ описать влияние параметров used и max — это в терминах полных циклов GC. Если вы выделяете много пользовательских блоков с used / max = 1 / N, GC выполнит один полный цикл (проверит каждый объект в куче и вызовет функции завершения для тех, которые недоступны) каждые N выделений. Например, если used = 1 и max = 1000, GC выполнит один полный цикл как минимум каждые 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.
20.9.3 Доступ к пользовательским блокам
Часть данных пользовательского блока v можно получить через указатель Data_custom_val(v). Этот указатель имеет тип void * и должен быть приведён к фактическому типу данных, хранящихся в пользовательском блоке.
Содержимое пользовательских блоков не сканируется сборщиком мусора и, следовательно, не должно содержать указателей внутри кучи OCaml. Другими словами, никогда не храните OCaml value в пользовательском блоке и не используйте Field, Store_field или caml_modify для доступа к части данных пользовательского блока. Напротив, любая структура данных C (не содержащая указателей на кучу) может быть сохранена в пользовательском блоке.
20.9.4 Написание пользовательских функций сериализации и десериализации
Следующие функции, определённые в <caml/intext.h>, предназначены для записи и повторного чтения содержимого пользовательских блоков переносимым способом. Эти функции обрабатывают преобразования порядка байтов, например, когда данные записываются на машине с малым порядком байтов и считываются обратно на машине с большим порядком байтов.
| Функция | Действие |
| 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 для заполнения части данных пользовательского блока.
20.9.5 Выбор идентификаторов
Идентификаторы в struct custom_operations необходимо выбирать тщательно, так как они должны однозначно идентифицировать структуру данных для операций сериализации и десериализации. В частности, следует учитывать включение номера версии в идентификатор; таким образом, формат данных можно изменить в будущем, но при этом можно предоставить функции десериализации, совместимые со старыми версиями.
Идентификаторы, начинающиеся с _ (символа нижнего подчеркивания), зарезервированы для системы времени выполнения OCaml; не используйте их для ваших пользовательских данных. Рекомендуется использовать URL (http://mymachine.mydomain.com/mylibrary/version-number) или имя пакета в стиле Java (com.mydomain.mymachine.mylibrary.version-number) в качестве идентификаторов, чтобы минимизировать риск столкновения идентификаторов.
20.9.6 Блоки finalize
Пользовательские блоки обобщают блоки finalize, которые присутствовали в OCaml до версии 3.00. Для обеспечения обратной совместимости формат пользовательских блоков совместим с форматом блоков finalize, и функция alloc_final по-прежнему доступна для выделения пользовательского блока с заданной функцией завершения, но со стандартными функциями сравнения, хэширования и сериализации. Функция caml_alloc_final(n, f, used, max) возвращает новый пользовательский блок размером n+1 слово, с функцией завершения f. Первое слово зарезервировано для хранения пользовательских операций; другие n слов доступны для ваших данных. Два параметра used и max используются для управления скоростью сборки мусора, как описано для caml_alloc_custom.
20.10 Дополнительная тема: Bigarrays и интерфейс OCaml-C
В этом разделе объясняется, как код-заглушка C, который связывает код C или Fortran с кодом OCaml, может использовать Bigarrays.
20.10.1 Файл заголовков
В файл заглушки C необходимо включить файл заголовков <caml/bigarray.h>. В нём объявлены функции, константы и макросы, обсуждаемые ниже.
20.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;
}
20.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);
}
20.11 Дополнительная тема: более быстрый вызов C
В этом разделе описано, как сделать вызовы функций C более быстрыми.
Примечание: это применимо только к нативному компилятору. Поэтому всякий раз, когда вы используете любой из этих методов, вам необходимо предоставить альтернативную заглушку байт-кода, которая игнорирует все специальные аннотации.
20.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]). Это связано с тем, что их размер часто отличается.
20.11.2 Прямой вызов C
Для возможности запуска сборщика мусора в середине функции C, компилятор OCaml нативного кода генерирует некоторые вспомогательные коды вокруг вызовов C. Технически он оборачивает каждый вызов C функцией C caml_c_call, которая является частью среды выполнения OCaml.
Для небольших функций, которые вызываются многократно, эта косвенность может существенно повлиять на производительность. Однако это не требуется, если известно, что функция C не выделяет память, не вызывает исключения и не освобождает главный замок (см. раздел 20.12.2). Мы можем указать компилятору OCaml нативного кода об этом факте, добавив атрибут [@@noalloc] к внешнему объявлению:
external bar : int -> int -> int = "foo" [@@noalloc]
В этом случае вызов bar из OCaml так же эффективен, как и вызов любой другой функции OCaml, за исключением того, что компилятор OCaml не может встроить функции C…
20.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. *)
20.12 Расширенная тема: многопоточность
Использование нескольких потоков (конкурентность с общей памятью) в смешанном приложении OCaml/C требует специальных мер предосторожности, которые описаны в этом разделе.
20.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.
20.12.2 Параллельное выполнение длительных функций C
Система выполнения OCaml не является рекурсивной: в любой момент времени не более одного потока может выполнять код 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.
Эти функции проверяют наличие ожидающих сигналов, вызывая асинхронные обратные вызовы (раздел 20.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);
}
Обратные вызовы из 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, обычно блокирующая операция ввода-вывода.
20.13 Расширенная тема: взаимодействие с API Windows Unicode
Этот раздел содержит общие рекомендации для написания C-заглушек, использующих API Windows Unicode.
Система OCaml под Windows может быть настроена во время сборки в одном из двух режимов:
- режим legacy: Все имена путей, переменные среды, аргументы командной строки и т. д. со стороны OCaml предполагаются закодированными с использованием текущей 8-битной кодовой страницы системы.
- режим Unicode: Все имена путей, переменные среды, аргументы командной строки и т. д. со стороны OCaml предполагаются закодированными с использованием UTF-8.
В дальнейшем мы говорим, что строка имеет кодировку OCaml, если она закодирована в UTF-8 в режиме Unicode, в текущей кодовой странице в режиме legacy или является произвольной строкой под Unix. Строка имеет платформенную кодировку, если она закодирована в UTF-16 под Windows или является произвольной строкой под Unix.
С точки зрения автора C-стыков, проблемы взаимодействия с API Windows Unicode двоякие:
- API Windows использует кодировку UTF-16 для поддержки Unicode. Система выполнения выполняет необходимые преобразования, так что программисту OCaml нужно работать только с кодировкой OCaml. C-стыки, которые вызывают API Windows 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
Остальная часть привязки одинакова для обеих платформ:
#define CAML_NAME_SPACE
#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);
}
20.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, поскольку они необходимы в разное время в зависимости от того, поддерживаются ли общие библиотеки.
20.15 Предупреждения: внутренний API выполнения
Не все заголовки, доступные в каталоге caml/, были описаны в предыдущих разделах. Все эти не упомянутые заголовки являются частью внутреннего API выполнения, для которого нет гарантии стабильности. Если вам действительно нужен доступ к этому внутреннему API выполнения, этот раздел предоставляет некоторые рекомендации, которые могут помочь вам написать код, который, возможно, не сломается при каждой новой версии OCaml.
Примечание
Разработчики, которые полагаются на внутренний API для использования, которое они считают реалистичным и полезным, могут подать запрос на улучшение в системе отслеживания ошибок.
20.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>
20.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/4.14/htmlman/intfc.html