背景:
我发现自己承担着将 C++ GNU/Linux 应用程序移植到 Windows 的艰巨任务。此应用程序所做的一件事是在特定路径上搜索共享库,然后使用 posix dlopen() 和 dlsym() 调用动态地从中加载类。我们有充分的理由以这种方式加载,我不会在这里讨论。
问题:
要动态发现由 C++ 编译器使用 dlsym() 或 GetProcAddress() 生成的符号,必须使用 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 信号处理程序(我知道这很恶心 - 我愿意接受更好的想法)。因此,为了确认加载旧库会出现段错误,我认为它会使用旧函数原型(返回列表对象)通过新加载代码(需要指向列表的指针)运行库,令我惊讶的是刚刚工作。我的问题是为什么?
下面的加载代码适用于上面列出的两个函数原型。我已经确认它可以在 Fedora 12、RedHat 5.5 和 RedHawk 5.1 上使用 gcc 版本 4.1.2 和 4.4.4。使用带有 -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);
}