C++ вызов функции от переменной

419
26 января 2017, 03:02

Как вызвать ф-ию, с переменной в C++, вот пример

char* funcName = "somefunction";
char* params[] = {"param1", "param2", "param3"};
callFunction(funcName, params);

Так вот, как можно реализовать ф-ию "callFunction", чтоб она вызывало другие ф-ии, как нелепо бы это было сказано.

Буду благодарен за любую помощь!

Answer 1

Получилось довольно коряво, но работает.

В предлагаемом методе должны выполняться следующие условия:

  • Функции должны "лежать" в динамических библиотеках;
  • Функции должны иметь тип void* (void*).

Пример файла исходных кодов с функциями:

#include <tuple>
#include <iostream>
extern "C" void* func0(void *args)
{
    std::tuple<int, int> *t = (std::tuple<int, int> *)args;
    int result = std::get<0>(*t) + std::get<1>(*t);
    return std::move((void *)&(result));
}
extern "C" void* func1(void *args)
{
    std::tuple<std::string, std::string> *t = (std::tuple<std::string, std::string> *)args;
    std::cout << std::get<0>(*t) << ' ' << std::get<1>(*t) << std::endl;
    return nullptr;
}

Компилируем:

g++ -fPIC -shared functions.cpp -o libfunctions.so

Чтобы получить список имён функций (символов) и сохранить их указатель, воспользуемся возможностями библиотек libdlfcn и libbfd.

Хранить соответствия "Имя функции -> Указатель" будем в следующем std::map:

std::map<std::string, std::function<void*(void*)> > functions;

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

template<typename ... Args>
void* callFunction(const std::string &funcName, Args... args)
{
    std::tuple<Args...> t = {args...};
    return functions[funcName](&t);
}

Для возможности вызова функций из внешней библиотеки необходимо держать "хэндлер" на эту библиотеку.

void * handle;

Функция заполнения мэпа. Входные параметры: путь до библиотеки. Возвращаемое значение - EXIT_FAILURE или EXIT_SUCCESS.

int start(const std::string& libName)
{
    bfd * abfd = bfd_openr(libName.c_str(), 0);
    if (abfd == NULL)
    {
        std::cout << "Error at openning: " << libName << std::endl;
        return EXIT_FAILURE;
    }
    (void) bfd_check_format(abfd, bfd_object);
    handle = dlopen(libName.c_str(), RTLD_LAZY);
    if (!handle)
    {
        std::cout << "Error at dlopen: " << libName << std::endl;
        bfd_close(abfd);
        return EXIT_FAILURE;
    }
    long storage_needed = bfd_get_symtab_upper_bound(abfd);
    if (storage_needed <= 0)
    {
        std::cout << "Error at get storage: " << libName << std::endl;
        bfd_close(abfd);
        dlclose(handle);
        return EXIT_FAILURE;
    }
    asymbol **symbol_table = (asymbol**) calloc (storage_needed, sizeof(char));
    if (symbol_table == NULL)
    {
        std::cout << "Error at calloc: " << libName << std::endl;
        bfd_close(abfd);
        dlclose(handle);
        return EXIT_FAILURE;
    }
    long number_of_symbols = bfd_canonicalize_symtab(abfd, symbol_table);
    if (number_of_symbols <= 0)
    {
        std::cout << "Error at get number: " << libName << std::endl;
        free(symbol_table);
        bfd_close(abfd);
        dlclose(handle);
        return EXIT_FAILURE;
    }
    for (int i = 0; i < number_of_symbols; ++i)
    {
        const char* function_name = symbol_table[i]->name;
        void*(*method)(void*) = (void*(*)(void*))dlsym(handle, function_name);
        if (dlerror() == NULL)
            functions[function_name] = method;
    }
    free(symbol_table);
    bfd_close(abfd);
    return EXIT_SUCCESS;
}

Кроме того, нужно обеспечить закрытие "хэндлера" библиотеки:

void stop()
{
    dlclose(handle);
}

Функция main может выглядеть, например, так:

int main()
{
    if (start("./libfunctions.so") != EXIT_SUCCESS)
        return EXIT_FAILURE;
    void * ret = callFunction("func0", 1, 2);
    std::cout << *(int*)ret << std::endl;
    callFunction("func1", std::string("Hello"), std::string("World"));
    stop();
    return EXIT_SUCCESS;
}

Не забудем добавить необходимые хэддеры:

#include <tuple>
#include <iostream>
#include <map>
#include <string>
#include <functional>
#include <dlfcn.h>
#include <bfd.h>

Компилируем с линковкой к ранее указанным библиотекам:

g++ main.cpp -lbfd -ldl

Сразу в глаза бросаются проблемы с возвращаемым значением.

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

Кроме того, при вызове каждой функции необходимо указывать параметры именно нужного ей типа. Вот почему в вызове функции func1 я указал преобразование к std::string.

Но и на этом проблемы не заканчиваются. После обработки библиотеки map будет заполнен некоторым набором ненужных нам символов. Можно конечно добавить ещё какое-нибудь условие (например, функции не должны начинаться с нижнего подчёркивания). Возможно, библиотека libbfd содержит что-то, позволяющее решить эту проблему, но я не особо углублялся.

Универсальностью данное решение, очевидно, не обладает. Однако, оно может являться опорной точкой. И я был бы рад услышать дополнения и улучшения от более опытных программистов.

Answer 2

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

typedef struct {
    char* name;
    void* func;
} funcnames_t;

далее объявите массив:

funcnames_t arr[3] = {
    { "func1", (void*) func1 },
    { "func2", (void*) func2 },
    { "func3", (void*) func3 }
};

следующим шагом у Вас перебор массива для поиска нужного элемента и вызов функции.

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

void func(int argc, void** argv);

смысл думаю понятен.

если принять, что функция выглядит именно так как указал я, то первую структуру можно привести к виду:

typedef void (*func_t)(int, void**);
typedef struct {
    char* name;
    func_t func;
} funcnames_t;

а Ваш код будет выглядеть примерно так:

for (i = 0; i < sizeof(arr) / sizeof(funcnames_t); i++) {
    if (!strcmp(arr[i].name, funcname)) {
        arr[i].func(num_params, params);
    }
}

ЗЫ: код не претендует на работоспособность и призван лишь дать представление о логике.

Answer 3

Решение близко к тому, что Вам нужно. Возможно, что оно даже Вас устроит. Нашёл здесь. Продублирую в этом ответе:

#include <type_traits>
#include <utility>
#include <string>
#include <iostream>
template<typename Fn, Fn fn, typename... Args>
typename std::result_of<Fn(Args...)>::type
wrapper(Args&&... args)
{
    return fn(std::forward<Args>(args)...);
}
#define WRAPPER(FUNC, ...) wrapper<decltype(&FUNC), &FUNC>(__VA_ARGS__)
int myFunc0(int arg1, int arg2)
{
    return arg1 + arg2;
}
std::string myFunc1()
{
    return "Some string";
}
int main()
{
    auto var1 = WRAPPER(myFunc0, 1, 2);
    auto var2 = WRAPPER(myFunc1);
    std::cout << var1 << std::endl << var2 << std::endl;
    return 0;
}

Компилятор должен поддерживать современные стандарты языка C++.

READ ALSO
Какого типа `1.`?

Какого типа `1.`?

В такой программе на Си

390
Как хранятся в памяти литералы?

Как хранятся в памяти литералы?

Как хранятся в памяти литералы?

344
Как построить native android программу через cmd в Windows? [требует правки]

Как построить native android программу через cmd в Windows? [требует правки]

Какиеexe нужно использовать? Из папки x86_64 или arm-linux-androideabi или других? и т

313
Переопределение макроса

Переопределение макроса

Подскажите, можно ли определить макрос так, чтобы в зависимости от того, использовался он, или нет, он принимал разные значения ?

347