43

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

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

4

9 回答 9

47

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

您可以在线检索完整的示例。

于 2008-12-21T10:53:15.557 回答
38

在 Linux / UNIX 中,您可以使用 POSIX dlopen//函数来动态打开共享库并访问它们提供的符号(包括函数),详细信息请参见手册页dlsymdlerrordlclose

于 2008-12-21T05:23:51.803 回答
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);
}
于 2008-12-21T14:45:26.337 回答
6

对于 GNU/Linux 用户

动态加载库是一种机制,我们可以使用它运行我们的程序,并在运行时决定我们要使用/调用什么函数。我认为在某些情况下static变量也是可能的。

首先开始看man 3 dlopen在线看

所需的头文件是:dlfcn并且由于这不是标准的一部分,因此您应该使用此库将其链接到您的目标文件:libdl.(so/a)因此您需要以下内容:

gcc yours.c -ldl

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


一个完整的例子:

第一个箱子 2 个文件func1.cfunc2.c分别。我们想在运行时调用这些函数。

函数c

int func1(){
    return 1;
}

函数2.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链接器
  2. 使用环境变量LD_LIBRARY_PATH
  3. 使用标准路径

第一种方式,借助ld

使用-Wl,-rpath,andpwd并将路径作为它的参数

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 编程书籍。

于 2017-09-10T14:02:54.367 回答
3

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

于 2008-12-22T19:17:11.373 回答
2

如果您愿意考虑该框架,Qt 提供了 QPluginLoader:Qt 5 文档(或者对于旧的 Qt 4.8 文档,请参见此处

如果您需要/想要更细粒度的控制,Qt 还提供了一种使用 QLibrary 动态加载库的方法:Qt 5 文档(或旧的 Qt 4.8 文档,请参见此处

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

于 2012-03-04T18:03:10.290 回答
1

像 Perl 这样的动态语言一直都在这样做。Perl 解释器是用 C 编写的,许多 Perl 模块部分是用 C 编写的。当需要这些模块时,编译后的 C 组件会动态加载。如另一个答案中所述,存储这些模块的机制是 Windows 上的 DLL,以及 UNIX 上的共享库(.so 文件)。我相信在 UNIX 上加载共享库的调用是 dlopen()。从该调用的文档开始,您可能会找到有关如何在 UNIX 上完成此操作的指针。对于 Windows,您需要研究 DLL 并了解如何在运行时动态加载它们。[或者可能通过 Cygwin UNIX 仿真层,这可能允许您在 Windows 上使用与在 UNIX 上相同的调用,但我不建议您这样做,除非您

请注意,这与仅链接共享库不同。如果您提前知道您将调用什么代码,您可以针对共享库进行构建,并且构建将“动态链接”到该库;无需您进行任何特殊处理,仅当您的程序实际调用它们时,库中的例程才会加载到内存中。但是,如果您打算编写能够加载任意目标代码的东西,您现在无法在构建时识别代码,而是在运行时等待以某种方式被选中,那么您就不能这样做。为此,您必须使用 dlopen() 及其 Windows 表亲。

您可能会查看 Perl 或其他动态语言执行此操作的方式以查看一些真实示例。负责这种动态加载的 Perl 库是 DynaLoader;我相信它同时具有 Perl 和 C 组件。我敢肯定,像 Python 这样的其他动态语言也有类似的东西,你可能会喜欢看;Parrot,未发布的 Perl 6 的虚拟机,肯定也有这样做的机制(或者将来会这样做)。

就此而言,Java 通过其 JNI(Java 本机接口)接口实现了这一点,因此您可能可以查看 OpenJDK 的源代码以了解 Java 如何在 UNIX 和 Windows 上实现这一点。

于 2008-12-21T05:27:40.923 回答
0

有一种DIY方法。虽然执行此操作的方法(和可能性)因系统而异,但总体思路是打开一个文件,将文件的内容读入内存,使所述内存可执行,初始化一个指向该内存中有效位置的函数指针,你就在那里。

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

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

于 2008-12-21T05:26:53.717 回答
0

在 Windows 下,我是这样做的:

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

生成/编译/链接步骤通常需要不到一秒钟的时间。

于 2008-12-31T17:46:08.180 回答