Dll的分析与编写(一)

原本就对DLL半知半解的,花了两天时间学习了一下DLL有关的知识,下面是我自己的理解:

 

1、LIB与DLL文件的区别

2、静态编译和动态链接的23事...

3、*.h、*.lib/*.a、*.dll 之间的关系

4、为无LIB的DLL制作LIB函数符号输入库  

5、调用dll文件     <这里分C版接口和C++版接口,要弄清概念>

6、DEV-C++编写dll文件的几个知识点


1、DLL是一个完整的程序,中文名称为“动态链接库”,DLL中包含的主要有三块内容:1.全部变量 2.函数接口 3.资源。我们这里说的DLL中包含许多的函数接口,以便供我们来调用,实现我们的软件功能。DLL中有一个函数导出表,其中每一项都是一个函数名称,我们可以通过一定的方式连接这些函数接口,来调用这些函数的功能。DLL中的代码在程序主动调用的时候才会被调入内存(DLL没有自己的内存,它会被分配到调用程序的内存区域中)。

             LIB被称为导入库文件,其中存放的内容根据动态和静态的区别会有两种不同的内容:

            1、  在静态库情况下,函数和数据被编译成一个二进制文件(*.LIB),编译器在处理程序代码时,将从静态库中恢复这些函数和数据,并把他们和应用程序中的其他模块组合在一起生成可执行文件,(通俗的说就是,*.LIB文件会被整个编译进可执行文件中),因此发布的时候,就无需发布整个静态库文件,应用程序只有一个单独的exe文件比较干净,但是exe文件可能要大很多。

            2 、   在动态库情况下,有连个文件,一个因入库(*.LIB)一个是DLL文件,此时LIB文件中包含的就不是实际的函数和数据了,而是被DLL文件中导出表中所有导出函数的名称和位置,因此整个LIB文件会很小,相比静态库的LIB文件(这个小的LIB文件在编译的时候还是会被编译进程序体内,因此这种情况发布的程序,也不用发布LIB文件,只需要DLL文件),DLL文件中才真正包含函数和数据,此时DLL文件中的函数和数据并不复制到程序中,程序中存放的则是DLL中所要调用的函数的内存地址,而不是被调用的函数代码。


2、其实在第一部分我已经将这块内容涉及到一些了,这里我通俗的说吧。

       所谓静态编译就是,把你所需要的调用所有外部函数和数据编译成一个LIB文件,然后再把这个文件整个的融合到应用程序中,则这个可执行程序可谓集各方武功捆绑于一身,只有一个exe文件(发布的时候仅仅只需要这个文件就可以完成所有功能),单相对会比较大。

       所谓动态编译就是,实现某些功能的代码分布在几个dll文件中,发布的时候你要将exe文件和这几个dll文件一起发布。Windows下的程序是最好的例子了,通常的程序只有一个单独的exe文件,其实很多实现某些功能的代码都在系统目录的几个dll文件中,需要的时候再去调用罢了。


3、这里我依然举例子来说明。假如现在我在做一个项目,我需要用到语音识别功能,我就去往某开发公司要,而他们为了盈利也只是给我了几个用来测试的文件,并且是已经编译好的文件,一种可能会给 my.h、my.lib 两个文件,一种可能会给 my.h、my.lib、my.dll  三个文件。具体的区别就是:第一种给两个文件的需要静态编译,也就是说my.lib会被融合到可执行程序中,发布的时候只有一个exe文件;第二种给三个文件的是需要动态链接,my.lib文件依然会和可执行程序融为一体,发布的时候需要将exe文件和dll文件一起发布。

        这里还要说明一下 *.lib 和 *.a 文件,这两个文件都是导入库文件,VC++只能识别 *.lib文件,而DEV-C++ 这两种文件都可以识别使用。


4、依然举例子来理解,现在假设语音开发公司,只给了我这样两个文件 my.h 与 my.dll ,因为没有 my.lib 文件,那样我在调用其语音功能时会相当痛苦。这里先说说,假如说有 my.lib 文件会发生什么,在my.dll 中有一个add(int a,int b) 用于将两个数相加结果返回,那么在调用文件中,我仅仅只需要引入 my.h 头文件,然后就可以直接调用add()函数得到结果;但是如果没有my.lib文件,将十分的痛苦,我需要调用Win32 API 来实现,需要调用LoadLibrary()与GetProcAddress(), 大体如下面这样调用(这种方式成为显示,另外的一种是常用的隐式):

 1  #include  < windows.h >
 2  #include  < stdio.h >
 4   
 5  typedef  int  ( * Fun)( int , int );      // 这里声明一个函数指针,typedef 关键字是必须的,好像要显示调用dll中的函数,都需要这样用函数指针给出声明
 6   
 7  int  main()
 8  {
 9      HINSTANCE hDll;
10      Fun Add;
11      hDll = LoadLibrary( " myDll.dll " );
12       if  (hDll == NULL)
13      {
14          printf( " %s " , " failed to load dll!\n " );
15      }
16       else
17      {
18          printf( " %s " , " succeeded in loading dll\n " );
19          Add = (Fun)GetProcAddress(hDll, " add " );
20           if  (Add != NULL)
21          {
22               int  i,j;
23              printf( " %s " , " input the first number: " );
24              scanf( " %d " , & i);
25              printf( " %s " , " input the first number: " );
26              scanf( " %d " , & j);
27              printf( " sum = %d\n " ,Add(i,j));
28          }
29      }
30      FreeLibrary(hDll);
31      
32      system( " pause " );
33       return   0 ;
34  }
  确实很痛苦吧,好了,罗嗦了这末多了,还没说假如说没有 *.lib 文件,那么如何从 dll 文件中导出来呢?这里我们需要两个工具,一个是微软提供的 dumpbin.exe(它依赖于另外两个文件:link.exe 与 mspdb60.dll ) 另一个是DEV-C++ 中的一个工具,叫 dlltool.exe。

dumpbin.exe可以从dll文件中导出一个 def  文件,注意这个文件只是个半成品,我们还需要手工改一下,后面会有讲解。

dlltool.exe 可以帮助我们用现有的 dll 和刚导出的 def 文件生成一个 .lib/.a 文件。

dumpbin的用法为:dumpbin   / exports  my.dll   >   my.def
dlltool的用法为:dlltool  
- D  my.dll   - d  my.def   - l  my.lib   // 生成 .lib 文件
         或者为:dlltool   - D  my.dll   - d  my.def   - l  my.a     //  生成 .a 文件
  经过这两步后就可以导出一个lib文件了。

接下来我们看一个用dumpbin.exe工具生成的def文件中的内容(def是一个文本文件):

Microsoft (R) COFF Binary File Dumper Version  6.00 . 8168
Copyright (C) Microsoft Corp 
1992 - 1998 . All rights reserved.
Dump of file myDll.dll
File Type: DLL
  Section contains the following exports 
for  myDll.dll
 
           
0  characteristics
    4C6D24A5 time date stamp Thu Aug 
19   20 : 33 : 41   2010
        
0.00  version
           
1  ordinal  base
           
2  number of functions
           
2  number of names
 
    ordinal hint RVA      name
          
2      0  000011D0 HelloWorld
          
1      1  0000120B add
  Summary
        
1000  .bss
        
1000  .data
        
1000  .edata
        
1000  .idata
        
1000  .rdata
        
1000  .reloc
        
1000  .text
 其中对我们有用的只有粉红色的那两行,我们将其他内容删除,下面来修改一下剩下的这两行,其中每行最后的字符串为函数名,每行最前面的数字具体我也说不清楚,反正是根据这个数据来识别函数的好像,我就叫他识别数吧。我们将函数名放在最前面,然后在后面加上一个 “@” 符号,再在最后缀上 识别数,形成如下形式:(在def文件中,分号为注释符号)
LIBRARY my                    ;这个关键字是必须的, 它告诉链接器(linker)如何命名你的DLL
EXPORTS                        ;这个关键字也是必须的, 这个部分使得该函数可以被其它应用程序访问到并且它创 建一个导入库
           HelloWorld @ 2
          add @ 1

 5、OK,我们现在已经有了 my.h、my.lib、my.dll 三个文件,(我们将所有文件拷贝到一个文件夹下,因为方便...)下面开始用程序来调用里面的功能函数,激动人心的时刻到了,O(∩_∩)O~

     1、首先我们需要用DEV-C++新建一个控制台程序(文件->新建->工程,选中其中的Console Application,然后选择右下角的C工程), 这里还有一点要十分注意,由于我们刚才选的是C工程,所以这里只能用C来写,用C++代码来写会报错的
     2、在程序中引入头文件(#include "my.h"  在我时间过程中,用C写的dll可以完全不用包含这个头文件,而用C++写的dll则要包含,不知道为什么,为了保险,我们都包含头文件),然后再为工程添加 导入库文件 my.lib ,点击菜单栏的中的“工程”菜单->工程属性->参数选项卡,点击右下角的“加入库或者对象”按钮,如图:
图片
     3、将 my.dll 拷贝到程序的输出目录中,我们的准备工作就可以了,直接就可以调用dll中的函数了,如:

 1  #include  < stdio.h >
 2  #include  < stdlib.h >
 3  #include  " my.h "
 4  int  main( int  argc,  char   * argv[])
 5  {
 6         printf( " 5 + 6 = %d " ,add( 5 , 6 ));       // 这里调用dll中的 add() 获得5和6的和,会成功的哦~~~
 7         system( " PAUSE " );      
 8          return   0 ;
 9  }

 上面是一个C版的dll文件,C++版的dll文件也没什么不一样的地方,唯一的区别就是选择C++接口后,编译器会生成一个类,仔细查看代码你会发现在类名的左侧有 DLLIMPORT 宏修饰,这里的意思是:这个类中所有的东东都将被导出。

        最重要的一点是,如果你在CPP代码中调用C接口的dll文件时候,在引入其头文件的时候,一定要加上 extern "C" {}    否则会链接出错下面是一个标准示例:

     extern "C"

    {

            #include "my.h"

    }

 6、最后呢,说一下,如何用DEV-C++编写dll文件。(这里以C接口为例,C++一样的)

      文件->新建->工程,选择“ DLL选项”,然后选右下角的“C工程”,确定后,编译器为我们生成了两个文件(dll.h、dllmain.c),我们看一下dll.h里面的代码:

 1  #ifndef _DLL_H_
 2  #define  _DLL_H_
 3   
 4  #if  BUILDING_DLL
 5  # define DLLIMPORT __declspec (dllexport)
 6  #else  /* Not BUILDING_DLL */
 7  # define DLLIMPORT __declspec (dllimport)
 8  #endif  /* Not BUILDING_DLL */
9   
10  DLLIMPORT  void  HelloWorld ( void );
11   
12  #endif  /* _DLL_H_ */

代码中的 __declspec(dllexport) 关键字从 DLL 导出数据、函数、类或类成员函数,这里为了代码的可读性,用了一个宏  DLLIMPORT 来代替,通俗的说用该宏修饰的数据、函数、类或类成员函数才能被外界调用;另外在dllmain.c中,对应函数的实现前也要加上 DLLIMPORT 宏修饰,必须的。

另外,看到网上一些人的dll头文件写的比较麻烦,比如这样:

extern   " C "  __declspec(dllexport)  int  maxfun( int , int );     // 函数

对应的实现文件这样写:

extern   " C "  __declspec(dllexport) int  maxfun( int  x, int  y)
{
    
return  x + y;
}

我觉得这样是相当麻烦的,都是为了程序的兼容性,我们可以用更好的方法,比如说:在头文件的用extern "C"关键字包括住全部的函数声明;在实现文件中,同样用该关键字包括住所有的函数定义,比如:

extern   " C "
{
        DLLIMPORT 
int  add( int  a, int  b);
        DLLIMPORT 
int  Fun( int  a);
        .... 
}

实现文件一样:

extern   " C "  
{
        DLLIMPORT 
int  add( int  a, int  b) { return  a + b;}
        DLLIMPORT 
int  Fun( int  a) { return  a * a * a;}
        .....
}

我觉得最简单的方法就是用我前面说的,如果在使用C版接口的dll文件时候,在引入其头文件的外面加上exter "C" 修饰最为方便。


另外在CSDN看到supconsupcon的一篇文章《VC环境下DLL接口申明的三种方式》,我觉得写得很棒,不转了,直接给出原文地址,以备参考:http://blog.csdn.net/supconsupcon/archive/2009/07/13/4345343.aspx

 

转载于:https://www.cnblogs.com/hicjiajia/archive/2010/08/27/1809997.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值