Основная причина написания модуля Emacs — предоставить дополнительные функции программам на Lisp, которые загружают этот модуль. В этом подразделе описывается, как писать такие функции модулей.
Функция модуля имеет следующий общий вид и сигнатуру:
emacs_value emacs_function (emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) ¶Аргумент env предоставляет указатель на среду API,
необходимую для доступа к объектам и функциям Emacs. Аргумент
nargs — это требуемое количество аргументов, которое может быть равно
нулю (см. make_function ниже для более гибкой спецификации
количества аргументов), а args — это указатель на массив
аргументов функции. Аргумент data указывает на дополнительные
данные, необходимые функции, которые были подготовлены при вызове
make_function (см. ниже) для создания функции Emacs из
emacs_function.
Функции модулей используют тип emacs_value для передачи объектов Lisp
между Emacs и модулем (см. Преобразование между значениями Lisp и модулей). API,
описанный ниже и в следующих подразделах, предоставляет средства для
преобразования между базовыми типами данных C и соответствующими
объектами emacs_value.
В теле функции модуля не пытайтесь обращаться к элементам
массива args за пределами индекса
nargs-1: память для массива args выделяется
ровно для размещения nargs значений, и обращение за эти пределы,
скорее всего, приведёт к краху вашего модуля. В частности, если значение
nargs, переданное функции во время выполнения, равно нулю, она не должна
обращаться к args вообще, поскольку в этом случае память для него
не выделялась.
Функция модуля всегда возвращает значение. Если функция завершается
нормально, код Lisp, который её вызвал, увидит объект Lisp,
соответствующий значению emacs_value, возвращённому функцией.
Однако если пользователь нажал C-g или если функция модуля или её
вызываемые функции сгенерировали ошибку или вышли нелокально (см. Нелокальные выходы в модулях), Emacs проигнорирует возвращённое значение и выполнит выход или
передаст исключение, как это происходит, когда код Lisp сталкивается с теми же
ситуациями.
Заголовочный файл emacs-module.h предоставляет тип
emacs_function как псевдоним типа для указателя на функцию
модуля.
После написания C-кода для функции модуля вы должны создать из неё объект
функции Lisp с помощью функции make_function, указатель на которую
предоставляется в среде (напомним, что указатель на среду возвращается
функцией get_environment). Это обычно делается в функции
инициализации модуля (см. функция инициализации модуля) после проверки
совместимости API.
emacs_value make_function (emacs_env *env, ptrdiff_t min_arity, ptrdiff_t max_arity, emacs_function func, const char *docstring, void *data) ¶Эта функция возвращает функцию Emacs, созданную из C-функции func,
сигнатура которой описана выше для emacs_function.
Аргументы
min_arity и max_arity задают минимальное и максимальное
количество аргументов, которые может принимать func. Аргумент
max_arity может иметь специальное значение emacs_variadic_function,
которое делает функцию принимающей неограниченное число аргументов, как
ключевое слово &rest в Lisp (см. Особенности списков аргументов).
Аргумент data — это способ передать произвольные дополнительные
данные в func при её вызове. Любой указатель, переданный
в make_function, будет без изменений передан в
func.
Аргумент docstring задаёт строку документации для
функции. Это должна быть либо строка ASCII, либо строка в кодировке
UTF-8, не являющаяся ASCII, либо указатель NULL; в
последнем случае функция не будет иметь документации. Строка
документации может заканчиваться строкой, указывающей соглашение о вызове,
см. Строки документации функций.
Поскольку каждая функция модуля должна принимать указатель на среду
в качестве первого аргумента, вызов make_function можно выполнить
из любой функции модуля, но обычно это делается в функции инициализации
модуля, чтобы все функции модуля были известны Emacs сразу после загрузки
модуля.
Наконец, вы должны связать функцию Lisp с символом, чтобы код Lisp
мог вызывать вашу функцию по имени. Для этого используйте функцию
API модуля intern (см. intern), указатель на которую
также предоставляется в среде, доступной функциям модуля.
Объединяя вышеуказанные шаги, код, который организует вызов C-функции
module_func из Lisp как module-func, будет выглядеть так
(как часть функции инициализации модуля):
emacs_env *env = runtime->get_environment (runtime);
emacs_value func = env->make_function (env, min_arity, max_arity,
module_func, docstring, data);
emacs_value symbol = env->intern (env, "module-func");
emacs_value args[] = {symbol, func};
env->funcall (env, env->intern (env, "defalias"), 2, args);
Это делает символ module-func известным Emacs с помощью вызова
env->intern, затем вызывает defalias из Emacs для привязки
функции к этому символу. Обратите внимание, что можно использовать
fset вместо defalias; различия описаны
в defalias.
Функции модуля, включая функцию emacs_module_init
(см. функция инициализации модуля), могут взаимодействовать с Emacs
только путём вызова функций среды из некоторого живого указателя
emacs_env, находясь в стеке вызовов прямо или косвенно от Emacs.
Иными словами, если функция модуля хочет вызывать функции Lisp или
примитивы Emacs, преобразовывать объекты emacs_value в типы C
и обратно (см. Преобразование между значениями Lisp и модулей) или взаимодействовать с Emacs любым другим
способом, в стеке вызовов должен быть некоторый вызов из Emacs в
emacs_module_init или в функцию модуля. Функции модуля не могут
взаимодействовать с Emacs во время сборки мусора; см. Сборка мусора. Они могут взаимодействовать с Emacs только из потоков интерпретатора
Lisp (включая основной поток), созданных Emacs; см. Потоки.
Параметр командной строки --module-assertions может обнаружить
некоторые нарушения вышеуказанных требований. См. Начальные параметры в Руководстве GNU Emacs.
Используя API модуля, можно определять более сложные
функции и типы данных: встроенные функции, макросы и т.д. Однако
результирующий C-код будет громоздким и трудным для чтения. Поэтому мы
рекомендуем ограничить код модуля, создающий функции и структуры
данных, абсолютным минимумом, а остальное оставить для пакета Lisp,
который будет сопровождать ваш модуль, поскольку выполнение этих
дополнительных задач в Lisp гораздо проще и приводит к значительно более
читаемому коду. Например, имея функцию модуля
module-func, определённую выше, один из способов создания макроса
module-macro на её основе — использовать следующую простую обёртку
на Lisp:
(defmacro module-macro (&rest args) "Строка документации для макроса." (module-func args))
Пакет Lisp, сопровождающий ваш модуль, может затем загрузить модуль
с помощью примитива load (см. Динамические модули Emacs) при
загрузке пакета в Emacs.
По умолчанию функции модуля, созданные с помощью make_function, не
являются интерактивными. Чтобы сделать их интерактивными, вы можете
использовать следующую функцию.
void make_interactive (emacs_env *env, emacs_value function, emacs_value spec) ¶Эта функция, доступная начиная с Emacs 28, делает функцию
function интерактивной, используя спецификацию интерактивности
spec. Emacs интерпретирует spec так же, как аргумент
формы interactive. Использование interactive и
см. Кодовые символы для interactive. function должна быть функцией модуля Emacs,
возвращённой make_function.
Обратите внимание, что в модулях нет встроенной поддержки для получения
спецификации интерактивности функции модуля. Для этого используйте функцию
interactive-form. Использование interactive. Невозможно
сделать функцию модуля неинтерактивной после того, как вы сделали её
интерактивной с помощью make_interactive.
Если вы хотите выполнить некоторый код, когда объект функции модуля (т.е. объект,
возвращённый make_function) собирается сборщиком мусора, вы можете
установить финализатор функции. Финализаторы функций доступны
начиная с Emacs 28. Например, если вы передали некоторую структуру,
выделенную в куче, в аргумент data функции make_function, вы
можете использовать финализатор для освобождения этой структуры. См. (libc)Базовое
выделение и (libc)Освобождение после malloc. Функция
финализатора имеет следующую сигнатуру:
void finalizer (void *data)
Здесь data получает значение, переданное в data при вызове
make_function. Обратите внимание, что финализатор не может
взаимодействовать с Emacs каким-либо образом.
Сразу после вызова make_function у только что созданной
функции нет финализатора. Используйте set_function_finalizer,
чтобы добавить его, если необходимо.
void emacs_finalizer (void *ptr) ¶Заголовочный файл emacs-module.h предоставляет тип
emacs_finalizer как псевдоним типа для функции-финализатора
Emacs.
emacs_finalizer get_function_finalizer (emacs_env *env, emacs_value arg) ¶Эта функция, доступная начиная с Emacs 28, возвращает финализатор
функции, связанный с функцией модуля, представленной
arg. arg должна ссылаться на функцию модуля, т.е. на
объект, возвращённый make_function. Если с функцией не связан
никакой финализатор, возвращается NULL.
void set_function_finalizer (emacs_env *env, emacs_value arg, emacs_finalizer fin) ¶Эта функция, доступная начиная с Emacs 28, устанавливает финализатор
функции, связанный с функцией модуля, представленной arg,
в fin. arg должна ссылаться на функцию модуля, т.е. на
объект, возвращённый make_function. fin может быть либо
NULL для очистки финализатора функции arg, либо указателем на
функцию, которая будет вызвана, когда объект, представленный arg,
будет собран сборщиком мусора. Для каждой функции можно установить не более
одного финализатора; если у arg уже есть финализатор, он заменяется
на fin.