Когда загруженная разделяемая библиотека открывается через функцию dlopen(), есть ли способ вызвать функции в основной программе?
его также можно использовать для управления инверсией, не так ли? Определите поток приложения в библиотеке, пока фактическая реализация находится в основном приложении.
Подумайте о модуле Perl XS. Он должен использовать функции Perl низкого уровня (скажем, newSViv () для создания SV из целого числа); удобно, если модуль использует функцию newSViv () из Perl, а не встраивает свою собственную копию в общий объект модуля. Также для кода нужна стандартная библиотека C.





Да, если вы предоставите своей библиотеке указатель на эту функцию, я уверен, что библиотека сможет запускать / выполнять функцию в основной программе.
Вот пример, не скомпилировал, так что будьте осторожны;)
/* in main app */
/* define your function */
int do_it( char arg1, char arg2);
int do_it( char arg1, char arg2){
/* do it! */
return 1;
}
/* some where else in main app (init maybe?) provide the pointer */
LIB_set_do_it(&do_it);
/** END MAIN CODE ***/
/* in LIBRARY */
int (*LIB_do_it_ptr)(char, char) = NULL;
void LIB_set_do_it( int (*do_it_ptr)(char, char) ){
LIB_do_it_ptr = do_it_ptr;
}
int LIB_do_it(){
char arg1, arg2;
/* do something to the args
...
... */
return LIB_do_it_ptr( arg1, arg2);
}
do_it_ptr принимает указатель на функцию, которая ожидает 3 аргумента типа char; вы назначаете указатели на функции для функций, которые принимают только 2 аргумента типа char. Объявление extern для doit () вряд ли понадобится. Do_it_ptr не требуется; вы можете просто передать do_it по имени, где вы сейчас передаете do_it_ptr. Так далее!
это правильно :) на самом деле вы также можете избавиться от LIB_get_it () и просто определить новый LIB_do_it (int (* do_it_ptr) (char, char, char)) {return do_it_ptr (arg1, arg2, arg3)}
Код dlo.c (библиотека):
#include <stdio.h>
// function is defined in main program
void callb(void);
void test(void) {
printf("here, in lib\n");
callb();
}
Скомпилировать с
gcc -shared -olibdlo.so dlo.c
Вот код основной программы (скопирован из man-страницы dlopen и скорректирован):
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
void callb(void) {
printf("here, i'm back\n");
}
int
main(int argc, char **argv)
{
void *handle;
void (*test)(void);
char *error;
handle = dlopen("libdlo.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
dlerror(); /* Clear any existing error */
*(void **) (&test) = dlsym(handle, "test");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
(*test)();
dlclose(handle);
exit(EXIT_SUCCESS);
}
Строить с
gcc -ldl -rdynamic main.c
Выход:
[js@HOST2 dlopen]$ LD_LIBRARY_PATH=. ./a.out
here, in lib
here, i'm back
[js@HOST2 dlopen]$
Опция -rdynamic помещает все символы в динамическую таблицу символов (которая отображается в память), а не только имена используемых символов. Об этом читайте дальше здесь. Конечно, вы также можете предоставить указатели на функции (или структуру указателей на функции), которые определяют интерфейс между библиотекой и вашей основной программой. На самом деле я бы выбрал именно этот метод. Я слышал от других людей, что сделать -rdynamic в Windows не так просто, и это также сделало бы более чистую связь между библиотекой и основной программой (у вас есть точный контроль над тем, что можно вызывать, а что нет), но для этого также требуется больше домашнего хозяйства.
Отличный ответ. Кто-нибудь заработал 10к реп :-)
Это интересный способ кастинга. Разве не более нормально преобразовать возвращаемое значение dlsym () в указатель на функцию, а не делать вид, что указатель, который вы назначаете, имеет тот же тип, что и функция, возвращаемая dlsym ()?
C не говорит, что произойдет, если вы приведете значение void * к указателю на функцию. к сожалению, я не нашел абзаца, в котором говорилось бы, что это неопределенное поведение, но в справочной странице говорится об этом.
вы найдете более подробную информацию здесь: opengroup.org/onlinepubs/009695399/functions/dlsym.html. В любом случае, C++ явно запрещает приведение из void * или указателей объектов к типу указателя на функцию или обратно.
поэтому приведение материала к void **, как это сделал я, а затем разыменование по-прежнему является неопределенным поведением, но XSI-совместимые системы будут его поддерживать, а C++ явно не запрещает (делает его плохо сформированным) (вместо этого он говорит, что это неопределенное поведение ).
@litb - я хотел бы добавить комментарий длиной более 300 символов об использовании union для «решения» проблемы. Могу я отредактировать ваш ответ, чтобы добавить эту информацию?
Джонатан, я думаю, вы хотите использовать объединение и поместить в них указатель функции и указатель void. затем назначьте указатель void и прочитайте член указателя функции. он, конечно, будет компилироваться, как и приведение к void **, и оба являются неопределенным поведением в C.
@litb: Да, вы поняли. Единственное отличие состоит в том, что объединение игнорирует предупреждения, которых нет при приведении типов.
Функция dlopen(), как обсуждается @litb, в основном предоставляется в системах, использующих объектные файлы формата ELF. Это довольно мощный инструмент, который позволяет вам контролировать, могут ли символы, на которые ссылается загруженная библиотека, выполняться из основной программы, и, как правило, позволяет им удовлетворяться. Не все системы загрузки разделяемых библиотек настолько гибки - имейте в виду, если дело дойдет до переноса кода.
Механизм обратного вызова, описанный @hhafez, теперь работает, когда изгибы в этом коде устранены.
Приведенные ниже ответы хорошо отвечают на вопрос, но я должен спросить - не могли бы вы объяснить более широкий контекст этого требования? Когда я обнаружил, что это необходимо, это либо для создания модели расширяемости / плагина, либо потому, что моя программа не очень хорошо продумана.