Как вызвать ф-ию, с переменной в C++, вот пример
char* funcName = "somefunction";
char* params[] = {"param1", "param2", "param3"};
callFunction(funcName, params);
Так вот, как можно реализовать ф-ию "callFunction", чтоб она вызывало другие ф-ии, как нелепо бы это было сказано.
Буду благодарен за любую помощь!
Получилось довольно коряво, но работает.
В предлагаемом методе должны выполняться следующие условия:
Пример файла исходных кодов с функциями:
#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 содержит что-то, позволяющее решить эту проблему, но я не особо углублялся.
Универсальностью данное решение, очевидно, не обладает. Однако, оно может являться опорной точкой. И я был бы рад услышать дополнения и улучшения от более опытных программистов.
если Вам необходимо вызывать функцию именно по имени указанном в виде строки, то первое что приходит в голову так это объявить двумерный массив с именами и сопоставленными им адресами функций. Для наглядности, можно определить структуру описывающую элемент массива:
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);
}
}
ЗЫ: код не претендует на работоспособность и призван лишь дать представление о логике.
Решение близко к тому, что Вам нужно. Возможно, что оно даже Вас устроит. Нашёл здесь. Продублирую в этом ответе:
#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++.
Продвижение своими сайтами как стратегия роста и независимости