Как можно загружать скомпилированный C-код во время выполнения, а затем вызывать функции внутри него? Не как просто вызов exec().
EDIT: программа, загружающая модуль, находится в C.
Как можно загружать скомпилированный C-код во время выполнения, а затем вызывать функции внутри него? Не как просто вызов exec().
EDIT: программа, загружающая модуль, находится в C.
В Linux/UNIX вы можете использовать функции POSIX dlopen
/dlsym
/dlerror
/dlclose
для динамического открытия разделяемых библиотек и доступа к символам (включая функции), которые они предоставляют, см. man page.
dlopen - это путь. Вот несколько примеров:
Загрузка плагина с помощью dlopen:
#include <dlfcn.h>
...
int
main (const int argc, const char *argv[])
{
char *plugin_name;
char file_name[80];
void *plugin;
...
plugin = dlopen(file_name, RTLD_NOW);
if (!plugin)
{
fatal("Cannot load %s: %s", plugin_name, dlerror ());
}
Компиляция выше:
% cc -ldl -o program program.o
Затем, предполагая этот API для плагинов:
/* The functions we will find in the plugin */
typedef void (*init_f) ();
init_f init;
typedef int (*query_f) ();
query_f query;
Поиск адреса init() в плагине:
init = dlsym(plugin, "init");
result = dlerror();
if (result)
{
fatal("Cannot find init in %s: %s", plugin_name, result);
}
init();
С помощью другой функции query(), которая возвращает значение:
query = dlsym (plugin, "query");
result = dlerror();
if (result)
{
fatal("Cannot find query in %s: %s", plugin_name, result);
}
printf("Result of plugin %s is %d\n", plugin_name, query ());
Вы можете получить полный пример в строке.
Посмотрите на этот вопрос, но ответили, что другие заинтересованные в этой теме могут оценить пример кросс-платформы из старого приложения на основе плагинов. Пример работает на win32 или linux, а также выполняет поиск и вызывает функцию "конструктор" в динамически загружаемых .so или .dll, указанных в аргументе файла. Пример приведен в С++, но процедуры должны быть одинаковыми для c.
//firstly the includes
#if !defined WIN32
#include <dlfcn.h>
#include <sys/types.h>
#else
#include <windows.h>
#endif
//define the plugin constructor function type named PConst
typedef tcnplugin* (*PConst)(tcnplugin*,tcnplugin*,HANDLE);
//loads a single specified tcnplugin,allmychildren[0] = null plugin
int tcnplugin::loadplugin(char *file) {
tcnplugin *hpi;
#if defined WIN32 //Load library windows style
HINSTANCE hplugin=LoadLibrary(file);
if (hplugin != NULL) {
PConst pinconstruct = (PConst)GetProcAddress(hplugin,"construct");
#else //Load it nix style
void * hplugin=dlopen(file,RTLD_NOW);
if (hplugin != NULL) {
PConst pinconstruct = (PConst)dlsym(hplugin,"construct");
#endif
if (pinconstruct != NULL) { //Try to call constructor function in dynamically loaded file, which returns a pointer to an instance of the plugin class
hpi = pinconstruct(this, this, hstdout);
} else {
piprintf("Cannot find constructor export in plugin!\n");
return 0;
}
} else {
piprintf("Cannot open plugin!\n");
#if !defined WIN32
perror(dlerror());
#endif
return 0;
}
return addchild(hpi); //add pointer to plugin class to our list of plugins
}
Можно также упомянуть, что если модуль, функции которого вы хотите вызвать, написан в С++, вы должны объявить функцию с помощью extern "C", например:
extern "C" pcparport * construct(tcnplugin *tcnptr,tcnplugin *parent) {
return new pcparport(tcnptr,parent,"PCPARPORT",0,1);
}
Также вы можете посмотреть cpluff. Это библиотека управления плагинами на чистом c.
Если вы хотите рассмотреть структуру, Qt предоставляет QPluginLoader: Qt 5 docs (или для старых документов Qt 4.8 см. здесь)
Если вам нужен/нужен более мелкозернистый элемент управления, Qt также предоставляет средства для загрузки библиотек "на лету" с помощью QLibrary: Qt 5 docs ( или для старых документов Qt 4.8 см. здесь)
Даже лучше, они переносятся на разных платформах.
Динамические языки, такие как Perl, делают это все время. Perl-интерпретатор написан на C, а многие модули Perl частично написаны на C. Когда эти модули требуются, скомпилированные C-компоненты динамически загружаются "на лету". Как отмечено в другом ответе, механизм для хранения этих модулей - это библиотеки DLL на окнах и разделяемые библиотеки (.so файлы) в UNIX. Я считаю, что вызов для загрузки разделяемой библиотеки в UNIX - это dlopen(). Вероятно, вы найдете указатели на то, как это сделать в UNIX, начиная с документации для этого вызова. Для Windows вам необходимо исследовать библиотеки DLL и научиться загружать их динамически во время выполнения. [Или, возможно, пройти через уровень эмуляции Cygwin UNIX, который, вероятно, позволит вам использовать те же вызовы в Windows, что и в UNIX, но я бы не рекомендовал это, если вы уже не используете и не компилируете Cygwin.]
Обратите внимание, что это отличается от простой привязки к общей библиотеке. Если вы заранее знаете, какой код вы будете называть, вы можете создать против общей библиотеки, и сборка будет "динамически связана" с этой библиотекой; без какой-либо специальной обработки от вас процедуры из библиотеки будут загружены в память только тогда, когда и если ваша программа на самом деле их называет. Но вы не можете этого сделать, если вы планируете написать что-то, способное загрузить любой произвольный код объекта, код, который вы не можете идентифицировать сейчас, во время сборки, но вместо этого ожидаете, чтобы его выбрали как-то во время выполнения. Для этого вам нужно будет использовать dlopen() и его двоюродных братьев Windows.
Вы можете посмотреть, как Perl или другие динамические языки делают это, чтобы увидеть некоторые реальные примеры. Библиотека Perl, ответственная за такую динамическую загрузку, - DynaLoader; Я полагаю, у него есть как Perl, так и C-компонент. Я уверен, что другие динамические языки, такие как Python, имеют нечто похожее, на что вы можете смотреть; и Parrot, виртуальная машина для невыпущенного Perl 6, несомненно, имеет механизм для этого (или будет в будущем).
В этом отношении Java реализует это через интерфейс JNI (Java Native Interface), поэтому вы, вероятно, можете посмотреть исходный код OpenJDK, чтобы узнать, как Java выполняет это как в UNIX, так и в Windows.
Существует подход DIY. Хотя метод (и возможность) этого варьируется от системы к системе, общая идея состоит в том, чтобы открыть файл, прочитать содержимое файла в памяти, сделать исполняемую память памяти, инициализировать указатель на действительную позицию в этой памяти, и вот вы.
Конечно, это предполагает, что он просто исполняемый код - весьма маловероятен. Код, вероятно, требует, чтобы данные также загружались в ОЗУ, и может потребоваться пространство для глобальных/статических переменных. Вы можете загрузить все это самостоятельно, но вам нужно будет войти в исполняемый код и отрегулировать все ссылки на память.
Большинство операционных систем позволяют динамическое связывание, что делает все это для вас.
В Windows это так:
Шаги generate/compile/link обычно занимают менее секунды.
динамическая библиотека загрузки - это механизм, который с помощью этого позволяет запустить нашу программу и во время выполнения решить, какую функцию мы хотим использовать. Я думаю, что в некоторых случаях возможно изменение переменной static
.
Сначала начните с просмотра man 3 dlopen
или см. его онлайн
Требуется заголовочный файл: dlfcn
, и поскольку это не является частью стандартного, вы должны, как он, в ваш объектный файл с помощью этой библиотеки: libdl.(so/a)
и, следовательно, вам нужно что-то как:
gcc yours.c -ldl
тогда у вас есть имя файла a.out
, и вы можете запустить его НО, он работает неправильно, и я объясню почему.
Полный пример:
сначала соберите 2 файла func1.c
и func2.c
соответственно. Мы хотим вызвать эти функции во время выполнения.
func.c
int func1(){
return 1;
}
func2.c
const char* func2(){
return "upgrading to version 2";
}
Теперь у нас есть 2 функции, пусть наши модули:
ALP ❱ gcc -c -fPIC func1.c
ALP ❱ gcc -c -fPIC func2.c
ALP ❱ gcc -o libfunc.so -shared -fPIC func1.o func2.o
для вопрошающего ума о -fPIC
= > PIC
Теперь у вас есть имена dynamic library
: libfunc.so
Позвольте создать основную программу (= temp.c
), которая хочет использовать эти функции.
заголовочные файлы
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
и основная программа
int main()
{
// pointer function to func1 and func2
int ( *f1ptr )();
const char* ( *f2ptr )();
// for pointing to the library
void* handle = NULL;
// for saving the error messages
const char* error_message = NULL;
// on error dlopen returns NULL
handle = dlopen( "libfunc.so", RTLD_LAZY );
// check for error, if it is NULL
if( !handle )
{
fprintf( stderr, "dlopen() %s\n", dlerror() );
exit( 1 );
}
/*
according to the header file:
When any of the above functions fails, call this function
to return a string describing the error. Each call resets
the error string so that a following call returns null.
extern char *dlerror (void) __THROW;
*/
// So, reset the error string, of course we no need to do it just for sure
dlerror();
// point to func1
f1ptr = (int (*)()) dlsym( handle, "func1" );
// store the error message to error_message
// because it is reseted if we use it directly
error_message = dlerror();
if( error_message ) // it means if it is not null
{
fprintf( stderr, "dlsym() for func1 %s\n", error_message );
dlclose( handle );
exit( 1 );
}
// point the func2
f2ptr = (const char* (*)()) dlsym( handle, "func2" );
// store the error message to error_message
// because it is reseted if we use it directly
error_message = dlerror();
if( error_message ) // it means if it is not null
{
fprintf( stderr, "dlsym() for func2 %s\n", error_message );
dlclose( handle );
exit( 1 );
}
printf( "func1: %d\n", ( *f1ptr )() );
printf( "func2: %s\n", ( *f2ptr )() );
// unload the library
dlclose( handle );
// the main return value
return 0;
}
Теперь нам просто нужно скомпилировать этот код (= temp.c
), поэтому попробуйте:
ALP ❱ gcc temp.c -ldl
ALP ❱ ./a.out
libfunc.so: cannot open shared object file: No such file or directory
Это не работает! ПОЧЕМУ легко; потому что наша программа a.out
не знает, где найти связанную библиотеку: libfunc.so
и, следовательно, она сообщает нам cannot not open ...
как сообщить программе (= a.out
) ее библиотеку?
ld
компоновщикLD_LIBRARY_PATH
в первую очередь, с помощью ld
используйте -Wl,-rpath,
и pwd
и поместите путь в качестве аргумента для него
ALP ❱ gcc temp.c -ldl
ALP ❱ ./a.out
libfunc.so: cannot open shared object file: No such file or directory
ALP ❱ pwd
/home/shu/codeblock/ALP
ALP ❱ gcc temp.c -ldl -Wl,-rpath,/home/shu/codeblock/ALP
ALP ❱ ./a.out
func1: 1
func2: upgrading to version 2
второй способ
ALP ❱ gcc temp.c -ldl
ALP ❱ ./a.out
libfunc.so: cannot open shared object file: No such file or direc
ALP ❱ export LD_LIBRARY_PATH=$PWD
ALP ❱ echo $LD_LIBRARY_PATH
/home/shu/codeblock/ALP
ALP ❱ ./a.out
func1: 1
func2: upgrading to version 2
ALP ❱ export LD_LIBRARY_PATH=
ALP ❱ ./a.out
libfunc.so: cannot open shared object file: No such file or
и третий способ
у вас есть libfunc.so
в вашем текущем пути, поэтому вы можете скопировать его в стандартный путь для библиотек.
ALP $ sudo cp libfunc.so /usr/lib
ALP ❱ gcc temp.c -ldl
ALP ❱ ./a.out
func1: 1
func2: upgrading to version 2
вы можете удалить его из /usr/lib
и использовать его. Это зависит от вас.
ПРИМЕЧАНИЕ
как узнать, что наш a.out
знает о его пути?
легко:
ALP ❱ gcc temp.c -ldl -Wl,-rpath,/home/shu/codeblock/ALP
ALP ❱ strings a.out | grep \/
/lib/ld-linux.so.2
/home/shu/codeblock/ALP
как мы можем использовать его в С++?
> Пока я знаю, что вы не можете, потому что g++
управляет именами функций, тогда как gcc
не использует: extern "C" int func1();
например.
Для получения более подробной информации см. справочные страницы и книги по программированию Linux.