cafesun 2007-02-16
这几天看到讲解dlopen,dlsym函数的文章,忍不住自己编码尝试了一下。引出了一些其他知识。
dlsym()的函数原型是
void* dlsym(void* handle,const char* symbol)
handle是由dlopen打开动态链接库后返回的指针,symbol就是要求获取的函数的名称,函数返回值是void*,指向函数的地址,供调用使用。dlsym的返回值与symbol参数就是本文着重要讲述的要点。
先看下面一段代码
DLLTest.cpp//
#include "DateTime.h"
#include <dlfcn.h>
#include <iostream>
using namespace std;
typedef int(*FuncDatePtr)(DateType* d);
int main(int argc,char* argv[])
{
DateType d;
//TimeType t;
void* dp=0;
char* error=0;
cout<<"Dll Programe Demo:"<<endl;
dp=dlopen("libtime.so",RTLD_NOW);
if(dp==0)
{
cout<<"Open time.so Failed!"<<dlerror()<<endl;
return 1;
}
//int(*f)(DateType* d);
FuncDatePtr f=(FuncDatePtr)dlsym(dp,"getdate2");
//void* test=dlsym(dp,"getdate");
//funcDate=(FuncDate)funcDate;
error=dlerror();
if(error)
{
cout<<"ERROR:"<<error<<endl;
return 1;
}
f(&d);
cout<<"Today Year="<<d.year<<endl;
dlclose(dp);
return 0;
}
//DateTime.h/
#ifndef DATETIME_H_
#define DATETIME_H_
#ifdef __cplusplus
extern "C"{
#endif
struct DateType
{
int year;
int month;
int day;
};
struct TimeType
{
int hour;
int minute;
int second;
};
int getdate2(DateType* d);
int gettime2(TimeType* t);
#ifdef __cplusplus
}
#endif
///DateTime.cpp
#include <time.h>
#include <iostream>
#include "DateTime.h"
using namespace std;
int getdate2(DateType* d)
{
long ti;
struct tm *tm;
time(&ti);
tm=localtime(&ti);
d->year=tm->tm_year+1900;
d->month=tm->tm_mon+1;
d->day=tm->tm_mday;
cout<<"function getdate() loaded!"<<endl;
return 0;
}
int gettime2(TimeType* t)
{
long ti;
struct tm *tm;
time(&ti);
tm=localtime(&ti);
t->hour=tm->tm_hour;
t->minute=tm->tm_min;
t->second=tm->tm_sec;
return 0;
}
DateTime.h,DateTime.cpp两个文件主要包含两个函数getdate2,gettime2(为什么函数名如此,在下面会专门提到)生成了动态链接库libtime.so。DLLTest.cpp中的逻辑就是打开libtime.so这个文件,然后取getdate2函数,并调用它。
第一个要讲的就是 FuncDatePtr f=(FuncDatePtr)dlsym(dp,"getdate2");这个地方。看似简单的一个指针强制转换,实际上并不是那么简单,这里的指针转换不同于一般的指针转换,实际上是将一个指向对象的指针转换为指向函数的指针,这里除了使用传统的强制转换方式,还可以使用C++自带的reinterpret_cast转换符,可以这样写:
void* vf=dlsym(dp,"getdate2");
FuncDatePtr f=reinterpret_cast<FuncDatePtr>(vf);
这个转换只在C++中需要,因而也只在C++中遇到这个问题。
第二点要讲的是DateTime.h中的
#ifdef __cplusplus
extern "C"{
#endif
......
#ifdef __cplusplus
}
#endif
为什么一定要定义这样的宏呢,如果不定义会怎么样,也许很多人(包括我以前)也会这样问。既然有这样的疑问,那一切以实例说话,我去掉这个地方extern “C”的声明,看看编译的程序运行起来会怎么样......
稍作修改后,DllTest依然可以编译,无甚特别的。但运行一下呢?喔喔,怎么老是报错“Error:No Error”?Error是我代码里面定义的输出,那个No Error是dlerror返回的字符串。说是No Error,其实还是有错的,只是错不在dlsym(),而是错在我这里。
可以返回去重新看看那段代码
FuncDatePtr f=(FuncDatePtr)dlsym(dp,"getdate2");
如果你一眼看出问题了,那下面要讲的内容,你可以不看了。如果3分钟内没有发现问题,那么还是听我罗嗦一下。大家肯定听说过C++有Name Mangling机制吧(没有听说的话,可以查看一下Lippman的《深入探索C++对象模型》)。当我去掉了extern “C”的时候,就注定会跌入这个陷阱。我用的g++编译的库文件,g++自然的运用了Name Mangling技术,原本的getdate(DateType& d)函数,很有可能其名字已变成了_getdate_DateType(DateType& d)这样(不同的编译器实现不一样),而对于引用dlsym这样的纯C编译好的库中的函数,他们对Name Mangling机制无甚了解,不会有智能的转换,所以它在C++编译器下不会自动的去作名字处理,当我传入getdate2作为symbol参数的时候,dlsym自然是找不到对应的函数地址的。但他自己又没啥错,就丢给我一串“No Error”。
到了这里,了解没有extern “C”会发生什么事情,并且了解了原因后,我们可以转回来。extern “C”起了什么作用,大家心里应该都清楚了。我懒得引用官方正式的解释了,虽然定义的很完善,但我觉得很挠口,很难懂。所以我把我自己的理解帖出来,不怕大家见笑。
extern “C“就是告诉C++编译器,到了这里要用C编译器的方式来编译,不要什么Name Mangling之类的高科技了。细致的从语法上分析,有两层含义,但同样讲得也是这个意思。
extern 关键字声明被修饰的对象可供其他模块调用(这里乱盖一下,可能被乱砖砸晕。我试着用GCC编译了一下,在C编译器下,如果函数被extern修饰,其他模块引用该函数,可以不包含该模块的头文件的)
“C”,告诉C++编译器,这里用C的标准编译。
好了,dlsym引出的问题一一解决了,这里提醒各位,在编译的时候,最好加上-ldl参数。至于makefile,我想大家都有(或者不需要makefile),编译的问题,我就不罗嗦了。Over!
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/blueshadow18/archive/2007/02/16/1511033.aspx