Romi-知行合一

轻轻的风轻轻的梦,轻轻的晨晨昏昏, 淡淡的云淡淡的泪,淡淡的年年岁岁。
  博客园  :: 首页  :: 新随笔  :: 订阅 订阅  :: 管理

VC++中DLL(动态链接库)使用的若干问题

Posted on 2012-01-06 18:53  romi  阅读(11837)  评论(0编辑  收藏  举报

根据本人学习中遇到的问题,本文围绕以下几个问题展开:

1.DLL的相关概念

2.动态非MFC DLL在VC++中的使用

3.宏在DLL导出函数和导入函数声明中的应用

 

一 DLL相关概念

可以简单把DLL看做一个仓库,它提供给你可以直接使用的变量、函数或类。动态链接库DLL实现了库的共享,体现了代码重用的思想。我们可以把广泛的、具有共性的、能够多次被利用的函数和类定义在库中。这样,在再次使用这些函数和类的时候,就不再需要重新添加与这些函数和类相关的代码。DLL在软件世界中随处可见,比如我们在Windows目录下的system32文件夹中会看到kernel32.dll、user32.dll和gdi32.dll,windows的大多数API都包含在这些DLL中。kernel32.dll中的函数主要处理内存管理和进程调度;user32.dll中的函数主要控制用户界面;gdi32.dll中的函数则负责图形方面的操作。在MFC应用程序编译时就会经常调用这里面的函数和基类,因为这些库里的函数和类给我们提供了一般的方法方便我们编写应用程序。

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,但是接口不能为MFC;MFC扩展DLL采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。

DLL应用场合:需要经常使用的函数、变量或基类都可以封装在DLL中。比如通用的算法、常用的通信控制等;此外采用DLL在一个大工程中还可以方便分工,提高效率,增强系统易扩展性,就不必把整个代码全放在一个工程文件内,初学者最喜欢这样了(我也是初学者^_^)。

 

二 动态非MFC DLL在VC++中的使用

DLL的三种类型用法其实都是大同小异 ,所不同的是接口和应用范围的差异,使用方法均相同。这里我就只讨论非MFC DLL的使用,接口涉及到变量、函数和类,这里只讨论函数的情况,变量和类的情况是差不多的,类的调用的话可能要求按需要加类定义的头文件。也就是此节只讨论非MFC DLL函数调用分方法。下面内容只说函数(变量和类也可作为接口,这里省略不做讨论)

DLL的使用包括两个方面:一是函数的导出,二是函数的导入。导出在DLL中进行,导入在调用DLL的应用程序中。

导出方法(两种方法):①在函数声明前加上extern "C" _declspec(dllexport)

                            ②在.def文件中说明(貌似vs2010中没有这个文件。。。)

注:通常情况下,为了确保不同的语言编写的可执行模块都能够正确地访问到导出函数,习惯上都采用extern "C"来指定导出函数采用C链接方式。_declspec(dllexport)为DLL导出的关键字。

代码示例:      extern "C" __declspec(dllexport) float Add(float,float);

在函数定义时就不需要再加extern "C" __declspec(dllexport)这些关键字了,跟通常函数定义一样。

导入方法(两种方法):①静态导入:需要将相应的.dll和.lib文件拷贝到应用程序的工程目录中,在调用函数的源文件中使用用#pragma comment(lib,"xxx.lib")或直接项工程中加.lib文件,然后再声明导入函数,声明导入函数代码如下:

                                   extern "C" __declspec(import) float Add(float,float);

在需要调用函数的位置直接使用函数即可,注意函数参数和返回值类型,就跟使用MFC类库你的函数一样。

                                   ②动态调用:需要将相应的.DLL拷贝到应用程序工程目录中,再采用LoadLibrary-GetProcAddress-FreeLibrary方法进行函数导入。LoadLibrary、GetProcAddress、FreeLibrary函数的使用参见msdn,这里不详诉。

代码示例:

.h文件:

protected:
float m_Num1;
float m_Num2;
float m_Result;

.cpp文件:

typedef float (*fn_Add)(float,float);
HINSTANCE hLib=LoadLibrary(_T("Win32Dll.dll"));
fn_Add pAdd=(fn_Add)GetProcAddress(hLib,"Add");
UpdateData(true);
m_Result=(*pAdd)(m_Num1,m_Num2);
FreeLibrary(hLib);
UpdateData(false);

 

注:这里为了运行不出错,最好使用if语句判断下hLib和pAdd是否为NULL。此外有时还需要LoadLibrary函数的参数写DLL的具体文件地址而不是DLL文件名(比如从VC低版本转高版本时需要注意这个问题)。

DllMain函数:Windows在加载DLL的时候,需要一个入口函数,就如同控制台或DOS程序需要main函数、WIN32程序需要WinMain函数一样。在非MFC DLL中,DLL并没有提供DllMain函数,应用工程也能成功引用DLL,这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的缺省DllMain函数版本,并不意味着DLL可以放弃DllMain函数。需要注意的是,在MFC规则DLL和MFC扩展DLL工程中都有DllMain函数,但在非MFC DLL工程中没有,除非特殊需要,我们一般不管这个函数。


三 宏在DLL导出函数和导入函数声明的应用

前面讲了函数的导出和导入,我们看到在静态导入中还需要对导入函数进行声明,而在调用MFC的函数时是不需要声明的,可不可以是本文讨论的函数也这样呢?那当然是可以的,这里就要用到宏定义,在Dll中使用宏对导出和导入关键字进行定义。定义一个宏,用于控制函数处于导出声明或调用导入声明状态。对于DLL定义文件,在包含DLL头文件之前,首先定义一个控制宏,用于声明所有的函数为导出函数;而在隐式调用中,在包含DLL头文件时不需要定义控制宏,用于声明所有的函数为导入函数。

DLL头文件格式如下:

#ifdef WIN32DLL_EXPORTS
#define WIN32DLL_API __declspec(dllexport)
#else
#define WIN32DLL_API __declspec(dllimport)
#endif

函数声明:extern "C" WIN32DLL_API float Add(float,float);      //函数声明也在头文件中

对DLL文件进行使用时,就可以直接使用函数Add了而不需要再声明:m_Result=Add(m_Num1,m_Num2);

注意:调用DLL的应用程序原文件还要加入以上宏定义所在的头文件,不然会编译出错,提示Add函数不能识别。