zoukankan      html  css  js  c++  java
  • 转:DLL教程

    首先:DLL技术是针对C的技术,虽然也支持C++,但是对C++的支持不够好。C++对应的是COM技术。

    建议首先看一下Programming  Windows的21章,虽然没有讲MFC如何制作DLL,但是讲了一些很重要的基本概念。

    教程地址:

    http://blog.csdn.net/chenqiang35/article/details/3069382

    http://oulehui.blog.163.com/blog/static/79614698201152423656383/

    http://www.cnblogs.com/DxSoft/archive/2011/04/22/2024686.html

    笔记:环境为Win7+VS2013

    1.DLL工程的创建

    1.1.Win32  DLL项目的创建

    新建项目->VC++->Win32项目->下一步->DLL->完成

    1.2.MFC DLL项目的创建

    新建项目->VC++->MFC->MFC DLL->下一步->选择DLL类型->完成

    2.第一个Win32 DLL的制作

    2.1.创建Win32 DLL项目:AddXxx,注意Wizard自动生成了stdafx.h,AddXxx.cpp,dllmain.cpp,stdafx.cpp等文件,目前不去管他们,默认即可

    2.2.添加Add.h和Add.cpp

    Add.h

    这里的DLLEXPORT宏是借鉴Programming Windows 5th的做法(作者用的是EXPORT,但是我发现和afx某个头文件中的宏定义冲突,所以我改为了DLLEXPORT),凡是DLL内部使用的符号,函数,则头文件中其声明之前不用DLLEXPORT宏,否则头文件中声明之前要加DLLEXPORT宏。

    简单说,DLL模块的interface部分需要加DLLEXPORT声明,属于implementation的部分不应该加DLLEXPORT声明

    1 #pragma once
    2 
    3 #ifdef __cplusplus
    4 #define DLLEXPORT extern "C" __declspec (dllexport)
    5 #else
    6 #define DLLEXPORT __declspec (dllexport)
    7 #endif
    8 
    9 DLLEXPORT int add(int a, int b);

    Add.cpp

    #include "stdafx.h"
    #include "Add.h"
    
    
    int add(int x, int y)
    {
        return x + y;
    }

    注意,这里有第二种方法声明哪些部分是DLL的接口,就是用.def文件,即模块配置文件,这样你就用不着DLLEXPORT宏了,我比较偏爱这种方法(二者的比较:http://blog.chinaunix.net/uid-9681606-id-1998574.html)

    把上面跟DLLEXPORT相关的部分全部删除,然后

    添加->VC++->代码->模块定义文件->添加一个名为AddXxx的模块定义文件,内容如下

    1 LIBRARY "AddXxx"
    2 
    3 EXPORTS
    4 
    5 add @ 1

    .def文件的规则为:(更完整的建议看http://blog.163.com/hanyinlong@126/blog/static/99751486201363115639401/)

      (1)LIBRARY语句说明.def文件对应的DLL;

      (2)EXPORTS语句后列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示要导出函数的序号为n(在进行函数调用时,这个序号将发挥其作用);

      (3).def 文件中的注释由每个注释行开始处的分号 (;) 指定,且注释不能与语句共享一行。

      由此可以看出,例子中lib.def文件的含义为生成名为“dllTest”的动态链接库,导出其中的add函数,并指定add函数的序号为1。

    好,生成解决方案,生成了AddXxx.dll和AddXxx.lib

    这里的AddXxx.lib是什么?是对应的静态库吗?不是。参考Programming Windows  5th,这玩意儿叫做import library,即IL,IL里面不包含你写的可执行代码,只包含一些其他信息。一般我们生成的静态库是object library(因为他本质上就是对obj文件进行了打包封装)叫做OL,OL里面是包含你写的可执行代码的。要说明IL是拿来干嘛的,就要说到DLL的两种加载方式。

    假设你写了另一个项目叫做Test要用到AddXxx.dll,你有两种办法加载它:

    一种是配置AddXxx.dll对应的头文件的搜索路径,然后在代码中调用LoadLibrary加载AddXxx.dll,用完了以后调用FreeLibrary卸载。这叫所谓的动态调用

    另一种是配置好AddXxx.dll对应的头文件的搜索路径(包含目录)+AddXxx.dll以及AddXxx.lib的搜索路径(库目录)+链接器中配置好AddXxx.dll和AddXxx.lib(链接器/输入/附加倚赖项,延迟加载的DLL),从而程序在运行的时候自动加载之,程序结束的时候自动卸载。这叫所谓的静态调用

    静态调用就要用到IL,上面提到了静态调用需要事先在项目里配置DLL,其实不仅要配置DLL,还要配置IL,IL里面包含是用来给linker提供DLL的信息从而linker可以把这些信息写到其他DLL或者EXE中,从而实现程序运行时自动加载相应的DLL,因此你在用的时候不需要任何显式加载DLL的代码,用起来就跟使用OL一样方便,这是静态调用的优点;其缺点在于没有动态调用灵活,因为动态调用可以更精确地控制什么时候加载DLL什么时候卸载DLL。

    在编译阶段,无论是静态调用还是动态调用,都需要相应DLL的头文件;静态调用由于编译阶段就需要解析DLL中的符号(动态调用则不用,因为动态调用是用LoadLibrary这些API去加载),因此还需要IL的名称,位置,可以符号即其他信息从IL中提取出来放到生成的EXE中。

    在运行阶段(或者调试的时候),只需要DLL文件就可以了。

    编译阶段:

    动态调用的代码示例(只需要配置好头文件的位置即可):

    头文件位置的配置:项目属性包含目录中添加一项->E:vs_wkspPW_MFC_2NDAddXxxAddXxx;

    #include "Add.h"

    typedef int(*lpAddFun)(int, int); HINSTANCE hDll; lpAddFun addFun; hDll = ::LoadLibrary(_T("E:\vs_wksp\PW_MFC_2ND\AddXxx\Debug\AddXxx.dll")); addFun = (lpAddFun)::GetProcAddress(hDll, "add"); if (addFun(1, 2) == 3) MessageBox(_T("This feature is currently unimplemented. Sorry!"), _T("Error"), MB_ICONINFORMATION | MB_OK); FreeLibrary(hDll);

     这样你就可以编译过了

    静态调用的代码实例(不仅要配置好头文件的位置,还要配置好IL的位置以及IL的名称):

    如上所述配置头文件的位置

    配置IL的位置:项目属性,库目录添加一项->E:vs_wkspPW_MFC_2NDAddXxxDebug,这个目录是编译阶段用来搜索AddXxx.lib的位置的

    配置IL的名称:项目属性,链接器,输入,附加依赖项,填上AddXxx.lib

    #include "Add.h"
    
    if (add(1, 2) == 3)
        MessageBox(_T("This feature is currently unimplemented. Sorry!"),
            _T("Error"), MB_ICONINFORMATION | MB_OK);

    好,对于静态调用的代码,这样你也可以编译过了

    对于链接器中的另一个选项,延迟加载,其含义是你指定DLL文件的名字,在这些DLL中的代码只有在真正被调用到的时候才会加载该DLL,如果必要的话,你可以加上(参考:http://blog.csdn.net/panda1987/article/details/5936770)

    运行/调试阶段:

    前面说过,此阶段只要DLL文件就行了。与IL不一样,IL是你在编译的时候有用,编译完就没用了,而DLL是你的EXE在运行的时候要用的,运行的时候DLL必须存在在如下位置,从而你的EXE能找到它。

    配置DLL的搜索路径:

    你的

    1、把DLL文件与EXE放到同一个目录(亲测可行),或者直接用LoadLibrary用代码显式加载(可行)

    你在调试EXE的时候,有两个办法,一个是手工把DLL拷贝到EXE所在目录,另一个是修改项目属性->配置属性->调试->工作目录->修改为DLL所在的目录

    2、在IL指定的目录(不知道能不能行,反正我捣鼓半天没捣鼓明白到底怎么弄)

    参考http://blog.csdn.net/h57020877/article/details/5943898,你在生成DLL之前,通过配置DLL项目,使得生成的IL中指明DLL文件所在的位置,从而编译EXE的时候,IL中的信息编译到EXE中,EXE运行的时候就会自动到指定目录去加载DLL

    3、把DLL文件放到系统的SYSTEM32目录下(亲测没卵用,不知道什么情况)

    参考http://bbs.csdn.net/topics/390192334,http://bbs.csdn.net/topics/350072027,http://bbs.csdn.net/topics/390499759

    4、PATH环境变量指定的目录下(没试)

    5、.local重定向(没仔细研究)

    参考http://blog.csdn.net/wingeek/article/details/3621822

    导出DLL中的全局变量

    这里我使用DEF文件导出DLL中的一个函数一个全局变量,导入的时候用__declspec(dllimport)即可

    创建一个workspace,名为DLLDEMO,把上面创建的AddXxx项目导入这个workspace,然后再在这个workspace中新建一个TestDLL项目用于测试AddXxx.dll,配置好TestDLL项目从而它可以包含AddXxx的头文件以及link到TestDLL的import library。

    选择:项目->项目依赖项,让TestDLL依赖于AddXxx,这样在构建TestDLL的时候可以自动构建AddXxx(如果AddXxx没有保持最新的话)

    在解决方案资源管理器中双击TestDLL,选择:项目->设为启动项目。这是因为TestDLL是整个程序的入口。调试的时候就从TestDLL生成的EXE启动

    AddXxx跟上面一样,不去修改生成的stdafx.h,stdafx.cpp,dllmain.cpp等文件。添加AddXxx.h。代码如下:

    AddXxx.h

     1 #pragma once
     2 
     3 #ifdef DLL_EXPORT
     4 #define DLL_API 
     5 #else
     6 #define DLL_API __declspec (dllimport)
     7 #endif
     8 
     9 DLL_API extern int nGlobal;
    10 
    11 DLL_API int add(int a, int b);

    因为使用DEF文件对DLL的API进行导出,所以没有使用__declspec(dllexport);用__declspec(dllimport)对DLL的API进行导入,所以这里定义了宏DLL_API。在编译DLL项目的时候,去preprocessor的选项中添加上DLL_EXPORT宏,在编译客户程序的时候则不添加DLL_EXPORT宏

    AddXxx.cpp

     1 // AddXxx.cpp : 定义 DLL 应用程序的导出函数。
     2 //
     3 
     4 #include "stdafx.h"
     5 #include "AddXxx.h"
     6 
     7 
     8 
     9 int add(int x, int y)
    10 {
    11     return x + y + nGlobal;
    12 }
    13 
    14 int nGlobal = 1000;

    AddXxx.def

    1 LIBRARY AddXxx
    2 
    3 EXPORTS
    4 
    5 add @ 1
    6 
    7 nGlobal DATA

    TestDLL项目中客户程序关键代码:

    1     if (add(0, 0) == nGlobal++) {
    2         CString info;
    3         info.Format(_T("%d%s"), add(0, 0), _T("This feature is currently unimplemented. Sorry!"));
    4         MessageBox(info,
    5             _T("Error"), MB_ICONINFORMATION | MB_OK);
    6     }

    TestDLL的配置:

    1、AddXxx的头文件路径(在项目属性的VC++目录中配置)

    2、静态调用,所以配置AddXxx的import library,即AddXxx.lib(在项目属性的VC++目录中配置其路径,在链接器输入中配置AddXxx.lib)

    什么时候用静态调用,什么时候用动态调用:https://msdn.microsoft.com/en-us/library/253b8k2c.aspx

    什么时候用DEF文件,什么时候用__declspec(dllexport):https://msdn.microsoft.com/en-us/library/900axts6.aspx

    由于项目AddXxx和TestDLL都是一个workspace的,所以输出的DLL文件和EXE文件是放在一个目录下的,因此EXE文件在运行时可以找到DLL文件(这样静态调用就能正确起作用)。

  • 相关阅读:
    java基础部分的一些有意思的东西。
    antdvue按需加载插件babelpluginimport报错
    阿超的烦恼 javaScript篇
    .NET E F(Entity Framework)框架 DataBase First 和 Code First 简单用法。
    JQuery获得input ID相同但是type不同的方法
    gridview的删除,修改,数据绑定处理
    jgGrid数据格式
    Cannot read configuration file due to insufficient permissions
    Invoke action which type of result is JsonResult on controller from view using Ajax or geJSon
    Entity model数据库连接
  • 原文地址:https://www.cnblogs.com/qrlozte/p/4844429.html
Copyright © 2011-2022 走看看