Создание модульной системы (динамическая загрузка) в C - PullRequest
36 голосов
/ 21 декабря 2008

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

РЕДАКТИРОВАТЬ: программа загрузки модуля находится в C.

Ответы [ 9 ]

40 голосов
/ 21 декабря 2008

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

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

32 голосов
/ 21 декабря 2008

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

8 голосов
/ 21 декабря 2008

См., Что на этот вопрос ответили, но думали, что другие, интересующиеся этой темой, могут оценить кроссплатформенный пример из старого приложения на основе плагинов. Этот пример работает на win32 или linux, он выполняет поиск и вызывает функцию с именем 'constructor' в динамически загружаемых .so или .dll, указанных в аргументе файла. Пример написан на c ++, но процедуры должны быть такими же для c.

//firstly the includes
#if !defined WIN32
   #include <dlfcn.h>
   #include <sys/types.h>
#else
   #include <windows.h>
#endif

//define the plugin's 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's 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's class to our list of plugins
}

Можно также упомянуть, что если модуль, функции которого вы хотите вызвать, написан на c ++, вы должны объявить функцию с внешним "C", например:

extern "C" pcparport * construct(tcnplugin *tcnptr,tcnplugin *parent) {
    return new pcparport(tcnptr,parent,"PCPARPORT",0,1);
}
4 голосов
/ 10 сентября 2017

для пользователей 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) найти ее библиотеку?

  1. с использованием ld linker
  2. с использованием переменной среды LD_LIBRARY_PATH
  3. используя стандартный путь

первый способ, с помощью 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.

3 голосов
/ 22 декабря 2008

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

2 голосов
/ 04 марта 2012

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

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

Еще лучше, они переносимы между платформами.

1 голос
/ 21 декабря 2008

Динамические языки, такие как Perl, делают это постоянно. Интерпретатор Perl написан на C, а многие модули Perl частично написаны на C. Когда эти модули требуются, скомпилированные компоненты C динамически загружаются на лету. Как отмечалось в другом ответе, механизм хранения этих модулей - это библиотеки DLL в Windows и общие библиотеки (файлы .so) в UNIX. Я считаю, что вызов для загрузки разделяемой библиотеки в UNIX является dlopen (). Вероятно, вы можете найти указатели на то, как сделать это в UNIX, начав с документации для этого вызова. Для Windows вам необходимо изучить библиотеки DLL и научиться динамически загружать их во время выполнения. [Или, возможно, пройти через уровень эмуляции Cygwin UNIX, который, возможно, позволит вам использовать те же вызовы в Windows, что и в UNIX, но я бы не рекомендовал этого, если вы уже не используете и не компилируете для Cygwin.]

Обратите внимание, что это отличается от простого связывания с общей библиотекой. Если вы заранее точно знаете, какой код вы будете вызывать, вы можете создать его из общей библиотеки, и сборка будет «динамически связана» с этой библиотекой; без какой-либо специальной обработки с вашей стороны подпрограммы из библиотеки будут загружены в память только тогда, когда ваша программа их вызовет. Но вы не сможете этого сделать, если вы планируете написать что-то, способное загрузить любой произвольный объектный код, код, который вы не можете идентифицировать сейчас, во время сборки, но вместо этого ждете, чтобы его как-то выбрали во время выполнения. Для этого вам придется использовать dlopen () и его двоюродных братьев Windows.

Вы можете посмотреть, как это делают Perl или другие динамические языки, чтобы увидеть реальные примеры. Библиотека Perl, ответственная за этот вид динамической загрузки, является DynaLoader; Я полагаю, что в нем есть и компонент Perl, и Си. Я уверен, что другие динамические языки, такие как Python, имеют что-то похожее, на что вы могли бы взглянуть; и Parrot, виртуальная машина для неизданного Perl 6, безусловно, имеет механизм для этого (или будет в будущем).

В этом отношении Java выполняет это через свой интерфейс JNI (собственный интерфейс Java), поэтому вы, вероятно, могли бы взглянуть на исходный код OpenJDK, чтобы увидеть, как Java выполняет это как в UNIX, так и в Windows.

0 голосов
/ 31 декабря 2008

Под Windows вот как я это делаю:

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

Шаги генерации / компиляции / компоновки обычно занимают менее секунды.

0 голосов
/ 21 декабря 2008

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...