Ответ 1
Я являюсь автором libffi. Он будет делать то, что вы просите.
Предположим, что список аргументов хранится как-то, например, в массиве.
Указатель функции, как я могу позвонить ему, передав сохраненный список аргументов?
Я не пытаюсь передать массив как аргумент ok. Вы поняли, хорошо? Я хочу передать каждый из его элементов в качестве аргумента. Массив просто для иллюстрации, я мог бы хранить аргументы в некоторой структуре кортежа. Кроме того, посмотрите, что у меня есть указатель на функцию и может иметь подпись в формате строки. Я не пытаюсь просто определить функцию, которая может работать с вариационным списком.
Единственный способ, которым я вижу, как это сделать, - использовать сборку (через __asm push
и др.) или это:
void (*f)(...);
int main()
{
f = <some function pointer>;
int args[]; <stored in a array, just to illustrate>
int num_args = <some value>;
switch(num_args)
{
case 0:
f();
break;
case 1:
f(args[0]);
break;
case 2:
f(args[0], args[1]);
break;
/* etc */
}
return 0;
}
Мне слишком не нравится этот подход...
Есть ли другая портативная и более короткая форма?
Несколько языков script могут вызывать функции C.
Как работают script языки, такие как Python или Ruby? Как они реализуют его переносимым образом? Они просто используют сборку для нескольких платформ или выше в конце?
Посмотрите, что я действительно не спрашиваю о деталях маршалинга параметров и других материалах от языков script до C, меня интересует только то, как, в конце концов, внутренний вызов функции C с помощью script.
Я сохраню заголовок вопроса, но я думаю, что лучший способ задать вопрос:
Как вызвать функцию C со своим указателем и сигнатурой, доступной только во время выполнения?
От Внешний интерфейс для схемы PLT:
Вызов - это вызов нормальной функции. В динамическом режиме, мы создаем объект "call-interface", который специфицирует (двоичный) типы ввода/вывода; этот объект можно использовать с произвольным указатель функции и массив входных значений для выполнения выноска функции и получения ее результата. Для этого требуется манипулируя стеком и зная, как называется функция, это детали, с которыми работает libffi.
Спасибо @AnttiHaapala за поиск, поиск и наведение libffi. Это то, что я искал, оно используется связкой языков script, это портативная библиотека, реализованная в нескольких архитектурах и компиляторах.
Я являюсь автором libffi. Он будет делать то, что вы просите.
Вы спросили, что такое переносимый способ вызова любого указателя функции с заданным количеством аргументов. Правильный ответ заключается в том, что такого пути нет.
Например, python может вызывать функции C через модуль ctypes, но он переносится только до тех пор, пока вы знаете точные прототипы и вызовы. В C самым простым способом добиться того же - знать прототип указателя функции во время компиляции.
Для примера python/ctypes на каждой платформе, на которой включен модуль ctypes, python знает, как записать вызывающий стек для заданного набора аргументов. В Windows, например, python знает два стандартных соглашения о вызовах - cdecl с C параметрами порядка в стеке и stdcall с "упорядочением стиля pascal". В Linux необходимо беспокоиться о том, следует ли вызывать 32 или 64-битные общие объекты и т.д. Если python скомпилирован на другую платформу, ctypes также нуждается в изменениях; C-код в модуле ctypes не является, как таковой, переносимым.
Для Python здесь находится волшебство: ctypes исходный код. Примечательно, что ссылка http://sourceware.org/libffi/ может быть именно тем, что вам нужно.
@AnttiHaapala указала libffi. Вот некоторая информация об этом:
Что такое libffi?
Некоторые программы могут не знать во время компиляции, какие аргументы должны быть переданы функции. Например, во время выполнения интерпретатору может быть рассказано о количестве и типах аргументов, используемых для вызова данной функции. "Libffi может использоваться в таких программах, чтобы обеспечить мост от программы-переводчика до скомпилированного кода.
Библиотека libffi предоставляет переносимый высокоуровневый программный интерфейс для различных соглашений вызова. Это позволяет программисту вызывать любую функцию, указанную в описании интерфейса вызова во время выполнения.
FFI означает интерфейс внешней функции. Интерфейс внешних функций - это популярное имя интерфейса, которое позволяет коду, написанному на одном языке, вызывать код, написанный на другом языке. Библиотека libffi действительно обеспечивает только самый низкий, зависящий от машины уровень полнофункционального интерфейса внешней функции. Уровень должен существовать над libffi, который обрабатывает преобразования типов для значений, передаваемых между двумя языками.
'libffi предполагает, что у вас есть указатель на функцию, которую вы хотите вызвать, и что вы знаете количество и типы аргументов для ее передачи, а также возвращаемый тип функции.
libffi, первоначально разработанный Энтони Грин (SO user: anthony-green), был вдохновлен библиотекой Gencall от Silicon Graphics. Gencall был разработан Джанни Мариани, затем нанят SGI, чтобы разрешить вызовы функций по адресам и создать кадр вызова для конкретного соглашения о вызовах. Энтони Грин уточнил эту идею и распространил ее на другие архитектуры и вызывая соглашения и открытый источник libffi.
#include <stdio.h>
#include <math.h>
#include <ffi.h>
int main()
{
ffi_cif call_interface;
ffi_type *ret_type;
ffi_type *arg_types[2];
/* pow signature */
ret_type = &ffi_type_double;
arg_types[0] = &ffi_type_double;
arg_types[1] = &ffi_type_double;
/* prepare pow function call interface */
if (ffi_prep_cif(&call_interface, FFI_DEFAULT_ABI, 2, ret_type, arg_types) == FFI_OK)
{
void *arg_values[2];
double x, y, z;
/* z stores the return */
z = 0;
/* arg_values elements point to actual arguments */
arg_values[0] = &x;
arg_values[1] = &y;
x = 2;
y = 3;
/* call pow */
ffi_call(&call_interface, FFI_FN(pow), &z, arg_values);
/* 2^3=8 */
printf("%.0f^%.0f=%.0f\n", x, y, z);
}
return 0;
}
Я думаю, что могу утверждать, что libffi - это переносимый способ делать то, что я просил, вопреки утверждению Антти Хаапалы о том, что такого пути нет. Если мы не можем назвать libffi переносимой технологией, учитывая, насколько она портирована/реализована в разных компиляторах и архитектурах, а какой интерфейс соответствует стандарту C, мы также не можем назвать C или что-нибудь портативное.
Информация и история извлечены из:
https://github.com/atgreen/libffi/blob/master/doc/libffi.info
В целях безопасности вы должны распаковать переменные до их отправки. Использование ассемблера для взлома стека параметров может быть несовместимым между компиляторами. Соглашения о вызовах могут отличаться.
Я не могу говорить за Ruby, но я написал довольно много программ, используя интерфейсы C для Perl и Python. Переменные Perl и Python напрямую не сопоставляются с переменными C, у них есть еще много функций. Например, скаляр Perl может иметь двойные строковые и числовые значения, только один из которых действителен в любой момент времени.
Преобразование между переменными Perl/Python и C выполняется с помощью pack
и unpack
(в модуле struct
в Python). В интерфейсе C вы должны вызвать определенные API для выполнения преобразования, в зависимости от типа. Таким образом, это не просто прямая передача указателя, и это, конечно, не связано с ассемблером.