为 Linux 应用程序编写 DLL


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

Allen Wilson ( [email protected]), 电子商务设计师, IBM
Allen Wilson 在位于美国堪萨斯州奥斯汀的 IBM 任电子商务设计师兼顾问。他的专长领域包括 Linux、AIX、WebSphere Application Server 和应用程序托管。您可以通过  [email protected] 同他联系。

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

平均分 3 星 共 9 个评分 平均分 (9个评分)
为本文评分

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);
}


参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文

  • 下载 IBM developer kit for Linux


  • 同样在 developerWorks上,请阅读相关文章 "shared objects for the object disoriented!"


  • 在 developerWorks上浏览 更多 Linux 参考资料。 


  • 在 developerWorks上浏览 更多开放源码参考资料。 

关于作者

Allen Wilson 在位于美国堪萨斯州奥斯汀的 IBM 任电子商务设计师兼顾问。他的专长领域包括 Linux、AIX、WebSphere Application Server 和应用程序托管。您可以通过 [email protected] 同他联系。

 

 

 

dlopen

基本定义

  功能:打开一个动态链接库 
  包含头文件: 
  #include <dlfcn.h> 
  函数定义: 
  void * dlopen( const char * pathname, int mode ); 
  函数描述: 
  在dlopen的()函数以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程。使用dlclose()来卸载打开的库。 
  mode:分为这两种 
  RTLD_LAZY 暂缓决定,等有需要时再解出符号 
  RTLD_NOW 立即决定,返回前解除所有未决定的符号。 
  RTLD_LOCAL 
  RTLD_GLOBAL 允许导出符号 
  RTLD_GROUP 
  RTLD_WORLD 
  返回值: 
  打开错误返回NULL 
  成功,返回库引用 
  编译时候要加入 -ldl (指定dl库) 
  例如 
  gcc test.c -o test -ldl
编辑本段
使用 dlopen
  dlopen()是一个强大的库函数。该函数将打开一个新库,并把它装入内存。该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的。比如 Apache Web 服务器利用这个函数在运行过程中加载模块,这为它提供了额外的能力。一个配置文件控制了加载模块的过程。这种机制使得在系统中添加或者删除一个模块时,都不需要重新编译了。 
  可以在自己的程序中使用 dlopen()。dlopen() 在 dlfcn.h 中定义,并在 dl 库中实现。它需要两个参数:一个文件名和一个标志。文件名可以是我们学习过的库中的 soname。标志指明是否立刻计算库的依赖性。如果设置为 RTLD_NOW 的话,则立刻计算;如果设置的是 RTLD_LAZY,则在需要的时候才计算。另外,可以指定 RTLD_GLOBAL,它使得那些在以后才加载的库可以获得其中的符号。 

  当库被装入后,可以把 dlopen() 返回的句柄作为给 dlsym() 的第一个参数,以获得符号在库中的地址。使用这个地址,就可以获得库中特定函数的指针,并且调用装载库中的相应函数。

--------------------------------------------------------------------------------------------------------------------------

dlsym
   
  dlsym()的函数原型是 
  void* dlsym(void* handle,const char* symbol) 
  该函数在<dlfcn.h>文件中。 
  handle是由dlopen打开动态链接库后返回的指针,symbol就是要求获取的函数的名称,函数返回值是void*,指向函数的地址,供调用使用

取动态对象地址:
#include <dlfcn.h>
void *dlsym(void *pHandle, char *symbol);
dlsym根据动态链接库操作句柄(pHandle)与符号(symbol),返回符号对应的地址。
使用这个函数不但可以获取函数地址,也可以获取变量地址。比如,假设在so中
定义了一个void mytest()函数,那在使用so时先声明一个函数指针:
void (*pMytest)(),然后使用dlsym函数将函数指针pMytest指向mytest函数,
pMytest = (void (*)())dlsym(pHandle, "mytest");


--------------------------------------------------------------------------------------------------------------------------

dlclose
  dlclose() 
  包含头文件: 
  #include <dlfcn.h> 
  函数原型为: 
  int dlclose (void *handle); 
  函数描述: 
   dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。

--------------------------------------------------------------------------------------------------------------------------

dlerror
  dlerror() 
  包含头文件: 
  #include <dlfcn.h> 
  函数原型: 
  const char *dlerror(void); 
  函数描述: 
  当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。


LINUX创建与使用动态链接库并不是一件难事。
  编译函数源程序时选用-shared选项即可创建动态链接库,注意应以.so后缀命名,最好放到公用库目录(如/lib,/usr/lib等)下面,并要写好用户接口文件,以便其它用户共享。
  使用动态链接库,源程序中要包含dlfcn.h头文件,写程序时注意dlopen等函数的正确调用,编译时要采用-rdynamic选项与-ldl选项 ,以产生可调用动态链接库的执行代码。

[cpp]  view plain copy
  1. EXAMPLE  
  2. Load the math library, and print the cosine of 2.0: #include <stdio.h>  
  3. #include <dlfcn.h>   
  4.   
  5. int main(int argc, char **argv) {  
  6.     void *handle;  
  7.     double (*cosine)(double);  
  8.     char *error;  
  9.   
  10.     handle = dlopen ("libm.so", RTLD_LAZY);  
  11.     if (!handle) {  
  12.         fprintf (stderr, "%s\n", dlerror());  
  13.         exit(1);  
  14.     }  
  15.   
  16.     cosine = dlsym(handle, "cos");  
  17.     if ((error = dlerror()) != NULL)  {  
  18.         fprintf (stderr, "%s\n", error);  
  19.         exit(1);  
  20.     }  
  21.   
  22.     printf ("%f\n", (*cosine)(2.0));  
  23.     dlclose(handle);  
  24.     return 0;  
  25. }  
  26. #include <stdio.h>   
  27. #include <dlfcn.h>   
  28.   
  29. int main(int argc, char **argv) {  
  30.     void *handle;  
  31.     double (*cosine)(double);  
  32.     char *error;  
  33.   
  34.     handle = dlopen ("libm.so", RTLD_LAZY);  
  35.     if (!handle) {  
  36.         fprintf (stderr, "%s\n", dlerror());  
  37.         exit(1);  
  38.     }  
  39.   
  40.     cosine = dlsym(handle, "cos");  
  41.     if ((error = dlerror()) != NULL)  {  
  42.         fprintf (stderr, "%s\n", error);  
  43.         exit(1);  
  44.     }  
  45.   
  46.     printf ("%f\n", (*cosine)(2.0));  
  47.     dlclose(handle);  
  48.     return 0;  
  49. }  
  50.   
  51.  If this program were in a file named "foo.c", you would build the program with the following command:   
  52.   
  53.  gcc -rdynamic -o foo foo.c -ldl  
[cpp]  view plain copy
  1. EXAMPLE  
  2. Load the math library, and print the cosine of 2.0: #include <stdio.h>  
  3. #include <dlfcn.h>  
  4.   
  5. int main(int argc, char **argv) {  
  6.     void *handle;  
  7.     double (*cosine)(double);  
  8.     char *error;  
  9.   
  10.     handle = dlopen ("libm.so", RTLD_LAZY);  
  11.     if (!handle) {  
  12.         fprintf (stderr, "%s\n", dlerror());  
  13.         exit(1);  
  14.     }  
  15.   
  16.     cosine = dlsym(handle, "cos");  
  17.     if ((error = dlerror()) != NULL)  {  
  18.         fprintf (stderr, "%s\n", error);  
  19.         exit(1);  
  20.     }  
  21.   
  22.     printf ("%f\n", (*cosine)(2.0));  
  23.     dlclose(handle);  
  24.     return 0;  
  25. }  
  26. #include <stdio.h>  
  27. #include <dlfcn.h>  
  28.   
  29. int main(int argc, char **argv) {  
  30.     void *handle;  
  31.     double (*cosine)(double);  
  32.     char *error;  
  33.   
  34.     handle = dlopen ("libm.so", RTLD_LAZY);  
  35.     if (!handle) {  
  36.         fprintf (stderr, "%s\n", dlerror());  
  37.         exit(1);  
  38.     }  
  39.   
  40.     cosine = dlsym(handle, "cos");  
  41.     if ((error = dlerror()) != NULL)  {  
  42.         fprintf (stderr, "%s\n", error);  
  43.         exit(1);  
  44.     }  
  45.   
  46.     printf ("%f\n", (*cosine)(2.0));  
  47.     dlclose(handle);  
  48.     return 0;  
  49. }  
  50.   
  51.  If this program were in a file named "foo.c", you would build the program with the following command:   
  52.   
  53.  gcc -rdynamic -o foo foo.c -ldl  

你可能感兴趣的:(为 Linux 应用程序编写 DLL)