linux动态库编译和使用详细剖析 - 后续
引言 - 也许是修行
很久以前写过关于动态库科普文章, 废话反正是说了好多. 核心就是在 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动态库编译和使用详细剖析 - 后续的更多相关文章
- linux动态库编译和使用详细剖析
引言 重点讲述linux上使用gcc编译动态库的一些操作.并且对其深入的案例分析.最后介绍一下动态库插件技术, 让代码向后兼容.关于linux上使用gcc基础编译, 预编译,编译,生成机械码最后链接输 ...
- linux动态库编译和使用
linux动态库编译和使用详细剖析 引言 重点讲述linux上使用gcc编译动态库的一些操作.并且对其深入的案例分析.最后介绍一下动态库插件技术, 让代码向后兼容.关于linux上使用gcc基础编译, ...
- Linux动态库的编译与使用 转载【转】
转自:http://www.cnblogs.com/leaven/archive/2010/06/11/1756294.html http://hi.baidu.com/linuxlife/blog/ ...
- 【转载】Linux动态库搜索路径的技巧
转自:http://soft.chinabyte.com/os/232/11488732_2.shtml 众所周知,Linux动 态库的默认搜索路径是/lib和/usr/lib.动态库被创建后,一般都 ...
- linux动态库加载RPATH, RUNPATH
摘自http://gotowqj.iteye.com/blog/1926771 linux动态库加载RPATH, RUNPATH 链接动态库 如何程序在连接时使用了共享库,就必须在运行的时候能够找到共 ...
- Linux动态库(.so)搜索路径
主要内容: 1.Linux动态库.so搜索路径 编译目标代码时指定的动态库搜索路径: 环境变量LD_LIBRARY_PATH指定的动态库搜索路径: 配置文件/etc/ld.so.conf中指定的动态库 ...
- Linux动态库的导出控制
在实际工作中,许多软件模块是以动态库的方式提供的.做为模块开发人员,我们不仅要掌握如何编写和构建动态库,还要了解如何控制动态库的导出接口,这样,我们可以向模块的用户仅导出必要的接口,而另一些内部接口, ...
- Linux动态库搜索路径的技巧
众所周知,Linux动态库的默认搜索路径是/lib和/usr/lib.动态库被创建后,一般都复制到这两个目录中.当程序执行时需要某动态库,并且该动态库还未加载到内存中,则系统会自动到这两个默认搜索路径 ...
- .netcore在linux下使用P/invoke方式调用linux动态库
http://www.mamicode.com/info-detail-2358309.html .netcore下已经实现了通过p/invoke方式调用linux的动态链接库(*.so)文件 1 ...
随机推荐
- jmeter同步定时器
同步定时器是jmeter中一个比较重要的定时器,同步定时器,相当于一个储蓄池,累积一定的请求,当在规定的时间内达到一定的线程数量,这些线程会在同一个时间点一起并发,可以用来做大数据量的并发请求. 验证 ...
- HTTP摘要认证原理以及HttpClient4.3实现
基本认证便捷灵活,但极不安全.用户名和密码都是以明文形式传送的,也没有采取任何措施防止对报文的篡改.安全使用基本认证的唯一方式就是将其与 SSL 配合使用. 摘要认证是另一种HTTP认证协议,它试图修 ...
- Linux环境安装.NET运行环境
Linux环境安装.NET运行环境 Linux环境安装.NET运行环境 1. 构建编译环境: (1) sudo apt-get install build-essential (2) sudo apt ...
- 常州day5
Task 1 小 W 和小 M 一起玩拼图游戏啦~ 小 M 给小 M 一张 N 个点的图,有 M 条可选无向边,每条边有一个甜蜜值,小 W 要选 K条边,使得任意两点间最多有一条路径,并且选择的 K条 ...
- BZOJ3530:[SDOI2014]数数——题解
https://www.lydsy.com/JudgeOnline/problem.php?id=3530 我们称一个正整数N是幸运数,当且仅当它的十进制表示中不包含数字串集合S中任意一个元素作为其子 ...
- 洛谷 P1278 单词游戏 【状压dp】
题目描述 Io和Ao在玩一个单词游戏. 他们轮流说出一个仅包含元音字母的单词,并且后一个单词的第一个字母必须与前一个单词的最后一个字母一致. 游戏可以从任何一个单词开始. 任何单词禁止说两遍,游戏中只 ...
- hadoop(三)HDFS基础使用
一.HDFS前言 1. 设计思想 分而治之:将大文件,大批量文件,分布式的存放于大量服务器上.以便于采取分而治之的方式对海量数据进行运算分析 2. 在大数据系统架构中的应用 ...
- 【loj6179】Pyh的求和
Portal -->loj6179 Solution 这题其实有一个式子一喵一样的版本在bzoj,但是那题是\(m\)特别大然后只有一组数据 这题多组数据== 首先根据\(\v ...
- 编写优质嵌入式C程序(转)
前言:这是一年前我为公司内部写的一个文档,旨在向年轻的嵌入式软件工程师们介绍如何在裸机环境下编写优质嵌入式C程序.感觉是有一定的参考价值,所以拿出来分享,抛砖引玉. 转载请注明出处:http://bl ...
- PID控制算法的c语言实现十二 模糊PID的参数整定
这几天一直在考虑如何能够把这一节的内容说清楚,对于PID而言应用并没有多大难度,按照基本的算法设计思路和成熟的参数整定方法,就算是没有经过特殊训练和培训的人,也能够在较短的时间内容学会使用PID算法. ...