在C++编译器下dlsym()引发的思考

    技术2022-05-11  73

    C++编译器下dlsym()引发的思考

    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.soDLLTest.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 Errordlerror返回的字符串。说是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


    最新回复(0)