DLL的创建和使用

这篇博客详细介绍了DLL(动态链接库)在Windows下的创建和使用,包括为何需要DLL、静态库与DLL的区别、如何使用DLL(显式调用与隐式调用)以及在Visual Studio 2010中创建DLL的步骤。通过DLL,可以实现代码的二进制级别复用,解决白盒复用的不足,如隐藏源代码、减少命名冲突和存储浪费等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DLL的创建和使用

参考:http://blog.youkuaiyun.com/btwsmile/article/details/6676802

           http://blog.youkuaiyun.com/hjsunj/article/details/2047376

         http://blog.youkuaiyun.com/wujian53/article/details/706975
一、为什么需要dll

代码复用是提高软件开发效率的重要途径。一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块

并在之后的项目中重复使用。比较常见的例子是各种应用程序框架,如ATL、MFC等,它们都以源代码的形式发布。由于

这种复用是“源码级别”的,源代码完全暴露给了程序员,因而称之为“白盒复用”。

“白盒复用”的缺点比较多,总结起来有4点。

1.暴露了源代码;
2.容易与程序员的“普通”代码发生命名冲突;
3.多份拷贝,造成存储浪费;
4.更新功能模块比较困难。
实际上,以上4点概括起来就是“暴露的源代码”造成“代码严重耦合”。为了弥补这些不足,提出了“二进制级别”的代码复用。

使用二进制级别的代码复用一定程度上隐藏了源代码,对于缓解代码耦合现象起到了一定的作用。这样的复用被称为“黑盒

复用”。在Windows操作系统中有两种可执行文件,其后缀名分别为.exe和.dll。它们的区别在于,.exe文件可被独立的装载于

内存中运行;.dll文件却不能,它只能被其它进程调用。然而无论什么格式,它们都是二进制文件。上面说到的“二进制级别”

的代码复用,可以使用.dll来实现。与白盒复用相比,.dll很大程度上弥补了上述4大缺陷。.dll是二进制文件,因此隐藏了源代

码;如果采用“显式调用”(后边将会提到),一般不会发生命名冲突;由于.dll是动态链接到应用程序中去的,它并不会在链

接生成程序时被原原本本拷贝进去;.dll文件相对独立的存在,因此更新功能模块是可行的。

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

二、静态库与DLL的不同之处

可执行文件的生成(Link期):前者很慢(因为要将库中的所有符号定义Link到EXE文件中),而后者很快(因为后者

    被Link的引入库文件无符号定义)可执行文件的大小:前者很大,后者很小(加上DLL的大小就和前者差不多了)

可执行文件的运行速度:前者快(直接在EXE模块的内存中查找符号),后者慢(需要在DLL模块的内存中查找,在另

    一个模块 的内存中查找自然较慢)
可共享性:前者不可共享,也就是说如果两个EXE使用了同一个静态库,那么实际在内存中存在此库的两份拷贝,而后

      者是可共享的。
可升级性:前者不可升级(因为静态库符号已经编入EXE中,要升级则EXE也需要重新编译),后者可以升级(只要接

口不变,  DLL即可被升级为不同的实现)
 
综合以上,选择静态库还是DLL
1. 静态库适于稳定的代码,而动态库则适于经常更改代码(当然接口要保持不变),当DLL更改(仅实现部分)后,

    用户不需要  重编工程,只需要使用新的Dll即可。
2. 由于静态库很吃可执行文件的生成(Link期)时间,所以如果对可执行文件的Link时间比较敏感,那么就用DLL。

三、使用DLL

1. 显式调用(也叫动态调用)
  显示调用使用API函数LoadLibrary或者MFC提供的AfxLoadLibrary将DLL加载到内存,再用GetProcAddress()在内存中获取引入函数地址,然后
  你就可以象使用本地函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的AfxLoadLibrary释放DLL。

  下面是个显示调用的例子,假定你已经有一个Test.dll,并且DLL中有个函数为void Test();

#include < iostream >
using namespace std;

typedef void(*TEST )();
int main( char argc, char* argv[] ) {         
    const char* dllName = "Test.dll";
    const char* funcName = "Test";

    HMODULE hDLL = LoadLibrary( dllName );
    if ( hDLL != NULL ) {
        TEST func = TEST( GetProcAddress( hDLL, funcName ) );
        if ( func != NULL ) {
            func();
        }
        else {
            cout << "Unable to find function /'" << funcName << "/' !" << endl;
        }
        FreeLibrary( hDLL );
    } 
    else {
        cout << "Unable to load DLL /'" << dllName << "/' !" << endl;
    }    
    return 0;
}


 

注意
1). 显示调用使用GetProcAddress,所以只能加载函数,无法加载变量和类。
2). 此外GetProcAddress是直接在.dll文件中寻找同名函数,如果DLL中的Test函数是个C++函数,那么由于在.dll文件中的
   实际文件名会被修饰(具体被修饰的规则可参考函数调用约定详解或者使用VC自带的Depends.exe查看),所以直接找Test
   是找不到的,这时必须把函数名修改为正确的被修饰后的名称,下面是一种可能(此函数在DLL中的调用约定为__cdecl):
  const char* funcName = "?Test@@YAXXZ";

2. 隐式调用(也叫静态调用)
   隐式调用必须提供DLL的头文件和引入库(可以看作轻量级的静态库(没有符号定义,但是说明了符号处于哪个DLL中))。
   有了头文件和引入库,DLL的使用就跟普通静态库的使用没啥区别,只除了DLL要和EXE一起发布。

   显示调用与隐式调用的优缺点

   显示调用使用复杂,但能更加有效地使用内存,因为DLL是在EXE运行时(run time)加载,必须由用户使用LoadLibrary和
   FreeLibrary来控制DLL从内存加载或卸载的时机。此外还可以加载其他语言编写的DLL函数。
   静态调用使用简单,但不能控制DLL加载时机,EXE加载到内存同时自动加载DLL到内存,EXE退出时DLL也被卸载

四、vs2010中创建DLL
1、File > New > Project  > Win32 > Win32 project,命名为MyDLL.
2、win32应用程序向导>下一步>勾选"DLL(D)"和"导出符号(X)",完成.
    (勾选"导出符号(X)"后,vs2010会自动生成MyDLL.h的头文件,主要包含如下内容:

 #ifdef MyDLL_EXPORTS
    #define MyDLL_API __declspec(dllexport)
    #else
    #define MyDLL_API __declspec(dllimport)
    #endif

 )

项目结构图:


3、在MyDLL.h中完成函数声明,在MyDLL.cpp中完成函数实现。此处写一个函数int add(int a,int b)为例。对DLL外部可见的函数,即DLL提供给外部调用的函数,
   需要在函数头部加上MyDLL_API(在MyDLL.h中宏定义好的),表示这个函数是可以被外部调用的。

   改好后的MyDLL.h如下:

// 下列 ifdef 块是创建使从 DLL 导出更简单的
// 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 MYDLL_EXPORTS
// 符号编译的。在使用此 DLL 的
// 任何其他项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将
// MYDLL_API 函数视为是从 DLL 导入的,而此 DLL 则将用此宏定义的
// 符号视为是被导出的。
#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif

MYDLL_API int add(int,int);//函数声明

改好后 MyDLL.cpp如下:

// MyDLL.cpp : 定义 DLL 应用程序的导出函数。
//
#include "stdafx.h"
#include "MyDLL.h"

// 这是导出函数的一个示例。
MYDLL_API int add(int a,int b)
{
 return a+b;
}


4、生成项目,在 项目目录\Debug文件下就生成了MyDLL.dll文件

  i、如果是C++函数
   此时使用MyDLL.dll,以add的名称来访问int add(int a,int b)函数时,很可能失败。原因是源码在编译生成二进制的DLL时,函数是个C++函数,
   那么由于在.dll文件中的实际函数名会被修饰,同时包含函数名和参数(以便于重载的实现)(具体被修饰的规则可参考函数调用约定详解或者

   使用VC自带的Depends.exe查看),所以直接找Test是找不到的,这时必须把函数名修改为正确的被修饰后的名称来访问,下面是一种可能的修饰后的名称):
  "?add@@YAHHH@Z"

 --使用VS2010附带工具dumpbin,查看CreateDLL.dll的导出函数名,结果如图所示:

ii、如果是C函数,可以正常调用

 虽然可以使用上面查到的DLL中的名字来调用,但很麻烦。
 简单的方法有:
 a、运用模块定义文件.def:

在项目中添加MyDLL.def文件:

内容为:
LIBRARY MyDLL
 EXPORTS
 add
 
   LIBRARY是模块定义文件必须的一部分,它告诉链接器(linker)如何命名你的DLL。EXPORTS也是模块定义文件必须的一部分,

   这部分使得该函 数可以被其它应用程序访问到并且它创建一个导入库。当你生成这个项目时,不仅是一个.dll文件被创建,而且一

   个文件扩展名为.lib的导出 库也会被创建。EXPORTS后面列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示

   要导出函数的序号为n(对于VB、PB、Delphi用户,通常使用按名称进行调用的方式,这个数关系不大,但是对于使用.lib链接的

   VC程序来说,不是按名称进行调用,而是按照这个数进行调用的,所以最好给出。)

从新生成MyDLL.dll文件,查看:

此时就可以直接用源码中的名称调用这个函数了。

b运用extern  "C"修饰函数add(). (extern  "C" 作用是,让被其修饰的函数在编译好的文件(如.dll和.lib文件)中按照C语言的方式命名)

 


 

内容概要:本文详细介绍了扫描单分子定位显微镜(scanSMLM)技术及其在三维超分辨体积成像中的应用。scanSMLM通过电调透镜(ETL)实现快速轴向扫描,结合4f检测系统将不同焦平面的荧光信号聚焦到固定成像面,从而实现快速、大视场的三维超分辨成像。文章不仅涵盖了系统硬件的设计与实现,还提供了详细的软件代码实现,包括ETL控制、3D样本模拟、体积扫描、单分子定位、3D重建分子聚类分析等功能。此外,文章还比较了循环扫描与常规扫描模式,展示了前者在光漂白效应上的优势,并通过荧光珠校准、肌动蛋白丝、线粒体网络流感A病毒血凝素(HA)蛋白聚类的三维成像实验,验证了系统的性能应用潜力。最后,文章深入探讨了HA蛋白聚类与病毒感染的关系,模拟了24小时内HA聚类的动态变化,提供了从分子到细胞尺度的多尺度分析能力。 适合人群:具备生物学、物理学或工程学背景,对超分辨显微成像技术感兴趣的科研人员,尤其是从事细胞生物学、病毒学或光学成像研究的科学家技术人员。 使用场景及目标:①理解掌握scanSMLM技术的工作原理及其在三维超分辨成像中的应用;②学习如何通过Python代码实现完整的scanSMLM系统,包括硬件控制、图像采集、3D重建数据分析;③应用于单分子水平研究细胞内结构动态过程,如病毒入侵机制、蛋白质聚类等。 其他说明:本文提供的代码不仅实现了scanSMLM系统的完整工作流程,还涵盖了多种超分辨成像技术的模拟比较,如STED、GSDIM等。此外,文章还强调了系统在硬件改动小、成像速度快等方面的优势,为研究人员提供了从理论到实践的全面指导。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值