Chinaunix首页 | 论坛 | 博客
  • 博客访问: 912642
  • 博文数量: 299
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 2493
  • 用 户 组: 普通用户
  • 注册时间: 2014-03-21 10:07
个人简介

Linux后台服务器编程。

文章分类

全部博文(299)

文章存档

2015年(2)

2014年(297)

分类: C/C++

2014-11-09 17:44:16

1.dll的优点

代码复用是提高软件开发效率的重要途径。一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用。比较常见的例子是各种应用程序框架,ATL、MFC等,它们都以源代码的形式发布。由于这种复用是“源码级别”的,源代码完全暴露给了程序员,因而称之为“白盒复用”。“白盒复用”的缺点比较多,总结起来有4点。 
暴露了源代码;多份拷贝,造成存储浪费; 
容易与程序员的“普通”代码发生命名冲突; 
更新功能模块比较困难,不利于问题的模块化实现; 
实际上,以上4点概括起来就是“暴露的源代码”造成“代码严重耦合”。为了弥补这些不足,就提出了“二进制级别”的代码复用。使用二进制级别的代码复用一定程度上隐藏了源代码,对于缓解代码耦合现象起到了一定的作用。这样的复用被称为“黑盒复用”。 
说明:实现“黑盒复用”的途径不只dll一种,静态链接库甚至更高级的COM组件都是。

2.dll的创建

参考程序原文: 
新建“Win32项目”,选择应用程序类型为"DLL”,其他默认。添加头文件testdll.h

点击(此处)折叠或打开

  1. //testdll.h
  2. #ifdef TESTDLL_EXPORTS
  3. #define TESTDLL_API __declspec(dllexport)
  4. #else
  5. #define TESTDLL_API __declspec(dllimport)
  6. #endif
  7. namespace MathFuncs
  8. {
  9.     // This class is exported from the testdll.dll
  10.     class MyMathFuncs
  11.     {
  12.     public:
  13.         // Returns a + b
  14.         static TESTDLL_API double Add(double a, double b);

  15.         // Returns a - b
  16.         static TESTDLL_API double Subtract(double a, double b);

  17.         // Returns a * b
  18.         static TESTDLL_API double Multiply(double a, double b);

  19.         // Returns a / b
  20.         // Throws const std::invalid_argument& if b is 0
  21.         static TESTDLL_API double Divide(double a, double b);
  22.     };
  23. }


当定义了符号TESTDLL_EXPORTS,TESTDLL_API被设置为 __declspec(dllexport) 修饰符,
This modifier enables the function to be exported by the DLL so that it can be used by other applications。若未定义则TESTDLL_API被设置为__declspec(dllimport),This modifier enables the compiler to optimize the importing of the function from the DLL for use in other applications。当DLL项目生成时,TESTDLL_EXPORTS默认是定义的,所以默认设置的是__declspec(dllexport) 修饰符。 

添加cpp文件

点击(此处)折叠或打开

  1. // testdll.cpp : 定义 DLL 应用程序的导出函数。
  2. #include "stdafx.h"
  3. #include "testdll.h"
  4. #include <stdexcept>
  5. using namespace std;

  6. namespace MathFuncs
  7. {
  8.     double MyMathFuncs::Add(double a, double b)
  9.     {
  10.         return a + b;
  11.     }

  12.     double MyMathFuncs::Subtract(double a, double b)
  13.     {
  14.         return a - b;
  15.     }

  16.     double MyMathFuncs::Multiply(double a, double b)
  17.     {
  18.         return a * b;
  19.     }

  20.     double MyMathFuncs::Divide(double a, double b)
  21.     {
  22.         if (b == 0)
  23.         {
  24.             throw invalid_argument("b cannot be zero!");
  25.         }
  26.         return a / b;
  27.     }
  28. }


编译就会生成对应的dll文件,同时也会生成对应的lib文件。 

注意:a.DLL中导出函数的声明有两种方式:在函数声明中加上__declspec(dllexport);采用模块定义(.def)文件声明。详见:http://www.cnblogs.com/enterBeijingThreetimes/archive/2010/08/04/1792099.html 
b.对于C文件创建dll时或者想使用C编译器创建dll时,建议使用 extern “C” 标志,参见extern "C"的简单解析

3.dll的调用

应用程序使用DLL可以采用两种方式:一种是隐式链接(调用),另一种是显式链接。在使用DLL之前首先要知道DLL中函数的结构信息。VS在VC\bin目录下提供了一个名为Dumpbin.exe的小程序,用它可以查看DLL文件中的函数结构。两种的对比详见:http://blog.sina.com.cn/s/blog_53004b4901009h3b.html 
隐式链接采用静态加载的方式,比较简单,需要.h、.lib、.dll三件套。新建“控制台应用程序”或“空项目”。配置如下: 
项目->属性->配置属性->VC++ 目录-> 在“包含目录”里添加头文件testdll.h所在的目录 
项目->属性->配置属性->VC++ 目录-> 在“库目录”里添加头文件testdll.lib所在的目录 
项目->属性->配置属性->链接器->输入-> 在“附加依赖项”里添加“testdll.lib”(若有多个 lib 则以空格隔开) 
添加cpp文件

点击(此处)折叠或打开

  1. //mydll.cpp
  2. #include <iostream>
  3. #include "testdll.h"
  4. using namespace std;

  5. int main()
  6. {
  7.     double a = 7.4;
  8.     int b = 99;

  9.     cout << "a + b = " <<
  10.         MathFuncs::MyMathFuncs::Add(a, b) << endl;
  11.     cout << "a - b = " <<
  12.         MathFuncs::MyMathFuncs::Subtract(a, b) << endl;
  13.     cout << "a * b = " <<
  14.         MathFuncs::MyMathFuncs::Multiply(a, b) << endl;
  15.     cout << "a / b = " <<
  16.         MathFuncs::MyMathFuncs::Divide(a, b) << endl;

  17.     try
  18.     {
  19.         cout << "a / 0 = " <<
  20.             MathFuncs::MyMathFuncs::Divide(a, 0) << endl;
  21.     }
  22.     catch (const invalid_argument &e)
  23.     {
  24.         cout << "Caught exception: " << e.what() << endl;
  25.     }
  26.     return 0;
  27. }


现在可以编译通过了,但是程序运行就报错,还需要将testdll.dll复制到当前项目生成的可执行文件所在的目录。 

显式链接是应用程序在执行过程中随时可以加载DLL文件,也可以随时卸载DLL文件,这是隐式链接所无法作到的,所以显式链接具有更好的灵活性,对于解释性语言更为合适。 
新建项目,不需要特殊配置,添加cpp文件

点击(此处)折叠或打开

  1. /*
  2.  *作者:侯凯
  3.  *说明:显式调用DLL
  4.  *日期:2013-6-5
  5. */
  6. #include<Windows.h> //加载的头文件
  7. #include<iostream>
  8. using namespace std;

  9. int main()
  10. {
  11.     typedef double (*pAdd)(double a, double b);
  12.     typedef double (*pSubtract)(double a, double b);
  13.  
  14.     HMODULE hDLL = LoadLibrary("testdll.dll"); //加载dll文件
  15.     if(hDLL != NULL)
  16.     {
  17.         pAdd fp1 = pAdd(GetProcAddress(hDLL, MAKEINTRESOURCE(1))); //得到dll中的第一个函数
  18.         if(fp1 != NULL)
  19.         {
  20.             cout<<fp1(2.5, 5.5)<<endl;
  21.         }
  22.         else
  23.         {
  24.             cout<<"Cannot Find Function "<<"add"<<endl;
  25.         }
  26.         pSubtract fp2 = pSubtract(GetProcAddress(hDLL, "?Subtract@MyMathFuncs@MathFuncs@@SANNN@Z")); //得到dll中标示为"?..."的函数,C++编译器考虑了函数的参数
  27.         if(fp2 != NULL)
  28.         {
  29.             cout<<fp2(5.5, 2.5)<<endl;
  30.         }
  31.         else
  32.         {
  33.             cout<<"Cannot Find Function "<<"Subtract"<<endl;
  34.         }
  35.         FreeLibrary(hDLL);
  36.     }
  37.     else
  38.     {
  39.         std::cout<<"Cannot Find "<<"testdll"<<std::endl;
  40.     }
  41.     return 1;
  42. }


显式调用的问题:
在DLL文件中,dll工程中函数名称在编译生成DLL的过程中发生了变化(C++编译器),在DLL文件中称变化后的字符为“name标示”。GetProcAddress中第二个参数可以由DLL文件中函数的顺序获得,或者直接使用DLL文件中的”name标示”,这个标示可以通过Dumpbin.exe小程序查看。如果C++编译器下,想让函数名更规范(和原来工程中一样),具体方法详见:http://blog.csdn.net/btwsmile/article/details/6676802。 

当然,为了让函数名更规范,最常用的方式是:创建dll过程中使用C编译器来编译函数,这样DLL文件中的函数名和原dll工程中的函数名就一致了。

4.更一般的显式调用

为了解决上部分最后的问题,可以使用 extern “C” 为dll工程中的函数建立C连接,简单的示例工程如下。 
在DLL创建的工程中,添加cpp文件

点击(此处)折叠或打开

  1. /*
  2.  *作者:侯凯
  3.  *说明:创建dll,使用C接口——C编译器生成的dll中函数的"name标示"仍为addfun
  4.  *日期:2013-6-5
  5. */
  6. // cdll.cpp : 定义 DLL 应用程序的导出函数。
  7. //
  8. #include "stdafx.h"

  9. #ifdef __cplusplus // if used by C++ code
  10. extern "C" { // we need to export the C interface
  11. #endif

  12. __declspec(dllexport) int addfun(int a, int b)
  13. {
  14.         return a+b;
  15. }

  16. #ifdef __cplusplus
  17. }
  18. #endif


编译即可生成DLL文件。在dll调用工程中,添加cpp文件

点击(此处)折叠或打开

  1. /*
  2.  *作者:侯凯
  3.  *说明:显式调用dll
  4.  *日期:2013-6-5
  5. */
  6. #include <windows.h>
  7. #include <iostream>
  8. using namespace std;

  9. void main()
  10. {
  11.     typedef int(*FUNA)(int,int);
  12.     HMODULE hMod = LoadLibrary("cdll.dll");//dll路径
  13.     if (hMod)
  14.     {
  15.         FUNA addfun = (FUNA)GetProcAddress(hMod, TEXT("addfun"));//直接使用原工程函数名
  16.         if (addfun != NULL)
  17.         {
  18.             cout<<addfun(5, 4)<<endl;
  19.         }
  20.         else
  21.         {
  22.             cout<<"ERROR on GetProcAddress"<<endl;
  23.         }
  24.         FreeLibrary(hMod);
  25.     }
  26.     else
  27.         cout<<"ERROR on LoadLibrary"<<endl;
  28. }


运行,这样便可以调用dll的函数了。再进一步,上述dll文件如果通过隐式调用,利用.dll、.lib文件,调用函数应为

点击(此处)折叠或打开

  1. //隐式链接
  2. #include <iostream>
  3. #pragma comment(lib,"cdll.lib")
  4. using namespace std;

  5. extern "C" _declspec(dllimport) int addfun(int a,int b);
  6. //载入addfun函数,这里起到了.h文件的作用
  7. //dll中使用C编译器 故这里需要extern "C" 如果dll中无extern "C"
  8. //此处为:_declspec(dllimport) int addfun(int a,int b);
  9. void main()
  10. {
  11.     cout<<addfun(5,4)<<endl;
  12. }

阅读(2042) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~