VS2013 编写调用 动态链接库

时间:2021-01-06 15:47:40

VS2013 编写调用 动态链接库

说明: 学习资料:孙鑫C++教程19 动态链接库  。

         生成dll问题:导出函数名会发生改变问题,可采用 extern " c"或模块定义 方法

         dll加载方式:1.隐式链接 2.显示加载

         存在问题:1.编码方式 ANSI/Unicode

                          2.调用约定 (calling convention) _stdcall/_cdecl 

                         3.模块定义属性设置(未解决)

1.编写dll 文件。(编译--生成)

  新建--项目--win32项目(文件名Dll1)--DLL(空项目)--完成

1)源文件--新建项Dll1.cpp

<span style="font-size:12px;">#define Dll1_API  _declspec(dllexport)  //动态链接库本身导出
//#define Dll1_API extern "C" _declspec(dllexport)
#include "Dll1.h"
#include <WINDOWS.h>
#include <stdio.h>
int _stdcall add(int a, int b)
{
return a + b;
}
int _stdcall sub(int a, int b)
{
return a - b;
}</span>

     头文件--新建项Dll1.h

<span style="font-size:12px;">#ifdef Dll1_API//其他函数调用dll ,未定义Dll1_API ,则为 导入 add(),sub()函数。
#else
#define Dll1_API _declspec(dllimport)//_declspec为标识符
//#define Dll1_API extern "C" _declspec(dllimport)

//加extern "C",为了使得 导出后函数名字不发生改变,
//但是extern "C"只针对全局函数,类或类的成员函数不可以使用。
//调用约定(_stdcall/c约定)发生变化,即使使用 extern “C”,函数名字仍然改变

#endif // Dll1_API

Dll1_API int _stdcall add(int a, int b);
Dll1_API int _stdcall sub(int a, int b);

</span>
       Dll1.cpp  标识_declspec(dllexport)可导出全局函数add()和sub()函数.

       当其它程序调用Dll1.h头文件时,_declspec(dllimport)可将add()/sub()函数导入这些程序中.

       但是在函数导出过程中,函数名字会产生变化,如“add”函数名变为“?add@@YGHHH@Z”。在大多数情况下,并不会影响程序逻辑,但遇到跨语言编程时,就会出现错误。使用 extern "C" 可使得函数名不发生改变,但有一定的缺陷,如上述代码中的注释。

       通常解决这种问题可采用  模块定义的方法 。

以上两个程序

2)模块定义(确保导出后,函数名不发生变换)

       在项目当前文件夹下新建文本文件,后缀名改为.def(Dll2.def).

       资源文件--添加现有项--Dll2.def--编辑如下:

<span style="font-size:12px;">LIBRARY Dll2

EXPORTS
add
subtract</span>
       在VS2013还需要设置     项目--属性--链接器--输入--模块定义文件--./Dll2.def    (网上资料介绍这样配置,但是本人的程序有错,对比老师的程序也没有找到症结所在。)

      此时源文件--添加新建项Dll2.cpp。采用模块定义方式,Dll2.cpp 本身无需export出 add()和sub()函数

<span style="font-size:12px;">int /*_stdcall*/ add(int a,int b)
{
return a+b;
}

int /*_stdcall*/ sub(int a,int b)
{
return a-b;
}</span>
2.调用dll文件(测试程序执行)

   动态链接库两种加载方式

1)隐式链接

     需要.lib 文件  .h文件  .dll文件

     测试程序添加头文件

#include "..\..\Dll1\Dll1\Dll1.h"
     即上述1.1)所生成的头文件(在vs2013中,要写对路劲)

     同时将1.1)Debug后生成的.lib 文件复制到项目文件DllTest/Dll1Test文件夹下。将1.1)Debug后生成的.dll文件复制到DLLTest/Debug文件夹下。

     这样测试程序就可以正常调用动态链接库中的add() 或sub()函数了。

     测试程序部分内容:

<span style="font-size:12px;">void CDllTestDlg::OnBtnAdd() 
{
// TODO: Add your control notification handler code here
CString str;
str.Format("5+3=%d",add(5,3)); //配置好.h .lib .dll后,可直接调用add()函数.
MessageBox(str);
<span style="font-size:18px;">}</span></span>

2)动态加载

     动态加载是在使用该函数的时候才加载到内存中,较隐式链接节省空间。

     动态加载不需要手工添加.h  .lib  .dll 文件的路径,加载方法直接写在程序语句中。

     若调用动态链接库时,只提供了.dll 文件,则采用动态加载。

<span style="font-size:12px;">// TODO:  在此添加控件通知处理程序代码
HINSTANCE hInst;
hInst = LoadLibrary("Dll2.dll"); //加载动态链接库
typedef int(/*_stdcall*/ *ADDPROC)(int a, int b); // ADDPROC为函数指针类型
ADDPROC Add = (ADDPROC)GetProcAddress(hInst, "add"); //Add指向dll中add()函数的地址
//ADDPROC Add=(ADDPROC)GetProcAddress(hInst,"?add@@YAHHH@Z");//使用改变的函数名字
     //ADDPROC Add=(ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1));//通过序号调用
  if (!Add)
{
MessageBox("获取函数地址失败!");

}
CString str;
str.Format(_T("%d") ,Add(5, 3)); //Add(5,3)=add(5,3);
MessageBox(str);
FreeLibrary(hInst); //释放动态链接库</span>


存在问题解答:1.vs2013默认采用Unicode(16位)方式编码,不同于以往的ANSI(8位)编码,孙鑫老师视频中的有些代码不适用。解决方法:项目--属性--配置属性--常规--字符集--使用多字节字符集。

                         2.使用不同的调用约定(函数参数压栈顺序),动态链接库中导出函数的名字会产生不同的变化,默认的调用约定为_cdecl(加 extern “C” 有效),但是使用_stdcall,即使使用extern “C”,函数名仍会发生变化,此时可采用模块定义的方法解决。


                                                                                                                                                   菜鸟一枚 ,多多指教