LINUX.ORG.RU

Вызов функции по ссылке с передачей всех аргументов

 ,


0

2

Каким образом можно вызвать функцию по ссылке с передачей всех аргументов?

На данный момент у меня получился такой код:

#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)

typedef void*(*any_fptr)(...);

EXTERN_DLL_EXPORT void* invoke5(const any_fptr &fptr, void** arg1, void** arg2, void** arg3, void** arg4, void** arg5) {
  return fptr(arg1, arg2, arg3, arg4, arg5);
}

EXTERN_DLL_EXPORT void* invoke10(const any_fptr &fptr, void** arg1, void** arg2, void** arg3, void** arg4, void** arg5, void** arg6, void** arg7, void** arg8, void** arg9, void** arg10) {
  return fptr(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
}

Опытным путем установлено, что необязательно указывать все аргументы функций invoke5 / invoke10, т.е. вот так invoke работает:

invoke5(&fptr, arg1, arg2);
invoke10(&fptr, arg1, arg2, arg3);

Хочу сделать реализацию красивее, что-то вроде:

EXTERN_DLL_EXPORT void* invoke(const any_fptr &fptr, void** args...) {
  return fptr(args);
}

Но не понимаю как это сделать без шаблонов.

Функция invoke обязательно должна экспортироваться.

★★

Ответ на: комментарий от zerhud

Так это же шаблон или я чего-то не понимаю?

Я же не могу сделать что-то вроде:

EXTERN_DLL_EXPORT void* invoke(const any_fptr &fptr, void** args...) {
  return std::invoke(fptr, args);
}

?

SaBo ★★ ()
Ответ на: комментарий от SaBo

ну, тело другое будет. только так сделать в любом случае не получится. void** это для массива, надо как-то указывать его конец. и вызывать надо будет foo(fptr, {a1,a2,a3}). тогда можно и экспортировать.

zerhud ()

Пока ты используешь cdecl без цпп name mangling, то количество и тип аргументов никак не влияют на имя символа. Ты можешь спокойно экспортировать так:

extern "C" void f(int cnt_or_format, ...) {} // + мелкомягкая __declspec(dllexport)

для парсинга на вызывающей стороне https://en.cppreference.com/w/cpp/language/variadic_arguments.

pavlick ★★ ()
Последнее исправление: pavlick (всего исправлений: 1)

Опытным путем установлено, что необязательно указывать все аргументы функций invoke5 / invoke10

это фича си. функция f() принимает переменное количество аргументов, f(void) принимает ноль аргументов. про эпсилон (...) тебе уже сказали.

anonymous ()
Ответ на: комментарий от anonymous

Почему тогда такой финт не работает?

template <typename... Args>
void* invokeFptr(const any_fptr &fptr, Args... args) {
    typedef void*(*Function)(Args...);
    return ((Function)fptr)(args...);
}

EXTERN_DLL_EXPORT void* invoke(const any_fptr &fptr, void** args...) {
    return invokeFptr(fptr, args);
}
SaBo ★★ ()
Ответ на: комментарий от SaBo

А кто тебе сказал, что он должен работать? Ты хочешь инстанцировать шаблон для неизвестного кол-ва аргументов, это будет ясно лишь в рантайме. Если бы ходил по ссылке, то увидел бы как парсят:

#include <iostream>
#include <cstdarg>
 
int add_nums(int count, ...) 
{
    int result = 0;
    std::va_list args;
    va_start(args, count);
    for (int i = 0; i < count; ++i) {
        result += va_arg(args, int);
    }
    va_end(args);
    return result;
}

Можешь пихать аргументы в array, например, который передашь куда следует.

pavlick ★★ ()
Ответ на: комментарий от pavlick

А чем мне va_list поможет? Не понимаю...

Я же не могу сделать как-то так:

typedef void* (*va_fptr)(va_list);

int func3(int a, int b) {
    return a * b;
}

void *invoke(va_fptr &fptr, ...) {
  va_list args;
  ...
  va_fptr f = (va_fptr)func3;
  return f(args);
}

Мне нужно как-то va_list передать в функцию, которая ничего о va_list не знает, но при этом принимает содержащиеся в ней аргументы.

SaBo ★★ ()
Последнее исправление: SaBo (всего исправлений: 1)
Ответ на: комментарий от SaBo

Его не надо передавать. Перед тобой лишь примерно такая возможность:

typedef /*тип аргументов*/ arg_t;

void *func3(arg_t *args, unsigned args_cnt) {
    if (args < 2)
       return 0;
    return args[0]*args[1] > 0 ? nullptr : nullptr;
}

void *invoke(va_fptr &fptr, ...) {
  arg_t args[MAX_ARGS];
  unsigned args_cnt = 0;
  va_list args;
  // парсим аргументы, пихаем их в args;
  ...
  return func3(args, args_cnt);
}

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

pavlick ★★ ()
Ответ на: комментарий от SaBo

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

объясни ещё раз, что ты хочешь сделать?

anonymous ()
Ответ на: комментарий от pavlick

У меня есть две системы:

- одна на C#

- вторая умеет дергать dll, но не умеет дергать функции по ссылке

Так вот вторая, поднимает хост .NET, получает экспортируемые методы первой и они являются ссылками на функции, но ссылки вторая система вызывать не умеет. По этому есть ещё invoke.dll, которому передается ссылка на функцию и список аргументов. Всё это работает как написано в первом сообщении.

Я хочу просто сократить invoke5(5 аргументов) и invoke10(10 аргументов) до invoke(с любым количеством аргументов).

SaBo ★★ ()
Последнее исправление: SaBo (всего исправлений: 2)

Вот есть отличный способ:

EXTERN_DLL_EXPORT void* invoke_gcc(const any_fptr3 &fptr, ...) {
    void *args = __builtin_apply_args();
    void *ret = __builtin_apply(fptr, args, 200);
    __builtin_return(ret);
}

Но проблема в том, что __builtin_apply_args, похоже, передает все аргументы включая fptr.

SaBo ★★ ()

Тут возможно понадобится ассемблер для подготовки аргументов в соответствии соглашения вызовов. Также нужен тип аргументов, т.к. аргументы могут иметь разный размер и передаваться в разных регистрах, например float/double может передаваться в отдельных рагистрах с плавающей точкой.

В Обероне есть универсальный вызов процедур (x86 32 bit):

(*
  type        par
  32 bit scalar  value
  64 bit scalar  low hi
  var scalar    address
  record      address tag
  array      address size
  open array  address length_N-1 .. length_0
*)
PROCEDURE Call* (adr: INTEGER; sig: Signature; IN par: ARRAY OF INTEGER; n: INTEGER): LONGINT;
  VAR p, kind, sp, size: INTEGER; typ: Type; r: REAL;
BEGIN
  p := sig.num;
  WHILE p > 0 DO  (* push parameters from right to left *)
    DEC(p);
    typ := sig.par[p].struct;
    kind := sig.par[p].id MOD 16;
    IF (S.VAL(INTEGER, typ) DIV 256 = 0) OR (typ.id MOD 4 IN {0, 3}) THEN  (* scalar, ANYREC *)
      IF (kind = 10) & ((S.VAL(INTEGER, typ) = 8) OR (S.VAL(INTEGER, typ) = 10)) THEN  (* 64 bit *)
        DEC(n); PUSH(par[n])  (* push hi word *)
      ELSIF S.VAL(INTEGER, typ) = 11 THEN   (* ANYREC *)
        ASSERT(kind # 10); (* not a value par, also checked by compiler *)
        DEC(n); PUSH(par[n])   (* push tag *)
      END;
      DEC(n); PUSH(par[n])  (* push value/address *)
    ELSIF typ.id MOD 4 = 1 THEN  (* record *)
      IF kind # 10 THEN  (* var par *)
        DEC(n); PUSH(par[n]);  (* push tag *)
        DEC(n); PUSH(par[n])  (* push address *)
      ELSE
        DEC(n, 2);  (* skip tag *)
        S.GETREG(SP, sp); sp := (sp - typ.size) DIV 4 * 4; S.PUTREG(SP, sp);  (* allocate space *)
        S.MOVE(par[n], sp, typ.size)  (* copy to stack *)
      END
    ELSIF typ.size = 0 THEN  (* open array *)
      size := typ.id DIV 16 MOD 16;  (* number of open dimensions *)
      WHILE size > 0 DO
        DEC(size); DEC(n); PUSH(par[n])  (* push length *)
      END;
      DEC(n); PUSH(par[n])  (* push address *)
    ELSE  (* fix array *)
      IF kind # 10 THEN  (* var par *)
        DEC(n, 2); PUSH(par[n])  (* push address *)
      ELSE
        DEC(n); size := par[n]; DEC(n);
        S.GETREG(SP, sp); sp := (sp - size) DIV 4 * 4; S.PUTREG(SP, sp);  (* allocate space *)
        S.MOVE(par[n], sp, size)  (* copy to stack *)
      END
    END
  END;
  ASSERT(n = 0);
  IF S.VAL(INTEGER, sig.retStruct) = 7 THEN  (* shortreal *)
    CALL(adr);
    RETURN S.VAL(INTEGER, SHORT(RETR()))  (* return value in fpu register *)
  ELSIF S.VAL(INTEGER, sig.retStruct) = 8 THEN  (* real *)
    CALL(adr); r := RETR();
    RETURN S.VAL(LONGINT, r)  (* return value in fpu register *)
  ELSE
    CALL(adr);
    RETURN RETI()  (* return value in integer registers *)
  END
END Call;
X512 ★★★★ ()
Ответ на: комментарий от SaBo

Я хочу просто сократить invoke5(5 аргументов) и invoke10(10 аргументов) до invoke(с любым количеством аргументов).

в си это обычно делается так:

void* invoke(const any_fptr &fptr, int argc, void** argv[]) {
  return fptr(argc, argv);
}

но если ты хочешь упороться (не рекомендую), то можно попробовать собрать аргументы в tuple, а затем вызвать std::apply. но это самому надо писать, штырить будет довольно сильно, просто предупреждаю заранее.

ещё можно попробовать с макросами поколдовать, но это в общем случае тоже нельзя рекомендовать.

anonymous ()
Ответ на: комментарий от anonymous

Проблема в том, что у меня fptr ничего не знает о int argc и void** argv[].

Типичный пример fptr:

wchar_t* func1(wchar_t* a, wchar_t* b) { ... }
SaBo ★★ ()
Ответ на: комментарий от SaBo

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

можешь попробовать макросы, что-то типа:

#define invoke(fptr, ...) fptr(__VA_ARGS__)

https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html

только макросами не надо упарываться потому что потом разупороться будет очень сложно. \ - это запретный символ, если макрос длинее чем 80 символов - его надо удалить навсегда.

anonymous ()

Если возникает задача об обёртке над функцией с переменным количеством аргументов и нет v-функций, то ваша библиотека просто недоделана из соображения полноты API и начинать надо именно с этого: основная работа должна проходить в v-функциях, а функции с переменным количеством параметров - это только для упрощения вызова, такой внешний экспортированный сахар.

vodz ★★★★★ ()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.