在C++中调用DLL中的函数

2023-05-16


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_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)文件声明。详见: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中函数的结构信息。VSVC\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.55.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.52.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( 54)<<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;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在C++中调用DLL中的函数 的相关文章

随机推荐