В Си, учитывая переменный список аргументов, как построить вызов функции, используя их?
Предположим, что есть список аргументов, сохраненных как-то, например, в массиве.
Учитывая указатель на функцию, как я могу сделать вызов, передавая сохраненный список аргументов?
Я не пытаюсь передать массив в качестве аргумента, хорошо. Вы поняли, хорошо? Я хочу передать каждый из его элементов в качестве аргумента. Массив просто для иллюстрации, я мог бы хранить аргументы в некоторой структуре кортежа. Кроме того, посмотрите, что у меня есть указатель на функцию и может иметь подпись в строковом формате. Я не пытаюсь просто определить функцию, которая способна иметь дело со списком переменных.
Единственный способ понять, как это сделать - использовать сборку __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;
}
Мне не очень нравится такой подход...
Есть ли другая портативная и более короткая форма?
Несколько языков сценариев могут вызывать функции Си.
Как языки сценариев, такие как Python или Ruby, делают это? Как они реализуют это портативным способом? Они просто используют сборку для нескольких платформ или выше в конце?
Посмотрите, что я действительно не спрашиваю о деталях маршалинга параметров и других вещах от языков сценариев до C, меня интересует только то, как, в конце концов, внутренне, вызывается функция C языком сценариев.
РЕДАКТИРОВАТЬ
Я сохраню название вопроса, но думаю, что лучший способ его задать:
Как вызвать функцию C, указатель и подпись которой доступны только во время выполнения?
ОБНОВИТЬ
Из внешнего интерфейса для схемы PLT:
Вызов - это обычный вызов функции. В динамической настройке мы создаем объект "call-interface", который задает (двоичные) типы ввода / вывода; этот объект может использоваться с произвольным указателем на функцию и массивом входных значений для выполнения вызова функции и получения ее результата. Для этого требуется манипулировать стеком и знать, как вызывается функция, это детали, с которыми имеет дело libffi.
Спасибо @AnttiHaapala за поиск, поиск и указание на libffi. Это то, что я искал, это используется кучей языков сценариев, это переносимая библиотека, реализованная в нескольких архитектурах и компиляторах.
4 ответа
Вы спросили, что является переносимым способом для вызова любого указателя на функцию с заданным количеством аргументов. Правильный ответ - такого пути нет.
Например, python может вызывать функции C через модуль ctypes, но это переносимо только до тех пор, пока вы знаете точный прототип и соглашения о вызовах. В C самый простой способ добиться того же - знать прототип указателя на функцию во время компиляции.
Обновить
Например, для python / ctypes на каждой платформе, в которой включен модуль ctypes, python знает, как написать вызывающий стек для заданного набора аргументов. Например, в Windows python знает 2 стандартных соглашения о вызовах - cdecl с C-порядком параметров в стеке и stdcall с "порядком в стиле Паскаля". В Linux нужно беспокоиться о том, вызывать ли 32- или 64-битные разделяемые объекты и т. Д. Если python скомпилирован на другой платформе, ctypes также нуждается в изменениях; C-код в модуле ctypes как таковой не является переносимым.
Обновление 2
Для Python магия здесь: исходный код ctypes. В частности, кажется, что это ссылка http://sourceware.org/libffi/ которая может быть именно тем, что вам нужно.
@AnttiHaapala указал на libffi. Вот некоторая информация об этом:
Что такое либфи?
Некоторые программы могут не знать во время компиляции, какие аргументы должны быть переданы в функцию. Например, интерпретатору можно сообщить во время выполнения о количестве и типах аргументов, используемых для вызова данной функции. "libffi" может использоваться в таких программах для обеспечения моста между программой-интерпретатором и скомпилированным кодом.
Библиотека 'libffi' предоставляет переносимый высокоуровневый интерфейс программирования для различных соглашений о вызовах. Это позволяет программисту вызывать любую функцию, указанную в описании интерфейса вызова, во время выполнения.
FFI обозначает Интерфейс Внешней Функции. Интерфейс сторонней функции - это популярное название интерфейса, которое позволяет коду, написанному на одном языке, вызывать код, написанный на другом языке. Библиотека 'libffi' действительно предоставляет только самый нижний, машинно-зависимый уровень полнофункционального интерфейса сторонних функций. Над уровнем libffi должен существовать слой, который обрабатывает преобразования типов для значений, передаваемых между двумя языками.
'libffi' предполагает, что у вас есть указатель на функцию, которую вы хотите вызвать, и что вы знаете количество и типы аргументов для ее передачи, а также тип возвращаемого значения функции.
Историческая справка
libffi, первоначально разработанный Энтони Грин (пользователь SO: 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 для выполнения преобразования, в зависимости от типа. Таким образом, это не просто прямая передача указателя, и это, конечно, не требует ассемблера.