1.dll的优点

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

说明:实现“黑盒复用”的途径不只dll一种,静态链接库甚至更高级的COM组件都是。

2.dll的创建

参考程序原文:http://msdn.microsoft.com/zh-cn/library/ms235636.aspx

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

//testdll.h

#ifdef TESTDLL_EXPORTS  
#define TESTDLL_API __declspec(dllexport)   
#else  
#define TESTDLL_API __declspec(dllimport)   
#endif  
namespace MathFuncs  
{  
    // This class is exported from the testdll.dll  
    class MyMathFuncs  
    {  
    public:   
        // Returns a + b  
        static TESTDLL_API double Add(double a, double b);

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

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

// Returns a / b  
        // Throws const std::invalid_argument& if b is 0  
        static TESTDLL_API double Divide(double a, double b);
    };  
}

当定义了符号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文件

// testdll.cpp : 定义 DLL 应用程序的导出函数。

#include "stdafx.h"
#include "testdll.h"  
#include <stdexcept>  
using namespace std;

namespace MathFuncs  
{  
    double MyMathFuncs::Add(double a, double b)  
    {  
        return a + b;  
    }

double MyMathFuncs::Subtract(double a, double b)  
    {  
        return a - b;  
    }

double MyMathFuncs::Multiply(double a, double b)  
    {  
        return a * b;  
    }

double MyMathFuncs::Divide(double a, double b)  
    {  
        if (b == 0)  
        {  
            throw invalid_argument("b cannot be zero!");  
        }  
        return a / b;  
    }  
}

编译就会生成对应的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"的简单解析(在C++中调用DLL中的函数(1))

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文件

//mydll.cpp

#include <iostream>  
#include "testdll.h"  
using namespace std;

int main()  
{  
    double a = 7.4;  
    int b = 99;

cout << "a + b = " <<  
        MathFuncs::MyMathFuncs::Add(a, b) << endl;  
    cout << "a - b = " <<  
        MathFuncs::MyMathFuncs::Subtract(a, b) << endl;  
    cout << "a * b = " <<  
        MathFuncs::MyMathFuncs::Multiply(a, b) << endl;  
    cout << "a / b = " <<  
        MathFuncs::MyMathFuncs::Divide(a, b) << endl;

try  
    {  
        cout << "a / 0 = " <<  
            MathFuncs::MyMathFuncs::Divide(a, 0) << endl;   
    }  
    catch (const invalid_argument &e)   
    {  
        cout << "Caught exception: " << e.what() << endl;   
    }  
    return 0;  
}

现在可以编译通过了,但是程序运行就报错,还需要将testdll.dll复制到当前项目生成的可执行文件所在的目录。 
显式链接是应用程序在执行过程中随时可以加载DLL文件,也可以随时卸载DLL文件,这是隐式链接所无法作到的,所以显式链接具有更好的灵活性,对于解释性语言更为合适。 
新建项目,不需要特殊配置,添加cpp文件

#include<Windows.h> //加载的头文件

#include<iostream>
using namespace std;

int main()  
{  
    typedef double (*pAdd)(double a, double b);
    typedef double (*pSubtract)(double a, double b);
 
    HMODULE hDLL = LoadLibrary("testdll.dll"); //加载dll文件 
    if(hDLL != NULL)  
    {  
        pAdd fp1 = pAdd(GetProcAddress(hDLL, MAKEINTRESOURCE(1))); //得到dll中的第一个函数
        if(fp1 != NULL)  
        {   
            cout<<fp1(2.5, 5.5)<<endl; 
        }  
        else  
        {  
            cout<<"Cannot Find Function "<<"add"<<endl;  
        }  
        pSubtract fp2 = pSubtract(GetProcAddress(hDLL, "?Subtract@MyMathFuncs@MathFuncs@@SANNN@Z")); //得到dll中标示为"?..."的函数,C++编译器考虑了函数的参数
        if(fp2 != NULL)  
        {  
            cout<<fp2(5.5, 2.5)<<endl;  
        }  
        else  
        {  
            cout<<"Cannot Find Function "<<"Subtract"<<endl;  
        }  
        FreeLibrary(hDLL);  
    }  
    else  
    {  
        std::cout<<"Cannot Find "<<"testdll"<<std::endl;  
    }  
    return 1;  
}

显式调用的问题:在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文件

#include "stdafx.h"


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

__declspec(dllexport) int addfun(int a, int b)
{
        return a+b;
}

#ifdef __cplusplus
}
#endif
 
编译即可生成DLL文件。在dll调用工程中,添加cpp文件
 
/*
 *作者:侯凯
 *说明:显式调用dll
 *日期:2013-6-5
*/
#include <windows.h>
#include <iostream>
using namespace std;

void main()
{
    typedef int(*FUNA)(int,int);
    HMODULE hMod = LoadLibrary("cdll.dll");//dll路径
    if (hMod)
    {
        FUNA addfun = (FUNA)GetProcAddress(hMod, TEXT("addfun"));//直接使用原工程函数名 
        if (addfun != NULL)
        {
            cout<<addfun(5, 4)<<endl;
        }
        else
        {
            cout<<"ERROR on GetProcAddress"<<endl;
        }
        FreeLibrary(hMod);
    }
    else
        cout<<"ERROR on LoadLibrary"<<endl;
}

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

//隐式链接

#include <iostream>
#pragma comment(lib,"cdll.lib")
using namespace std;

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

在C++中调用DLL中的函数相关推荐

  1. 天马行空W:在C++中调用DLL中的函数

    1.dll的优点 代码复用是提高软件开发效率的重要途径.一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用.比较常见的例子是各种应用程序框架,ATL.MFC等 ...

  2. 在C++中调用DLL中的函数(2)

    本文转自:http://blog.sina.com.cn/s/blog_53004b4901009h3b.html 应用程序使用DLL可以采用两种方式: 一种是隐式链接,另一种是显式链接.在使用DLL ...

  3. C++中调用DLL中的函数的两种方式

    一.DLL源文件: extern "C" __declspec(dllexport) int add(int a, int b) {return a + b; } 二.静态调用: ...

  4. C#调用dll中的函数

    C#调用dll中的函数 文章分类:操作系统 文章来源:http://blog.csdn.net/strmagic/archive/2007/11/02/1863462.aspx 大家在实际工作学习C# ...

  5. 通过GetProcAddress函数动态调用dll中地函数,是否必须通过extern C声明导出函数?(转)...

    通过GetProcAddress函数动态调用dll中的函数,是否必须通过extern "C"声明导出函数? [已结贴,结贴人:darongtou] 如题,网上搜了N多资料,一直找不 ...

  6. Qt调用dll中的功能函数

    http://www.cnblogs.com/hicjiajia/archive/2010/08/27/1810239.html 声明: 事先我已经自己动手写了一个简单的dll文件(myDLL.dll ...

  7. 如何调用 DLL 中的函数

     如何调用 DLL 中的函数 在 DLL工程中的 cpp中函数定义如下: extern "C" _declspec (dllexport ) int add(int a, ch ...

  8. 【转】Qt调用dll中的功能函数

    DLL 优点 ------------------------------------- 1.扩展了应用程序的特性: 2.可以用许多种编程语言来编写: 3.简化了软件项目的管理: 4.有助于节省内存: ...

  9. GetProcAddress()函数动态调用DLL中的函数,是否必须通过extern C声明导出函数?

    GetProcAddress()函数动态调用DLL中的函数,是否必须通过extern C声明导出函数? 通过GetProcAddress函数动态调用dll中的函数,是否必须通过extern " ...

最新文章

  1. 云端一体全栈解决方案
  2. 2018个人写作计划~
  3. 最长回文子串动态规划_九章算法 | 微软面试题:最长回文子串
  4. 基于 Egg.js 框架的 Node.js 服务构建之用户管理设计
  5. 万云:区块链可帮助公证行业创新改革,为互联网公证打下基础
  6. ABB RAPID 在 Notepad++ 中语法高亮的实现
  7. 分享一个JAVA专业接口开发利器,牛牛牛新鲜出炉!!!
  8. Java 内存模型(零)
  9. php过滤文件中的空行,如何从PHP文本中删除空行?
  10. 有人问曹德旺:你经历的最大的困难是什么?
  11. java 异常处理发生异常_Java中的异常处理
  12. java Http post请求发送json字符串
  13. mysql数据库过滤数据_MySQL数据库常规操作一些简单绕过过滤的方法
  14. android 二进制编辑器,二进制编辑器 (c + +)
  15. 判断一个正整数是素数还是合数
  16. 笔记本电脑键盘个别键失灵的修复方法
  17. ant下载与本地配置
  18. matlab 读取midi,matlab miditoolbox中的midi文件生成函数的改进
  19. ununtu20.04系统中如何划词翻译_接住了!有了这几个翻译软件,英语渣渣也能轻松读文献...
  20. Java8 ConcurrentHashMap的get()方法真的不需要加锁吗?

热门文章

  1. 安装linux并卸载windows,如何在安装双启动后卸载 Windows 或者 Linux
  2. centos 6.5 openldap php,centos6.5安装openldap+phpldapadmin
  3. 10投屏后没有声音_手机投屏到电视没有声音?
  4. 关于华硕主板“USB Devices Over Current Status Detected!”
  5. wumpus java_人工智能经典问题The Wumpus World-简明窗体小游戏
  6. python大作业爬虫_Python大作业---微博爬虫及简单数据分析
  7. java方法重载_Java方法的重载
  8. java lambda表达式_凯哥带你从零学大数据系列之Java篇---第二十二章:Lambda表达式...
  9. 大数据复核_【BIM技术】三维扫描结合BIM技术在结构复核中的应用
  10. mysql查询显示柱形图_Grafana配置mysql展示自定义分组柱状图(Mac)