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

Nik*_*ron 36 c frameworks module

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

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

bor*_*yer 41

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 ());
  }
Run Code Online (Sandbox Code Playgroud)

编译以上内容:

% cc  -ldl -o program program.o 
Run Code Online (Sandbox Code Playgroud)

然后,假设插件的API:

/* The functions we will find in the plugin */
typedef void (*init_f) ();
init_f init;
typedef int (*query_f) ();
query_f query;
Run Code Online (Sandbox Code Playgroud)

在插件中查找init()的地址:

init = dlsym(plugin, "init");
result = dlerror();
if (result)
{
   fatal("Cannot find init in %s: %s", plugin_name, result);
}
init();
Run Code Online (Sandbox Code Playgroud)

使用另一个函数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 ());
Run Code Online (Sandbox Code Playgroud)

您可以获取完整的例子就行.

  • 你介意把完整的例子放到github上吗?在那里阅读会更容易。 (2认同)

Rob*_*ble 32

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


Sha*_*iri 11

对于 GNU/Linux 用户

\n

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

\n

首先开始查看man 3 dlopen在线查看

\n

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

\n
gcc yours.c -ldl\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
\n

一个完整的例子:

\n

首先分别创建 2 个文件func1.cfunc2.c。我们想在运行时调用这些函数。

\n

函数c

\n
int func1(){\n    return 1;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

函数2.c

\n
const char* func2(){\n    return "upgrading to version 2";\n}\n
Run Code Online (Sandbox Code Playgroud)\n

现在我们有 2 个函数,让我们来制作我们的模块:

\n
ALP \xe2\x9d\xb1 gcc -c -fPIC func1.c\nALP \xe2\x9d\xb1 gcc -c -fPIC func2.c\nALP \xe2\x9d\xb1 gcc -o libfunc.so -shared -fPIC func1.o func2.o \n
Run Code Online (Sandbox Code Playgroud)\n

询问关于-fPIC=> PIC 的想法

\n

现在你有一个dynamic library名字:libfunc.so

\n

temp.c让我们创建想要使用这些函数的主程序 (= )。

\n

头文件

\n
#include <stdio.h>\n#include <stdlib.h>\n#include <dlfcn.h> \n
Run Code Online (Sandbox Code Playgroud)\n

和主程序

\n
int main()\n{\n    // pointer function to func1 and func2\n    int         ( *f1ptr )();\n    const char* ( *f2ptr )();\n\n    // for pointing to the library\n    void* handle = NULL;\n\n    // for saving the error messages\n    const char* error_message = NULL;\n\n    // on error dlopen returns NULL\n    handle = dlopen( "libfunc.so", RTLD_LAZY );\n\n    // check for error, if it is NULL\n    if( !handle )\n    {\n        fprintf( stderr, "dlopen() %s\\n", dlerror() );\n        exit( 1 );\n    }\n\n    /*\n        according to the header file:\n\n        When any of the above functions fails, call this function\n        to return a string describing the error.  Each call resets\n        the error string so that a following call returns null.\n\n        extern char *dlerror (void) __THROW;\n    */\n\n    // So, reset the error string, of course we no need to do it just for sure\n    dlerror();\n\n    // point to func1\n    f1ptr = (int (*)()) dlsym( handle, "func1" );\n\n    // store the error message to error_message\n    // because it is reseted if we use it directly\n    error_message = dlerror();\n    if( error_message ) //   it means if it is not null\n    {\n        fprintf( stderr, "dlsym() for func1 %s\\n", error_message );\n        dlclose( handle );\n        exit( 1 );\n    }\n\n    // point the func2\n    f2ptr = (const char* (*)()) dlsym( handle, "func2" );\n\n    // store the error message to error_message\n    // because it is reseted if we use it directly\n    error_message = dlerror();\n    if( error_message ) //   it means if it is not null\n    {\n        fprintf( stderr, "dlsym() for func2 %s\\n", error_message );\n        dlclose( handle );\n        exit( 1 );\n    }\n\n    printf( "func1: %d\\n", ( *f1ptr )() );\n    printf( "func2: %s\\n", ( *f2ptr )() );\n\n    // unload the library\n    dlclose( handle );\n\n    // the main return value\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

现在我们只需要编译这段代码(= temp.c),然后尝试:

\n
ALP \xe2\x9d\xb1 gcc temp.c -ldl\nALP \xe2\x9d\xb1 ./a.out\nlibfunc.so: cannot open shared object file: No such file or directory\n
Run Code Online (Sandbox Code Playgroud)\n

这是行不通的!为什么容易;因为我们的a.out程序不知道在哪里可以找到相关的库:libfunc.so因此它告诉我们cannot not open ...

\n

如何告诉程序(= a.out)找到它的库?

\n
    \n
  1. 使用ld链接器
  2. \n
  3. 使用环境变量LD_LIBRARY_PATH
  4. \n
  5. 使用标准路径
  6. \n
\n

第一种方式,在帮助下ld

\n

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

\n
ALP \xe2\x9d\xb1 gcc temp.c -ldl\nALP \xe2\x9d\xb1 ./a.out\nlibfunc.so: cannot open shared object file: No such file or directory\nALP \xe2\x9d\xb1 pwd\n/home/shu/codeblock/ALP\nALP \xe2\x9d\xb1 gcc temp.c -ldl -Wl,-rpath,/home/shu/codeblock/ALP\nALP \xe2\x9d\xb1 ./a.out\nfunc1: 1\nfunc2: upgrading to version 2\n
Run Code Online (Sandbox Code Playgroud)\n

第二种方式

\n
ALP \xe2\x9d\xb1 gcc temp.c -ldl\nALP \xe2\x9d\xb1 ./a.out\nlibfunc.so: cannot open shared object file: No such file or direc\nALP \xe2\x9d\xb1 export LD_LIBRARY_PATH=$PWD\nALP \xe2\x9d\xb1 echo $LD_LIBRARY_PATH\n/home/shu/codeblock/ALP\nALP \xe2\x9d\xb1 ./a.out\nfunc1: 1\nfunc2: upgrading to version 2\nALP \xe2\x9d\xb1 export LD_LIBRARY_PATH=\nALP \xe2\x9d\xb1 ./a.out\nlibfunc.so: cannot open shared object file: No such file or \n
Run Code Online (Sandbox Code Playgroud)\n

和第三条路

\n

您当前路径中有libfunc.so,因此您可以将其复制到库的标准路径中。

\n
ALP $ sudo cp libfunc.so /usr/lib\nALP \xe2\x9d\xb1 gcc temp.c -ldl\nALP \xe2\x9d\xb1 ./a.out\nfunc1: 1\nfunc2: upgrading to version 2\n
Run Code Online (Sandbox Code Playgroud)\n

您可以将其删除/usr/lib并使用它。它是由你决定。

\n

笔记

\n

如何知道我们a.out知道它的路径?
\n简单:

\n
ALP \xe2\x9d\xb1 gcc temp.c -ldl -Wl,-rpath,/home/shu/codeblock/ALP\nALP \xe2\x9d\xb1 strings a.out  | grep \\/\n/lib/ld-linux.so.2\n/home/shu/codeblock/ALP\n
Run Code Online (Sandbox Code Playgroud)\n

我们如何在中使用它?
\n只要我知道你不能,因为g++会破坏函数名称,而gcc不会,因此你应该使用:extern "C" int func1();例如。

\n

有关更多详细信息,请参阅手册页和 Linux 编程书籍。

\n

  • 好的!根据 dlopen 手册页,还有第四种方法“如果文件名包含斜杠(“/”),则它被解释为(相对或绝对)路径名。” 所以 'handle = dlopen("./libfunc.so", RTLD_LAZY );' 允许按照描述进行编译,并且只需成功执行“./a.out”即可,无需执行任何其他操作。 (2认同)

eri*_*isu 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
}
Run Code Online (Sandbox Code Playgroud)

可能还会提到,如果要调用的函数模块是用c ++编写的,则必须使用extern"C"声明函数,例如:

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