背景
我发现自己遇到了将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);
}
答案 0 :(得分:4)
aschepler说,因为你很幸运。
事实证明,用于x86(和大多数其他编译器)的x86和x64的ABI通过向函数传递一个额外的“隐藏”指针arg来返回“大”结构(太大而不适合寄存器) ,它使用该指针作为存储返回值的空间,然后返回指针本身。事实证明,形式的功能
struct foo func(...)
大致相当于
struct foo *func(..., struct foo *)
其中调用者应该为'foo'(可能在堆栈上)分配空间并传入指向它的指针。
所以只是如果你有一个期望以这种方式调用的函数(期望返回一个结构)而是通过一个返回指针的函数指针调用它,它可能看似起作用 - 如果它为额外的arg获取的垃圾位(调用者留下的随机寄存器内容)碰巧指向某处可写,被调用的函数将很乐意在那里写入其返回值,然后返回该指针,因此被调用的代码将返回一些内容看起来像一个有效的指向它期望的结构的指针。因此,代码可能表面上看似可行,但它实际上可能会破坏随后可能很重要的随机内存。