孙鑫VC学习笔记:第十九讲 动态链接库

动态链接库
Windows API中的所有函数都包含在DLL中。其中有三个最重要的DLL,Kernel32.dll,它包含用于管理内存、进程和线程的各个函数;User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;GDI32.dll,它包含用于画图和显示文本的各个函数。
---------------------------------------------------------------------------------
静态库和动态库
静态库:函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.EXE文件);
在使用动态库的时候,往往提供两个文件:一个因入库和一个DLL。因入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,在运行时候,再去加载DLL,访问DLL中导出的函数。
---------------------------------------------------------------------------------
 
使用动态链接库的好处
可以采用多种编程语言来编写。
增强产品的功能。
提供二次开发的平台。
简化项目管理
可以节省磁盘空间和内存。
有助于资源的共享。
有助于实现应用程序的本地化
---------------------------------------------------------------------------------
动态链接库被多个进程访问:
---------------------------------------------------------------------------------
动态链接库的加载方式:1.隐式加载 和 2.显示加载
 
一、先做一个隐式加载的例子:
      1)用MFC向导新建一个Win32 Dynamic-Link Library的工程项目,名字叫做Dll1,
并且新建一个Dll1的C++源文件
      2)在Dll1.cpp文件中,编写完成加法与减法运算的函数add()和substract()
      3)编译之后,我们可以在工程目录的Debug文件夹中发现一个Dll1.dll的文件,
这就是动态链接库文件。
 
现在已经有了一个动态链接库,那么怎么让其他程序访问它呢?
     首先,动态链接库中的文件必须被导出后才能被其它程序访问。
我们可以用VC自带的工具Dumpbin来查看那些动态链接库的函数被导出了。
可在命令行下进入Debug所在目录,输入以下命令
dumpbin -exports dll.dll 
有些时候由于某种安装原因,dumpbin被认为是无效命令,可以到VC的安装目录
../Microsoft Visual Studio/VC98/Bin/下
找到VCVARS32.bat并在命令行运行,之后就能执行dumpbin命令了。
输入dumpbin -exports Dll1.dll 就可以查看函数的导出状况了,
不过我们没有看到任何导出的函数。
怎样才能让我们自己编写的函数被导出呢?
只要在每个函数定义的前面加上_declspec(dllexport)标记就行了,如下所示:
 
_declspec(dllexport) int Add(int x,int y)
{
return x+y;
}
_declspec(dllexport) int Subtract(int x,int y)
{
return x-y;
}
加了_declspec(dllexport)以后再编译一下,就会再生一个*.lib引入库文件和一个*.exp文件,里面包含的是导出的函数或者变量的符号名。而exp文件是一个输出库文件。
这时我们再一次在命令提示符下面输入命令: dumpbin -exports Dll1.dll
就会有下面的结果出现
 
 
   然后,新建一个基于对话框的MFC AppWizard 测试工程,在对话框上添加Add与SubTract按钮,
在这两个按钮中,分别调用动态链接库中所写的两个函数。
不过为了使编译器认识动态链接库的Add,Subtract,必须在之前使用两个声明:
extern int Add(int x,int y);
extern int Subtract(int x,int y);
 
接下来就可以在两个按钮中调用动态链接库的函数了,代码如下:
void CDllTestDlg::OnBtnAdd()
{
CString str;
str.Format("3+5=&d",Add(3,5));
MessageBox(str);
}
 
void CDllTestDlg::OnBtnSubtract()
{
CString str;
str.Format("5-3=%d",Subtract(5,3));
MessageBox(str);
}
 
 注意:在编译之前需要将前面编译好的Dll1.dll和Dll1.lib拷贝到DllTest项目目录下,
同时按下F7,在工程设置中的Link选项卡的Object/library modules中
(在project--->setting--->link--->Object/Library Modules)填写Dll1.lib,
否则虽然声明了动态链接库的两个函数,在编译的时候不会出错,
但是在链接的时候会找不到这两个函数,从而报错。
 
如果要查看DllTest.exe文件信息,使用命令行dumpbin -imports dlltest.exe
 
    上面说过,在调用动态链接库的两个函数之前,应该事先用extern声明一下,
其实也可以用_declspec(dllimport)标识符声明,所以我们可以用
_declspec(dllimport) int Add(int x,int y);
_declspec(dllimport) int Subtract(int x,int y);
取代
extern int Add(int x,int y);
extern int Subtract(int x,int y);
而且用这种方法有个好处,_declspec(dllimport)会告诉编译器要引用的函数是从*.lib文件中输入的,
这可以让编译器生成效率更高的代码。
---------------------------------------------------------------------------------
二、我们在编写动态链接库的时候,往往会提供一个包含导出函数的原型声明的头文件,
     可以将这个头文件提供给其他使用动态链接库的开发人员,
     他们就可以通过这个头文件获得导出函数的信息以及相关的说明文档。
     下面在动态链接库工程中添加一个这样的头文件,步骤如下:
    1)增加一个Dll1.h的头文件
    2)事实上,这个头文件是提供给客户端使用的,所以我们把声明动态链接库两个函数的任务放到这里
_declspec(dllimport) int Add(int x,int y);
_declspec(dllimport) int Subtract(int x,int y);
这样,我们只要在DllTest工程中#include这个头文件,就声明了动态链接库的两个函数。
    3)可以对这个头文件改进一下,让它既可以为客户端使用,也可以为动态链接库本身服务。
首先在Dll1.h中添加一个条件编译指令,代码如下:
 
在Dll.cpp文件,首先定义一个宏,#define DLL_API _declspec(dllexport)
然后包含这个头文件:#include "Dll.h"
接着去掉函数定义前的_declspec(dllexport)标识符,
(为什么可以去掉这个标识符,而不影响函数的导出?大家自己想想吧。)
代码如下:
 
这样做是为了方便外部程序调用同时方便内部程序使用,因为动态链接库中只有导出的函数才可以被使用,没有导出的函数在外部是看不到的,是不能被访问的
 
    4)接下来导出整个类,代码:
注意:在客户端对类成员函数的访问,仍然受制于访问权限
定义类的时候在Class 与 point之间加了DLL_API就会导出整个类。
类的实现:
    接下来我们在测试工程中新建一个按钮,调用动态链接库函数,代码如下:
---------------------------------------------------------------------------------
三、我们自己编写的到这里已经可以在C++程序中正常运行了,但是如果把这个动态链接库拿给其他语言用,比如拿给C语言调用,又会出问题,问题就在于C++导出或导入动态链接库会发生名字的改编,
在C语言中导入动态链接库时,不会正确地将名字改编回来,于是会找不到函数。
那么有没有办法使编译的时候不发生名字改编呢?
只要在定义DLL_API宏的时候,加上一句extern "C" (C要大写)就行了,如下:
#define DLL_API extern "c" _declspec(dllexport)
这样编译器就不会进行名字改编,导出的函数跟我们自己写的一模一样,
一个用C语言编写的客户端程序就可以调用这个用C++编写的动态链接库。
 
注意:用 extern "c"不让名字发生改编有两个缺点是,
一是不能导出类中的函数,只能导出全局函数;
二是如果函数使用标准调用约定_stdcall,即使用了extern "c",函数仍会发生改编。
---------------------------------------------------------------------------------
四、上面的方法可以实现编译时不发生名字改编,但是有两个缺点,特别是使用_stdcall时,
     仍然会发生名字改编。下面介绍一种模块定义文件的方式来解决这个问题:
 
   1)接下来新建一个动态链接库文件,文件名为Dll2.cpp文件代码为:
    2)新建一个模块文件Dll.def(可以先新建一个文本文档,然后将它改成Dll2.def,
注意连扩展名也要改),将其加入要项目工程当中。
    3)在VC编辑器中对Dll2.def进行编辑,添加代码
LIBRARY Dll2:指定动态链接库的内部名称,这句不是必须的
EXPORTS :即使调用_stdcall约定,也不会发生改编,而只会调用这里显示的
Add与Subtract:字符串
 
EXPORTS 语句引入了一个由一个或多个 definitions(导出的函数或数据)组成的节。每个定义必须在单独一行上。EXPORTS 关键字可以在第一个定义所在的同一行上或在前一行上。
*.def 文件可以包含一个或多个 EXPORTS 语句。
    4)下面在DllTest工程中显式地加载这个动态链接库,并且测试一下Dll2。
动态加载动态链接库要用到LoadLibrary函数;
接下来在MFC文件中改写按钮void CDllTestDlg::OnBtnAdd() 代码:
 
 注意:1.因为调用LoadLibrary时动态加载动态链接库,所以不需要头文件和.lib文件
2.如果我们在动态链接库中使用标准调用约定_stdcall来导出函数,在模块定义文件方式下,导出的函数名字不会发生改编。但是在客户端的可执行程序中,定义函数指针类型时也要采用_stdcall调用约定: typedef int ( _stdcall * ADDPROC)( int a , int b );
3.动态调用动态链接库时,最好不要发生名字改编,不然函数名字发生改编之后我们不知道函数名字就无法根据函数的名字进行调用了。当然如果知道DLL中函数的序号,这时可以使用宏MAKEINTRESOURCE把序号转变成名字,如:
ADDPROC Add=(ADDPROC)GetProcAddress( hInst,MAKEINTRESOURCE (1));

DllMain
它是一个动态链接库的可选入口点。
The DllMain function is an optional entry point into a dynamic-link library (DLL). If the function is used, it is called by the system when processes and threads are initialized and terminated, or upon calls to the LoadLibrary and FreeLibrary functions.
 
DllMain is a placeholder for the library-defined function name. You must specify the actual name you use when you build your DLL. For more information, see the documentation included with your development tools.
 
 
BOOL WINAPI DllMain(
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved
);
 
当我们的动态链接库不再使用时可以调用FreeLibrary使动态链接库使用计数减1,当使用计数为零时,系统会收回模块资源
FreeLibrary
The FreeLibrary function decrements the reference count of the loaded dynamic-link library (DLL). When the reference count reaches zero, the module is unmapped from the address space of the calling process and the handle is no longer valid.
 
BOOL FreeLibrary(
HMODULE hModule
);
 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
孙鑫vc是一种特殊的混合编程语言,它结合了C语言和Verilog语言的特点。在深入详解孙鑫vc代码之前,我们先了解一下它的一些特性。 首先,孙鑫vc具有高度的可定制性。用户可以根据自己的需求选择C语言和Verilog语言中的特性来编写代码。这种灵活性使得孙鑫vc可以适用于不同的应用领域。 其次,孙鑫vc支持并行计算。它提供了一种简单而有效的方式来利用硬件资源进行并行计算,提高程序的执行效率。 另外,孙鑫vc还具有强大的调试功能。它能够在运行时对代码进行监控和调试,帮助开发者快速定位问题并进行修复。 深入详解孙鑫vc代码包括以下几个方面: 首先,我们可以从代码的结构和组织方式入手。孙鑫vc代码一般由多个模块组成,每个模块包含了各自的功能和接口。 其次,我们需要了解代码中使用的变量和数据类型。在孙鑫vc中,可以使用C语言和Verilog语言中的数据类型,如整型、浮点型等。了解这些数据类型的使用方法和限制对理解代码非常重要。 然后,我们需要分析代码中的控制流和算法。这包括了代码中的条件语句、循环语句等,以及算法的实现细节。通过对控制流和算法的分析,我们可以更好地理解代码的逻辑和实现原理。 最后,我们还需要关注代码中的接口和数据传输方式。在孙鑫vc中,模块之间通过接口进行数据的传递和交互。了解接口的定义和使用方式对于理解代码的功能和模块之间的关系非常重要。 综上所述,深入详解孙鑫vc代码需要从代码的结构和组织方式、变量和数据类型、控制流和算法、接口和数据传输方式等多个方面进行分析和理解。通过对这些方面的研究,我们可以更好地理解孙鑫vc代码,并且能够对代码进行修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值