如何在运行时加载已编译的 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 ());
您可以在线检索完整的示例。
在 Linux / UNIX 中,您可以使用 POSIX dlopen
//函数来动态打开共享库并访问它们提供的符号(包括函数),详细信息请参见手册页。dlsym
dlerror
dlclose
看到这个问题已经得到回答,但认为对这个主题感兴趣的其他人可能会欣赏一个基于旧插件的应用程序的跨平台示例。该示例适用于 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);
}
动态加载库是一种机制,我们可以使用它运行我们的程序,并在运行时决定我们要使用/调用什么函数。我认为在某些情况下static
变量也是可能的。
首先开始看man 3 dlopen
或在线看
所需的头文件是:dlfcn
并且由于这不是标准的一部分,因此您应该使用此库将其链接到您的目标文件:libdl.(so/a)
因此您需要以下内容:
gcc yours.c -ldl
然后你有一个文件名a.out
,你可以运行它,但它不能正常工作,我会解释为什么。
一个完整的例子:
第一个箱子 2 个文件func1.c
和func2.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
)找到它的库?
ld
链接器LD_LIBRARY_PATH
第一种方式,借助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
我们如何在C++中使用它?
只要我知道你不能因为g++
破坏函数名称而gcc
你应该使用:extern "C" int func1();
例如。
有关更多详细信息,请参阅手册页和 Linux 编程书籍。
你也可以看看cpluff。它是纯 c 上的插件管理库。
像 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 上实现这一点。
有一种DIY方法。虽然执行此操作的方法(和可能性)因系统而异,但总体思路是打开一个文件,将文件的内容读入内存,使所述内存可执行,初始化一个指向该内存中有效位置的函数指针,你就在那里。
当然,这是假设它只是可执行代码 - 不太可能。该代码可能也需要将数据加载到 RAM 中,并且可能需要用于全局/静态变量的空间。您可以自己加载这一切,但您需要进入可执行代码并调整其中的所有内存引用。
大多数操作系统都允许动态链接,它可以为您完成所有这些工作。
在 Windows 下,我是这样做的:
生成/编译/链接步骤通常需要不到一秒钟的时间。