我发现自己有一个将C GNU / Linux应用程序移植到Windows上的令人尴尬的任务.此应用程序执行的操作之一是在特定路径上搜索共享库,然后使用posix dlopen()和dlsym()调用动态地加载它们中的类.我们有充分的理由以这种方式加载,我不会进入这里.
问题:
要使用dlsym()或GetProcAddress()动态发现由C编译器生成的符号,必须使用extern“C”链接块对其进行解组.例如:
#include <list> #include <string> using std::list; using std::string; extern "C" { list<string> get_list() { list<string> myList; myList.push_back("list object"); return myList; } }
这段代码是完全有效的C,可以在Linux和Windows上的众多编译器上编译和运行.但是,它不能与MSVC一起编译,因为“返回类型无效C”.我们提出的解决方法是更改函数以返回指向列表而不是列表对象的指针:
#include <list> #include <string> using std::list; using std::string; extern "C" { list<string>* get_list() { list<string>* myList = new list<string>(); myList->push_back("ptr to list"); return myList; } }
我一直在努力为GNU / Linux加载器找到一个最佳解决方案,它既可以使用新函数,也可以使用旧的遗留函数原型,或至少检测何时遇到不推荐使用的函数并发出警告.如果代码在他们尝试使用旧库时只是分段,那对我们的用户来说是不合适的.我最初的想法是在调用get_list期间设置一个SIGSEGV信号处理程序(我知道这很icky – 我对更好的想法持开放态度).所以只是为了确认加载一个旧库会发生段错误,我认为我会使用旧的函数原型(返回列表对象)通过新的加载代码(期望指向列表的指针)运行库,令我惊讶的是刚刚工作.我的问题是为什么?
下面的加载代码适用于上面列出的两个函数原型.我已经确认它适用于使用gcc版本4.1.2和4.4.4的Fedora 12,RedHat 5.5和RedHawk 5.1.使用带-shared和-fPIC的g编译库,可执行文件需要与dl(-ldl)链接.
#include <dlfcn.h> #include <stdio.h> #include <stdlib.h> #include <list> #include <string> using std::list; using std::string; int main(int argc,char **argv) { void *handle; list<string>* (*getList)(void); char *error; handle = dlopen("library path",RTLD_LAZY); if (!handle) { fprintf(stderr,"%s\n",dlerror()); exit(EXIT_FAILURE); } dlerror(); *(void **) (&getList) = dlsym(handle,"get_list"); if ((error = dlerror()) != NULL) { printf("%s\n",error); exit(EXIT_FAILURE); } list<string>* libList = (*getList)(); for(list<string>::iterator iter = libList->begin(); iter != libList->end(); iter++) { printf("\t%s\n",iter->c_str()); } dlclose(handle); exit(EXIT_SUCCESS); }
解决方法
事实证明,用于x86(和大多数其他编译器)的x86和x64的ABI通过向函数传递一个额外的“隐藏”指针arg来返回“大”结构(太大而不适合寄存器)该指针作为存储返回值的空间,然后返回指针本身.事实证明,这是一种形式的功能
struct foo func(...)
大致相当于
struct foo *func(...,struct foo *)
期望调用者为’foo'(可能在堆栈上)分配空间并传入指向它的指针.
所以如果你有一个期望以这种方式调用的函数(期望返回一个结构)而是通过一个返回指针的函数指针来调用它,它可能会起作用 – 如果它是垃圾位的话得到额外的arg(调用者留下的随机寄存器内容)碰巧指向某处可写,被调用的函数会愉快地在那里写入返回值,然后返回该指针,因此被调用的代码将返回看起来像一个指向它所期望的结构的有效指针.因此,代码可能表面上似乎有效,但它实际上可能会破坏随后可能重要的随机内存.