目录
动态链接库 (.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.