博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
在C++中调用DLL中的函数
阅读量:4303 次
发布时间:2019-05-27

本文共 5430 字,大约阅读时间需要 18 分钟。

1.dll的优点

代码复用是提高软件开发效率的重要途径。一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用。比较常见的例子是各种应用程序框架,
ATL
MFC
等,它们都以源代码的形式发布。由于这种复用是
源码级别
的,源代码完全暴露给了程序员,因而称之为
白盒复用
白盒复用
的缺点比较多,总结起来有
4
点。 

暴露了源代码;多份拷贝,造成存储浪费; 

容易与程序员的
普通
代码发生命名冲突; 

更新功能模块比较困难,不利于问题的模块化实现; 

实际上,以上
4
点概括起来就是
暴露的源代码
造成
代码严重耦合
。为了弥补这些不足,就提出了
二进制级别
的代码复用。使用二进制级别的代码复用一定程度上隐藏了源代码,对于缓解代码耦合现象起到了一定的作用。这样的复用被称为
黑盒复用
 

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

2.dll的创建

参考程序原文:
 

新建“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_EXPORTSTESTDLL_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)文件声明。详见: 
b.
对于C文件创建dll时或者想使用C编译器创建dll时,建议使用 extern “C” 标志,参见在C++中调用DLL中的函数(1)

 3.dll的调用

 应用程序使用DLL可以采用两种方式:一种是隐式链接(调用),另一种是显式链接。在使用DLL之前首先要知道DLL中函数的结构信息。VSVC\bin目录下提供了一个名为Dumpbin.exe的小程序,用它可以查看DLL文件中的函数结构。两种的对比详见: 

隐式链接采用静态加载的方式,比较简单,需要.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++编译器下,想让函数名更规范(和原来工程中一样),具体方法详见: 

当然,为了让函数名更规范,最常用的方式是:创建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;

}

转载地址:http://filws.baihongyu.com/

你可能感兴趣的文章
uni-app高效开发技巧
查看>>
uni-app class属性的动态绑定
查看>>
uni-app 条件渲染v-if和v-else
查看>>
uniapp 数据绑定demo
查看>>
uniapp 事件绑定@click 特殊变量 $event
查看>>
uniapp 组件注册
查看>>
javascript ES6 class类的创建和调用
查看>>
uniapp 组件父子间通讯 props emit
查看>>
uni-app slot默认插槽和具名插槽
查看>>
微信QQ所有滑块验证码统一自动化实现方案
查看>>
节点精灵入门教程
查看>>
节点精灵教程
查看>>
java脑图和java学习手册
查看>>
java类加载器ClassLoader
查看>>
常用在线工具箱
查看>>
springmvc-helloworld(idea)
查看>>
JDK下载(百度网盘)
查看>>
idea用得溜,代码才能码得快
查看>>
tornado入门看这一篇足以
查看>>
tornado同步转异步几种方式
查看>>