VC++(mfc)如何创建和调用动态链接库(DLL)上

作者:小知 发布时间:2017-08-07 分类:MFC

     技术所限,难免错误,如有不对之处,敬请留言指正!



     本篇文章使用的工程源码:

    1:static.rar

    2:staticcall.rar 

    3:static.rar

    4:dlltest.rar

    5:dllcall.rar

    6:dll.rar

    7:dllcall.rar

    8:dll.rar

    9:dllcall.rar

    可用vc++6.0或者vs2008打开。


    以下内容分为几个模块,篇幅较长,可选择性浏览:

    1:静态链接库和动态链接库简介

    2:静态链接库的创建和调用

    3:链接库的调试和查看方法

    4:非MFC DLL(动态链接库)的创建及其静态调用动态调用

    5:动态链接库导出类或者变量




    DLL全称Dynamic Linkable Library翻译过来就是动态链接库的意思,DLL可以理解为一种封装的接口,这个接口提供一些现成的变量、函数或者类。VC++编程开发经历了"无库->静态链接库->动态链接库"的一个过程。

    静态链接库和动态链接库的区别:

    1:如果mfc工程采用静态链接库的调用方式,静态链接库lib中的指令都被直接包含在最终生成的EXE文件中了。这时候EXE的大小通常会很大。若使用动态链接库DLL,该DLL没有被包含在最终的EXE文件中,此时EXE文件执行的时候只要指明DLL所在位置并且调用就可以了,这时候的EXE文件大小通常较小。

    2:静态链接库中不能再包含其他的动态链接库或者动态链接库,但是动态链接库中可以继续包含动态链接库或者静态链接库。

    DLL的特点:

    1:只要遵循DLL接口规范和调用方式,任何编程语言编写的DLL都可以互相被调用。比如Delphi语言编写的DLL可以被VC++编写的DLL调用。

    2:Windows系统的系统盘system32文件夹下面有很多的dll文件,这些dll文件涵盖了Windows的大多数API接口。例如MessageBox函数就需要用到user32.dll。

QQ截图20170807154623.png

    3:VC++有三种类型的DLL,分别是MFC Regular DLL(MFC规则DLL)、MFC Extension DLL(MFC扩展DLL)、Non-MFC DLL(非MFC动态库)。

    MFC规则DLL包含一个继承自CWinApp的类,但其无消息循环;

    MFC扩展DLL采用MFC的动态链接版本创健,他只能被用MFC类库所编写的应用程序所调用。

    非MFC动态库不采用MFC类库结构,其导出函数为标准的C接口,能被非MFC或MFC编写的应用程序所调用。

        

   说了这么多,我们先创建并且调用一个静态链接库来直观的感受一下吧。

    1:在vc++ 6.0中新建一个Win32 Static Library工程(此工程源码标记为1),命名为static,并且新建一个static.h文件和一个static.cpp文件。

QQ截图20170807161336.png

QQ截图20170807161403.png

QQ截图20170807161420.png

QQ截图20170807162110.png

QQ截图20170807162557.png

    static.h的内容如下:

//文件:static.h
#ifndef STATIC_H
#define STATIC_H
extern "C" int add(int x,int y);  //声明为C编译、连接方式的外部函数
#endif

    static.cpp的内容如下:

//文件:static.cpp
#include "static.h"
int add(int x,int y)
{
return x+y;
}

   选择组建菜单进行组建->编译,然后发现工程所在文件夹debug目录多了一个static.lib文件。

QQ截图20170807163322.png

    这个static.lib静态链接库封装了add函数。

    这时候我们再用vc++6.0新建一个Win32 Console Application空工程(此工程源码标记为2,命名为staticcall,并添加一个staticcall.cpp文件。

QQ截图20170807163806.png

QQ截图20170807163815.png

QQ截图20170807163848.png

     staticcall.cpp代码如下:

#include <stdio.h>
#include "static.h"
#pragma comment(lib,"static.lib")
int main(int argc,char* argv[])
{
printf("6+7=%d\n",add(2,3));
}

    并把上面的static.h和static.lib这两个文件复制到此工程的根目录文件夹

QQ截图20170807164751.png

    此时运行工程,发现调用静态链接库static.lib中的add函数成功。也可以不复制static.h和static.lib文件,但是要指出其所在位置。

#include <stdio.h>
#include "static.h所在位置比如..\\static.h" 
#pragma comment(lib,"static.lib所在位置比如..\\debug\\static.lib")
int main(int argc,char* argv[])
{
printf("6+7=%d\n",add(2,3));
}

   或者在菜单里面设置lib的路径和include的路径,步骤为选择菜单->工具->选项->目录->include files填写你的static.h所在位置,在library files填写你的static.lib所在位置,然后确定。这时候代码可以写成如下:

#include <stdio.h>
#include "static.h"
#pragma comment(lib,"static.lib")
int main(int argc,char* argv[])
{
printf("6+7=%d\n",add(2,3));
}

QQ截图20170807172149.png

QQ截图20170807164920.png

    至此,完成了一个静态链接库的创建和调用,这里的静态链接库只封装了一个函数,静态链接库也可以用来封装类或者变量。

    库的调试与查看方法(同时适用于静态链接库和动态链接库):

    方法1:库文件不能单独执行,因此库文件工程执行的时候会弹出如下对话框。

QQ截图20170807223717.png

    我们用上面的例子来做示范,staticcall工程执行完毕debug目录下有一个EXE文件,此时在上图可执行文件名选择staticcall工程debug目录的exe,执行成功。

    

QQ截图20170807223956.png

QQ截图20170807224030.png

    方法2:将库工程和应用工程放在同一VC工作区,只对应用工程进行调试。如下图:先新建static库工程,再在这个基础上建立一个Win32 Console Application工程此工程源码标记为3

QQ截图20170807230358.png

QQ截图20170807230407.png

QQ截图20170807230437.png

QQ截图20170807230813.png

    staticcall工程的代码如下:

#include <stdio.h>
#include "..\\static.h"
#pragma comment(lib,"..\\debug\\static.lib")
int main(int argc,char* argv[])
{
printf("6+7=%d\n",add(2,3));
}

    工程目录结构如下:

QQ截图20170807232023.png

    方法3:使用depends工具查看动态链接库(DLL),企业版的VC++6.0一般含有depends工具。

QQ截图20170807233634.png

QQ截图20170807233322.png

QQ截图20170807233107.png

    可以看到system32文件夹下的user32.dll包含MessageBox的几种版本。

    好了,讲完了静态链接库的使用方法,接下来讲一讲动态链接库的使用。

    一:非MFC DLL的创建和使用

    1:首先新建一个Win32 Dynamic-Link Library工程,注意不要选择mfc appwizard dll工程,因为这里建立的是非mfc dll工程此工程源码标记为:4,如下图:

QQ截图20170808085709.png

QQ截图20170808085747.png

    2:新建好dll工程之后,在此工程基础上新建一个dll.h文件和dll.cpp文件

    dll.h的代码如下:

//文件名:dll.h
#ifndef DLL_H
#define DLL_H
extern "C" int _declspec(dllexport)add(int x,int y); //声明函数add为DLL的导出函数,应用程序只能调用DLL的导出函数
#endif

   dll.cpp的代码如下:


//文件名:dll.cpp
#include "dll.h"
int add(int x,int y)
{
return x+y;
}

QQ截图20170808085820.png

QQ截图20170808090234.png

QQ截图20170808090251.png

    此时,编译执行完毕,会发现debug目录有dll文件,这就是我们创建生成的非mfc的动态链接库。

QQ截图20170808093510.png

    3:接下来新建一个Win32 Console Application工程此工程源码标记为:5调用上面这个非mfc动态链接库。

QQ截图20170808093935.png

QQ截图20170808093946.png

    然后新建一个dllcall.cpp文件,添加如下代码,并且把上面生成的dlltest.dll复制到此工程根目录:


#include<stdio.h>
#include<windows.h>
typedef int(*pAdd)(int,int); //宏定义函数指针类型,定义一个与add函数接受参数类型和返回值均相同的函数指针类型
int main(int argc,char* argv[])
{
HINSTANCE hDll; //DLL句柄
pAdd addFun; //函数指针实例
hDll=LoadLibrary("dlltest.dll");
if(hDll!=NULL)
{
addFun=(pAdd)GetProcAddress(hDll,"add");
if(addFun!=NULL)
{
int result=addFun(2,3);
printf("%d\n",result);
}
FreeLibrary(hDll);//释放已经加载的DLL模块
}
return 0;
}

QQ截图20170808094005.png

QQ截图20170808094531.png

    编译运行,发现调用成功。

QQ截图20170808094937.png

    声明导出函数的几种方式:

    1:在要导出的函数前面加上_declspec(dllexport),例如上面的extern "C" int _declspec(dllexport)add(int x,int y)就把add函数变成了导出函数。

    2:采用模块定义添加def文件声明,例如上面dlltest工程的例子中把dll.h的代码改成如下:


//文件名:dll.h
#ifndef DLL_H
#define DLL_H
extern "C" int add(int x,int y);
#endif

   然后给dlltest工程添加一个def文件,其代码如下:

;dll.def:导出DLL函数 
LIBRARY dlltest
EXPORTS
add @ 1

QQ截图20170808101208.png

QQ截图20170808102251.png

    编译运行把dlltest.dll拷贝到dllcall的根目录,发现调用dll成功!

QQ截图20170808102147.png

    说明一下def的作用:

    LIBRARY dlltest:说明def文件对应的dll名称为dlltest;

    EXPORTS语句后面列出要导出函数的名称add,1表示要导出函数的序号;

    def中的分号';'表示注释内容,且注释不能放在语句后面,只能单独起一行

    dlltest中的def表示生成名为dlltest.dll的动态链接库,导出其中的add函数,并指定add函数的序号为1。

    我们发现非mfc dll工程编译完成后不但在debug目录发现一个dll文件还发现一个lib文件,那么这个lib文件是干什么的呢?通过上面的例子我们可以看出由“LoadLibrary-GetProcAddress-FreeLibrary”三位一体的“DLL加载-DLL函数地址获取-DLL释放”的方式调用动态链接库,这种调用方式称为DLL的动态调用

    我们发现,链接库分为静态链接库和动态链接库,动态链接库的调用又分为静态调用和动态调用,真是十分有趣!那么如何静态调用DLL呢?

    我们还是用上面的dlltest工程作为例子来示范,首先将dlltest的debug目录下面的dlltest.lib和dlltest.dll文件都拷贝到dllcall根目录下面,接着把dllcall的cpp文件代码改成如下形式:

#include<stdio.h>
#pragma comment(lib,"dlltest.lib")
extern "C" _declspec(dllimport) add(int x,int y);
int main(int argc,char* argv[])
{
int result=add(2,3);
printf("%d\n",result);
return 0;
}

QQ截图20170808105233.png

QQ截图20170808105249.png

QQ截图20170808105554.png

    至此,便完成了动态链接库的静态调用,那么dlltest.lib起什么作用呢?如果只复制dlltest.lib到dllcall根目录会怎样呢?

QQ截图20170808105845.png

    上图所示的运行结果充分表明dlltest.lib的作用是包含了DLL导出函数的符号名add以及序号并不包含实际的代码,就好比lib文件只是一个中介的作用,指出函数的代码在dll中,在应用程序中,lib文件将作为dll的替代文件参与编译。

    _declspec(dllimport)add声明add为导入函数


    DLL导出变量:

    DLL定义的全局变量可以被应用程序访问,DLL也可以访问应用程序中的全局变量,下面举个例子:

    首先新建一个Win32 Dynamic-Link Library工程此工程源码标记为6。然后新建一个dll.h和dll.cpp文件,dll.h代码如下:


//文件名:dll.h
#ifndef DLL_H
#define DLL_H
extern int N; //定义全局变量为N
#endif

    dll.cpp代码如下:


//文件名:dll.cpp
#include "dll.h"
#include<windows.h>
int N=7;

    再添加一个dll.def文件代码如下:



;文件名:dll.def
;在DLL中导出变量 N
LIBRARY "dll"
EXPORTS
N DATA

   然后选择编译组建得到dll.dll和dll.lib文件在debug目录。

QQ截图20170808145659.png

QQ截图20170808145704.png

QQ截图20170808150446.png

QQ截图20170808150506.png

    接下来新建一个Win32 Console Application工程此工程源码标记为7,新建一个dllcall.cpp文件,代码如下:


#include<stdio.h>
#pragma comment(lib,"dll.lib")
extern int _declspec(dllimport) N; //用_declspec(dllimport)导入DLL中的变量
int main(int argc,char* argv[])
{
printf("%d\n",N);
N=1;
printf("%d\n",N);
return 0;
}

    把上面的dll.dll和dll.lib拷贝到dllcall的工程目录,编译运行,发现调用DLL中的变量成功!

QQ截图20170808150701.png

QQ截图20170808150709.png

QQ截图20170808151204.png


    DLL导出类:

    新建一个win32 Dynamic-Link Library工程此工程源码标记为8,然后新建circle.h、point.h、circle.cpp、point.cpp四个文件。代码如下:

    point.h代码:


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

   circle.h代码:


//文件名:circle.h,circle类的声明
#ifndef CIRCLE_H
#define CIRCLE_H
#include "point.h"
#ifdef DLL_FILE
class _declspec(dllexport) circle //导出类circle
#else
class _declspec(dllimport) circle //导入类circle
#endif
{
public:
void SetCentre(const point &centrePoint); //圆点
void SetRadius(float r); //半径
float GetGirth(); //周长
float GetArea(); //面积
circle();
private:
float radius;
point centre;
};
#endif

    point.cpp代码:


//文件名:point.cpp,point类的实现
#ifndef DLL_FILE
#define DLL_FILE
#endif
#include "point.h"
point::point()  //类point的默认构造函数
{
x=0.0;
y=0.0;
}
point::point(float x_coordinate,float y_coordinate) //类point的构造函数
{
x=x_coordinate;
y=y_coordinate;
}

   circle.cpp代码:

//文件名:circle.cpp,circle类的实现
#ifndef DLL_FILE
#define DLL_FILE
#endif
#include "circle.h"
#define PI 3.1415926
circle::circle() //circle类的构造函数
{
centre=point(0,0);
radius=0;
}
float circle::GetArea() //得到圆的面积
{
return PI*radius*radius;
}
float circle::GetGirth() //得到圆的周长
{
return 2*PI*radius;
}
void circle::SetCentre(const point &centrePoint) //设置圆心坐标
{
centre=centrePoint;
}
void circle::SetRadius(float r)
{
radius=r;
}

QQ截图20170808163238.png

    之后编译运行便可以在debug目录得到一个dll文件和一个lib文件,再新建一个Win32 Console Application工程此工程源码标记为9,新建一个dllcall.cpp文件代码如下:


#include<stdio.h>
#include "circle.h" //包含类声明头文件
#pragma comment(lib,"dll.lib")
int main(int argc,char* argv[])
{
circle c;
point p(2.0,2.0);
c.SetCentre(p);
c.SetRadius(1.0);
printf("area:%f girth:%f\n",c.GetArea(),c.GetGirth());
return 0;
}

    然后把point.h和circle.h和dll.dll以及dll.lib四个文件拷贝到此工程根目录,编译运行,发现调用DLL中的circle类以及point类成功!

QQ截图20170808163942.png

QQ截图20170808163922.png


未完待续...






    




    


呃 本文暂时没人评论 来添加一个吧

发表评论

必填

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。