在C中创建模块系统(动态加载)

时间:2008-12-21 05:12:13

标签: c frameworks module

如何在运行时加载已编译的C代码,然后调用其中的函数?不像简单地调用exec()。

编辑:加载模块的程序在C。

9 个答案:

答案 0 :(得分:40)

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

您可以检索完整示例on line

答案 1 :(得分:32)

在Linux / UNIX中,您可以使用POSIX dlopen / dlsym / dlerror / dlclose函数动态打开共享库并访问符号(包括函数)请参阅man page了解详细信息。

答案 2 :(得分:8)

看到这个问题已得到解答,但认为对此主题感兴趣的其他人可能会欣赏来自旧的基于插件的应用程序的跨平台示例。该示例适用于win32或linux,并在文件参数中指定的动态加载的.so或.dll中调用并调用名为“constructor”的函数。示例是在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 ++编写的,则必须使用extern“C”声明函数,例如:

extern "C" pcparport * construct(tcnplugin *tcnptr,tcnplugin *parent) {
    return new pcparport(tcnptr,parent,"PCPARPORT",0,1);
}

答案 3 :(得分:4)

适用于GNU / Linux用户

动态加载库是一种机制,借助于此,我们可以运行我们的程序,并在运行时决定我们想要使用的功能。我认为在某些情况下static变量也是可能的。

首先看看man 3 dlopensee it online

所需的标头文件是:dlfcn,因为这不是标准的一部分,所以应该喜欢使用此库的目标文件:libdl.(so/a)和所以你需要这样的东西:

gcc yours.c -ldl

然后你有一个文件名a.out,你可以运行它它不能正常工作我会解释原因。

一个完整的例子:

首先分别列出2个文件func1.cfunc2.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 =&gt;的思考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链接器
  2. 使用环境变量LD_LIBRARY_PATH
  3. 使用标准路径
  4. 第一种方式,在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编程书。

答案 4 :(得分:3)

您也可以查看cpluff。它是纯c上的插件管理库。

答案 5 :(得分:2)

如果你愿意考虑这个框架,Qt提供了QPluginLoader:Qt 5 docs(或者对于旧的Qt 4.8文档,请参阅here

如果您需要/想要更精细的控制,Qt还提供了一种使用QLibrary动态加载库的方法:Qt 5 docs(或者对于旧的Qt 4.8文档,请参阅here

更好的是,它们可以跨平台移植。

答案 6 :(得分:1)

像Perl这样的动态语言一直这样做。 Perl解释器是用C语言编写的,许多Perl模块部分用C语言编写。当需要这些模块时,编译的C组件可以动态加载。如另一个答案所述,存储这些模块的机制是Windows上的DLL和UNIX上的共享库(.so文件)。我相信在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 :(得分:0)

有一种DIY方法。虽然这样做的方法(和可能性)因系统而异,但一般的想法是打开文件,将文件内容读入内存,使所述内存可执行,将函数指针初始化为该内存中的有效位置,你就在那里。

当然这是假设它只是可执行代码 - 不太可能。代码可能也需要将数据加载到RAM中,并且可能需要空间用于全局/静态变量。您可以自己加载,但是您需要进入可执行代码并调整其中的所有内存引用。

大多数操作系统都允许动态链接,它可以为您完成所有这些操作。

答案 8 :(得分:0)

在Windows下,我就是这样做的:

  • 生成代码(在C中,因为很容易找到编译器,库要求很少)
  • 生成一个作业以将其编译/链接到DLL
  • 使用LoadLibrary
  • 加载它
  • 使用GetProcAddress获取函数指针

生成/编译/链接步骤通常不到一秒钟。