现在的位置: 首页 > 综合 > 正文

动态库编程详解

2013年02月23日 ⁄ 综合 ⁄ 共 4785字 ⁄ 字号 评论关闭

 

目录

概述

一、动态库概念与分类

1、什么是动态库

2、动态库分类

4、动态库解决的问题

二、动态库的创建

1、规则动态库

2、声明导出函数的两种方式

2.1__declspec(dllexport)导出

2.2 .def文件导出

3、导出导入类

三、隐式、显示调用动态库

1、动态库隐式调用

2、动态库显示调用

3.显示、隐式调用的区别

四、动态库的测试

 

 

 

概述

      动态库是继静态库发展起来的一种封装重用技术,在灵活性、扩展性、重用性各方面取得了突破,本文只介绍动态库的基础知识,关于动态库的高级编程,推荐参看Jeffrey
Reichter
Chiristophe Nasarre合著的《Windows核心编程》第5版。

      

一、动态库概念与分类

1、什么是动态库

   DLL(Dynamic Linkable Library)动态链接库亦简称动态库,它是一块封装好的代码块,包含着一些方法,一般不包括消息循环,也建议不要去包含这些。可把它看成一个仓库,其提供了可直接使用的变量、函数、类等。打个不太生动的比喻,动态库犹如保卫森严的生产基地,但你可以通过正确入口进入,获得你想要的东西,你不用管也管不着这东西是怎么生产的,拿走从出口出来就行,同时生产基地是共享的,大家都可以通过入口获得相应的东西。

      在“库”的发展史上经历了“无库---静态库---动态库”的时代,无论是动态库还是静态库都能解决代码共享的问题。

动态库是基于二进制级重用的,所以与语言无关、环境无关(前提是你动态库中没有涉及对环境有依赖的东西,如调用一些第三方DLL)的,再一个得遵循DLL接口规范和调用约定,简而言之,用各种语言编写的标准DLL其他语言都可以调用。所以如果想创建一个通用的DLL,那么得严格遵守DLL规范,包括导出、调用约定、形参几方面的内容。

 

2、动态库分类 

通过VC++工具编写的动态库分为两类----规则DLL与非规则动态库,Visual
C++
支持编写三种DLL,它们分别是Non-MFC
DLL
(非MFC动态库)、MFC
Regular DLL
MFC规则DLL)、MFC
Extension DLL
MFC扩展DLL)。非MFC动态库不采用MFC类库结构,其导出函数为标准的C接口,能被非MFCMFC编写的应用程序所调用;MFC规则DLL包含一个继承自CWinApp的类,但其无消息循环;MFC扩展DLL采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。

4、动态库解决的问题

      节省资源:如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件中了。但是若使用DLL,该DLL不必被包含在最终EXE文件中,EXE文件执行时可以动态地引用和卸载这个与EXE独立的DLL文件。

      节省内存,假如本地有多个进程用到动态库,那么在内存只是只载入一次的,两个进程共用该DLL在内存中的页面。

灵活性:我认为灵活性才是动态库最值得称道的地方,发行的动态库,只要原有的接口不改变,那么你可以任意地改动,增加动态库里面的内容,同时以新版本替换旧版本,而不影响依赖于此动态库的程序。举个例子吧,例如你的一个软件已经发行了,如果出了什么问题,只要找出出问题的模块(假如它就是个动态库),并处理好,把新版的动态库给用户替换旧版的就OK了,如果全是静态库呢?那只有把整个工程编译一编,发一个大包给人家,人家也许还要把以前的软件卸载,重新装一次新的版本。

模块化:这对大项目特别有利,利用动态库可以把项目切割成N个小块加以分工,还有利于错误定位等,岂不快哉。

语言无关性:只要遵循约定的DLL接口规范和调用方式,用各种语言编写的DLL其他语言都可以相互调用。

特殊用途:windows提供的一些特性只有通过DLL才用使用,如钩子(hook)就需要通过DLL来实现,关于钩子的编程细节,将在后续的博文中详细介绍。

 

二、动态库的创建

1、规则动态库

   VC++6.0为开发工具,在VC++中创建Win32
Dynamic-Link Library
工程,下图中选择第一项,当然也可以选择其他项。

在创建的工程中创建一个头文件MyDll.h与一个CPP文件MyDll.cpp,在头文件与源文件中写入以下代码:

//MyDll.h

#ifndef MYDLLEIPORT

#define MYDLLEIPORT extern "C" __declspec(dllimport)

#endif

MYDLLEIPORT int add(int a, int b);

MYDLLEIPORT int     g_nCount;

 

定义实体:

#define MYDLLEIPORT extern "C" __declspec(dllexport)

#include "MyDll.h"

int g_nCount = 0;

int add(int a, int b){

      return (g_nCount += a + b);

}

代码写完了,现在的工作是怎么把函数导出的问题了,下面分析如何导出函数,与导出的方式

2、声明导出函数的两种方式

DLL中导出函数的声明有两种方式:一种是在函数声明中加上__declspec(dllexport)调用DLL时配合.lib文件通过__declsped(dllimport)指令导入函数即可,另外一种方式是采用模块定义(.def)文件声明,.def文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。

通过修饰符__declspec(dllexport)导出的函数,可能会面临函数名改编的问题,而通过.def文件导出的,将不会有此问题,要导出编译时函数名不会被改编的函数,还有一种方法,就是在DLL源文件中输入以下代码:

#pragma comment(linker, “/export:add=_add@8”)

不过此方法有个麻烦事,你得事先知道函数改篇后的名字是什么,如函数add改编后是_add@8,还有此方法导出的函数名是两个,”add””_add@8”,个人建议使用.def文件导出最为方便快捷,省去非常多的麻烦。

      这两种导出方式都会生成一个与Dll同名的.lib文件,此文件在隐式调用时得用。.lib文件只是包含一些导出函数的属性说明信息。

2.1__declspec(dllexport)导出

   在头文件声明函数前加上__declspec(dllexport),如下:

#ifndef MYDLLEIPORT

#define MYDLLEIPORT extern "C" __declspec(dllimport)

#endif

MYDLLEIPORT int add(int a, int b);//导出函数

MYDLLEIPORT int g_nCount;//导出变量

其中extern"C"表示函数是安C语言的方式编译,C++对函数名称编译时会改编如add,C++编译后变成_add@8,改名后,C语言或者其他语言调用的时候会出现问题,会找不到,同时不同厂家的编译器编译时生成的函数名也可能不一样;
__declspec(dllexport)
是导出修饰符,指明导出此函数,不用过分解释,照用就行;下面是对extern
"C"
的解释:

extern "C"包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”,表示可被外部文件引擎;其次,被它修饰的目标是“C”的,表示按照C语言方式编译和连接。

编译规(extern
"C")
必须指定,才能编译出规范的DLL接口。用这个方法导出接口,如果用VC++编译,还是会有函数名改编的问题,编译后,函数名应该是_add@8,调用DLL时,如果用同一厂家(VC++)编译器,是不是会有问题的,用其他厂家的编译器,那就不能保证了,所以这种导出方法并不是很好,可采用下面介绍的通过.def文件导出的方法。

2.2 .def文件导出

      此方法导出函数,将避免函数名改编的问题。在工程中添加一.def文件---MyDll.def:

在新加入的MyDll.def文件中输入以下代码:

;MyDll.def :导出DLL函数

LIBRARY DLLDemo

EXPORTS

add @ 1

g_nCount DATA

.def文件的规则为:
  (1)LIBRARY语句说明.def文件相应的DLL
  (2)EXPORTS语句后列出要导出函数的名称或者变量。可以在.def文件中的导出函数

名后加@n,表示要导出函数的序号为n,在进行函数调用时,这个序号将发挥其作用,

可通过序号来调用相应的函数(通过序号取得函数方法:GetProcAddress(hDll,

MAKEINTRESOURCE(1))),不过此方法不推荐,因为难以维护之类的原因,现在基本

上不怎么使用;
  (3).def文件中的注释由每个注释行开始处的分号(;)指定,且注释不能与语句共享一行。

      (4)导出全局变量格式:全局变量DATADATA是关键字,例:g_nCount
DATA

其实也可简单地写成如下格式:

EXPORTS

add

g_nCount DATA

不需要序号,只给个函数名就OK,记住别把函数名弄错了。

 

3、导出导入类

   最好不要导出类,类的导出会破坏DLL的通用性,C++写的类,C#或者其他语言不一定支持,在这里只是简单的说一下。

//文件名:point.hpoint类的声明
#ifndef   POINT_H
#define  POINT_H
#ifdef  DLL_FILE
 class _declspec(dllexport) point //导出类point
#else
 class _declspec(dllimport) point //导入类point
#endif
{
 public:
  float y;
  float x;
  point();
  point(float x_coordinate, float y_coordinate);
};

#endif

此处有宏POINT_HDLL_FILE,第一个宏的作用当然是防止头文件重编译,第二个宏则是用来判断当前是用来导出还是导入的,在此头的实现文件(.cpp)中先对DLL_FILE定义,则导出此文件,而当用户用这个动态库时,因为不知道DLL_FILE,所以自然没定义,则会导入此文件。
//文件名:point.cpppoint类的实现
#ifndef DLL_FILE
 #define DLL_FILE
#endif
#include "point.h"
//
point的缺省构造函数
point::point()
{
 x = 0.0;
 y = 0.0;
}

类的导出导入看似乎不适合动态调用,因为要包含其头文件,在主调应用程序中生成对象。注意类的导出导入格式如下:

class _declspec(dllexport) 类名 //导出类point
    class _declspec(dllimport) 类名
//
导入类point

三、隐式、显示调用动态库

1、动态库隐式调用

   隐式调用也有通过指令与通过IDE设置两种方式。隐式调用需要.lib文件,把.dll.lib放同一目录下,然后按以下方法操作

Ø #pragma
comment
指令:

      #include “..\MyDll.h”----可以是绝对路径,也以的相对路径

      #pragma comment(lib, “..\\DllDemo.lib”) ----可以是绝对路径,也可以是相对路径

这种方法似乎有点麻烦,必须把路径设对了,不然可能就找不到。

Ø 在编译器(VC)中设置:

1、依次选择tools->options->directories->Show directories
for->Library files
然后

添加DllDemo.lib

抱歉!评论已关闭.