zoukankan      html  css  js  c++  java
  • [转]linux 调用动态库so文件

    记录一个面试被问到的问题。

    extern 有什么用途?

    除了多文件共享全局变量外还有呢?

    extern "C" 的功能?

    我想看完这篇文章就可以知道第三个问题了。

      关于动态调用动态库方法说明 
    一、        动态库概述 
    1、  动态库的概念 
    日常编程中,常有一些函数不需要进行编译或者可以在多个文件中使用(如数据库输入/输出操作或屏幕控制等标准任务函数)。可以事先对这些函数进行编译,然后将它们放置在一些特殊的目标代码文件中,这些目标代码文件就称为库。库文件中的函数可以通过连接程序与应用程序进行链接,这样就不必在每次开发程序时都对这些通用的函数进行编译了。

           动态库是一种在已经编译完毕的程序开始启动运行时,才被加载来调用其中函数的库。其加载方式与静态库截然不同。

    2、  动态库的命名 
    Linux下,动态库通常以.so(share object)结尾。(通常/lib和/usr/lib等目录下存在大量系统提供的以.so结尾的动态库文件)

    Windows下,动态库常以.dll结尾。(通常C:windowsSystem32等目录下存在大量系统提供的以.dll结尾的动态库文件)

    3、  动态库与静态库之间的区别 
    静态库是指编译连接时,把库文件的代码全部加入到可执行文件中,所以生成的文件较大,但运行时,就不再需要库文件了。即,程序与静态库编译链接后,即使删除静态库文件,程序也可正常执行。

    动态库正好相反,在编译链接时,没有把库文件的代码加入到可执行文件中,所以生成的文件较小,但运行时,仍需要加载库文件。即,程序只在执行启动时才加载动态库,如果删除动态库文件,程序将会因为无法读取动态库而产生异常。

    二、        Linux下动态调用动态库 
    备注:以下linux实例说明都是在RedHat 5.1系统+ gcc 版本 4.1.2 20080704 (Red Hat 4.1.2-46)上实现。

    1、  .so动态库的生成 
    可使用gcc或者g++编译器生成动态库文件(此处以g++编译器为例)

    g++ -shared -fPIC -c XXX.cpp

    g++ -shared -fPIC -o XXX.so XXX.o

    2、  .so动态库的动态调用接口函数说明 
    动态库的调用关系可以在需要调用动态库的程序编译时,通过g++的-L和-l命令来指定。例如:程序test启动时需要加载目录/root/src/lib中的libtest_so1.so动态库,编译命令可照如下编写执行:

    g++ -g -o test test.cpp –L/root/src/lib –ltest_so1

    (此处,我们重点讲解动态库的动态调用的方法,关于静态的通过g++编译命令调用的方式不作详细讲解,具体相关内容可上网查询)

    Linux下,提供专门的一组API用于完成打开动态库,查找符号,处理出错,关闭动态库等功能。

    下面对这些接口函数逐一介绍(调用这些接口时,需引用头文件#include <dlfcn.h>):

    1)        dlopen

    函数原型:void *dlopen(const char *libname,int flag);

    功能描述:dlopen必须在dlerror,dlsym和dlclose之前调用,表示要将库装载到内存,准备使用。如果要装载的库依赖于其它库,必须首先装载依赖库。如果dlopen操作失败,返回NULL值;如果库已经被装载过,则dlopen会返回同样的句柄。

    参数中的libname一般是库的全路径,这样dlopen会直接装载该文件;如果只是指定了库名称,在dlopen会按照下面的机制去搜寻:

    a.根据环境变量LD_LIBRARY_PATH查找

    b.根据/etc/ld.so.cache查找

    c.查找依次在/lib和/usr/lib目录查找。

    flag参数表示处理未定义函数的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暂时不去处理未定义函数,先把库装载到内存,等用到没定义的函数再说;RTLD_NOW表示马上检查是否存在未定义的函数,若存在,则dlopen以失败告终。

    2)        dlerror

    函数原型:char *dlerror(void);

    功能描述:dlerror可以获得最近一次dlopen,dlsym或dlclose操作的错误信息,返回NULL表示无错误。dlerror在返回错误信息的同时,也会清除错误信息。

    3)        dlsym

    函数原型:void *dlsym(void *handle,const char *symbol);

    功能描述:在dlopen之后,库被装载到内存。dlsym可以获得指定函数(symbol)在内存中的位置(指针)。如果找不到指定函数,则dlsym会返回NULL值。但判断函数是否存在最好的方法是使用dlerror函数,

    4)        dlclose

    函数原型:int dlclose(void *);

    功能描述:将已经装载的库句柄减一,如果句柄减至零,则该库会被卸载。如果存在析构函数,则在dlclose之后,析构函数会被调用。

    3、  普通函数的调用 
    此处以源码实例说明。各源码文件关系如下:

    test_so1.h和test_so1.cpp生成test_so1.so动态库。

    test_so2.h和test_so2.cpp生成test_so2.so动态库。

    test_dl.cpp生成test_dl可执行程序,test_dl通过dlopen系列等API函数,并使用函数指针以到达动态调用不同so库中test函数的目的。

    ////////////////////////////////test_so1.h//////////////////////////////////////////////////////

    #include <stdio.h>

    #include <stdlib.h>

    extern "C" {

    int test(void);

    }

    ////////////////////////////////ttest_so1.cpp//////////////////////////////////////////////////////

    #include "test_so1.h"

    int test(void)

    {

            printf("USING TEST_SO1.SO NOW! ");//注意此处与test_so2.cpp中的

                                                                            //test函数的不同

    return 1;

    }

    //////////////////////////////// test_so2.h //////////////////////////////////////////////////////

    #include <stdio.h>

    #include <stdlib.h>

    extern "C" {

    int test(void);

    }

    ////////////////////////////////ttest_so2.cpp//////////////////////////////////////////////////////

    #include "test_so2.h"

    int test(void)

    {

            printf("USING TEST_SO2.SO NOW! ");//注意此处与test_so1.cpp中的

                                                                            //test函数的不同

            return 1;

    }

    ////////////////////////////////test_dl.cpp//////////////////////////////////////////////////////

    #include <stdio.h>

    #include <stdlib.h>

    #include <dlfcn.h>

    int main(int argc, char **argv)

    {

            if(argc!=2)

            {

                    printf("Argument Error! You must enter like this: ");

                    printf("./test_dl test_so1.so ");

                    exit(1);

            }

            void *handle;

            char *error;

            typedef void (*pf_t)();   //声明函数指针类型

            handle = dlopen (argv[1], RTLD_NOW);     //打开argv[1]指定的动态库

            if (!handle)

            {

                    fprintf (stderr, "%s ", dlerror());

                    exit(1);

            }

            dlerror();  

             pf_t pf=(pf_t)dlsym(handle,"test" );    //指针pf指向test在当前内存中的地址

            if ((error = dlerror()) != NULL)

            {

                    fprintf (stderr, "%s ", error);

                    exit(1);

            }

            pf();        //通过指针pf的调用来调用动态库中的test函数

            dlclose(handle);      //关闭调用动态库句柄

            return 0;

    }

    ////////////////////////////////makefile//////////////////////////////////////////////////////

    .SUFFIXES: .c .cpp .o

    CC=g++  -shared -fPIC

    GCC=g++

    all:test_so1.so test_so2.so test_dl clean

    OBJ1=test_so1.o

    OBJ2=test_so2.o

    OBJ3=test_dl.o

    test_so1.so:$(OBJ1)

            $(CC) -o $@ $?

            cp $@ /usr/lib

    test_so2.so:$(OBJ2)

            $(CC) -o $@ $?

            cp $@ /usr/lib

    test_dl:$(OBJ3)

            $(GCC)  -o $@ $? -ldl

    .cpp.o:

            $(CC) -c $*.cpp

    .c.o:

            $(CC) -c $*.c

    clean:

            rm -f *.o

    上述源程序中,需重点注意两个问题:

    1、test_dl.cpp中,对于动态库中的test函数调用是通过函数指针来完成的。

    2、test_so1.h和test_so2.h中都使用了extern "C"。

    在每个C++程序(或库、目标文件)中,所有非静态(non-static)函数在二进制文件中都是以“符号(symbol)”形式出现的。这些符号都是唯一的字符串,从而把各个函数在程序、库、目标文件中区分开来。

    在C中,符号名正是函数名:strcpy函数的符号名就是“strcpy”。这可能是因为两个非静态函数的名字一定各不相同的缘故。

    而C++允许重载(不同的函数有相同的名字但不同的参数),并且有很多C所没有的特性──比如类、成员函数、异常说明──几乎不可能直接用函数名作符号名。为了解决这个问题,C++采用了所谓的name mangling。它把函数名和一些信息(如参数数量和大小)杂糅在一起,改造成奇形怪状,只有编译器才懂的符号名。例如,被mangle后的foo可能看起来像foo@4%6^,或者,符号名里头甚至不包括“foo”。

    其中一个问题是,C++标准(目前是[ISO14882])并没有定义名字必须如何被mangle,所以每个编译器都按自己的方式来进行name mangling。有些编译器甚至在不同版本间更换mangling算法(尤其是g++ 2.x和3.x)。即使您搞清楚了您的编译器到底怎么进行mangling的,从而可以用dlsym调用函数了,但可能仅仅限于您手头的这个编译器而已,而无法在下一版编译器下工作。

    用 extern "C"声明的函数将使用函数名作符号名,就像C函数一样。因此,只有非成员函数才能被声明为extern "C",并且不能被重载。尽管限制多多,extern "C"函数还是非常有用,因为它们可以象C函数一样被dlopen动态加载。冠以extern "C"限定符后,并不意味着函数中无法使用C++代码了,相反,它仍然是一个完全的C++函数,可以使用任何C++特性和各种类型的参数。所以extern "C" 只是告诉编译器编和链接的时候都用c的方式的函数名字,函数里的内容可以为c的代码也可以为c++的。


    执行makefile正常编译后,可生成test_so1.so、test_so2.so动态库以及test_dl执行程序。可执行test_dl,显示结果如下:

    [root@localhost so_src]# ./test_dl test_so1.so

    USING TEST_SO1.SO NOW!

    [root@localhost so_src]# ./test_dl test_so2.so

    USING TEST_SO2.SO NOW!

    [root@localhost so_src]# ./test_dl

    Argument Error! You must enter like this:

    ./test_dl test_so1.so

    备注:如果我们去掉test_so1.h和test_so2.h中的extern "C",重新编译执行后将可能会出现什么情况?有兴趣的朋友可以试下:

    [root@localhost so_src]# ./test_dl test_so1.so

    /usr/lib/test_so1.so: undefined symbol: test

    [root@localhost so_src]# ./test_dl test_so2.so

    /usr/lib/test_so2.so: undefined symbol: test

    4、  类的调用 
    加载类有点困难,因为我们需要类的一个实例,而不仅仅是一个函数指针。我们无法通过new来创建类的实例,因为类是在动态库中定义的而不是在可执行程序中定义的,况且有时候我们连动态库中具体的类的名字都不知道。

    解决方案是:利用多态性!我们在可执行文件中定义一个带虚成员函数的接口基类,而在模块中定义派生实现类。通常来说,接口类是抽象的(如果一个类含有虚函数,那它就是抽象的)。因为动态加载类往往用于实现插件,这意味着必须提供一个清晰定义的接口──我们将定义一个接口类和派生实现类。

    接下来,在模块中,我们会定义两个附加的类工厂函数(class factory functions)(或称对象工厂函数)。其中一个函数创建一个类实例,并返回其指针;另一个函数则用以销毁该指针。这两个函数都以extern "C"来限定修饰。

           实例如下:

           test_base.hpp中定义一个含有纯虚函数virtual void display() const = 0的基类。

           test_1.cpp中定义继承类test1,并实现虚函数virtual void display() const的定义,并实现一个创建类函数和一个销毁类指针函数。

           test_2.cpp中定义继承类test2,并实现虚函数virtual void display() const的定义,并实现一个创建类函数和一个销毁类指针函数。

    main.cpp中实现动态的调用不同库中的display()方法。

    ////////////////////////////////test_base.hpp//////////////////////////////////////////////////////

    #ifndef TEST_BASE_HPP

    #define TEST_BASE_HPP

    #include <iostream>

    using namespace std;

    class test_base {

    public:

        test_base(){}

        virtual ~test_base() {}

        void call_base() {

            cout << "call base" << endl;

        }

        virtual void display() const = 0  ;

    };

    // the types of the class factories

    typedef test_base* create_t();

    typedef void destroy_t(test_base*);

    #endif

    ////////////////////////////////test1.cpp//////////////////////////////////////////////////////

    #include "test_base.hpp"

    class test1 : public test_base {

    public:

        virtual void display() const {

            cout << "Running in test1.so Now" << endl;

        }

    };

    // the class factories

    extern "C" test_base* create() {

        return new test1;

    }

    extern "C" void destroy(test_base* p) {

        delete p;

    }

    ////////////////////////////////test1.cpp//////////////////////////////////////////////////////

    #include "test_base.hpp"

    class test2 : public test_base {

    public:

        virtual void display() const {

            cout << "Running in test2.so Now" << endl;

        }

    };

    // the class factories

    extern "C" test_base* create() {

        return new test2;

    }

    extern "C" void destroy(test_base* p) {

        delete p;

    }

    ////////////////////////////////main.cpp//////////////////////////////////////////////////////

    #include "test_base.hpp"

    #include <iostream>

    #include <dlfcn.h>

    int main(int argc , char** argv) {

        // load the test library

        if(argc!=2)

        {

            cout << "Argument Error! You must enter like this: " << ' ';

            cout << "./a.out test_1.so " << ' ';

            return 1;

        }

      

        void* test_index = dlopen(argv[1], RTLD_NOW);

        if (!test_index) {

            cerr << "Cannot load library: " << dlerror() << ' ';

            return 1;

        }

        // reset errors

        dlerror();

      

        // load the symbols

        create_t* create_test = (create_t*) dlsym(test_index, "create");

        const char* dlsym_error = dlerror();

        if (dlsym_error) {

            cerr << "Cannot load symbol create: " << dlsym_error << ' ';

            return 1;

        }

      

        destroy_t* destroy_test = (destroy_t*) dlsym(test_index, "destroy");

        dlsym_error = dlerror();

        if (dlsym_error) {

            cerr << "Cannot load symbol destroy: " << dlsym_error << ' ';

            return 1;

        }

        // create an instance of the class

        test_base* c_test = create_test();

        // use the class

        c_test->display();

        destroy_test(c_test);

        // unload the test library

        dlclose(test_index);

    }

    ////////////////////////////////makefile//////////////////////////////////////////////////////

    .SUFFIXES: .c .cpp .o

    CC=g++ -g -shared -fPIC

    GCC=g++ -g

    all:clear test_1.so a.out test_2.so clean

    OBJ1=test_1.o

    OBJ2=main.o

    OBJ3=test_2.o

    clear:

            rm -rf *.so a.out b.out

    test_1.so:$(OBJ1)

            $(CC) -o $@ $?

            cp $@ /usr/lib

    a.out:$(OBJ2)

            $(GCC)  -o $@ $? -ldl

    test_2.so:$(OBJ3)

            $(CC) -o $@ $?

            cp $@ /usr/lib

    .cpp.o:

            $(CC) -c $*.cpp

    .c.o:

            $(CC) -c $*.c

    clean:

            rm -f *.o

    执行makefile正常编译后,可生成test_1.so、test_2.so动态库以及a.out执行程序。可执行a.out,显示结果如下:

    [root@localhost c++_so_src]# ./a.out test_1.so

    Running in test1.so Now

    [root@localhost c++_so_src]# ./a.out test_2.so

    Running in test2.so Now

    [root@localhost c++_so_src]# ./a.out

    Argument Error! You must enter like this:

    ./a.out test_1.so

  • 相关阅读:
    升级windows 11小工具
    windows 10更新升级方法
    您需要了解的有关 Oracle 数据库修补的所有信息
    Step by Step Apply Rolling PSU Patch In Oracle Database 12c RAC Environment
    Upgrade Oracle Database Manually from 12.2.0.1 to 19c
    如何应用版本更新 12.2.0.1.210420(补丁 32507738 – 2021 年 4 月 RU)
    xtrabackup 安装、备份和恢复
    Centos_Lvm expand capacity without restarting CentOS
    Centos_Lvm_Create pv vg lv and mount
    通过全备+relaylog同步恢复被drop的库或表
  • 原文地址:https://www.cnblogs.com/chenhuan001/p/7688731.html
Copyright © 2011-2022 走看看