插件和 DLL 通常是用来无须编写整个新应用程序而添加功能的极好方法。

在 Linux 中,插件和 DLL 是以动态库形式实现的。

电子商务顾问兼设计师 Allen Wilson 介绍了动态库,并且向您演示了如何在某一个应用程序正在运行之后使用动态库来更改该应用程序。

Internet 浏览器用户非常熟悉插件的概念。从 Web 上下载插件,通常这些插件为浏览器的音频、视频以及特殊效果提供增强支持。一般来讲,在不更改原有应用程序的情况下,插件为现有应用程序提供新功能。

DLL 是程序函数,它们在设计和构建应用程序时为该程序所知。设计应用程序的主程序时使用程序框架或底板,这些程序框架或底板在运行时选择性地装入所需的 dll,这些 dll 位于磁盘上同主程序分离的一些文件中。这一打包和动态装入提供了灵活的升级、维护、以及许可策略。

随 Linux 一起交付的还有几千条命令和应用程序,它们至少都需要 libc 库函数。如果 libc 函数与每一个应用程序都打包在一起,那么磁盘上将会出现几千个相同函数的副本。Linux 构建这些应用程序,以使用通常所需的系统库的单个系统级副本,而不浪费磁盘空间。Linux 甚至做得更好,每个需要公共系统库函数的进程使用单个的系统级内的副本,一次性将该副本装入到内存并为各进程所共享。

在 Linux 中,插件和 dll 以动态库形式实现。本文的余下部分是在应用程序运行之后使用动态库更改该应用程序的示例。

Linux 动态链接

Linux 中的应用程序以以下两种方式之一链接到外部函数:要么在构建时与静态库( lib*.a ) 静态地链接,并且将库代码包含在该应用程序的可执行文件里;要么在运行时与共享库( lib*.so ) 动态地链接。通过动态链接装入器,将动态库映射进应用程序的可执行内存中。在启动应用程序之前,动态链接装入器将所需的共享目标库映射到应用程序的内存,或者使用系统共享的目标并为应用程序解析所需的外部引用。现在应用程序就可以运行了。

作为示例,下面有一个演示 Linux 中对动态链接库的缺省使用的小程序:

main()
{
printf("Hello world
");
}

当使用 gcc 编译 hello.c 时,就创建了一个名为 a.out 的可执行文件。通过使用 Linux 命令 ldd a.out (该命令打印出共享库的相互依赖性),可以看出所需的共享库是:

        libc.so.6 => /lib/libc.so.6 (0x4001d000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

使用相同的动态链接装入器在应用程序运行之后将 dll 映射进应用程序的内存。通过使用 Linux 动态装入器例程,应用程序控制装入哪一个动态库以及调用库中的哪一个函数,以执行装入和链接以及返回所需入口点的地址。

Linux dll 函数

Linux 提供 4 个库函数( dlopen , dlerror , dlsym 和 dlclose ),一个 include 文件( dlfcn.h )以及两个共享库(静态库 libdl.a 和动态库 libdl.so ),以支持动态链接装入器。这些库函数是:

  • dlopen 将共享目标文件打开并且映射到内存中,并且返回句柄
  • dlsym返回一个指向被请求入口点的指针
  • dlerror 返回 NULL 或者一个指向描述最近错误的 ASCII 字符串的指针
  • dlclose关闭句柄并且取消共享目标文件的映射

动态链接装入器例程 dlopen 需要在文件系统中查找共享目标文件以打开文件并创建句柄。有 4 种方式用以指定文件的位置:

  • dlopen call 中的绝对文件路径
  • 在 LD_LIBRARY_PATH 环境变量中指定的目录中
  • 在 /etc/ld.so.cache 中指定的库列表之中
  • 先在 /usr/lib 之中,然后在 /lib 之中

dll 示例:小的 C 程序和 dlTest

动态链接装入器示例程序是一个小的 C 程序,该程序被设计用来练习 dl 例程。该程序基于每个人都编写过的一个 C 程序,它将“Hello World”打印到控制台上。最初打印的消息是“HeLlO WoRlD”。该测试程序链接到再次打印该消息的两个函数上:第一次都用大写字符,第二次都用小写字符。

以下是该程序的概要:

  1. 定义 dll include 文件 dlfcn.h 和所需的变量。至少需要这些变量:

    • 到共享库文件的句柄
    • 指向被映射函数入口点的指针
    • 指向错误字符串的指针
  2. 打印初始消息,“HeLlO WoRlD”。
  3. 使用绝对路径“/home/dlTest/UPPERCASE.so”和选项 RTLD_LAZY, dlopen 打开 UPPERCASE dll 的共享目标文件并返回句柄。
    • 选项 RTLD_LAZY 推迟解析 dll 的外部引用,直到 dll 被执行。
    • 选项 RTLD_NOW 在 dlopen 返回之前解析所有的外部引用。
  4. dlsym 返回入口点 printUPPERCASE 的地址。
  5. 调用 printUPPERCASE 并且打印修改过的消息“HELLO WORLD”。
  6. dlclose 关闭到 UPPERCASE.so 的句柄,并且从内存中取消 dll 映射。
  7. dlopen 使用基于环境变量 LD_LIBRARY_PATH 的相对路径查找共享目标路径,来打开 lowercase dll 的共享目标文件 lowercase.so,并且返回句柄。
  8. dlsym 返回入口点 printLowercase 的地址。
  9. 调用 printLowercase 并且打印修改过的信息“hello world”。
  10. dlclose 关闭到 lowercase.so 的句柄,并且从内存中取消 dll 映射。

注意,每次调用 dlopen 、 dlsym 或 dlclose 之后,调用 dlerror 以获取最后的错误信息,并且打印该错误信息字符串。以下是 dlTest 的测试运行:

   dlTest  2-Original message
HeLlO WoRlD
dlTest 3-Open Library with absolute path return-(null)-
dlTest 4-Find symbol printUPPERCASE return-(null)-
HELLO WORLD
dlTest 5-printUPPERCASE return-(null)-
dlTest 6-Close handle return-(null)-
dlTest 7-Open Library with relative path return-(null)-
dlTest 8-Find symbol printLowercase return-(null)-
hello world
dlTest 9-printLowercase return-(null)-
dlTest 10-Close handle return-(null)-

完整的 dlTest.c、UPPERCASE.c 和 lowercase.c 源代码清单在本文后面的 清单里。

构建 dlTest

启用运行时动态链接需要三步:

  1. 将 dll 编译为位置无关代码
  2. 创建 dll 共享目标文件
  3. 编译主程序并同 dl 库相链接

编译 UPPERCASE.c 和 lowercase.c 的 gcc 命令包含 -fpic 选项。选项 -fpic 和 -fPIC 导致生成的代码是位置无关的,重建共享目标库需要位置无关。-fPIC 选项产生位置无关的代码,这类代码支持大偏移。用于 UPPERCASE.o 和 lowercase.o 的第二个 gcc 命令,带有 -shared 选项,该选项产生适合于动态链接的共享目标文件 a*.so。

用于编译和执行 dltest 的 ksh 脚本如下:

#!/bin/ksh
# Build shared library
#
#set -x
clear #
# Shared library for dlopen absolute path test
#
if [ -f UPPERCASE.o ]; then rm UPPERCASE.o
fi
gcc -c -fpic UPPERCASE.c
if [ -f UPPERCASE.so ]; then rm UPPERCASE.so
fi
gcc -shared -lc -o UPPERCASE.so UPPERCASE.o #
# Shared library for dlopen relative path test
#
export LD_LIBRARY_PATH=`pwd`
if [ -f lowercase.o ]; then rm lowercase.o
fi
gcc -c -fpic lowercase.c
if [ -f lowercase.so ]; then rm lowercase.so
fi
gcc -shared -lc -o lowercase.so lowercase.o #
# Rebuild test program
#
if [ -f dlTest ]; then rm dlTest
fi
gcc -o dlTest dlTest.c -ldl
echo Current LD_LIBRARY_PATH=$LD_LIBRARY_PATH
dlTest

结束语

创建能在运行时被动态链接到 Linux 系统上的应用程序的共享目标代码是一项非常简单的练习。应用程序通过使用对动态链接装入器的 dlopen、dlsym 和 dlclose 函数调用来获取对共享目标文件的访问。dlerror 以字符串的形式返回任何错误,这些错误信息字符串描述 dl 函数碰到的最后一个错误。在运行时,主应用程序使用绝对路径或相对于 LD_LIBRARY_PATH 的相对路径找到共享目标库,并且请求所需的 dll 入口点的地址。当需要时,也可对 dll 进行间接函数调用,最后,关闭到共享目标文件的句柄,并且从内存中取消该目标文件映射,使之不可用。

使用附加选项 -fpic 或 -fPIC 编译共享目标代码,以产生位置无关的代码,使用 -shared 选项将目标代码放进共享目标库中。

Linux 中的共享目标代码库和动态链接装入器向应用程序提供了额外的功能。减少了磁盘上和内存里的可执行文件的大小。可以在需要时,装入可选的应用程序功能,可以在无须重新构建整个应用程序的情况下修正缺陷,并且应用程序可以包含第三方的插件。

清单(应用程序和 dll)

dlTest.c:

/*************************************************************/
/* Test Linux Dynamic Function Loading */
/* */
/* void *dlopen(const char *filename, int flag) */
/* Opens dynamic library and return handle */
/* */
/* const char *dlerror(void) */
/* Returns string describing the last error. */
/* */
/* void *dlsym(void *handle, char *symbol) */
/* Return pointer to symbol's load point. */
/* If symbol is undefined, NULL is returned. */
/* */
/* int dlclose (void *handle) */
/* Close the dynamic library handle. */
/* */
/* */
/* */
/*************************************************************/
#include
#include /* */
/* 1-dll include file and variables */
/* */
#include
void *FunctionLib; /* Handle to shared lib file */
int (*Function)(); /* Pointer to loaded routine */
const char *dlError; /* Pointer to error string */ main( argc, argv )
{
int rc; /* return codes */
char HelloMessage[] = "HeLlO WoRlD\n"; /* */
/* 2-print the original message */
/* */
printf(" dlTest 2-Original message \n");
printf("%s", HelloMessage); /* */
/* 3-Open Dynamic Loadable Libary with absolute path */
/* */
FunctionLib = dlopen("/home/dlTest/UPPERCASE.so",RTLD_LAZY);
dlError = dlerror();
printf(" dlTest 3-Open Library with absolute path return-%s- \n", dlError);
if( dlError ) exit(1); /* */
/* 4-Find the first loaded function */
/* */
Function = dlsym( FunctionLib, "printUPPERCASE");
dlError = dlerror();
printf(" dlTest 4-Find symbol printUPPERCASE return-%s- \n", dlError);
if( dlError ) exit(1); /* */
/* 5-Execute the first loaded function */
/* */
rc = (*Function)( HelloMessage );
printf(" dlTest 5-printUPPERCASE return-%s- \n", dlError); /* */
/* 6-Close the shared library handle */
/* Note: after the dlclose, "printUPPERCASE" is not loaded */
/* */
rc = dlclose(FunctionLib);
dlError = dlerror();
printf(" dlTest 6-Close handle return-%s-\n",dlError);
if( rc ) exit(1); /* */
/* 7-Open Dynamic Loadable Libary using LD_LIBRARY path */
/* */
FunctionLib = dlopen("lowercase.so",RTLD_LAZY);
dlError = dlerror();
printf(" dlTest 7-Open Library with relative path return-%s- \n", dlError);
if( dlError ) exit(1); /* */
/* 8-Find the second loaded function */
/* */
Function = dlsym( FunctionLib, "printLowercase");
dlError = dlerror();
printf(" dlTest 8-Find symbol printLowercase return-%s- \n", dlError);
if( dlError ) exit(1); /* */
/* 8-execute the second loaded function */
/* */
rc = (*Function)( HelloMessage );
printf(" dlTest 9-printLowercase return-%s- \n", dlError); /* */
/* 10-Close the shared library handle */
/* */
rc = dlclose(FunctionLib);
dlError = dlerror();
printf(" dlTest 10-Close handle return-%s-\n",dlError);
if( rc ) exit(1); return(0); }

UPPERCASE.c:

/************************************************/
/* Function to print input string as UPPER case. */
/* Returns 1. */
/*********************************************** */ int printUPPERCASE ( inLine )
char inLine[];
{
char UPstring[256];
char *inptr, *outptr; inptr = inLine;
outptr = UPstring;
while ( *inptr != '\0' )
*outptr++ = toupper(*inptr++);
*outptr++ = '\0';
printf(UPstring);
return(1);
}

lowercase.c

/********************************************/
/* Function to print input string as lower case. */
/* Returns 2. */
/******************************************* */
int printLowercase( inLine )
char inLine[];
{
char lowstring[256];
char *inptr, *outptr;
inptr = inLine;
outptr = lowstring;
while ( *inptr != '' )
*outptr++ = tolower(*inptr++);
*outptr++ = '';
printf(lowstring);
return(2);
}
 refs:

http://blog.chinaunix.net/uid-20453876-id-1944884.html

https://wenku.baidu.com/view/6c5de9c55fbfc77da269b1b0.html

为 Linux 应用程序编写 DLL的更多相关文章

  1. 为 Linux 应用程序编写 DLL[转]

    自:http://www.ibm.com/developerworks/cn/linux/sdk/dll/index.html 在仅仅只会编写插件的时候为什么要编写整个应用程序? 插件和 DLL 通常 ...

  2. 编写Linux C++程序如何影响VIRT(虚存)和RES(实存/常驻内存)

    转载目的,主要是为了理解lVIRT虚拟内存.RES常驻内存.共享内存SHR.SWAP和实际程序应用如何对应的. 在Linux命令行中执行top命令,可以查询到所有进程使用的VIRT虚拟内存.RES常驻 ...

  3. Linux下的 sniff-andthen-spoof程序编写

    Linux下的 sniff-andthen-spoof程序编写 一.任务描述 在本任务中,您将结合嗅探和欺骗技术来实现以下嗅探然后欺骗程序.你需要两台机器在同一个局域网.从机器A ping IP_X, ...

  4. 基于DCMTK的DICOM相关程序编写攻略

    2008年09月10日 星期三 15:35 基于DCMTK的DICOM相关程序编写攻略 前言: 由于现在的医学影像设备的图像存储和传输正在逐渐向DICOM标准靠拢,在我们进行医学图像处理的过程中,经常 ...

  5. 嵌入式linux应用程序移植方法总结

    嵌入式linux应用程序移植方法总结 前段时间一直在做openCapwap的移植和调试工作,现在工作已接近尾声,编写本文档对前段工作进行一个总结,分享下openCapwap移植过程中的经验和感悟.江浩 ...

  6. 第六章第一个linux个程序:统计单词个数

    第六章第一个linux个程序:统计单词个数 从本章就开始激动人心的时刻——实战,去慢慢揭开linux神秘的面纱.本章的实例是统计一片文章或者一段文字中的单词个数.  第 1 步:建立 Linu x 驱 ...

  7. C++编写DLL的方法

    http://files.cnblogs.com/files/profession/DllTest.zip 在写C++程序时,时常需要将一个class写成DLL,供客户端程序调用.这样的DLL可以导出 ...

  8. QT编写DLL给外部程序调用,提供VC/C#/C调用示例(含事件)

    最近这阵子,接了个私活,封装一个开发包俗称的SDK给客户调用,查阅了很多人家的SDK,绝大部分用VC编写,而且VC6.0居多,估计也是为了兼容大量的XP用户及IE浏览器,XP自带了VC6.0运行库,所 ...

  9. 配置开发支持高并发TCP连接的Linux应用程序全攻略

    http://blog.chinaunix.net/uid-20733992-id-3447120.html http://blog.chinaunix.net/space.php?uid=16480 ...

随机推荐

  1. jQyery简史和下载引用方法

    1.jQuery简介 jQuery是一个快速,小型且功能丰富的JavaScript库.借助易于使用的API(可在多种浏览器中使用),使HTML文档的遍历和操作,事件处理,动画和Ajax等事情变得更加简 ...

  2. Scrapy的中间件(二)

    爬虫中间件 爬虫中间件的用法与下载器中间件非常相似,只是它们的作用对象不同.下载器中间件的作用对象是请求request和返回response:爬虫中间件的作用对象是爬虫,更具体地来说,就是写在spid ...

  3. 从rpm包提取rpm spec 的几种方法

    包含了源码包 先安装,然后在rpmbuild 目录直接可以查看文件 不用安装 ,使用rpm2cpio rpm2cpio myrpm.src.rpm | cpio -civ '*.spec' 没有源码 ...

  4. 无旋treap大法好

    无旋Treap大法好 原理? 是一棵二叉查找树: 一个节点左子树权值都比他小,右子树权值都比他大 所以可以维护序列(以位置为权值),或数值(以数值为权值) 是一个堆: 每个节点除了上述提到的权值外,还 ...

  5. 讲题专用——线段树——优化DP

    题目链接:http://codevs.cn/problem/3342/ 题解: 最小化最大值:二分 二分最长空题段 令f[i]表示抄第i道题所花费的最小时间 状态转移方程:f[i]=min(f[j]) ...

  6. app内嵌h5分享到小程序分享功能

    if (this.GLOBAL.env !== 'production') { try { window.JSBridge.shareMiniProgramToWx('https://www.lexi ...

  7. git bash 乱码问题之解决方案

    解决办法:右击左上方git标识,然后进入到如图中,点击Text,进行操作. 操作完毕后,关闭git bash,然后再重新打开,执行ls或ll命令,查看对应的以中文作为目录或文件名是否显示乱码,如果之前 ...

  8. javascript系列--认识并理解构造函数,原型和原型链

    一.前言 介绍构造函数,原型,原型链.比如说经常会被问道:symbol是不是构造函数:constructor属性是否只读:prototype.[[Prototype]]和__proto__的区别:什么 ...

  9. 【医学】三分钟看懂乳腺BI-RADS分级

    “BI-RADS”是指美国放射学会的乳腺影像报告和数据系统(Breast Imaging Reporting and Data System)的缩写.BI-RADS分级法将乳腺病变分为0-6级,用来评 ...

  10. 一道经典的Java面试题:equals ,== 和hashcode()的区别

    一句话区别:==比较的是内存地址,equals比较的是值内容 结论: 从以下三点展开: 基本数据类型.字符串.对象 对于基本数据类型:只有==,没有equals. 对于字符串:==比较的是内存地址,e ...