编译与链接

编译与链接
2011-04-07 11:32

      

        链接是指在程序的多个模块之间传递参数和控制命令,把它们整个成一个可执行的整体程序。链接之前要先进行编译。如果此前没有对单个的程序文件进行编译过,则在执行链接操作之前先自动进行编译。
        链接分为三种:静态链接、装入时动态链接和运行时动态链接

        C++中要求所有的变量、函数或者类在使用之前都要预先声明。为了方便使用,把一些常用的函数声明放在头文件中。别的文件需要使用这些函数时,使用include把头文件包含进来。文件包含命令在预编译阶段处理。处理方式是把头文件中的内容全部包含进来。所以,有可能造成变量/类重复编译的错误。可以使用条件编译命令,使每个类只编译一次

        一般头文件中只包含函数声明,函数具体的实现放在别的文件中。这样可以减少代码总量

        编译程序读取源程序,首先进行预处理,即将其中的宏定义替换,将头文件全部包含进来,使之成为一个没有宏定义指令、没有条件汇编、没有头文件的源文件。然后对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,最后经链接生成可执行程序
        编  译:把经过预编译生成的文件给编译成.obj文件,这个文件保存了机器码化的函数、函数的描述、全局变量的描述和段的描述等。编译是以单个文件为单位进行。
        链  接:把可执行程序需要的所有编译过程所产生的.obj文件组合在一起(这里也包括.lib文件。.lib文件实质上就是打包的.obj文件的集合)。另外链接过程还会组合一些其它数据

       链接器主要完成两个工作:符号解析与重定位
符号解析:一个模块可以使用在别的模块中定义过的函数或全局变量,编译器生成的符号表会标记出所有这样的函数或全局变量。链接器会在别的模块中查找它们的定义,如果没有找到或者找到的合适定义不惟一,符号解析无法正常完成
重定  位:编译器在生成目标文件时,通常都使用从零开始的相对地址。然而,在链接过程中,链接器将从一个指定的地址开始,根据输入的目标文件的顺序以段为单位将它们一个一个的拼接起来。除了目标文件的拼装以外,在重定位的过程中还完成两个任务:一是生成最终的符号表;二是对代码段中的某些位置进行修改,所有需要修改的位置都由编译器生成的重定位表指出

链接程序把所有.obj文件和.lib文件中的机器码组合在一起。静态地建立函数之间的链接,即在程序执行之前建立组成程序的源文件中所包含的函数链接
动态链接库中的函数链接是在程序调用函数时才建立的,在程序调用之前,该链接不存在

动态链接库的优点:一个库中的函数可以在几个并行执行的程序之间共享,这将节省相同函数占用的内存空间
另一个优点是动态链接库在调用其中的函数之前不会加载到内存中。也就是说,如果不使用给定动态链接库中的函数,该动态链接库就不会占用内存空间

静态链接库(.lib)实际上就是已经编译链接好的二进制代码库,集成了一些编程时常用的功能。程序编译时会把lib文件的代码添加到自己的程序中,因此会增加代码的大小
动态链接库是一种用来为其它可执行文件(包括EXE文件和其它DLL)提供共享的函数库,通常我们编写的应用程序中需要使用DLL的应用程序,可以调用DLL中的导出函数,在应用程序本身的执行代码中并不包含这些DLL中函数的执行代码,它们经过编译和链接之后,独立的保存在DLL中。使用DLL的应用程序只包括了用于从DLL中定位所引用的函数的信息,而没有函数具体的实现,要等到程序运行时才从DLL中获得函数的实现代码。

动态链接库实现了多个应用程序共享数据和代码的方式。由于多个应用程序共享同一个DLL中的函数,因而使用DLL可以显著节省磁盘空间。
其次,多个应用程序还可以同时共享动态链接库在内存中的同一份考备,这样就可以节省应用程序所占用的内存资源,减少了频繁的内存交换,提高了运行效率。

应用程序使用DLL有两种链接方式:一是隐式链接,二是显示链接
隐式链接:又称为静态加载。应用程序和DLL同时加载。
显示链接:又称为动态加载。应用程序必须在代码中用语句明确的加载所使用的DLL,并使用指针来调用DLL中的导出函数。在使用完毕之后,应用程序必须显示卸载所使用的DLL
同一个DLL可以被应用程序显示链接,也可以隐示链接,取决于具体情况。

 

       很多程序之间有相当多的重复代码,每写一个程序就重写一遍这些代码既浪费时间又没有必要。一般的做法是把这些重复的代码写成一个函数,然后按类别放到不同的库文件中。当要使用这些函数时,把目标文件和相应的库文件进行链接。在链接时链接器会从库文件中抽取相关的信息,并把它们插入到可执行文件中。这个过程叫做静态链接。这种链接方式有一个显著的缺点是每一个调用库函数的程序中都必须嵌入同一个函数的拷备。这显然是个浪费。如果在多任务系统下,多个任务同时运行,就要存在着相同函数的拷备,这是对内存的浪费。有鉴于此,提出了动态链接库的概念。

       动态链接库,dynamic link library,简写为DLL,是一个包含可由多个程序同时使用的代码和数据的库。DLL不是可执行文件,它提供了一种解决方案,可使进程调用不属于其可执行代码的函数。函数的可执行代码位于一个DLL中,该DLL包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL从表面上看和静态链接库一样,也是一大堆的通用函数,不过它在内存中始终只有一份拷备,即使有多个程序调用同一个库。而且DLL并不是把自己的可执行代码嵌入到应用程序中去。而是当程序已经在内存中运行,需要调用这些函数时,立即在内存中查找是否存在。如果不存在,立即加载。

       DLL在调用时,内存中的代码只有一份,但每个应用程序都要提供自己单独的数据段。

使用dll的好处有:
       可以实现代码集成封装

       有助于节省内存、实现资源共享、解决平台差异、实现应用程序本地化
       实现生成的应用程序以文件为载体实现模块化。在升级程序版本时,不用重新编译应用程序,只需要将相应的DLL文件进行替换
       可以实现跨语言调用。对于一些用C#作为主要开发语言的程序,需要使用C++进行接近硬件的底层操作时,可以使用DLL技术,实现语言的混合编程。C#具有开发高效的特点,C++具有运行高效性和对底层良好操作性的特点,DLL可以实现两种语言优点的结合

 

         动态库在编译链接完成之后会生成两个文件,一个是.dll文件,其中内容是函数的实现;另一个是.lib文件,包含本DLL导出文件的声明

         在VS中使用静态链接库时,在项目--配置属性--链接器--输入--附加依赖项中加入.lib文件。然后在文件中加入具体的头文件

         当要使用静态的程序库时,链接器会找出程序所需的函数,然后将它们拷贝到执行文件。由于这种拷贝是完整的,所以一旦链接成功,静态程序库也就不再需要了。
          对于动态库则不然。动态库会在执行程序内留下一个标记"指明当程序执行时,首先必须载入这个库"。然后在执行的时候才使用这个库中的函数。动态库里面的函数不是执行程序本身的一部分,而是根据执行需要按需载入,其执行代码可以在多个程序中共享。动态库在运行时被系统加载到调用进程的虚拟空间中,使用从调用进程的虚拟地址空间分配的内存,成为调用进程的一部分。DLL也只能被该进程的线程所访问

        静态调用,也称为隐式调用,由编译系统完成对DLL的加载和应用程序结束时DLL卸载。通常采用的方式是把把产生动态链接库时产生的.lib文件加入到应用程序的工程中。想使用DLL中的函数时,只须在源文件中声明一下。.lib文件包含了每一个DLL导出函数的符号名和可选择的标识号以及DLL文件名,不含有实际的代码。.lib文件包含的信息进入到生成的应用程序中,被调用的DLL文件会在应用程序加载时同时加载到内存中


       DLL的简单实例

       在VC中新建一个Win32 Dynamic-Link Library工程,工程名称随意取。不妨为chong

       新建一个add.cpp文件,代码如下:

       extern  "C"   int _declspec(dllexport) add(int a,int b)

       {         return  a + b;     }

       在DLL项目中,函数前面加上关键字_declspec(dllexport),表示本函数可供外界调用;如果不加该关键字,则表示本函数为dll内部调用,外界不可调用。

       如果是Win32 Console Application程序,在编译链接以后,会在Debug目录下生成一个.exe文件;而如果是Win32 Dynamic-Link Library程序,则会在Debug目录下生成一个.dll文件。
       另建一个Win32 Console Application项目,以测试刚才新建立的DLL文件。

       新建一个.cpp文件,录入代码如下:

       #include <iostream.h>
       #include <Windows.h>
       void main(){
       typedef int (*ADD)(int ,int);//函数指针类型
       HINSTANCE Hint = ::LoadLibrary("chong.dll");//加载我们刚才生成的dll
       ADD add = (ADD)GetProcAddress(Hint,"add");//取得dll导出的add方法
       cout<<add(3,4)<<endl;
      }

      并将.dll文件拷入到本项目Debug文件夹下。因为程序会在如下的路径中查找dll文件:当前可执行模块所在的目录,当前目录, Windows 系统目录,Windows 目录

      至此,dll调用成功。

      除了使用关键字来声明导出函数之外,还可以使用.def文件的方式来声明。

      在源文件同目录下新建一个.def文件,内容如下:

      LIBRARY
      EXPORTS
          add //此处为导出函数名字

       在该文件中,可以使用;来起注释作用。

       还可以改变导出函数的名字,如 myadd = add。即在调用add函数时使用myadd,而不是add

      

        以上为在运行时加载所需DLL,还可以设置在启动时加载相应的DLL

       

         #include  < iostream.h >
         #pragma  comment(lib,"DLL.lib")
         extern   int  myadd( int  , int  ); // 没有加这句而只加上面这句(或在工程设置里加上DLL.lib)会链接错误
         void  main()
          {
              cout
<< myadd( 3 , 4 ) << endl;
           }
           使用这种方法来调用,在链接的时候即可以加载所需要DLL。
          
          DLL的调用方式:显式调用和隐式调用
          显式调用:是指在应用程序中用Load library或MFC提供的afxloadlibrary显式的将自己所做的动态链接库调用进来,并指定DLL的路径作为参数。该函数返回HINSTACE参数。在应用程序退出之前,应用用Free Library或MFC提供的函数释放动态链接库。
          使用显式调用方式可以让程序员来决定DLL文件何时加载或不加载,而操作系统在载入应用程序时不必将所有该应用程序所引用的DLL都一起加载到内存中,只要在使用某个DLL时再将其载入,这样就可以减少应用在初始加载时所使用的时间和对内存的消耗。
          隐式调用:这种调用方式需要把产生动态链接时产生的.lib文件加入到应用程序的工程中,在使用DLL中的函数时,只需说明一下即可以直接通过函数名调用DLL的输出函数。调用方法和程序内部其它的函数一样。隐式调用不需要调用loadlibrary()和free library()。程序员在建立一个DLL文件时,链接程序会自动生成一个与之对应的.lib文件。该文件包含了每一个DLL堀函数的符号名和可选的标识号,但是并不含实际的代码。.lib文件作为DLL的替代文件被编译到应用程序项目中。当程序员通过隐式调用方式编译生成应用程序时,应用程序中的调用函数与.lib文件中符号相match,然后通过符号或标识号实现对DLL函数的动态链接。所有被应用程序调用的DLL文件都会在应用程序EXE文件加载时被加载到内存中。
          .lib文件有两种:一种是静态库。这种库中包含文件的实现代码,一般用在静态链接上,它是将.lib中的代码加入到exe文件中,当编译完成之后,.lib文件也就没用了。另一种是在动态库中,.lib和.dll文件配合使用,里面没有代码,只有函数的声明。具体的实现代码放在.dll文件中。这种.lib文件是用于静态调用dll中。动态调用则完全用不上.lib文件

当应用程序对DLL的.lib文件加载后,还需要把.dll对应的头文件包含到其中,在这个头文件中给出.dll中定义的函数原形,然后声明

转载自http://hi.baidu.com/xingwangchenxi/blog/item/dd452509e63ad592e850cd01.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值