自:http://www.ibm.com/developerworks/cn/linux/sdk/dll/index.html

在仅仅只会编写插件的时候为什么要编写整个应用程序?

插件和 DLL 通常是用来无须编写整个新应用程序而添加功能的极好方法。在 Linux 中,插件和 DLL 是以动态库形式实现的。电子商务顾问兼设计师 Allen Wilson 介绍了动态库,并且向您演示了如何在某一个应用程序正在运行之后使用动态库来更改该应用程序。

Allen Wilson (wilsona@us.ibm.com), 电子商务设计师, IBM  2001 年 10 月 01 日

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<stdio.h>
#include <stdlib.h> /* */
/* 1-dll include file and variables */
/* */
#include <dlfcn.h>
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);
}

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

  1. 为 Linux 应用程序编写 DLL

    插件和 DLL 通常是用来无须编写整个新应用程序而添加功能的极好方法. 在 Linux 中,插件和 DLL 是以动态库形式实现的. 电子商务顾问兼设计师 Allen Wilson 介绍了动态库,并且向 ...

  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. facebook darkforest围棋ai测试

    0.darkforest说明 darkforest是facebook田渊栋博士开发的深度学习围棋程序,2016的uec cup得了第二名(http://jsb.cs.uec.ac.jp/~igo/en ...

  2. Linux系统中Xampp+Bugfree安装

    一.XAMPP下载安装: 1.https://www.apachefriends.org/download.html下载安装包, 因为我们是要安装到linux下面,所以下载xampp的linux版本, ...

  3. DBA-mysql-表

    create table  student( id int(4) not null, name char(20) not null, age int(3) not null default '0', ...

  4. 【转】关于启用 HTTPS 的一些经验分享

    随着国内网络环境的持续恶化,各种篡改和劫持层出不穷,越来越多的网站选择了全站 HTTPS.HTTPS 通过 TLS 层和证书机制提供了内容加密.身份认证和数据完整性三大功能,可以有效防止数据被查看或篡 ...

  5. geohash基本原理

    geohash基本原理是将地球理解为一个二维平面,将平面递归分解成更小的子块,每个子块在一定经纬度范围内拥有相同的编码,这种方式简单粗暴,可以满足对小规模的数据进行经纬度的检索 目录: 经纬度常识 认 ...

  6. IOS开发UI基础学习-------总结

    什么叫控件? 屏幕上所有UI元素都叫做控件(也有叫做视图.组件) 控件的共同属性 尺寸 位置 背景色 ... 苹果将控件的共同属性都抽取到父类UIView中 所有控件最终都继承自UIView 父子控件 ...

  7. 要用于尝试,广东移动间接实现“流量不清零”[bubuko.com]

    拥有1亿用户的广东移动在推出流量共享后,推出4G套餐外流量的“自动升档”服务,每月根据客户消费情况动态自动匹配当月最恰当的一档流量资费.未来,还将推出“流量转赠”服务,用不完的流量可转赠给其他用户. ...

  8. Building good docker images

    The docker registry is bursting at the seams. At the time of this writing, a search for "node&q ...

  9. 在ESXi 5.x 和 ESXi 6.0.x 中如何安装第三方供应商开发的驱动程序

    在 VMware ESXi 5.x 和 ESXi 6.0.x 中如何下载并安装异步驱动程序 (2076262)   Symptoms 免责声明:本文为 How to download and inst ...

  10. Mysql大范围分页优化案例

    在BBS线上业务抓到如下分页SQL: meizu_bbs meizu_bbs Query Sending data , meizu_bbs meizu_bbs Query Sending data , ...