【Linux .so库】【编译通过】生成和使用 手把手导出HelloWorld服务库

动态链接库 (.so) 导出类 导出HelloWorld服务库

导出函数我们经常使用,导出类倒是不经常见到。之前有写过一个Hello World服务器 RAII版,我想把它封装成动态链接库(.so)的形式,供外部调用。
动态链接库的使用,主要分为显示调用、隐式调用。所谓的显示调用就是通过系统提供的API去灵活的按需加载,按需卸载。还有一种情况是,库是第三方的,无法得到源代码,我们只能以显式调用(二进制的方式)加载。 动态链接库的显示加载就是插件思想

  • 动态链接库的编写
    类外部接口声明为虚函数
    另写一个函数,用于获取类的对象指针 createObject
    销毁对象函数,用户回收内存空间 destory
  • 动态链接库的使用
    使用dlopen得到.so句柄
    使用dlsym得到create 函数地址 映射给函数指针
    调用函数指针,得到对象指针
    调用库的接口

大范围上动态链接库的导出、使用主要有以下几种方法:

  • 第一种 利用虚函数导出 『导出HelloWorld服务库』
  • 第二种 利用抽象类导出 『典型的动态链接库 接口设计 抽象类』
  • 第三种 静态加载的方式

windows平台下可以使用:

hDll = LoadLibrary(_T("libHasdk.dll")); LoadLibrary LoadLibraryEx
HA_GetVersion_p = (HA_GetVersion_t)GetProcAddress(hDll, "HA_GetVersion"); GetProcAddress
FreeLibrary(hDll); FreeLibrary

linux平台下可以使用:

p.handle = dlopen("./libdemo_1.so", RTLD_NOW | RTLD_DEEPBIND);
so_init create_fun = (so_init) dlsym(p.handle, "create");
dlclose(p.handle);
//可能也会报错 提供了报错的功能
if (!p.handle) {
        cout << "Cannot open library: " << dlerror() << endl;
        return -1;
    }

代码

CMakeList.txt 文件

//通过『遇到的问题部分』,证明这部分没有问题,之前有怀疑过这里  
set(lib_name pthread dl)  
add_executable(uselib  uselib/common.h uselib/uselib.cpp)
target_link_libraries(uselib ${lib_name})

也可以使用g++编译  //我也使用了
见『典型的动态链接库 接口设计』main.cpp部分

uselib.cpp 文件

#include <iostream>
#include <dlfcn.h>
#include "./common.h"

using namespace std;
using namespace Lionel;
//声明函数指针
typedef socket_RAII *(*so_init)();

//定义插件类来封装,句柄用完后需要释放
struct Plugin {
    void *handle;
    socket_RAII *t;

    Plugin() : handle(NULL), t(NULL) {}

    ~Plugin() {
        if(t) { delete t; }  //virtual ~socket_RAII() needed
        if (handle) { dlclose(handle); }
    }
};

int create_instance(const char *so_file, Plugin &p) {
    //根据特定的模式打开so文件, 获取so文件句柄
    //RTLD_NOW:需要在dlopen返回前,解析出所有未定义符号
    //RTLD_DEEPBIND:在搜索全局符号前先搜索库内的符号,避免同名符号的冲突
    //p.handle = dlopen(so_file, RTLD_NOW | RTLD_DEEPBIND); 调用std::cout会seg fault问题
    p.handle = dlopen(so_file, RTLD_LAZY);
    //p.handle = dlopen(so_file, RTLD_NOW); //也可以
    if (!p.handle) {
        cout << "Cannot open library: " << dlerror() << endl;
        return -1;
    }

    //根据字符串"create"读取库中对应到函数, 并返回函数地址,可以理解为一种间接的“反射机制”
    so_init create_fun = (so_init) dlsym(p.handle, "createObject");
    if (!create_fun) {
        cout << "Cannot load symbol" << endl;
        dlclose(p.handle);
        return -1;
    }

    //调用方法, 获取类实例
    p.t = create_fun();

    return 0;
}

int main() {


        Plugin p1;

        if (0 != create_instance("./libcommon.so", p1)) {
            cout << "create_instance failed" << endl;
            return 0;
        }

       bool flag = p1.t->socket_init();

        if(false == p1.t->socket_bind("127.0.0.1",2333))
            return  -1;
        if(false == p1.t->socket_listen(20))
            return -1;

        p1.t->work();



    return 0;
}

build .so 文件

g++ -fPIC -rdynamic -shared common.cpp -o libcommon.so

common 类

common.h 文件

//
// Created by lionel on 2021/8/15.
//

#ifndef SERVERCODE_TEST_BOOK_COMMON_H
#define SERVERCODE_TEST_BOOK_COMMON_H


#include <netinet/in.h>

using namespace  std;
namespace Lionel {
//
    class socket_RAII {
    public:
        socket_RAII();

        virtual ~socket_RAII();
        virtual bool socket_init();

        virtual bool socket_bind(string ip, int port);

        virtual bool socket_listen(int value);

        virtual void work();

        virtual void Test();

    private:
        void sync_do_work(int fd);

    private:

        int m_listenfd;
        sockaddr_in serveraddr;

    };

}
#endif //SERVERCODE_TEST_BOOK_COMMON_H

common.cpp

//
// Created by lionel on 2021/8/15.
//
#include <iostream>
#include <sys/socket.h>
#include <cassert>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>
//头文件先放到.cpp中
#include "./common.h"

namespace Lionel {
    socket_RAII::socket_RAII() {
        m_listenfd = -1;
    }

    socket_RAII::~socket_RAII() {
        if (-1 != m_listenfd) close(m_listenfd);

//        cout << "~socket_RAII()" << endl;
        printf("~socket_RAII() \n");
    }

    void socket_RAII::sync_do_work(int fd) {
        char writeBuffer[1024] = {'\0'};
        std::sprintf(writeBuffer, "%s", "hello world \n");
        int byteNum = send(fd, writeBuffer, strlen(writeBuffer), 0);
        close(fd);
    }

    bool socket_RAII::socket_init() {
        memset(&serveraddr, 0, sizeof(serveraddr));
        m_listenfd = socket(AF_INET, SOCK_STREAM, 0);


        if (-1 == m_listenfd) {
            //   logger
//            assert(-1 != m_listenfd);
            return false;
        }
        return true;
    }

    bool socket_RAII::socket_bind(string ip, int port) {

        in_addr addr;
        int res = inet_aton(ip.c_str(), &addr);

        if (0 == res) {
//            log ip convert error
            return false;
        }

        serveraddr.sin_family = AF_INET;
        serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
        serveraddr.sin_port = htons(port);

        socklen_t len = sizeof(serveraddr);
        int ret = bind(m_listenfd, (struct sockaddr *) &serveraddr, len);


        if (-1 == ret) {
            return false;
        }
        return true;
    }

    bool socket_RAII::socket_listen(int value) {
        int ret = listen(m_listenfd, value);

        if (-1 == ret) {
            return false;
        }
        return true;

    }

    void socket_RAII::work() {
        do {
            sockaddr_in cliaddr;
            memset(&cliaddr, 0, sizeof(cliaddr));
            socklen_t cliaddr_len = sizeof(cliaddr);

            int connfd = accept(m_listenfd, (sockaddr *) &cliaddr, &cliaddr_len);
            assert (-1 != connfd);
            printf("connfd: %d Client IP: %s Port %d \n", connfd, inet_ntoa(cliaddr.sin_addr),
                   ntohs(cliaddr.sin_port));

            //async_do_work_struct(connfd);
            sync_do_work(connfd);
        } while (0);

    }

    void socket_RAII::Test() {
        printf("socket_RAII::Test() \n");
    }


#ifdef __cplusplus
    extern "C" {
#endif
    socket_RAII *createObject() {
        return new socket_RAII();
    }
#ifdef __cplusplus
    }
#endif

}

client 类


int main() {


    in_addr addr;
    int res = inet_aton("192.168.1.1",&addr);

    //socket 1
    int clientfd = 0;
    int ret = clientfd = socket(AF_INET, SOCK_STREAM, 0);
    assert(-1 != ret);

    struct sockaddr_in servaddr, cliaddr;

    int port = 2333;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);
    socklen_t seraddr_len = sizeof(servaddr);
    ret  = connect(clientfd,(sockaddr *) &servaddr,seraddr_len);

    while(1)
    {
        char readBuffer[1024] = {'\0'};
        int bytenum = recv(clientfd,readBuffer,sizeof (readBuffer),0);
        if(0 == bytenum)
        {
            printf("%s","client shut down.\n");
            break;
        }
        printf("%s",readBuffer);

    }


    close(clientfd);

    return 0;
}

遇到的问题

.so 接口中有 std::cout 就会抛出 segment fault Error,没找到原因。
问题找到了

p.handle = dlopen(so_file, RTLD_NOW | RTLD_DEEPBIND);

去掉RTLD_DEEPBIND 就好了

RTLD_DEEPBIND This means that a self-contained object will use its own symbols in preference to global symbols with the same name contained in objects that have already been loaded.
是寻找符号(symbol)的时候优先内部,再去从外部寻找,可能在寻找的时候出问题了。
dlopen配置RTLD_DEEPBIND文章

典型的动态链接库 接口设计 抽象类

//----------
//polygon.hpp:
//----------
#ifndef POLYGON_HPP
#define POLYGON_HPP


class polygon {
  protected:
    double side_length_;


  public:
    polygon(): side_length_(0) {}


  virtual ~polygon() {}


  void set_side_length(double side_length) {
    side_length_ = side_length;
  }


  virtual double area() const = 0;
};


// the types of the class factories
typedef polygon* create_t();
typedef void destroy_t(polygon*);


#endif
//----------
//triangle.cpp:
//----------
#include "polygon.hpp"
#include <cmath>


class triangle : public polygon {
public:
  virtual double area() const {
    return side_length_ * side_length_ * sqrt(3) / 2;
  }
};


// the class factories
extern "C" polygon* create() {
  return new triangle;
}

extern "C" void destroy(polygon* p) {
  delete p;
}
//----------
//main.cpp:
//----------
#include <dlfcn.h>
#include "polygon.hpp"
#include <iostream>


int main() {
  using std::cout;
  using std::cerr;

  // load the triangle library
  void* triangle = dlopen("./triangle.so", RTLD_LAZY);
  if (!triangle) {
    cerr << "Cannot load library: " << dlerror() << '\n';
    return 1;
  }

  // reset errors
  dlerror();

  // load the symbols
  create_t* create_triangle = (create_t*) dlsym(triangle, "create");
  const char* dlsym_error = dlerror();

  if (dlsym_error) {
    cerr << "Cannot load symbol create: " << dlsym_error << '\n';
    return 1;
  }

  destroy_t* destroy_triangle = (destroy_t*) dlsym(triangle, "destroy");
  dlsym_error = dlerror();
  if (dlsym_error) {
    cerr << "Cannot load symbol destroy: " << dlsym_error << '\n';
    return 1;
  }


  // create an instance of the class
  polygon* poly = create_triangle();


  // use the class
  poly->set_side_length(7);
  cout << "The area is: " << poly->area() << '\n';


  // destroy the class
  destroy_triangle(poly);


  // unload the triangle library
  dlclose(triangle);
}

/*
动态库的编译:
$ g++ -Wall -g -fPIC -o triangle.so -shared triangle.cpp

主程序的编译与运行:
$ g++ -Wall -g -rdynamic main.cpp -o compile_c++LIBc++ -ldl 
$ ./compile_c++LIBc++ 
The area is: 42.4352


加载类时有一些值得注意的地方:
◆ 你必须(译者注:在模块或者说共享库中)同时提供一个创造函数和一个销毁函数,
   且不能在执行文件内部使用delete来销毁实例,只能把实例指针传递给模块的销毁函数处理。
   这是因为C++里头,new操作符可以被重载;
   这容易导致new-delete的不匹配调用,造成莫名其妙的内存泄漏和段错误。
   这在用不同的标准库链接模块和可执行文件时也一样。
◆ 接口类的析构函数在任何情况下都必须是虚函数(virtual)。
   因为即使出错的可能极小,近乎杞人忧天了,但仍旧不值得去冒险,反正额外的开销微不足道。
   如果基类不需要析构函数,定义一个空的(但必须虚的)析构函数吧,否则你迟早要遇到问题,我向您保证。
   你可以在comp.lang.c++ FAQ( http://www.parashift.com/c++-faq-lite/ )的
   第20节了解到更多关于该问题的信息。
*/

其他

-w warning
-Wall warning all
-rdynamic g++ 9.4.0 没有找到 查了发现和 -export-dynamic 是一个,用来导出符号
-export-dynamic
Pass the flag ‘-export-dynamic’ to the ELF linker, on targets that support
it. This instructs the linker to add all symbols, not only used ones, to the
dynamic symbol table. This option is needed for some uses of dlopen or to
allow obtaining backtraces from within a program.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux中,动态链接库(.so)的使用需要以下几个步骤: 1. 编译动态:首先,你需要编写源代码并将其编译为动态。你可以使用编译器(如gcc)来完成此操作。以下是一个示例命令: ``` gcc -shared -o libexample.so example.c ``` 这将把名为`example.c`的源文件编译为动态`libexample.so`。 2. 设置的搜索路径:在使用动态之前,你需要将的路径添加到链接器的搜索路径中,以便系统能够找到文件。你可以通过以下方式之一来实现: - 将文件所在的目录添加到`LD_LIBRARY_PATH`环境变量中: ``` export LD_LIBRARY_PATH=/path/to/library:$LD_LIBRARY_PATH ``` - 将文件所在的目录添加到系统的动态链接器配置文件中(如`/etc/ld.so.conf`),然后运行以下命令更新链接器缓存: ``` sudo ldconfig ``` 3. 编写并编译程序:接下来,你需要编写你的程序,并在编译时指定使用动态。假设你的程序为`program.c`,以下是一个示例命令: ``` gcc -o program program.c -L/path/to/library -l example ``` 这将使用动态`libexample.so`进行链接。 4. 运行程序:最后,你可以运行生成的可执行文件: ``` ./program ``` 需要注意的是,动态使用可能因编程语言、编译器和操作系统而有所不同。上述步骤是一个基本的示例,具体的使用方法应根据你的需求和环境进行调整。此外,你还可以参考相关的文档和手册来了解更多关于动态使用方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值