动态库的创建及使用

+++++++++++++++++++++++++++++++++++++++++++++

++++++++++++  动态库的创建及使用     +++++++++++

+++++++++++++++++++++++++++++++++++++++++++++

1.    创建一个简单的非MFCDLL

   用动态链接库实现一个同样功能的add函数

l        创建一个DLL的项目:

文件》新建—》项目win32控制台》输入名字“dllTestDll—》空项目》完成。

l        在建立的工程中添加lib.hlib.cpp文件,源代码如下:

lib.h

/* 文件名:lib.h */

 

#ifndef LIB_H

 

#define LIB_H

 

//对函数add的声明前面添加了__declspec(dllexport)语句。这个语句的含义是声明函数addDLL的导出函数

extern "C" int __declspec(dllexport)add(int x, int y);

 

#endif

lib.cpp

/* 文件名:lib.cpp */

 

#include "lib.h"

#include <iostream>

using namespace std;

 

int add(int x, int y)

 

{

    std::cout<<"动态库中......两个数之积:是吗???"<<(x*y)<<std::endl;

    return x * y;

 

}

l        编译成功后

你在..//Debue里面能找到下面的文件,则证明动态库生成成功。

 

l        动态调用方式————创建同一个解决方案下的应用项目dllCall

加上下面的代码,编译运行。

Main.cpp

#include <stdio.h>

#include <tchar.h>               //_T有关,且是Unicode

#include <windows.h>

 

typedef int(*lpAddFun)(int, int); //宏定义函数指针类型,定义了一个与add函数接受参数类型和返回值均相同的函数指针类型

 

int main(int argc, char *argv[])

 

{

 

    HINSTANCE hDll; //DLL句柄

 

    lpAddFun addFun; //函数指针,定义了lpAddFun的实例addFun

   

   

    //通过Win32 Api函数LoadLibrary动态加载了DLL模块并将DLL模块句柄赋给了hDll

    //动态库所在的路径,如果有误则会报错。上一级目录下面的Debug文件夹下面的,一个dllTest.dll文件

    hDll = LoadLibraryW(_T("..//Debug//dllTest.dll"));

 

    if (hDll != NULL)

 

    {

        //Win32 Api函数GetProcAddress得到了所加载DLL模块中函数add的地址并赋给了addFun

       addFun = (lpAddFun)GetProcAddress(hDll, "add");

 

       if (addFun != NULL)

 

       {   //应用程序使用动态库中的函数

 

           int result = addFun(2, 3);

 

           printf("%d", result);

 

       }

 

       //应用工程使用完DLL后,在函数main中通过Win32 Api函数FreeLibrary释放了已经加载的DLL模块。

 

       FreeLibrary(hDll);

 

    }

 

    return 0;

 

}

能得到你要的结果。你可以在上面的   int result = addFun(2, 3);中设置断点,然后F11单步调试,你可以进入到原动态库里面的语句中。

 

通过这个简单的例子,我们获知DLL定义和调用的一般概念:
  (1)DLL中需以某种特定的方式声明导出函数(或变量、类);
  (2)应用工程需以某种特定的方式调用DLL的导出函数(或变量、类)。

2.声明导出函数

在函数声明中加上__declspec(dllexport),上面已经举例说明;另外一种方式是采用模块定义(.def) 文件声明,.def文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。

l        dllTest中添加lib.def的文件

 

 

 

 

l        lib.def中加下面的内容:

LIBRARY dllTest

EXPORTS

add @ 1

l        .def文件的规则:

(1)LIBRARY语句说明.def文件相应的DLL
 (2)EXPORTS语句后列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示要导出函数的序号为n(在进行函数调用时,这个序号将发挥其作用);

 (3).def 文件中的注释由每个注释行开始处的分号 (;) 指定,且注释不能与语句共享一行。
  由此可以看出,例子中lib.def文件的含义为生成名为“dllTest”的动态链接库,导出其中的add函数,并指定add函数的序号为1

 

  

3.Dll的调用方式:

1中已经介绍了动态调用方式,现在我们来认识静态调用方式

l        创建一个新的控制台项目StaticCallDll,并把下面代码拷贝到

StaticMain.cpp

#pragma comment(lib,"dllTest.lib")

#include <iostream>

using  namespace std;

 

//.lib文件中仅仅是关于其对应DLL文件中函数的重定位信息

extern "C" __declspec(dllimport) int  add(int x,int y);

using namespace std;

 

 

 

 

void main(int argc, char* argv[])

 

{

 

    int result = add(2,3);

 

   

    std::cout<<"应用程序中两个数的之和:"<<result<<std::endl;

   

 

}

l        将编译dllTest工程所生成的.lib.dll文件拷入dllCall工程所在的路径。

当然你也可以在

StaticCallDll的属性》链接—》常用—》增加Lib路径中设置新的路径。并在链接》输入—》dllTest.lib这个名字的Lib

l        总结:

(1)告诉编译器与DLL相对应的.lib文件所在的路径及文件名,#pragma comment(lib,"dllTest.lib")就是起这个作用。

 程序员在建立一个DLL文件时,连接器会自动为其生成一个对应的.lib文件,该文件包含了DLL 导出函数的符号名及序号(并不含有实际的代码)。在应用程序里,.lib文件将作为DLL的替代文件参与编译。

  (2)声明导入函数,extern "C" __declspec(dllimport) add(int x,int y)语句中的__declspec(dllimport)发挥这个作用。

  静态调用方式不再需要使用系统API来加载、卸载DLL以及获取DLL中导出函数的地址。这是因为,当程序员通过静态链接方式编译生成应用程序时,应用程序中调用的与.lib文件中导出符号相匹配的函数符号将进入到生成的EXE 文件中,.lib文件中所包含的与之对应的DLL文件的文件名也被编译器存储在 EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows将根据这些信息发现并加载DLL,然后通过符号名实现对DLL 函数的动态链接。这样,EXE将能直接通过函数名调用DLL的输出函数,就象调用程序内部的其他函数一样。

 

 

 

 

 

1.    导出变量:

DLL定义的全局变量可以被调用进程访问;DLL也可以访问调用进程的全局数据,我们来看看在应用工程中引用DLL中变量的例子。

l        在原来的动态库项目中修改如下:

lib.h

/* 文件名:lib.h */

 

#ifndef LIB_H

 

#define LIB_H

 

//对函数add的声明前面添加了__declspec(dllexport)语句。这个语句的含义是声明函数addDLL的导出函数

extern "C" int __declspec(dllexport)add(int x, int y);

//导出一个全局变量

extern int dllGlobalVar;

 

 

#endif

lib.cpp

#include "lib.h"

#include "windows.h"

#include "stdio.h"

#include <iostream>

using namespace  std;

// lib.cpp : DLLMain函数的例子

 

 

//如果程序员没有为DLL模块编写一个DLLMain函数,系统会从其它运行库中引入一个不做任何操作的

//缺省DLLMain函数版本。在单个线程启动和终止时,DLLMain函数也被调用。正如由dwReason参数所

//表明的那样。

 

 

int dllGlobalVar;  //全局变量声明

 

BOOL APIENTRY DllMain( HANDLE hModule,   

                    /*

                    进程中的每个DLL模块被全局唯一的字节的HINSTANCE句柄标识进程自己还有一个HINSTANCE句柄。

                    所有这些模块句柄都只有在特定的进程内部有效,它们代表了DLLEXE模块在进程虚拟空间中的起始

                    地址。在Win32中,HINSTANCEHMODULE的值是相同的,这两种类型可以替换使用。进程模块句柄几乎

                    总是等于x400000,而DLL模块的加载地址的缺省句柄是x10000000。如果程序同时使用了几个DLL

                    块,每一个都会有不同的HINSTANCE值。这是因为在创建DLL文件时指定了不同的基地址,或者是因为

                    加载程序对DLL代码进行了重定位。

                    */

                    DWORD  ul_reason_for_call,

                    LPVOID lpReserved

                    )

{

    switch (ul_reason_for_call)

    {

    case DLL_PROCESS_ATTACH:

       printf("/nprocess attach of dll/n");

       dllGlobalVar = 100; //dll被加载时,赋全局变量为

 

 

       break;

    case DLL_THREAD_ATTACH:

       printf("/nthread attach of dll/n");

       break;

    case DLL_THREAD_DETACH:

       printf("/nthread detach of dll/n");

       break;

    case DLL_PROCESS_DETACH:

       printf("/nprocess detach of dll/n");

       break;

    }

    return TRUE;

}

int add(int x, int y)

{  

    std::cout<<"动态库中......两个数之积:是吗???"<<(x*y)<<std::endl;

    return x * y;

 

}

lib.def

LIBRARY    "dllTest"

EXPORTS

 

add @ 1

dllGlobalVar CONSTANT

 

;dllGlobalVar DATA

lib.hlib.cpp中可以看出,全局变量在DLL中的定义和使用方法与一般的程序设计是一样的。若要导出某全局变量,我们需要在.def文件的EXPORTS后添加:

变量名 CONSTANT   //过时的方法

  或

变量名 DATA     //VC++提示的新方法

StaticCallDll项目中的StaticMain.cpp里面修改如下

 

#pragma comment(lib,"dllTest.lib")

#include <iostream>

using  namespace std;

 

//.lib文件中仅仅是关于其对应DLL文件中函数的重定位信息

extern "C" __declspec(dllimport) int  add(int x,int y);

 

extern int dllGlobalVar; //声明所导入的并不是DLL中全局变量本身,而是其地址

//extern int _declspec(dllimport) dllGlobalVar; //_declspec(dllimport)导入

//通过_declspec(dllimport)方式导入的就是DLL中全局变量本身而不再是其地址了

 

using namespace std;

 

 

void main(int argc, char* argv[])

 

{

 

    int result = add(2,3);

 

   

    std::cout<<"应用程序中两个数的之和:"<<result<<std::endl;

 

 

    应用程序必须通过强制指针转换来使用DLL中的全局变量。这一点,从*(int*)dllGlobalVar可以看出

    printf("%d ", *(int*)dllGlobalVar);

 

    *(int*)dllGlobalVar = 1;

 

    printf("%d ", *(int*)dllGlobalVar);

 

   

 

}

 

l        应用工程中引用DLL中全局变量的一个更好方法:

#include <stdio.h>

 

#pragma comment(lib,"dllTest.lib")

 

extern int _declspec(dllimport) dllGlobalVar; //_declspec(dllimport)导入

 

int main(int argc, char *argv[])

 

{

 

    printf("%d ", dllGlobalVar);

 

    dllGlobalVar = 1; //这里就可以直接使用, 无须进行强制指针转换

 

    printf("%d ", dllGlobalVar);

 

    return 0;

 

}

 

 

通过_declspec(dllimport)方式导入的就是DLL中全局变量本身而不再是其地址了,笔者建议在一切可能的情况下都使用这种方式。

 

 

2.    导出类:

     DLL中定义的类可以在应用工程中使用。

下面的例子里,我们在DLL中定义了pointcircle两个类,并在应用工程中引用了它们。

 

l        添加代码:

 

point.h

#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.cpp

#ifndef DLL_FILE

 

#define DLL_FILE

 

#endif

 

#include "point.h"

 

//point的缺省构造函数

 

point::point()

 

{

 

    x = 0.0;

 

    y = 0.0;

 

}

//point的构造函数

 

point::point(float x_coordinate, float y_coordinate)

 

{

 

    x = x_coordinate;

 

    y = y_coordinate;

 

}

 

 

circle.h

#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 );

 

    void SetRadius(float r);

 

    float GetGirth();

 

    float GetArea();

 

    circle();

 

private:

 

    float radius;

 

    point centre;

 

};

 

#endif

 

circle.cpp

#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;

 

}

l        编译生成后,在应用程序中用下面的代码,编译再运行。

#pragma comment(lib,"dllTest.lib");

//#include "windows.h"

#include "stdio.h"

#include "circle.h" //包含类声明头文件

 

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", c.GetArea(), c.GetGirth());

 

 

    return 0;

 

}

 

 

 

 

从上述源代码可以看出,由于在DLL的类实现代码中定义了宏DLL_FILE,故在DLL的实现中所包含的类声明实际上为

class _declspec(dllexport) point //导出类point

{

}

  和

class _declspec(dllexport) circle //导出类circle

{

….

  而在应用工程中没有定义DLL_FILE,故其包含point.hcircle.h后引入的类声明为:

class _declspec(dllimport) point //导入类point

{

}

  和

class _declspec(dllimport) circle //导入类circle

{

}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值