![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Представьте, что вы разрабатываете некую софтину, и хотите разделить её на части. Одна часть по жизни должна оставаться постоянной, а другая часть (или много частей) могут заменяться или подгружаться по потребности. Типичный пример: ядро операционной системы и программы юзера, или графический редактор и его плагины.
Между частями софтины имеется определённый программный интерфейс: обычно набор процедур/функций, заданный на некоем языке, скажем Си. Постоянная часть реализует эти функции и "экспортирует" их для потребления переменными частями. То есть имеем некоторый include-файл, скажем "api.h".
Переменные части, или плагины, удобно компилировать в виде разделяемых библиотек в позиционно независимом коде, чтобы можно было подгрузить и выполнить в любом удобном месте.
Внимание, вопрос: как скомпилировать такой плагин? Ведь его не с чем линковать. У нас нет никакой библиотеки типа "api.so", которую можно было бы подсунуть линкеру.
Разберём на примере. Пусть основной код предоставляет плагину всего одну функцию: tell_user(string). Вот такой файл api.h:
Оказывается, есть способ. Компилятор clang умеет извлекать из Си-шных исходников описание интерфейса в формате YAML. А отдельная утилита llvm-ifs может делать из этого описания бинарник разделяемой библиотеки для линкера.
Делаем раз, получаем описание интерфейса:
Подробная статья про внутреннее устройство ELF-файла с динамическим связыванием: https://mcuoneclipse.com/2021/06/05/position-independent-code-with-gcc-for-arm-cortex-m/
Между частями софтины имеется определённый программный интерфейс: обычно набор процедур/функций, заданный на некоем языке, скажем Си. Постоянная часть реализует эти функции и "экспортирует" их для потребления переменными частями. То есть имеем некоторый include-файл, скажем "api.h".
Переменные части, или плагины, удобно компилировать в виде разделяемых библиотек в позиционно независимом коде, чтобы можно было подгрузить и выполнить в любом удобном месте.
Внимание, вопрос: как скомпилировать такой плагин? Ведь его не с чем линковать. У нас нет никакой библиотеки типа "api.so", которую можно было бы подсунуть линкеру.
Разберём на примере. Пусть основной код предоставляет плагину всего одну функцию: tell_user(string). Вот такой файл api.h:
Простейший плагин, выдающий юзеру сообщение:void tell_user(const char *);
Пытаемся компилировать, естественно, получаем ошибку:#include "api.h"
void _start()
{
tell_user("Hi!");
}
Линкеру нужно подсунуть некую разделяемую библиотеку api.so, чтобы он увидел там внешний символ tell_user и настроил связи на него. Как сделать такую библиотеку?$ cc -shared -fPIC plugin.c -o plugin.elf
plugin.c: undefined reference to `tell_user'
Оказывается, есть способ. Компилятор clang умеет извлекать из Си-шных исходников описание интерфейса в формате YAML. А отдельная утилита llvm-ifs может делать из этого описания бинарник разделяемой библиотеки для линкера.
Делаем раз, получаем описание интерфейса:
Делаем два, компилируем интерфейс в бинарник api.so:$ clang -c -emit-interface-stubs api.h
$ cat api.ifs
--- !ifs-v1
IfsVersion: 3.0
Target: x86_64-apple-macosx13.0.0
Symbols:
- { Name: "tell_user", Type: Func }
...
Теперь можем скомпилировать наш плагин:$ llvm-ifs api.ifs --output-elf=api.so
$ file api.so
api.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), no program header, stripped
Дело сделано, бинарник плагина готов. Теперь можно заняться реализацией собственно основной софтины, загружающей и выполняющей этот бинарник.$ cc -shared -fPIC plugin.c api.so -o plugin.elf
$ size plugin.elf
text data bss dec hex filename
677 344 80 1101 44d plugin.elf
Подробная статья про внутреннее устройство ELF-файла с динамическим связыванием: https://mcuoneclipse.com/2021/06/05/position-independent-code-with-gcc-for-arm-cortex-m/
no subject
Date: 2023-04-20 05:22 (UTC)no subject
Date: 2023-04-20 05:50 (UTC)no subject
Date: 2023-04-20 05:57 (UTC)Обычно это реализуется через инициализацию плагина Callback-ом:
void InitPlugin(void (*tell_user)(char*));
На Си++ передав указатель на абстрактный класс со всем нужным API.
no subject
Date: 2023-04-20 06:14 (UTC)Не самое элегантное решение.
no subject
Date: 2023-04-20 06:37 (UTC)no subject
Date: 2023-04-20 07:29 (UTC)no subject
Date: 2023-04-20 08:11 (UTC)no subject
Date: 2023-04-20 07:10 (UTC)Ничего себе автоматизировали. Я бы в этом плагине требовал передачи инстанса, где это tell_user имплементировано.
no subject
Date: 2023-04-20 07:13 (UTC)no subject
Date: 2023-04-20 14:22 (UTC)Я имею в виду, что какой-то инстанс нужен же. Но в си какой-то другой менталитет. Не лямбда.
no subject
Date: 2023-04-20 18:35 (UTC)Это паспортному столу нужен мой инстанс.
no subject
Date: 2023-04-20 20:31 (UTC)С точки зрения S-комбинатора это звучит очень необычно. Может быть, оттуда и все эти странные идеи насчет DI идут.
no subject
Date: 2023-04-20 17:31 (UTC)Но их вроде как нельзя из интерфейса сгенерировать.
no subject
Date: 2023-04-20 21:09 (UTC)dumpbin.exe /exports foo.dll >foo.def
lib.exe /def:foo.def /out:foo.lib
То есть достаточно экспортировать нужный API в главном приложении.
no subject
Date: 2023-04-20 21:11 (UTC)no subject
Date: 2023-04-20 22:04 (UTC)особенно для Си где нет mangled function names
no subject
Date: 2023-04-20 22:07 (UTC)no subject
Date: 2023-04-22 17:02 (UTC)no subject
Date: 2023-04-22 17:56 (UTC)no subject
Date: 2023-04-23 07:44 (UTC)