Создание модульной системы (динамическая загрузка) в C

Как можно загружать скомпилированный C-код во время выполнения, а затем вызывать функции внутри него? Не как просто вызов exec().

EDIT: программа, загружающая модуль, находится в C.

Ответ 1

В Linux/UNIX вы можете использовать функции POSIX dlopen/dlsym/dlerror/dlclose для динамического открытия разделяемых библиотек и доступа к символам (включая функции), которые они предоставляют, см. man page.

Ответ 2

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 ());

Вы можете получить полный пример в строке.

Ответ 3

Посмотрите на этот вопрос, но ответили, что другие заинтересованные в этой теме могут оценить пример кросс-платформы из старого приложения на основе плагинов. Пример работает на 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);
}

Ответ 4

Также вы можете посмотреть cpluff. Это библиотека управления плагинами на чистом c.

Ответ 5

Если вы хотите рассмотреть структуру, Qt предоставляет QPluginLoader: Qt 5 docs (или для старых документов Qt 4.8 см. здесь)

Если вам нужен/нужен более мелкозернистый элемент управления, Qt также предоставляет средства для загрузки библиотек "на лету" с помощью QLibrary: Qt 5 docs ( или для старых документов Qt 4.8 см. здесь)

Даже лучше, они переносятся на разных платформах.

Ответ 6

Динамические языки, такие как 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.

Ответ 7

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

Конечно, это предполагает, что он просто исполняемый код - весьма маловероятен. Код, вероятно, требует, чтобы данные также загружались в ОЗУ, и может потребоваться пространство для глобальных/статических переменных. Вы можете загрузить все это самостоятельно, но вам нужно будет войти в исполняемый код и отрегулировать все ссылки на память.

Большинство операционных систем позволяют динамическое связывание, что делает все это для вас.

Ответ 8

В Windows это так:

  • Сгенерировать код (в C, потому что легко найти компиляторы и требования к библиотеке минимальны)
  • создать задание для компиляции/ссылки на DLL
  • загрузите его с помощью LoadLibrary
  • получить указатели на функции с GetProcAddress

Шаги generate/compile/link обычно занимают менее секунды.

Ответ 9

для пользователей GNU/Linux

динамическая библиотека загрузки - это механизм, который с помощью этого позволяет запустить нашу программу и во время выполнения решить, какую функцию мы хотим использовать. Я думаю, что в некоторых случаях возможно изменение переменной 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.