如何在运行时加载已编译的C代码,并调用其中的函数?不是像简单地调用exec()那样。
编辑:加载模块的程序是用C编写的。
如何在运行时加载已编译的C代码,并调用其中的函数?不是像简单地调用exec()那样。
编辑:加载模块的程序是用C编写的。
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 ());
您可以从在线获取完整的示例。
dlsym
时,使用带有mangled字符串的函数名是标准的吗?还是在函数上使用extern "c"
只需使用正常的函数名在dlsym
上使用? - searchengine27在 Linux/UNIX 中,您可以使用 POSIX dlopen
/ dlsym
/ dlerror
/dlclose
函数来动态打开共享库并访问它们提供的符号(包括函数)。详情请参见手册页面。
动态加载库是一种机制,我们可以在运行时决定要使用/调用哪个函数来运行我们的程序。我认为在某些情况下也可能使用静态
变量。
首先查看man 3 dlopen
或在线查看
所需的头文件是:dlfcn
,由于这不是标准的一部分,因此您应该将其与此库链接到您的对象文件中:libdl。(so / a)
,因此您需要类似于:
gcc yours.c -ldl
a.out
的文件,可以运行它但是它无法正常工作,我将解释原因。
一个完整的示例:
首先创建两个文件func1.c
和func2.c
。我们想要在运行时调用这些函数。
func.c
int func1(){
return 1;
}
func2.c
const char* func2(){
return "upgrading to version 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
现在你有一个名为 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
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
我们如何在c++中使用它?
据我所知,你不能这样做,因为g++
会混淆函数名称,而gcc
则不会,因此你应该使用:extern "C" int func1();
例如。
有关更多详细信息,请参阅man页面和Linux编程书籍。
看到这个问题已经得到解答,但认为其他对此感兴趣的人可能会欣赏一下来自旧插件应用程序的跨平台示例。该示例适用于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
}
extern "C" pcparport * construct(tcnplugin *tcnptr,tcnplugin *parent) {
return new pcparport(tcnptr,parent,"PCPARPORT",0,1);
}
像Perl这样的动态语言经常这样做。 Perl解释器是用C编写的,许多Perl模块部分是用C编写的。 当需要这些模块时,编译后的C组件会动态地加载。 如另一个答案中所述,存储这些模块的机制是Windows上的DLL,在UNIX上是共享库(.so文件)。 我相信在UNIX上加载共享库的调用是dlopen()。 您可以通过查看该调用的文档来找到在UNIX上如何完成此操作的指针。 对于Windows,您需要研究DLL并学习如何在运行时动态加载它们。 [或者可能通过Cygwin UNIX仿真层,这可能允许您在Windows上使用与在UNIX上相同的调用,但我不建议除非您已经在使用和编译Cygwin。]
请注意,这与仅链接共享库不同。如果您事先知道将要调用的确切代码,可以对共享库进行构建,并且构建将“动态链接”到该库;没有任何特殊处理,从库中获取例程仅在程序实际调用它们时才会加载到内存中。但是,如果您计划编写能够加载任意任意对象代码的内容,即无法在构建时标识的代码,而是等待在运行时通过某种方式选择,则必须使用dlopen()及其Windows兄弟。有一种自己动手的方法。虽然这种方法(和可能性)因系统而异,但总体思路是打开文件,将文件内容读入内存,将该内存变为可执行状态,初始化函数指针到内存中的有效位置,就可以使用了。
当然,这是建立在假设代码是可执行的情况下 - 这种情况很不可能发生。代码可能需要将数据加载到RAM中,并且可能需要全局/静态变量的空间。您可以自己加载所有这些,但您需要进入可执行代码并调整其中的所有内存引用。
大多数操作系统都允许动态链接,这样可以为您完成所有这些工作。
在 Windows 下,我是这样做的:
通常,生成 / 编译 / 链接步骤不会超过一秒钟。