引言 - 也许是修行

  很久以前写过关于动态库科普文章, 废话反正是说了好多. 核心就是在 linux 上面玩了一下 dlopen : )

   linux动态库编译和使用详细剖析 - https://www.cnblogs.com/life2refuel/p/5332358.html

  本文是上面文章的补充部分. 因为单纯的 linux 玩还是不太通用 ~

  动态库最简单理解是为了解决操作系统级别的代码复用出现的技术. 现在服务器开发技术中,

几乎不再出现. 首先不好用, 其次多环境中常容易出错. 繁荣期应该在上古时代(2000-2005),  动态库技术

是一个很考验程序员的修养的基本功. (当前服务器主流是静态库, 客户端应该还是被动态库统治) 这里不妨扯一些

  Windows 和 Unix 下动态链接库的区别  https://blog.codingnow.com/2006/11/windows_unix_dynamic_library.html

  (没想到, 当年云风, 也会被上古的 winds 老前辈们 摩擦摩擦 ~.~ )

  想深入了解动态库原理, 可以多看几遍 <<程序员自我修养>> and <<高级C/C++ 编译技术>> : )

虽然看完没什么暖用. 但也可以解闷(特别是后面那本小册子)不是吗 ?      

  那开始代码之旅, 不来虚的 ~  

前言 - 准备测试环境

  动态库当你面试时候碰到的话, 实在答不上上来. 就别继续说了概念了.  就简单说我'不会', 但我会写会用的很溜.

也许能到 61 分吧.  把这篇文章代码手打出来 : ) 咱们就玩实心的.

先看编译模块 Makefile

main.exe:
gcc -fPIC -O2 -Wall -shared -o foo.dll foo.c
gcc -g -Wall -O2 -o main.exe main.c dllso.c -ldl clean:
-rm -rf *.o *.so *.exe *.out *.dll

待编译成动态库的文件 foo.h foo.c

#ifndef _F_FOO
#define _F_FOO #include <stdio.h> #ifndef extern
# if defined(_MSC_VER)
# define extern extern __declspec(dllexport)
# else
# define extern extern
# endif
#endif//extern extern void * foo(int hoge); #undef extern #endif//_F_FOO

这里构建的 extern 宏, 是不是很飘. 用于解决 cl 和 gcc 对于动态库导出约束不一样.

cl 默认没有 __declspec(dllexport) 就不导出. gcc 默认全部导出, __attribute__((visibility("hidden"))) 可以设置不可见.

#include "foo.h"

void *
foo(int hoge) {
static int _id; ++_id; // 简单自增长
printf("foo(%d) = %d\n", hoge, _id);
return &_id;
}

实现没有什么好说的.

其中动态库协助接口设计 dllso.h 

#ifndef _H_DLLSO
#define _H_DLLSO //
// ds_create - 构造加载动态库文件
// path : 动态库文件路径
// return : 失败返回 NULL
//
extern void * ds_create(const char * path); //
// ds_parse - 解析动态库文件, 返回执行函数
// so : 动态库对象
// name : 待解析的函数名称
// return : 返回解析的函数地址, NULL 是失败
//
extern void * ds_parse(void * so, const char * name); //
// ds_delete - 释放卸载动态库文件
// so : 动态库对象
// return : void
//
extern void ds_delete(void * so); #endif//_H_DLLSO

最终测试文件 main.c

#include "dllso.h"
#include <stdio.h>
#include <stdlib.h> #define _STR_FOO "./foo.dll" // 简单动态库测试
int main(int argc, char * argv[]) {
void * so = ds_create(_STR_FOO);
if (NULL == so) {
fprintf(stderr, "ds_create %s err", _STR_FOO);
exit(EXIT_FAILURE);
} void * (* foo)(int) = ds_parse(so, "foo");
if (NULL == foo) {
fprintf(stderr, "ds_parse err so = %p\n", so);
exit(EXIT_FAILURE);
} printf("foo() = %p\n", foo()); ds_delete(so);
return EXIT_SUCCESS;
}

到这里希望读者理解作者的思路. 动态库处理划分为三部分, 装载, 解析, 卸载.

这里扯淡一点, 对于动态库设计处理. winds 离不开 LoadLibrary 函数簇. linux 离不开 dlopen 函数簇.(自行科普)

当前测试核心思路就在 dllso.c

#include "dllso.h"

#if defined(_MSC_VER)
# include <windows.h> #define RTLD_LAZY LOAD_WITH_ALTERED_SEARCH_PATH
#define dlopen(filename, flags) LoadLibraryEx(filename, NULL, flags)
#define dlsym(handle, symbol) GetProcAddress(handle, symbol)
#define dlclose(handle) FreeLibrary(handle) #else
# include <dlfcn.h>
#endif //
// ds_create - 构造加载动态库文件
// path : 动态库文件路径
// return : 失败返回 NULL
//
inline void *
ds_create(const char * path) {
return dlopen(path, RTLD_LAZY);
} //
// ds_parse - 解析动态库文件, 返回执行函数
// so : 动态库对象
// name : 待解析的函数名称
// return : 返回解析的函数地址, NULL 是失败
//
inline void *
ds_parse(void * so, const char * name) {
return dlsym(so, name);
} //
// ds_delete - 释放卸载动态库文件
// so : 动态库对象
// return : void
//
inline void
ds_delete(void * so) {
if (so) {
dlclose(so);
}
}

核心思路是在 winds 上面采用最小的代价构建了一个 linux dlopen 操作三部曲.

实现的很一般般. 或者说不痛快, 胜在可用, 代价小.  推荐喜欢但基础一般的朋友可以练习练习多写写.

文章到这里也快尾声了, 算 Over ~

正文 - 原地踌躇, 也走到了 中年

  人生的修行, 那么让人不可捉摸. 心无旁贷, 好想有机会再能看看大山中柿子树. 甜甜涩涩的 ~

很幸运你读到这里. 不妨带大家看看 libuv 怎么封装 dll 的. 总的思路都一样, winds 向着 *nix 靠拢. 写的很平稳厚实.

先看基础部分 (dl.c, uv.h, internal.h)

/* Platform-specific definitions for uv_dlopen support. */
#define UV_DYNAMIC FAR WINAPI
typedef struct {
HMODULE handle;
char* errmsg;
} uv_lib_t;

static void uv__format_fallback_error(uv_lib_t* lib, int errorno){
DWORD_PTR args[] = { (DWORD_PTR) errorno };
LPSTR fallback_error = "error: %1!d!"; FormatMessageA(FORMAT_MESSAGE_FROM_STRING |
FORMAT_MESSAGE_ARGUMENT_ARRAY |
FORMAT_MESSAGE_ALLOCATE_BUFFER,
fallback_error, , ,
(LPSTR) &lib->errmsg,
, (va_list*) args);
}

static int uv__dlerror(uv_lib_t* lib, const char* filename, DWORD errorno) {
DWORD_PTR arg;
DWORD res;
char* msg; if (lib->errmsg) {
LocalFree(lib->errmsg);
lib->errmsg = NULL;
} if (errorno == )
return ; res = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorno,
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
(LPSTR) &lib->errmsg, , NULL); if (!res && GetLastError() == ERROR_MUI_FILE_NOT_FOUND) {
res = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorno,
, (LPSTR) &lib->errmsg, , NULL);
} if (res && errorno == ERROR_BAD_EXE_FORMAT && strstr(lib->errmsg, "%1")) {
msg = lib->errmsg;
lib->errmsg = NULL;
arg = (DWORD_PTR) filename;
res = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_ARGUMENT_ARRAY |
FORMAT_MESSAGE_FROM_STRING,
msg,
, , (LPSTR) &lib->errmsg, , (va_list*) &arg);
LocalFree(msg);
} if (!res)
uv__format_fallback_error(lib, errorno); return -;
}

uv__dlerror 是构造 linux dlerror 前戏.
作者设计的意图是围绕 winds FormatMessageA api 错误处理用法包装.

写的挺漂亮的. 其实还有更好更偷懒的实现方式, 参照基础项目 structc 中的

  stderr https://github.com/wangzhione/structc/blob/master/structc/system/stderr.c

winds 实现 strerror 函数. 用于解决 GetLastError 和 FormatMessage 配合的不爽.

libuv 写的真好, 不知道 niginx 源码是什么水平, 有机会研究一下.一块分享.

随后的代码很轻松和标准

int uv_dlopen(const char* filename, uv_lib_t* lib) {
WCHAR filename_w[]; lib->handle = NULL;
lib->errmsg = NULL; if (!MultiByteToWideChar(CP_UTF8,
,
filename,
-,
filename_w,
ARRAY_SIZE(filename_w))) {
return uv__dlerror(lib, filename, GetLastError());
} lib->handle = LoadLibraryExW(filename_w, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
if (lib->handle == NULL) {
return uv__dlerror(lib, filename, GetLastError());
} return ;
} void uv_dlclose(uv_lib_t* lib) {
if (lib->errmsg) {
LocalFree((void*)lib->errmsg);
lib->errmsg = NULL;
} if (lib->handle) {
/* Ignore errors. No good way to signal them without leaking memory. */
FreeLibrary(lib->handle);
lib->handle = NULL;
}
} int uv_dlsym(uv_lib_t* lib, const char* name, void** ptr) {
*ptr = (void*) GetProcAddress(lib->handle, name);
return uv__dlerror(lib, "", *ptr ? : GetLastError());
} const char* uv_dlerror(const uv_lib_t* lib) {
return lib->errmsg ? lib->errmsg : "no error";
}

其中 uv_dlsym 思路很漂亮. 返回 int 错误类型. 可惜也不是线程安全的.

其中用到的几个宏翻译如下

#define CP_UTF7                   65000       // UTF-7 translation
#define CP_UTF8 65001 // UTF-8 translation #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

其中 libuv uv_dlclose 实现行为和 linux 原生的 dlclose 原生行为基本一致, 没有对 NULL 判断.

让使用的朋友自己维护 NULL这块性能. 总体而言 libuv dl.c 实现的很舒服.

关于动态库话题简单的讲到这里了. 希望总是要有的 ~ ~

后记 - 欢迎指正, 感谢阅读, 错误是难免的.

  青春往事 - http://music.163.com/m/song?id=528723987&userid=16529894

  (记 龙泉寺 : )

linux动态库编译和使用详细剖析 - 后续的更多相关文章

  1. linux动态库编译和使用详细剖析

    引言 重点讲述linux上使用gcc编译动态库的一些操作.并且对其深入的案例分析.最后介绍一下动态库插件技术, 让代码向后兼容.关于linux上使用gcc基础编译, 预编译,编译,生成机械码最后链接输 ...

  2. linux动态库编译和使用

    linux动态库编译和使用详细剖析 引言 重点讲述linux上使用gcc编译动态库的一些操作.并且对其深入的案例分析.最后介绍一下动态库插件技术, 让代码向后兼容.关于linux上使用gcc基础编译, ...

  3. Linux动态库的编译与使用 转载【转】

    转自:http://www.cnblogs.com/leaven/archive/2010/06/11/1756294.html http://hi.baidu.com/linuxlife/blog/ ...

  4. 【转载】Linux动态库搜索路径的技巧

    转自:http://soft.chinabyte.com/os/232/11488732_2.shtml 众所周知,Linux动 态库的默认搜索路径是/lib和/usr/lib.动态库被创建后,一般都 ...

  5. linux动态库加载RPATH, RUNPATH

    摘自http://gotowqj.iteye.com/blog/1926771 linux动态库加载RPATH, RUNPATH 链接动态库 如何程序在连接时使用了共享库,就必须在运行的时候能够找到共 ...

  6. Linux动态库(.so)搜索路径

    主要内容: 1.Linux动态库.so搜索路径 编译目标代码时指定的动态库搜索路径: 环境变量LD_LIBRARY_PATH指定的动态库搜索路径: 配置文件/etc/ld.so.conf中指定的动态库 ...

  7. Linux动态库的导出控制

    在实际工作中,许多软件模块是以动态库的方式提供的.做为模块开发人员,我们不仅要掌握如何编写和构建动态库,还要了解如何控制动态库的导出接口,这样,我们可以向模块的用户仅导出必要的接口,而另一些内部接口, ...

  8. Linux动态库搜索路径的技巧

    众所周知,Linux动态库的默认搜索路径是/lib和/usr/lib.动态库被创建后,一般都复制到这两个目录中.当程序执行时需要某动态库,并且该动态库还未加载到内存中,则系统会自动到这两个默认搜索路径 ...

  9. .netcore在linux下使用P/invoke方式调用linux动态库

    http://www.mamicode.com/info-detail-2358309.html   .netcore下已经实现了通过p/invoke方式调用linux的动态链接库(*.so)文件 1 ...

随机推荐

  1. C++解析(25):关于动态内存分配、虚函数和继承中强制类型转换的疑问

    0.目录 1.动态内存分配 1.1 new和malloc的区别 1.2 delete和free的区别 2.虚函数 2.1 构造函数与析构函数是否可以成为虚函数? 2.2 构造函数与析构函数是否可以发生 ...

  2. [AT2000] [agc002_f] Leftmost Ball

    题目链接 AtCoder:https://agc002.contest.atcoder.jp/tasks/agc002_f 洛谷:https://www.luogu.org/problemnew/sh ...

  3. CentOS 7下安装pptp服务端手记 ok

    主要配置步骤 1. 安装前检查系统支持 2. 安装必要包 3. 修改相关配置文件 4. 设置开机自动启动 pptpd, iptables 5. iptables配置网络 6. 阿里云ECS可能还需要几 ...

  4. JDBC连接SQL Server

    下载jdbc驱动包 下载地址,我下载的是exe版本的,其实是格自解压包.下载完毕之后,双击运行,会解压在当前目录下. Microsoft SQL Server JDBC Driver 3.0\sqlj ...

  5. bzoj4144【AMPPZ2014】Petrol

    题解:  首先注意到起点和终点都是加油站;          假设中途经过某个非加油站的点u,u连到v,离u最近的加油站是x,那么从u到x加油后回到u,再到v一定不比直接从u到v差:        因 ...

  6. php7实现基于openssl的加密解密方法

    还需要注意的是加密字符串长度问题,如果加密字符串长度太长需要进行分段加解密,如下代码: 加密:(公匙加密,私密一般用来解密) function encrypt($originalData){ $pub ...

  7. Nginx基本功能极速入门

    http://xxgblog.com/2015/05/17/nginx-start/ 本文主要介绍一些Nginx的最基本功能以及简单配置,但不包括Nginx的安装部署以及实现原理.废话不多,直接开始. ...

  8. python学习(十)元类

    python 可以通过`type`函数创建类,也可通过type判断数据类型 import socket from io import StringIO import sys class TypeCla ...

  9. Centos 修改limits.conf open files后不生效的解决办法

    线上几台APACHE服务器报过三.四次open files的问题,导致服务不可用,执行ulimit -n 查看后,发现是默认的1024,找到原因所在了,就去修改下/etc/security/limit ...

  10. Spring整合JMS(四)——事务管理(转)

    *注:别人那复制来的 Spring提供了一个JmsTransactionManager用于对JMS ConnectionFactory做事务管理.这将允许JMS应用利用Spring的事务管理特性.Jm ...