Spec-Zone.ru › Nim

Интеграция Nim Backend

Исходный кодРедактировать

"Ересь проистекает из безделья." — Неизвестно.

Введение

Руководство пользователя компилятора Nim (Nim Compiler User Guide) описывает типичное вызов компилятора, используя команду compile или c, чтобы преобразовать файл .nim в один или несколько файлов .c, которые затем компилируются с помощью C-компилятора платформы в статический двоичный файл. Однако есть и другие команды для компиляции в C++, Objective-C или JavaScript. Этот документ пытается собрать все варианты бэкенда и интерфейса в одном месте.

Компилятор Nim поддерживает в основном две семейства бэкэндов: цели C, C++ и Objective-C, а также цель JavaScript. Цели типа C создают исходные файлы, которые могут быть скомпилированы в библиотеку или конечный исполняемый файл. Цель JavaScript может сгенерировать файл .js, который вы ссылаетесь из файла HTML, или создать самостоятельную программу Node.js.

Помимо генерации библиотек или автономных приложений, Nim предлагает двусторонний интерфейс с целевыми бэкендами с помощью общих и специфических псевдокоманд.

Бэкэнды

Цели типа C

Команды для компиляции в C, C++ или Objective-C:

compileToC, cc
компиляция проекта с генератором кода C
compileToCpp, cpp
компиляция проекта в код C++
compileToOC, objc
компиляция проекта в код Objective C

Самое существенное различие между этими командами заключается в том, что если вы посмотрите в директорию nimcache, вы найдете файлы .c, .cpp или .m, но все они создадут нативный двоичный файл для вашего проекта. Это позволяет вам взять сгенерированный код и поместить его непосредственно в проект, используя любой из этих языков. Вот некоторые типичные вызовы командной строки:

nim c hallo.nim
nim cpp hallo.nim
nim objc hallo.nim

Команды компилятора выбирают целевой бэкенд, но при необходимости вы можете указать дополнительные переключатели для кросс-компиляции, чтобы выбрать целевой процессор, операционную систему или команды компилятора/линкера.

Цель JavaScript

Nim также может генерировать код JavaScript с помощью команды js.

Nim ориентируется на JavaScript 1.5, который поддерживается любым широко используемым браузером. Поскольку JavaScript не имеет портативного способа включения другого модуля, Nim просто генерирует длинный файл .js.

Функции или модули, которые не поддерживаются платформой JavaScript, недоступны. К ним относятся:

  • управление памятью вручную (alloc, и т.д.)
  • приведение типов и другие небезопасные операции (оператор cast, zeroMem, и т.д.)
  • управление файлами
  • операции, зависящие от ОС
  • потоки, сопрограммы
  • некоторые модули стандартной библиотеки
  • правильная арифметика 64-битных целых чисел

В качестве компенсации, стандартная библиотека имеет модули специализированные для бэкенда JS, и в будущем будет добавлена ​​большая поддержка (например, привязки Node.js для получения информации об ОС).

Для компиляции модуля Nim в файл .js используйте команду js; по умолчанию создается файл .js, который должен быть указан в файле .html. Однако вы также можете запустить код с помощью nodejs (http://nodejs.org):

nim js -d:nodejs -r examples/hallo.nim

Если вы видите ошибки, говорящие о том, что globalThis не определено, убедитесь, что вы используете последнюю версию Node.js (по крайней мере 12.0).

Интерфейс

Nim предлагает двусторонний интерфейс с целевым бэкендом. Это означает, что вы можете вызывать код бэкенда из Nim, а код Nim может вызываться кодом бэкенда. Обычно направление, в котором осуществляется вызов, зависит от вашей архитектуры программного обеспечения (является ли Nim вашей основной программой или Nim предоставляет компонент?).

Код Nim, вызывающий бэкенд

Код Nim может взаимодействовать с бэкендом через интерфейс внешних функций в основном через псевдокоманду importc. Псевдокоманда importc является общепринятым способом обеспечения доступности символов бэкенда в Nim и доступна во всех целевых бэкендах (в том числе JavaScript). Бэкэнды C++ или Objective-C имеют свои соответствующие псевдокоманды ImportCpp и ImportObjC для вызова методов из классов.

Всякий раз, когда вы используете любую из этих псевдокоманд, вам необходимо интегрировать нативный код в ваш конечный двоичный файл. В случае JavaScript это не проблема, тот же файл HTML, который содержит сгенерированный JavaScript, вероятно, предоставляет другие функции JavaScript, которые вы импортируете с помощью importc.

Однако для целей типа C вам нужно связать внешний код статически или динамически. Предпочтительный способ интеграции нативного кода — использование динамической компоновки, так как это позволяет компилировать программы Nim, не требуя установки соответствующих библиотек разработки. Это делается с помощью псевдокоманды dynlib для импорта, хотя для получения более точного управления можно использовать модуль dynlib.

Переключатель командной строки dynlibOverride позволяет избежать динамической компоновки, если вам нужно выполнить статическую компоновку вместо этого. Оболочки Nim, предназначенные для статической компоновки исходных файлов, могут использовать псевдокоманду compile, если источников немного или их предоставление вместе с кодом Nim проще, чем использование системной библиотеки. Библиотеки, установленные на системе, можно связать с помощью псевдокоманды PassL.

Чтобы обернуть нативный код, обратитесь к инструменту c2nim, который помогает в процессе сканирования и преобразования заголовочных файлов в интерфейс Nim.

Пример вызова C

Создайте файл logic.c со следующим содержимым:

int addTwoIntegers(int a, int b)
{
  return a + b;
}

Создайте файл calculator.nim со следующим содержимым:

{.compile: "logic.c".}
proc addTwoIntegers(a, b: cint): cint {.importc.}

when isMainModule:
  echo addTwoIntegers(3, 7)

При наличии этих двух файлов вы можете запустить nim c -r calculator.nim, и компилятор Nim скомпилирует файл logic.c помимо calculator.nim и свяжет оба в исполняемый файл, который выводит 10 при запуске. Другой способ статически связать файл C и получить тот же эффект — удалить строку с псевдокомандой compile и выполнить следующие типичные команды Unix:

gcc -c logic.c
ar rvs mylib.a logic.o
nim c --passL:mylib.a -r calculator.nim

Так же, как в этом примере, мы передаем путь к библиотеке mylib.a (и мы могли бы передать и logic.o), мы могли бы передать переключатели для связывания любых других статических библиотек C.

Пример вызова JavaScript

Создайте файл host.html со следующим содержимым:

<html><body>
<script type="text/javascript">
function addTwoIntegers(a, b)
{
  return a + b;
}
</script>
<script type="text/javascript" src="calculator.js"></script>
</body></html>

Создайте файл calculator.nim со следующим содержимым (или используйте файл из предыдущего раздела):

proc addTwoIntegers(a, b: int): int {.importc.}

when isMainModule:
  echo addTwoIntegers(3, 7)

Скомпилируйте код Nim в JavaScript с помощью nim js -o:calculator.js calculator.nim и откройте файл host.html в браузере. Если браузер поддерживает JavaScript, вы увидите значение 10 в консоли браузера. Используйте модуль dom для запросов и модификаций DOM или посмотрите на karax, как разрабатывать приложения на основе браузера.

Код бэкенда, вызывающий Nim

Код бэкенда может взаимодействовать с кодом Nim, экспонированным через псевдокоманду exportc. Псевдокоманда exportc является общепринятым способом обеспечения доступности символов Nim в бэкендах. По умолчанию компилятор Nim будет изменять все символы Nim, чтобы избежать конфликтов имен, поэтому псевдокоманда exportc делает в основном то, что сохраняет имя символа Nim, или, если указано, использует альтернативное имя символа для бэкенда в случае, если правила символов не совпадают.

У цели JavaScript нет дополнительных соображений по интерфейсу, так как у нее также есть сборка мусора, но цели C требуют от вас инициализации внутренней среды Nim, что делается с помощью вызова функции NimMain. Кроме того, код C требует от вас указания предварительной декларации для функций, иначе компилятор предположит определенные типы для возвращаемого значения и параметров, что, вероятно, приведет к сбою вашей программы во время выполнения.

Имя NimMain может быть изменено с помощью переключателя --nimMainPrefix:prefix. Используйте --nimMainPrefix:MyLib, и имя вызываемой функции — MyLibNimMain.

При компиляции в статические или динамические библиотеки они не вызывают деструкторов глобальных переменных, как это делают обычные программы Nim. Для вызова этих глобальных деструкторов предоставляется C API NimDestroyGlobals.

Пример вызова Nim из C

Создайте файл fib.nim со следующим содержимым:

proc fib(a: cint): cint {.exportc.} =
  if a <= 2:
    result = 1
  else:
    result = fib(a - 1) + fib(a - 2)

Создайте файл maths.c со следующим содержимым:

#include <stdio.h>

int fib(int a);
void NimMain();

int main(void)
{
  NimMain();
  for (int f = 0; f < 10; f++)
    printf("Fib of %d is %d\n", f, fib(f));
  return 0;
}

Теперь вы можете выполнить следующие команды Unix, чтобы сначала сгенерировать исходные файлы C из кода Nim, а затем связать их в статический двоичный файл вместе с вашей основной программой C:

nim c --noMain --noLinking fib.nim
gcc -o m -I$HOME/.cache/nim/fib_d -Ipath/to/nim/lib $HOME/.cache/nim/fib_d/*.c maths.c

Первая команда запускает компилятор Nim с тремя специальными параметрами, чтобы избежать генерации функции main() в сгенерированных файлах и избежать связывания объектных файлов в окончательный двоичный файл. Все сгенерированные файлы помещаются в директорию nimcache. Поэтому следующая команда компилирует исходный файл maths.c плюс все файлы .c из nimcache. Кроме этого пути, вы также должны указать компилятору C, где найти заголовочный файл Nim nimbase.h.

Вместо того, чтобы полагаться на генерацию отдельных файлов .c , вы также можете попросить компилятор Nim сгенерировать статически связанную библиотеку:

nim c --app:staticLib fib.nim
gcc -o m -Inimcache -Ipath/to/nim/lib maths.c libfib.nim.a

Компилятор Nim будет обрабатывать компоновку исходных файлов, сгенерированных в каталоге nimcache, в статическую библиотеку libfib.nim.a, которую затем можно подключить к вашей программе на C. Обратите внимание, что эти команды являются общими и будут различаться для каждой системы. Например, в системах Linux, вероятно, потребуется использовать -ldl также для подключения необходимой функциональности dlopen.

Пример вызова Nim из JavaScript

Создайте файл mhost.html со следующим содержимым:

<html><body>
<script type="text/javascript" src="fib.js"></script>
<script type="text/javascript">
alert("Fib for 9 is " + fib(9));
</script>
</body></html>

Создайте файл fib.nim со следующим содержимым (или используйте файл из предыдущего раздела):

proc fib(a: cint): cint {.exportc.} =
  if a <= 2:
    result = 1
  else:
    result = fib(a - 1) + fib(a - 2)

Скомпилируйте код Nim в JavaScript с помощью nim js -o:fib.js fib.nim и откройте mhost.html в браузере. Если браузер поддерживает JavaScript, вы должны увидеть окно с всплывающим сообщением, отображающим текст Fib for 9 is 34. Как упоминалось ранее, JavaScript не требует вызова инициализации NimMain или подобной функции, и вы можете напрямую вызвать экспортированную процедуру Nim.

Логика именования nimcache

Каталог nimcache генерируется во время компиляции и будет содержать либо временные, либо окончательные файлы в зависимости от целевого бэкэнда. Имя каталога по умолчанию зависит от используемого бэкэнда и вашей операционной системы, но вы можете использовать переключатель компилятора --nimcache переключатель компилятора, чтобы изменить его.

Управление памятью

В предыдущих разделах появлялась функция NimMain(). Поскольку JavaScript уже предоставляет автоматическое управление памятью, вы можете свободно передавать объекты между двумя языками без проблем. В языках C и производных языках нужно быть осторожным с тем, что вы делаете и как вы обмениваетесь памятью. Предыдущие примеры затрагивали только простые скалярные значения, но передача строки Nim в C или чтение строки C в Nim уже требует осознания того, кто контролирует что, чтобы избежать сбоев.

Строки и C-строки

Документация упоминает, что строки Nim неявно преобразуются в cстроки, что делает взаимодействие обычно безболезненным. Большинство функций C, принимающих строку Nim, преобразованную в cstring, скорее всего, не будут сохранять эту строку, и к моменту возврата строки она больше не потребуется.

Аналогичная ситуация происходит, когда код C вызывает код Nim, который возвращает cstring. Рассмотрим следующую процедуру:

proc gimme(): cstring {.exportc.} =
  result = "Hey there C code! " & $rand(100)

Поскольку механизм подсчета ссылок Nim не осведомлен о коде C, по завершении процедуры gimme она может освободить память cstring.

Пользовательские типы данных

Так же, как и строки, пользовательские типы данных, которые должны обмениваться между Nim и бэкэндом, потребуют тщательного рассмотрения того, кто контролирует кого. Если вы хотите передать ссылку Nim в код C, вам необходимо использовать GC_ref, чтобы пометить ссылку как используемую, чтобы она не была освобождена. А для бэкэнда C вам потребуется экспортировать процедуру GC_unref для очистки этой памяти, когда она больше не нужна.

Опять же, если вы обертываете библиотеку, которая использует malloc и free для структур данных, вам нужно экспортировать соответствующую функцию free в Nim, чтобы вы могли очистить её. И, конечно же, после очистки вы должны избегать доступа к ней из Nim (или C). Обычно структуры данных C имеют свои собственные функции malloc_structure и free_structure, поэтому обертывание этих функций для стороны Nim должно быть достаточно.

© 2006–2024 Andreas Rumpf
Licensed under the MIT License.
https://nim-lang.org/docs/backends.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API