zoukankan      html  css  js  c++  java
  • 【读书笔记】动态链接库

    由于最近写DLL文件频繁遇到bug,今天抽空仔细研究一下动态链接库的基础知识。《VC++深入详解》当然是首选参考资料咯。

    DLL是和Windows操作系统一起诞生的,其历史悠久,经历了多年的考验,非常值得学习学习。当然这家伙也造成了DLL Hell。(陈皓注:DLL Hell——DLL灾难,就是微软的DLL升级时因为不同版本可能造成应用程序无法运行的灾难,首当其冲的是COM编程,相信大家都知道某些木马或是病毒更改了一些系统的DLL可以导致整个Windows不举,这就是DLL Hell)。不过这也提醒我们了,不了解DLL是很可怕的。

    强大的Windows API中所有的函数都包含在DLL中。最重要的有三:

    • Kernel32.dll:包含那些用于管理内存、进程和线程的函数,如CreateThread。[Base Services]
    • User32.dll:包含那些用于执行用户界面任务的函数,如CreateWindow。[Windows USER]
    • GDI32.dll:包含那些用于画图和显示文本的函数。[Graphics Device Interface]

    关于静态库和动态库,静态库就像是打包,把所有需要的东西塞进exe,一个exe走遍天下;动态库却是左右逢源,只有在运行时方才加载。我描述的很不专业,但也能看出后者是一种很聪明的做法,但是极容易出问题。但相信大多数程序员都喜欢灵活的方案,DLL就是这样流行起来的,我们可以选择任何语言进行编写DLL,不一定是VC,也可以是VB。(貌似只属于微软的语言有这种权利)而且这也符合高内聚低耦合的设计原则。当然,它还会保证你的程序不会很臃肿。(很难想象如果没有DLL,Windows会成什么样子)。因为一份DLL可以被多个进程共享。以上就是很明显的优势了。

    如果想让DLL导出一些函数,需要在函数前加入:_declspec(dllexport) 的导出标示符。书中也介绍了dumpbin命令的使用,对于调试很有帮助。

    DLL的加载有两种方式:

    • 隐式链接
    • 显示加载

    关于隐式链接的实现书上实在是讲的有点乱,我梳理了一下。一般来说我们用隐式链接的方式需要三个东西:

    1. dll
    2. lib
    3. 头文件
    #include "Dll1.h"
    #pragma comment(lib,"Dll1.lib")

    然后在需要加载的地方写以上代码即可。这是隐式链接使用dll的通常方式。这类dll应该如何写呢?

    首先我们需要声明标示符:_declspec(dllimport)。这玩意比较文明的方式是写在上述的头文件里。

    #ifdef DLL1_API
    #else
    #define  DLL1_API _declspec(dllimport)
    #endif

    然后在每个需要导出的函数前加上DLL1_API就行了,这名字是自己起的。多牛逼都可以,如WINDOWS_API。这样我们在cpp文件里只用加上:

    #define DLL1_API _declspec(dllexport)
    #include "Dll1.h"

    就可以了。这东西眼熟么?不就是上文所说的导出标示符么…

    最后一个书中着重讲解的问题,即名字改编问题的解决方案。我觉得最后那种依靠def文件的方式比较合适。这里面有两种函数,一种是普通函数,另一种是类函数。对于类函数需要做以下特殊照顾(这个书上可没写,但我仍旧拿书上代码举例)。

    LIBRARY Dll1
    
    EXPORTS
    add	@1
    subtract @2
    ?output@Point@@QAEXHH@Z @3
    ?test@Point@@QAEXXZ @4

    在def文件里加这几句话,前面和书上一样,最后两行如此丑陋,是因为它们是类函数。而后面接着的@1之类的是自定义编号。那两行丑陋的东西是如何得来的呢?

    在VC6下,settings->Link,勾上Generate mapfile。编译后,在debug下有一个.map文件,用记事本打开,找到类似下面这样的语句:

     0001:00000030       ?add@@YAHHH@Z              10001030 f   Dll1.obj
     0001:00000060       ?subtract@@YAHHH@Z         10001060 f   Dll1.obj
     0001:00000090       ?output@Point@@QAEXHH@Z    10001090 f   Dll1.obj
     0001:00000180       ?test@Point@@QAEXXZ        10001180 f   Dll1.obj

    可以知道丑陋之物是哪来的了吧!

    下面说说显示加载,也叫做“动态加载方式”。它动在什么地方呢?我觉得在于它的轻便随叫随到。轻在哪里?它只需要dll,其他都都不需要。随叫随到是指需要时才加载dll,不用了可以关闭。

    需要将dll放在工程下,然后写类似下面的代码:

    // 动态加载dll
    HINSTANCE hInst;
    hInst = LoadLibrary("Dll3.dll");
    // 定义函数指针类型
    typedef int (*ADDPROC)(int a,int b);
    // 获取dll导出函数
    /*ADDPROC Add = (ADDPROC)GetProcAddress(hInst,"?add@@YAHHH@Z");*/
    ADDPROC Add = (ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1));
    if (!Add)
    {
    	MessageBox("获取函数地址失败!");
    	return;
    }

    需要注重注意两个函数:LoadLibrary(LPCTSTR);和GetProAddress(HMODULE,LPCSTR);前者导入dll(隐式链接其实也是调用这个函数来实现的),后者获取导出函数。参数一为前者的返回值,参数二有两种方案:注释的那种是通常的方法,获取函数名。可以看到这里选取的函数名与上文中丑陋之物是一个东西,OK,这就是函数名。要保持一致。另外如果你得知函数的编号,就是上文中所述@1的字样,那么可以用MAKEINTRESOURCE宏实现转换。

    如果不再使用该dll,应该释放dll。如下:

    FreeLibrary(hInst);

    好了,动态加载其实就这么多内容,其实与隐式链接有很多想通之处。如果你的程序里大量使用dll,隐式链接一次性搞定的方案比较适合你,但如果只是偶尔一用(如曾经我在VC6里想使用CImage类的时候,在2008做了一个dll供VC6调用),那么还是推荐显示加载。

    如何选择取决于你的实际情况,永远不要脱离上下文编程!

    关于MFC DLL的内容只是略看了一下,毕竟MFC这个古老的东西日后必然会离我们而去的…

    作者:pezy 出处:http://www.cnblogs.com/pezy 欢迎转载,也请保留这段声明。谢谢!
  • 相关阅读:
    每周总结
    4月9日学习日志
    4月8日学习日志
    4月7日学习日志
    4月6日学习日志
    Cypress存取时间为10纳秒的异步SRAM
    超低功耗MCU如何降低功耗
    集成铁电存储器MCU为物联网应用提供出色性能
    读取优先和SRAM-MRAM混合结构
    磁阻式随机存储器MRAM基本原理
  • 原文地址:https://www.cnblogs.com/pezy/p/2235760.html
Copyright © 2011-2022 走看看